/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available 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.net.MalformedURLException; import java.net.URL; 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.HashSet; 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.Level; import java.util.logging.Logger; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.KeywordInfo; import org.geoserver.catalog.MetadataLinkInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.config.ContactInfo; import org.geoserver.config.GeoServer; import org.geoserver.config.ResourceErrorHandling; 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.vfny.geoserver.util.ResponseUtils; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.helpers.AttributesImpl; 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 */ protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(CapabilitiesTransformer.class.getPackage() .getName()); /** identifer of a http get + post request */ protected static final String HTTP_GET = "Get"; protected 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; /** wfs version */ protected WFSInfo.Version version; /** catalog */ protected Catalog catalog; /** * Creates a new CapabilitiesTransformer object. */ public CapabilitiesTransformer(WFSInfo wfs, WFSInfo.Version version, Catalog catalog) { super(); setNamespaceDeclarationEnabled(false); this.wfs = wfs; this.version = version; 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"); } } protected 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; } protected 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()]); } protected void updateSequence(AttributesImpl attributes) { attributes.addAttribute("", "updateSequence", "updateSequence", "", wfs.getGeoServer().getGlobal().getUpdateSequence() + ""); } protected 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); } } protected 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; } protected 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 { protected final boolean skipMisconfigured; public WFS1_0(WFSInfo wfs, Catalog catalog) { super(wfs, WFSInfo.Version.V_10, catalog); this.skipMisconfigured = ResourceErrorHandling.SKIP_MISCONFIGURED_LAYERS.equals( wfs.getGeoServer().getGlobal().getResourceErrorHandling()); } public Translator createTranslator(ContentHandler handler) { return new CapabilitiesTranslator1_0(handler); } protected 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> * */ protected 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> * */ protected 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> * */ protected 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> */ protected 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> */ protected 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> */ protected 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> */ protected 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(); for (String name : format.getCapabilitiesElementNames()) { if (!dupes.containsKey(name)) { element(name, null); dupes.put(name, 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> */ protected 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> */ protected 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> */ protected 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 */ protected 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> */ protected 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(); try { mark(); handleFeatureType(ftype); commit(); } catch (RuntimeException e) { if (skipMisconfigured) { reset(); LOGGER.log(Level.WARNING, "Couldn't encode WFS Capabilities entry for FeatureType: " + ftype.getPrefixedName(), e); } else { throw e; } } } 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. */ protected 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> */ protected 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> * */ protected 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 { static final Set<String> VALID_LINKS_METADATATYPES = new HashSet<String>(Arrays.asList("TC211", "FGDC", "19115", "13139")); static final Set<String> VALID_LINKS_FORMATS = new HashSet<String>(Arrays.asList("text/xml", "text/html", "text/sgml", "text/plain")); protected final boolean skipMisconfigured; protected final Collection<WFSExtendedCapabilitiesProvider> extCapsProviders; protected final String baseUrl; public WFS1_1(WFSInfo wfs, String baseUrl, Catalog catalog, Collection<WFSExtendedCapabilitiesProvider> extCapsProviders) { this(wfs, WFSInfo.Version.V_11, baseUrl, catalog, extCapsProviders); } public WFS1_1(WFSInfo wfs, WFSInfo.Version version, String baseUrl, Catalog catalog, Collection<WFSExtendedCapabilitiesProvider> extCapsProviders) { super(wfs, version, catalog); skipMisconfigured = ResourceErrorHandling.SKIP_MISCONFIGURED_LAYERS.equals( wfs.getGeoServer().getGlobal().getResourceErrorHandling()); this.extCapsProviders = extCapsProviders; this.baseUrl = baseUrl; } public Translator createTranslator(ContentHandler handler) { return new CapabilitiesTranslator1_1(handler, baseUrl, wfs, extCapsProviders); } protected class CapabilitiesTranslator1_1 extends TranslatorSupport { protected static final String GML_3_1_1_FORMAT = "text/xml; subtype=gml/3.1.1"; GetCapabilitiesRequest request; protected Collection<WFSExtendedCapabilitiesProvider> extCapsProviders; protected final WFSInfo wfs; protected final String schemaBaseURL; public CapabilitiesTranslator1_1(ContentHandler handler, String baseUrl, WFSInfo wfs, Collection<WFSExtendedCapabilitiesProvider> extCapsProviders) { super(handler, null, null); this.wfs = wfs; this.extCapsProviders = extCapsProviders; this.schemaBaseURL = baseUrl; // register namespaces provided by extended capabilities for (WFSExtendedCapabilitiesProvider cp : extCapsProviders) { cp.registerNamespaces(getNamespaceSupport()); } } public void encode(Object object) throws IllegalArgumentException { request = GetCapabilitiesRequest.adapt(object); verifyUpdateSequence(request); StringBuilder schemaLocation = new StringBuilder(); schemaLocation.append(org.geoserver.wfs.xml.v1_1_0.WFS.NAMESPACE); schemaLocation.append(" "); if (wfs.isCanonicalSchemaLocation()) { schemaLocation .append(org.geoserver.wfs.xml.v1_1_0.WFS.CANONICAL_SCHEMA_LOCATION); } else { schemaLocation .append(buildSchemaURL(request.getBaseUrl(), "wfs/1.1.0/wfs.xsd")); } addExtensionSchemaLocation(schemaLocation); 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", schemaLocation.toString() }); @SuppressWarnings("rawtypes") Enumeration prefixes = getNamespaceSupport().getPrefixes(); while (prefixes.hasMoreElements()) { String prefix = (String) prefixes.nextElement(); if ("xml".equals(prefix)) continue; attributes.addAttribute(null, null, "xmlns:" + prefix, null, getNamespaceSupport().getURI(prefix)); } registerNamespaces(attributes); updateSequence(attributes); start("wfs:WFS_Capabilities", attributes); serviceIdentification(); serviceProvider(wfs.getGeoServer()); operationsMetadata(); featureTypeList(); //supportsGMLObjectTypeList(); filterCapabilities(); end("wfs:WFS_Capabilities"); } protected String addExtensionSchemaLocation(StringBuilder schemaLocation) { for (WFSExtendedCapabilitiesProvider cp : extCapsProviders) { String[] locations = cp.getSchemaLocations(schemaBaseURL); try { for (int i = 0; i < locations.length - 1; i += 2) { schemaLocation.append(" "); schemaLocation.append(schemaLocation(locations[i], locations[i + 1])); } } catch (ArrayIndexOutOfBoundsException e) { throw new ServiceException( "Extended capabilities provider returned improper " + "set of namespace,location pairs from getSchemaLocations()", e); } } return schemaLocation.toString(); } protected String schemaLocation(String namespace, String uri) { String location = null; try { new URL(uri); // external location location = uri; } catch (MalformedURLException e) { // means the url is relative location = buildSchemaURL(schemaBaseURL, uri); } return namespace + " " + location; } /** * 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> * */ protected void serviceIdentification() { serviceIdentification("1.1.0"); } protected 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> * */ protected void serviceProvider(GeoServer gs) { ContactInfo contact = gs.getSettings().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:DeliveryPoint", contact.getAddressDeliveryPoint() ); element( "ows:City", contact.getAddressCity() ); element( "ows:AdministrativeArea", contact.getAddressState() ); element( "ows:PostalCode", contact.getAddressPostalCode() ); element( "ows:Country", contact.getAddressCountry() ); element( "ows:ElectronicMailAddress", contact.getContactEmail() ); /* Currently disregarding the maxOccurs for DeliveryPoint and ElectronicMailAddress, * because this can create issues with the XML serializer. */ 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> * */ protected 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(); } extendedCapabilities(); end("ows:OperationsMetadata"); } /** * Encodes the GetCapabilities ows:Operation element. * */ protected 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. */ protected 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. */ protected 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); } protected String[] getoutputFormatNames() { return getAvailableOutputFormatNames(GML_3_1_1_FORMAT); } /** * Encodes the GetFeatureWithLock ows:Operation element. */ protected 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. */ protected 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. */ protected 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. * */ protected void getGmlObject() { Map.Entry[] parameters = new Map.Entry[] { }; operation("GetGmlObject", parameters, true, true); } protected void extendedCapabilities() { for (WFSExtendedCapabilitiesProvider cp : this.extCapsProviders) { try { cp.encode(new WFSExtendedCapabilitiesProvider.Translator() { public void start(String element) { CapabilitiesTranslator1_1.this.start(element); } public void start(String element, Attributes attributes) { CapabilitiesTranslator1_1.this.start(element, attributes); } public void chars(String text) { CapabilitiesTranslator1_1.this.chars(text); } public void end(String element) { CapabilitiesTranslator1_1.this.end(element); } }, wfs, request); } catch (Exception e) { throw new ServiceException("Extended capabilities provider threw error", e); } } } /** * 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> */ protected 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"); } protected void featureTypes() { //featureTypes(false, "urn:x-ogc:def:crs:", request.getNamespace()); featureTypes(false, request.getNamespace()); } protected void featureTypes(boolean crs, 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()) { try { mark(); featureType(featureType, crs); commit(); } catch (RuntimeException ex) { if (skipMisconfigured) { reset(); LOGGER.log(Level.WARNING, "Couldn't encode WFS capabilities entry for featuretype: " + featureType.getPrefixedName(), ex); } else { throw ex; } } } } } /** * 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 */ protected void featureType(FeatureTypeInfo featureType, boolean crs) { GMLInfo gml = wfs.getGML().get(version); 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()); String srs = featureType.getSRS(); srs = applySRSNameStyle(gml, srs); // default srs if (crs) { //wfs 2.0 element("DefaultCRS", srs); } else { element("DefaultSRS", srs); } // other srs List<String> otherSRSes = getOtherSRS(featureType); for (String otherSRS : otherSRSes) { if(otherSRS != null) { otherSRS = applySRSNameStyle(gml, otherSRS); if(!otherSRS.equals(srs)) { if (crs) { element("OtherCRS", otherSRS); } else { element("OtherSRS", otherSRS); } } } } 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"); List<MetadataLinkInfo> mlinks = featureType.getMetadataLinks(); if(mlinks != null && !mlinks.isEmpty()) { for (MetadataLinkInfo link : mlinks) { metadataLink(link); } } end("FeatureType"); } private String applySRSNameStyle(GMLInfo gml, String srs) { if (srs != null) { String prefix = gml.getSrsNameStyle().getPrefix(); if(srs.matches("(?ui)EPSG:[0-9]+")) { srs = prefix + srs.substring(5); } else { srs = prefix + srs; } } return srs; } protected List<String> getOtherSRS(FeatureTypeInfo featureType) { List<String> extraSRS; if(featureType.isOverridingServiceSRS()) { extraSRS = featureType.getResponseSRS(); } else { extraSRS = wfs.getSRS(); } return extraSRS; } protected void metadataLink(MetadataLinkInfo link) { // extract format and metadata type, make sure they abide the WFS 1.1 // restrictions String format = link.getType(); String metadataType = link.getMetadataType(); if ("ISO19115:2003".equals(metadataType)) { metadataType = "19115"; } if (!VALID_LINKS_FORMATS.contains(format)) { LOGGER.log(Level.FINE, "Skipping metadata link " + link.getContent() + ", format " + format + " is not valid in WFS 1.1, supported types are: " + VALID_LINKS_FORMATS); return; } if (!VALID_LINKS_METADATATYPES.contains(metadataType)) { LOGGER.log(Level.FINE, "Skipping metadata link " + link.getContent() + ", metadata type " + metadataType + " is not valid in WFS 1.1, supported types are: " + VALID_LINKS_METADATATYPES); return; } if ((link.getContent() == null) || link.getContent().isEmpty()) { return; } AttributesImpl mtAtts = attributes("type", metadataType, "format", format); element("MetadataURL", ResponseUtils.proxifyMetadataLink(link, request.getBaseUrl()), mtAtts); } /** * 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> */ protected 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> * */ protected 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", "Within" })); 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"); } protected 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 */ protected 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"); } protected 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)} */ protected 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 */ protected 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"); } protected 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 */ protected 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; protected final Collection<WFSExtendedCapabilitiesProvider> extCapsProviders; protected final String baseUrl; public WFS2_0(WFSInfo wfs, String baseUrl, Catalog catalog, Collection<WFSExtendedCapabilitiesProvider> extCapsProviders) { super(wfs, WFSInfo.Version.V_20, catalog); this.extCapsProviders = extCapsProviders; this.baseUrl = baseUrl; } @Override public Translator createTranslator(ContentHandler handler) { return new CapabilitiesTranslator2_0(handler, baseUrl, wfs, this.extCapsProviders); } protected class CapabilitiesTranslator2_0 extends TranslatorSupport { protected GetCapabilitiesRequest request; protected WFS1_1.CapabilitiesTranslator1_1 delegate; public CapabilitiesTranslator2_0(ContentHandler handler, String baseUrl, WFSInfo wfs, Collection<WFSExtendedCapabilitiesProvider> extCapsProviders) { 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); // register namespaces provided by extended capabilities for (WFSExtendedCapabilitiesProvider cp : extCapsProviders) { cp.registerNamespaces(getNamespaceSupport()); } //wfs 1.1 already does a lot of the capabilities work, use that transformer // as a delegate WFS1_1 wfs1_1 = new WFS1_1(wfs, version, baseUrl, catalog, extCapsProviders) { @Override public Translator createTranslator(ContentHandler handler) { return new CapabilitiesTranslator1_1_v2MetadataLinks(handler, baseUrl, wfs, extCapsProviders); } class CapabilitiesTranslator1_1_v2MetadataLinks extends CapabilitiesTranslator1_1 { public CapabilitiesTranslator1_1_v2MetadataLinks(ContentHandler handler, String baseUrl, WFSInfo wfs, Collection<WFSExtendedCapabilitiesProvider> extCapsProviders) { super(handler, baseUrl, wfs, extCapsProviders); } @Override protected void metadataLink(MetadataLinkInfo link) { // WFS 2.0 metadata url is different than the v1.1 one, indeed it just // has an href if ((link.getContent() == null) || link.getContent().isEmpty()) { return; } AttributesImpl mtAtts = attributes("xlink:href", ResponseUtils.proxifyMetadataLink(link, request.getBaseUrl())); start("MetadataURL", mtAtts); end("MetadataURL"); } } }; delegate = (CapabilitiesTranslator1_1) wfs1_1.createTranslator(handler); } public void encode(Object o) throws IllegalArgumentException { request = GetCapabilitiesRequest.adapt(o); delegate.request = request; StringBuilder schemaLocation = new StringBuilder(); schemaLocation.append(WFS20_URI); schemaLocation.append(" "); if (wfs.isCanonicalSchemaLocation()) { schemaLocation.append(org.geotools.wfs.v2_0.WFS.CANONICAL_SCHEMA_LOCATION); } else { schemaLocation.append(buildSchemaURL(request.getBaseUrl(), "wfs/2.0/wfs.xsd")); } delegate.addExtensionSchemaLocation(schemaLocation); 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", schemaLocation.toString() }); @SuppressWarnings("rawtypes") Enumeration prefixes = getNamespaceSupport().getPrefixes(); while (prefixes.hasMoreElements()) { String prefix = (String) prefixes.nextElement(); attributes.addAttribute(null, null, "xmlns:" + prefix, null, getNamespaceSupport().getURI(prefix)); } 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"); } protected 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(); delegate.extendedCapabilities(); end("ows:OperationsMetadata"); } protected 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"); } protected void operation(String name, Map.Entry[] parameters, boolean get, boolean post) { operation(name, parameters, null, get, post); } /** * Encodes the GetCapabilities ows:Operation element. */ protected 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. */ protected 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. */ protected 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); } protected Map.Entry[] getFeatureConstraints() { return new Map.Entry[] { parameter("PagingIsTransactionSafe", false), parameter("CountDefault", wfs.getMaxFeatures()) }; } /** * Encodes the GetFeatureWithLock ows:Operation element. */ protected 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); } protected void getPropertyValue() { Map.Entry[] parameters = new Map.Entry[] { parameter("resolve", new String[] { "none" }), }; operation("GetPropertyValue", parameters, true, true); } /** * Encodes the LockFeature ows:Operation element. */ protected 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. */ protected 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. */ protected void listStoredQueries() { operation("ListStoredQueries", null, true, true); } /** * Encodes the ListStoredQueries ows:Operation element. */ protected void describeStoredQueries() { operation("DescribeStoredQueries", null, true, true); } /** * Encodes the CreateStoredQuery ows:Operation element. */ protected void createStoredQuery() { operation("CreateStoredQuery", null, false, true); } /** * Encodes the DropStoredQuery ows:Operation element. */ protected void dropStoredQuery() { operation("DropStoredQuery", null, true, true); } /** * Encodes service constraints. */ protected 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"}); } protected void constraint(String name, Object value) { if (value instanceof Boolean) { constraint(name, ((Boolean)value).booleanValue()); } else { constraint(name, (String) value.toString()); } } protected void constraint(String name, boolean value) { constraint(name, String.valueOf(value).toUpperCase()); } protected 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"); } protected 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"); } protected void featureTypeList() { if (catalog.getFeatureTypes().isEmpty()) { return; } start("FeatureTypeList"); //TODO: namespace filtering delegate.featureTypes(true, request.getNamespace()); end("FeatureTypeList"); } protected 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", "Within" })); 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"); } } protected 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); } } }