org.geoserver.csw.DownloadLinkHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.csw.DownloadLinkHandler.java

Source

/* (c) 2015 Open Source Geospatial Foundation - all rights reserved
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.csw;

import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FilenameUtils;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.Request;
import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.ows.util.ResponseUtils;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.data.CloseableIterator;
import org.geotools.data.FileGroupProvider.FileGroup;
import org.geotools.data.FileResourceInfo;
import org.geotools.data.ResourceInfo;
import org.geotools.factory.GeoTools;
import org.geotools.gce.imagemosaic.Utils;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.DateRange;
import org.geotools.util.NumberRange;
import org.geotools.util.Range;
import org.geotools.util.logging.Logging;

/**
 * Class delegated to setup direct download links for a {@link CatalogInfo} 
 * instance.
 */
public class DownloadLinkHandler {

    private static Set<String> STANDARD_DOMAINS;
    public final static String RESOURCE_ID_PARAMETER = "resourceId";
    public final static String FILE_PARAMETER = "file";
    public final static String FILE_TEMPLATE = "${" + FILE_PARAMETER + "}";

    static final Logger LOGGER = Logging.getLogger(DownloadLinkHandler.class);

    static {
        STANDARD_DOMAINS = new HashSet<String>();
        STANDARD_DOMAINS.add(Utils.TIME_DOMAIN);
        STANDARD_DOMAINS.add(Utils.ELEVATION_DOMAIN);
        STANDARD_DOMAINS.add(Utils.BBOX);

    }

    /** An implementation of {@link CloseableIterator} for links creation */
    static class CloseableLinksIterator<T> implements CloseableIterator<String> {

        private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

        private SimpleDateFormat dateFormat = null;

        public CloseableLinksIterator(String baseLink, CloseableIterator<FileGroup> dataIterator) {
            this.dataIterator = dataIterator;
            this.baseLink = baseLink;
        }

        private String baseLink;

        /** The underlying iterator providing files */
        private CloseableIterator<FileGroup> dataIterator;

        @Override
        public boolean hasNext() {
            return dataIterator.hasNext();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Remove operation isn't supported");
        }

        @Override
        public String next() {
            // Get the file from the underlying iterator
            FileGroup element = dataIterator.next();
            File mainFile = element.getMainFile();
            String canonicalPath = null;
            try {
                canonicalPath = mainFile.getCanonicalPath();

                // Hash the file and setup the download link
                String hashFile = hashFile(mainFile);
                StringBuilder builder = new StringBuilder(baseLink.replace(FILE_TEMPLATE, hashFile));
                Map<String, Object> metadata = element.getMetadata();
                if (metadata != null && !metadata.isEmpty()) {

                    Set<String> keys = metadata.keySet();

                    // Set bbox in the link
                    if (keys.contains(Utils.BBOX)) {
                        Object bbox = metadata.get(Utils.BBOX);
                        appendBBOXToLink((ReferencedEnvelope) bbox, builder);
                    }
                    // Set time and elevation as first domain elements in the link
                    if (keys.contains(Utils.TIME_DOMAIN)) {
                        Object time = metadata.get(Utils.TIME_DOMAIN);
                        appendRangeToLink(Utils.TIME_DOMAIN, time, builder);
                    }
                    if (keys.contains(Utils.ELEVATION_DOMAIN)) {
                        Object elevation = metadata.get(Utils.ELEVATION_DOMAIN);
                        appendRangeToLink(Utils.ELEVATION_DOMAIN, elevation, builder);
                    }
                    for (String key : keys) {
                        if (!STANDARD_DOMAINS.contains(key)) {
                            Object additional = metadata.get(key);
                            appendRangeToLink(key, additional, builder);
                        }
                    }
                }
                return builder.toString();
            } catch (IOException e) {
                throw new RuntimeException("Unable to encode the specified file:" + canonicalPath, e.getCause());
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("Unable to encode the specified file:" + canonicalPath, e.getCause());
            }
        }

        /**
         * Append the BBOX parameter to the directDownload link
         * @param envelope
         * @param builder
         */
        private void appendBBOXToLink(ReferencedEnvelope envelope, StringBuilder builder) {
            if (envelope == null) {
                throw new IllegalArgumentException("Envelope can't be null");
            }
            builder.append("&").append(Utils.BBOX).append("=").append(envelope.getMinX()).append(",")
                    .append(envelope.getMinY()).append(",").append(envelope.getMaxX()).append(",")
                    .append(envelope.getMaxY());
        }

