/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2016, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.wfs.internal.v2_0; import static org.geotools.data.wfs.internal.HttpMethod.GET; import static org.geotools.data.wfs.internal.HttpMethod.POST; import static org.geotools.data.wfs.internal.Loggers.debug; import static org.geotools.data.wfs.internal.Loggers.requestInfo; import static org.geotools.data.wfs.internal.Loggers.trace; import static org.geotools.data.wfs.internal.WFSOperationType.GET_FEATURE; import static org.geotools.data.wfs.internal.WFSOperationType.TRANSACTION; import java.io.IOException; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import javax.xml.namespace.QName; import net.opengis.fes20.AbstractQueryExpressionType; import net.opengis.ows11.DCPType; import net.opengis.ows11.DomainType; import net.opengis.ows11.OperationType; import net.opengis.ows11.OperationsMetadataType; import net.opengis.ows11.RequestMethodType; import net.opengis.ows11.ValueType; import net.opengis.wfs20.AbstractTransactionActionType; import net.opengis.wfs20.DeleteType; import net.opengis.wfs20.DescribeFeatureTypeType; import net.opengis.wfs20.DescribeStoredQueriesType; import net.opengis.wfs20.FeatureTypeListType; import net.opengis.wfs20.FeatureTypeType; import net.opengis.wfs20.GetFeatureType; import net.opengis.wfs20.ListStoredQueriesType; import net.opengis.wfs20.ParameterType; import net.opengis.wfs20.InsertType; import net.opengis.wfs20.PropertyType; import net.opengis.wfs20.QueryType; import net.opengis.wfs20.ResultTypeType; import net.opengis.wfs20.StoredQueryDescriptionType; import net.opengis.wfs20.StoredQueryType; import net.opengis.wfs20.TransactionType; import net.opengis.wfs20.UpdateType; import net.opengis.wfs20.ValueReferenceType; import net.opengis.wfs20.WFSCapabilitiesType; import net.opengis.wfs20.Wfs20Factory; import org.eclipse.emf.ecore.EObject; import org.geotools.data.wfs.WFSDataStore; import org.geotools.data.wfs.WFSServiceInfo; import org.geotools.data.wfs.internal.AbstractWFSStrategy; import org.geotools.data.wfs.internal.DescribeFeatureTypeRequest; import org.geotools.data.wfs.internal.FeatureTypeInfo; import org.geotools.data.wfs.internal.GetFeatureRequest; import org.geotools.data.wfs.internal.GetFeatureRequest.ResultType; import org.geotools.data.wfs.internal.v2_0.storedquery.ParameterTypeFactory; import org.geotools.data.wfs.internal.v2_0.storedquery.StoredQueryConfiguration; import org.geotools.data.wfs.internal.DescribeStoredQueriesRequest; import org.geotools.data.wfs.internal.HttpMethod; import org.geotools.data.wfs.internal.ListStoredQueriesRequest; import org.geotools.data.wfs.internal.TransactionRequest; import org.geotools.data.wfs.internal.TransactionRequest.Delete; import org.geotools.data.wfs.internal.TransactionRequest.Insert; import org.geotools.data.wfs.internal.TransactionRequest.TransactionElement; import org.geotools.data.wfs.internal.TransactionRequest.Update; import org.geotools.data.wfs.internal.Versions; import org.geotools.data.wfs.internal.WFSExtensions; import org.geotools.data.wfs.internal.WFSGetCapabilities; import org.geotools.data.wfs.internal.WFSOperationType; import org.geotools.data.wfs.internal.WFSResponseFactory; import org.geotools.data.wfs.internal.WFSStrategy; import org.geotools.factory.Hints; import org.geotools.factory.Hints.ConfigurationMetadataKey; import org.geotools.util.Version; import org.geotools.wfs.v2_0.WFS; import org.geotools.xml.Configuration; import org.opengis.feature.simple.SimpleFeature; import org.opengis.filter.Filter; import org.opengis.filter.capability.FilterCapabilities; /** * */ public class StrictWFS_2_0_Strategy extends AbstractWFSStrategy { private static final List<String> PREFERRED_FORMATS = Collections.unmodifiableList(Arrays .asList("application/gml+xml; version=3.2", // As per Table 12 in 09-25r1 OGC Web Feature Service WFS 2.0 "text/xml; subtype=gml/3.2", "gml32", "text/xml; subtype=gml/3.1.1", "gml3", "text/xml; subtype=gml/2.1.2", "GML2")); private net.opengis.wfs20.WFSCapabilitiesType capabilities; private final Map<QName, FeatureTypeType> typeInfos; private static final ConfigurationMetadataKey CONFIG_KEY = ConfigurationMetadataKey.get(WFSDataStore.STORED_QUERY_CONFIGURATION_HINT); public StrictWFS_2_0_Strategy() { super(); typeInfos = new HashMap<QName, FeatureTypeType>(); } /*--------------------------------------------------------------------- * AbstractWFSStrategy methods * ---------------------------------------------------------------------*/ @Override public Configuration getFilterConfiguration() { return FILTER_2_0_CONFIGURATION; } @Override public Configuration getWfsConfiguration() { return WFS_2_0_CONFIGURATION; } @Override protected QName getOperationName(WFSOperationType operation) { return new QName(WFS.NAMESPACE, operation.getName()); } /*--------------------------------------------------------------------- * WFSStrategy methods * ---------------------------------------------------------------------*/ @Override public void setCapabilities(WFSGetCapabilities capabilities) { net.opengis.wfs20.WFSCapabilitiesType caps = (WFSCapabilitiesType) capabilities .getParsedCapabilities(); this.capabilities = caps; typeInfos.clear(); FeatureTypeListType featureTypeList = this.capabilities.getFeatureTypeList(); @SuppressWarnings("unchecked") List<FeatureTypeType> featureTypes = featureTypeList.getFeatureType(); for (FeatureTypeType typeInfo : featureTypes) { QName name = typeInfo.getName(); typeInfos.put(name, typeInfo); } } @Override public WFSServiceInfo getServiceInfo() { URL getCapsUrl = getOperationURL(WFSOperationType.GET_CAPABILITIES, GET); return new Capabilities200ServiceInfo("http://schemas.opengis.net/wfs/2.0/wfs.xsd", getCapsUrl, capabilities); } @Override public boolean supports(ResultType resultType) { switch (resultType) { case RESULTS: case HITS: return true; default: return false; } } @Override public Version getServiceVersion() { return Versions.v2_0_0; } /** * @see WFSStrategy#getFeatureTypeNames() */ @Override public Set<QName> getFeatureTypeNames() { return new HashSet<QName>(typeInfos.keySet()); } /** * @see org.geotools.data.wfs.internal.WFSStrategy#getFeatureTypeInfo(javax.xml.namespace.QName) */ @Override public FeatureTypeInfo getFeatureTypeInfo(QName typeName) { FeatureTypeType eType = typeInfos.get(typeName); if (null == eType) { throw new IllegalArgumentException("Type name not found: " + typeName); } return new FeatureTypeInfoImpl(eType, config); } @Override public FilterCapabilities getFilterCapabilities() { return capabilities.getFilterCapabilities(); } @Override protected Map<String, String> buildGetFeatureParametersForGET( GetFeatureRequest query) { Map<String, String> kvp = null; if (query.isStoredQuery()) { FeatureTypeInfoImpl featureTypeInfo = (FeatureTypeInfoImpl)getFeatureTypeInfo(query.getTypeName()); StoredQueryDescriptionType desc = query.getStoredQueryDescriptionType(); StoredQueryConfiguration config = null; kvp = new HashMap<String, String>(); kvp.put("SERVICE", "WFS"); kvp.put("VERSION", getVersion()); kvp.put("REQUEST", "GetFeature"); kvp.put("STOREDQUERY_ID", desc.getId()); Filter originalFilter = query.getFilter(); query.setUnsupportedFilter(originalFilter); Map<String, String> viewParams = null; if (query.getRequestHints() != null) { viewParams = (Map<String, String>)query.getRequestHints() .get(Hints.VIRTUAL_TABLE_PARAMETERS); config = (StoredQueryConfiguration)query.getRequestHints().get(CONFIG_KEY); } List<ParameterType> params = new ParameterTypeFactory(config, desc, featureTypeInfo) .buildStoredQueryParameters(viewParams, originalFilter); for (ParameterType p : params) { kvp.put(p.getName(), p.getValue()); } } else { kvp = super.buildGetFeatureParametersForGET(query); // Very crude if (query.getMaxFeatures() != null) { String count = kvp.remove("MAXFEATURES"); kvp.put("COUNT", count); } } return kvp; } @Override protected EObject createDescribeFeatureTypeRequestPost(DescribeFeatureTypeRequest request) { final Wfs20Factory factory = Wfs20Factory.eINSTANCE; DescribeFeatureTypeType dft = factory.createDescribeFeatureTypeType(); Version version = getServiceVersion(); dft.setService("WFS"); dft.setVersion(version.toString()); dft.setHandle(request.getHandle()); if (Versions.v1_0_0.equals(version)) { dft.setOutputFormat(null); } QName typeName = request.getTypeName(); @SuppressWarnings("unchecked") List<QName> typeNames = dft.getTypeName(); typeNames.add(typeName); return dft; } @Override protected EObject createGetFeatureRequestPost(GetFeatureRequest query) throws IOException { final QName typeName = query.getTypeName(); final FeatureTypeInfoImpl featureTypeInfo = (FeatureTypeInfoImpl)getFeatureTypeInfo(typeName); final Wfs20Factory factory = Wfs20Factory.eINSTANCE; GetFeatureType getFeature = factory.createGetFeatureType(); getFeature.setService("WFS"); getFeature.setVersion(getVersion()); String outputFormat = query.getOutputFormat(); getFeature.setOutputFormat(outputFormat); getFeature.setHandle(query.getHandle()); Integer maxFeatures = query.getMaxFeatures(); if (maxFeatures != null) { getFeature.setCount(BigInteger.valueOf(maxFeatures.intValue())); } ResultType resultType = query.getResultType(); getFeature.setResultType(ResultType.RESULTS == resultType ? ResultTypeType.RESULTS : ResultTypeType.HITS); AbstractQueryExpressionType abstractQuery; if (query.isStoredQuery()) { StoredQueryDescriptionType desc = query.getStoredQueryDescriptionType(); StoredQueryType storedQuery = factory.createStoredQueryType(); storedQuery.setId(desc.getId()); // The query filter must be processed locally in full query.setUnsupportedFilter(query.getFilter()); Map<String, String> viewParams = null; StoredQueryConfiguration config = null; if (query.getRequestHints() != null) { viewParams = (Map<String, String>)query.getRequestHints() .get(Hints.VIRTUAL_TABLE_PARAMETERS); config = (StoredQueryConfiguration)query.getRequestHints().get(CONFIG_KEY); } List<ParameterType> params = new ParameterTypeFactory(config, desc, featureTypeInfo) .buildStoredQueryParameters(viewParams, query.getFilter()); storedQuery.getParameter().addAll(params); abstractQuery = storedQuery; } else { QueryType wfsQuery = factory.createQueryType(); wfsQuery.getTypeNames().add(typeName); // Lifted from 1.0 / 1.1 final Filter supportedFilter; final Filter unsupportedFilter; { final Filter filter = query.getFilter(); Filter[] splitFilters = splitFilters(typeName, filter); supportedFilter = splitFilters[0]; unsupportedFilter = splitFilters[1]; } query.setUnsupportedFilter(unsupportedFilter); if (!Filter.INCLUDE.equals(supportedFilter)) { wfsQuery.setFilter(supportedFilter); } String srsName = query.getSrsName(); if (null == srsName) { srsName = featureTypeInfo.getDefaultSRS(); } try { wfsQuery.setSrsName(new URI(srsName)); } catch (URISyntaxException e) { throw new RuntimeException("Can't create a URI from the query CRS: " + srsName, e); } String[] propertyNames = query.getPropertyNames(); boolean retrieveAllProperties = propertyNames == null; if (!retrieveAllProperties) { List<QName> propertyName = wfsQuery.getPropertyNames(); for (String propName : propertyNames) { // These get encoded into <fes:AbstractProjectionClause/> elements. Something's missing propertyName.add(new QName(featureTypeInfo.getQName().getNamespaceURI(), propName)); } } /* * System.err.println("SortBy is not yet implemented in StrictWFS_2_0_Strategy"); SortBy[] sortByList = query.getSortBy(); if (sortByList != null) { for (SortBy sortBy : sortByList) { wfsQuery.getSortBy().add(sortBy); } } */ abstractQuery = wfsQuery; } getFeature.getAbstractQueryExpression().add(abstractQuery); return getFeature; } @Override protected EObject createListStoredQueriesRequestPost( ListStoredQueriesRequest request) throws IOException { final Wfs20Factory factory = Wfs20Factory.eINSTANCE; ListStoredQueriesType ret = factory.createListStoredQueriesType(); return ret; } @Override protected EObject createDescribeStoredQueriesRequestPost( DescribeStoredQueriesRequest request) throws IOException { final Wfs20Factory factory = Wfs20Factory.eINSTANCE; DescribeStoredQueriesType ret = factory.createDescribeStoredQueriesType(); ret.getStoredQueryId().addAll(request.getStoredQueryIds()); return ret; } @Override protected EObject createTransactionRequest(TransactionRequest request) throws IOException { final Wfs20Factory factory = Wfs20Factory.eINSTANCE; TransactionType tx = factory.createTransactionType(); tx.setService("WFS"); tx.setHandle(request.getHandle()); tx.setVersion(getVersion()); List<TransactionElement> transactionElements = request.getTransactionElements(); if (transactionElements.isEmpty()) { requestInfo("Asked to perform transaction with no transaction elements"); return tx; } @SuppressWarnings("unchecked") List<AbstractTransactionActionType> actions = tx.getAbstractTransactionAction(); try { for (TransactionElement elem : transactionElements) { AbstractTransactionActionType action = null; if (elem instanceof TransactionRequest.Insert) { action = createInsert(factory, (Insert) elem); } else if (elem instanceof TransactionRequest.Update) { action = createUpdate(factory, (Update) elem); } else if (elem instanceof TransactionRequest.Delete) { action = createDelete(factory, (Delete) elem); } actions.add(action); } } catch (IOException e) { throw e; } catch (RuntimeException re) { throw re; } catch (Exception other) { throw new RuntimeException(other); } return tx; } @Override protected String getOperationURI(WFSOperationType operation, HttpMethod method) { OperationsMetadataType omt = this.capabilities.getOperationsMetadata(); omt.getOperation(); trace("Looking operation URI for ", operation, "/", method); List<OperationType> operations = capabilities.getOperationsMetadata().getOperation(); for (OperationType op : operations) { if (!operation.getName().equals(op.getName())) { continue; } List<DCPType> dcpTypes = op.getDCP(); if (null == dcpTypes) { continue; } for (DCPType d : dcpTypes) { List<RequestMethodType> methods; if (HttpMethod.GET.equals(method)) { methods = d.getHTTP().getGet(); } else { methods = d.getHTTP().getPost(); } if (null == methods || methods.isEmpty()) { continue; } String href = methods.get(0).getHref(); debug("Returning operation URI for ", operation, "/", method, ": ", href); return href; } } debug("No operation URI found for ", operation, "/", method); return null; } @Override public Set<String> getServerSupportedOutputFormats(WFSOperationType operation) { String parameterName; switch (operation) { case GET_FEATURE: case DESCRIBE_FEATURETYPE: case GET_FEATURE_WITH_LOCK: parameterName = "outputFormat"; break; case TRANSACTION: parameterName = "inputFormat"; break; case LIST_STORED_QUERIES: case DESCRIBE_STORED_QUERIES: // These return XML as specified in WFS 2.0.0 return Collections.singleton("text/xml"); default: throw new UnsupportedOperationException("not yet implemented for " + operation); } final OperationType operationMetadata = getOperationMetadata(operation); Set<String> serverSupportedFormats; serverSupportedFormats = findParameters(operationMetadata, parameterName); return serverSupportedFormats; } @Override public Set<String> getServerSupportedOutputFormats(QName typeName, WFSOperationType operation) { Set<String> ftypeFormats = new HashSet<String>(); final Set<String> serviceOutputFormats = getServerSupportedOutputFormats(operation); ftypeFormats.addAll(serviceOutputFormats); if (GET_FEATURE.equals(operation)) { final FeatureTypeInfo typeInfo = getFeatureTypeInfo(typeName); final Set<String> typeAdvertisedFormats = typeInfo.getOutputFormats(); ftypeFormats.addAll(typeAdvertisedFormats); } return ftypeFormats; } @Override public List<String> getClientSupportedOutputFormats(WFSOperationType operation) { List<WFSResponseFactory> operationResponseFactories; operationResponseFactories = WFSExtensions.findResponseFactories(operation); List<String> outputFormats = new LinkedList<String>(); for (WFSResponseFactory factory : operationResponseFactories) { List<String> factoryFormats = factory.getSupportedOutputFormats(); outputFormats.addAll(factoryFormats); } if (GET_FEATURE.equals(operation)) { for (String preferred : PREFERRED_FORMATS) { boolean hasFormat = outputFormats.remove(preferred); if (hasFormat) { outputFormats.add(0, preferred); break; } } } return outputFormats; } @Override public boolean supportsTransaction(QName typeName) { try { getFeatureTypeInfo(typeName); } catch (IllegalArgumentException e) { throw e; } if (!supportsOperation(TRANSACTION, POST)) { return false; } return true; } /** * @return the operation metadata advertised in the capabilities for the given operation * @see #getServerSupportedOutputFormats(WFSOperationType) */ protected OperationType getOperationMetadata(final WFSOperationType operation) { final OperationsMetadataType operationsMetadata = capabilities.getOperationsMetadata(); @SuppressWarnings("unchecked") final List<OperationType> operations = operationsMetadata.getOperation(); final String expectedOperationName = operation.getName(); for (OperationType operationType : operations) { String operationName = operationType.getName(); if (expectedOperationName.equalsIgnoreCase(operationName)) { return operationType; } } throw new NoSuchElementException("Operation metadata not found for " + expectedOperationName + " in the capabilities document"); } @Override public Set<String> getSupportedCRSIdentifiers(QName typeName) { FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(typeName); String defaultSRS = featureTypeInfo.getDefaultSRS(); List<String> otherSRS = featureTypeInfo.getOtherSRS(); Set<String> ftypeCrss = new HashSet<String>(); ftypeCrss.add(defaultSRS); ftypeCrss.addAll(otherSRS); final boolean wfs2_0 = Versions.v2_0_0.equals(getServiceVersion()); if (wfs2_0) { OperationType operationMetadata = getOperationMetadata(GET_FEATURE); final String operationParameter = "SrsName"; Set<String> globalSrsNames = findParameters(operationMetadata, operationParameter); ftypeCrss.addAll(globalSrsNames); } return ftypeCrss; } @SuppressWarnings("unchecked") protected Set<String> findParameters(final OperationType operationMetadata, final String parameterName) { Set<String> outputFormats = new HashSet<String>(); List<DomainType> parameters = operationMetadata.getParameter(); for (DomainType param : parameters) { String paramName = param.getName(); if (parameterName.equals(paramName)) { for (ValueType value : (List<ValueType>) param.getAllowedValues().getValue()) { outputFormats.add(value.getValue()); } } } return outputFormats; } protected AbstractTransactionActionType createInsert(Wfs20Factory factory, Insert elem) throws Exception { InsertType insert = factory.createInsertType(); String srsName = getFeatureTypeInfo(elem.getTypeName()).getDefaultSRS(); insert.setSrsName(srsName); List<SimpleFeature> features = elem.getFeatures(); insert.getAny().addAll(features); return insert; } protected AbstractTransactionActionType createUpdate(Wfs20Factory factory, Update elem) throws Exception { List<QName> propertyNames = elem.getPropertyNames(); List<Object> newValues = elem.getNewValues(); if (propertyNames.size() != newValues.size()) { throw new IllegalArgumentException("Got " + propertyNames.size() + " property names and " + newValues.size() + " values"); } UpdateType update = factory.createUpdateType(); QName typeName = elem.getTypeName(); update.setTypeName(typeName); String srsName = getFeatureTypeInfo(typeName).getDefaultSRS(); update.setSrsName(srsName); Filter filter = elem.getFilter(); update.setFilter(filter); @SuppressWarnings("unchecked") List<PropertyType> properties = update.getProperty(); for (int i = 0; i < propertyNames.size(); i++) { QName propName = propertyNames.get(i); Object value = newValues.get(i); PropertyType property = factory.createPropertyType(); ValueReferenceType ref = factory.createValueReferenceType(); ref.setValue(propName); property.setValueReference(ref); property.setValue(value); properties.add(property); } return update; } protected AbstractTransactionActionType createDelete(Wfs20Factory factory, Delete elem) throws Exception { DeleteType delete = factory.createDeleteType(); QName typeName = elem.getTypeName(); delete.setTypeName(typeName); Filter filter = elem.getFilter(); delete.setFilter(filter); return delete; } }