/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2008, 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.protocol.http.HttpMethod.GET;
import static org.geotools.data.wfs.protocol.http.HttpMethod.POST;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.geotools.data.AbstractDataStoreFactory;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.Parameter;
import org.geotools.data.wfs.protocol.http.HTTPProtocol;
import org.geotools.data.wfs.protocol.http.HTTPResponse;
import org.geotools.data.wfs.protocol.http.HttpMethod;
import org.geotools.data.wfs.protocol.http.SimpleHttpProtocol;
import org.geotools.data.wfs.protocol.wfs.Version;
import org.geotools.data.wfs.protocol.wfs.WFSProtocol;
import org.geotools.data.wfs.v1_0_0.WFS100ProtocolHandler;
import org.geotools.data.wfs.v1_0_0.WFS_1_0_0_DataStore;
import org.geotools.data.wfs.v1_1_0.ArcGISServerStrategy;
import org.geotools.data.wfs.v1_1_0.CubeWerxStrategy;
import org.geotools.data.wfs.v1_1_0.DefaultWFSStrategy;
import org.geotools.data.wfs.v1_1_0.GeoServerStrategy;
import org.geotools.data.wfs.v1_1_0.IonicStrategy;
import org.geotools.data.wfs.v1_1_0.WFSStrategy;
import org.geotools.data.wfs.v1_1_0.WFS_1_1_0_DataStore;
import org.geotools.data.wfs.v1_1_0.WFS_1_1_0_Protocol;
import org.geotools.util.logging.Logging;
import org.geotools.wfs.WFS;
import org.geotools.wfs.protocol.ConnectionFactory;
import org.geotools.wfs.protocol.DefaultConnectionFactory;
import org.geotools.xml.XMLHandlerHints;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* A {@link DataStoreFactorySpi} to connect to a Web Feature Service.
* <p>
* Produces a {@link WFSDataStore} is 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, so client application 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>
* <p>
* That said, for the time being, the current default version is {@code 1.0.0} instead of {@code
* 1.1.0}, since the former is the one that supports transactions. When further development provides
* transaction support for the WFS 1.1.0 version, propper version negotiation capabilities will be
* added.
* </p>
* <p>
* Among feeding the wfs datastore with a {@link WFSProtocol} that can handle the WFS version agreed
* upong the server and this client, this factory will try to provide the datastore with a
* {@link WFSStrategy} appropriate for the WFS implementation, if that could be somehow guessed.
* That is so the datastore itself nor the protocol need to worry about any implementation specific
* limitation or deviation from the standard the actual server may have.
* </p>
*
* @author dzwiers
* @author Gabriel Roldan (TOPP)
*
*
* @source $URL$
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools
* /data/wfs/WFSDataStoreFactory.java $
* @see WFSDataStore
* @see WFSProtocol
* @see WFSStrategy
*/
@SuppressWarnings( { "unchecked", "nls" })
public class WFSDataStoreFactory extends AbstractDataStoreFactory {
private static final Logger logger = Logging.getLogger("org.geotools.data.wfs");
/**
* A {@link Param} subclass that allows to provide a default value to the lookUp method.
*
* @author Gabriel Roldan
* @version $Id$
* @since 2.5.x
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools
* /data/wfs/WFSDataStoreFactory.java $
*/
public static class WFSFactoryParam<T> extends Param {
private T defaultValue;
/**
* Creates a required parameter
*
* @param key
* @param type
* @param description
*/
public WFSFactoryParam(String key, Class type, String description) {
super(key, type, description, true);
}
/**
* Creates an optional parameter with the supplied default value
*
* @param key
* @param type
* @param description
* @param required
*/
public WFSFactoryParam(String key, Class type, String description, T defaultValue) {
super(key, type, description, false);
this.defaultValue = defaultValue;
}
public WFSFactoryParam(String key, Class type, String description, T defaultValue, Object... metadata) {
super(key, type, description, false, metadata);
this.defaultValue = defaultValue;
}
public T lookUp(final Map params) throws IOException {
T parameter = (T) super.lookUp(params);
return parameter == null ? defaultValue : parameter;
}
}
/** Access with {@link WFSDataStoreFactory#getParametersInfo() */
private static final WFSFactoryParam<?>[] parametersInfo = new WFSFactoryParam[12];
/**
* Mandatory DataStore parameter indicating the URL for the WFS GetCapabilities document.
*/
public static final WFSFactoryParam<URL> URL;
static {
String name = "WFSDataStoreFactory:GET_CAPABILITIES_URL";
String description = "Represents a URL to the getCapabilities document or a server instance.";
parametersInfo[0] = URL = new WFSFactoryParam<URL>(name, URL.class, description);
}
/**
* Optional {@code Boolean} DataStore parameter acting as a hint for the HTTP protocol to use
* preferably against the WFS instance, with the following semantics:
* <ul>
* <li>{@code null} (not supplied): use "AUTO", let the DataStore decide.
* <li>{@code Boolean.TRUE} use HTTP POST preferably.
* <li {@code Boolean.FALSE} use HTTP GET preferably.
* </ul>
*/
public static final WFSFactoryParam<Boolean> PROTOCOL;
static {
String name = "WFSDataStoreFactory:PROTOCOL";
String description = "Sets a preference for the HTTP protocol to use when requesting "
+ "WFS functionality. Set this value to Boolean.TRUE for POST, Boolean.FALSE "
+ "for GET or NULL for AUTO";
parametersInfo[1] = PROTOCOL = new WFSFactoryParam<Boolean>(name, Boolean.class, description,null);
}
/**
* Optional {@code String} DataStore parameter supplying the user name to use when the server
* requires HTTP authentication
* <p>
* Shall be used together with {@link #PASSWORD} or not used at all.
* </p>
*
* @see Authenticator
*/
public static final WFSFactoryParam<String> USERNAME;
static {
String name = "WFSDataStoreFactory:USERNAME";
String description = "This allows the user to specify a username. This param should not "
+ "be used without the PASSWORD param.";
parametersInfo[2] = USERNAME = new WFSFactoryParam(name, String.class, description, null);
}
/**
* Optional {@code String} DataStore parameter supplying the password to use when the server
* requires HTTP authentication
* <p>
* Shall be used together with {@link #USERNAME} or not used at all.
* </p>
*
* @see Authenticator
*/
public static final WFSFactoryParam<String> PASSWORD;
static {
String name = "WFSDataStoreFactory:PASSWORD";
String description = "This allows the user to specify a username. This param should not"
+ " be used without the USERNAME param.";
parametersInfo[3] = PASSWORD = new WFSFactoryParam(name, String.class, description, null,
Param.IS_PASSWORD, true);
}
/**
* Optional {@code String} DataStore parameter supplying a JVM supported {@link Charset charset}
* name to use as the character encoding for XML requests sent to the server.
*/
public static final WFSFactoryParam<String> ENCODING;
static {
String name = "WFSDataStoreFactory:ENCODING";
String description = "This allows the user to specify the character encoding of the "
+ "XML-Requests sent to the Server. Defaults to UTF-8";
parametersInfo[4] = ENCODING = new WFSFactoryParam<String>(name, String.class, description, "UTF-8");
}
/**
* Optional {@code Integer} DataStore parameter indicating a timeout in milliseconds for the
* HTTP connections.
* <>p>
* @TODO: specify if its just a connection timeout or also a read timeout
*/
public static final WFSFactoryParam<Integer> TIMEOUT;
static {
String name = "WFSDataStoreFactory:TIMEOUT";
String description = "This allows the user to specify a timeout in milliseconds. This param"
+ " has a default value of 3000ms.";
parametersInfo[5] = TIMEOUT = new WFSFactoryParam<Integer>(name, Integer.class, description, 3000);
}
/**
* Optional {@code Integer} parameter stating how many Feature instances to buffer at once. Only
* implemented for WFS 1.0.0 support.
*/
public static final WFSFactoryParam<Integer> BUFFER_SIZE;
static {
String name = "WFSDataStoreFactory:BUFFER_SIZE";
String description = "This allows the user to specify a buffer size in features. This param "
+ "has a default value of 10 features.";
parametersInfo[6] = BUFFER_SIZE = new WFSFactoryParam<Integer>(name, Integer.class, description, 10);
}
/**
* Optional {@code Boolean} data store parameter indicating whether to set the accept GZip
* encoding on the HTTP request headers sent to the server
*/
public static final WFSFactoryParam<Boolean> TRY_GZIP;
static {
String name = "WFSDataStoreFactory:TRY_GZIP";
String description = "Indicates that datastore should use gzip to transfer data if the server "
+ "supports it. Default is true";
parametersInfo[7] = TRY_GZIP = new WFSFactoryParam<Boolean>(name, Boolean.class, description, Boolean.TRUE);
}
/**
* Optional {@code Boolean} DataStore parameter indicating whether to be lenient about parsing
* bad data
*/
public static final WFSFactoryParam<Boolean> LENIENT;
static {
String name = "WFSDataStoreFactory:LENIENT";
String description = "Indicates that datastore should do its best to create features from the "
+ "provided data even if it does not accurately match the schema. Errors will "
+ "be logged but the parsing will continue if this is true. Default is false";
parametersInfo[8] = LENIENT = new WFSFactoryParam<Boolean>(name, Boolean.class,
description, false);
}
/**
* Optional positive {@code Integer} used as a hard limit for the amount of Features to retrieve
* for each FeatureType. A value of zero or not providing this parameter means no limit.
*/
public static final WFSFactoryParam<Integer> MAXFEATURES;
static {
String name = "WFSDataStoreFactory:MAXFEATURES";
String description = "Positive integer used as a hard limit for the amount of Features to retrieve"
+ " for each FeatureType. A value of zero or not providing this parameter means no limit.";
parametersInfo[9] = MAXFEATURES = new WFSFactoryParam<Integer>(name, Integer.class, description, 0 );
}
/**
* Optional {@code Integer} DataStore parameter indicating level of compliance to WFS
* specification
* <ul>
* <li>{@link XMLHandlerHints#VALUE_FILTER_COMPLIANCE_LOW}</li>
* <li>{@link XMLHandlerHints#VALUE_FILTER_COMPLIANCE_MEDIUM}</li>
* <li>{@link XMLHandlerHints#VALUE_FILTER_COMPLIANCE_HIGH}</li>
* </ul>
*/
public static final WFSFactoryParam<Integer> FILTER_COMPLIANCE;;
static {
String name = "WFSDataStoreFactory:FILTER_COMPLIANCE";
String description = "Level of compliance to WFS specification (0-low,1-medium,2-high)";
List<Integer> options = Arrays.asList(new Integer[]{0,1,2} );
parametersInfo[10] = FILTER_COMPLIANCE = new WFSFactoryParam<Integer>(name, Integer.class,
description, null, Parameter.OPTIONS, options );
}
/**
* Optional {@code String} DataStore parameter indicating either "mapserver", "geoserver",
* "strict" or "nonstrict" strategy
*/
public static final WFSFactoryParam<String> WFS_STRATEGY;
static {
String name = "WFSDataStoreFactory:WFS_STRATEGY";
String description = "Override wfs stragegy with either arcgis, cubwerx, ionic, mapserver"
+ ", geoserver, strict or nonstrict strategy.";
List<String> options = Arrays.asList(new String[] { "strict", "nonstrict",
"mapserver", "geoserver", "arcgis", "cubewerx", "ionix" });
parametersInfo[11] = WFS_STRATEGY = new WFSFactoryParam<String>(name, String.class,
description, null, Parameter.OPTIONS, options);
}
protected Map<Map, WFSDataStore> perParameterSetDataStoreCache = new HashMap();
/**
* 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(URL, Version)} first.
* </p>
*
* @see org.geotools.data.DataStoreFactorySpi#createDataStore(java.util.Map)
*/
public WFSDataStore createDataStore(final Map params) throws IOException {
if (perParameterSetDataStoreCache.containsKey(params)) {
return perParameterSetDataStoreCache.get(params);
}
final URL getCapabilitiesRequest = (URL) URL.lookUp(params);
final Boolean protocol = (Boolean) PROTOCOL.lookUp(params);
final String user = (String) USERNAME.lookUp(params);
final String pass = (String) PASSWORD.lookUp(params);
final int timeoutMillis = (Integer) TIMEOUT.lookUp(params);
final int buffer = (Integer) BUFFER_SIZE.lookUp(params);
final boolean tryGZIP = (Boolean) TRY_GZIP.lookUp(params);
final boolean lenient = (Boolean) LENIENT.lookUp(params);
final String encoding = (String) ENCODING.lookUp(params);
final Integer maxFeatures = (Integer) MAXFEATURES.lookUp(params);
final Charset defaultEncoding = Charset.forName(encoding);
final String wfsStrategy = (String) WFS_STRATEGY.lookUp(params);
final Integer filterCompliance = (Integer) FILTER_COMPLIANCE.lookUp(params);
if (((user == null) && (pass != null)) || ((pass == null) && (user != null))) {
throw new IOException(
"Cannot define only one of USERNAME or PASSWORD, must define both or neither");
}
final WFSDataStore dataStore;
final HTTPProtocol http = new SimpleHttpProtocol();
http.setTryGzip(tryGZIP);
http.setAuth(user, pass);
http.setTimeoutMillis(timeoutMillis);
final byte[] wfsCapabilitiesRawData = loadCapabilities(getCapabilitiesRequest, http);
final Document capsDoc = parseCapabilities(wfsCapabilitiesRawData);
final Element rootElement = capsDoc.getDocumentElement();
final String capsVersion = rootElement.getAttribute("version");
final Version version = Version.find(capsVersion);
if (Version.v1_0_0 == version) {
final ConnectionFactory connectionFac = new DefaultConnectionFactory(tryGZIP, user,
pass, defaultEncoding, timeoutMillis);
InputStream reader = new ByteArrayInputStream(wfsCapabilitiesRawData);
final WFS100ProtocolHandler protocolHandler = new WFS100ProtocolHandler(reader,
connectionFac);
try {
HttpMethod prefferredProtocol = Boolean.TRUE.equals(protocol) ? POST : GET;
dataStore = new WFS_1_0_0_DataStore(prefferredProtocol, protocolHandler,
timeoutMillis, buffer, lenient, wfsStrategy, filterCompliance);
} catch (SAXException e) {
logger.warning(e.toString());
throw new IOException(e.toString());
}
} else {
InputStream capsIn = new ByteArrayInputStream(wfsCapabilitiesRawData);
WFS_1_1_0_Protocol wfs = new WFS_1_1_0_Protocol(capsIn, http);
WFSStrategy strategy = determineCorrectStrategy(getCapabilitiesRequest, capsDoc, wfsStrategy );
wfs.setStrategy(strategy);
dataStore = new WFS_1_1_0_DataStore(wfs);
dataStore.setMaxFeatures(maxFeatures);
dataStore.setPreferPostOverGet(protocol);
}
perParameterSetDataStoreCache.put(new HashMap(params), dataStore);
return dataStore;
}
private static Document parseCapabilities(final byte[] wfsCapabilitiesRawData)
throws IOException, DataSourceException {
Document capsDoc;
{
ByteArrayInputStream inputStream = new ByteArrayInputStream(wfsCapabilitiesRawData);
capsDoc = parseCapabilities(inputStream);
Element root = capsDoc.getDocumentElement();
String localName = root.getLocalName();
String namespace = root.getNamespaceURI();
if (!WFS.NAMESPACE.equals(namespace)
|| !WFS.WFS_Capabilities.getLocalPart().equals(localName)) {
if ("http://www.opengis.net/ows".equals(namespace)
&& "ExceptionReport".equals(localName)) {
StringBuffer message = new StringBuffer();
Element exception = (Element) capsDoc.getElementsByTagNameNS("*", "Exception")
.item(0);
if (exception == null) {
throw new DataSourceException(
"Exception Report when requesting capabilities");
}
Node exceptionCode = exception.getAttributes().getNamedItem("exceptionCode");
Node locator = exception.getAttributes().getNamedItem("locator");
Node exceptionText = exception.getElementsByTagNameNS("*", "ExceptionText")
.item(0);
message.append("Exception Report ");
String text = exceptionText.getTextContent();
if (text != null) {
message.append(text.trim());
}
message.append(" Exception Code:");
message.append(exceptionCode == null ? "" : exceptionCode.getTextContent());
message.append(" Locator: ");
message.append(locator == null ? "" : locator.getTextContent());
throw new DataSourceException(message.toString());
}
throw new DataSourceException("Expected " + WFS.WFS_Capabilities + " but was "
+ namespace + "#" + localName);
}
}
return capsDoc;
}
/**
* Determine correct WFSStrategy based on capabilities document.
*
* @param getCapabilitiesRequest
* @param capabilitiesDoc
* @param override optional override provided by user
* @return WFSStrategy to use
*/
static WFSStrategy determineCorrectStrategy(URL getCapabilitiesRequest, Document capabilitiesDoc, String override) {
WFSStrategy strategy = null;
// override
if( override != null ){
if( override.equalsIgnoreCase("geoserver")){
strategy = new GeoServerStrategy();
}
else if( override.equalsIgnoreCase("arcgis")){
strategy = new ArcGISServerStrategy();
}
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) {
// guess server implementation from capabilities URI
String uri = getCapabilitiesRequest.toExternalForm();
if (uri.contains("geoserver")) {
strategy = new GeoServerStrategy();
}else if (uri.contains("/ArcGIS/services/")){
strategy = new ArcGISServerStrategy();
}
}
if (strategy == null) {
// use fallback strategy
strategy = new DefaultWFSStrategy();
}
logger.info("Using WFS Strategy: " + strategy.getClass().getName());
return strategy;
}
/**
* Unsupported operation, can't create a WFS service.
*
* @throws UnsupportedOperationException
* always, as this operation is not applicable to WFS.
* @see org.geotools.data.DataStoreFactorySpi#createNewDataStore(java.util.Map)
*/
public DataStore createNewDataStore(final Map params) throws IOException {
throw new UnsupportedOperationException("Operation not applicable to a WFS service");
}
/**
* @see org.geotools.data.DataStoreFactorySpi#getDescription()
*/
public String getDescription() {
return "The WFSDataStore represents a connection to a Web Feature Server. This connection provides access to the Features published by the server, and the ability to perform transactions on the server (when supported / allowed).";
}
/**
* Returns the set of parameter descriptors needed to connect to a WFS.
*
* @see org.geotools.data.DataStoreFactorySpi#getParametersInfo()
* @see #URL
* @see #PROTOCOL
* @see #USERNAME
* @see #PASSWORD
* @see #TIMEOUT
* @see #BUFFER_SIZE
* @see #TRY_GZIP
* @see #LENIENT
* @see #ENCODING
*/
public Param[] getParametersInfo() {
int length = parametersInfo.length;
Param[] params = new Param[length];
System.arraycopy(parametersInfo, 0, params, 0, length);
return params;
}
/**
* 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>whether both {@link #USERNAME} and {@link #PASSWORD} are provided, or none.
* </ul>
* Availability of the other optional parameters is not checked for existence.
* </p>
*
* @param params
* non null map of datastore parameters.
* @see org.geotools.data.DataStoreFactorySpi#canProcess(java.util.Map)
*/
public boolean canProcess(final Map params) {
if (params == null) {
return false; // throw new NullPointerException("params");
}
try {
URL url = (URL) URL.lookUp(params);
if( !"http".equalsIgnoreCase(url.getProtocol()) && !"https".equalsIgnoreCase(url.getProtocol())){
return false; // must be http or https since we use SimpleHttpProtocol class
}
} catch (Exception e) {
return false;
}
// check password / username
if (params.containsKey(USERNAME.key)) {
if (!params.containsKey(PASSWORD.key)) {
return false; // must have both
}
} else {
if (params.containsKey(PASSWORD.key)) {
return false; // must have both
}
}
return true;
}
/**
* @see org.geotools.data.DataStoreFactorySpi#getDisplayName()
*/
public String getDisplayName() {
return "Web Feature Server";
}
/**
* @return {@code true}, no extra or external requisites for datastore availability.
* @see org.geotools.data.DataStoreFactorySpi#isAvailable()
*/
public boolean isAvailable() {
return true;
}
/**
* 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");
}
HTTPProtocol httpUtils = new SimpleHttpProtocol();
Map<String, String> getCapsKvp = new HashMap<String, String>();
getCapsKvp.put("SERVICE", "WFS");
getCapsKvp.put("REQUEST", "GetCapabilities");
getCapsKvp.put("VERSION", version.toString());
URL getcapsUrl;
try {
getcapsUrl = httpUtils.createUrl(host, getCapsKvp);
} catch (MalformedURLException e) {
logger.log(Level.WARNING, "Can't create GetCapabilities request from " + host, e);
throw new RuntimeException(e);
}
return getcapsUrl;
}
/**
* 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 = Version.highest();
// We cannot use the highest vesion as the default yet
// since v1_1_0 does not implement a read/write datastore
// and is still having trouble with requests from
// different projections etc...
//
// this is a result of the udig code sprint QA run
final Version defaultVersion = Version.v1_0_0;
// 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 = Version.find(version);
if (requestVersion == null) {
requestVersion = defaultVersion;
}
}
}
}
return createGetCapabilitiesRequest(host, requestVersion);
}
/**
* Package visible to be overridden by unit test.
*
* @param capabilitiesUrl
* @param tryGZIP
* @param auth
* @return
* @throws IOException
*/
byte[] loadCapabilities(final URL capabilitiesUrl, HTTPProtocol http) throws IOException {
byte[] wfsCapabilitiesRawData;
HTTPResponse httpResponse = http.issueGet(capabilitiesUrl, Collections.EMPTY_MAP);
InputStream inputStream = httpResponse.getResponseStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int readCount;
while ((readCount = inputStream.read(buff)) != -1) {
out.write(buff, 0, readCount);
}
wfsCapabilitiesRawData = out.toByteArray();
return wfsCapabilitiesRawData;
}
static Document parseCapabilities(InputStream inputStream) throws IOException,
DataSourceException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder documentBuilder;
try {
documentBuilder = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
Document document;
try {
document = documentBuilder.parse(inputStream);
} catch (SAXException e) {
throw new DataSourceException("Error parsing capabilities document", e);
}
return document;
}
}