/* * 3D City Database Web Feature Service * http://www.3dcitydb.org/ * * Copyright 2014 - 2016 * virtualcitySYSTEMS GmbH * Tauentzienstrasse 7b/c * 10789 Berlin, Germany * http://www.virtualcitysystems.de/ * * 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 vcs.citydb.wfs.operation.getcapabilities; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.namespace.QName; import net.opengis.fes._2.ConformanceType; import net.opengis.fes._2.Filter_Capabilities; import net.opengis.fes._2.Id_CapabilitiesType; import net.opengis.fes._2.ResourceIdentifierType; import net.opengis.ows._1.AllowedValues; import net.opengis.ows._1.DCP; import net.opengis.ows._1.DomainType; import net.opengis.ows._1.HTTP; import net.opengis.ows._1.NoValues; import net.opengis.ows._1.Operation; import net.opengis.ows._1.OperationsMetadata; import net.opengis.ows._1.RequestMethodType; import net.opengis.ows._1.ValueType; import net.opengis.wfs._2.FeatureTypeListType; import net.opengis.wfs._2.FeatureTypeType; import net.opengis.wfs._2.GetCapabilitiesType; import net.opengis.wfs._2.WFS_CapabilitiesType; import org.citydb.api.database.DatabaseSrs; import org.citydb.database.DatabaseConnectionPool; import org.citydb.log.Logger; import org.citydb.util.Util; import org.citygml4j.builder.jaxb.JAXBBuilder; import org.citygml4j.model.module.citygml.CityGMLModule; import org.citygml4j.model.module.citygml.CityGMLVersion; import org.citygml4j.model.module.gml.GMLCoreModule; import org.citygml4j.model.module.gml.XLinkModule; import org.citygml4j.util.xml.SAXWriter; import org.xml.sax.SAXException; import vcs.citydb.wfs.config.Constants; import vcs.citydb.wfs.config.WFSConfig; import vcs.citydb.wfs.config.capabilities.OWSMetadata; import vcs.citydb.wfs.config.feature.FeatureType; import vcs.citydb.wfs.config.operation.DescribeFeatureTypeOutputFormat; import vcs.citydb.wfs.config.operation.GetFeatureOutputFormat; import vcs.citydb.wfs.exception.WFSException; import vcs.citydb.wfs.exception.WFSExceptionCode; import vcs.citydb.wfs.util.LoggerUtil; import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper; public class GetCapabilitiesHandler { private final Logger log = Logger.getInstance(); private final WFSConfig wfsConfig; private final Marshaller marshaller; private final net.opengis.wfs._2.ObjectFactory wfsFactory; private final net.opengis.ows._1.ObjectFactory owsFactory; private final DatabaseConnectionPool connectionPool; public GetCapabilitiesHandler(JAXBBuilder jaxbBuilder, WFSConfig wfsConfig) throws JAXBException { this.wfsConfig = wfsConfig; wfsFactory = new net.opengis.wfs._2.ObjectFactory(); owsFactory = new net.opengis.ows._1.ObjectFactory(); marshaller = jaxbBuilder.getJAXBContext().createMarshaller(); connectionPool = DatabaseConnectionPool.getInstance(); } public void doOperation(GetCapabilitiesType wfsRequest, ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) throws WFSException { log.info(LoggerUtil.getLogMessage(request, "Accepting GetCapabilities request.")); // check service attribute if (!wfsRequest.isSetService() || !Constants.WFS_SERVICE_STRING.equals(wfsRequest.getService())) throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The attribute 'service' must match the fixed value '" + Constants.WFS_SERVICE_STRING + "'."); // negotiate version String version = Constants.DEFAULT_WFS_VERSION; if (wfsRequest.isSetAcceptVersions() && wfsRequest.getAcceptVersions().isSetVersion()) { boolean success = false; for (String tmp : wfsRequest.getAcceptVersions().getVersion()) { if (Constants.SUPPORTED_WFS_VERSIONS.contains(tmp)) { version = tmp; success = true; break; } } if (!success) throw new WFSException(WFSExceptionCode.VERSION_NEGOTIATION_FAILED, "None of the requested version numbers '" + Util.collection2string(wfsRequest.getAcceptVersions().getVersion(), ", ") + "' is supported by this WFS service implementation."); } // check whether we are supposed to return a static capabilities document boolean dynamic = true; if (wfsConfig.getCapabilities() != null && wfsConfig.getCapabilities().getValue() instanceof String) { File capabilities = new File((String)wfsConfig.getCapabilities().getValue()); if (capabilities.exists() && capabilities.canRead()) { printStaticCapabilitiesDocument(request, response, capabilities); dynamic = false; } } if (dynamic) { // dynamically generate capabilities document WFS_CapabilitiesType capabilities = new WFS_CapabilitiesType(); capabilities.setVersion(version); // service identification and provider are copied from configuration file if (wfsConfig.getCapabilities() != null && wfsConfig.getCapabilities().getValue() instanceof OWSMetadata) { OWSMetadata owsMetadata = (OWSMetadata)wfsConfig.getCapabilities().getValue(); capabilities.setServiceIdentification(owsMetadata.getServiceIdentification()); capabilities.setServiceProvider(owsMetadata.getServiceProvider()); } OperationsMetadata operations = new OperationsMetadata(); FeatureTypeListType featureTypeList = new FeatureTypeListType(); Filter_Capabilities filterCapabilities = new Filter_Capabilities(); capabilities.setOperationsMetadata(operations); capabilities.setFeatureTypeList(featureTypeList); capabilities.setFilter_Capabilities(filterCapabilities); // add operations addOperations(operations); // add service and operation constraints addServiceAndOperationConstraints(operations); // add feature types addFeatureTypes(featureTypeList); // add filter capabilities addFilterCapabilities(filterCapabilities); // write response printDynamicCapabilitiesDocument(request, response, capabilities); } log.info(LoggerUtil.getLogMessage(request, "GetCapabilities operation successfully finished.")); } private void addOperations(OperationsMetadata operationsMetadata) { // add operations to satisfy WFS Simple conformance class // create external service URL String externalServiceURL = wfsConfig.getServer().getExternalServiceURL(); if (externalServiceURL.endsWith("/")) externalServiceURL = externalServiceURL.substring(0, externalServiceURL.length() - 1); externalServiceURL += Constants.WFS_SERVICE_PATH; RequestMethodType request = new RequestMethodType(); request.setHref(externalServiceURL); DCP getAndPost = new DCP(); getAndPost.setHTTP(new HTTP()); getAndPost.getHTTP().getGetOrPost().add(owsFactory.createHTTPGet(request)); getAndPost.getHTTP().getGetOrPost().add(owsFactory.createHTTPPost(request)); DCP post = new DCP(); post.setHTTP(new HTTP()); post.getHTTP().getGetOrPost().add(owsFactory.createHTTPPost(request)); // operations version DomainType operationVersion = new DomainType(); operationVersion.setName("version"); operationVersion.setAllowedValues(new AllowedValues()); for (String version : Constants.SUPPORTED_WFS_VERSIONS) { ValueType value = new ValueType(); value.setValue(version); operationVersion.getAllowedValues().getValueOrRange().add(value); } // GetCapabilities operation { Operation getCapabilities = new Operation(); getCapabilities.setName("GetCapabilities"); getCapabilities.getDCP().add(getAndPost); operationsMetadata.getOperation().add(getCapabilities); DomainType acceptVersions = new DomainType(); getCapabilities.getParameter().add(acceptVersions); acceptVersions.setName("AcceptVersions"); acceptVersions.setAllowedValues(new AllowedValues()); for (String version : Constants.SUPPORTED_WFS_VERSIONS) { ValueType value = new ValueType(); value.setValue(version); acceptVersions.getAllowedValues().getValueOrRange().add(value); } DomainType acceptFormats = new DomainType(); getCapabilities.getParameter().add(acceptFormats); acceptFormats.setName("AcceptFormats"); acceptFormats.setAllowedValues(new AllowedValues()); ValueType format = new ValueType(); format.setValue("text/xml"); acceptFormats.getAllowedValues().getValueOrRange().add(format); } // DescribeFeatureType operation { Operation describeFeatureType = new Operation(); describeFeatureType.setName("DescribeFeatureType"); describeFeatureType.getDCP().add(post); operationsMetadata.getOperation().add(describeFeatureType); DomainType outputFormat = new DomainType(); describeFeatureType.getParameter().add(outputFormat); outputFormat.setName("outputFormat"); outputFormat.setAllowedValues(new AllowedValues()); for (DescribeFeatureTypeOutputFormat format : wfsConfig.getOperations().getDescribeFeatureType().getOutputFormat()) { ValueType value = new ValueType(); value.setValue(format.value()); outputFormat.getAllowedValues().getValueOrRange().add(value); } } // GetFeature operation { Operation getFeature = new Operation(); getFeature.setName("GetFeature"); getFeature.getDCP().add(post); operationsMetadata.getOperation().add(getFeature); DomainType outputFormat = new DomainType(); getFeature.getParameter().add(outputFormat); outputFormat.setName("outputFormat"); outputFormat.setAllowedValues(new AllowedValues()); for (GetFeatureOutputFormat format : wfsConfig.getOperations().getGetFeature().getOutputFormat()) { ValueType value = new ValueType(); value.setValue(format.value()); outputFormat.getAllowedValues().getValueOrRange().add(value); } } // ListStoredQueries operation { Operation listStoredQueries = new Operation(); listStoredQueries.setName("ListStoredQueries"); listStoredQueries.getDCP().add(post); operationsMetadata.getOperation().add(listStoredQueries); } // DescribeStoredQueries operation { Operation describeStoredQueries = new Operation(); describeStoredQueries.setName("DescribeStoredQueries"); describeStoredQueries.getDCP().add(post); operationsMetadata.getOperation().add(describeStoredQueries); } operationsMetadata.getParameter().add(operationVersion); } private void addServiceAndOperationConstraints(OperationsMetadata operationsMetadata) { ValueType trueValue = new ValueType(); trueValue.setValue("TRUE"); ValueType falseValue = new ValueType(); falseValue.setValue("FALSE"); // mandatory constraints LinkedHashMap<String, ValueType> constraints = new LinkedHashMap<>(); constraints.put("ImplementsBasicWFS", falseValue); constraints.put("ImplementsTransactionalWFS", falseValue); constraints.put("ImplementsLockingWFS", falseValue); constraints.put("KVPEncoding", falseValue); constraints.put("XMLEncoding", trueValue); constraints.put("SOAPEncoding", falseValue); constraints.put("ImplementsInheritance", falseValue); constraints.put("ImplementsRemoteResolve", falseValue); constraints.put("ImplementsResultPaging", falseValue); constraints.put("ImplementsStandardJoins", falseValue); constraints.put("ImplementsSpatialJoins", falseValue); constraints.put("ImplementsTemporalJoins", falseValue); constraints.put("ImplementsFeatureVersioning", falseValue); constraints.put("ManageStoredQueries", falseValue); for (Entry<String, ValueType> entry : constraints.entrySet()) { DomainType constraint = new DomainType(); constraint.setName(entry.getKey()); constraint.setDefaultValue(entry.getValue()); constraint.setNoValues(new NoValues()); operationsMetadata.getConstraint().add(constraint); } // optional constraints // default count if (wfsConfig.getSecurity().isSetMaxFeatureCount()) { DomainType countDefault = new DomainType(); countDefault.setName("CountDefault"); ValueType countDefaultValue = new ValueType(); countDefaultValue.setValue(String.valueOf(wfsConfig.getSecurity().getMaxFeatureCount())); countDefault.setDefaultValue(countDefaultValue); countDefault.setNoValues(new NoValues()); operationsMetadata.getConstraint().add(countDefault); } // we do not preserve sibling order DomainType siblingOrder = new DomainType(); siblingOrder.setName("PreservesSiblingOrder"); siblingOrder.setDefaultValue(falseValue); siblingOrder.setNoValues(new NoValues()); operationsMetadata.getConstraint().add(siblingOrder); // announce supported query types DomainType queryExpressions = new DomainType(); queryExpressions.setName("QueryExpressions"); queryExpressions.setAllowedValues(new AllowedValues()); ValueType storedQueryValue = new ValueType(); storedQueryValue.setValue(new StringBuilder(Constants.WFS_NAMESPACE_PREFIX).append(":").append("StoredQuery").toString()); queryExpressions.getAllowedValues().getValueOrRange().add(storedQueryValue); operationsMetadata.getConstraint().add(queryExpressions); } private void addFeatureTypes(FeatureTypeListType featureTypeList) { for (FeatureType featureType : wfsConfig.getFeatureTypes().getFeatureTypes()) { for (CityGMLVersion version : wfsConfig.getFeatureTypes().getVersions()) { // announce feature type by name FeatureTypeType type = new FeatureTypeType(); QName name = featureType.getQName(version); if (name == null) continue; type.setName(name); featureTypeList.getFeatureType().add(type); // create list of supported SRS List<DatabaseSrs> srsList = new ArrayList<>(); srsList.add(connectionPool.getActiveDatabaseAdapter().getConnectionMetaData().getReferenceSystem()); for (DatabaseSrs srs : srsList) { String srsName = srs.getGMLSrsName(); if (srsName == null || srsName.trim().length() == 0) srsName = "http://www.opengis.net/def/crs/epsg/0/" + srs.getSrid(); if (srs == connectionPool.getActiveDatabaseAdapter().getConnectionMetaData().getReferenceSystem()) type.setDefaultCRS(srsName); else type.getOtherCRS().add(srsName); } // announce bounding box if (featureType.getWGS84BoundingBox() != null) type.getWGS84BoundingBox().add(featureType.getWGS84BoundingBox()); } } } private void addFilterCapabilities(Filter_Capabilities filterCapabilities) { // conformance section ConformanceType conformance = new ConformanceType(); filterCapabilities.setConformance(conformance); ValueType trueValue = new ValueType(); trueValue.setValue("TRUE"); ValueType falseValue = new ValueType(); falseValue.setValue("FALSE"); LinkedHashMap<String, ValueType> constraints = new LinkedHashMap<>(); constraints.put("ImplementsQuery", trueValue); constraints.put("ImplementsAdHocQuery", falseValue); constraints.put("ImplementsFunctions", falseValue); constraints.put("ImplementsResourceld", falseValue); constraints.put("ImplementsMinStandardFilter", falseValue); constraints.put("ImplementsStandardFilter", falseValue); constraints.put("ImplementsMinSpatialFilter", falseValue); constraints.put("ImplementsSpatialFilter", falseValue); constraints.put("ImplementsMinTemporalFilter", falseValue); constraints.put("ImplementsTemporalFilter", falseValue); constraints.put("ImplementsVersionNav", falseValue); constraints.put("ImplementsSorting", falseValue); constraints.put("ImplementsExtendedOperators", falseValue); constraints.put("ImplementsMinimumXPath", falseValue); constraints.put("ImplementsSchemaElementFunc", falseValue); for (Entry<String, ValueType> entry : constraints.entrySet()) { DomainType constraint = new DomainType(); constraint.setName(entry.getKey()); constraint.setDefaultValue(entry.getValue()); constraint.setNoValues(new NoValues()); conformance.getConstraint().add(constraint); } // id capabilities Id_CapabilitiesType idCapabilities = new Id_CapabilitiesType(); filterCapabilities.setId_Capabilities(idCapabilities); ResourceIdentifierType resourceIdentifier = new ResourceIdentifierType(); resourceIdentifier.setName(new QName(Constants.FES_NAMESPACE_URI, "ResourceId")); idCapabilities.getResourceIdentifier().add(resourceIdentifier); } private void printDynamicCapabilitiesDocument(HttpServletRequest request, HttpServletResponse response, WFS_CapabilitiesType capabilities) throws WFSException { final SAXWriter saxWriter = new SAXWriter(); try { JAXBElement<WFS_CapabilitiesType> responseElement = wfsFactory.createWFS_Capabilities(capabilities); // write response response.setContentType("text/xml"); response.setCharacterEncoding("UTF-8"); saxWriter.setWriteEncoding(true); saxWriter.setIndentString(" "); saxWriter.setPrefix(Constants.WFS_NAMESPACE_PREFIX, Constants.WFS_NAMESPACE_URI); saxWriter.setPrefix(Constants.FES_NAMESPACE_PREFIX, Constants.FES_NAMESPACE_URI); saxWriter.setPrefix(Constants.OWS_NAMESPACE_PREFIX, Constants.OWS_NAMESPACE_URI); saxWriter.setPrefix(GMLCoreModule.v3_1_1.getNamespacePrefix(), GMLCoreModule.v3_1_1.getNamespaceURI()); saxWriter.setPrefix(XLinkModule.v3_1_1.getNamespacePrefix(), XLinkModule.v3_1_1.getNamespaceURI()); // declare CityGML namespaces boolean multipleVersions = wfsConfig.getFeatureTypes().getVersions().size() > 1; for (CityGMLModule module : wfsConfig.getFeatureTypes().getCityGMLModules()) { String prefix = module.getNamespacePrefix(); String uri = module.getNamespaceURI(); if (multipleVersions) prefix += (CityGMLVersion.fromCityGMLModule(module) == CityGMLVersion.v2_0_0) ? "2" : "1"; saxWriter.setPrefix(prefix, uri); } saxWriter.setSchemaLocation(Constants.WFS_NAMESPACE_URI, Constants.WFS_SCHEMA_LOCATION); saxWriter.setOutput(new OutputStreamWriter(response.getOutputStream(), "UTF-8")); marshaller.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", new NamespacePrefixMapper() { public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) { return saxWriter.getPrefix(namespaceUri); } }); marshaller.marshal(responseElement, saxWriter); // close SAX writer. this also closes the servlet output stream. saxWriter.close(); } catch (JAXBException e) { throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "A fatal JAXB error occurred whilst marshalling the response document.", e); } catch (IOException e) { throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "A fatal SAX error occurred whilst marshalling the response document.", e); } catch (SAXException e) { throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to close the SAX writer..", e); } } private void printStaticCapabilitiesDocument(HttpServletRequest request, HttpServletResponse response, File capabilities) throws WFSException { try { response.setContentType("text/xml"); response.setCharacterEncoding("UTF-8"); BufferedReader reader = new BufferedReader(new FileReader(capabilities)); PrintWriter writer = response.getWriter(); String line; while ((line = reader.readLine()) != null) writer.println(line); writer.flush(); reader.close(); } catch (IOException e) { throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to read local capabilities document.", e); } } }