/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.wps;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.measure.Unit;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.geotoolkit.client.AbstractClient;
import org.geotoolkit.client.CapabilitiesException;
import org.geotoolkit.client.ClientFactory;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.parameter.ParameterBuilder;
import org.geotoolkit.ows.xml.v110.BoundingBoxType;
import org.geotoolkit.ows.xml.v110.DomainMetadataType;
import org.geotoolkit.parameter.DefaultParameterDescriptor;
import org.geotoolkit.utility.parameter.ExtendedParameterDescriptor;
import org.geotoolkit.parameter.Parameters;
import org.geotoolkit.process.*;
import org.geotoolkit.process.Process;
import org.geotoolkit.process.ProcessDescriptor;
import org.geotoolkit.process.ProcessingRegistry;
import org.geotoolkit.processing.AbstractProcessDescriptor;
import org.apache.sis.referencing.CRS;
import org.geotoolkit.security.ClientSecurity;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.iso.DefaultInternationalString;
import org.apache.sis.util.ObjectConverters;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.wps.converters.WPSConvertersUtils;
import org.geotoolkit.wps.converters.WPSObjectConverter;
import org.geotoolkit.wps.io.WPSIO;
import org.geotoolkit.wps.v100.DescribeProcess100;
import org.geotoolkit.wps.v100.Execute100;
import org.geotoolkit.wps.v100.GetCapabilities100;
import org.geotoolkit.wps.xml.WPSMarshallerPool;
import org.geotoolkit.wps.xml.v100.*;
import org.apache.sis.xml.MarshallerPool;
import org.geotoolkit.storage.DataStores;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.identification.Identification;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
import org.opengis.util.NoSuchIdentifierException;
import org.apache.sis.measure.Units;
/**
* WPS server, used to aquiere capabilites and requests process.
*
* @author Quentin Boileau
* @module
*/
public class WebProcessingClient extends AbstractClient implements ProcessingRegistry {
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.wps");
private static final long TIMEOUT_CAPS = 10000L;
/**
* A key for {@link ExtendedParameterDescriptor} user data map. Specify the format to use for parameter, using {FormatSupport} object.
*/
private static final String USE_FORMAT_KEY = "format";
//process descriptors
private final Map<String, ProcessDescriptor> descriptors = new HashMap<>();
/** A map whose key is a process identifier, and value a boolean to specify if it supports outputs as reference (true) or not (false). */
private final Map<String, Boolean> storageSupported = new HashMap<>();
/** A map whose key is a process identifier, and value a boolean to specify if it supports status (true) or not (false). */
private final Map<String, Boolean> statusSupported = new HashMap<>();
/**
* A map to specify for each process if we should ask its outputs as reference. Key is process identifier, and value
* a boolean : true if we want references, false otherwise. It's important to notice that even if we set a value to
* true, references will be used ONLY if this process can handle it (check it with {@link WebProcessingClient#supportStorage(String)}.
*/
private final Map<String, Boolean> outputAsReference = new HashMap<>();
private boolean descriptorsCached = false;
private WPSCapabilitiesType capabilities;
private String storageDirectory;
private String storageURL;
private boolean forceGET = true;
/**
* Static enumeration of WPS server versions.
*/
public static enum WPSVersion {
v100("1.0.0");
private final String code;
private WPSVersion(final String code) {
this.code = code;
}
public String getCode() {
return code;
}
/**
* Get the version enum from the string code.
*
* @param version
* @return The enum which matches with the given string.
* @throws IllegalArgumentException if the enum class does not contain any enum types for the given string
* value.
*/
public static WPSVersion getVersion(final String version) {
for (WPSVersion vers : values()) {
if (vers.getCode().equals(version)) {
return vers;
}
}
try {
return WPSVersion.valueOf(version);
} catch (IllegalArgumentException ex) {
}
throw new IllegalArgumentException("The given string \"" + version + "\" is not "
+ "a known version.");
}
}
/**
* Constructor
*
* @param serverURL
* @param version
*/
public WebProcessingClient(final URL serverURL, final String version) throws CapabilitiesException {
this(serverURL, null, version, true);
}
/**
* Costructor with forceGET tunning.
*
* @param serverURL
* @param version
* @param forceGET if true, GetCapabilities and DescribeProcess will be request in GET, otherwise POST is used.
* @throws CapabilitiesException
*/
public WebProcessingClient(final URL serverURL, final String version, final boolean forceGET) throws CapabilitiesException {
this(serverURL, null, version, forceGET);
}
/**
* Constructor
*
* @param serverURL
* @param security
* @param version
* @throws CapabilitiesException
*/
public WebProcessingClient(final URL serverURL, final ClientSecurity security, final String version) throws CapabilitiesException {
super(create(WPSClientFactory.PARAMETERS, serverURL, security));
if (version.equals("1.0.0")) {
Parameters.getOrCreate(WPSClientFactory.VERSION, parameters).setValue(WPSVersion.v100.getCode());
} else {
throw new IllegalArgumentException("Unknown version : " + version);
}
getCapabilities();
LOGGER.log(Level.INFO, "Web processing client initialization complete.");
}
/**
* Constructor
*
* @param serverURL
* @param security
* @param version
* @param forceGET if true, GetCapabilities and DescribeProcess will be request in GET, otherwise POST is used.
* @throws CapabilitiesException
*/
public WebProcessingClient(final URL serverURL, final ClientSecurity security, final String version, final boolean forceGET)
throws CapabilitiesException {
super(create(WPSClientFactory.PARAMETERS, serverURL, security));
if (version.equals("1.0.0")) {
Parameters.getOrCreate(WPSClientFactory.VERSION, parameters).setValue(WPSVersion.v100.getCode());
} else {
throw new IllegalArgumentException("Unknown version : " + version);
}
this.forceGET = forceGET;
getCapabilities();
LOGGER.log(Level.INFO, "Web processing client initialization complete.");
}
/**
* Constructor
*
* @param serverURL
* @param security
* @param version
* @throws CapabilitiesException
*/
public WebProcessingClient(final URL serverURL, final ClientSecurity security, final WPSVersion version) throws CapabilitiesException {
super(create(WPSClientFactory.PARAMETERS, serverURL, security));
if (version == null) {
throw new IllegalArgumentException("Unknown version : " + version);
}
Parameters.getOrCreate(WPSClientFactory.VERSION, parameters).setValue(version);
getCapabilities();
LOGGER.log(Level.INFO, "Web processing client initialization complete.");
}
public WebProcessingClient(ParameterValueGroup params) throws CapabilitiesException {
super(params);
getCapabilities();
LOGGER.log(Level.INFO, "Web processing client initialization complete.");
}
@Override
public ClientFactory getFactory() {
return (ClientFactory) DataStores.getFactoryById(WPSClientFactory.NAME);
}
/**
* @return WPSVersion : currently used version for this server
*/
public WPSVersion getVersion() {
return WPSVersion.getVersion(Parameters.value(WPSClientFactory.VERSION, parameters));
}
@Override
public Logger getLogger() {
return super.getLogger();
}
public String getStorageDirectory() {
return storageDirectory;
}
public void setStorageDirectory(String storageDirectory) {
this.storageDirectory = storageDirectory;
}
public String getStorageURL() {
return storageURL;
}
public void setStorageURL(String storageURL) {
this.storageURL = storageURL;
}
/**
* @return WPSCapabilitiesType : WPS server capabilities
*/
public WPSCapabilitiesType getCapabilities() throws CapabilitiesException {
if (capabilities != null) {
return capabilities;
}
//Thread to prevent infinite request on a server
final Thread thread = new Thread() {
@Override
public void run() {
Unmarshaller unmarshaller = null;
try {
final InputStream is = createGetCapabilities().getResponseStream();
unmarshaller = WPSMarshallerPool.getInstance().acquireUnmarshaller();
capabilities = ((JAXBElement<WPSCapabilitiesType>) unmarshaller.unmarshal(is)).getValue();
WPSMarshallerPool.getInstance().recycle(unmarshaller);
is.close();
} catch (Exception ex) {
capabilities = null;
LOGGER.log(Level.WARNING, ex.getMessage(), ex);
}
}
};
thread.start();
try {
thread.join(TIMEOUT_CAPS);
} catch (InterruptedException ex) {
throw new CapabilitiesException("GetCapabilities takes too much time. Abort.");
}
if (capabilities == null) {
throw new CapabilitiesException("A problem occured while getting Service capabilities.");
}
LOGGER.log(Level.INFO, "GetCapabilities request succeed.");
return capabilities;
}
/**
* @return ProcessDescriptions : WPS process description
*/
private ProcessDescriptions getDescribeProcess(final List<String> processIDs) {
final ProcessDescriptions[] description = new ProcessDescriptions[1];
//Thread to prevent infinite request on a server
final Thread thread = new Thread() {
@Override
public void run() {
final DescribeProcessRequest describe = createDescribeProcess();
describe.setIdentifiers(processIDs);
try {
final InputStream request = describe.getResponseStream();
final Unmarshaller unmarshaller = WPSMarshallerPool.getInstance().acquireUnmarshaller();
description[0] = (ProcessDescriptions) unmarshaller.unmarshal(request);
WPSMarshallerPool.getInstance().recycle(unmarshaller);
request.close();
} catch (Exception ex) {
description[0] = null;
LOGGER.log(Level.WARNING, ex.getMessage(), ex);
}
}
};
thread.start();
try {
thread.join(TIMEOUT_CAPS);
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "The thread to obtain describeProcess doesn't answer.", ex);
}
return description[0];
}
/**
* Create a getCapabilities request.
*
* @return GetCapabilitiesRequest : getCapabilities request.
*/
public GetCapabilitiesRequest createGetCapabilities() {
switch (getVersion()) {
case v100:
return new GetCapabilities100(serverURL.toString(), getClientSecurity(), forceGET);
default:
throw new IllegalArgumentException("Version not defined or unsupported.");
}
}
/**
* Create a describe process request
*
* @return DescribeProcessRequest : describe process request.
*/
public DescribeProcessRequest createDescribeProcess() {
switch (getVersion()) {
case v100:
return new DescribeProcess100(serverURL.toString(), getClientSecurity(), forceGET);
default:
throw new IllegalArgumentException("Version was not defined or unsupported.");
}
}
/**
* Create an execute request
*
* @return ExecuteRequest : execute request.
*/
public ExecuteRequest createExecute() {
switch (getVersion()) {
case v100:
return new Execute100(serverURL.toString(), getClientSecurity());
default:
throw new IllegalArgumentException("Version was not defined or unsupported.");
}
}
@Override
public Identification getIdentification() {
return getFactory().getIdentification();
}
@Override
public List<ProcessDescriptor> getDescriptors() {
checkDescriptors();
final Collection<ProcessDescriptor> values = descriptors.values();
return new ArrayList<>(values);
}
@Override
public List<String> getNames() {
checkDescriptors();
final Set<String> keys = descriptors.keySet();
return new ArrayList<>(keys);
}
@Override
public ProcessDescriptor getDescriptor(final String name) throws NoSuchIdentifierException {
checkDescriptors();
final ProcessDescriptor desc = descriptors.get(name);
if (desc == null) {
throw new NoSuchIdentifierException("No process descriptor for name :", name);
} else {
return desc;
}
}
private void checkDescriptors() {
if (!descriptorsCached) {
requestDescriptors();
}
}
private void requestDescriptors() {
if (capabilities.getProcessOfferings() == null) {
return;
}
final List<ProcessBriefType> processBrief = capabilities.getProcessOfferings().getProcess();
scan: for (final ProcessBriefType processBriefType : processBrief) {
final String processIdentifier = processBriefType.getIdentifier().getValue();
final InternationalString processAbstract;
if (processBriefType.getAbstract() != null) {
processAbstract = new DefaultInternationalString(processBriefType.getAbstract().getValue());
} else {
processAbstract = new DefaultInternationalString("");
}
final List<ParameterDescriptor> inputDescriptors = new ArrayList<>();
final List<ParameterDescriptor> outputDescriptors = new ArrayList<>();
final Map<String, String> inputTypes = new HashMap<>();
final ProcessDescriptions wpsProcessDescriptions = getDescribeProcess(Collections.singletonList(processIdentifier));
if (wpsProcessDescriptions.getProcessDescription() == null) {
continue;
}
final ProcessDescriptionType wpsProcessDesc = wpsProcessDescriptions.getProcessDescription().get(0);
// INPUTS
if (wpsProcessDesc.getDataInputs() != null) {
final List<InputDescriptionType> inputDescriptionList = wpsProcessDesc.getDataInputs().getInput();
for (final InputDescriptionType inputDesc : inputDescriptionList) {
final String inputName = inputDesc.getIdentifier().getValue();
final String inputAbstract = (inputDesc.getAbstract() == null)? "No description available" : inputDesc.getAbstract().getValue();
final Integer max = inputDesc.getMaxOccurs().intValue();
final Integer min = inputDesc.getMinOccurs().intValue();
final Map<String, String> properties = new HashMap<>();
properties.put("name", inputName);
properties.put("remarks", inputAbstract);
final SupportedComplexDataInputType complexInput = inputDesc.getComplexData();
final LiteralInputType literalInput = inputDesc.getLiteralData();
final SupportedCRSsType bboxInput = inputDesc.getBoundingBoxData();
if (complexInput != null) {
final ComplexDataCombinationType complexDefault = complexInput.getDefault();
if (complexDefault != null && complexDefault.getFormat() != null) {
String mime = complexDefault.getFormat().getMimeType();
String encoding = complexDefault.getFormat().getEncoding();
String schema = complexDefault.getFormat().getSchema();
/**
* Make a first try on default format, as it should be the more stable. If we don't support
* default format, we check the other supported formats until we find one we can use.
*/
Class clazz = WPSIO.findClass(WPSIO.IOType.INPUT, WPSIO.FormChoice.COMPLEX, mime, encoding, schema, null);
if (clazz == null) {
for (ComplexDataDescriptionType currentDesc : complexInput.getSupported().getFormat()) {
mime = currentDesc.getMimeType();
encoding = currentDesc.getEncoding();
schema = currentDesc.getSchema();
clazz = WPSIO.findClass(WPSIO.IOType.INPUT, WPSIO.FormChoice.COMPLEX, mime, encoding, schema, null);
if (clazz != null) {
break;
}
}
if (clazz == null) {
LOGGER.log(Level.WARNING, "No compatible format found for output (id: " +
inputName + ", process (id: " + processIdentifier + ") is skipped.");
continue scan;
}
}
final WPSIO.FormatSupport support = new WPSIO.FormatSupport(clazz, WPSIO.IOType.INPUT, mime, encoding, schema, false);
inputDescriptors.add(new ExtendedParameterDescriptor(
properties, clazz, null, null, null, null, null, true, Collections.singletonMap(USE_FORMAT_KEY, (Object) support)));
inputTypes.put(inputName, "complex");
} else {
LOGGER.log(Level.WARNING, "Invalid describeProcess. No default format specified for input " +
inputName + ". Process " + processIdentifier + ") is skipped.");
continue scan;
}
} else if (literalInput != null) {
final DomainMetadataType inputType = literalInput.getDataType();
Class clazz = WPSIO.findClass(WPSIO.IOType.INPUT, WPSIO.FormChoice.LITERAL, null, null, null, inputType);
if (clazz == null) {
clazz = String.class;
}
final String defaultValue = literalInput.getDefaultValue();
final SupportedUOMsType inputUom = literalInput.getUOMs();
Unit unit = null;
if (inputUom != null) {
unit = Units.valueOf(inputUom.getDefault().getUOM().getValue());
}
WPSObjectConverter converter = null;
try {
converter = WPSIO.getConverter(clazz, WPSIO.IOType.INPUT, WPSIO.FormChoice.LITERAL);
if (converter == null) {
LOGGER.log(Level.WARNING, "Can't find the converter for the default literal input value.");
continue scan;
}
} catch (UnconvertibleObjectException ex) {
LOGGER.log(Level.WARNING, "Can't find the converter for the default literal input value.", ex);
continue scan;
}
//At this state the converter can't be null.
try {
inputDescriptors.add(new DefaultParameterDescriptor(properties, clazz, null, converter.convert(defaultValue, null), null, null, unit, min != 0));
inputTypes.put(inputName, "literal");
} catch (UnconvertibleObjectException ex2) {
LOGGER.log(Level.WARNING, "Can't convert the default literal input value.", ex2);
continue scan;
}
} else if (bboxInput != null) {
inputDescriptors.add(new DefaultParameterDescriptor(properties, Envelope.class, null, null, null, null, null, min != 0));
inputTypes.put(inputName, "bbox");
} else {
LOGGER.log(Level.WARNING, "Unidentifiable input (id: " + inputName + ", process is (id: " + processIdentifier + ") skipped.");
continue scan;
}
}
}
//OUTPUTS
if (wpsProcessDesc.getProcessOutputs() != null) {
final List<OutputDescriptionType> outputDescriptionList = wpsProcessDesc.getProcessOutputs().getOutput();
for (final OutputDescriptionType outputDesc : outputDescriptionList) {
final String outputName = outputDesc.getIdentifier().getValue();
final String outputAbstract = (outputDesc.getAbstract() == null)? "No description available" : outputDesc.getAbstract().getValue();
final SupportedComplexDataType complexOutput = outputDesc.getComplexOutput();
final LiteralOutputType literalOutput = outputDesc.getLiteralOutput();
final SupportedCRSsType bboxOutput = outputDesc.getBoundingBoxOutput();
final Map<String, String> properties = new HashMap<>();
properties.put("name", outputName);
properties.put("remarks", outputAbstract);
if (complexOutput != null) {
final ComplexDataCombinationType complexDefault = complexOutput.getDefault();
// If default format is missing, describe process is not valid, we stop here.
if (complexDefault != null && complexDefault.getFormat() != null) {
String mime = complexDefault.getFormat().getMimeType();
String encoding = complexDefault.getFormat().getEncoding();
String schema = complexDefault.getFormat().getSchema();
/**
* Make a first try on default format, as it should be the more stable. If we don't support
* default format, we check the other supported formats until we find one we can use.
*/
Class clazz = WPSIO.findClass(WPSIO.IOType.OUTPUT, WPSIO.FormChoice.COMPLEX, mime, encoding, schema, null);
if (clazz == null) {
for (ComplexDataDescriptionType currentDesc : complexOutput.getSupported().getFormat()) {
mime = currentDesc.getMimeType();
encoding = currentDesc.getEncoding();
schema = currentDesc.getSchema();
clazz = WPSIO.findClass(WPSIO.IOType.OUTPUT, WPSIO.FormChoice.COMPLEX, mime, encoding, schema, null);
if (clazz != null) {
break;
}
}
if (clazz == null) {
LOGGER.log(Level.WARNING, "No compatible format found for output (id: " +
outputName + ", process (id: " + processIdentifier + ") is skipped.");
continue scan;
}
}
final WPSIO.FormatSupport support = new WPSIO.FormatSupport(clazz, WPSIO.IOType.OUTPUT, mime, encoding, schema, false);
outputDescriptors.add(new ExtendedParameterDescriptor(
outputName, outputAbstract, clazz, null, true, Collections.singletonMap(USE_FORMAT_KEY, (Object) support)));
} else {
LOGGER.log(Level.WARNING, "Invalid describeProcess. No default format specified for output " +
outputName + ". Process " + processIdentifier + ") is skipped.");
continue scan;
}
} else if (literalOutput != null) {
final DomainMetadataType inputType = literalOutput.getDataType();
Class clazz = WPSIO.findClass(WPSIO.IOType.OUTPUT, WPSIO.FormChoice.LITERAL, null, null, null, inputType);
if (clazz == null) {
clazz = String.class;
}
final SupportedUOMsType inputUom = literalOutput.getUOMs();
Unit unit = null;
if (inputUom != null) {
unit = Units.valueOf(inputUom.getDefault().getUOM().getValue());
}
outputDescriptors.add(new DefaultParameterDescriptor(properties, clazz, null, null, null, null, unit, true));
} else if (bboxOutput != null) {
outputDescriptors.add(new DefaultParameterDescriptor(properties, Envelope.class, null, null, null, null, null, true));
} else {
LOGGER.log(Level.WARNING, "Unidentifiable output (id: " + outputName + ", process is (id: " + processIdentifier + ") skipped.");
continue scan;
}
}
}
final ParameterDescriptorGroup inputs = new ParameterBuilder().addName("inputs").createGroup(
inputDescriptors.toArray(new ParameterDescriptor[inputDescriptors.size()]));
final ParameterDescriptorGroup outputs = new ParameterBuilder().addName("ouptuts").createGroup(
outputDescriptors.toArray(new ParameterDescriptor[outputDescriptors.size()]));
//Process Descriptor creation
final ProcessDescriptor processDesc = new AbstractProcessDescriptor(processIdentifier, getIdentification(), processAbstract, inputs, outputs) {
@Override
public Process createProcess(ParameterValueGroup input) {
return new WPSProcess(WebProcessingClient.this, this, inputTypes, input);
}
};
storageSupported .put(processIdentifier, wpsProcessDesc.isStoreSupported());
statusSupported .put(processIdentifier, wpsProcessDesc.isStatusSupported());
outputAsReference.put(processIdentifier, Boolean.FALSE);
descriptors .put(processIdentifier, processDesc);
}
descriptorsCached = true;
}
/**
* Specify if you want outputs sent back as references for the process identified by given name.
* @param processId The identifier of the process wanted.
* @param asReference True if you want references in output, false otherwise.
* @throws NoSuchIdentifierException If we can't find a process matching given name.
*/
public void setOutputsAsReference(final String processId, final boolean asReference) throws NoSuchIdentifierException {
checkDescriptors();
final Boolean supportReference = storageSupported.get(processId);
if(supportReference == null) {
throw new NoSuchIdentifierException("No process descriptor for name :", processId);
}
outputAsReference.put(processId, supportReference && asReference);
}
/**
* Check the current output settings for this process.
* @param processId The name of the process to check.
* @return True if the process return its outputs as reference, false otherwise.
* @throws NoSuchIdentifierException If we can't find a process matching given name.
*/
public boolean isOutputAsReference(final String processId) throws NoSuchIdentifierException {
checkDescriptors();
final Boolean asRef = outputAsReference.get(processId);
if(asRef == null) {
throw new NoSuchIdentifierException("No process descriptor for name :", processId);
}
return asRef;
}
/**
* Set all processes to send (or not) references for its outputs. Default behaviour is no reference. An important
* fact is that references are going to be used only if the process support storage (see
* {@link WebProcessingClient#supportStorage(String)}).
* @param choice True if you want reference as output, false otherwise.
*/
public void setOutputAsReferenceForAll(final boolean choice) {
checkDescriptors();
for (Map.Entry<String, Boolean> current : outputAsReference.entrySet()) {
current.setValue(choice && storageSupported.get(current.getKey()));
}
}
/**
* Inform the user if the process identified by given String can return outputs as reference or not.
* @param processId The name of the process to check.
* @return True if this process can use reference for its outputs, false otherwise.
* @throws NoSuchIdentifierException If we can't find the named process on WPS server, or if we can't manage it.
*/
public boolean supportStorage(final String processId) throws NoSuchIdentifierException {
checkDescriptors();
final Boolean result = storageSupported.get(processId);
if (result == null) {
throw new NoSuchIdentifierException("No process descriptor for name :", processId);
}
return result;
}
/**
* Inform the user if the process identified by given String can do quick updates of its status document.
* @param processId The name of the process to check.
* @return True if this process can update status before process ending, false otherwise.
* @throws NoSuchIdentifierException If we can't find the named process on WPS server, or if we can't manage it.
*/
public boolean supportStatus(final String processId) throws NoSuchIdentifierException {
checkDescriptors();
final Boolean result = statusSupported.get(processId);
if (result == null) {
throw new NoSuchIdentifierException("No process descriptor for name :", processId);
}
return result;
}
/**
* Make a WPS Execute request from {@link ParameterValueGroup values}.
*
* @param inputs
* @param descriptor
* @param inputTypes
* @return
* @throws ProcessException
*/
public Execute createRequest(final ParameterValueGroup inputs, final ProcessDescriptor descriptor,
final Map<String, String> inputTypes) throws ProcessException {
try {
final List<GeneralParameterDescriptor> inputParamDesc = inputs.getDescriptor().descriptors();
final List<GeneralParameterDescriptor> outputParamDesc = descriptor.getOutputDescriptor().descriptors();
final List<AbstractWPSInput> wpsIN = new ArrayList<>();
final List<WPSOutput> wpsOUT = new ArrayList<>();
final String processId = descriptor.getIdentifier().getCode();
final boolean asReference = outputAsReference.get(processId);
/*
* INPUTS
*/
for (final GeneralParameterDescriptor inputGeneDesc : inputParamDesc) {
if (inputGeneDesc instanceof ParameterDescriptor) {
final ParameterDescriptor inputDesc = (ParameterDescriptor) inputGeneDesc;
final String inputIdentifier = inputDesc.getName().getCode();
final String type = inputTypes.get(inputIdentifier);
final Class inputClazz = inputDesc.getValueClass();
final Object value = inputs.parameter(inputIdentifier).getValue();
final String unit = inputDesc.getUnit() != null ? inputDesc.getUnit().toString() : null;
if ("literal".equals(type)) {
wpsIN.add(new WPSInputLiteral(inputIdentifier, String.valueOf(value), WPSConvertersUtils.getDataTypeString(getVersion().code, inputClazz), unit));
} else if ("bbox".equals(type)) {
final Envelope envelop = (Envelope) value;
final String crs = envelop.getCoordinateReferenceSystem().getName().getCode();
final int dim = envelop.getDimension();
final List<Double> lower = new ArrayList<>();
final List<Double> upper = new ArrayList<>();
for (int i = 0; i < dim; i++) {
lower.add(envelop.getLowerCorner().getOrdinate(i));
upper.add(envelop.getUpperCorner().getOrdinate(i));
}
wpsIN.add(new WPSInputBoundingBox(inputIdentifier, lower, upper, crs, dim));
} else if ("complex".equals(type)) {
String mime = null;
String encoding = null;
String schema = null;
if (inputGeneDesc instanceof ExtendedParameterDescriptor) {
final Map<String, Object> userMap = ((ExtendedParameterDescriptor) inputGeneDesc).getUserObject();
if(userMap.containsKey(USE_FORMAT_KEY)) {
final WPSIO.FormatSupport support = (WPSIO.FormatSupport) userMap.get(USE_FORMAT_KEY);
mime = support.getMimeType();
encoding = support.getEncoding();
schema = support.getSchema();
}
}
wpsIN.add(new WPSInputComplex(inputIdentifier, value, inputClazz, encoding, schema, mime));
}
}
}
/*
* OUPTUTS
*/
for (final GeneralParameterDescriptor outputGeneDesc : outputParamDesc) {
if (outputGeneDesc instanceof ParameterDescriptor) {
final ParameterDescriptor outputDesc = (ParameterDescriptor) outputGeneDesc;
final String outputIdentifier = outputDesc.getName().getCode();
final Class outputClazz = outputDesc.getValueClass();
String mime = null;
String encoding = null;
String schema = null;
if (outputDesc instanceof ExtendedParameterDescriptor) {
final Map<String, Object> userMap = ((ExtendedParameterDescriptor) outputDesc).getUserObject();
if(userMap.containsKey(USE_FORMAT_KEY)) {
final WPSIO.FormatSupport support = (WPSIO.FormatSupport) userMap.get(USE_FORMAT_KEY);
mime = support.getMimeType();
encoding = support.getEncoding();
schema = support.getSchema();
}
}
wpsOUT.add(new WPSOutput(outputIdentifier, encoding, schema, mime, null, asReference));
}
}
final Execute100 exec100 = new Execute100(serverURL.toString(), null);
exec100.setIdentifier(processId);
exec100.setInputs(wpsIN);
exec100.setOutputs(wpsOUT);
exec100.setStorageDirectory(storageDirectory);
exec100.setOutputStorage(asReference);
// Status can be activated only if we ask outputs as references.
exec100.setOutputStatus(asReference && statusSupported.get(processId));
exec100.setStorageURL(storageURL);
LOGGER.log(Level.INFO, "Execute request created for "+processId+" in "+((asReference)? "asynchronous": "synchronous") + " mode.");
return exec100.makeRequest();
} catch (UnconvertibleObjectException ex) {
throw new ProcessException("Error during conversion step.", null, ex);
}
}
/**
* Send a secured request to the server URL in POST mode and return the unmarshalled response.
*
* @param request Request
* @return Response of this request
* @throws ProcessException is can't reach the server or if there is an
* error during Marshalling/Unmarshalling request or response.
*/
public Object sendSecuredRequestInPost(final Object request) throws JAXBException, IOException {
final MarshallerPool pool = WPSMarshallerPool.getInstance();
Unmarshaller unmarshaller;
Marshaller marshaller;
InputStream requestIS;
OutputStream requestOS;
unmarshaller = pool.acquireUnmarshaller();
marshaller = pool.acquireMarshaller();
// Build the request content (POST method)
final StringWriter content = new StringWriter();
marshaller.marshal(request, content);
// Make request
final ClientSecurity security = getClientSecurity();
security.secure(serverURL);
final URLConnection conec = serverURL.openConnection();
conec.setRequestProperty("content-type", "text/xml");
conec.setConnectTimeout((int)TIMEOUT_CAPS);
conec.setDoOutput(true);
security.secure(conec);
LOGGER.log(Level.INFO, "Sending execute request.");
// Write request content
requestOS = conec.getOutputStream();
security.encrypt(requestOS);
final OutputStreamWriter writer = new OutputStreamWriter(requestOS);
writer.write(content.toString());
writer.flush();
// Parse the response
requestIS = security.decrypt(conec.getInputStream());
final Object response = unmarshaller.unmarshal(requestIS);
if (response instanceof JAXBElement) {
return ((JAXBElement) response).getValue();
}
pool.recycle(unmarshaller);
pool.recycle(marshaller);
requestOS.close();
requestIS.close();
return response;
}
/**
* Send a secured request to the server URL in GET mode and return the unmarshalled response.
*
* @param url Request
* @return Response of this request
* @throws ProcessException is can't reach the server or if there is an
* error during Marshalling/Unmarshalling request or response.
*/
public Object sendSecuredRequestInGet(final URL url) throws JAXBException, IOException {
final ClientSecurity security = getClientSecurity();
security.secure(url);
final MarshallerPool pool = WPSMarshallerPool.getInstance();
Unmarshaller unmarshaller = null;
InputStream in = null;
try {
unmarshaller = pool.acquireUnmarshaller();
in = url.openStream();
final Object response = unmarshaller.unmarshal(in);
pool.recycle(unmarshaller);
if (response instanceof JAXBElement) {
return ((JAXBElement) response).getValue();
}
return response;
} finally {
if (in != null) {
in.close();
}
}
}
/**
* Fill {@link ParameterValueGroup parameters} of the process using the WPS
* {@link ExecuteResponse response}.
*
* @param outputs
* @param descriptor
* @param response
* @throws ProcessException if data conversion fails.
*/
public void fillOutputs(final ParameterValueGroup outputs, final ProcessDescriptor descriptor, final ExecuteResponse response)
throws ProcessException {
ArgumentChecks.ensureNonNull("response", response);
if (response.getProcessOutputs() != null) {
final List<OutputDataType> wpsOutputs = response.getProcessOutputs().getOutput();
LOGGER.log(Level.INFO, "Starting to parse output parameters. We found "+wpsOutputs.size()+" of them.");
for (final OutputDataType output : wpsOutputs) {
LOGGER.log(Level.INFO, "Parsing "+output.getIdentifier().getValue()+" output.");
final ParameterDescriptor outDesc = (ParameterDescriptor) descriptor.getOutputDescriptor().descriptor(output.getIdentifier().getValue());
final Class clazz = outDesc.getValueClass();
/*
* Reference
*/
if (output.getReference() != null) {
try {
outputs.parameter(output.getIdentifier().getValue()).setValue(WPSConvertersUtils.convertFromReference(output.getReference(), clazz));
} catch (UnconvertibleObjectException ex) {
throw new ProcessException(ex.getMessage(), null, ex);
}
} else {
final DataType outputType = output.getData();
/*
* BBOX
*/
if (outputType.getBoundingBoxData() != null) {
try {
final BoundingBoxType bbox = outputType.getBoundingBoxData();
final CoordinateReferenceSystem crs = CRS.forCode(bbox.getCrs());
final int dim = bbox.getDimensions();
final List<Double> lower = bbox.getLowerCorner();
final List<Double> upper = bbox.getUpperCorner();
final GeneralEnvelope envelope = new GeneralEnvelope(crs);
for (int i = 0; i < dim; i++) {
envelope.setRange(i, lower.get(i), upper.get(i));
}
outputs.parameter(output.getIdentifier().getValue()).setValue(envelope);
} catch (FactoryException ex) {
throw new ProcessException(ex.getMessage(), null, ex);
}
/*
* Complex
*/
} else if (outputType.getComplexData() != null) {
try {
outputs.parameter(output.getIdentifier().getValue()).setValue(WPSConvertersUtils.convertFromComplex("1.0.0", outputType.getComplexData(), clazz));
} catch (UnconvertibleObjectException ex) {
throw new ProcessException(ex.getMessage(), null, ex);
}
/*
* Literal
*/
} else if (outputType.getLiteralData() != null) {
try {
final LiteralDataType outputLiteral = outputType.getLiteralData();
final ObjectConverter converter = ObjectConverters.find(String.class, clazz);
outputs.parameter(output.getIdentifier().getValue()).setValue(converter.apply(outputLiteral.getValue()));
} catch (UnconvertibleObjectException ex) {
throw new ProcessException("Error during literal output conversion.", null, ex);
}
}
}
}
}
}
}