001package dk.netarkivet.common.utils.service;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.net.MalformedURLException;
006import java.net.URI;
007import java.net.URISyntaxException;
008import java.net.URL;
009import java.net.URLEncoder;
010import java.nio.charset.StandardCharsets;
011import java.nio.file.Path;
012import java.nio.file.Paths;
013import java.util.ArrayList;
014import java.util.List;
015import java.util.regex.Pattern;
016import java.util.stream.Collectors;
017
018import org.apache.commons.io.IOUtils;
019import org.apache.http.client.methods.CloseableHttpResponse;
020import org.apache.http.client.methods.HttpUriRequest;
021import org.apache.http.impl.client.CloseableHttpClient;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import dk.netarkivet.common.CommonSettings;
026import dk.netarkivet.common.utils.HttpsClientBuilder;
027import dk.netarkivet.common.utils.Settings;
028
029/**
030 * A FileResolver client to communicate with a service implementing the FileResolver API
031 * e.g. url's like http://some.url.dk/555-.*
032 */
033public class FileResolverRESTClient implements FileResolver {
034
035    private static final Logger log = LoggerFactory.getLogger(FileResolverRESTClient.class);
036    private static final HttpsClientBuilder clientBuilder;
037
038    static {
039        String privateKeyFile = Settings.get(CommonSettings.FILE_RESOLVER_KEYFILE);
040        clientBuilder = new HttpsClientBuilder(privateKeyFile);
041    }
042
043    /**
044     * Base url for the API endpoint
045     */
046    private final URL baseUrl;
047
048    public FileResolverRESTClient() {
049        baseUrl = getBaseURL();
050    }
051
052    private URL getBaseURL() {
053        final URL baseUrl;
054        String url = Settings.get(CommonSettings.FILE_RESOLVER_BASE_URL);
055        try {
056            baseUrl = new URL(url);
057        } catch (MalformedURLException e) {
058            log.error("Malformed Url for FileResolver", e);
059            throw new RuntimeException(e);
060        }
061        return baseUrl;
062    }
063
064    @Override public List<Path> getPaths(Pattern filepattern) {
065        return getPaths(filepattern, false);
066    }
067
068    private List<Path> getPaths(Pattern filepattern, boolean exactfilename) {
069        try {
070            String pattern = filepattern.pattern();
071            URI uri = new URL(baseUrl + "/" + URLEncoder.encode(pattern, StandardCharsets.UTF_8.toString())).toURI().normalize();
072            CGIRequestBuilder requestBuilder = new CGIRequestBuilder(uri);
073            HttpUriRequest request = requestBuilder.buildFileResolverRequest(exactfilename);
074            CloseableHttpClient httpClient = clientBuilder.getHttpsClient();
075
076            try (CloseableHttpResponse httpResponse = httpClient.execute(request)) {
077                InputStream istr = httpResponse.getEntity().getContent();
078                List<String> results = IOUtils.readLines(istr);
079                return results.stream()
080                        .filter(path -> !"".equals(path.trim()))   //remove empties and whitespace
081                        .map(pathString -> Paths.get(pathString.trim()))   //convert to Path
082                        .collect(Collectors.toList());
083            }
084        } catch (IOException | URISyntaxException e) {
085            log.error("Problem resolving file " + filepattern, e);
086            return new ArrayList<>();
087        }
088    }
089
090    /**
091     * Note that the input to this method should be a literal filename but no checking or escaping is
092     * done to prevent the inclusion of regex directives.
093     * @param filename The filename to resolve.
094     * @return The first Path to a matching file or null if no such file is found
095     */
096    @Override public Path getPath(String filename) {
097        final List<Path> paths = getPaths(Pattern.compile(filename), true);
098        if (!paths.isEmpty()) {
099            return paths.get(0);
100        } else {
101            return null;
102        }
103    }
104}