/*
* 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.rs;
import org.apache.sis.util.ArgumentChecks;
import org.constellation.ServiceDef;
import org.constellation.ServiceDef.Specification;
import org.constellation.wps.configuration.WPSConfigurer;
import org.constellation.wps.ws.WPSWorker;
import org.constellation.ws.CstlServiceException;
import org.constellation.ws.MimeType;
import org.constellation.ws.ServiceConfigurer;
import org.constellation.ws.Worker;
import org.constellation.ws.rs.OGCWebService;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.ows.xml.RequestBase;
import org.geotoolkit.ows.xml.v110.AcceptVersionsType;
import org.geotoolkit.ows.xml.v110.CodeType;
import org.geotoolkit.ows.xml.v110.ExceptionReport;
import org.geotoolkit.wps.xml.WPSMarshallerPool;
import org.geotoolkit.wps.xml.v100.DataInputsType;
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.GetCapabilities;
import org.geotoolkit.wps.xml.v100.InputReferenceType;
import org.geotoolkit.wps.xml.v100.InputType;
import org.geotoolkit.wps.xml.v100.OutputDefinitionType;
import org.geotoolkit.wps.xml.v100.ProcessDescriptions;
import org.geotoolkit.wps.xml.v100.ResponseDocumentType;
import org.geotoolkit.wps.xml.v100.ResponseFormType;
import org.geotoolkit.wps.xml.v100.WPSCapabilitiesType;
import javax.inject.Singleton;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.NotSupportedException;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.referencing.CommonCRS;
import static org.constellation.api.QueryConstants.*;
import static org.constellation.api.QueryConstants.ACCEPT_VERSIONS_PARAMETER;
import static org.constellation.api.QueryConstants.REQUEST_PARAMETER;
import static org.constellation.api.QueryConstants.SERVICE_PARAMETER;
import static org.constellation.api.QueryConstants.UPDATESEQUENCE_PARAMETER;
import static org.constellation.api.QueryConstants.VERSION_PARAMETER;
import org.constellation.wps.utils.WPSUtils;
import static org.constellation.wps.ws.WPSConstant.AS_REFERENCE_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.BODY_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.BODY_REFERENCE_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.DATA_INPUTS_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.DATA_TYPE_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.DESCRIBEPROCESS;
import static org.constellation.wps.ws.WPSConstant.ENCODING_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.EXECUTE;
import static org.constellation.wps.ws.WPSConstant.FORMAT_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.GETCAPABILITIES;
import static org.constellation.wps.ws.WPSConstant.HEADER_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.HREF_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.LINEAGE_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.METHOD_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.MIME_TYPE_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.RAW_DATA_OUTPUT_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.RESPONSE_DOCUMENT_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.SCHEMA_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.STATUS_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.STORE_EXECUTE_RESPONSE_PARAMETER;
import static org.constellation.wps.ws.WPSConstant.UOM_PARAMETER;
import org.geotoolkit.gml.xml.v311.DirectPositionType;
import static org.geotoolkit.ows.xml.OWSExceptionCode.INVALID_FORMAT;
import static org.geotoolkit.ows.xml.OWSExceptionCode.INVALID_REQUEST;
import static org.geotoolkit.ows.xml.OWSExceptionCode.MISSING_PARAMETER_VALUE;
import static org.geotoolkit.ows.xml.OWSExceptionCode.OPERATION_NOT_SUPPORTED;
import org.geotoolkit.ows.xml.v110.BoundingBoxType;
import org.geotoolkit.referencing.CRS;
import org.geotoolkit.wps.xml.v100.ComplexDataType;
import org.geotoolkit.wps.xml.v100.LiteralDataType;
import org.opengis.geometry.Envelope;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.FactoryException;
import org.opengis.geometry.MismatchedDimensionException;
/**
* WPS web service class.
*
* @author Quentin Boileau (Geomatys).
* @author Benjamin Garcia (Geomatys).
*
* @version 0.9
*/
@Path("wps/{serviceId}")
@Singleton
public class WPSService extends OGCWebService<WPSWorker> {
/**
* The default CRS to apply on a bounding box when no CRS are provided with
* a GET request using the execute method
*/
private static final CoordinateReferenceSystem DEFAULT_CRS = CommonCRS.WGS84.normalizedGeographic();
/**
* Executor thread pool.
*/
public static ExecutorService EXECUTOR;
/**
* Build a new instance of the webService and initialize the JAXB context.
*/
public WPSService() {
super(Specification.WPS);
setFullRequestLog(true);
//we build the JAXB marshaller and unmarshaller to bind java/xml
setXMLContext(WPSMarshallerPool.getInstance());
LOGGER.log(Level.INFO, "WPS REST service running ({0} instances)", getWorkerMapSize());
}
@Override
protected Class getWorkerClass() {
return WPSWorker.class;
}
/**
* {@inheritDoc}
*/
@Override
protected Class<? extends ServiceConfigurer> getConfigurerClass() {
return WPSConfigurer.class;
}
@Override
public void destroy() {
super.destroy();
//Shutdown the WPS scheduler.
LOGGER.log(Level.INFO, "Shutdown executor pool");
if (EXECUTOR != null) {
EXECUTOR.shutdown();
EXECUTOR = null;
}
}
public static synchronized ExecutorService getExecutor() {
if (EXECUTOR == null) {
EXECUTOR = Executors.newCachedThreadPool();
}
return EXECUTOR;
}
@Override
protected Response treatIncomingRequest(final Object objectRequest, final WPSWorker worker) {
final UriInfo uriContext = getUriContext();
ServiceDef version = null;
String requestName = null;
try {
// Handle an empty request by sending a basic web page.
if ((null == objectRequest) && (0 == uriContext.getQueryParameters().size())) {
return Response.ok(getIndexPage(), MimeType.TEXT_HTML).build();
}
// if the request is not an xml request we fill the request parameter.
final RequestBase request;
if (objectRequest == null) {
//build objectRequest from parameters
version = worker.getVersionFromNumber(getParameter(VERSION_PARAMETER, false)); // needed if exception is launch before request build
requestName = getParameter(REQUEST_PARAMETER, true);
request = adaptQuery(requestName, worker);
} else if (objectRequest instanceof RequestBase) {
request = (RequestBase) objectRequest;
} else {
throw new CstlServiceException("The operation " + objectRequest.getClass().getName() + " is not supported by the service",
OPERATION_NOT_SUPPORTED, objectRequest.getClass().getName());
}
version = worker.getVersionFromNumber(request.getVersion());
/*
* GetCapabilities request
*/
if (request instanceof GetCapabilities) {
final GetCapabilities getcaps = (GetCapabilities) request;
final WPSCapabilitiesType capsResponse = worker.getCapabilities(getcaps);
return Response.ok(capsResponse, MimeType.TEXT_XML).build();
}
/*
* DescribeProcess request
*/
if (request instanceof DescribeProcess) {
final DescribeProcess descProc = (DescribeProcess) request;
final ProcessDescriptions describeResponse = worker.describeProcess(descProc);
return Response.ok(describeResponse, MimeType.TEXT_XML).build();
}
/*
* Execute request
*/
if (request instanceof Execute) {
final Execute exec = (Execute) request;
final Object executeResponse = worker.execute(exec);
boolean isTextPlain = false;
boolean isImage = false;
//if response is a literal
if (executeResponse instanceof String || executeResponse instanceof Double
|| executeResponse instanceof Float || executeResponse instanceof Integer
|| executeResponse instanceof Boolean || executeResponse instanceof Long) {
isTextPlain = true;
}
if (executeResponse instanceof RenderedImage || executeResponse instanceof BufferedImage
|| executeResponse instanceof GridCoverage2D) {
isImage = true;
}
if (isTextPlain) {
return Response.ok(executeResponse.toString(), MimeType.TEXT_PLAIN).build();
} else if (isImage) {
return Response.ok(executeResponse.toString(), MimeType.IMAGE_PNG).build();
} else {
return Response.ok(executeResponse, MimeType.TEXT_XML).build();
}
}
throw new CstlServiceException("This service can not handle the requested operation: " + request.getClass().getName() + ".",
OPERATION_NOT_SUPPORTED, requestName);
} catch (CstlServiceException ex) {
/*
* This block handles all the exceptions which have been generated anywhere in the service and transforms them to a response
* message for the protocol stream which JAXB, in this case, will then marshall and serialize into an XML message HTTP response.
*/
return processExceptionResponse(ex, version, worker);
}
}
/**
* {@inheritDoc}
*/
@Override
protected Response processExceptionResponse(final CstlServiceException ex, ServiceDef serviceDef, final Worker worker) {
logException(ex);
// SEND THE HTTP RESPONSE
if (serviceDef == null) {
serviceDef = ServiceDef.WPS_1_0_0;
}
final String exceptionCode = getOWSExceptionCodeRepresentation(ex.getExceptionCode());
final ExceptionReport report = new ExceptionReport(ex.getMessage(), exceptionCode, ex.getLocator(),
serviceDef.exceptionVersion.toString());
return Response.ok(report, MimeType.TEXT_XML).build();
}
/**
* Handle GET request in KVP.
*
* @param request
* @return GetCapabilities or DescribeProcess or Execute object.
* @throws CstlServiceException if request is unknow.
*/
public RequestBase adaptQuery(final String request, final Worker w) throws CstlServiceException {
if (GETCAPABILITIES.equalsIgnoreCase(request)) {
return adaptKvpGetCapabilitiesRequest();
} else if (DESCRIBEPROCESS.equalsIgnoreCase(request)) {
return adaptKvpDescribeProcessRequest(w);
} else if (EXECUTE.equalsIgnoreCase(request)) {
return adaptKvpExecuteRequest();
}
throw new CstlServiceException("The operation " + request + " is not supported by the service",
OPERATION_NOT_SUPPORTED, request);
}
/**
* Create GetCapabilities object from kvp parameters.
*
* @return GetCapabilities object.
* @throws CstlServiceException
*/
private GetCapabilities adaptKvpGetCapabilitiesRequest() throws CstlServiceException {
final GetCapabilities capabilities = new GetCapabilities();
capabilities.setService(getParameter(SERVICE_PARAMETER, true));
capabilities.setLanguage(getParameter(LANGUAGE_PARAMETER, false));
capabilities.setUpdateSequence(getParameter(UPDATESEQUENCE_PARAMETER, false));
final String acceptVersionsParam = getParameter(ACCEPT_VERSIONS_PARAMETER, false);
if(acceptVersionsParam!= null){
final String[] acceptVersions = acceptVersionsParam.split(",");
capabilities.setAcceptVersions(new AcceptVersionsType(acceptVersions));
}
return capabilities;
}
/**
* Create DescribeProcess object from kvp parameters.
*
* @return DescribeProcess object.
* @throws CstlServiceException if mandatory parameters are missing.
*/
private DescribeProcess adaptKvpDescribeProcessRequest(final Worker w) throws CstlServiceException {
final String strVersion = getParameter(VERSION_PARAMETER, true);
w.checkVersionSupported(strVersion, false);
final DescribeProcess describe = new DescribeProcess();
describe.setService(getParameter(SERVICE_PARAMETER, true));
describe.setVersion(strVersion);
describe.setLanguage(getParameter(LANGUAGE_PARAMETER, false));
final String allIdentifiers = getParameter(IDENTIFER_PARAMETER, true);
if (allIdentifiers != null) {
final String[] splitStr = allIdentifiers.split(",");
final List<String> identifiers = Arrays.asList(splitStr);
for (final String ident : identifiers) {
describe.getIdentifier().add(new CodeType(ident));
}
return describe;
} else {
throw new CstlServiceException("The parameter " + IDENTIFER_PARAMETER + " must be specified.",
MISSING_PARAMETER_VALUE, IDENTIFER_PARAMETER.toLowerCase());
}
}
/**
* Create Execute object from kvp parameters.
*
* @return Execute object.
* @throws CstlServiceException
*/
private Execute adaptKvpExecuteRequest() throws CstlServiceException {
final String version = getParameter(VERSION_PARAMETER, true);
final String service = getParameter(SERVICE_PARAMETER, true);
final String identifier = getParameter(IDENTIFER_PARAMETER, true);
final String language = getParameter(LANGUAGE_PARAMETER, true);
final String dataInputs = getParameter(DATA_INPUTS_PARAMETER, false);
final String respDoc = getParameter(RESPONSE_DOCUMENT_PARAMETER, false);
final String respRawData = getParameter(RAW_DATA_OUTPUT_PARAMETER, false);
final String lineage = getParameter(LINEAGE_PARAMETER, false);
final String status = getParameter(STATUS_PARAMETER, false);
final String storeExecuteResponse = getParameter(STORE_EXECUTE_RESPONSE_PARAMETER, false);
if (ServiceDef.getServiceDefinition(service, version).equals(ServiceDef.WPS_1_0_0)) {
final Execute exec = new Execute();
exec.setLanguage(language);
exec.setIdentifier(new CodeType(identifier));
// Check dataInputs nullity
if (dataInputs != null && !dataInputs.isEmpty())
exec.setDataInputs(extractInput(identifier, dataInputs));
boolean statusBoolean = extractOutputParameter(status);
boolean lineageBoolean = extractOutputParameter(lineage);
boolean storeExecuteResponseBoolean = extractOutputParameter(storeExecuteResponse);
if (respDoc != null && !respDoc.isEmpty()) {
ResponseFormType responseForm = extractResponseForm(respDoc, false);
responseForm.getResponseDocument().setLineage(lineageBoolean);
responseForm.getResponseDocument().setStatus(statusBoolean);
responseForm.getResponseDocument().setStoreExecuteResponse(storeExecuteResponseBoolean);
exec.setResponseForm(responseForm);
}
else if (respRawData != null && !respRawData.isEmpty()) {
if (lineage != null || status != null || storeExecuteResponse != null)
throw new CstlServiceException("lineage, status and storeExecuteResponse can not be set alongside a RawDataOutput");
exec.setResponseForm(extractResponseForm(respRawData, true));
}
return exec;
} else
throw new CstlServiceException("The version number specified for this request is not handled.");
}
/**
* Helper method that extracts a boolean from one of the following WPS GET
* argument : lineage, status, storeExecuteResponse
* @param parameter should be a string extracted using getParameter with one
* of the following arguments : STATUS_PARAMETER, LINEAGE_PARAMETER, STATUS_PARAMETER
* @return the value of the extracted boolean
* @throws CstlServiceException if the extraced value is not a boolean
*/
static boolean extractOutputParameter(String parameter) throws CstlServiceException {
if (parameter == null)
return false;
Map<String, Map> inputMap = extractDataFromKvpString(parameter);
// Since this method is used with three different parameters
// which are expected to just contain a boolean the map must have exactly
// one element.
assert inputMap.keySet().size() == 1;
String value = inputMap.keySet().iterator().next();
if ("true".equalsIgnoreCase(value))
return true;
else if ("false".equalsIgnoreCase(value))
return false;
throw new CstlServiceException("Expected values for lineage, status and storeExecuteResponse are true or false, the current value is " + value);
}
/**
* Helper method to detect wether a given input is a reference input or not
*
* Since a reference has a mandatory 'href' attribute the test consist in checking
* if this value exists in the attributes map.
*
* @param attributesMap attributes map for a given input
* @return true if a 'href' attribute is detected
*/
static boolean detectReference(final Map<String, String> attributesMap) {
return attributesMap.keySet().contains(HREF_PARAMETER.toLowerCase());
}
/**
* Helper method to detect wether a given input is a bounding box or not.
*
* Detecting a bounding box is a little tricky in some cases.
*
* When the literal has no declared attribute and bounding box has no CRS
* they can not be distinguished.
*
* eg :
* literal -> array=42,26,30,102
* bounding box -> bbox=104,16,27,83
*
* So the solution is to get the input's class type by using its
* ParameterDescriptor through the WPSUtils.getClassFromIOIdentifier method.
*
* But BoundingBox has no attributes, so if there's an attributes map with more
* than one key (because it contains always at least one key) it means that
* we are not reading a bounding box
*
* @param processIdentifier identifier of the current process
* @param inputIdentifier input's identifier which may be a bounding box
* @param attributesMap attributes map for a given input
* @return true if a bounding box is detected
*/
static boolean detectBoundingBox(final String processIdentifier, final String inputIdentifier, final Map<String, String> attributesMap) throws CstlServiceException {
if (attributesMap.keySet().size() > 1)
return false;
Class inputType;
try {
inputType = WPSUtils.getIOClassFromIdentifier(processIdentifier, inputIdentifier);
}
catch (ParameterNotFoundException ex) {
throw new CstlServiceException("Can not found the input " + inputIdentifier + " in the process " + processIdentifier + "\n" + ex.getLocalizedMessage());
}
return inputType == org.opengis.geometry.Envelope.class;
}
/**
* Parse the decoded arguments of a GET request
* @param processIdentifier process identifier, useful to give hints to the detect bounding box method
* @param dataInputs the decoded arguments
* @return a DataInputsType containing all the inputs read from the GET request
* and translated into WPS Object
* @throws CstlServiceException when an unknown attribute read
*/
static DataInputsType extractInput(final String processIdentifier, final String dataInputs) throws CstlServiceException {
ArgumentChecks.ensureNonEmpty("processIdentifier", processIdentifier);
ArgumentChecks.ensureNonEmpty("dataInputs", dataInputs);
final DataInputsType inputsType = new DataInputsType();
List<InputType> inputTypeList = inputsType.getInput();
Map<String, Map> inputMap = extractDataFromKvpString(dataInputs);
for (String inputIdentifier : inputMap.keySet()) {
Map<String, String> attributesMap = inputMap.get(inputIdentifier);
if (detectReference(attributesMap))
inputTypeList.add(readReference(inputIdentifier, attributesMap));
else if (detectBoundingBox(processIdentifier, inputIdentifier, attributesMap))
inputTypeList.add(readBoundingBoxData(inputIdentifier, attributesMap));
else
inputTypeList.add(readLiteralData(inputIdentifier, attributesMap));
}
return inputsType;
}
/**
* Read an input assuming it's a reference input and encapsulate it into an InputType
* @param inputIdentifier input identifier of the current input being processed
* @param attributesMap attributes map associated with the current input
* @return an InputReferenceType encapsulated into an InputType
* @throws CstlServiceException when an unknown attributes is read
*/
static InputType readReference(final String inputIdentifier, final Map<String, String> attributesMap) throws CstlServiceException {
final InputType inputType = new InputType();
inputType.setIdentifier(new CodeType(inputIdentifier));
InputReferenceType inputRef = new InputReferenceType();
for (String key : attributesMap.keySet()) {
String value = attributesMap.get(key);
if (key.equalsIgnoreCase(MIME_TYPE_PARAMETER) || key.equalsIgnoreCase(FORMAT_PARAMETER))
inputRef.setMimeType(value);
else if (key.equalsIgnoreCase(ENCODING_PARAMETER))
inputRef.setEncoding(value);
else if (key.equalsIgnoreCase(SCHEMA_PARAMETER))
inputRef.setSchema(value);
else if (key.equalsIgnoreCase(HREF_PARAMETER))
inputRef.setHref(value);
else if (key.equalsIgnoreCase(METHOD_PARAMETER) ||
key.equalsIgnoreCase(BODY_PARAMETER) ||
key.equalsIgnoreCase(BODY_REFERENCE_PARAMETER) ||
key.equalsIgnoreCase(HEADER_PARAMETER))
throw new NotSupportedException("The " + key + " attribute is not supported in a GET request");
else if (!(key.equals(inputIdentifier) && value == null))
throw new CstlServiceException("Trying to set an InputReference with the unknown attribute " + key + " (value : " + value + ")");
}
inputType.setReference(inputRef);
return inputType;
}
/**
* Read an input assuming it's a literal data and encapsulate it into an InputType
* @param inputIdentifier input identifier of the current input being processed
* @param attributesMap attributes of the current input
* @return a LiteralDataType encapsulated into an InputType
* @throws CstlServiceException when an unknown attribute is read
*/
static InputType readLiteralData(final String inputIdentifier, final Map<String, String> attributesMap) throws CstlServiceException {
final InputType inputType = new InputType();
inputType.setIdentifier(new CodeType(inputIdentifier));
LiteralDataType literalData = new LiteralDataType();
for (String key : attributesMap.keySet()) {
String value = attributesMap.get(key);
if (key.equalsIgnoreCase(DATA_TYPE_PARAMETER))
literalData.setDataType(value);
else if (key.equalsIgnoreCase(UOM_PARAMETER))
literalData.setUom(value);
else if (inputIdentifier.equals(key))
literalData.setValue(value);
else
throw new CstlServiceException("Trying to set a LiteralData with the unknown attribute " + key + " (value : " + value + ")");
}
// Ensure the literal has a value
if (literalData.getValue() == null || literalData.getValue().isEmpty())
throw new CstlServiceException("No value given to " + inputIdentifier);
DataType dataType = new DataType();
dataType.setLiteralData(literalData);
inputType.setData(dataType);
return inputType;
}
/**
* Read an input assuming it's a bounding box
* @param inputIdentifier identifier of the current input being processed
* @param attributesMap attributes of the current input
* @return a BoundingBoxType encapsulated into an InputType
*/
static InputType readBoundingBoxData(final String inputIdentifier, final Map<String, String> attributesMap) throws CstlServiceException {
final InputType inputType = new InputType();
inputType.setIdentifier(new CodeType(inputIdentifier));
DataType dataType = new DataType();
// A bounding box input has no attributes
// So the only key in the attributesMap is equals to inputIdentifier
// and its value is the bounding box string to parse
assert attributesMap.size() == 1;
String bboxString = attributesMap.values().iterator().next();
String comaSeparatedStrings[] = bboxString.split(",");
/*
* These variables indicate if there is a crs code in the coma-separated string
* and how many dimension there is in the crs
*/
CoordinateReferenceSystem crs = null;
//-- reading coordinate list.
//-- in case where length is odd the last interger should be equals to coordinates numbers
final List<Double> coords = new ArrayList<>();
// Pre analysis
for (String value : comaSeparatedStrings) {
if (NumberUtils.isNumber(value)) {
coords.add(Double.valueOf(value));
} else {
// If a CRS has been already read...abort
if (crs != null)
throw new CstlServiceException("Two CRS found while reading the " + inputIdentifier + " BoundingBox");
try {
// If when reading the crs you already read an odd number of
// bounding box coordinates there is a problem
if (coords.size() % 2 != 0)
throw new CstlServiceException("An odd number of bounding box coordinates has been read.");
crs = CRS.decode(value);
} catch (FactoryException ex) {
throw new CstlServiceException(ex);
}
}
}
final int coordsListLength = coords.size();
// If no coordinate has been read...abort
if (coords.isEmpty())
throw new CstlServiceException("Could not read any coordinate from the BoundingBox");
// Extract BoundingBox dimension
final int bboxDimension = coordsListLength >> 1;
//-- if coordinates numbers is odd
if (coordsListLength % 2 != 0) {//-- coordinateElement & 1 == 0
/*
* In the the following strings the number N tell us how many dimension
* there is in the bounding box :
* 46,102,... 47,103,... crs code,N
* 46,102,... 47,103, ...N,crs code
*
* But this number is not mandatory.
*/
final int evenListLength = coordsListLength & ~1;//-- = -1 on odd number
final int dimensionHint = (int) StrictMath.round(coords.get(evenListLength));
//-- check that cast double to integer has no problem
if (StrictMath.abs(coords.get(evenListLength) - dimensionHint) > 1E-12)
throw new CstlServiceException("The dimension parameter is not an integer : " + coords.get(evenListLength));
ArgumentChecks.ensureStrictlyPositive("dimensionHint", dimensionHint);
assert dimensionHint >= 2 : "Expected dimension hint equal or greater than 4, adapted for Geographical coordinates. Found : " + dimensionHint;
if (evenListLength != dimensionHint * 2)
throw new CstlServiceException("Expected " + evenListLength + " coordinates whereas " + dimensionHint + " was expected.");
}
if (crs != null && bboxDimension != crs.getCoordinateSystem().getDimension())
throw new CstlServiceException("Reading coordinates number does not match with CRS dimension number.\n"
+ " CRS dimension : "+crs.getCoordinateSystem().getDimension()+". Coordinates number : "+bboxDimension);
//-- bind list -> array
final double[] coordsArrayLower = new double[bboxDimension];
final double[] coordsArrayUpper = new double[bboxDimension];
for (int i = 0; i < bboxDimension; i++) {
coordsArrayLower[i] = coords.get(i);
coordsArrayUpper[i] = coords.get(i + bboxDimension);
}
final GeneralEnvelope generalEnvelope = new GeneralEnvelope(coordsArrayLower, coordsArrayUpper);
if (crs == null) {
// If no CRS are provided we set a default one which is the WGS84.
// But this CRS can not be applied on every bounding box, so we have to
// check the dimensions and raise an error when they are different
if (bboxDimension > DEFAULT_CRS.getCoordinateSystem().getDimension())
throw new CstlServiceException("No CRS provided and the default 2D CRS"
+ " can not be applied because the bounding box has " + bboxDimension + " dimensions.");
generalEnvelope.setCoordinateReferenceSystem(DEFAULT_CRS);
}
else
generalEnvelope.setCoordinateReferenceSystem(crs);
dataType.setBoundingBoxData(new BoundingBoxType(generalEnvelope));
inputType.setData(dataType);
return inputType;
}
/**
* Parse the decoded arguments of a GET request in order to extract the response
* form.
*
* This method assumes that responseString contains only the response field of
* the GET request and that it is URL decoded
*
* @param responseString the string containing document response attributes
* @param isRawData set to true if responseString contains raw data
* @return a ResponseDocumentType encapsulated into a ResponseFormType
* @throws CstlServiceException when an unknown attribute is read
*/
static ResponseFormType extractResponseForm(final String responseString, boolean isRawData) throws CstlServiceException {
ArgumentChecks.ensureNonEmpty("responseString", responseString);
Map<String, Map> inputMap = extractDataFromKvpString(responseString);
ResponseDocumentType responseDocument = new ResponseDocumentType();
ResponseFormType responseForm = new ResponseFormType();
responseForm.setResponseDocument(responseDocument);
for (String inputIdentifier : inputMap.keySet()) {
Map<String, String> attributesMap = inputMap.get(inputIdentifier);
OutputDefinitionType docOutput;
if (isRawData)
docOutput = new OutputDefinitionType();
else
docOutput = new DocumentOutputDefinitionType();
docOutput.setIdentifier(new CodeType(inputIdentifier));
for (String key : attributesMap.keySet()) {
String value = attributesMap.get(key);
if (key.equalsIgnoreCase(MIME_TYPE_PARAMETER) || key.equalsIgnoreCase(FORMAT_PARAMETER))
docOutput.setMimeType(value);
else if (key.equalsIgnoreCase(ENCODING_PARAMETER))
docOutput.setEncoding(value);
else if (key.equalsIgnoreCase(SCHEMA_PARAMETER))
docOutput.setSchema(value);
else if (key.equalsIgnoreCase(UOM_PARAMETER))
docOutput.setUom(value);
else if (key.equalsIgnoreCase(AS_REFERENCE_PARAMETER)) {
if (!isRawData)
((DocumentOutputDefinitionType)docOutput).setAsReference(Boolean.parseBoolean(value));
else
throw new CstlServiceException("Trying to set RawDataOutput with unknown attribute " + key + " (value : " + value + ")");
}
else if (!key.equals(inputIdentifier)) {
if (isRawData)
throw new CstlServiceException("Trying to set RawDataOutput with unknown attribute " + key + " (value : " + value + ")");
else
throw new CstlServiceException("Trying to set DocumentOutputDefinition with unknown attribute " + key + " (value : " + value + ")");
}
}
// We can have more than one DocumentOutputDefinition
// but we can have just one RawDataOutput. Since the code to read
// rawdata attribute is almost the same as the one to read
// DocumentOutputDefinition (see the above condition against
// AS_REFERENCE_PARAMETER), we kept everything in the same method
// and just break the loop when a RawData has been read
if (isRawData) {
responseForm.setRawDataOutput(docOutput);
break;
}
else
responseDocument.getOutput().add((DocumentOutputDefinitionType) docOutput);
}
return responseForm;
}
static Map extractDataFromKvpString(final String inputString) throws CstlServiceException {
final String[] allInputs = inputString.split(";");
Map<String, Map> inputMap = new HashMap<>();
for (String input : allInputs) {
final String[] attribs = input.split("@");
final String inputIdent = attribs[0].split("=")[0];
final Map<String, String> attributsMap = new HashMap<>();
for (String attribut : attribs) {
String[] splitAttribute = attribut.split("=");
if (splitAttribute.length == 2) {
attributsMap.put(splitAttribute[0], splitAttribute[1]);
} else if (splitAttribute.length == 1) {
attributsMap.put(splitAttribute[0], null);
} else {
throw new CstlServiceException("Invalid DataInputs format", INVALID_FORMAT, VERSION_PARAMETER.toLowerCase());
}
}
inputMap.put(inputIdent, attributsMap);
}
return inputMap;
}
/**
* Get an html page for the root resource.
*/
private String getIndexPage() {
return "<html>\n"
+ " <title>Constellation WPS</title>\n"
+ " <body>\n"
+ " <h1><i>Constellation:</i></h1>\n"
+ " <h1> Web Processing Service</h1>\n"
+ " <p>\n"
+ " In order to access this service, you must form a valid request.\n"
+ " </p\n"
+ " <p>\n"
+ " Try using a <a href=\"" + getUriContext().getAbsolutePath().toString()
+ "?service=WPS&request=GetCapabilities\""
+ ">Get Capabilities</a> request to obtain the 'Capabilities'<br>\n"
+ " document which describes the resources available on this server.\n"
+ " </p>\n"
+ " </body>\n"
+ "</html>\n";
}
}