        /**
         * Append a coverage domain (time, elevation, custom) to the direct download link.
         * @param key the name of the parameter domain to be added
         * @param domain the value of the domain
         * @param builder the builder currently used for Link construction
         */
        private void appendRangeToLink(String key, Object domain, StringBuilder builder) {
            builder.append("&").append(key).append("=");
            if (domain instanceof DateRange) {
                // instantiate a new DateFormat instead of using a static one since
                // it's not thread safe
                if (dateFormat == null) {
                    dateFormat = new SimpleDateFormat(DATE_FORMAT);
                    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
                }
                DateRange dateRange = (DateRange) domain;
                builder.append(dateFormat.format(dateRange.getMinValue())).append("/")
                        .append(dateFormat.format(dateRange.getMaxValue()));
            } else if (domain instanceof NumberRange) {
                NumberRange numberRange = (NumberRange) domain;
                builder.append(numberRange.getMinValue()).append("/").append(numberRange.getMaxValue());
            } else if (domain instanceof Range) {
                // Generic range
                Range range = (Range) domain;
                builder.append(range.getMinValue()).append("/").append(range.getMaxValue());
            } else {
                throw new IllegalArgumentException("Domain " + domain + " isn't supported");
            }
        }

        @Override
        public void close() throws IOException {
            dataIterator.close();
        }
    }

    /** Template download link to be updated with actual values */
    protected static String LINK = "ows?service=CSW&version=${version}&request=" + "DirectDownload&"
            + RESOURCE_ID_PARAMETER + "=${nameSpace}:${layerName}&" + FILE_PARAMETER + "=" + FILE_TEMPLATE;

    /**
     * Generate download links for the specified info object.
     * 
     * @param info
     *
     */
    public CloseableIterator<String> generateDownloadLinks(CatalogInfo info) {
        Request request = Dispatcher.REQUEST.get();
        String baseURL = null;

        // Retrieve the baseURL (something like: http://host:port/geoserver/...)
        try {
            if (baseURL == null) {
                baseURL = ResponseUtils.baseURL(request.getHttpRequest());
            }

            baseURL = ResponseUtils.buildURL(baseURL, "/", null, URLType.SERVICE);
        } catch (Exception e) {
        }
        if (baseURL == null) {
            throw new IllegalArgumentException("baseURL is required to create download links");
        }
        baseURL += LINK;
        baseURL = baseURL.replace("${version}", request.getVersion());

        if (info instanceof CoverageInfo) {
            return linksFromCoverage(baseURL, (CoverageInfo) info);
        } else {
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning("Download link for vectors isn't supported." + " Returning null");
            }
        }
        return null;
    }

    /**
     * Return an {@link Iterator} containing {@link String}s representing
     * the downloadLinks associated to the provided {@link CoverageInfo} object.
     *
     * @param baseURL
     * @param coverageInfo
     *
     */
    protected CloseableIterator<String> linksFromCoverage(String baseURL, CoverageInfo coverageInfo) {
        GridCoverage2DReader reader;
        try {
            reader = (GridCoverage2DReader) coverageInfo.getGridCoverageReader(null, GeoTools.getDefaultHints());
            String name = DirectDownload.extractName(coverageInfo);
            if (reader == null) {
                throw new IllegalArgumentException("No reader available for the specified coverage: " + name);
            }
            ResourceInfo resourceInfo = reader.getInfo(name);
            if (resourceInfo instanceof FileResourceInfo) {
                FileResourceInfo fileResourceInfo = (FileResourceInfo) resourceInfo;

                // Replace the template URL with proper values
                String baseLink = baseURL.replace("${nameSpace}", coverageInfo.getNamespace().getName())
                        .replace("${layerName}", coverageInfo.getName());

                CloseableIterator<org.geotools.data.FileGroupProvider.FileGroup> dataIterator = fileResourceInfo
                        .getFiles(null);
                return new CloseableLinksIterator(baseLink, dataIterator);

            } else {
                throw new RuntimeException("Donwload links handler need to provide "
                        + "download links to files. The ResourceInfo associated with the store should be a FileResourceInfo instance");
            }
        } catch (IOException e) {
            throw new RuntimeException("Unable to generate download links", e.getCause());
        }
    }

    /**
     * Return a SHA-1 based hash for the specified file, by appending the file's base name to the hashed full path. This allows to hide the underlying
     * file system structure.
     */
    public static String hashFile(File mainFile) throws IOException, NoSuchAlgorithmException {
        String canonicalPath = mainFile.getCanonicalPath();
        String mainFilePath = FilenameUtils.getPath(canonicalPath);

        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(mainFilePath.getBytes());
        return Hex.encodeHexString(md.digest()) + "-" + mainFile.getName();
    }

    /**
     * Given a file download link, extract the link with no file references, used to 
     * request the full layer download.
     * 
     *
     */
    public String extractFullDownloadLink(String link) {
        int resourceIdIndex = link.indexOf(RESOURCE_ID_PARAMETER);
        int nextParamIndex = link.indexOf("&" + FILE_PARAMETER, resourceIdIndex);
        return link.substring(0, nextParamIndex);
    }
}