/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2017, 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;
import static org.geotools.data.wfs.internal.URIs.buildURL;
import org.geotools.util.Version;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.ows.HTTPClient;
import org.geotools.data.ows.SimpleHttpClient;
import org.geotools.data.wfs.impl.WFSDataAccessFactory;
import org.geotools.data.wfs.internal.Versions;
import org.geotools.data.wfs.internal.WFSClient;
import org.geotools.data.wfs.internal.WFSConfig;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.type.FeatureTypeFactoryImpl;
import org.geotools.ows.ServiceException;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;
/**
* A {@link DataStoreFactorySpi} to connect to a Web Feature Service.
* <p>
* Produces a {@link WFSDataStore} if the correct set of connection parameters are provided. For
* instance, the only mandatory one is {@link #URL}.
* </p>
* <p>
* As with all the DataStoreFactorySpi implementations, this one is not intended to be used directly
* but through the {@link DataStoreFinder} mechanism, hence client applications should not have
* strong dependencies over this module.
* </p>
* <p>
* Upon a valid URL to a WFS GetCapabilities document, this factory will perform version negotiation
* between the server supported protocol versions and this plugin supported ones, and will return a
* {@link DataStore} capable of communicating with the server using the agreed WFS protocol version.
* </p>
* <p>
* In the case the provided GetCapabilities URL explicitly contains a VERSION parameter and both the
* server and client support that version, that version will be used.
* </p>
*
* @see WFSDataStore
* @see WFSClient
*/
@SuppressWarnings({ "unchecked", "nls" })
public class WFSDataStoreFactory extends WFSDataAccessFactory implements DataStoreFactorySpi {
private static int GMLComplianceLevel = 0;
/**
* Requests the WFS Capabilities document from the {@link WFSDataStoreFactory#URL url} parameter
* in {@code params} and returns a {@link WFSDataStore} according to the version of the
* GetCapabilities document returned.
* <p>
* Note the {@code URL} provided as parameter must refer to the actual {@code GetCapabilities}
* request. If you need to specify a preferred version or want the GetCapabilities request to be
* generated from a base URL build the URL with the {@link #createGetCapabilitiesRequest} first.
* </p>
*
* @see org.geotools.data.DataStoreFactorySpi#createDataStore(java.util.Map)
*/
@Override
public WFSDataStore createDataStore(final Map<String, Serializable> params)
throws IOException {
final WFSConfig config = WFSConfig.fromParams(params);
{
String user = config.getUser();
String password = config.getPassword();
if (((user == null) && (password != null))
|| ((config.getPassword() == null) && (config.getUser() != null))) {
throw new IOException(
"Cannot define only one of USERNAME or PASSWORD, must define both or neither");
}
}
final HTTPClient http = new SimpleHttpClient();// new MultithreadedHttpClient();
http.setTryGzip(config.isTryGZIP());
http.setUser(config.getUser());
http.setPassword(config.getPassword());
int timeoutMillis = config.getTimeoutMillis();
http.setConnectTimeout(timeoutMillis / 1000);
http.setReadTimeout(timeoutMillis / 1000);
final URL capabilitiesURL = (URL) URL.lookUp(params);
// WFSClient performs version negotiation and selects the correct strategy
WFSClient wfsClient;
try {
wfsClient = new WFSClient(capabilitiesURL, http, config);
} catch (ServiceException e) {
throw new IOException(e);
}
WFSDataStore dataStore = new WFSDataStore(wfsClient);
// factories
dataStore.setFilterFactory(CommonFactoryFinder.getFilterFactory(null));
dataStore.setGeometryFactory(new GeometryFactory(
PackedCoordinateSequenceFactory.DOUBLE_FACTORY));
dataStore.setFeatureTypeFactory(new FeatureTypeFactoryImpl());
dataStore.setFeatureFactory(CommonFactoryFinder.getFeatureFactory(null));
dataStore.setDataStoreFactory(this);
dataStore.setNamespaceURI(config.getNamespaceOverride());
return dataStore;
}
@Override
public DataStore createNewDataStore(final Map<String, Serializable> params) throws IOException {
throw new UnsupportedOperationException("Operation not applicable to a WFS service");
}
@Override
public String getDisplayName() {
return "Web Feature Server (NG)";
}
@Override
public String getDescription() {
return "Provides access to the Features published a Web Feature Service, "
+ "and the ability to perform transactions on the server (when supported / allowed).";
}
/**
* Checks whether {@code params} contains a valid set of parameters to connecto to a WFS.
* <p>
* Rules are:
* <ul>
* <li>The mandatory {@link #URL} is provided.
* <li>Either both {@link #USERNAME} and {@link #PASSWORD} are provided, or none.
* </ul>
* </p>
*/
@Override
public boolean canProcess(@SuppressWarnings("rawtypes") final Map params) {
return super.canProcess(params, GMLComplianceLevel);
}
/**
* Creates a HTTP GET Method based WFS {@code GetCapabilities} request for the given protocol
* version.
* <p>
* If the query string in the {@code host} URL already contains a VERSION number, that version
* is <b>discarded</b>.
* </p>
*
* @param host
* non null URL from which to construct the WFS {@code GetCapabilities} request by
* discarding the query string, if any, and appending the propper query string.
* @return
*/
public static URL createGetCapabilitiesRequest(URL host, Version version) {
if (host == null) {
throw new NullPointerException("null url");
}
if (version == null) {
throw new NullPointerException("version");
}
Map<String, String> getCapsKvp = new HashMap<String, String>();
getCapsKvp.put("SERVICE", "WFS");
getCapsKvp.put("REQUEST", "GetCapabilities");
getCapsKvp.put("VERSION", version.toString());
return buildURL(host, getCapsKvp);
}
/**
* Creates a HTTP GET Method based WFS {@code GetCapabilities} request.
* <p>
* If the query string in the {@code host} URL already contains a VERSION number, that version
* is used, otherwise the queried version will be 1.0.0.
* </p>
* <p>
* <b>NOTE</b> the default version will be 1.0.0 until the support for 1.1.0 gets stable enough
* for general use. If you want to use a 1.1.0 WFS you'll have to explicitly provide the
* VERSION=1.1.0 parameter in the GetCapabilities request meanwhile.
* </p>
*
* @param host
* non null URL pointing either to a base WFS service access point, or to a full
* {@code GetCapabilities} request.
* @return
*/
public static URL createGetCapabilitiesRequest(final URL host) {
if (host == null) {
throw new NullPointerException("url");
}
String queryString = host.getQuery();
queryString = queryString == null || "".equals(queryString.trim()) ? "" : queryString
.toUpperCase();
final Version defaultVersion = Versions.highest();
// which version to use
Version requestVersion = defaultVersion;
if (queryString.length() > 0) {
Map<String, String> params = new HashMap<String, String>();
String[] split = queryString.split("&");
for (String kvp : split) {
int index = kvp.indexOf('=');
String key = index > 0 ? kvp.substring(0, index) : kvp;
String value = index > 0 ? kvp.substring(index + 1) : null;
params.put(key, value);
}
String request = params.get("REQUEST");
if ("GETCAPABILITIES".equals(request)) {
String version = params.get("VERSION");
if (version != null) {
requestVersion = Versions.find(version);
if (requestVersion == null) {
requestVersion = defaultVersion;
}
}
}
}
return createGetCapabilitiesRequest(host, requestVersion);
}
/**
* Defaults to true, only a few datastores need to check for drivers.
*
* @return <code>true</code>
*/
public boolean isAvailable() {
return true;
}
}