/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.wfs; import static org.geoserver.ows.util.ResponseUtils.appendQueryString; import static org.geoserver.ows.util.ResponseUtils.buildSchemaURL; import static org.geoserver.ows.util.ResponseUtils.buildURL; import static org.geoserver.ows.util.ResponseUtils.params; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Logger; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.KeywordInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.config.ContactInfo; import org.geoserver.config.GeoServer; import org.geoserver.ows.URLMangler.URLType; import org.geoserver.ows.xml.v1_0.OWS; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.ServiceException; import org.geoserver.wfs.CapabilitiesTransformer.WFS1_1.CapabilitiesTranslator1_1; import org.geoserver.wfs.request.GetCapabilitiesRequest; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.NameImpl; import org.geotools.filter.FunctionFactory; import org.geotools.filter.v1_0.OGC; import org.geotools.filter.v2_0.FES; import org.geotools.gml3.GML; import org.geotools.xlink.XLINK; import org.geotools.xml.transform.TransformerBase; import org.geotools.xml.transform.Translator; import org.geotools.xs.XS; import org.opengis.feature.type.AttributeType; import org.opengis.feature.type.Name; import org.opengis.feature.type.Schema; import org.opengis.filter.FilterFactory; import org.opengis.filter.capability.FunctionName; import org.opengis.parameter.Parameter; import org.vfny.geoserver.global.FeatureTypeInfoTitleComparator; import org.xml.sax.ContentHandler; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.NamespaceSupport; import com.vividsolutions.jts.geom.Envelope; /** * Based on the <code>org.geotools.xml.transform</code> framework, does the job * of encoding a WFS 1.0 Capabilities document. * * @author Gabriel Roldan, Axios Engineering * @author Chris Holmes * @author Justin Deoliveira * * @version $Id$ */ public abstract class CapabilitiesTransformer extends TransformerBase { /** logger */ private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(CapabilitiesTransformer.class.getPackage() .getName()); /** identifer of a http get + post request */ private static final String HTTP_GET = "Get"; private static final String HTTP_POST = "Post"; /** wfs namespace */ protected static final String WFS_URI = "http://www.opengis.net/wfs"; /** xml schema namespace + prefix */ protected static final String XSI_PREFIX = "xsi"; protected static final String XSI_URI = "http://www.w3.org/2001/XMLSchema-instance"; /** filter namesapce + prefix */ protected static final String OGC_PREFIX = "ogc"; protected static final String OGC_URI = OGC.NAMESPACE; /** wfs service */ protected WFSInfo wfs; /** catalog */ protected Catalog catalog; /** * Creates a new CapabilitiesTransformer object. */ public CapabilitiesTransformer(WFSInfo wfs, Catalog catalog) { super(); setNamespaceDeclarationEnabled(false); this.wfs = wfs; this.catalog = catalog; } /** * It turns out that the he WFS 1.0 and 1.1 specifications don't actually support an updatesequence-based * getcapabilities operation. There's no mention of an updatesequence request parameter in the getcapabilities * operation, and there's no normative behaviour description for what the updatesequence parameter in the * capabilities document should *do*. * * So this behaviour is not used right now, at all (as of Jan 2007) * @param request * @throws ServiceException */ public void verifyUpdateSequence(GetCapabilitiesRequest request) throws ServiceException { long reqUS = -1; if (request.getUpdateSequence() != null) { try { reqUS = Long.parseLong(request.getUpdateSequence()); } catch (NumberFormatException nfe) { throw new ServiceException("GeoServer only accepts numbers in the updateSequence parameter"); } } long geoUS = wfs.getGeoServer().getGlobal().getUpdateSequence(); if (reqUS > geoUS) { throw new ServiceException("Client supplied an updateSequence that is greater than the current sever updateSequence","InvalidUpdateSequence"); } if (reqUS == geoUS) { throw new ServiceException("WFS capabilities document is current (updateSequence = " + geoUS + ")","CurrentUpdateSequence"); } } Set<FunctionName> getAvailableFunctionNames() { //Sort them up for easier visual inspection SortedSet sortedFunctions = new TreeSet(new Comparator() { public int compare(Object o1, Object o2) { String n1 = ((FunctionName) o1) .getName(); String n2 = ((FunctionName) o2).getName(); return n1.toLowerCase().compareTo(n2.toLowerCase()); } }); Set<FunctionFactory> factories = CommonFactoryFinder.getFunctionFactories(null); for (FunctionFactory factory : factories) { sortedFunctions.addAll(factory.getFunctionNames()); } return sortedFunctions; } String[] getAvailableOutputFormatNames(String first) { List<String> oflist = new ArrayList<String>(); Collection featureProducers = GeoServerExtensions.extensions(WFSGetFeatureOutputFormat.class); for (Iterator i = featureProducers.iterator(); i.hasNext();) { WFSGetFeatureOutputFormat format = (WFSGetFeatureOutputFormat) i.next(); for ( Iterator f = format.getOutputFormats().iterator(); f.hasNext(); ) { oflist.add(f.next().toString()); } } Collections.sort(oflist); if(oflist.contains(first)) { oflist.remove(first); oflist.add(0, first); } return (String[]) oflist.toArray(new String[oflist.size()]); } void updateSequence(AttributesImpl attributes) { attributes.addAttribute("", "updateSequence", "updateSequence", "", wfs.getGeoServer().getGlobal().getUpdateSequence() + ""); } void registerNamespaces(AttributesImpl attributes) { List<NamespaceInfo> namespaces = catalog.getNamespaces(); for (NamespaceInfo namespace : namespaces) { String prefix = namespace.getPrefix(); String uri = namespace.getURI(); //ignore xml prefix if ("xml".equals(prefix)) { continue; } String prefixDef = "xmlns:" + prefix; attributes.addAttribute("", prefixDef, prefixDef, "", uri); } } AttributesImpl attributes(String[] nameValues) { AttributesImpl atts = new AttributesImpl(); for (int i = 0; i < nameValues.length; i += 2) { String name = nameValues[i]; String valu = nameValues[i + 1]; atts.addAttribute(null, null, name, null, valu); } return atts; } Map.Entry parameter(final String name, final Object value) { return new Map.Entry() { public Object getKey() { return name; } public Object getValue() { return value; } public Object setValue(Object value) { return null; } }; } /** * Transformer for wfs 1.0 capabilities document. */ public static class WFS1_0 extends CapabilitiesTransformer { public WFS1_0(WFSInfo wfs, Catalog catalog) { super(wfs, catalog); } public Translator createTranslator(ContentHandler handler) { return new CapabilitiesTranslator1_0(handler); } class CapabilitiesTranslator1_0 extends TranslatorSupport { GetCapabilitiesRequest request; public CapabilitiesTranslator1_0(ContentHandler handler) { super(handler, null, null); } public void encode(Object object) throws IllegalArgumentException { request = GetCapabilitiesRequest.adapt(object); // Not used. WFS 1.1 and 1.0 don't actually support updatesequence //verifyUpdateSequence(request); AttributesImpl attributes = new AttributesImpl(); attributes.addAttribute("", "version", "version", "", "1.0.0"); attributes.addAttribute("", "xmlns", "xmlns", "", WFS_URI); List<NamespaceInfo> namespaces = catalog.getNamespaces(); for (NamespaceInfo namespace : namespaces ) { String prefix = namespace.getPrefix(); String uri = namespace.getURI(); if ("xml".equals(prefix)) { continue; } String prefixDef = "xmlns:" + prefix; attributes.addAttribute("", prefixDef, prefixDef, "", uri); } //filter attributes.addAttribute("", "xmlns:" + OGC_PREFIX, "xmlns:" + OGC_PREFIX, "", OGC_URI); //xml schema attributes.addAttribute("", "xmlns:" + XSI_PREFIX, "xmlns:" + XSI_PREFIX, "", XSI_URI); String locationAtt = XSI_PREFIX + ":schemaLocation"; String locationDef = WFS_URI + " " + (wfs.isCanonicalSchemaLocation()?org.geoserver.wfs.xml.v1_0_0.WFS.CANONICAL_SCHEMA_LOCATION_CAPABILITIES: buildSchemaURL(request.getBaseUrl(), "wfs/1.0.0/WFS-capabilities.xsd")); attributes.addAttribute("", locationAtt, locationAtt, "", locationDef); start("WFS_Capabilities", attributes); handleService(); handleCapability(); handleFeatureTypes(); handleFilterCapabilities(); end("WFS_Capabilities"); } /** * Encodes the wfs:Service element. * * <pre> * <xsd:complexType name="ServiceType"> * <xsd:sequence> * <xsd:element name="Name" type="xsd:string"/> * <xsd:element ref="wfs:Title"/> * <xsd:element ref="wfs:Abstract" minOccurs="0"/> * <xsd:element ref="wfs:Keywords" minOccurs="0"/> * <xsd:element ref="wfs:OnlineResource"/> * <xsd:element ref="wfs:Fees" minOccurs="0"/> * <xsd:element ref="wfs:AccessConstraints" minOccurs="0"/> * </xsd:sequence> * </xsd:complexType> * * </pre> * */ private void handleService() { start("Service"); element("Name", wfs.getName()); element("Title", wfs.getTitle()); element("Abstract", wfs.getAbstract()); handleKeywords(wfs.getKeywords()); element("OnlineResource", buildURL(request.getBaseUrl(), "wfs", null, URLType.SERVICE)); element("Fees", wfs.getFees()); element("AccessConstraints", wfs.getAccessConstraints()); end("Service"); } /** * Encodes the wfs:Keywords element. * <p> * * <pre> * <!-- Short words to help catalog searching. * Currently, no controlled vocabulary has * been defined. --> * <xsd:element name="Keywords" type="xsd:string"/> * </pre> * * </p> * */ private void handleKeywords(String[] kwlist) { if (kwlist == null) { handleKeywords((List) null); } else { handleKeywords(Arrays.asList(kwlist)); } } /** * Encodes the wfs:Keywords element. * <p> * * <pre> * <!-- Short words to help catalog searching. * Currently, no controlled vocabulary has * been defined. --> * <xsd:element name="Keywords" type="xsd:string"/> * </pre> * * </p> * */ private void handleKeywords(List kwlist) { StringBuffer kwds = new StringBuffer(); for (int i = 0; (kwlist != null) && (i < kwlist.size()); i++) { kwds.append(kwlist.get(i)); if (i != (kwlist.size() - 1)) { kwds.append(", "); } } element("Keywords", kwds.toString()); } /** * Encodes the wfs:Capability element. * <p> * * <pre> * <xsd:complexType name="CapabilityType"> * <xsd:sequence> * <xsd:element name="Request" type="wfs:RequestType"/> * <!-- The optional VendorSpecificCapabilities element lists any * capabilities unique to a particular server. Because the * information is not known a priori, it cannot be constrained * by this particular schema document. A vendor-specific schema * fragment must be supplied at the start of the XML capabilities * document, after the reference to the general WFS_Capabilities * schema. --> * <xsd:element ref="wfs:VendorSpecificCapabilities" minOccurs="0"/> * </xsd:sequence> * </xsd:complexType> * </pre> * * </p> */ private void handleCapability() { start("Capability"); start("Request"); handleGetCapabilities(); handleDescribeFT(); handleGetFeature(); if (wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.TRANSACTIONAL ) ) { handleTransaction(); } if (wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.COMPLETE ) ) { handleLock(); handleFeatureWithLock(); } end("Request"); end("Capability"); } /** * Encodes the wfs:GetCapabilities elemnt. * <p> * * <pre> * <xsd:complexType name="GetCapabilitiesType"> * <xsd:sequence> * <xsd:element name="DCPType" type="wfs:DCPTypeType" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> * </pre> * * </p> */ private void handleGetCapabilities() { String capName = "GetCapabilities"; start(capName); handleDcpType(capName, HTTP_GET); handleDcpType(capName, HTTP_POST); end(capName); } /** * Encodes the wfs:DescribeFeatureType element. * <p> * <pre> * <xsd:complexType name="DescribeFeatureTypeType"> * <xsd:sequence> * <xsd:element name="SchemaDescriptionLanguage" * type="wfs:SchemaDescriptionLanguageType"/> * <xsd:element name="DCPType" * type="wfs:DCPTypeType" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> * </pre> * </p> */ private void handleDescribeFT() { String capName = "DescribeFeatureType"; start(capName); start("SchemaDescriptionLanguage"); element("XMLSCHEMA", null); end("SchemaDescriptionLanguage"); handleDcpType(capName, HTTP_GET); handleDcpType(capName, HTTP_POST); end(capName); } /** * Encodes the wfs:GetFeature element. * * <xsd:complexType name="GetFeatureTypeType"> * <xsd:sequence> * <xsd:element name="ResultFormat" type="wfs:ResultFormatType"/> * <xsd:element name="DCPType" type="wfs:DCPTypeType" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> */ private void handleGetFeature() { String capName = "GetFeature"; start(capName); String resultFormat = "ResultFormat"; start(resultFormat); //we accept numerous formats, but cite only allows you to have GML2 if (wfs.isCiteCompliant()) { element("GML2", null); } else { //FULL MONTY Collection featureProducers = GeoServerExtensions.extensions(WFSGetFeatureOutputFormat.class); Map dupes = new HashMap(); for (Iterator i = featureProducers.iterator(); i.hasNext();) { WFSGetFeatureOutputFormat format = (WFSGetFeatureOutputFormat) i.next(); if (!dupes.containsKey(format.getCapabilitiesElementName())) { element(format.getCapabilitiesElementName(), null); dupes.put(format.getCapabilitiesElementName(), new Object()); } } } end(resultFormat); handleDcpType(capName, HTTP_GET); handleDcpType(capName, HTTP_POST); end(capName); } /** * Encodes the wfs:Transaction element. * <p> * <pre> * <xsd:complexType name="TransactionType"> * <xsd:sequence> * <xsd:element name="DCPType" type="wfs:DCPTypeType" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> * </pre> * </p> */ private void handleTransaction() { String capName = "Transaction"; start(capName); handleDcpType(capName, HTTP_GET); handleDcpType(capName, HTTP_POST); end(capName); } /** * Encodes the wfs:LockFeature element. * <p> * <pre> * <xsd:complexType name="LockFeatureTypeType"> * <xsd:sequence> * <xsd:element name="DCPType" type="wfs:DCPTypeType" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> * </pre> * </p> */ private void handleLock() { String capName = "LockFeature"; start(capName); handleDcpType(capName, HTTP_GET); handleDcpType(capName, HTTP_POST); end(capName); } /** * Encodes the wfs:GetFeatureWithLock element. * * <xsd:complexType name="GetFeatureTypeType"> * <xsd:sequence> * <xsd:element name="ResultFormat" type="wfs:ResultFormatType"/> * <xsd:element name="DCPType" type="wfs:DCPTypeType" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> */ private void handleFeatureWithLock() { String capName = "GetFeatureWithLock"; start(capName); start("ResultFormat"); //TODO: output format extensions element("GML2", null); end("ResultFormat"); handleDcpType(capName, HTTP_GET); handleDcpType(capName, HTTP_POST); end(capName); } /** * Encodes a <code>DCPType</code> element. * <p> * <pre> * <!-- Available Distributed Computing Platforms (DCPs) are * listed here. At present, only HTTP is defined. --> * <xsd:complexType name="DCPTypeType"> * <xsd:sequence> * <xsd:element name="HTTP" type="wfs:HTTPType"/> * </xsd:sequence> * </xsd:complexType> * * </pre> * </p> * @param capabilityName the URL of the onlineresource for HTTP GET * method requests * @param httpMethod the URL of the onlineresource for HTTP POST method * requests */ private void handleDcpType(String capabilityName, String httpMethod) { String onlineResource; if(HTTP_GET.equals(httpMethod)) { onlineResource = buildURL(request.getBaseUrl(), "wfs", params("request", capabilityName), URLType.SERVICE); } else { // make sure it ends with ? onlineResource = buildURL(request.getBaseUrl(), "wfs", null, URLType.SERVICE); appendQueryString(onlineResource, ""); } start("DCPType"); start("HTTP"); AttributesImpl atts = new AttributesImpl(); atts.addAttribute("", "onlineResource", "onlineResource", "", onlineResource); element(httpMethod, null, atts); end("HTTP"); end("DCPType"); } /** * Encodes the wfs:FeatureTYpeList element. * <p> * <pre> * <xsd:complexType name="FeatureTypeListType"> * <xsd:sequence> * <xsd:element name="Operations" * type="wfs:OperationsType" minOccurs="0"/> * <xsd:element name="FeatureType" * type="wfs:FeatureTypeType" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> * </pre> * </p> */ private void handleFeatureTypes() { if (!wfs.isEnabled()) { // should we return anything if we are disabled? } start("FeatureTypeList"); start("Operations"); if ((wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.BASIC ) )) { element("Query", null); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.TRANSACTION_INSERT))) { element("Insert", null); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.TRANSACTION_UPDATE))) { element("Update", null); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.TRANSACTION_DELETE))) { element("Delete", null); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.LOCKFEATURE))) { element("Lock", null); } end("Operations"); List featureTypes = new ArrayList(catalog.getFeatureTypes()); // filter out disabled feature types for (Iterator it = featureTypes.iterator(); it.hasNext();) { FeatureTypeInfo ft = (FeatureTypeInfo) it.next(); if(!ft.enabled()) it.remove(); } // filter the layers if a namespace filter has been set if(request.getNamespace() != null) { String namespace = request.getNamespace(); for (Iterator it = featureTypes.iterator(); it.hasNext();) { FeatureTypeInfo ft = (FeatureTypeInfo) it.next(); if(!namespace.equals(ft.getNamespace().getPrefix())) it.remove(); } } Collections.sort(featureTypes, new FeatureTypeInfoTitleComparator()); for (Iterator it = featureTypes.iterator(); it.hasNext();) { FeatureTypeInfo ftype = (FeatureTypeInfo) it.next(); handleFeatureType(ftype); } end("FeatureTypeList"); } /** * Default handle of a FeatureTypeInfo content that writes the * latLongBBox as well as the GlobalBasic's parameters * * <p> * <pre> * <xsd:complexType name="FeatureTypeType"> * <xsd:sequence> * <xsd:element name="Name" type="xsd:QName"/> * <xsd:element ref="wfs:Title" minOccurs="0"/> * <xsd:element ref="wfs:Abstract" minOccurs="0"/> * <xsd:element ref="wfs:Keywords" minOccurs="0"/> * <xsd:element ref="wfs:SRS"/> * <xsd:element name="Operations" * type="wfs:OperationsType" minOccurs="0"/> * <xsd:element name="LatLongBoundingBox" * type="wfs:LatLongBoundingBoxType" * minOccurs="0" maxOccurs="unbounded"/> * <xsd:element name="MetadataURL" * type="wfs:MetadataURLType" * minOccurs="0" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> * </pre> * </p> * @param ftype The FeatureType configuration to report capabilities * on. * * @throws RuntimeException For any errors. */ private void handleFeatureType(FeatureTypeInfo info) { Envelope bbox = null; bbox = info.getLatLonBoundingBox(); start("FeatureType"); element("Name", info.getPrefixedName()); element("Title", info.getTitle()); element("Abstract", info.getAbstract()); handleKeywords(info.getKeywords()); element("SRS", info.getSRS()); String minx = String.valueOf(bbox.getMinX()); String miny = String.valueOf(bbox.getMinY()); String maxx = String.valueOf(bbox.getMaxX()); String maxy = String.valueOf(bbox.getMaxY()); AttributesImpl bboxAtts = new AttributesImpl(); bboxAtts.addAttribute("", "minx", "minx", "", minx); bboxAtts.addAttribute("", "miny", "miny", "", miny); bboxAtts.addAttribute("", "maxx", "maxx", "", maxx); bboxAtts.addAttribute("", "maxy", "maxy", "", maxy); element("LatLongBoundingBox", null, bboxAtts); end("FeatureType"); } /** * Encodes the ogc:Filter_Capabilities element. * <p> * <pre> * <xsd:element name="Filter_Capabilities"> * <xsd:complexType> * <xsd:sequence> * <xsd:element name="Spatial_Capabilities" type="ogc:Spatial_CapabilitiesType"/> * <xsd:element name="Scalar_Capabilities" type="ogc:Scalar_CapabilitiesType"/> * </xsd:sequence> * </xsd:complexType> *</xsd:element> * </pre> * </p> */ private void handleFilterCapabilities() { String ogc = "ogc:"; //REVISIT: for now I"m just prepending ogc onto the name element. //Is the proper way to only do that for the qname? I guess it //would only really matter if we're going to be producing capabilities //documents that aren't qualified, and I don't see any reason to //do that. start(ogc + "Filter_Capabilities"); start(ogc + "Spatial_Capabilities"); start(ogc + "Spatial_Operators"); element(ogc + "Disjoint", null); element(ogc + "Equals", null); element(ogc + "DWithin", null); element(ogc + "Beyond", null); element(ogc + "Intersect", null); element(ogc + "Touches", null); element(ogc + "Crosses", null); element(ogc + "Within", null); element(ogc + "Contains", null); element(ogc + "Overlaps", null); element(ogc + "BBOX", null); end(ogc + "Spatial_Operators"); end(ogc + "Spatial_Capabilities"); start(ogc + "Scalar_Capabilities"); element(ogc + "Logical_Operators", null); start(ogc + "Comparison_Operators"); element(ogc + "Simple_Comparisons", null); element(ogc + "Between", null); element(ogc + "Like", null); element(ogc + "NullCheck", null); end(ogc + "Comparison_Operators"); start(ogc + "Arithmetic_Operators"); element(ogc + "Simple_Arithmetic", null); handleFunctions(ogc); //djb: list functions end(ogc + "Arithmetic_Operators"); end(ogc + "Scalar_Capabilities"); end(ogc + "Filter_Capabilities"); } /** * <xsd:complexType name="FunctionsType"> * <xsd:sequence> * <xsd:element name="Function_Names" type="ogc:Function_NamesType"/> * </xsd:sequence> * </xsd:complexType> * */ private void handleFunctions(String prefix) { FilterFactory ff = CommonFactoryFinder.getFilterFactory(null); start(prefix + "Functions"); start(prefix + "Function_Names"); Set<FunctionName> functions = getAvailableFunctionNames(); Iterator it = functions.iterator(); while (it.hasNext()) { FunctionName fname = (FunctionName) it.next(); AttributesImpl atts = new AttributesImpl(); atts.addAttribute("", "nArgs", "nArgs", "", fname.getArgumentCount() + ""); element(prefix + "Function_Name", fname.getName(), atts); } end(prefix + "Function_Names"); end(prefix + "Functions"); } } } /** * Transformer for wfs 1.1 capabilities document. */ public static class WFS1_1 extends CapabilitiesTransformer { public WFS1_1(WFSInfo wfs, Catalog catalog) { super(wfs, catalog); } public Translator createTranslator(ContentHandler handler) { return new CapabilitiesTranslator1_1(handler); } class CapabilitiesTranslator1_1 extends TranslatorSupport { private static final String GML_3_1_1_FORMAT = "text/xml; subtype=gml/3.1.1"; GetCapabilitiesRequest request; public CapabilitiesTranslator1_1(ContentHandler handler) { super(handler, null, null); } public void encode(Object object) throws IllegalArgumentException { request = GetCapabilitiesRequest.adapt(object); verifyUpdateSequence(request); AttributesImpl attributes = attributes(new String[] { "version", "1.1.0", "xmlns:xsi", XSI_URI, "xmlns", WFS_URI, "xmlns:wfs", WFS_URI, "xmlns:ows", OWS.NAMESPACE, "xmlns:gml", GML.NAMESPACE, "xmlns:ogc", OGC.NAMESPACE, "xmlns:xlink", XLINK.NAMESPACE, "xsi:schemaLocation", org.geoserver.wfs.xml.v1_1_0.WFS.NAMESPACE + " " + (wfs.isCanonicalSchemaLocation()? org.geoserver.wfs.xml.v1_1_0.WFS.CANONICAL_SCHEMA_LOCATION: (buildSchemaURL(request.getBaseUrl(), "wfs/1.1.0/wfs.xsd"))) }); registerNamespaces(attributes); updateSequence(attributes); start("wfs:WFS_Capabilities", attributes); serviceIdentification(); serviceProvider(wfs.getGeoServer()); operationsMetadata(); featureTypeList(); //supportsGMLObjectTypeList(); filterCapabilities(); end("wfs:WFS_Capabilities"); } /** * Encodes the ows:ServiceIdentification element. * <p> * <pre> * <complexType> * <complexContent> * <extension base="ows:DescriptionType"> * <sequence> * <element name="ServiceType" type="ows:CodeType"> * <annotation> * <documentation>A service type name from a registry of services. * For example, the values of the codeSpace URI and name and code string may * be "OGC" and "catalogue." This type name is normally used for * machine-to-machine communication.</documentation> * </annotation> * </element> * <element name="ServiceTypeVersion" type="ows:VersionType" maxOccurs="unbounded"> * <annotation> * <documentation>Unordered list of one or more versions of this service * type implemented by this server. This information is not adequate for * version negotiation, and shall not be used for that purpose. </documentation> * </annotation> * </element> * <element ref="ows:Fees" minOccurs="0"> * <annotation> * <documentation>If this element is omitted, no meaning is implied. </documentation> * </annotation> * </element> * <element ref="ows:AccessConstraints" minOccurs="0" maxOccurs="unbounded"> * <annotation> * <documentation>Unordered list of access constraints applied to assure * the protection of privacy or intellectual property, and any other * restrictions on retrieving or using data from or otherwise using this * server. The reserved value NONE (case insensitive) shall be used to * mean no access constraints are imposed. If this element is omitted, * no meaning is implied. </documentation> * </annotation> * </element> * </sequence> * </extension> * </complexContent> *</complexType> * </pre> * </p> * */ void serviceIdentification() { serviceIdentification("1.1.0"); } void serviceIdentification(String version) { start("ows:ServiceIdentification"); element("ows:Title", wfs.getTitle()); element("ows:Abstract", wfs.getAbstract()); keywords(wfs.getKeywords()); element("ows:ServiceType", "WFS"); element("ows:ServiceTypeVersion", version); element("ows:Fees", wfs.getFees()); element("ows:AccessConstraints", wfs.getAccessConstraints()); end("ows:ServiceIdentification"); } /** * Encodes the ows:ServiceProvider element. * <p> * <pre> * <complexType> * <sequence> * <element name="ProviderName" type="string"> * <annotation> * <documentation>A unique identifier for the service provider organization. </documentation> * </annotation> * </element> * <element name="ProviderSite" type="ows:OnlineResourceType" minOccurs="0"> * <annotation> * <documentation>Reference to the most relevant web site of the service provider. </documentation> * </annotation> * </element> * <element name="ServiceContact" type="ows:ResponsiblePartySubsetType"> * <annotation> * <documentation>Information for contacting the service provider. The * OnlineResource element within this ServiceContact element should not be used * to reference a web site of the service provider. </documentation> * </annotation> * </element> * </sequence> *</complexType> * </pre> * </p> * */ void serviceProvider(GeoServer gs) { ContactInfo contact = gs.getGlobal().getContact(); start("ows:ServiceProvider"); element("ows:ProviderName", contact.getContactOrganization()); start( "ows:ServiceContact"); /* <sequence> <element ref="ows:IndividualName" minOccurs="0"/> <element ref="ows:OrganisationName" minOccurs="0"/> <element ref="ows:PositionName" minOccurs="0"/> <element ref="ows:ContactInfo" minOccurs="0"/> <element ref="ows:Role"/> </sequence> */ element( "ows:IndividualName", contact.getContactPerson()); element( "ows:PositionName", contact.getContactPosition() ); start( "ows:ContactInfo" ); /* <sequence> <element name="Phone" type="ows:TelephoneType" minOccurs="0"> <element name="Address" type="ows:AddressType" minOccurs="0"> <element name="OnlineResource" type="ows:OnlineResourceType" minOccurs="0"> <element name="HoursOfService" type="string" minOccurs="0"> <element name="ContactInstructions" type="string" minOccurs="0"> </sequence> */ start( "ows:Phone"); element( "ows:Voice", contact.getContactVoice() ); element( "ows:Facsimile", contact.getContactFacsimile() ); end( "ows:Phone"); start( "ows:Address"); /* <element name="DeliveryPoint" type="string" minOccurs="0" maxOccurs="unbounded"> <element name="City" type="string" minOccurs="0"> <element name="AdministrativeArea" type="string" minOccurs="0"> <element name="PostalCode" type="string" minOccurs="0"> <element name="Country" type="string" minOccurs="0"> <element name="ElectronicMailAddress" type="string" minOccurs="0" maxOccurs="unbounded"> */ element( "ows:City", contact.getAddressCity() ); element( "ows:AdministrativeArea", contact.getAddressState() ); element( "ows:PostalCode", contact.getAddressPostalCode() ); element( "ows:Country", contact.getAddressCountry() ); end( "ows:Address" ); end( "ows:ContactInfo" ); end( "ows:ServiceContact"); end("ows:ServiceProvider"); } /** * Encodes the ows:OperationsMetadata element. * <p> * <pre> * <complexType> * <sequence> * <element ref="ows:Operation" minOccurs="2" maxOccurs="unbounded"> * <annotation> * <documentation>Metadata for unordered list of all the (requests for) operations * that this server interface implements. The list of required and optional * operations implemented shall be specified in the Implementation Specification * for this service. </documentation> * </annotation> * </element> * <element name="Parameter" type="ows:DomainType" minOccurs="0" maxOccurs="unbounded"> * <annotation> * <documentation>Optional unordered list of parameter valid domains that each * apply to one or more operations which this server interface implements. The * list of required and optional parameter domain limitations shall be specified * in the Implementation Specification for this service. </documentation> * </annotation> * </element> * <element name="Constraint" type="ows:DomainType" minOccurs="0" maxOccurs="unbounded"> * <annotation> * <documentation>Optional unordered list of valid domain constraints on * non-parameter quantities that each apply to this server. The list of * required and optional constraints shall be specified in the Implementation * Specification for this service. </documentation> * </annotation> * </element> * <element ref="ows:ExtendedCapabilities" minOccurs="0"/> * </sequence> *</complexType> * </pre> * </p> * */ void operationsMetadata() { start("ows:OperationsMetadata"); getCapabilities(); describeFeatureType(); getFeature(); getGmlObject(); if (wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.COMPLETE )) { lockFeature(); getFeatureWithLock(); } if (wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.TRANSACTIONAL) ) { transaction(); } end("ows:OperationsMetadata"); } /** * Encodes the GetCapabilities ows:Operation element. * */ void getCapabilities() { Map.Entry[] parameters = new Map.Entry[] { parameter("AcceptVersions", new String[] { "1.0.0", "1.1.0" }), parameter("AcceptFormats", new String[] { "text/xml" }) // parameter( // "Sections", // new String[]{ // "ServiceIdentification", "ServiceProvider", "OperationsMetadata", // "FeatureTypeList", "ServesGMLObjectTypeList", "SupportsGMLObjectTypeList", // "Filter_Capabilities" // } // ) }; operation("GetCapabilities", parameters, true, true); } /** * Encodes the DescribeFeatureType ows:Operation element. */ void describeFeatureType() { //TODO: process extension point Map.Entry[] parameters = new Map.Entry[] { parameter("outputFormat", new String[] { GML_3_1_1_FORMAT }) }; operation("DescribeFeatureType", parameters, true, true); } /** * Encodes the GetFeature ows:Operation element. */ void getFeature() { String[] oflist = getoutputFormatNames(); Map.Entry[] parameters = new Map.Entry[] { parameter("resultType", new String[] { "results", "hits" }), parameter("outputFormat", oflist) }; Map.Entry[] constraints = new Map.Entry[] { parameter("LocalTraverseXLinkScope", new String[]{ "2" } ) }; operation("GetFeature", parameters, constraints, true, true); } private String[] getoutputFormatNames() { return getAvailableOutputFormatNames(GML_3_1_1_FORMAT); } /** * Encodes the GetFeatureWithLock ows:Operation element. */ void getFeatureWithLock() { String[] oflist = getoutputFormatNames(); Map.Entry[] parameters = new Map.Entry[] { parameter("resultType", new String[] { "results", "hits" }), parameter("outputFormat", oflist) }; operation("GetFeatureWithLock", parameters, true, true); } /** * Encodes the LockFeature ows:Operation element. */ void lockFeature() { Map.Entry[] parameters = new Map.Entry[] { parameter("releaseAction", new String[] { "ALL", "SOME" }) }; operation("LockFeature", parameters, true, true); } /** * Encodes the Transaction ows:Operation element. */ void transaction() { Map.Entry[] parameters = new Map.Entry[] { parameter("inputFormat", new String[] { GML_3_1_1_FORMAT }), parameter("idgen", new String[] { "GenerateNew", "UseExisting", "ReplaceDuplicate" }), parameter("releaseAction", new String[] { "ALL", "SOME" }) }; operation("Transaction", parameters, true, true); } /** * Encodes the GetGmlObject ows:Operation element. * */ void getGmlObject() { Map.Entry[] parameters = new Map.Entry[] { }; operation("GetGmlObject", parameters, true, true); } /** * Encdoes the wfs:FeatureTypeList element. *<p> *<pre> * <xsd:complexType name="FeatureTypeListType"> * <xsd:annotation> * <xsd:documentation> * A list of feature types available from this server. * </xsd:documentation> * </xsd:annotation> * <xsd:sequence> * <xsd:element name="Operations" * type="wfs:OperationsType" * minOccurs="0"/> * <xsd:element name="FeatureType" * type="wfs:FeatureTypeType" * maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> *</pre> *</p> */ void featureTypeList() { start("FeatureTypeList"); start("Operations"); if ((wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.BASIC )) ) { element("Operation", "Query"); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.TRANSACTION_INSERT) ) ) { element("Operation", "Insert"); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.TRANSACTION_UPDATE) ) ) { element("Operation", "Update"); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.TRANSACTION_DELETE) ) ) { element("Operation", "Delete"); } if ((wfs.getServiceLevel().getOps().contains( WFSInfo.Operation.LOCKFEATURE) ) ) { element("Operation", "Lock"); } end("Operations"); featureTypes(); end("FeatureTypeList"); } void featureTypes() { featureTypes(true, "urn:x-ogc:def:crs:", request.getNamespace()); } void featureTypes(boolean crs, String srsPrefix, String namespace) { List featureTypes = new ArrayList(catalog.getFeatureTypes()); // filter out disabled feature types for (Iterator it = featureTypes.iterator(); it.hasNext();) { FeatureTypeInfo ft = (FeatureTypeInfo) it.next(); if(!ft.enabled()) it.remove(); } // filter the layers if a namespace filter has been set if(namespace != null) { for (Iterator it = featureTypes.iterator(); it.hasNext();) { FeatureTypeInfo ft = (FeatureTypeInfo) it.next(); if(!namespace.equals(ft.getNamespace().getPrefix())) it.remove(); } } Collections.sort(featureTypes, new FeatureTypeInfoTitleComparator()); for (Iterator i = featureTypes.iterator(); i.hasNext();) { FeatureTypeInfo featureType = (FeatureTypeInfo) i.next(); if(featureType.enabled()) featureType(featureType, crs, srsPrefix); } } /** * Encodes the wfs:FeatureType element. * <p> * <pre> * <xsd:complexType name="FeatureTypeType"> * <xsd:annotation> * <xsd:documentation> * An element of this type that describes a feature in an application * namespace shall have an xml xmlns specifier, e.g. * xmlns:bo="http://www.BlueOx.org/BlueOx" * </xsd:documentation> * </xsd:annotation> * <xsd:sequence> * <xsd:element name="Name" type="xsd:QName"> * <xsd:annotation> * <xsd:documentation> * Name of this feature type, including any namespace prefix * </xsd:documentation> * </xsd:annotation> * </xsd:element> * <xsd:element name="Title" type="xsd:string"> * <xsd:annotation> * <xsd:documentation> * Title of this feature type, normally used for display * to a human. * </xsd:documentation> * </xsd:annotation> * </xsd:element> * <xsd:element name="Abstract" type="xsd:string" minOccurs="0">* <xsd:annotation> * <xsd:documentation> * Brief narrative description of this feature type, normally* used for display to a human. * </xsd:documentation> * </xsd:annotation> * </xsd:element> * <xsd:element ref="ows:Keywords" minOccurs="0" maxOccurs="unbounded"/> * <xsd:choice> * <xsd:sequence> * <xsd:element name="DefaultSRS" * type="xsd:anyURI"> * <xsd:annotation> * <xsd:documentation> * The DefaultSRS element indicated which spatial * reference system shall be used by a WFS to * express the state of a spatial feature if not * otherwise explicitly identified within a query * or transaction request. The SRS may be indicated * using either the EPSG form (EPSG:posc code) or * the URL form defined in subclause 4.3.2 of * refernce[2]. * </xsd:documentation> * </xsd:annotation> * </xsd:element> * <xsd:element name="OtherSRS" * type="xsd:anyURI" * minOccurs="0" maxOccurs="unbounded"> * <xsd:annotation> * <xsd:documentation> * The OtherSRS element is used to indicate other * supported SRSs within query and transaction * operations. A supported SRS means that the * WFS supports the transformation of spatial * properties between the OtherSRS and the internal * storage SRS. The effects of such transformations * must be considered when determining and declaring * the guaranteed data accuracy. * </xsd:documentation> * </xsd:annotation> * </xsd:element> * </xsd:sequence> * <xsd:element name="NoSRS"> * <xsd:complexType/> * </xsd:element> * </xsd:choice> * <xsd:element name="Operations" * type="wfs:OperationsType" * minOccurs="0"/> * <xsd:element name="OutputFormats" * type="wfs:OutputFormatListType" * minOccurs="0"/> * <xsd:element ref="ows:WGS84BoundingBox" * minOccurs="1" maxOccurs="unbounded"/> * <xsd:element name="MetadataURL" * type="wfs:MetadataURLType" * minOccurs="0" maxOccurs="unbounded"/> * </xsd:sequence> * </xsd:complexType> * </pre> * </p> * @param featureType */ void featureType(FeatureTypeInfo featureType, boolean crs, String srsPrefix) { String prefix = featureType.getNamespace().getPrefix(); String uri = featureType.getNamespace().getURI(); start("FeatureType", attributes(new String[] { "xmlns:" + prefix, uri })); element("Name", featureType.getPrefixedName()); element("Title", featureType.getTitle()); element("Abstract", featureType.getAbstract()); keywords(featureType.getKeywords()); //default srs if (crs) { //wfs 2.0 element("DefaultCRS", srsPrefix + featureType.getSRS()); } else { element("DefaultSRS", srsPrefix + featureType.getSRS()); } //TODO: other srs's Envelope bbox = null; bbox = featureType.getLatLonBoundingBox(); start("ows:WGS84BoundingBox"); element("ows:LowerCorner", bbox.getMinX() + " " + bbox.getMinY()); element("ows:UpperCorner", bbox.getMaxX() + " " + bbox.getMaxY()); end("ows:WGS84BoundingBox"); end("FeatureType"); } /** * Encodes the wfs:SupportsGMLObjectTypeList element. * <p> * <pre> *<xsd:complexType name="GMLObjectTypeListType"> * <xsd:sequence> * <xsd:element name="GMLObjectType" type="wfs:GMLObjectTypeType" * maxOccurs="unbounded"> * <xsd:annotation> * <xsd:documentation> * Name of this GML object type, including any namespace prefix * </xsd:documentation> * </xsd:annotation> * </xsd:element> * </xsd:sequence> * </xsd:complexType> * </pre> * </p> */ void supportsGMLObjectTypeList() { element("SupportsGMLObjectTypeList", null); } /** * Encodes the ogc:Filter_Capabilities element. * <p> * <pre> * *<xsd:element name="Filter_Capabilities"> * <xsd:complexType> * <xsd:sequence> * <xsd:element name="Spatial_Capabilities" * type="ogc:Spatial_CapabilitiesType"/> * <xsd:element name="Scalar_Capabilities" * type="ogc:Scalar_CapabilitiesType"/> * <xsd:element name="Id_Capabilities" * type="ogc:Id_CapabilitiesType"/> * </xsd:sequence> * </xsd:complexType> * </xsd:element> * </pre> * </p> * */ void filterCapabilities() { start("ogc:Filter_Capabilities"); start("ogc:Spatial_Capabilities"); start("ogc:GeometryOperands"); element("ogc:GeometryOperand", "gml:Envelope"); element("ogc:GeometryOperand", "gml:Point"); element("ogc:GeometryOperand", "gml:LineString"); element("ogc:GeometryOperand", "gml:Polygon"); end("ogc:GeometryOperands"); start("ogc:SpatialOperators"); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Disjoint" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Equals" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "DWithin" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Beyond" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Intersects" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Touches" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Crosses" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Contains" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "Overlaps" })); element("ogc:SpatialOperator", null, attributes(new String[] { "name", "BBOX" })); end("ogc:SpatialOperators"); end("ogc:Spatial_Capabilities"); start("ogc:Scalar_Capabilities"); element("ogc:LogicalOperators", null); start("ogc:ComparisonOperators"); element("ogc:ComparisonOperator", "LessThan"); element("ogc:ComparisonOperator", "GreaterThan"); element("ogc:ComparisonOperator", "LessThanEqualTo"); element("ogc:ComparisonOperator", "GreaterThanEqualTo"); element("ogc:ComparisonOperator", "EqualTo"); element("ogc:ComparisonOperator", "NotEqualTo"); element("ogc:ComparisonOperator", "Like"); element("ogc:ComparisonOperator", "Between"); element("ogc:ComparisonOperator", "NullCheck"); end("ogc:ComparisonOperators"); start("ogc:ArithmeticOperators"); element("ogc:SimpleArithmetic", null); functions(); end("ogc:ArithmeticOperators"); end("ogc:Scalar_Capabilities"); start("ogc:Id_Capabilities"); element("ogc:FID", null); element("ogc:EID", null); end("ogc:Id_Capabilities"); end("ogc:Filter_Capabilities"); } void functions() { start("ogc:Functions"); Set<FunctionName> functions = getAvailableFunctionNames(); if (!functions.isEmpty()) { start("ogc:FunctionNames"); for (FunctionName fe : functions) { element("ogc:FunctionName", fe.getName(), attributes(new String[] { "nArgs", "" + fe.getArgumentCount() })); } end("ogc:FunctionNames"); } end("ogc:Functions"); } /** * Encodes the ows:Keywords element. * <p> * <pre> * <complexType name="KeywordsType"> * <annotation> * <documentation>Unordered list of one or more commonly used or formalised word(s) or phrase(s) used to describe the subject. When needed, the optional "type" can name the type of the associated list of keywords that shall all have the same type. Also when needed, the codeSpace attribute of that "type" can reference the type name authority and/or thesaurus. </documentation> * <documentation>For OWS use, the optional thesaurusName element was omitted as being complex information that could be referenced by the codeSpace attribute of the Type element. </documentation> * </annotation> * <sequence> * <element name="Keyword" type="string" maxOccurs="unbounded"/> * <element name="Type" type="ows:CodeType" minOccurs="0"/> * </sequence> * </complexType> * </pre> * </p> * @param keywords */ void keywords(KeywordInfo[] keywords) { if ((keywords == null) || (keywords.length == 0)) { return; } start("ows:Keywords"); for (int i = 0; i < keywords.length; i++) { element("ows:Keyword", keywords[i].getValue()); } end("ows:Keywords"); } void keywords(List keywords) { if(keywords != null){ keywords((KeywordInfo[]) keywords.toArray(new KeywordInfo[keywords.size()])); } } /** * @see {@link #operation(String, java.util.Map.Entry[], java.util.Map.Entry[], boolean, boolean)} */ void operation(String name, Map.Entry[] parameters, boolean get, boolean post) { operation(name,parameters,null,get,post); } /** * Encodes the ows:Operation element. * <p> * <pre> * <complexType> * <sequence> * <element ref="ows:DCP" maxOccurs="unbounded"> * <annotation> * <documentation>Unordered list of Distributed Computing Platforms (DCPs) supported for this operation. At present, only the HTTP DCP is defined, so this element will appear only once. </documentation> * </annotation> * </element> * <element name="Parameter" type="ows:DomainType" minOccurs="0" maxOccurs="unbounded"> * <annotation> * <documentation>Optional unordered list of parameter domains that each apply to this operation which this server implements. If one of these Parameter elements has the same "name" attribute as a Parameter element in the OperationsMetadata element, this Parameter element shall override the other one for this operation. The list of required and optional parameter domain limitations for this operation shall be specified in the Implementation Specification for this service. </documentation> * </annotation> * </element> * <element name="Constraint" type="ows:DomainType" minOccurs="0" maxOccurs="unbounded"> * <annotation> * <documentation>Optional unordered list of valid domain constraints on non-parameter quantities that each apply to this operation. If one of these Constraint elements has the same "name" attribute as a Constraint element in the OperationsMetadata element, this Constraint element shall override the other one for this operation. The list of required and optional constraints for this operation shall be specified in the Implementation Specification for this service. </documentation> * </annotation> * </element> * <element ref="ows:Metadata" minOccurs="0" maxOccurs="unbounded"> * <annotation> * <documentation>Optional unordered list of additional metadata about this operation and its' implementation. A list of required and optional metadata elements for this operation should be specified in the Implementation Specification for this service. (Informative: This metadata might specify the operation request parameters or provide the XML Schemas for the operation request.) </documentation> * </annotation> * </element> * </sequence> * <attribute name="name" type="string" use="required"> * <annotation> * <documentation>Name or identifier of this operation (request) (for example, GetCapabilities). The list of required and optional operations implemented shall be specified in the Implementation Specification for this service. </documentation> * </annotation> * </attribute> * </complexType> * </pre> * </p> * * @param name * @param parameters * @param get * @param post */ void operation(String name, Map.Entry[] parameters, Map.Entry[] constraints, boolean get, boolean post) { start("ows:Operation", attributes(new String[] { "name", name })); String serviceURL = buildURL(request.getBaseUrl(), "wfs", null, URLType.SERVICE); //dcp dcp(serviceURL, get, post); //parameters for (int i = 0; i < parameters.length; i++) { String pname = (String) parameters[i].getKey(); String[] pvalues = (String[]) parameters[i].getValue(); start("ows:Parameter", attributes(new String[] { "name", pname })); for (int j = 0; j < pvalues.length; j++) { element("ows:Value", pvalues[j]); } end("ows:Parameter"); } //constraints for ( int i = 0; constraints != null && i < constraints.length; i++ ) { String cname = (String) constraints[i].getKey(); String[] cvalues = (String[]) constraints[i].getValue(); start( "ows:Constraint", attributes(new String[] { "name", cname })); for (int j = 0; j < cvalues.length; j++) { element("ows:Value", cvalues[j]); } end( "ows:Constraint" ); } end("ows:Operation"); } void dcp(String serviceURL, boolean get, boolean post) { start("ows:DCP"); start("ows:HTTP"); if (get) { element("ows:Get", null, attributes(new String[] { "xlink:href", serviceURL})); } if (post) { element("ows:Post", null, attributes(new String[] { "xlink:href", serviceURL})); } end("ows:HTTP"); end("ows:DCP"); } } } /** * Transformer for wfs 2.0 capabilities document. */ public static class WFS2_0 extends CapabilitiesTransformer { /** wfs namespace uri */ static String WFS20_URI = "http://www.opengis.net/wfs/2.0"; /** gml 3.2 mime type */ static final String GML32_FORMAT = "text/xml; subtype=gml/3.2"; /** filter namespace + prefix */ protected static final String FES_PREFIX = "fes"; protected static final String FES_URI = FES.NAMESPACE; public WFS2_0(WFSInfo wfs, Catalog catalog) { super(wfs, catalog); } @Override public Translator createTranslator(ContentHandler handler) { return new CapabilitiesTranslator2_0(handler); } class CapabilitiesTranslator2_0 extends TranslatorSupport { GetCapabilitiesRequest request; WFS1_1.CapabilitiesTranslator1_1 delegate; public CapabilitiesTranslator2_0(ContentHandler handler) { super(handler, null, null); //register schema mappings for function return + argument types getNamespaceSupport().declarePrefix("xs", XS.NAMESPACE); getNamespaceSupport().declarePrefix("gml", org.geotools.gml3.v3_2.GML.NAMESPACE); //wfs 1.1 already does a lot of the capabilities work, use that transformer // as a delegate delegate = (CapabilitiesTranslator1_1) new WFS1_1(wfs, catalog).createTranslator(handler); } public void encode(Object o) throws IllegalArgumentException { request = GetCapabilitiesRequest.adapt(o); AttributesImpl attributes = attributes(new String[] { "version", "2.0.0", "xmlns:xsi", XSI_URI, "xmlns", WFS20_URI, "xmlns:wfs", WFS20_URI, "xmlns:ows", org.geotools.ows.v1_1.OWS.NAMESPACE, "xmlns:gml", org.geotools.gml3.v3_2.GML.NAMESPACE, "xmlns:fes", FES_URI, "xmlns:xlink", XLINK.NAMESPACE, "xmlns:xs", XS.NAMESPACE, "xsi:schemaLocation", WFS20_URI + " " + (wfs.isCanonicalSchemaLocation()? org.geoserver.wfs.xml.v1_1_0.WFS.CANONICAL_SCHEMA_LOCATION: (buildSchemaURL(request.getBaseUrl(), "wfs/2.0/wfs.xsd"))) }); registerNamespaces(attributes); updateSequence(attributes); start("wfs:WFS_Capabilities", attributes); delegate.serviceIdentification("2.0.0"); delegate.serviceProvider(wfs.getGeoServer()); operationsMetadata(); featureTypeList(); filterCapabilities(); end("wfs:WFS_Capabilities"); } void operationsMetadata() { start("ows:OperationsMetadata"); getCapabilities(); describeFeatureType(); getFeature(); getPropertyValue(); //TODO: make whether to support stored queries optional like transations listStoredQueries(); describeStoredQueries(); createStoredQuery(); dropStoredQuery(); // getGmlObject(); // if (wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.COMPLETE )) { lockFeature(); getFeatureWithLock(); } if (wfs.getServiceLevel().contains( WFSInfo.ServiceLevel.TRANSACTIONAL) ) { transaction(); } constraints(); end("ows:OperationsMetadata"); } void operation(String name, Map.Entry[] parameters, Map.Entry[] constraints, boolean get, boolean post) { start("ows:Operation", attributes(new String[] { "name", name })); String serviceURL = buildURL(request.getBaseUrl(), "wfs", null, URLType.SERVICE); //dcp delegate.dcp(serviceURL, get, post); //parameters for (int i = 0; parameters != null && i < parameters.length; i++) { String pname = (String) parameters[i].getKey(); String[] pvalues = (String[]) parameters[i].getValue(); start("ows:Parameter", attributes(new String[] { "name", pname })); start("ows:AllowedValues"); for (int j = 0; j < pvalues.length; j++) { element("ows:Value", pvalues[j]); } end("ows:AllowedValues"); end("ows:Parameter"); } //constraints for (int i = 0; constraints != null && i < constraints.length; i++) { String cname = (String) constraints[i].getKey(); constraint(cname, constraints[i].getValue()); } end("ows:Operation"); } void operation(String name, Map.Entry[] parameters, boolean get, boolean post) { operation(name, parameters, null, get, post); } /** * Encodes the GetCapabilities ows:Operation element. */ void getCapabilities() { Map.Entry[] parameters = new Map.Entry[] { parameter("AcceptVersions", new String[] { "1.0.0", "1.1.0", "2.0.0" }), parameter("AcceptFormats", new String[] { "text/xml" }) }; operation("GetCapabilities", parameters, true, true); } /** * Encodes the DescribeFeatureType ows:Operation element. */ void describeFeatureType() { Map.Entry[] parameters = new Map.Entry[] { parameter("outputFormat", new String[] { GML32_FORMAT }) }; operation("DescribeFeatureType", parameters, true, true); } /** * Encodes the GetFeature ows:Operation element. */ void getFeature() { String[] oflist = getAvailableOutputFormatNames(GML32_FORMAT); Map.Entry[] parameters = new Map.Entry[] { parameter("resultType", new String[] { "results", "hits" }), parameter("outputFormat", oflist) }; operation("GetFeature", parameters, getFeatureConstraints(), true, true); } Map.Entry[] getFeatureConstraints() { return new Map.Entry[] { parameter("PagingIsTransactionSafe", false), parameter("CountDefault", wfs.getMaxFeatures()) }; } /** * Encodes the GetFeatureWithLock ows:Operation element. */ void getFeatureWithLock() { String[] oflist = getAvailableOutputFormatNames(GML32_FORMAT); Map.Entry[] parameters = new Map.Entry[] { parameter("resultType", new String[] { "results", "hits" }), parameter("outputFormat", oflist) }; operation("GetFeatureWithLock", parameters, getFeatureConstraints(), true, true); } void getPropertyValue() { Map.Entry[] parameters = new Map.Entry[] { parameter("resolve", new String[] { "none" }), }; operation("GetPropertyValue", parameters, true, true); } /** * Encodes the LockFeature ows:Operation element. */ void lockFeature() { Map.Entry[] parameters = new Map.Entry[] { parameter("releaseAction", new String[] { "ALL", "SOME" }) }; operation("LockFeature", parameters, true, true); } /** * Encodes the Transaction ows:Operation element. */ void transaction() { Map.Entry[] parameters = new Map.Entry[] { parameter("inputFormat", new String[] { GML32_FORMAT }), parameter("releaseAction", new String[] { "ALL", "SOME" }) }; operation("Transaction", parameters, true, true); } /** * Encodes the ListStoredQueries ows:Operation element. */ void listStoredQueries() { operation("ListStoredQueries", null, true, true); } /** * Encodes the ListStoredQueries ows:Operation element. */ void describeStoredQueries() { operation("DescribeStoredQueries", null, true, true); } /** * Encodes the CreateStoredQuery ows:Operation element. */ void createStoredQuery() { operation("CreateStoredQuery", null, false, true); } /** * Encodes the DropStoredQuery ows:Operation element. */ void dropStoredQuery() { operation("DropStoredQuery", null, true, true); } /** * Encodes service constraints. */ void constraints() { constraint("ImplementsBasicWFS", true); constraint("ImplementsTransactionalWFS", true); constraint("ImplementsLockingWFS", true); constraint("KVPEncoding", true); constraint("XMLEncoding", true); constraint("SOAPEncoding", true); constraint("ImplementsInheritance", false); constraint("ImplementsRemoteResolve", false); constraint("ImplementsResultPaging", true); constraint("ImplementsStandardJoins", true); constraint("ImplementsSpatialJoins", true); constraint("ImplementsTemporalJoins", true); constraint("ImplementsFeatureVersioning", false); constraint("ManageStoredQueries", true); //capacity constraints constraint("PagingIsTransactionSafe", false); constraint("QueryExpressions", new String[]{"wfs:Query", "wfs:StoredQuery"}); } void constraint(String name, Object value) { if (value instanceof Boolean) { constraint(name, ((Boolean)value).booleanValue()); } else { constraint(name, (String) value.toString()); } } void constraint(String name, boolean value) { constraint(name, String.valueOf(value).toUpperCase()); } void constraint(String name, String value) { start("ows:Constraint", attributes(new String[]{"name", name})); element("ows:NoValues", null); element("ows:DefaultValue", value); end("ows:Constraint"); } void constraint(String name, String[] values) { start("ows:Constraint", attributes(new String[]{"name", name})); start("ows:AllowedValues"); for (String v : values) { element("ows:Value", v); } end("ows:AllowedValues"); end("ows:Constraint"); } void featureTypeList() { if (catalog.getFeatureTypes().isEmpty()) { return; } start("FeatureTypeList"); //TODO: namespace filtering delegate.featureTypes(true, "urn:ogc:def:crs:", request.getNamespace()); end("FeatureTypeList"); } void filterCapabilities() { start("fes:Filter_Capabilities"); start("fes:Conformance"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsQuery"})); element("ows:NoValues", null); element("ows:DefaultValue", "TRUE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name","ImplementsAdHocQuery"})); element("ows:NoValues", null); element("ows:DefaultValue", "TRUE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsFunctions"})); element("ows:NoValues", null); element("ows:DefaultValue", "TRUE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsMinStandardFilter"})); element("ows:NoValues", null); element("ows:DefaultValue", "TRUE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsStandardFilter"})); element("ows:NoValues", null); element("ows:DefaultValue", "FALSE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsMinSpatialFilter"})); element("ows:NoValues", null); element("ows:DefaultValue", "TRUE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsSpatialFilter"})); element("ows:NoValues", null); element("ows:DefaultValue", "FALSE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsMinTemporalFilter"})); element("ows:NoValues", null); element("ows:DefaultValue", "TRUE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsTemporalFilter"})); element("ows:NoValues", null); element("ows:DefaultValue", "TRUE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsVersionNav"})); element("ows:NoValues", null); element("ows:DefaultValue", "FALSE"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsSorting"})); start("ows:AllowedValues"); element("ows:Value", "ASC"); element("ows:Value", "DESC"); end("ows:AllowedValues"); element("ows:DefaultValue", "ASC"); end("fes:Constraint"); start("fes:Constraint", attributes(new String[]{"name", "ImplementsExtendedOperators"})); element("ows:NoValues", null); element("ows:DefaultValue", "FALSE"); end("fes:Constraint"); end("fes:Conformance"); start("fes:Id_Capabilities"); element("fes:ResourceIdentifier", null, attributes(new String[]{"name", "fes:ResourceId"})); end("fes:Id_Capabilities"); start("fes:Scalar_Capabilities"); element("fes:LogicalOperators", null); start("fes:ComparisonOperators"); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsLessThan"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsGreaterThan"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsLessThanOrEqualTo"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsGreaterThanOrEqualTo"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsEqualTo"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsNotEqualTo"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsLike"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsBetween"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsNull"})); element("fes:ComparisonOperator", null, attributes(new String[]{"name", "PropertyIsNil"})); end("fes:ComparisonOperators"); end("fes:Scalar_Capabilities"); start("fes:Spatial_Capabilities"); start("fes:GeometryOperands"); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:Envelope"})); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:Point"})); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:MultiPoint"})); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:LineString"})); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:MultiLineString"})); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:Polygon"})); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:MultiPolygon"})); element("fes:GeometryOperand", null, attributes(new String[]{"name", "gml:MultiGeometry"})); end("fes:GeometryOperands"); start("fes:SpatialOperators"); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Disjoint" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Equals" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "DWithin" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Beyond" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Intersects" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Touches" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Crosses" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Contains" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "Overlaps" })); element("fes:SpatialOperator", null, attributes(new String[] { "name", "BBOX" })); end("fes:SpatialOperators"); end("fes:Spatial_Capabilities"); start("fes:Temporal_Capabilities"); start("fes:TemporalOperands"); element("fes:TemporalOperand", null, attributes(new String[] { "name", "gml:TimeInstant" })); element("fes:TemporalOperand", null, attributes(new String[] { "name", "gml:TimePeriod" })); //element("fes:TemporalOperand", null, attributes(new String[] { "name", "gml:validTime" })); //element("fes:TemporalOperand", null, attributes(new String[] { "name", "gml:timePosition" })); //element("fes:TemporalOperand", null, attributes(new String[] { "name", "gml:timeInterval" })); //element("fes:TemporalOperand", null, attributes(new String[] { "name", "gml:duration" })); end("fes:TemporalOperands"); start("fes:TemporalOperators"); element("fes:TemporalOperator", null, attributes(new String[] { "name", "After" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "Before" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "Begins" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "BegunBy" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "TContains" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "During" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "TEquals" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "TOverlaps" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "Meets" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "OverlappedBy" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "MetBy" })); element("fes:TemporalOperator", null, attributes(new String[] { "name", "EndedBy" })); end("fes:TemporalOperators"); end("fes:Temporal_Capabilities"); List<Schema> typeMappingProfiles = org.geotools.gml3.v3_2.GML.getInstance().getAllTypeMappingProfiles(); start("fes:Functions"); for (FunctionName fn : getAvailableFunctionNames()) { start("fes:Function", attributes(new String[] { "name", fn.getName() })); //figure out return type Name returnType = lookupTypeName(typeMappingProfiles, fn.getReturn()); String prefix = getNamespaceSupport().getPrefix(returnType.getNamespaceURI()); if (prefix != null) { element("fes:Returns", prefix + ":" + returnType.getLocalPart()); } else { LOGGER.warning(String.format("Unable to map function return type to QName for " + "function %s. No namespace mapping for %s.", fn.getName(), returnType.getNamespaceURI() )); } if (!fn.getArgumentNames().isEmpty()) { start("fes:Arguments"); for(Parameter<?> arg : fn.getArguments()) { start("fes:Argument", attributes(new String[]{"name", arg.getName()})); Name argType = lookupTypeName(typeMappingProfiles, arg); prefix = getNamespaceSupport().getPrefix(argType.getNamespaceURI()); if (prefix != null) { element("fes:Type", prefix + ":" + argType.getLocalPart()); } else { LOGGER.warning(String.format("Unable to map function argument type to QName for " + "function %s. No namespace mapping for %s.", arg.getName(), argType.getNamespaceURI() )); } end("fes:Argument"); } end("fes:Arguments"); } end("fes:Function"); } end("fes:Functions"); //extended operators //TODO: eventually use all extended operator vactories... but for now just use // the WFS one because it gives some custom api for namespace stuff... // WFSExtendedOperatorFactory wfsExtOpsFactory = new WFSExtendedOperatorFactory(); // if (!wfsExtOpsFactory.getFunctionNames().isEmpty()) { // start("fes:ExtendedCapabilities"); // // //declare the necessary namespaces // NamespaceSupport extOpNamespaces = wfsExtOpsFactory.getNamespaces(); // Enumeration prefixes = extOpNamespaces.getDeclaredPrefixes(); // // List<String> xmlns = new ArrayList(); // while(prefixes.hasMoreElements()) { // String prefix = (String) prefixes.nextElement(); // if ("".equals(prefix) || "xml".equals(prefix)) { // continue; // } // xmlns.add("xmlns:" + prefix); // xmlns.add(extOpNamespaces.getURI(prefix)); // } // // start("fes:AdditionalOperators", attributes(xmlns.toArray(new String[xmlns.size()]))); // for (Name extOp : wfsExtOpsFactory.getOperatorNames()) { // String prefix = extOpNamespaces.getPrefix(extOp.getNamespaceURI()); // String qName = prefix != null ? prefix + ":" + extOp.getLocalPart() : // extOp.getLocalPart(); // // element("fes:Operator", null, attributes(new String[]{"name", qName})); // } // end("fes:AdditionalOperators"); // end("fes:ExtendedCapabilities"); // } end("fes:Filter_Capabilities"); } } Name lookupTypeName(List<Schema> profiles, Parameter arg) { //hack, look up for geometry mae if ("geometry".equals(arg.getName())) { return new NameImpl(org.geotools.gml3.v3_2.GML.AbstractGeometryType); } //default Class clazz = arg.getType(); if (clazz == null || clazz == Object.class) { return new NameImpl(XS.STRING); } //TODO: this is stolen from FeaturTypeSchemaBuilder, factor out into utility class for (Schema profile : profiles) { for (Map.Entry<Name,AttributeType> e : profile.entrySet()) { AttributeType at = e.getValue(); if (at.getBinding() != null && at.getBinding().equals(clazz)) { return at.getName(); } } for (AttributeType at : profile.values()) { if (clazz.isAssignableFrom(at.getBinding())) { return at.getName(); } } } return new NameImpl(XS.STRING); } } }