/* * 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.wfs.ws.rs; // J2SE dependencies import java.io.IOException; import org.apache.sis.xml.MarshallerPool; import org.constellation.ServiceDef; import org.constellation.ServiceDef.Specification; import org.constellation.wfs.ws.DefaultWFSWorker; import org.constellation.wfs.ws.WFSWorker; import org.constellation.ws.CstlServiceException; import org.constellation.ws.UnauthorizedException; import org.constellation.ws.WebServiceUtilities; import org.constellation.ws.Worker; import org.constellation.ws.rs.GridWebService; import org.constellation.xml.PrefixMappingInvocationHandler; import org.geotoolkit.client.RequestsUtilities; import org.geotoolkit.ogc.xml.FilterXmlFactory; import org.geotoolkit.ogc.xml.SortBy; import org.geotoolkit.ogc.xml.XMLFilter; import org.geotoolkit.ows.xml.AcceptFormats; import org.geotoolkit.ows.xml.AcceptVersions; import org.geotoolkit.ows.xml.ExceptionResponse; import org.geotoolkit.ows.xml.RequestBase; import org.geotoolkit.ows.xml.Sections; import org.geotoolkit.ows.xml.v100.SectionsType; import org.geotoolkit.wfs.xml.AllSomeType; import org.geotoolkit.wfs.xml.BaseRequest; import org.geotoolkit.wfs.xml.CreateStoredQuery; import org.geotoolkit.wfs.xml.DeleteElement; import org.geotoolkit.wfs.xml.DescribeFeatureType; import org.geotoolkit.wfs.xml.DescribeStoredQueries; import org.geotoolkit.wfs.xml.DropStoredQuery; import org.geotoolkit.wfs.xml.GetCapabilities; import org.geotoolkit.wfs.xml.GetFeature; import org.geotoolkit.wfs.xml.GetGmlObject; import org.geotoolkit.wfs.xml.GetPropertyValue; import org.geotoolkit.wfs.xml.ListStoredQueries; import org.geotoolkit.wfs.xml.LockFeature; import org.geotoolkit.wfs.xml.Parameter; import org.geotoolkit.wfs.xml.ParameterExpression; import org.geotoolkit.wfs.xml.Query; import org.geotoolkit.wfs.xml.ResultTypeType; import org.geotoolkit.wfs.xml.StoredQuery; import org.geotoolkit.wfs.xml.Transaction; import org.opengis.filter.sort.SortOrder; import javax.inject.Singleton; import javax.ws.rs.Path; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import java.io.InputStream; import java.io.StringReader; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.UUID; import java.util.logging.Level; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.PathParam; import static org.constellation.api.QueryConstants.ACCEPT_FORMATS_PARAMETER; import static org.constellation.api.QueryConstants.ACCEPT_VERSIONS_PARAMETER; import static org.constellation.api.QueryConstants.REQUEST_PARAMETER; import static org.constellation.api.QueryConstants.SECTIONS_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 static org.constellation.wfs.ws.WFSConstants.FILTER; import static org.constellation.wfs.ws.WFSConstants.GML_3_1_1; import static org.constellation.wfs.ws.WFSConstants.GML_3_2_1; import org.constellation.wfs.ws.WFSConstants.GetXSD; import static org.constellation.wfs.ws.WFSConstants.HANDLE; import static org.constellation.wfs.ws.WFSConstants.NAMESPACE; import static org.constellation.wfs.ws.WFSConstants.STR_CREATE_STORED_QUERY; import static org.constellation.wfs.ws.WFSConstants.STR_DESCRIBEFEATURETYPE; import static org.constellation.wfs.ws.WFSConstants.STR_DESCRIBE_STORED_QUERIES; import static org.constellation.wfs.ws.WFSConstants.STR_DROP_STORED_QUERY; import static org.constellation.wfs.ws.WFSConstants.STR_GETCAPABILITIES; import static org.constellation.wfs.ws.WFSConstants.STR_GETFEATURE; import static org.constellation.wfs.ws.WFSConstants.STR_GETGMLOBJECT; import static org.constellation.wfs.ws.WFSConstants.STR_GET_PROPERTY_VALUE; import static org.constellation.wfs.ws.WFSConstants.STR_LIST_STORED_QUERIES; import static org.constellation.wfs.ws.WFSConstants.STR_LOCKFEATURE; import static org.constellation.wfs.ws.WFSConstants.STR_TRANSACTION; import static org.constellation.wfs.ws.WFSConstants.STR_XSD; import org.constellation.ws.WSEngine; import static org.geotoolkit.ows.xml.OWSExceptionCode.INVALID_PARAMETER_VALUE; import static org.geotoolkit.ows.xml.OWSExceptionCode.MISSING_PARAMETER_VALUE; import org.geotoolkit.util.FileUtilities; import org.geotoolkit.wfs.xml.InsertElement; import org.geotoolkit.wfs.xml.Property; import org.geotoolkit.wfs.xml.ReplaceElement; import org.geotoolkit.wfs.xml.StoredQueryDescription; import org.geotoolkit.wfs.xml.UpdateElement; import org.geotoolkit.wfs.xml.WFSXmlFactory; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildAcceptFormat; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildAcceptVersion; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildBBOXFilter; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildDecribeFeatureType; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildDeleteElement; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildDescribeStoredQueries; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildDropStoredQuery; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildGetCapabilities; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildGetFeature; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildGetGmlObject; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildGetPropertyValue; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildListStoredQueries; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildLockFeature; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildParameter; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildQuery; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildSections; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildSortBy; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildStoredQuery; import static org.geotoolkit.wfs.xml.WFSXmlFactory.buildTransaction; import org.w3c.dom.Node; // JAXB dependencies // jersey dependencies // constellation dependencies // Geotoolkit dependencies /** * * @author Guilhem Legal (Geomatys) */ @Path("wfs/{serviceId}") @Singleton public class WFSService extends GridWebService<WFSWorker> { static { System.setProperty("javax.xml.stream.XmlInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); System.setProperty("javax.xml.stream.XmlEventFactory", "com.ctc.wstx.stax.WstxEventFactory"); System.setProperty("javax.xml.stream.XmlOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); } /** * Build a new Restful WFS service. */ public WFSService() { super(Specification.WFS); try { final MarshallerPool pool = new MarshallerPool(JAXBContext.newInstance( "org.geotoolkit.wfs.xml.v110" + ":org.geotoolkit.ogc.xml.v110" + ":org.geotoolkit.wfs.xml.v200" + ":org.geotoolkit.gml.xml.v311" + ":org.geotoolkit.gml.xml.v321" + ":org.geotoolkit.xsd.xml.v2001" + ":org.apache.sis.internal.jaxb.geometry"), null); setXMLContext(pool); LOGGER.log(Level.INFO, "WFS REST service running ({0} instances)", getWorkerMapSize()); } catch (JAXBException ex){ LOGGER.warning("The WFS REST service is not running.\ncause : Error creating XML context.\n error : " + ex.getMessage() + "\n details: " + ex.toString()); } } @Override protected Class getWorkerClass() { return DefaultWFSWorker.class; } /** * {@inheritDoc} */ @Override public Response treatIncomingRequest(final Object objectRequest, final WFSWorker worker) { ServiceDef version = null; try { // if the request is not an xml request we fill the request parameter. final RequestBase request; if (objectRequest == null) { version = worker.getVersionFromNumber(getParameter(VERSION_PARAMETER, false)); // needed if exception is launch before request build request = adaptQuery(getParameter(REQUEST_PARAMETER, true), worker); } else if (objectRequest instanceof RequestBase) { request = (RequestBase) objectRequest; } else { throw new CstlServiceException("The operation " + objectRequest.getClass().getName() + " is not supported by the service", INVALID_PARAMETER_VALUE, "request"); } version = worker.getVersionFromNumber(request.getVersion()); if (request instanceof GetCapabilities) { final GetCapabilities model = (GetCapabilities) request; String outputFormat = model.getFirstAcceptFormat(); if (outputFormat == null) { outputFormat = "application/xml"; } return Response.ok(worker.getCapabilities(model), outputFormat).build(); } else if (request instanceof DescribeFeatureType) { final DescribeFeatureType model = (DescribeFeatureType) request; String requestOutputFormat = model.getOutputFormat(); final MediaType outputFormat; if (requestOutputFormat == null || requestOutputFormat.equals("text/xml; subtype=gml/3.1.1")) { outputFormat = GML_3_1_1; } else if (requestOutputFormat.equals("text/xml; subtype=gml/3.2.1") || requestOutputFormat.equals("text/xml; subtype=gml/3.2")|| requestOutputFormat.equals("application/gml+xml; version=3.2")) { outputFormat = GML_3_2_1; } else { try { outputFormat = MediaType.valueOf(requestOutputFormat); } catch (IllegalArgumentException ex) { throw new CstlServiceException(ex, INVALID_PARAMETER_VALUE, "outputFormat"); } } LOGGER.log(Level.INFO, "outputFormat asked:{0}", requestOutputFormat); return Response.ok(worker.describeFeatureType(model), outputFormat).build(); } else if (request instanceof GetFeature) { final GetFeature model = (GetFeature) request; String requestOutputFormat = model.getOutputFormat(); final MediaType outputFormat; if (requestOutputFormat == null || requestOutputFormat.equals("text/xml; subtype=gml/3.1.1")) { outputFormat = GML_3_1_1; } else if (requestOutputFormat.equals("text/xml; subtype=gml/3.2.1") || requestOutputFormat.equals("text/xml; subtype=gml/3.2") || requestOutputFormat.equals("application/gml+xml; version=3.2")) { outputFormat = GML_3_2_1; } else { outputFormat = MediaType.valueOf(requestOutputFormat); } final Object response = worker.getFeature(model); return Response.ok(response, outputFormat).build(); } else if (request instanceof GetPropertyValue) { final GetPropertyValue model = (GetPropertyValue) request; String requestOutputFormat = model.getOutputFormat(); final MediaType outputFormat; if (requestOutputFormat == null || requestOutputFormat.equals("text/xml; subtype=gml/3.1.1")) { outputFormat = GML_3_1_1; } else if (requestOutputFormat.equals("text/xml; subtype=gml/3.2.1") || requestOutputFormat.equals("text/xml; subtype=gml/3.2") || requestOutputFormat.equals("application/gml+xml; version=3.2")) { outputFormat = GML_3_2_1; } else { outputFormat = MediaType.valueOf(requestOutputFormat); } final Object response = worker.getPropertyValue(model); return Response.ok(response, outputFormat).build(); } else if (request instanceof CreateStoredQuery) { final CreateStoredQuery model = (CreateStoredQuery) request; final WFSResponseWrapper response = new WFSResponseWrapper(worker.createStoredQuery(model),version.version.toString()); return Response.ok(response, MediaType.TEXT_XML).build(); } else if (request instanceof DropStoredQuery) { final DropStoredQuery model = (DropStoredQuery) request; final WFSResponseWrapper response = new WFSResponseWrapper(worker.dropStoredQuery(model),version.version.toString()); return Response.ok(response, MediaType.TEXT_XML).build(); } else if (request instanceof ListStoredQueries) { final ListStoredQueries model = (ListStoredQueries) request; final WFSResponseWrapper response = new WFSResponseWrapper(worker.listStoredQueries(model),version.version.toString()); return Response.ok(response, MediaType.TEXT_XML).build(); } else if (request instanceof DescribeStoredQueries) { final DescribeStoredQueries model = (DescribeStoredQueries) request; final WFSResponseWrapper response = new WFSResponseWrapper(worker.describeStoredQueries(model),version.version.toString()); return Response.ok(response, MediaType.TEXT_XML).build(); } else if (request instanceof GetGmlObject) { final GetGmlObject model = (GetGmlObject) request; final WFSResponseWrapper response = new WFSResponseWrapper(worker.getGMLObject(model),version.version.toString()); return Response.ok(response, MediaType.TEXT_XML).build(); } else if (request instanceof LockFeature) { final LockFeature model = (LockFeature) request; return Response.ok(worker.lockFeature(model), MediaType.TEXT_XML).build(); } else if (request instanceof Transaction) { final Transaction model = (Transaction) request; return Response.ok(worker.transaction(model), MediaType.TEXT_XML).build(); } else if (request instanceof GetXSD) { final GetXSD model = (GetXSD) request; return Response.ok(worker.getXsd(model), MediaType.TEXT_XML).build(); } throw new CstlServiceException("The operation " + request.getClass().getName() + " is not supported by the service", INVALID_PARAMETER_VALUE, "request"); } catch (CstlServiceException ex) { return processExceptionResponse(ex, version, worker); } } /** * {@inheritDoc} */ @Override protected Response processExceptionResponse(final CstlServiceException ex, ServiceDef serviceDef, final Worker worker) { // asking for authentication if (ex instanceof UnauthorizedException) { return Response.status(Response.Status.UNAUTHORIZED).header("WWW-Authenticate", " Basic").build(); } logException(ex); if (serviceDef == null) { serviceDef = worker.getBestVersion(null); } final String version = serviceDef.exceptionVersion.toString(); final String exceptionCode = getOWSExceptionCodeRepresentation(ex.getExceptionCode()); final ExceptionResponse report; if (serviceDef.exceptionVersion.toString().equals("1.0.0")) { report = new org.geotoolkit.ows.xml.v100.ExceptionReport(ex.getMessage(), exceptionCode, ex.getLocator(), version); return Response.ok(report, "text/xml").build(); } else { report = new org.geotoolkit.ows.xml.v110.ExceptionReport(ex.getMessage(), exceptionCode, ex.getLocator(), version); final int port = getHttpCodeFromErrorCode(exceptionCode); return Response.ok(report, "text/xml").status(port).build(); } } private int getHttpCodeFromErrorCode(final String exceptionCode) { if ("CannotLockAllFeatures".equals(exceptionCode) || "FeaturesNotLocked".equals(exceptionCode) || "InvalidLockId".equals(exceptionCode) || "InvalidValue".equals(exceptionCode) || "OperationParsingFailed".equals(exceptionCode) || "OperationNotSupported".equals(exceptionCode) || "MissingParameterValue".equals(exceptionCode) || "InvalidParameterValue".equals(exceptionCode) || "VersionNegotiationFailed".equals(exceptionCode) || "InvalidUpdateSequence".equals(exceptionCode) || "OptionNotSupported".equals(exceptionCode) || "NoApplicableCode".equals(exceptionCode)) { return 400; } else if ("DuplicateStoredQueryIdValue".equals(exceptionCode) || "DuplicateStoredQueryParameterName".equals(exceptionCode)) { return 409; } else if ("LockHasExpired".equals(exceptionCode) || "OperationProcessingFailed".equals(exceptionCode)) { return 403; } else { return 200; } } /** * Override the parent method in order to extract namespace mapping * during the unmarshall. * * @param unmarshaller * @param is * @return * @throws JAXBException */ @Override protected Object unmarshallRequest(final Unmarshaller unmarshaller, final InputStream is) throws JAXBException { final Map<String, String> prefixMapping = new LinkedHashMap<>(); return unmarshallRequestWithMapping(unmarshaller, is, prefixMapping); } @Override protected Object unmarshallRequestWithMapping(final Unmarshaller unmarshaller, final InputStream is, final Map<String, String> prefixMapping) throws JAXBException { final JAXBEventHandler handler = new JAXBEventHandler(); unmarshaller.setEventHandler(handler); try { final XMLEventReader rootEventReader = XMLInputFactory.newInstance().createXMLEventReader(is); final XMLEventReader eventReader = (XMLEventReader) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{XMLEventReader.class}, new PrefixMappingInvocationHandler(rootEventReader, prefixMapping)); Object request = unmarshaller.unmarshal(eventReader); if (request instanceof JAXBElement) { request = ((JAXBElement)request).getValue(); } if (request instanceof BaseRequest) { ((BaseRequest)request).setPrefixMapping(prefixMapping); } return request; } catch (XMLStreamException ex) { throw new JAXBException(ex); } } private RequestBase adaptQuery(final String request, final WFSWorker worker) throws CstlServiceException { if (STR_GETCAPABILITIES.equalsIgnoreCase(request)) { return createNewGetCapabilitiesRequest(worker); } else if (STR_DESCRIBEFEATURETYPE.equalsIgnoreCase(request)) { return createNewDescribeFeatureTypeRequest(worker); } else if (STR_GETFEATURE.equalsIgnoreCase(request)) { return createNewGetFeatureRequest(worker); } else if (STR_GETGMLOBJECT.equalsIgnoreCase(request)) { return createNewGetGmlObjectRequest(worker); } else if (STR_LOCKFEATURE.equalsIgnoreCase(request)) { return createNewLockFeatureRequest(worker); } else if (STR_TRANSACTION.equalsIgnoreCase(request)) { return createNewTransactionRequest(worker); } else if (STR_DESCRIBE_STORED_QUERIES.equalsIgnoreCase(request)) { return createNewDescribeStoredQueriesRequest(worker); } else if (STR_LIST_STORED_QUERIES.equalsIgnoreCase(request)) { return createNewListStoredQueriesRequest(worker); } else if (STR_GET_PROPERTY_VALUE.equalsIgnoreCase(request)) { return createNewGetPropertyValueRequest(worker); } else if (STR_CREATE_STORED_QUERY.equalsIgnoreCase(request)) { return createNewCreateStoredQueryRequest(); } else if (STR_DROP_STORED_QUERY.equalsIgnoreCase(request)) { return createNewDropStoredQueryRequest(worker); } else if (STR_XSD.equalsIgnoreCase(request)) { return createNewXsdRequest(worker); } throw new CstlServiceException("The operation " + request + " is not supported by the service", INVALID_PARAMETER_VALUE, "request"); } private DescribeFeatureType createNewDescribeFeatureTypeRequest(final Worker w) throws CstlServiceException { String outputFormat = getParameter("outputFormat", false); final String handle = getParameter(HANDLE, false); final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); w.checkVersionSupported(version, false); final String namespace = getParameter(NAMESPACE, false); final Map<String, String> mapping = WebServiceUtilities.extractNamespace(namespace); final String typeName = getParameter("typeName", false); final List<QName> typeNames = extractTypeName(typeName, mapping); return buildDecribeFeatureType(version, service, handle, typeNames, outputFormat); } private GetCapabilities createNewGetCapabilitiesRequest(final Worker w) throws CstlServiceException { String version = getParameter(ACCEPT_VERSIONS_PARAMETER, false); String currentVersion = getParameter(VERSION_PARAMETER, false); if (currentVersion == null) { currentVersion = w.getBestVersion(null).version.toString(); } w.checkVersionSupported(currentVersion, true); final List<String> versions = new ArrayList<>(); if (version != null) { String[] vArray = version.split(","); versions.addAll(Arrays.asList(vArray)); } else { versions.add(currentVersion); } final AcceptVersions acceptVersions = buildAcceptVersion(currentVersion, versions); final String updateSequence = getParameter(UPDATESEQUENCE_PARAMETER, false); final AcceptFormats formats = buildAcceptFormat(currentVersion, Arrays.asList(getParameter(ACCEPT_FORMATS_PARAMETER, false))); //We transform the String of sections in a list. //In the same time we verify that the requested sections are valid. final Sections sections; final String section = getParameter(SECTIONS_PARAMETER, false); if (section != null && !section.equalsIgnoreCase("All")) { final List<String> requestedSections = new ArrayList<>(); final StringTokenizer tokens = new StringTokenizer(section, ",;"); while (tokens.hasMoreTokens()) { final String token = tokens.nextToken().trim(); if (SectionsType.getExistingSections().contains(token)){ requestedSections.add(token); } else { throw new CstlServiceException("The section " + token + " does not exist", INVALID_PARAMETER_VALUE, "Sections"); } } sections = buildSections(currentVersion, requestedSections); } else { sections = null; } return buildGetCapabilities(currentVersion, acceptVersions, sections, formats, updateSequence, getParameter(SERVICE_PARAMETER, true)); } private GetFeature createNewGetFeatureRequest(final WFSWorker worker) throws CstlServiceException { Integer maxFeature = null; final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String handle = getParameter(HANDLE, false); final String outputFormat = getParameter("outputFormat", false); final String max; if (version.equals("2.0.0")) { max = getParameter("count", false); } else { max = getParameter("maxfeatures", false); } if (max != null) { try { maxFeature = Integer.parseInt(max); } catch (NumberFormatException ex) { throw new CstlServiceException("Unable to parse the integer maxfeatures parameter" + max, INVALID_PARAMETER_VALUE, "MaxFeatures"); } } final Integer startIndex = parseOptionalIntegerParam("StartIndex"); final String namespace = getParameter(NAMESPACE, false); final Map<String, String> mapping = WebServiceUtilities.extractNamespace(namespace); final String result = getParameter("resultType", false); ResultTypeType resultType = null; if (result != null) { resultType = ResultTypeType.fromValue(result.toLowerCase()); } final String featureVersion = getParameter("featureVersion", false); String featureId; if (version.equals("2.0.0")) { featureId = getParameter("ressourceid", false); } else { featureId = getParameter("featureid", false); } boolean mandatory = true; if (featureId != null) { //cite test fix if (featureId.endsWith(",")) { featureId = featureId.substring(0, featureId.length() - 1); } mandatory = false; } final String storedQuery = getParameter("storedquery_id", false); final List<Parameter> parameters; if (storedQuery != null) { mandatory = false; // extract stored query params parameters = extractParameters(worker, storedQuery, version, mapping); } else { parameters = new ArrayList<>(); } final String typeName; if (version.equals("2.0.0")) { typeName = getParameter("typeNames", mandatory); } else { typeName = getParameter("typeName", mandatory); } final List<QName> typeNames = extractTypeName(typeName, mapping); final String srsName = getParameter("srsName", false); final SortBy sortBy = parseSortByParameter(version); final List<String> propertyNames = parseCommaSeparatedParameter("propertyName"); if (featureId != null) { final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, featureId); final Query query = buildQuery(version, filter, typeNames, featureVersion, srsName, sortBy, propertyNames); return buildGetFeature(version, service, handle, startIndex, maxFeature, query, resultType, outputFormat); } else if (storedQuery != null) { final StoredQuery query = buildStoredQuery(version, storedQuery, handle, parameters); return buildGetFeature(version, service, handle, startIndex, maxFeature, query, resultType, outputFormat); } final Object xmlFilter = getComplexParameter(FILTER, false); XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } final String bbox = getParameter("bbox", false); if (bbox != null) { final double[] coodinates = new double[4]; final StringTokenizer tokens = new StringTokenizer(bbox, ",;"); int index = 0; while (tokens.hasMoreTokens() && index < 4) { final double value = RequestsUtilities.toDouble(tokens.nextToken()); coodinates[index] = value; index++; } String crs = null; if (tokens.hasMoreTokens()) { crs = tokens.nextToken(); } if (coodinates != null) { if (filter == null) { filter = buildBBOXFilter(version, "", coodinates[0], coodinates[1], coodinates[2], coodinates[3], crs); } else { LOGGER.info("unexpected case --> filter + bbox TODO"); } } } try { final Query query = buildQuery(version, filter, typeNames, featureVersion, srsName, sortBy, propertyNames); final GetFeature gf = buildGetFeature(version, service, handle, startIndex, maxFeature, query, resultType, outputFormat); gf.setPrefixMapping(prefixMapping); return gf; } catch (IllegalArgumentException ex) { throw new CstlServiceException(ex); } } private List<Parameter> extractParameters(WFSWorker worker, final String storedQuery, final String version, Map<String,String> mapping) { final List<Parameter> parameters = new ArrayList<>(); // extract stored query params final List<ParameterExpression> params = worker.getParameterForStoredQuery(storedQuery); for (ParameterExpression param : params) { final String paramValue = getSafeParameter(param.getName()); if (paramValue != null) { // TODO handle different type final Object obj; if (param.getType().getLocalPart().equals("QName")) { final int separator = paramValue.indexOf(':'); if (separator != -1) { final String prefix = paramValue.substring(0, separator); final String namespac = mapping.get(prefix); final String localPart = paramValue.substring(separator + 1); obj = new QName(namespac, localPart); } else { obj = new QName(paramValue); } } else { obj = paramValue; } parameters.add(buildParameter(version, param.getName(), obj)); } } return parameters; } private SortBy parseSortByParameter(String version) { // TODO handle multiple properties and handle prefixed properties String sortByParam = getSafeParameter("sortBy"); final SortBy sortBy; if (sortByParam != null) { if (sortByParam.indexOf(':') != -1) { sortByParam = sortByParam.substring(sortByParam.indexOf(':') + 1); } //we get the order final SortOrder order; if (sortByParam.indexOf(' ') != -1) { final char cOrder = sortByParam.charAt(sortByParam.length() -1); sortByParam = sortByParam.substring(0, sortByParam.indexOf(' ')); if (cOrder == 'D') { order = SortOrder.DESCENDING; } else { order = SortOrder.ASCENDING; } } else { order = SortOrder.ASCENDING; } sortBy = buildSortBy(version, sortByParam, order); } else { sortBy = null; } return sortBy; } private Integer parseOptionalIntegerParam(String paramName) throws CstlServiceException { Integer result = null; final String max = getParameter(paramName, false); if (max != null) { try { result = Integer.parseInt(max); } catch (NumberFormatException ex) { throw new CstlServiceException("Unable to parse the integer " + paramName + " parameter" + max, INVALID_PARAMETER_VALUE, paramName); } } return result; } private List<String> parseCommaSeparatedParameter(String paramName) throws CstlServiceException { final String propertyNameParam = getParameter(paramName, false); final List<String> results = new ArrayList<>(); if (propertyNameParam != null) { final StringTokenizer tokens = new StringTokenizer(propertyNameParam, ",;"); while (tokens.hasMoreTokens()) { final String token = tokens.nextToken().trim(); results.add(token); } } return results; } private GetPropertyValue createNewGetPropertyValueRequest(final Worker worker) throws CstlServiceException { final Integer maxFeature = parseOptionalIntegerParam("maxfeatures"); final Integer startIndex = parseOptionalIntegerParam("StartIndex"); final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); final String handle = getParameter(HANDLE, false); final String outputFormat = getParameter("outputFormat", false); worker.checkVersionSupported(version, false); final String namespace = getParameter(NAMESPACE, false); final Map<String, String> mapping = WebServiceUtilities.extractNamespace(namespace); final String result = getParameter("resultType", false); ResultTypeType resultType = null; if (result != null) { resultType = ResultTypeType.fromValue(result.toLowerCase()); } final String valueReference = getParameter("valueReference", true); final String featureVersion = getParameter("featureVersion", false); String featureId; if (version.equals("2.0.0")) { featureId = getParameter("ressourceid", false); } else { featureId = getParameter("featureid", false); } boolean mandatory = true; if (featureId != null) { //cite test fix if (featureId.endsWith(",")) { featureId = featureId.substring(0, featureId.length() - 1); } mandatory = false; } final String typeName; if (version.equals("2.0.0")) { typeName = getParameter("typeNames", mandatory); } else { typeName = getParameter("typeName", mandatory); } final List<QName> typeNames = extractTypeName(typeName, mapping); final String srsName = getParameter("srsName", false); // TODO handle multiple properties and handle prefixed properties String sortByParam = getParameter("sortBy", false); final SortBy sortBy; if (sortByParam != null) { if (sortByParam.indexOf(':') != -1) { sortByParam = sortByParam.substring(sortByParam.indexOf(':') + 1); } //we get the order final SortOrder order; if (sortByParam.indexOf(' ') != -1) { final char cOrder = sortByParam.charAt(sortByParam.length() -1); sortByParam = sortByParam.substring(0, sortByParam.indexOf(' ')); if (cOrder == 'D') { order = SortOrder.DESCENDING; } else { order = SortOrder.ASCENDING; } } else { order = SortOrder.ASCENDING; } sortBy = buildSortBy(version, sortByParam, order); } else { sortBy = null; } final String propertyNameParam = getParameter("propertyName", false); final List<String> propertyNames = new ArrayList<>(); if (propertyNameParam != null && !propertyNameParam.isEmpty()) { final StringTokenizer tokens = new StringTokenizer(propertyNameParam, ",;"); while (tokens.hasMoreTokens()) { final String token = tokens.nextToken().trim(); propertyNames.add(token); } } if (featureId != null) { final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, featureId); final Query query = buildQuery(version, filter, typeNames, featureVersion, srsName, sortBy, propertyNames); return buildGetPropertyValue(version, service, handle, startIndex, maxFeature, query, resultType, outputFormat, valueReference); } final Object xmlFilter = getComplexParameter(FILTER, false); XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } final String bbox = getParameter("bbox", false); if (bbox != null) { final double[] coodinates = new double[4]; final StringTokenizer tokens = new StringTokenizer(bbox, ",;"); int index = 0; while (tokens.hasMoreTokens() && index < 4) { final double value = RequestsUtilities.toDouble(tokens.nextToken()); coodinates[index] = value; index++; } String crs = null; if (tokens.hasMoreTokens()) { crs = tokens.nextToken(); } if (coodinates != null) { if (filter == null) { filter = buildBBOXFilter(version, "", coodinates[0], coodinates[1], coodinates[2], coodinates[3], crs); } else { LOGGER.info("unexpected case --> filter + bbox TODO"); } } } final Query query = buildQuery(version, filter, typeNames, featureVersion, srsName, sortBy, propertyNames); final GetPropertyValue gf = buildGetPropertyValue(version, service, handle, startIndex, maxFeature, query, resultType, outputFormat, valueReference); gf.setPrefixMapping(prefixMapping); return gf; } private GetGmlObject createNewGetGmlObjectRequest(final Worker worker) throws CstlServiceException { final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String handle = getParameter(HANDLE, false); final String outputFormat = getParameter("outputFormat", false); final String id = getParameter("gmlobjectid", true); return buildGetGmlObject(version, id, service, handle, outputFormat); } private LockFeature createNewLockFeatureRequest(final Worker worker) throws CstlServiceException { final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String handle = getParameter(HANDLE, false); final String lockAct = getParameter("lockAction", false); AllSomeType lockAction = null; if (lockAct != null) { lockAction = AllSomeType.fromValue(lockAct); } final String exp = getParameter("expiry", false); Integer expiry = null; if (exp != null) { try { expiry = Integer.parseInt(exp); } catch (NumberFormatException ex) { throw new CstlServiceException("The service was to parse the expiry value :" + exp, INVALID_PARAMETER_VALUE, "expiry"); } } final String namespace = getParameter(NAMESPACE, false); final Map<String, String> mapping = WebServiceUtilities.extractNamespace(namespace); final String typeName = getParameter("typeName", true); final List<QName> typeNames = extractTypeName(typeName, mapping); final Object xmlFilter = getComplexParameter(FILTER, false); final XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } // TODO final QName typeNamee = typeNames.get(0); final LockFeature lf = buildLockFeature(version, service, handle, lockAction, filter, typeNamee, expiry); lf.setPrefixMapping(prefixMapping); return lf; } private Transaction createNewTransactionRequest(final Worker worker) throws CstlServiceException { final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String handle = getParameter(HANDLE, false); final String relAct = getParameter("releaseAction", false); AllSomeType releaseAction = null; if (relAct != null) { releaseAction = AllSomeType.fromValue(relAct); } final String namespace = getParameter(NAMESPACE, false); final Map<String, String> mapping = WebServiceUtilities.extractNamespace(namespace); final String typeName = getParameter("typeName", true); final List<QName> typeNames = extractTypeName(typeName, mapping); final Object xmlFilter = getComplexParameter(FILTER, false); final XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } // TODO final QName typeNamee = typeNames.get(0); final DeleteElement delete = buildDeleteElement(version, filter, handle, typeNamee); final Transaction t = buildTransaction(version, service, handle, releaseAction, delete); t.setPrefixMapping(prefixMapping); return t; } /** * Extract proper QName from a String list of typeName. * @param typeName A String with the pattern: ns1:type1,ns1:type2,ns2:type3 * @param mapping A Map of @{<prefix, namespace>} * * @return A list of QName. * @throws CstlServiceException if the pattern of the typeName parameter if wrong, * or if a prefix is not bounded to a namespace in the mapping map. */ private List<QName> extractTypeName(final String typeName, final Map<String, String> mapping) throws CstlServiceException { final List<QName> typeNames = new ArrayList<>(); if (typeName != null) { final StringTokenizer tokens = new StringTokenizer(typeName, ",;"); while (tokens.hasMoreTokens()) { final String token = tokens.nextToken().trim(); if (token.indexOf(':') != -1) { final String prefix = token.substring(0, token.indexOf(':')); final String localPart = token.substring(token.indexOf(':') + 1); final String namesp = mapping.get(prefix); if (namesp != null) { typeNames.add(new QName(namesp, localPart, prefix)); } else { typeNames.add(new QName(prefix, localPart)); /*throw new CstlServiceException("The typeName parameter is malformed : the prefix [" + prefix + "] is not bounded with a namespace", INVALID_PARAMETER_VALUE, "typeName");*/ } } else { typeNames.add(new QName(token)); /*throw new CstlServiceException("The typeName parameter is malformed : [" + token + "] the good pattern is ns1:feature", INVALID_PARAMETER_VALUE, "typeName");*/ } } } return typeNames; } /** * {@inheritDoc} * overriden for extract namespace mapping. */ @Override protected Object getComplexParameter(final String parameterName, final boolean mandatory) throws CstlServiceException { try { final MultivaluedMap<String,String> parameters = getUriContext().getQueryParameters(); List<String> list = parameters.get(parameterName); if (list == null) { for(final String key : parameters.keySet()){ if(parameterName.equalsIgnoreCase(key)){ list = parameters.get(key); break; } } if (list == null) { if (!mandatory) { return null; } else { throw new CstlServiceException("The parameter " + parameterName + " must be specified", MISSING_PARAMETER_VALUE); } } } final StringReader sr = new StringReader(list.get(0)); final Map<String, String> prefixMapping = new LinkedHashMap<>(); final XMLEventReader rootEventReader = XMLInputFactory.newInstance().createXMLEventReader(sr); final XMLEventReader eventReader = (XMLEventReader) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{XMLEventReader.class}, new PrefixMappingInvocationHandler(rootEventReader, prefixMapping)); final Unmarshaller unmarshaller = getMarshallerPool().acquireUnmarshaller(); Object result = unmarshaller.unmarshal(eventReader); getMarshallerPool().recycle(unmarshaller); if (result instanceof JAXBElement) { result = ((JAXBElement)result).getValue(); } if (result instanceof XMLFilter) { ((XMLFilter)result).setPrefixMapping(prefixMapping); } return result; } catch (JAXBException | XMLStreamException ex) { throw new CstlServiceException("The xml object for parameter " + parameterName + " is not well formed:" + '\n' + ex, INVALID_PARAMETER_VALUE); } } private DescribeStoredQueries createNewDescribeStoredQueriesRequest(final Worker worker) throws CstlServiceException { final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String handle = getParameter(HANDLE, false); final String storedQueryIdParam = getParameter("StoredQueryId", false); final List<String> storedQueryId = new ArrayList<>(); if (storedQueryIdParam != null) { final StringTokenizer tokens = new StringTokenizer(storedQueryIdParam, ",;"); while (tokens.hasMoreTokens()) { final String token = tokens.nextToken().trim(); storedQueryId.add(token); } } return buildDescribeStoredQueries(version, service, handle, storedQueryId); } private ListStoredQueries createNewListStoredQueriesRequest(final Worker worker) throws CstlServiceException { final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String handle = getParameter(HANDLE, false); return buildListStoredQueries(version, service, handle); } private CreateStoredQuery createNewCreateStoredQueryRequest() throws CstlServiceException { throw new CstlServiceException("KVP encoding is not allowed for CreateStoredQuery request"); } private DropStoredQuery createNewDropStoredQueryRequest(final Worker worker) throws CstlServiceException { final String service = getParameter(SERVICE_PARAMETER, true); final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String handle = getParameter(HANDLE, false); final String id = getParameter("id", true); return buildDropStoredQuery(version, service, handle, id); } private GetXSD createNewXsdRequest(WFSWorker worker) throws CstlServiceException { final String version = getParameter(VERSION_PARAMETER, true); worker.checkVersionSupported(version, false); final String targetNamespace = getParameter("targetnamespace", true); final String namespace = getParameter(NAMESPACE, false); final Map<String, String> mapping = WebServiceUtilities.extractNamespace(namespace); final String typeNameStr = getParameter("typeName", true); final List<QName> typeNames = extractTypeName(typeNameStr, mapping); QName typeName; if (typeNames.size() == 1) { typeName = typeNames.get(0); } else { typeName = null; } return new GetXSD(typeName, targetNamespace, version); } private WFSWorker getWorker() { final String serviceID = getSafeParameter("serviceId"); if (serviceID != null && WSEngine.serviceInstanceExist("WFS", serviceID)) { return (WFSWorker) WSEngine.getInstance("WFS", serviceID); } return null; } @GET @Path("{version}") public Response processGetCapabilitiesRestful(@PathParam("version") final String version) throws CstlServiceException { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final Sections sections; final String section = getSafeParameter(SECTIONS_PARAMETER); if (section != null && !section.equalsIgnoreCase("All")) { final List<String> requestedSections = new ArrayList<>(); final StringTokenizer tokens = new StringTokenizer(section, ",;"); while (tokens.hasMoreTokens()) { final String token = tokens.nextToken().trim(); if (SectionsType.getExistingSections().contains(token)){ requestedSections.add(token); } else { throw new CstlServiceException("The section " + token + " does not exist", INVALID_PARAMETER_VALUE, "Sections"); } } sections = buildSections(version, requestedSections); } else { sections = null; } final List<String> versions = new ArrayList<>(); versions.add(version); final AcceptVersions acceptVersions = buildAcceptVersion(version, versions); final GetCapabilities gc = WFSXmlFactory.buildGetCapabilities(version, acceptVersions, sections, null, null, "WFS"); return treatIncomingRequest(gc); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @GET @Path("{version}/schema") public Response processDescribeFeatureRestful(@PathParam("version") final String version) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final DescribeFeatureType df = WFSXmlFactory.buildDecribeFeatureType(version, "WFS", null, null, null); return treatIncomingRequest(df); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @GET @Path("{version}/{featureType}") public Response processFeatureTypeRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final Object request; if (featureType.endsWith(".xsd") || featureType.endsWith(".jsd")) { featureType = featureType.substring(0, featureType.length() - 4); final List<QName> typeNames = Arrays.asList(new QName(featureType)); String outFormat = null; if (featureType.endsWith(".jsd")) { outFormat = "application/schema+json"; } request = WFSXmlFactory.buildDecribeFeatureType(version, "WFS", null, typeNames, outFormat); } else { // TODO namespace param final SortBy sortBy = parseSortByParameter(version); String srsName = getSafeParameter("srsName"); String resultTypeStr = getSafeParameter("resultType"); ResultTypeType resultType = ResultTypeType.RESULTS; if (resultTypeStr != null) { resultType = ResultTypeType.fromValue(resultTypeStr); } final Integer maxFeature = parseOptionalIntegerParam("count"); final Integer startIndex = parseOptionalIntegerParam("startIndex"); final String outputFormat = getSafeParameter("outputFormat"); final List<String> propertyNames = parseCommaSeparatedParameter("propertyName"); final List<QName> typeNames = Arrays.asList(new QName(featureType)); final Object xmlFilter = getComplexParameter(FILTER, false); XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } final Query query = buildQuery(version, filter, typeNames, null, srsName, sortBy, propertyNames); final GetFeature gf = WFSXmlFactory.buildGetFeature(version, "WFS", null, startIndex, maxFeature, query, resultType, outputFormat); gf.setPrefixMapping(prefixMapping); request = gf; } return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @POST @Path("{version}/{featureType}") public Response processTransactionInsertRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, final Node in) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final String srsName = getSafeParameter("srsName"); final InsertElement elem = WFSXmlFactory.buildInsertElement(version, null, srsName, in); final Transaction request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, elem); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @PUT @Path("{version}/{featureType}") public Response processTransactionReplaceRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, final Node in) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final String srsName = getSafeParameter("srsName"); final Object xmlFilter = getComplexParameter(FILTER, false); final XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } final ReplaceElement elem = WFSXmlFactory.buildReplaceElement(version, null, srsName, filter, in); final Transaction request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, elem); request.setPrefixMapping(prefixMapping); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @DELETE @Path("{version}/{featureType}") public Response processTransactionDeleteRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final Object xmlFilter = getComplexParameter(FILTER, false); final XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } final DeleteElement elem = WFSXmlFactory.buildDeleteElement(version, filter, null, new QName(featureType)); final Transaction request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, elem); request.setPrefixMapping(prefixMapping); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @GET @Path("{version}/{featureType}/{feature}") public Response processGetFeatureRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("feature") String feature) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, feature); // TODO namespace param final SortBy sortBy = parseSortByParameter(version); final String srsName = getSafeParameter("srsName"); final String outputFormat = getSafeParameter("outputFormat"); String resultTypeStr = getSafeParameter("resultType"); ResultTypeType resultType = ResultTypeType.RESULTS; if (resultTypeStr != null) { resultType = ResultTypeType.fromValue(resultTypeStr); } final Integer maxFeature = parseOptionalIntegerParam("count"); final Integer startIndex = parseOptionalIntegerParam("startIndex"); final List<String> propertyNames = parseCommaSeparatedParameter("propertyName"); final List<QName> typeNames = Arrays.asList(new QName(featureType)); final Query query = buildQuery(version, filter, typeNames, null, srsName, sortBy, propertyNames); final Object request = WFSXmlFactory.buildGetFeature(version, "WFS", null, startIndex, maxFeature, query, resultType, outputFormat); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @PUT @Path("{version}/{featureType}/{feature}") public Response processTransactionReplaceRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("feature") String feature, final Node in) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, feature); final String srsName = getSafeParameter("srsName"); final ReplaceElement elem = WFSXmlFactory.buildReplaceElement(version, null, srsName, filter, in); final Transaction request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, elem); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @DELETE @Path("{version}/{featureType}/{feature}") public Response processTransactionDeleteRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("feature") String feature) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, feature); final DeleteElement elem = WFSXmlFactory.buildDeleteElement(version, filter, null, new QName(featureType)); final Transaction request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, elem); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @GET @Path("{version}/{featureType}/property/{prop}") public Response processGetPropertyValueRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("prop") String prop) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final Object xmlFilter = getComplexParameter(FILTER, false); final XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } // TODO namespace param final SortBy sortBy = parseSortByParameter(version); final String srsName = getSafeParameter("srsName"); final String outputFormat = getSafeParameter("outputFormat"); String resultTypeStr = getSafeParameter("resultType"); ResultTypeType resultType = ResultTypeType.RESULTS; if (resultTypeStr != null) { resultType = ResultTypeType.fromValue(resultTypeStr); } final Integer maxFeature = parseOptionalIntegerParam("count"); final Integer startIndex = parseOptionalIntegerParam("startIndex"); final List<QName> typeNames = Arrays.asList(new QName(featureType)); final List<String> propNames = Arrays.asList(prop); final Query query = buildQuery(version, filter, typeNames, null, srsName, sortBy, propNames); final GetPropertyValue request = WFSXmlFactory.buildGetPropertyValue(version, "WFS", null, startIndex, maxFeature, query, resultType, outputFormat, prop); request.setPrefixMapping(prefixMapping); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @PUT @Path("{version}/{featureType}/property/{prop}") public Response processTransactionUpdateRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("prop") String prop, final InputStream in) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final Object xmlFilter = getComplexParameter(FILTER, false); final XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } final String srsName = getSafeParameter("srsName"); /** * getting the value is a little bit tricky. use inputFormat? */ final Unmarshaller um = getMarshallerPool().acquireUnmarshaller(); final Object obj = um.unmarshal(in); getMarshallerPool().recycle(um); final Property property = WFSXmlFactory.buildProperty(version, prop, obj); final UpdateElement update = WFSXmlFactory.buildUpdateElement(version, null, srsName, filter, new QName(featureType), Arrays.asList(property)); final Transaction request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, update); request.setPrefixMapping(prefixMapping); return treatIncomingRequest(request); } catch (IllegalArgumentException | JAXBException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @DELETE @Path("{version}/{featureType}/property/{prop}") public Response processTransactionUpdateNullRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("prop") String prop) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final Object xmlFilter = getComplexParameter(FILTER, false); final XMLFilter filter; final Map<String, String> prefixMapping; if (xmlFilter instanceof XMLFilter) { filter = (XMLFilter) xmlFilter; prefixMapping = filter.getPrefixMapping(); } else { filter = null; prefixMapping = new HashMap<>(); } final String srsName = getSafeParameter("srsName"); final Property property = WFSXmlFactory.buildProperty(version, prop, null); final UpdateElement update = WFSXmlFactory.buildUpdateElement(version, null, srsName, filter, new QName(featureType), Arrays.asList(property)); final Transaction request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, update); request.setPrefixMapping(prefixMapping); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @GET @Path("{version}/{featureType}/{feature}/{prop}") public Response processGetPropertyValueRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("feature") String feature, @PathParam("prop") String prop) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, feature); // TODO namespace param final SortBy sortBy = parseSortByParameter(version); final String srsName = getSafeParameter("srsName"); final String outputFormat = getSafeParameter("outputFormat"); String resultTypeStr = getSafeParameter("resultType"); ResultTypeType resultType = ResultTypeType.RESULTS; if (resultTypeStr != null) { resultType = ResultTypeType.fromValue(resultTypeStr); } final Integer maxFeature = parseOptionalIntegerParam("count"); final Integer startIndex = parseOptionalIntegerParam("startIndex"); final List<QName> typeNames = Arrays.asList(new QName(featureType)); final List<String> propNames = Arrays.asList(prop); final Query query = buildQuery(version, filter, typeNames, null, srsName, sortBy, propNames); final Object request = WFSXmlFactory.buildGetPropertyValue(version, "WFS", null, startIndex, maxFeature, query, resultType, outputFormat, prop); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @PUT @Path("{version}/{featureType}/{feature}/{prop}") public Response processTransactionUpdateRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("feature") String feature, @PathParam("prop") String prop, final InputStream in) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, feature); final String srsName = getSafeParameter("srsName"); /** * getting the value is a little bit tricky. use inputFormat? */ final Unmarshaller um = getMarshallerPool().acquireUnmarshaller(); final Object obj = um.unmarshal(in); getMarshallerPool().recycle(um); final Property property = WFSXmlFactory.buildProperty(version, prop, obj); final UpdateElement update = WFSXmlFactory.buildUpdateElement(version, null, srsName, filter, new QName(featureType), Arrays.asList(property)); final Object request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, update); return treatIncomingRequest(request); } catch (IllegalArgumentException | JAXBException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @DELETE @Path("{version}/{featureType}/{feature}/{prop}") public Response processTransactionUpdateNullRestful(@PathParam("version") final String version, @PathParam("featureType") String featureType, @PathParam("feature") String feature, @PathParam("prop") String prop) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final XMLFilter filter = FilterXmlFactory.buildFeatureIDFilter(version, feature); final String srsName = getSafeParameter("srsName"); final Property property = WFSXmlFactory.buildProperty(version, prop, null); final UpdateElement update = WFSXmlFactory.buildUpdateElement(version, null, srsName, filter, new QName(featureType), Arrays.asList(property)); final Object request = WFSXmlFactory.buildTransaction(version, "WFS", null, AllSomeType.ALL, update); return treatIncomingRequest(request); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @GET @Path("{version}/query") public Response processListStoredQueriesRestful(@PathParam("version") final String version) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final ListStoredQueries lsq = WFSXmlFactory.buildListStoredQueries(version, "WFS", null); return treatIncomingRequest(lsq); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @POST @Path("{version}/query") public Response processCreateStoredQueriesRestful(@PathParam("version") final String version, final InputStream queryStream) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final String queryString = FileUtilities.getStringFromStream(queryStream); final Unmarshaller um = getMarshallerPool().acquireUnmarshaller(); Object obj = um.unmarshal(new StringReader(queryString)); getMarshallerPool().recycle(um); if (obj instanceof JAXBElement) { obj = ((JAXBElement)obj).getValue(); } if (obj instanceof Query) { // adHocQuery final String id = UUID.randomUUID().toString(); List<ParameterExpression> parameters = extractPram(version, queryString); final StoredQueryDescription desc = WFSXmlFactory.buildStoredQueryDescription(version, id, (Query)obj, parameters); final CreateStoredQuery lsq = WFSXmlFactory.buildCreateStoredQuery(version, "WFS", null, Arrays.asList(desc)); return treatIncomingRequest(lsq); } else { throw new CstlServiceException("Unexpected content for query body"); } } catch (IllegalArgumentException | JAXBException | IOException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } public static List<ParameterExpression> extractPram(String version, String s) { final List<ParameterExpression> results = new ArrayList<>(); int pos = s.indexOf("${"); while (pos != -1) { int endPos = s.indexOf('}'); if (endPos != -1 && pos < endPos) { results.add(WFSXmlFactory.buildParameterDescription(version, s.substring(pos + 2, endPos), new QName("http://www.w3.org/2001/XMLSchema", "string", "xs"))); s = s .substring(endPos + 1); } else if (endPos < pos){ s = s .substring(endPos + 1); } pos = s.indexOf("${"); } return results; } @GET @Path("{version}/query/{queryId}") public Response processExecuteStoredQueriesRestful(@PathParam("version") final String version, @PathParam("queryId") final String queryId) { final WFSWorker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final List<Parameter> params = extractParameters(worker, queryId, version, new HashMap<String, String>()); final StoredQuery query = WFSXmlFactory.buildStoredQuery(version, queryId, null, params); final GetFeature lsq = WFSXmlFactory.buildGetFeature(version, "WFS", null, null, null, query, null, null); return treatIncomingRequest(lsq); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @POST @Path("{version}/query/{queryId}") public Response processCreateStoredQueriesRestful(@PathParam("version") final String version, @PathParam("queryId") final String queryId, final InputStream queryStream) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final String queryString = FileUtilities.getStringFromStream(queryStream); final Unmarshaller um = getMarshallerPool().acquireUnmarshaller(); Object obj = um.unmarshal(new StringReader(queryString)); getMarshallerPool().recycle(um); if (obj instanceof JAXBElement) { obj = ((JAXBElement)obj).getValue(); } if (obj instanceof Query) { final String id = queryId; List<ParameterExpression> parameters = extractPram(version, queryString); final StoredQueryDescription desc = WFSXmlFactory.buildStoredQueryDescription(version, id, (Query)obj, parameters); final CreateStoredQuery lsq = WFSXmlFactory.buildCreateStoredQuery(version, "WFS", null, Arrays.asList(desc)); return treatIncomingRequest(lsq); } else { throw new CstlServiceException("Unexpected content for query body"); } } catch (IllegalArgumentException | JAXBException | IOException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @PUT @Path("{version}/query/{queryId}") public Response processUpdateStoredQueriesRestful(@PathParam("version") final String version, @PathParam("queryId") final String queryId, final InputStream queryStream) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final String queryString = FileUtilities.getStringFromStream(queryStream); final Unmarshaller um = getMarshallerPool().acquireUnmarshaller(); Object obj = um.unmarshal(new StringReader(queryString)); getMarshallerPool().recycle(um); if (obj instanceof JAXBElement) { obj = ((JAXBElement)obj).getValue(); } if (obj instanceof Query) { // REMOVE then INSERT final DropStoredQuery dsq = WFSXmlFactory.buildDropStoredQuery(version, "WFS", null, queryId); treatIncomingRequest(dsq); final String id = queryId; List<ParameterExpression> parameters = extractPram(version, queryString); final StoredQueryDescription desc = WFSXmlFactory.buildStoredQueryDescription(version, id, (Query)obj, parameters); final CreateStoredQuery csq = WFSXmlFactory.buildCreateStoredQuery(version, "WFS", null, Arrays.asList(desc)); return treatIncomingRequest(csq); } else { throw new CstlServiceException("Unexpected content for query body"); } } catch (IllegalArgumentException | JAXBException | IOException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } @DELETE @Path("{version}/query/{queryId}") public Response processDeleteStoredQueriesRestful(@PathParam("version") final String version, @PathParam("queryId") final String queryId) { final Worker worker = getWorker(); if (worker != null) { try { worker.checkVersionSupported(version, true); final DropStoredQuery lsq = WFSXmlFactory.buildDropStoredQuery(version, "WFS", null, queryId); return treatIncomingRequest(lsq); } catch (IllegalArgumentException ex) { return processExceptionResponse(new CstlServiceException(ex), null, worker); } catch (CstlServiceException ex) { return processExceptionResponse(ex, null, worker); } } return Response.status(Response.Status.NOT_FOUND).build(); } }