/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wps.gs.download; import java.io.File; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.config.GeoServer; import org.geoserver.platform.resource.Resource; import org.geoserver.platform.resource.Resources; import org.geoserver.wps.ppio.ZipArchivePPIO; import org.geoserver.wps.resource.WPSFileResource; import org.geoserver.wps.resource.WPSResourceManager; import org.geotools.process.ProcessException; import org.geotools.process.factory.DescribeParameter; import org.geotools.process.factory.DescribeProcess; import org.geotools.process.factory.DescribeResult; import org.geotools.process.gs.GSProcess; import org.geotools.resources.image.ImageUtilities; import org.geotools.util.Utilities; import org.geotools.util.logging.Logging; import org.opengis.filter.Filter; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.ProgressListener; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import com.vividsolutions.jts.geom.Geometry; /** * The main DownloadProcess class. * * This class is simply responsible for deciding who is going to take care of the request and then for putting together the final result as a zip file * adding the needed styles. * * * @author "Alessio Fabiani - alessio.fabiani@geo-solutions.it" * @author Simone Giannecchini, GeoSolutions SAS */ @SuppressWarnings("deprecation") @DescribeProcess(title = "Enterprise Download Process", description = "Downloads Layer Stream and provides a ZIP.") public class DownloadProcess implements GSProcess, ApplicationContextAware { /** The LOGGER. */ private static final Logger LOGGER = Logging.getLogger(DownloadProcess.class); /** The estimator. */ private final DownloadEstimatorProcess estimator; /** The catalog. */ private final Catalog catalog; private WPSResourceManager resourceManager; private ApplicationContext context; /** * Instantiates a new download process. * * @param geoServer the geo server * @param sendMail the send mail * @param estimator the estimator * @param resourceManager the resourceManager to track resources to be cleaned up */ public DownloadProcess(GeoServer geoServer, DownloadEstimatorProcess estimator, WPSResourceManager resourceManager) { Utilities.ensureNonNull("geoServer", geoServer); this.catalog = geoServer.getCatalog(); this.estimator = estimator; this.resourceManager = resourceManager; } /** * This process returns a zipped file containing the selected layer, cropped if needed. * * @param layerName the layer name * @param filter the filter * @param email the email * @param mimeType the output format * @param targetCRS the target crs * @param roiCRS the roi crs * @param roi the roi * @param clip the crop to geometry * @param interpolation interpolation method to use when reprojecting / scaling * @param targetSizeX the size of the target image along the X axis * @param targetSizeY the size of the target image along the Y axis * @param bandIndices the band indices selected for output, in case of raster input * @param progressListener the progress listener * @return the file * @throws ProcessException the process exception */ @DescribeResult(name = "result", description = "Zipped output files to download") public File execute( @DescribeParameter(name = "layerName", min = 1, description = "Original layer to download") String layerName, @DescribeParameter(name = "filter", min = 0, description = "Optional Vector Filter") Filter filter, @DescribeParameter(name = "outputFormat", min = 1, description = "Output Format Mime-Type") String mimeType, @DescribeParameter(name = "targetCRS", min = 0, description = "Optional Target CRS") CoordinateReferenceSystem targetCRS, @DescribeParameter(name = "RoiCRS", min = 0, description = "Optional Region Of Interest CRS") CoordinateReferenceSystem roiCRS, @DescribeParameter(name = "ROI", min = 0, description = "Optional Region Of Interest (Polygon)") Geometry roi, @DescribeParameter(name = "cropToROI", min = 0, description = "Crop to ROI") Boolean clip, @DescribeParameter(name = "interpolation", description = "Interpolation function to use when reprojecting / scaling raster data. Values are NEAREST (default), BILINEAR, BICUBIC2, BICUBIC", min = 0) Interpolation interpolation, @DescribeParameter(name = "targetSizeX", min = 0, minValue = 1, description = "X Size of the Target Image (applies to raster data only)") Integer targetSizeX, @DescribeParameter(name = "targetSizeY", min = 0, minValue = 1, description = "Y Size of the Target Image (applies to raster data only)") Integer targetSizeY, @DescribeParameter(name = "selectedBands", description = "Band Selection Indices", min = 0) int[] bandIndices, final ProgressListener progressListener) throws ProcessException { try { // // initial checks on mandatory params // // layer name if (layerName == null || layerName.length() <= 0) { throw new IllegalArgumentException("Empty or null layerName provided!"); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Download process called on resource: " + layerName); } // Default behavior is intersection if (clip == null) { clip = false; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Clipping disabled"); } } // Checking the validity of the input ROI if (roi != null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "ROI check"); } DownloadUtilities.checkPolygonROI(roi); if (roiCRS == null) { throw new IllegalArgumentException("ROI without a CRS is not usable!"); } roi.setUserData(roiCRS); } // set default interpolation value if (interpolation == null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Interpolation parameter not specified, using default (Nearest Neighbor)"); } interpolation = (Interpolation) ImageUtilities.NN_INTERPOLATION_HINT .get(JAI.KEY_INTERPOLATION); } // // do we respect limits? // if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Running the estimator"); } if (!estimator.execute(layerName, filter, targetCRS, roiCRS, roi, clip, targetSizeX, targetSizeY, bandIndices, progressListener)) { throw new IllegalArgumentException("Download Limits Exceeded. Unable to proceed!"); } // // Move on with the real code // // checking for the resources on the GeoServer catalog LayerInfo layerInfo = catalog.getLayerByName(layerName); if (layerInfo == null) { // could not find any layer ... abruptly interrupt the process throw new IllegalArgumentException("Unable to locate layer: " + layerName); } ResourceInfo resourceInfo = layerInfo.getResource(); if (resourceInfo == null) { // could not find any data store associated to the specified layer ... abruptly interrupt the process throw new IllegalArgumentException("Unable to locate ResourceInfo for layer:" + layerName); } // // Limits // DownloadServiceConfiguration limits = estimator.getDownloadServiceConfiguration(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Employing limits " + limits); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "The resource to work on is " + resourceInfo.getName()); } // CORE CODE Resource internalOutput = null; if (resourceInfo instanceof FeatureTypeInfo) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "The resource to work on is a vector layer"); } // // VECTOR // // perform the actual download of vectorial data accordingly to the request inputs internalOutput = new VectorDownload(limits, resourceManager, context).execute( (FeatureTypeInfo) resourceInfo, mimeType, roi, clip, filter, targetCRS, progressListener); } else if (resourceInfo instanceof CoverageInfo) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "The resource to work on is a raster layer"); } // // RASTER // CoverageInfo cInfo = (CoverageInfo) resourceInfo; // convert/reproject/crop if needed the coverage internalOutput = new RasterDownload(limits, resourceManager, context).execute( mimeType, progressListener, cInfo, roi, targetCRS, clip, filter, interpolation, targetSizeX, targetSizeY, bandIndices); } else { // wrong type throw new IllegalArgumentException( "Could not complete the Download Process, requested layer was of wrong type-->" + resourceInfo.getClass()); } // // Work on result // // checks if (internalOutput == null) { // wrong type throw new IllegalStateException( "Could not complete the Download Process, output file is null"); } if (!Resources.exists(internalOutput) || !Resources.canRead(internalOutput)) { // wrong type throw new IllegalStateException( "Could not complete the Download Process, output file invalid! --> " + internalOutput.path()); } // adding the style and zipping if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Preparing the result"); } // build output zip final Resource result = resourceManager.getOutputResource( resourceManager.getExecutionId(true), resourceInfo.getName() + ".zip"); try (OutputStream os1 = result.out()) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Listing files"); } // output List<File> filesToDownload = new ArrayList<File>(); filesToDownload.add(internalOutput.file()); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Collecting styles"); } // add all SLD to zip for (Resource style : DownloadUtilities.collectStyles(layerInfo)) { filesToDownload.add(style.file()); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Zipping files"); } // zip them all new ZipArchivePPIO(estimator.getDownloadServiceConfiguration() .getCompressionLevel()).encode(filesToDownload, os1); } finally { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Prepare the result for deletion"); } // mark original output for deletion resourceManager.addResource(new WPSFileResource(internalOutput)); } // // finishing // // Completed! if (progressListener != null) { progressListener.complete(); } // return return result.file(); } catch (Throwable e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Download failed"); } // FAILED // catch and rethrow but warn the listener final ProcessException processException = new ProcessException(e); if (progressListener != null) { progressListener.exceptionOccurred(processException); } throw processException; } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } }