/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2008-2016, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.data.wfs.internal;
import static org.geotools.data.wfs.internal.Loggers.requestDebug;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.geotools.data.ows.AbstractOpenWebService;
import org.geotools.data.ows.GetCapabilitiesRequest;
import org.geotools.data.ows.HTTPClient;
import org.geotools.data.ows.Request;
import org.geotools.data.ows.Response;
import org.geotools.data.ows.Specification;
import org.geotools.data.wfs.WFSServiceInfo;
import org.geotools.data.wfs.internal.GetFeatureRequest.ResultType;
import org.geotools.data.wfs.internal.v1_x.CubeWerxStrategy;
import org.geotools.data.wfs.internal.v1_x.GeoServerPre200Strategy;
import org.geotools.data.wfs.internal.v1_x.IonicStrategy;
import org.geotools.data.wfs.internal.v1_x.MapServerWFSStrategy;
import org.geotools.data.wfs.internal.v1_x.StrictWFS_1_x_Strategy;
import org.geotools.data.wfs.internal.v2_0.StrictWFS_2_0_Strategy;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.ows.ServiceException;
import org.geotools.util.Version;
import org.geotools.util.logging.Logging;
import org.geotools.xml.XMLHandlerHints;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class WFSClient extends AbstractOpenWebService<WFSGetCapabilities, QName> {
private static final Logger LOGGER = Logging.getLogger(WFSClient.class);
protected final WFSConfig config;
public WFSClient(URL capabilitiesURL, HTTPClient httpClient, WFSConfig config)
throws IOException, ServiceException {
this(capabilitiesURL, httpClient, config, (WFSGetCapabilities) null);
}
public WFSClient(URL capabilitiesURL, HTTPClient httpClient, WFSConfig config,
WFSGetCapabilities capabilities) throws IOException, ServiceException {
super(capabilitiesURL, httpClient, capabilities, Collections.singletonMap(XMLHandlerHints.ENTITY_RESOLVER, config.getEntityResolver()));
this.config = config;
super.specification = determineCorrectStrategy();
((WFSStrategy) specification).setCapabilities(super.capabilities);
}
protected WFSStrategy getStrategy() {
return (WFSStrategy) super.specification;
}
@Override
public WFSGetCapabilities getCapabilities() {
return capabilities;
}
@Override
public WFSServiceInfo getInfo() {
return (WFSServiceInfo) super.getInfo();
}
@Override
protected WFSServiceInfo createInfo() {
return getStrategy().getServiceInfo();
}
@Override
protected FeatureTypeInfo createInfo(QName typeName) {
return getStrategy().getFeatureTypeInfo(typeName);
}
@Override
protected void setupSpecifications() {
specs = new Specification[3];
WFSStrategy strictWFS_1_0_0_Strategy = new StrictWFS_1_x_Strategy(Versions.v1_0_0);
WFSStrategy strictWFS_1_1_0_Strategy = new StrictWFS_1_x_Strategy(Versions.v1_1_0);
WFSStrategy strictWFS_2_0_0_Strategy = new StrictWFS_2_0_Strategy();
strictWFS_1_0_0_Strategy.setConfig(config);
strictWFS_1_1_0_Strategy.setConfig(config);
strictWFS_2_0_0_Strategy.setConfig(config);
specs[0] = strictWFS_1_0_0_Strategy;
specs[1] = strictWFS_1_1_0_Strategy;
specs[2] = strictWFS_2_0_0_Strategy;
}
/**
* Determine correct WFSStrategy based on capabilities document.
*
* @param getCapabilitiesRequest
* @param capabilitiesDoc
* @param override
* optional override provided by user
* @return WFSStrategy to use
*/
private WFSStrategy determineCorrectStrategy() {
final Version capsVersion = new Version(capabilities.getVersion());
Document capabilitiesDoc = capabilities.getRawDocument();
final String override = config.getWfsStrategy();
WFSStrategy strategy = null;
//only one 2.0.0 strategy!
if (Versions.v2_0_0.equals(capsVersion)) {
strategy = new StrictWFS_2_0_Strategy();
}
// override
if (!"auto".equals(override)) {
if (override.equalsIgnoreCase("geoserver")) {
strategy = new GeoServerPre200Strategy();
} else if (override.equalsIgnoreCase("mapserver")) {
strategy = new MapServerWFSStrategy(capabilitiesDoc);
} else if (override.equalsIgnoreCase("cubewerx")) {
strategy = new CubeWerxStrategy();
} else if (override.equalsIgnoreCase("ionic")) {
strategy = new IonicStrategy();
} else {
LOGGER.warning("Could not handle wfs strategy override " + override
+ " proceeding with autodetection");
}
}
// auto detection
if (strategy == null) {
// look in comments for indication of CubeWerx server
NodeList childNodes = capabilitiesDoc.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() == Node.COMMENT_NODE) {
String nodeValue = child.getNodeValue();
nodeValue = nodeValue.toLowerCase();
if (nodeValue.contains("cubewerx")) {
strategy = new CubeWerxStrategy();
break;
}
}
}
}
if (strategy == null) {
// Ionic declares its own namespace so that's our hook
Element root = capabilitiesDoc.getDocumentElement();
String ionicNs = root.getAttribute("xmlns:ionic");
if (ionicNs != null) {
if (ionicNs.equals("http://www.ionicsoft.com/versions/4")) {
strategy = new IonicStrategy();
} else if (ionicNs.startsWith("http://www.ionicsoft.com/versions")) {
LOGGER.warning("Found a Ionic server but the version may not match the strategy "
+ "we have (v.4). Ionic namespace url: " + ionicNs);
strategy = new IonicStrategy();
}
}
}
if (strategy == null) {
java.net.URL capabilitiesURL = super.serverURL;
// guess server implementation from capabilities URI
String uri = capabilitiesURL.toExternalForm();
/*
* TODO: the "file:" test is a workaround for GEOT-4223
*
* Gabriel Roldán commented on GEOT-4223: sigh, yeah, that's probably that worse heuristic ever, but its inherited from the current wfs
* 1.0 module. We've briefly discussed a better approach on the list a while back (checking for the list of supported functions to figure
* out whether its a geoserver. Some key function names may even let you know which geoserver version it is). For the time being, please
* feel free to apply the patch if its a blocker for you, but don't close this issue. Just add a reminder that the heuristic for geoserver
* needs to be improved?
*/
if (!uri.startsWith("file:") && uri.contains("geoserver") && !Versions.v2_0_0.equals(capsVersion)) {
strategy = new GeoServerPre200Strategy();
} else if (uri.contains("/ArcGIS/services/")) {
strategy = new StrictWFS_1_x_Strategy(); // new ArcGISServerStrategy();
} else if (uri.contains("mapserver") || uri.contains("map=")) {
strategy = new MapServerWFSStrategy(capabilitiesDoc);
}
}
if (strategy == null) {
// use fallback strategy
if (Versions.v1_0_0.equals(capsVersion)) {
strategy = new StrictWFS_1_x_Strategy(Versions.v1_0_0);
} else if (Versions.v1_1_0.equals(capsVersion)) {
strategy = new StrictWFS_1_x_Strategy(Versions.v1_1_0);
} else {
throw new IllegalArgumentException("Unsupported version: " + capsVersion);
}
}
LOGGER.finer("Using WFS Strategy: " + strategy.getClass().getName());
strategy.setConfig(config);
return strategy;
}
public Set<QName> getRemoteTypeNames() {
Set<QName> featureTypeNames = getStrategy().getFeatureTypeNames();
return featureTypeNames;
}
public boolean supportsTransaction(QName typeName) {
return getStrategy().supportsTransaction(typeName);
}
public boolean canLimit() {
return true;
}
public boolean canFilter() {
return true;
}
public boolean canRetype() {
return true;
}
public boolean canSort() {
final Version capsVersion = new Version(capabilities.getVersion());
//currently on version 1.1.0 supports native sorting
if (Versions.v1_1_0.equals(capsVersion)) {
return true;
} else {
return false;
}
}
public boolean supportsStoredQueries() {
return getStrategy().supportsOperation(WFSOperationType.LIST_STORED_QUERIES, HttpMethod.POST) ||
getStrategy().supportsOperation(WFSOperationType.LIST_STORED_QUERIES, HttpMethod.GET);
}
public ReferencedEnvelope getBounds(QName typeName, CoordinateReferenceSystem targetCrs) {
WFSStrategy strategy = getStrategy();
final FeatureTypeInfo typeInfo = strategy.getFeatureTypeInfo(typeName);
ReferencedEnvelope nativeBounds = typeInfo.getBounds();
ReferencedEnvelope bounds;
try {
bounds = nativeBounds.transform(targetCrs, true);
} catch (Exception e) {
LOGGER.log(Level.WARNING,
"Can't transform native bounds of " + typeName + ": " + e.getMessage());
try {
bounds = typeInfo.getWGS84BoundingBox().transform(targetCrs, true);
} catch (Exception ew) {
LOGGER.log(Level.WARNING,
"Can't transform wgs84 bounds of " + typeName + ": " + e.getMessage());
bounds = new ReferencedEnvelope(targetCrs);
}
}
return bounds;
}
public boolean canCount() {
return getStrategy().supports(ResultType.HITS);
}
public GetFeatureRequest createGetFeatureRequest() {
WFSStrategy strategy = getStrategy();
return new GetFeatureRequest(config, strategy);
}
@Override
protected Response internalIssueRequest(Request request) throws IOException {
Response response;
try {
response = super.internalIssueRequest(request);
} catch (ServiceException e) {
throw new IOException(e);
}
return response;
}
@Override
public GetCapabilitiesResponse issueRequest(GetCapabilitiesRequest request) throws IOException,
ServiceException {
return (GetCapabilitiesResponse) internalIssueRequest(request);
}
public ListStoredQueriesResponse issueRequest(ListStoredQueriesRequest request)
throws IOException {
return (ListStoredQueriesResponse) internalIssueRequest(request);
}
public DescribeStoredQueriesResponse issueRequest(DescribeStoredQueriesRequest request)
throws IOException {
return (DescribeStoredQueriesResponse) internalIssueRequest(request);
}
public TransactionRequest createTransaction() {
WFSStrategy strategy = getStrategy();
return new TransactionRequest(config, strategy);
}
public TransactionResponse issueTransaction(TransactionRequest request) throws IOException {
requestDebug("Sending Transaction request to ", request.getFinalURL());
Response response = internalIssueRequest(request);
return (TransactionResponse) response;
}
public GetFeatureResponse issueRequest(GetFeatureRequest request) throws IOException {
requestDebug("Sending GetFeature request to ", request.getFinalURL());
Response response = internalIssueRequest(request);
return (GetFeatureResponse) response;
}
public DescribeFeatureTypeRequest createDescribeFeatureTypeRequest() {
return new DescribeFeatureTypeRequest(config, getStrategy());
}
public ListStoredQueriesRequest createListStoredQueriesRequest() {
return new ListStoredQueriesRequest(config, getStrategy());
}
public DescribeStoredQueriesRequest createDescribeStoredQueriesRequest() {
return new DescribeStoredQueriesRequest(config, getStrategy());
}
public DescribeFeatureTypeResponse issueRequest(DescribeFeatureTypeRequest request)
throws IOException {
requestDebug("Sending DFT request to ", request.getFinalURL());
Response response = internalIssueRequest(request);
return (DescribeFeatureTypeResponse) response;
}
/**
* Splits the filter provided by the geotools query into the server supported and unsupported
* ones.
*
* @param typeName
*
* @return a two-element array where the first element is the supported filter and the second
* the one to post-process
* @see org.geotools.data.wfs.internal.WFSStrategy#splitFilters(org.opengis.filter.Filter)
*/
public Filter[] splitFilters(QName typeName, Filter filter) {
return getStrategy().splitFilters(typeName, filter);
}
public CoordinateReferenceSystem getDefaultCRS(QName typeName) {
final WFSStrategy strategy = getStrategy();
FeatureTypeInfo typeInfo = strategy.getFeatureTypeInfo(typeName);
CoordinateReferenceSystem crs = typeInfo.getCRS();
return crs;
}
public String getAxisOrderFilter(){
return config.getAxisOrderFilter();
}
public URL getCapabilitiesURL() {
return serverURL;
}
public WFSConfig getConfig() {
return config;
}
}