/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.constellation.wps.ws;
import com.vividsolutions.jts.geom.Geometry;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.xml.MarshallerPool;
import org.constellation.ServiceDef;
import org.constellation.configuration.ConfigurationException;
import org.constellation.configuration.Process;
import org.constellation.configuration.ProcessContext;
import org.constellation.configuration.ProcessFactory;
import org.constellation.configuration.WebdavContext;
import org.constellation.dto.Details;
import org.constellation.process.ConstellationProcessFactory;
import org.constellation.process.service.RestartServiceDescriptor;
import org.constellation.provider.DataProvider;
import org.constellation.provider.DataProviders;
import org.constellation.security.SecurityManagerHolder;
import org.constellation.wps.utils.WPSUtils;
import org.constellation.wps.ws.rs.WPSService;
import org.constellation.ws.AbstractWorker;
import org.constellation.ws.CstlServiceException;
import org.constellation.ws.WSEngine;
import org.constellation.ws.Worker;
import org.geotoolkit.feature.ComplexAttribute;
import org.geotoolkit.feature.Feature;
import org.geotoolkit.feature.type.FeatureType;
import org.geotoolkit.geometry.isoonjts.GeometryUtils;
import org.geotoolkit.gml.JTStoGeometry;
import org.geotoolkit.ows.xml.v110.AllowedValues;
import org.geotoolkit.ows.xml.v110.AnyValue;
import org.geotoolkit.ows.xml.v110.BoundingBoxType;
import org.geotoolkit.ows.xml.v110.CodeType;
import org.geotoolkit.ows.xml.v110.ExceptionReport;
import org.geotoolkit.ows.xml.v110.LanguageStringType;
import org.geotoolkit.ows.xml.v110.OperationsMetadata;
import org.geotoolkit.ows.xml.v110.ServiceIdentification;
import org.geotoolkit.ows.xml.v110.ServiceProvider;
import org.geotoolkit.parameter.Parameters;
import org.geotoolkit.process.ProcessDescriptor;
import org.geotoolkit.process.ProcessException;
import org.geotoolkit.process.ProcessFinder;
import org.geotoolkit.process.ProcessingRegistry;
import org.geotoolkit.referencing.CRS;
import org.geotoolkit.util.Exceptions;
import org.geotoolkit.util.FileUtilities;
import org.geotoolkit.utility.parameter.ExtendedParameterDescriptor;
import org.geotoolkit.wps.converters.WPSConvertersUtils;
import org.geotoolkit.wps.io.WPSIO;
import org.geotoolkit.wps.io.WPSMimeType;
import org.geotoolkit.wps.xml.WPSMarshallerPool;
import org.geotoolkit.wps.xml.v100.CRSsType;
import org.geotoolkit.wps.xml.v100.ComplexDataType;
import org.geotoolkit.wps.xml.v100.DataType;
import org.geotoolkit.wps.xml.v100.DescribeProcess;
import org.geotoolkit.wps.xml.v100.DocumentOutputDefinitionType;
import org.geotoolkit.wps.xml.v100.Execute;
import org.geotoolkit.wps.xml.v100.ExecuteResponse;
import org.geotoolkit.wps.xml.v100.ExecuteResponse.ProcessOutputs;
import org.geotoolkit.wps.xml.v100.GetCapabilities;
import org.geotoolkit.wps.xml.v100.InputDescriptionType;
import org.geotoolkit.wps.xml.v100.InputReferenceType;
import org.geotoolkit.wps.xml.v100.InputType;
import org.geotoolkit.wps.xml.v100.LiteralDataType;
import org.geotoolkit.wps.xml.v100.LiteralInputType;
import org.geotoolkit.wps.xml.v100.LiteralOutputType;
import org.geotoolkit.wps.xml.v100.OutputDataType;
import org.geotoolkit.wps.xml.v100.OutputDefinitionType;
import org.geotoolkit.wps.xml.v100.OutputDefinitionsType;
import org.geotoolkit.wps.xml.v100.OutputDescriptionType;
import org.geotoolkit.wps.xml.v100.OutputReferenceType;
import org.geotoolkit.wps.xml.v100.ProcessDescriptionType;
import org.geotoolkit.wps.xml.v100.ProcessDescriptions;
import org.geotoolkit.wps.xml.v100.ProcessFailedType;
import org.geotoolkit.wps.xml.v100.ProcessOfferings;
import org.geotoolkit.wps.xml.v100.ResponseDocumentType;
import org.geotoolkit.wps.xml.v100.ResponseFormType;
import org.geotoolkit.wps.xml.v100.StatusType;
import org.geotoolkit.wps.xml.v100.SupportedCRSsType;
import org.geotoolkit.wps.xml.v100.WPSCapabilitiesType;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.geometry.Envelope;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.FactoryException;
import org.opengis.util.NoSuchIdentifierException;
import javax.measure.converter.UnitConverter;
import javax.measure.unit.Unit;
import javax.xml.bind.JAXBException;
import java.io.File;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import static org.constellation.api.CommonConstants.DEFAULT_CRS;
import static org.constellation.api.QueryConstants.ACCEPT_VERSIONS_PARAMETER;
import static org.constellation.api.QueryConstants.SERVICE_PARAMETER;
import static org.constellation.api.QueryConstants.VERSION_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.IDENTIFER_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.LANGUAGE_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.WPS_1_0_0;
import static org.constellation.wps.ws.WPSConstant.WPS_LANG;
import static org.constellation.wps.ws.WPSConstant.WPS_SERVICE;
import static org.geotoolkit.ows.xml.OWSExceptionCode.INVALID_PARAMETER_VALUE;
import static org.geotoolkit.ows.xml.OWSExceptionCode.MISSING_PARAMETER_VALUE;
import static org.geotoolkit.ows.xml.OWSExceptionCode.NO_APPLICABLE_CODE;
import static org.geotoolkit.ows.xml.OWSExceptionCode.OPERATION_NOT_SUPPORTED;
import static org.geotoolkit.ows.xml.OWSExceptionCode.STORAGE_NOT_SUPPORTED;
import static org.geotoolkit.ows.xml.OWSExceptionCode.VERSION_NEGOTIATION_FAILED;
import org.geotoolkit.processing.AbstractProcess;
/**
* WPS worker.Compute response of getCapabilities, DescribeProcess and Execute requests.
*
* @author Quentin Boileau
*/
public class WPSWorker extends AbstractWorker {
/**
* Supported CRS.
*/
private static final SupportedCRSsType WPS_SUPPORTED_CRS;
static {
WPS_SUPPORTED_CRS = new SupportedCRSsType();
//Default CRS.
final SupportedCRSsType.Default defaultCRS = new SupportedCRSsType.Default();
defaultCRS.setCRS(DEFAULT_CRS.get(0));
WPS_SUPPORTED_CRS.setDefault(defaultCRS);
//Supported CRS.
final CRSsType supportedCRS = new CRSsType();
supportedCRS.getCRS().addAll(DEFAULT_CRS);
WPS_SUPPORTED_CRS.setSupported(supportedCRS);
}
/**
* Timeout in seconds use to kill long process execution in synchronous mode.
*/
private static final int TIMEOUT = 30;
private static final String SCHEMA_FOLDER_NAME = "/schemas";
private static final String WMS_SUPPORTED = "WMS_SUPPORTED";
/**
* Try to create temporary directory.
*/
private final boolean supportStorage;
/**
* Path where output file will be saved.
*/
private final String webdavFolderPath;
private final String webdavName;
private final boolean isTmpWebDav;
/**
* WebDav URL.
*/
private String webdavURL;
private final String schemaFolder;
private String schemaURL;
/**
* WMS link attributes
*/
private boolean wmsSupported = false;
private String wmsInstanceName;
private String wmsInstanceURL;
private String wmsProviderId;
private String fileCoverageStorePath;
/**
* WPS context configuration.
*/
private ProcessContext context;
/**
* List of process descriptors available.
*/
private final List<ProcessDescriptor> processDescriptorList = new ArrayList<>();
/**
* Constructor.
*
* @param id
*/
public WPSWorker(final String id) {
super(id, ServiceDef.Specification.WPS);
try {
final Object obj = serviceBusiness.getConfiguration("WPS", id);
if (obj instanceof ProcessContext) {
context = (ProcessContext) obj;
applySupportedVersion();
isStarted = true;
} else {
startError = "The process context File does not contain a ProcessContext object";
isStarted = false;
LOGGER.log(Level.WARNING, "\nThe worker ({0}) is not working!\nCause: " + startError, id);
}
} catch (ConfigurationException ex) {
startError = ex.getMessage();
isStarted = false;
LOGGER.log(Level.WARNING, "\nThe worker ({0}) is not working!\nCause: " + startError, id);
} catch (CstlServiceException ex) {
startError = "Error applying supported versions : " + ex.getMessage();
isStarted = false;
LOGGER.log(Level.WARNING, "\nThe worker ({0}) is not working!\nCause: " + startError, id);
}
webdavName = "wps-"+ id;
if (context != null && context.getWebdavDirectory() != null) {
webdavFolderPath = context.getWebdavDirectory();
isTmpWebDav = false;
} else {
isTmpWebDav = true;
final File tmpFolder = new File(System.getProperty("java.io.tmpdir"), webdavName);
if (!tmpFolder.isDirectory()) {
final boolean created = tmpFolder.mkdirs();
if (created) {
tmpFolder.deleteOnExit();
} else {
LOGGER.log(Level.WARNING, "Ressource folder for WPS services cannot be created. " +
"It makes WPS unable to use reference parameters. You should restart server or create manually following directory : "
+ tmpFolder.getAbsolutePath());
}
}
webdavFolderPath = tmpFolder.getAbsolutePath();
}
//Configure the directory to store parameters schema into.
File schemaLoc = new File(webdavFolderPath + SCHEMA_FOLDER_NAME);
schemaLoc.mkdir();
if(schemaLoc.exists()) {
schemaFolder = schemaLoc.getAbsolutePath();
} else {
schemaFolder = webdavFolderPath;
}
this.webdavURL = null; //initialize on WPS execute request.
//create new WebDav instance
final boolean webdav = createWebDav();
if (!webdav) {
this.supportStorage = false;
LOGGER.log(Level.WARNING, "\nThe worker ({0}) does not support stockage!\nCause: Error during WPS WebDav service creation.", id);
} else {
this.supportStorage = true;
}
//WMS link
if (context.getWmsInstanceName() != null) {
this.wmsInstanceName = context.getWmsInstanceName();
if (context.getFileCoverageProviderId() != null) {
this.wmsProviderId = context.getFileCoverageProviderId();
DataProvider provider = null;
for (DataProvider p : DataProviders.getInstance().getProviders()) {
if (p.getId().equals(this.wmsProviderId)) {
provider = p;
break;
}
}
if(WSEngine.getInstance("WMS", wmsInstanceName) == null || provider == null) {
startError = "Linked WMS instance is not found or FileCoverageStore not defined.";
} else {
final ParameterValue pathParam = (ParameterValue)Parameters.search(provider.getSource(), "path",3).get(0);
this.fileCoverageStorePath = ((URL) pathParam.getValue()).getPath();
final File dir = new File(this.fileCoverageStorePath);
dir.mkdirs();
this.wmsSupported = true;
}
} else {
startError = "Linked provider identifier name is not defined.";
}
} else {
startError = "Linked WMS instance name is not defined.";
}
if (!wmsSupported) {
LOGGER.log(Level.WARNING, "\nThe WPS worker ({0}) don\'t support WMS outputs : \n " + startError, id);
}
fillProcessList();
if (isStarted) {
LOGGER.log(Level.INFO, "WPS worker {0} running", id);
}
}
/**
* Create process list from context file.
*/
private void fillProcessList() {
if (context == null || context.getProcesses() == null) {
return;
}
// Load all processes from all factory
if (Boolean.TRUE.equals(context.getProcesses().getLoadAll())) {
LOGGER.info("Loading all process");
final Iterator<ProcessingRegistry> factoryIte = ProcessFinder.getProcessFactories();
while (factoryIte.hasNext()) {
final ProcessingRegistry factory = factoryIte.next();
for (final ProcessDescriptor descriptor : factory.getDescriptors()) {
if (WPSUtils.isSupportedProcess(descriptor)) {
processDescriptorList.add(descriptor);
} else {
LOGGER.log(Level.WARNING, "Process {0}:{1} not supported.",
new Object[] {descriptor.getIdentifier().getAuthority().getTitle().toString(),descriptor.getIdentifier().getCode()});
}
}
}
} else {
for (final ProcessFactory processFactory : context.getProcessFactories()) {
final ProcessingRegistry factory = ProcessFinder.getProcessFactory(processFactory.getAutorityCode());
if (factory != null) {
if (Boolean.TRUE.equals(processFactory.getLoadAll())) {
LOGGER.log(Level.INFO, "loading all process for factory:{0}", processFactory.getAutorityCode());
for (final ProcessDescriptor descriptor : factory.getDescriptors()) {
if (WPSUtils.isSupportedProcess(descriptor)) {
processDescriptorList.add(descriptor);
} else {
LOGGER.log(Level.WARNING, "Process {0}:{1} not supported.",
new Object[] {descriptor.getIdentifier().getAuthority().getTitle().toString(),descriptor.getIdentifier().getCode()});
}
}
} else {
for (final Process process : processFactory.getInclude().getProcess()) {
try {
final ProcessDescriptor desc = factory.getDescriptor(process.getId());
if (desc != null) {
if (WPSUtils.isSupportedProcess(desc)) {
processDescriptorList.add(desc);
} else {
LOGGER.log(Level.WARNING, "Process {0}:{1} not supported.",
new Object[] {desc.getIdentifier().getAuthority().getTitle().toString(),desc.getIdentifier().getCode()});
}
}
} catch (NoSuchIdentifierException ex) {
LOGGER.log(Level.WARNING, "Unable to find a process named:" + process.getId() + " in factory " + processFactory.getAutorityCode(), ex);
}
}
}
} else {
LOGGER.log(Level.WARNING, "No process factory found for authorityCode:{0}", processFactory.getAutorityCode());
}
}
}
for(ProcessDescriptor desc : processDescriptorList) {
try {
checkForSchemasToStore(desc);
} catch (JAXBException ex) {
LOGGER.log(Level.WARNING, "Process " + desc.getDisplayName() + " can't be added. Needed schemas can't be build.", ex);
processDescriptorList.remove(desc);
continue;
}
}
LOGGER.log(Level.INFO, "{0} processes loaded.", processDescriptorList.size());
}
@Override
protected MarshallerPool getMarshallerPool() {
return WPSMarshallerPool.getInstance();
}
@Override
public void destroy() {
super.destroy();
//Delete recursively temporary directory.
if (isTmpWebDav) {
try {
FileUtilities.deleteDirectory(new File(webdavFolderPath));
serviceBusiness.delete("webdav", webdavName);
} catch (ConfigurationException ex) {
LOGGER.log(Level.WARNING, "Erro while deleting temporary webdav service", ex);
}
}
}
/**
* Update the current WMS URL based on the current service URL.
*/
private void updateWMSURL() {
String webappURL = getServiceUrl();
if (webappURL != null) {
if (webappURL.contains("/wps/"+getId())) {
this.wmsInstanceURL = webappURL.replace("/wps/"+getId(), "/wms/"+this.wmsInstanceName);
} else {
LOGGER.log(Level.WARNING, "Wrong service URL.");
}
} else {
LOGGER.log(Level.WARNING, "Service URL undefined.");
}
}
/**
* Update the current WebDav URL based on the current service URL.
* TODO find a better way to build webdavURL
*/
private void updateWebDavURL() {
String webappURL = getServiceUrl();
if (webappURL != null) {
final int index = webappURL.indexOf("/wps/" + getId());
if (index != -1) {
webappURL = webappURL.substring(0, index);
if (webappURL.contains("/WS")) {
webappURL = webappURL.substring(0, webappURL.indexOf("/WS"));
}
this.webdavURL = webappURL + "/webdav/" + webdavName;
} else {
LOGGER.log(Level.WARNING, "Wrong service URL.");
}
} else {
LOGGER.log(Level.WARNING, "Service URL undefined.");
}
}
/**
* Create a new configuration of webdav servlet if not exist and make
* directory where temporary files will be stored.
*
* @return false if something went wrong.
*/
private boolean createWebDav() {
final File tmpDir = new File(webdavFolderPath);
if (!tmpDir.isDirectory()) {
tmpDir.mkdirs();
}
//configure webdav if not exist
try {
Object configuration = serviceBusiness.getConfiguration("webdav", webdavName);
if (configuration == null) {
final WebdavContext webdavCtx = new WebdavContext(webdavFolderPath);
webdavCtx.setId(webdavName);
serviceBusiness.create("webdav", webdavName, webdavCtx, new Details());
// at startup, webdav service may be started after WPS
if (WSEngine.isSetService("WEBDAV")) {
final Worker worker = WSEngine.buildWorker("WEBDAV", webdavName);
WSEngine.addServiceInstance("WEBDAV", webdavName, worker);
}
}
} catch (ConfigurationException ex) {
LOGGER.log(Level.WARNING, "Error during WebDav configuration", ex);
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////
// GetCapabilities
//////////////////////////////////////////////////////////////////////
/**
* GetCapabilities request
*
* @param request request
* @return WPSCapabilitiesType
* @throws CstlServiceException
*/
public WPSCapabilitiesType getCapabilities(final GetCapabilities request) throws CstlServiceException {
isWorking();
//check SERVICE=WPS
if (!request.getService().equalsIgnoreCase(WPS_SERVICE)) {
throw new CstlServiceException("The parameter " + SERVICE_PARAMETER + " must be specified as WPS",
INVALID_PARAMETER_VALUE, SERVICE_PARAMETER.toLowerCase());
}
//check LANGUAGE=en-EN
if (request.getLanguage() != null && !request.getLanguage().equalsIgnoreCase(WPS_LANG)) {
throw new CstlServiceException("The specified " + LANGUAGE_PARAMETER + " is not handled by the service. ",
INVALID_PARAMETER_VALUE, LANGUAGE_PARAMETER.toLowerCase());
}
//check VERSION
List<String> versionsAccepted = null;
if (request.getAcceptVersions() != null) {
versionsAccepted = request.getAcceptVersions().getVersion();
}
boolean versionSupported = false;
if (versionsAccepted != null) {
if (versionsAccepted.contains(ServiceDef.WPS_1_0_0.version.toString())) {
versionSupported = true;
}
}
//if versionAccepted parameter is not define return the last getCapabilities
if (versionsAccepted == null || versionSupported) {
//set the current updateSequence parameter
final boolean returnUS = returnUpdateSequenceDocument(request.getUpdateSequence());
if (returnUS) {
return new WPSCapabilitiesType("1.0.0", getCurrentUpdateSequence());
}
final Object cachedCapabilities = getCapabilitiesFromCache("1.0.0", null);
if (cachedCapabilities != null) {
return (WPSCapabilitiesType) cachedCapabilities;
}
// We unmarshall the static capabilities document.
final Details skeleton = getStaticCapabilitiesObject("WPS", null);
final WPSCapabilitiesType staticCapabilities = (WPSCapabilitiesType) WPSConstant.createCapabilities("1.0.0", skeleton);
final ServiceIdentification si = staticCapabilities.getServiceIdentification();
final ServiceProvider sp = staticCapabilities.getServiceProvider();
final OperationsMetadata om = (OperationsMetadata) WPSConstant.OPERATIONS_METADATA.clone();
om.updateURL(getServiceUrl());
final ProcessOfferings offering = new ProcessOfferings();
for (final ProcessDescriptor procDesc : processDescriptorList) {
offering.getProcess().add(WPSUtils.generateProcessBrief(procDesc));
}
final org.geotoolkit.wps.xml.v100.Languages languages = new org.geotoolkit.wps.xml.v100.Languages("en-EN", Arrays.asList("en-EN"));
final WPSCapabilitiesType response = new WPSCapabilitiesType(si, sp, om, "1.0.0", getCurrentUpdateSequence(), offering, languages, null);
putCapabilitiesInCache("1.0.0", null, response);
return response;
} else {
throw new CstlServiceException("The specified " + ACCEPT_VERSIONS_PARAMETER + " numbers are not handled by the service.",
VERSION_NEGOTIATION_FAILED, ACCEPT_VERSIONS_PARAMETER.toLowerCase());
}
}
public static Map<String, Object> buildParametersMap(final String webdavURL, final String webdavFolderPath, final String wmsInstanceName,
final String wmsInstanceURL, final String fileCoverageStorePath, final String wmsProviderId,
final String layerName, final Boolean wmsSupported) {
final Map<String, Object> parameters = new HashMap<>();
parameters.put(WPSConvertersUtils.OUT_STORAGE_DIR, webdavFolderPath);
parameters.put(WPSConvertersUtils.OUT_STORAGE_URL, webdavURL);
parameters.put(WPSConvertersUtils.WMS_INSTANCE_NAME, wmsInstanceName);
parameters.put(WPSConvertersUtils.WMS_INSTANCE_URL, wmsInstanceURL);
parameters.put(WPSConvertersUtils.WMS_STORAGE_DIR, fileCoverageStorePath);
parameters.put(WPSConvertersUtils.WMS_STORAGE_ID, wmsProviderId);
parameters.put(WPSConvertersUtils.WMS_LAYER_NAME, layerName);
parameters.put(WMS_SUPPORTED, wmsSupported);
return parameters;
}
//////////////////////////////////////////////////////////////////////
// DescribeProcess
//////////////////////////////////////////////////////////////////////
/**
* Describe process request.
*
* @param request request
* @return ProcessDescriptions
* @throws CstlServiceException
*
*/
public ProcessDescriptions describeProcess(final DescribeProcess request) throws CstlServiceException {
isWorking();
//check SERVICE=WPS
if (!request.getService().equalsIgnoreCase(WPS_SERVICE)) {
throw new CstlServiceException("The parameter " + SERVICE_PARAMETER + " must be specified as WPS",
INVALID_PARAMETER_VALUE, SERVICE_PARAMETER.toLowerCase());
}
//check LANGUAGE=en-EN
if (request.getLanguage() != null && !request.getLanguage().equalsIgnoreCase(WPS_LANG)) {
throw new CstlServiceException("The specified " + LANGUAGE_PARAMETER + " is not handled by the service. ",
INVALID_PARAMETER_VALUE, LANGUAGE_PARAMETER.toLowerCase());
}
//check mandatory version is not missing.
if (request.getVersion() == null || request.getVersion().toString().isEmpty()) {
throw new CstlServiceException("The parameter " + VERSION_PARAMETER + " must be specified.",
MISSING_PARAMETER_VALUE, VERSION_PARAMETER.toLowerCase());
}
//check VERSION=1.0.0
if (request.getVersion().toString().equals(ServiceDef.WPS_1_0_0.version.toString())) {
return describeProcess100((org.geotoolkit.wps.xml.v100.DescribeProcess) request);
} else {
throw new CstlServiceException("The specified " + VERSION_PARAMETER + " number is not handled by the service.",
VERSION_NEGOTIATION_FAILED, VERSION_PARAMETER.toLowerCase());
}
}
/**
* Describe a process in WPS v1.0.0.
*
* @param request request
* @return ProcessDescriptions
* @throws CstlServiceException
*/
private ProcessDescriptions describeProcess100(DescribeProcess request) throws CstlServiceException {
//needed to get the public adress of generated schemas (for feature parameters).
updateWebDavURL();
schemaURL = schemaFolder.replace(webdavFolderPath, webdavURL);
//check mandatory IDENTIFIER is not missing.
if (request.getIdentifier() == null || request.getIdentifier().isEmpty()) {
throw new CstlServiceException("The parameter " + IDENTIFER_PARAMETER + " must be specified.",
MISSING_PARAMETER_VALUE, IDENTIFER_PARAMETER.toLowerCase());
}
final ProcessDescriptions descriptions = new ProcessDescriptions();
descriptions.setLang(WPS_LANG);
descriptions.setService(WPS_SERVICE);
descriptions.setVersion(WPS_1_0_0);
for (final CodeType identifier : request.getIdentifier()) {
// Find the process
final ProcessDescriptor processDesc = WPSUtils.getProcessDescriptor(identifier.getValue());
if (!WPSUtils.isSupportedProcess(processDesc)) {
throw new CstlServiceException("Process " + identifier.getValue() + " not supported by the service.",
INVALID_PARAMETER_VALUE, IDENTIFER_PARAMETER.toLowerCase());
}
final ProcessDescriptionType descriptionType = new ProcessDescriptionType();
descriptionType.setIdentifier(identifier); //Process Identifier
descriptionType.setTitle(WPSUtils.buildProcessTitle(processDesc)); //Process Title
descriptionType.setAbstract(WPSUtils.capitalizeFirstLetter(processDesc.getProcedureDescription().toString())); //Process abstract
descriptionType.setProcessVersion(WPS_1_0_0); //Process version
descriptionType.setWSDL(null); //TODO WSDL
descriptionType.setStatusSupported(true);
descriptionType.setStoreSupported(supportStorage);
// Get process input and output descriptors
final ParameterDescriptorGroup input = processDesc.getInputDescriptor();
final ParameterDescriptorGroup output = processDesc.getOutputDescriptor();
///////////////////////////////
// Process Input parameters
///////////////////////////////
final ProcessDescriptionType.DataInputs dataInputs = new ProcessDescriptionType.DataInputs();
for (final GeneralParameterDescriptor param : input.descriptors()) {
/*
* Whatever the parameter type is, we prepare the name, title, abstract and multiplicity parts.
*/
final InputDescriptionType in = new InputDescriptionType();
// Parameter informations
in.setIdentifier(new CodeType(WPSUtils.buildProcessIOIdentifiers(processDesc, param, WPSIO.IOType.INPUT)));
in.setTitle(WPSUtils.capitalizeFirstLetter(param.getName().getCode()));
if (param.getRemarks() != null) {
in.setAbstract(WPSUtils.capitalizeFirstLetter(param.getRemarks().toString()));
} else {
in.setAbstract(WPSUtils.capitalizeFirstLetter("No description available"));
}
//set occurs
in.setMaxOccurs(BigInteger.valueOf(param.getMaximumOccurs()));
in.setMinOccurs(BigInteger.valueOf(param.getMinimumOccurs()));
// If the Parameter Descriptor isn't a ParameterDescriptorGroup
if (param instanceof ParameterDescriptor) {
final ParameterDescriptor paramDesc = (ParameterDescriptor) param;
// Input class
final Class clazz = paramDesc.getValueClass();
// BoundingBox type
if (WPSIO.isSupportedBBoxInputClass(clazz)) {
in.setBoundingBoxData(WPS_SUPPORTED_CRS);
//Complex type (XML, ...)
} else if (WPSIO.isSupportedComplexInputClass(clazz)) {
Map<String, Object> userData = null;
if (paramDesc instanceof ExtendedParameterDescriptor) {
userData = ((ExtendedParameterDescriptor) paramDesc).getUserObject();
}
in.setComplexData(WPSUtils.describeComplex(clazz, WPSIO.IOType.INPUT, WPSIO.FormChoice.COMPLEX, userData));
//Reference type (XML, ...)
} else if (WPSIO.isSupportedLiteralInputClass(clazz)) {
final LiteralInputType literal = new LiteralInputType();
if (paramDesc.getDefaultValue() != null) {
literal.setDefaultValue(paramDesc.getDefaultValue().toString()); //default value if enable
}
if (paramDesc.getUnit() != null) {
literal.setUOMs(WPSUtils.generateUOMs(paramDesc));
}
//AllowedValues setted
if (paramDesc.getValidValues() != null && !paramDesc.getValidValues().isEmpty()) {
literal.setAllowedValues(new AllowedValues(paramDesc.getValidValues()));
} else {
literal.setAnyValue(new AnyValue());
}
literal.setValuesReference(null);
literal.setDataType(WPSConvertersUtils.createDataType(clazz));
in.setLiteralData(literal);
} else if (WPSIO.isSupportedReferenceInputClass(clazz)) {
Map<String, Object> userData = null;
if (paramDesc instanceof ExtendedParameterDescriptor) {
userData = ((ExtendedParameterDescriptor) paramDesc).getUserObject();
}
in.setComplexData(WPSUtils.describeComplex(clazz, WPSIO.IOType.INPUT, WPSIO.FormChoice.REFERENCE, userData));
//Simple object (Integer, double, ...) and Object which need a conversion from String like affineTransform or WKT Geometry
} else {
throw new CstlServiceException("Process input not supported.", NO_APPLICABLE_CODE);
}
} else if (param instanceof ParameterDescriptorGroup) {
/*
* If we get a parameterDescriptorGroup, we must expose the
* parameters contained in it as one single input. To do so,
* we'll expose a feature type input.
*/
FeatureType ft = WPSConvertersUtils.descriptorGroupToFeatureType((ParameterDescriptorGroup) param);
// Build the schema xsd, and store it into temporary folder.
String placeToStore = schemaFolder + "/" + ft.getName().tip().toString()+ ".xsd";
String publicAddress = schemaURL + "/" + ft.getName().tip().toString() + ".xsd";
File xsdStore = new File(placeToStore);
try {
WPSUtils.storeFeatureSchema(ft, xsdStore);
final Class clazz = ft.getClass();
HashMap<String, Object> userData = new HashMap<>(1);
userData.put(WPSIO.SCHEMA_KEY, publicAddress);
in.setComplexData(WPSUtils.describeComplex(clazz, WPSIO.IOType.INPUT, WPSIO.FormChoice.COMPLEX, userData));
} catch (JAXBException ex) {
throw new CstlServiceException("The schema for parameter " + param.getName().getCode() + "can't be build.", NO_APPLICABLE_CODE);
}
} else {
throw new CstlServiceException("Process parameter invalid", NO_APPLICABLE_CODE);
}
dataInputs.getInput().add(in);
}
if (!dataInputs.getInput().isEmpty()) {
descriptionType.setDataInputs(dataInputs);
}
///////////////////////////////
// Process Output parameters
///////////////////////////////
final ProcessDescriptionType.ProcessOutputs dataOutput = new ProcessDescriptionType.ProcessOutputs();
for (GeneralParameterDescriptor param : output.descriptors()) {
final OutputDescriptionType out = new OutputDescriptionType();
//parameter information
out.setIdentifier(new CodeType(WPSUtils.buildProcessIOIdentifiers(processDesc, param, WPSIO.IOType.OUTPUT)));
out.setTitle(WPSUtils.capitalizeFirstLetter(param.getName().getCode()));
if (param.getRemarks() != null) {
out.setAbstract(WPSUtils.capitalizeFirstLetter(param.getRemarks().toString()));
} else {
out.setAbstract(WPSUtils.capitalizeFirstLetter("No description available"));
}
//simple parameter
if (param instanceof ParameterDescriptor) {
final ParameterDescriptor paramDesc = (ParameterDescriptor) param;
//input class
final Class clazz = paramDesc.getValueClass();
//BoundingBox type
if (WPSIO.isSupportedBBoxOutputClass(clazz)) {
out.setBoundingBoxOutput(WPS_SUPPORTED_CRS);
//Simple object (Integer, double) and Object which need a conversion from String like affineTransform or Geometry
//Complex type (XML, raster, ...)
} else if (WPSIO.isSupportedComplexOutputClass(clazz)) {
Map<String, Object> userData = null;
if (paramDesc instanceof ExtendedParameterDescriptor) {
userData = ((ExtendedParameterDescriptor) paramDesc).getUserObject();
}
out.setComplexOutput(WPSUtils.describeComplex(clazz, WPSIO.IOType.OUTPUT, WPSIO.FormChoice.COMPLEX, userData));
//Reference type (XML, ...)
} else if (WPSIO.isSupportedLiteralOutputClass(clazz)) {
final LiteralOutputType literal = new LiteralOutputType();
literal.setDataType(WPSConvertersUtils.createDataType(clazz));
if (paramDesc.getUnit() != null) {
literal.setUOMs(WPSUtils.generateUOMs(paramDesc));
}
out.setLiteralOutput(literal);
} else if (WPSIO.isSupportedReferenceOutputClass(clazz)) {
Map<String, Object> userData = null;
if (paramDesc instanceof ExtendedParameterDescriptor) {
userData = ((ExtendedParameterDescriptor) paramDesc).getUserObject();
}
out.setComplexOutput(WPSUtils.describeComplex(clazz, WPSIO.IOType.OUTPUT, WPSIO.FormChoice.REFERENCE, userData));
} else {
throw new CstlServiceException("Process output not supported.", NO_APPLICABLE_CODE);
}
} else if (param instanceof ParameterDescriptorGroup) {
/*
* If we get a parameterDescriptorGroup, we must expose the
* parameters contained in it as one single input. To do so,
* we'll expose a feature type input.
*/
FeatureType ft = WPSConvertersUtils.descriptorGroupToFeatureType((ParameterDescriptorGroup) param);
// Input class
final Class clazz = ft.getClass();
String placeToStore = schemaFolder + "/" + ft.getName().tip().toString() + ".xsd";
String publicAddress = schemaURL + "/" + ft.getName().tip().toString() + ".xsd";
File xsdStore = new File(placeToStore);
try {
WPSUtils.storeFeatureSchema(ft, xsdStore);
HashMap<String, Object> userData = new HashMap<>(1);
userData.put(WPSIO.SCHEMA_KEY, publicAddress);
out.setComplexOutput(WPSUtils.describeComplex(clazz, WPSIO.IOType.OUTPUT, WPSIO.FormChoice.COMPLEX, userData));
} catch (JAXBException ex) {
throw new CstlServiceException("The schema for parameter " + param.getName().getCode() + "can't be build.", NO_APPLICABLE_CODE);
}
} else {
throw new CstlServiceException("Process parameter invalid", NO_APPLICABLE_CODE);
}
dataOutput.getOutput().add(out);
}
descriptionType.setProcessOutputs(dataOutput);
descriptions.getProcessDescription().add(descriptionType);
}
return descriptions;
}
//////////////////////////////////////////////////////////////////////
// Execute
//////////////////////////////////////////////////////////////////////
/**
* Redirect execute requests from the WPS version requested.
*
* @param request request
* @return execute response (Raw data or Document response) depends of the ResponseFormType in execute request
* @throws CstlServiceException
*/
public Object execute(Execute request) throws CstlServiceException {
isWorking();
updateWebDavURL();
updateWMSURL();
//check SERVICE=WPS
if (!request.getService().equalsIgnoreCase(WPS_SERVICE)) {
throw new CstlServiceException("The parameter " + SERVICE_PARAMETER + " must be specified as WPS",
INVALID_PARAMETER_VALUE, SERVICE_PARAMETER.toLowerCase());
}
//check LANGUAGE=en-EN
if (request.getLanguage() != null && !request.getLanguage().equalsIgnoreCase(WPS_LANG)) {
throw new CstlServiceException("The specified " + LANGUAGE_PARAMETER + " is not handled by the service. ",
INVALID_PARAMETER_VALUE, LANGUAGE_PARAMETER.toLowerCase());
}
//check mandatory version is not missing.
if (request.getVersion() == null || request.getVersion().toString().isEmpty()) {
throw new CstlServiceException("The parameter " + VERSION_PARAMETER + " must be specified.",
MISSING_PARAMETER_VALUE, VERSION_PARAMETER.toLowerCase());
}
//check VERSION=1.0.0
if (request.getVersion().toString().equals(ServiceDef.WPS_1_0_0.version.toString())) {
return execute100((org.geotoolkit.wps.xml.v100.Execute) request);
} else {
throw new CstlServiceException("The specified " + VERSION_PARAMETER + " number is not handled by the service.",
VERSION_NEGOTIATION_FAILED, VERSION_PARAMETER.toLowerCase());
}
}
/**
* Execute a process in wps v1.0.0.
*
* @param request request
* @return execute response (Raw data or Document response) depends of the ResponseFormType in execute request
* @throws CstlServiceException
*/
private Object execute100(final Execute request) throws CstlServiceException {
//////////////////////////////
// REQUEST VALIDATION
//////////////////////////////
//check mandatory IDENTIFIER is not missing.
if (request.getIdentifier() == null || request.getIdentifier().getValue() == null || request.getIdentifier().getValue().isEmpty()) {
throw new CstlServiceException("The parameter " + IDENTIFER_PARAMETER + " must be specified.",
MISSING_PARAMETER_VALUE, IDENTIFER_PARAMETER.toLowerCase());
}
//Find the process
final ProcessDescriptor processDesc = WPSUtils.getProcessDescriptor(request.getIdentifier().getValue());
if (!WPSUtils.isSupportedProcess(processDesc)) {
throw new CstlServiceException("Process " + request.getIdentifier().getValue() + " not supported by the service.",
INVALID_PARAMETER_VALUE, IDENTIFER_PARAMETER.toLowerCase());
}
//check requested INPUT/OUTPUT. Throw a CstlException otherwise.
WPSUtils.checkValidInputOuputRequest(processDesc, request);
/*
* Get the requested output form
*/
final ResponseFormType responseForm = request.getResponseForm();
OutputDefinitionType rawData = null;
final ResponseDocumentType respDoc;
if (responseForm == null) {
//create default response form if not define
final List<DocumentOutputDefinitionType> outputs = new ArrayList<>();
for (GeneralParameterDescriptor gpd : processDesc.getOutputDescriptor().descriptors()) {
if (gpd instanceof ParameterDescriptor) {
final DocumentOutputDefinitionType docOutDef = new DocumentOutputDefinitionType();
docOutDef.setIdentifier(new CodeType(WPSUtils.buildProcessIOIdentifiers(processDesc, gpd, WPSIO.IOType.OUTPUT)));
docOutDef.setAsReference(false);
outputs.add(docOutDef);
}
//TODO handle sub levels of ParameterDescriptors
}
respDoc = new ResponseDocumentType();
respDoc.setLineage(false);
respDoc.setStatus(false);
respDoc.setStoreExecuteResponse(false);
respDoc.getOutput().addAll(outputs);
} else {
rawData = responseForm.getRawDataOutput();
respDoc = responseForm.getResponseDocument();
}
boolean isOutputRaw = rawData != null; // the default output is a ResponseDocument
boolean isOutputRespDoc = respDoc != null;
if (!isOutputRaw && !isOutputRespDoc) {
throw new CstlServiceException("The response form should be defined.", MISSING_PARAMETER_VALUE, "responseForm");
}
//status="true" && storeExecuteResponse="false" -> exception (see WPS-1.0.0 spec page 43).
if(isOutputRespDoc && respDoc.isStatus() && !respDoc.isStoreExecuteResponse()){
throw new CstlServiceException("Set the storeExecuteResponse to true if you want to see status in response documents.", INVALID_PARAMETER_VALUE, "storeExecuteResponse");
}
//////////////////////////////
// END OF REQUEST VALIDATION
//////////////////////////////
LOGGER.log(Level.INFO, "Process Execute : {0}", request.getIdentifier().getValue());
/*
* ResponseDocument attributes
*/
boolean useLineage = isOutputRespDoc && respDoc.isLineage();
boolean useStorage = isOutputRespDoc && respDoc.isStoreExecuteResponse();
final List<DocumentOutputDefinitionType> wantedOutputs = isOutputRespDoc ? respDoc.getOutput() : null;
//Input temporary files used by the process. In order to delete them at the end of the process.
final ArrayList<File> tempFiles = new ArrayList<>();
//Submit the process execution to the ExecutorService
final List<GeneralParameterDescriptor> processOutputDesc = processDesc.getOutputDescriptor().descriptors();
ParameterValueGroup result = null;
///////////////////////
// RUN Process
//////////////////////
if (isOutputRaw) {
////////
// RAW Sync no timeout
////////
final org.geotoolkit.process.Process process = createProcess(
processDesc, request.getDataInputs() == null? null : request.getDataInputs().getInput(),tempFiles);
final Future<ParameterValueGroup> future = WPSService.getExecutor().submit(process);
try {
result = future.get();
} catch (InterruptedException ex) {
throw new CstlServiceException("Process interrupted.", ex, NO_APPLICABLE_CODE);
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
if(cause instanceof ProcessException){
throw new CstlServiceException("Process execution failed."+ex.getCause().getMessage(), ex, NO_APPLICABLE_CODE);
}else if(cause != null){
throw new CstlServiceException("Process execution failed."+ex.getCause().getMessage(), ex, NO_APPLICABLE_CODE);
}else{
throw new CstlServiceException("Process execution failed.", ex, NO_APPLICABLE_CODE);
}
}
return createRawOutput(rawData, processOutputDesc, result);
} else {
final ExecuteResponse response = new ExecuteResponse();
response.setService(WPS_SERVICE);
response.setVersion(WPS_1_0_0);
response.setLang(WPS_LANG);
response.setServiceInstance(getServiceUrl() + "SERVICE=WPS&REQUEST=GetCapabilities");
//Give a brief process description into the execute response
response.setProcess(WPSUtils.generateProcessBrief(processDesc));
//Lineage option.
if (useLineage) {
//Inputs
response.setDataInputs(request.getDataInputs());
final OutputDefinitionsType outputsDef = new OutputDefinitionsType();
outputsDef.getOutput().addAll(respDoc.getOutput());
//Outputs
response.setOutputDefinitions(outputsDef);
}
final Map<String, Object> parameters = buildParametersMap(
webdavURL,
webdavFolderPath,
wmsInstanceName,
wmsInstanceURL,
fileCoverageStorePath,
wmsProviderId,
"WPS_"+getId()+"_"+WPSUtils.buildLayerName(processDesc),
wmsSupported);
StatusType status = new StatusType();
////////
// DOC Async
////////
if (useStorage) {
/*
* If we are in asynchronous execution, we create the status document and return, to specify user we accepted its
* request. We create the process in a thread, so if an input is a reference and we've got a timeout, client has
* already got the status location, and he will receive a proper exception report instead of a timeout error due
* to the input.
*/
if (!supportStorage) {
throw new CstlServiceException("Storage not supported.", STORAGE_NOT_SUPPORTED, "storeExecuteResponse");
}
final String respDocFileName = UUID.randomUUID().toString();
status.setCreationTime(WPSUtils.getCurrentXMLGregorianCalendar());
status.setProcessAccepted("Process " + request.getIdentifier().getValue() + " accepted.");
response.setStatus(status);
response.setStatusLocation(webdavURL + "/" + respDocFileName); //Output data URL
//store response document
WPSUtils.storeResponse(response, webdavFolderPath, respDocFileName);
//run process in asynchronous
new Thread(new Runnable() {
@Override
public void run() {
try {
// Prepare and launch process in a separate thread.
final org.geotoolkit.process.Process process = createProcess(
processDesc, request.getDataInputs() == null ? null : request.getDataInputs().getInput(), tempFiles);
process.addListener(new WPSProcessListener(request, response, respDocFileName, ServiceDef.WPS_1_0_0, parameters));
WPSService.getExecutor().submit(process);
} catch (Exception e) {
// If we've got an exception, input parsing must have failed.
final StatusType status = new StatusType();
status.setCreationTime(WPSUtils.getCurrentXMLGregorianCalendar());
final ProcessFailedType processFT = new ProcessFailedType();
processFT.setExceptionReport(new ExceptionReport(Exceptions.formatStackTrace(e), null, null, ServiceDef.WPS_1_0_0.exceptionVersion.toString()));
status.setProcessFailed(processFT);
response.setStatus(status);
WPSUtils.storeResponse(response, webdavFolderPath, respDocFileName);
}
}
}).start();
} else {
////////
// DOC Sync
////////
final org.geotoolkit.process.Process process = createProcess(
processDesc, request.getDataInputs() == null? null : request.getDataInputs().getInput(),tempFiles);
final Future<ParameterValueGroup> future = WPSService.getExecutor().submit(process);
final ProcessFailedType processFT = new ProcessFailedType();
ExceptionReport report = null;
// timeout
try {
//run process
result = future.get(TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "Process "+WPSUtils.buildProcessIdentifier(processDesc)+" interrupted.", ex);
report = new ExceptionReport(ex.getLocalizedMessage(), null, null, null);
} catch (ExecutionException ex) {
//process exception
LOGGER.log(Level.WARNING, "Process "+WPSUtils.buildProcessIdentifier(processDesc)+" has failed.", ex);
report = new ExceptionReport("Process error : " + ex.getLocalizedMessage(), null, null, null);
} catch (TimeoutException ex) {
((AbstractProcess) process).cancelProcess();
future.cancel(true);
report = new ExceptionReport("Process execution timeout. This process is too long and had been canceled,"
+ " re-run request with status set to true.", null, null, null);
}
if (report != null) {
processFT.setExceptionReport(report);
status.setProcessFailed(processFT);
status.setCreationTime(WPSUtils.getCurrentXMLGregorianCalendar());
} else {
//no error - fill response outputs.
final ExecuteResponse.ProcessOutputs outputs = new ExecuteResponse.ProcessOutputs();
fillOutputsFromProcessResult(outputs, wantedOutputs, processOutputDesc, result, parameters, false);
response.setProcessOutputs(outputs);
status.setCreationTime(WPSUtils.getCurrentXMLGregorianCalendar());
status.setProcessSucceeded("Process completed.");
}
// add status
response.setStatus(status);
}
//Delete input temporary files
//WPSUtils.cleanTempFiles(files);
return response;
}
}
/**
* Create a Process object from the given descriptor, and set its input using the {@link org.geotoolkit.wps.xml.v100.Execute}
* request input in parameter.
* @param processDesc A descriptor for Process creation.
* @param requestInputData The list of inputs to set to the process.
* @param tempFiles A list to store references of possible temporary files created with the process.
* @return An ready-to-use process.
*/
private org.geotoolkit.process.Process createProcess(
final ProcessDescriptor processDesc, List<InputType> requestInputData, List<File> tempFiles)
throws CstlServiceException {
//Create Process and Inputs
final ParameterValueGroup in = processDesc.getInputDescriptor().createValue();
if (requestInputData == null) {
requestInputData = new ArrayList<>();
}
final List<GeneralParameterDescriptor> processInputDesc = processDesc.getInputDescriptor().descriptors();
//Fill input process with there default values
for (final GeneralParameterDescriptor inputGeneDesc : processInputDesc) {
if (inputGeneDesc instanceof ParameterDescriptor) {
final ParameterDescriptor inputDesc = (ParameterDescriptor) inputGeneDesc;
if (inputDesc.getDefaultValue() != null) {
in.parameter(inputDesc.getName().getCode()).setValue(inputDesc.getDefaultValue());
}
}
}
//Fill process input with data from execute request.
fillProcessInputFromRequest(in, requestInputData, processInputDesc, tempFiles);
//Give input parameter to the process
return processDesc.createProcess(in);
}
/**
* For each inputs in Execute request, this method will find corresponding {@link ParameterDescriptor ParameterDescriptor} input in the
* process and fill the {@link ParameterValueGroup ParameterValueGroup} with the data.
*
* @param in
* @param requestInputData
* @param processInputDesc
* @param files
* @throws CstlServiceException
*/
private void fillProcessInputFromRequest(final ParameterValueGroup in, final List<InputType> requestInputData,
final List<GeneralParameterDescriptor> processInputDesc, List<File> files) throws CstlServiceException {
ArgumentChecks.ensureNonNull("in", in);
ArgumentChecks.ensureNonNull("requestInputData", requestInputData);
for (final InputType inputRequest : requestInputData) {
if (inputRequest.getIdentifier() == null || inputRequest.getIdentifier().getValue() == null || inputRequest.getIdentifier().getValue().isEmpty()) {
throw new CstlServiceException("Empty input Identifier.", INVALID_PARAMETER_VALUE);
}
final String inputIdentifier = inputRequest.getIdentifier().getValue();
final String inputIdentifierCode = WPSUtils.extractProcessIOCode(inputIdentifier);
//Check if it's a valid input identifier and hold it if found.
GeneralParameterDescriptor inputDescriptor = null;
for (final GeneralParameterDescriptor processInput : processInputDesc) {
if (processInput.getName().getCode().equals(inputIdentifierCode)) {
inputDescriptor = processInput;
break;
}
}
if (inputDescriptor == null) {
throw new CstlServiceException("Invalid or unknown input identifier " + inputIdentifier + ".", INVALID_PARAMETER_VALUE, inputIdentifier);
}
boolean isReference = false;
boolean isBBox = false;
boolean isComplex = false;
boolean isLiteral = false;
if (inputRequest.getReference() != null) {
isReference = true;
} else {
if (inputRequest.getData() != null) {
final DataType dataType = inputRequest.getData();
if (dataType.getBoundingBoxData() != null) {
isBBox = true;
} else if (dataType.getComplexData() != null) {
isComplex = true;
} else if (dataType.getLiteralData() != null) {
isLiteral = true;
} else {
throw new CstlServiceException("Input Data element not found.");
}
} else {
throw new CstlServiceException("Input doesn't have data or reference.");
}
}
/*
* Get expected input Class from the process input
*/
Class expectedClass;
if(inputDescriptor instanceof ParameterDescriptor) {
expectedClass = ((ParameterDescriptor)inputDescriptor).getValueClass();
} else {
expectedClass = Feature.class;
}
Object dataValue = null;
//LOGGER.log(Level.INFO, "Input : " + inputIdentifier + " : expected Class " + expectedClass.getCanonicalName());
/**
* Handle referenced input data.
*/
if (isReference) {
//Check if the expected class is supported for reference using
if (!WPSIO.isSupportedReferenceInputClass(expectedClass)) {
throw new CstlServiceException("The input" + inputIdentifier + " can't handle reference.", INVALID_PARAMETER_VALUE, inputIdentifier);
}
final InputReferenceType requestedRef = inputRequest.getReference();
if (requestedRef.getHref() == null) {
throw new CstlServiceException("Invalid reference input : href can't be null.", INVALID_PARAMETER_VALUE, inputIdentifier);
}
try {
dataValue = WPSConvertersUtils.convertFromReference(requestedRef, expectedClass);
} catch (UnconvertibleObjectException ex) {
LOGGER.log(Level.WARNING, "Error during conversion of reference input {0}.",inputIdentifier);
throw new CstlServiceException(ex.getMessage(), ex, NO_APPLICABLE_CODE);
}
if (dataValue instanceof File && files != null) {
files.add((File) dataValue);
}
}
/**
* Handle Bbox input data.
*/
if (isBBox) {
final BoundingBoxType bBox = inputRequest.getData().getBoundingBoxData();
final List<Double> lower = bBox.getLowerCorner();
final List<Double> upper = bBox.getUpperCorner();
final String crs = bBox.getCrs();
final int dimension = bBox.getDimensions();
//Check if it's a 2D boundingbox
if (dimension != 2 || lower.size() != 2 || upper.size() != 2) {
throw new CstlServiceException("Invalid data input : Only 2 dimension boundingbox supported.", OPERATION_NOT_SUPPORTED, inputIdentifier);
}
CoordinateReferenceSystem crsDecode;
try {
crsDecode = CRS.decode(crs);
} catch (FactoryException ex) {
throw new CstlServiceException("Invalid data input : CRS not supported.",
ex, OPERATION_NOT_SUPPORTED, inputIdentifier);
}
dataValue = GeometryUtils.createCRSEnvelope(crsDecode, lower.get(0), lower.get(1), upper.get(0), upper.get(1));
}
/**
* Handle Complex input data.
*/
if (isComplex) {
//Check if the expected class is supported for complex using
if (!WPSIO.isSupportedComplexInputClass(expectedClass)) {
throw new CstlServiceException("Complex value expected", INVALID_PARAMETER_VALUE, inputIdentifier);
}
final ComplexDataType complex = inputRequest.getData().getComplexData();
if (complex.getContent() == null || complex.getContent().size() <= 0) {
throw new CstlServiceException("Missing data input value.", INVALID_PARAMETER_VALUE, inputIdentifier);
} else {
try {
dataValue = WPSConvertersUtils.convertFromComplex(complex, expectedClass);
} catch (IllegalArgumentException ex) {
LOGGER.log(Level.WARNING, "Error during conversion of complex input {0}.",inputIdentifier);
throw new CstlServiceException(ex.getMessage(), ex, NO_APPLICABLE_CODE);
}
}
}
/**
* Handle Literal input data.
*/
if (isLiteral) {
//Check if the expected class is supported for literal using
if (!WPSIO.isSupportedLiteralInputClass(expectedClass)) {
throw new CstlServiceException("Literal value expected", INVALID_PARAMETER_VALUE, inputIdentifier);
}
if(!(inputDescriptor instanceof ParameterDescriptor)) {
throw new CstlServiceException("Invalid parameter type.", INVALID_PARAMETER_VALUE, inputIdentifier);
}
final LiteralDataType literal = inputRequest.getData().getLiteralData();
final String data = literal.getValue();
final Unit paramUnit = ((ParameterDescriptor)inputDescriptor).getUnit();
if (paramUnit != null) {
final Unit requestedUnit = Unit.valueOf(literal.getUom());
final UnitConverter converter = requestedUnit.getConverterTo(paramUnit);
dataValue = converter.convert(Double.valueOf(data));
} else {
try {
dataValue = WPSConvertersUtils.convertFromString(data, expectedClass);
} catch (UnconvertibleObjectException ex) {
LOGGER.log(Level.WARNING, "Error during conversion of literal input {0}.",inputIdentifier);
throw new CstlServiceException(ex.getMessage(), ex, NO_APPLICABLE_CODE);
}
}
}
try {
if(inputDescriptor instanceof ParameterDescriptor) {
in.parameter(inputIdentifierCode).setValue(dataValue);
} else if(inputDescriptor instanceof ParameterDescriptorGroup && dataValue instanceof ComplexAttribute) {
WPSConvertersUtils.featureToParameterGroup(
(ComplexAttribute)dataValue,
in.addGroup(inputIdentifierCode));
} else {
throw new Exception();
}
} catch (Exception ex) {
throw new CstlServiceException("Invalid data input value.", ex, INVALID_PARAMETER_VALUE, inputIdentifier);
}
}
}
/**
* Fill outputs of the ProcessOutputs object using the process result, the
* list of requested outputs and the list of process output descriptors.
*
* @param outputs The WPS outputs to fill.
* @param wantedOutputs The definition of the outputs we must treat.
* @param processOutputDesc The descriptors of the geotk process outputs.
* @param result The values which have been filled by the process.
* @param parameters URL of the WPS service.
* @param progressing True if the process is still in progress, for intermediate results.
* @throws CstlServiceException If one of the outputs is invalid.
*/
public static void fillOutputsFromProcessResult(final ProcessOutputs outputs, final List<DocumentOutputDefinitionType> wantedOutputs,
final List<GeneralParameterDescriptor> processOutputDesc, final ParameterValueGroup result, final Map<String, Object> parameters,
final boolean progressing)
throws CstlServiceException {
if (result == null) {
throw new CstlServiceException("Empty process result.", NO_APPLICABLE_CODE);
}
for (final DocumentOutputDefinitionType outputsRequest : wantedOutputs) {
if (outputsRequest.getIdentifier() == null || outputsRequest.getIdentifier().getValue() == null || outputsRequest.getIdentifier().getValue().isEmpty()) {
throw new CstlServiceException("Empty output Identifier.", INVALID_PARAMETER_VALUE);
}
final String outputIdentifier = outputsRequest.getIdentifier().getValue();
final String outputIdentifierCode = WPSUtils.extractProcessIOCode(outputIdentifier);
//Check if it's a valid input identifier and hold it if found.
GeneralParameterDescriptor outputDescriptor = null;
for (final GeneralParameterDescriptor processInput : processOutputDesc) {
if (processInput.getName().getCode().equals(outputIdentifierCode)) {
outputDescriptor = (ParameterDescriptor) processInput;
break;
}
}
if (outputDescriptor == null) {
throw new CstlServiceException("Invalid or unknown output identifier " + outputIdentifier + ".", INVALID_PARAMETER_VALUE, outputIdentifier);
}
if (outputDescriptor instanceof ParameterDescriptor) {
//output value object.
final Object outputValue = result.parameter(outputIdentifierCode).getValue();
if (!progressing || (progressing && outputValue != null)) {
outputs.getOutput().add(createDocumentResponseOutput((ParameterDescriptor) outputDescriptor, outputsRequest, outputValue, parameters));
}
} else if (outputDescriptor instanceof ParameterDescriptorGroup) {
/**
* TODO: Treat ParameterValueGroup for outputs.
*/
throw new CstlServiceException("Invalid or unknown output identifier " + outputIdentifier + ".", INVALID_PARAMETER_VALUE, outputIdentifier);
} else {
throw new CstlServiceException("Invalid or unknown output identifier " + outputIdentifier + ".", INVALID_PARAMETER_VALUE, outputIdentifier);
}
}//end foreach wanted outputs
}
/**
* Create {@link OutputDataType output} object for one requested output.
*
* @param outputDescriptor
* @param requestedOutput
* @param outputValue
* @param parameters
* @return
* @throws CstlServiceException
*/
public static OutputDataType createDocumentResponseOutput(final ParameterDescriptor outputDescriptor, final DocumentOutputDefinitionType requestedOutput,
final Object outputValue, final Map<String, Object> parameters) throws CstlServiceException {
final OutputDataType outData = new OutputDataType();
final String outputIdentifier = requestedOutput.getIdentifier().getValue();
final String outputIdentifierCode = WPSUtils.extractProcessIOCode(outputIdentifier);
//set Output information
outData.setIdentifier(new CodeType(outputIdentifier));
//support custom title/abstract.
final LanguageStringType titleOut = requestedOutput.getTitle() != null ?
requestedOutput.getTitle() : WPSUtils.capitalizeFirstLetter(outputIdentifierCode);
LanguageStringType abstractOut = requestedOutput.getAbstract();
if (abstractOut == null) {
if (outputDescriptor.getRemarks() != null) {
abstractOut = WPSUtils.capitalizeFirstLetter(outputDescriptor.getRemarks().toString());
} else {
abstractOut = WPSUtils.capitalizeFirstLetter("No description available");
}
}
outData.setTitle(titleOut);
outData.setAbstract(abstractOut);
final Class outClass = outputDescriptor.getValueClass(); // output class
if (requestedOutput.isAsReference()) {
final OutputReferenceType ref = createReferenceOutput(outputIdentifier, outClass, requestedOutput, outputValue, parameters);
outData.setReference(ref);
} else {
final DataType data = new DataType();
if (WPSIO.isSupportedBBoxOutputClass(outClass)) {
org.opengis.geometry.Envelope envelop = (org.opengis.geometry.Envelope) outputValue;
data.setBoundingBoxData(new BoundingBoxType(envelop));
} else if (WPSIO.isSupportedComplexOutputClass(outClass)) {
try {
ComplexDataType complex = null;
if (outputValue instanceof GridCoverage && requestedOutput.getMimeType().equals(WPSMimeType.OGC_WMS.val())) {
if (parameters.get(WMS_SUPPORTED).equals(Boolean.TRUE)) {
//add output identifier to layerName
parameters.put(WPSConvertersUtils.WMS_LAYER_NAME, parameters.get(WPSConvertersUtils.WMS_LAYER_NAME)+"_"+outputIdentifierCode);
complex = WPSConvertersUtils.convertToWMSComplex(
outputValue,
requestedOutput.getMimeType(),
requestedOutput.getEncoding(),
requestedOutput.getSchema(),
parameters);
restartWMS(parameters);
} else {
LOGGER.log(Level.WARNING, "Can't publish {0} value in a WMS.", outputIdentifier);
}
} else {
complex = WPSConvertersUtils.convertToComplex(
outputValue,
requestedOutput.getMimeType(),
requestedOutput.getEncoding(),
requestedOutput.getSchema(),
parameters);
}
data.setComplexData(complex);
} catch (UnconvertibleObjectException ex) {
LOGGER.log(Level.WARNING, "Error during conversion of complex output {0}.", outputIdentifier);
throw new CstlServiceException(ex.getMessage(), ex, NO_APPLICABLE_CODE);
}
} else if (WPSIO.isSupportedLiteralOutputClass(outClass)) {
final LiteralDataType literal = new LiteralDataType();
literal.setDataType(WPSConvertersUtils.getDataTypeString(outClass));
literal.setValue(WPSConvertersUtils.convertToString(outputValue));
if (outputDescriptor.getUnit() != null) {
literal.setUom(outputDescriptor.getUnit().toString());
}
data.setLiteralData(literal);
} else {
throw new CstlServiceException("Process output parameter invalid", MISSING_PARAMETER_VALUE, outputIdentifier);
}
outData.setData(data);
}
return outData;
}
private static void restartWMS(Map<String, Object> parameters) {
final String wmsInstance = (String) parameters.get(WPSConvertersUtils.WMS_INSTANCE_NAME);
final String providerId = (String) parameters.get(WPSConvertersUtils.WMS_STORAGE_ID);
//restart provider
final Collection<DataProvider> layerProviders = DataProviders.getInstance().getProviders();
for (DataProvider p : layerProviders) {
if (p.getId().equals(providerId)) {
p.reload();
break;
}
}
//restart WMS worker
try {
final ProcessDescriptor restartServiceDesc = ProcessFinder.getProcessDescriptor(ConstellationProcessFactory.NAME, RestartServiceDescriptor.NAME);
final ParameterValueGroup restartServiceInputs = restartServiceDesc.getInputDescriptor().createValue();
restartServiceInputs.parameter(RestartServiceDescriptor.SERVICE_TYPE_NAME).setValue("WMS");
restartServiceInputs.parameter(RestartServiceDescriptor.IDENTIFIER_NAME).setValue(wmsInstance);
restartServiceDesc.createProcess(restartServiceInputs).call();
} catch (ProcessException | NoSuchIdentifierException e) {
LOGGER.log(Level.WARNING, "Error during WMS " + wmsInstance + " restart.", e);
}
}
/**
* Create reference output.
*
* @param clazz
* @param requestedOutput
* @param outputValue
* @param parameters
* @return
* @throws CstlServiceException
*/
private static OutputReferenceType createReferenceOutput(final String outputIdentifier, final Class clazz, final DocumentOutputDefinitionType requestedOutput,
final Object outputValue, final Map<String, Object> parameters) throws CstlServiceException {
try {
return (OutputReferenceType) WPSConvertersUtils.convertToReference(
outputValue,
requestedOutput.getMimeType(),
requestedOutput.getEncoding(),
requestedOutput.getSchema(),
parameters,
WPSIO.IOType.OUTPUT);
} catch (UnconvertibleObjectException ex) {
LOGGER.log(Level.WARNING, "Error during conversion of reference output {0}.", outputIdentifier);
throw new CstlServiceException(ex.getMessage(), ex, NO_APPLICABLE_CODE);
}
}
/**
* Handle Raw output.
*
* @param rawData
* @param processOutputDesc
* @param result
* @return
* @throws CstlServiceException
*/
private Object createRawOutput(final OutputDefinitionType rawData, final List<GeneralParameterDescriptor> processOutputDesc,
final ParameterValueGroup result) throws CstlServiceException {
final String outputIdentifier = rawData.getIdentifier().getValue();
final String outputIdentifierCode = WPSUtils.extractProcessIOCode(outputIdentifier);
//Check if it's a valid input identifier and hold it if found.
ParameterDescriptor outputDescriptor = null;
for (final GeneralParameterDescriptor processInput : processOutputDesc) {
if (processInput.getName().getCode().equals(outputIdentifierCode) && processInput instanceof ParameterDescriptor) {
outputDescriptor = (ParameterDescriptor) processInput;
break;
}
}
if (outputDescriptor == null) {
throw new CstlServiceException("Invalid or unknown output identifier " + outputIdentifier + ".", INVALID_PARAMETER_VALUE, outputIdentifier);
}
//output value object.
final Object outputValue = result.parameter(outputIdentifierCode).getValue();
if (outputValue instanceof Geometry) {
try {
final Geometry jtsGeom = (Geometry) outputValue;
return JTStoGeometry.toGML("3.1.1", jtsGeom); // TODO determine gml version
} catch (FactoryException ex) {
throw new CstlServiceException(ex);
}
}
if (outputValue instanceof Envelope) {
return new BoundingBoxType((Envelope) outputValue);
}
return outputValue;
}
private void checkForSchemasToStore(ProcessDescriptor source) throws JAXBException {
/*
* Check each input and output. If we get a parameterDescriptorGroup,
* we must store a schema which describe its structure.
*/
for (GeneralParameterDescriptor desc : source.getInputDescriptor().descriptors()) {
if (desc instanceof ParameterDescriptorGroup) {
FeatureType ft = WPSConvertersUtils.descriptorGroupToFeatureType((ParameterDescriptorGroup) desc);
String placeToStore = schemaFolder + "/" + ft.getName().tip().toString() + ".xsd";
File xsdStore = new File(placeToStore);
WPSUtils.storeFeatureSchema(ft, xsdStore);
}
}
for (GeneralParameterDescriptor desc : source.getOutputDescriptor().descriptors()) {
if (desc instanceof ParameterDescriptorGroup) {
FeatureType ft = WPSConvertersUtils.descriptorGroupToFeatureType((ParameterDescriptorGroup) desc);
String placeToStore = schemaFolder + "/" + ft.getName().tip().toString() + ".xsd";
File xsdStore = new File(placeToStore);
WPSUtils.storeFeatureSchema(ft, xsdStore);
}
}
}
@Override
protected String getProperty(final String key) {
if (context != null && context.getCustomParameters() != null) {
return context.getCustomParameters().get(key);
}
return null;
}
}