Package org.geotools.data.wfs

Source Code of org.geotools.data.wfs.WFSDataStoreFactory$WFSFactoryParam

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2004-2014, 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.KVP;
import org.geotools.util.SimpleInternationalString;
import org.geotools.util.Version;

import java.io.IOException;
import java.io.Serializable;
import java.net.Authenticator;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.geotools.data.AbstractDataStoreFactory;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.Parameter;
import org.geotools.data.ows.HTTPClient;
import org.geotools.data.ows.SimpleHttpClient;
import org.geotools.data.wfs.internal.Loggers;
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 org.geotools.xml.XMLHandlerHints;

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 AbstractDataStoreFactory {
       
    /**
     * Values for the AXIS_ORDER and AXIS_ORDER_FILTER connection parameters.
     */
    public static final String AXIS_ORDER_EAST_NORTH = "East / North";
    public static final String AXIS_ORDER_NORTH_EAST = "North / East";
    public static final String AXIS_ORDER_COMPLIANT = "Compliant";

    /**
     * A {@link Param} subclass that allows to provide a default value to the lookUp method.
     */
    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<T> type, String title, String description) {
            this(key, type, title, description, null);
        }

        /**
         * Creates an optional parameter with the supplied default value
         *
         * @param key
         * @param type
         * @param description
         * @param required
         */
        public WFSFactoryParam(String key, Class<T> type, String title, String description, T defaultValue) {
            super(key, type, new SimpleInternationalString(title), new SimpleInternationalString(description), false, -1, -1, defaultValue, null);
            this.defaultValue = defaultValue;
        }
       
        /**
         * Creates an optional parameter with the supplied default value
         *
         * @param key
         * @param type
         * @param description
         * @param required
         */
        public WFSFactoryParam(String key, Class<T> type, String title, String description, T defaultValue, String level) {
            this(key, type, title, description, defaultValue, Param.LEVEL, level);
            this.defaultValue = defaultValue;
        }

        public WFSFactoryParam(String key, Class<T> type, String title, String description, T defaultValue,
                Object... metadata) {
            super(key, type, new SimpleInternationalString(title), new SimpleInternationalString(description), false, -1, -1, defaultValue, new KVP(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[17];

    /**
     * Mandatory DataStore parameter indicating the URL for the WFS GetCapabilities document.
     */
    public static final WFSFactoryParam<URL> URL;
    static {
        String key = "WFSDataStoreFactory:GET_CAPABILITIES_URL";
        String title = "WFS GetCapabilities URL";
        String description = "Represents a URL to the getCapabilities document or a server instance.";
        parametersInfo[0] = URL = new WFSFactoryParam<URL>(key, URL.class, title, 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 key = "WFSDataStoreFactory:PROTOCOL";
        String title = "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>(key, Boolean.class, title,
                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 key = "WFSDataStoreFactory:USERNAME";
        String title = "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<String>(key, String.class, title, description);
    }

    /**
     * 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 key = "WFSDataStoreFactory:PASSWORD";
        String title = "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<String>(key, String.class, title, 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 key = "WFSDataStoreFactory:ENCODING";
        String title = "Encoding";
        String description = "This allows the user to specify the character encoding of the "
                + "XML-Requests sent to the Server. Defaults to UTF-8";

        String defaultValue = "UTF-8";
        List<String> options = new ArrayList<String>(Charset.availableCharsets().keySet());
        Collections.sort(options);
        parametersInfo[4] = ENCODING = new WFSFactoryParam<String>(key, String.class, title, description,
                defaultValue, Parameter.OPTIONS, options);
    }

    /**
     * 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 key = "WFSDataStoreFactory:TIMEOUT";
        String title = "Time-out";
        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>(key, Integer.class, title,
                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 key = "WFSDataStoreFactory:BUFFER_SIZE";
        String title = "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>(key, Integer.class, title,
                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 key = "WFSDataStoreFactory:TRY_GZIP";
        String title = "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>(key, Boolean.class, title,
                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 key = "WFSDataStoreFactory:LENIENT";
        String title = "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>(key, Boolean.class, title,
                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 key = "WFSDataStoreFactory:MAXFEATURES";
        String title = "Maximum features";
        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>(key, Integer.class, title,
                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 key = "WFSDataStoreFactory:FILTER_COMPLIANCE";
        String title = "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>(key, Integer.class, title,
                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 key = "WFSDataStoreFactory:WFS_STRATEGY";
        String title = "WFS Strategy";
        String description = "Override wfs stragegy with either cubwerx, ionic, mapserver"
                + ", geoserver, or nonstrict strategy.";
        List<String> options = Arrays.asList(new String[] { "auto", "strict", "nonstrict",
                "mapserver", "geoserver", "cubewerx", "ionic" });
        parametersInfo[11] = WFS_STRATEGY = new WFSFactoryParam<String>(key, String.class, title,
                description, "auto", Parameter.OPTIONS, options);
    }

    /**
     * Optional {@code String} namespace URI to override the originial namespaces
     */
    public static final WFSFactoryParam<String> NAMESPACE;
    static {
        String key = "namespace";
        String title = "Namespace";
        String description = "Override the original WFS type name namespaces";
        parametersInfo[12] = NAMESPACE = new WFSFactoryParam<String>(key, String.class, title,
                description, null, "advanced");
    }
   
    /**
     * Optional {@code String} Flag to disable usage of OtherSRS in requests and
     * always use DefaultSRS (eventually reprojecting locally the results)
     */
    public static final WFSFactoryParam<Boolean> USEDEFAULTSRS;
    static {
        String key = "usedefaultsrs";
        String title = "Use Default SRS";
        String description = "Use always the declared DefaultSRS for requests and reproject locally if necessary";
        parametersInfo[13] = USEDEFAULTSRS = new WFSFactoryParam<Boolean>(key,
                Boolean.class, title, description, false, "advanced");
    }
       
    /**
     * Optional {@code String} DataStore parameter indicating axis order used by the
     * remote WFS server in result coordinates.
     */
    public static final WFSFactoryParam<String> AXIS_ORDER;
    static {
        String key = "WFSDataStoreFactory:AXIS_ORDER";
        String title = "Axis Order";
        String description = "Indicates axis order used by the remote WFS server in result coordinates. It applies only to WFS 1.1.0 servers. "
                + "Default is " + AXIS_ORDER_COMPLIANT;
        List<String> options = Arrays.asList(new String[] {
                AXIS_ORDER_COMPLIANT,
                AXIS_ORDER_EAST_NORTH,
                AXIS_ORDER_NORTH_EAST });
        parametersInfo[14] = AXIS_ORDER = new WFSFactoryParam<String>(key,
                String.class, title, description, AXIS_ORDER_COMPLIANT,
                Parameter.OPTIONS, options, Parameter.LEVEL, "advanced");
    }
   
    public static final WFSFactoryParam<String> AXIS_ORDER_FILTER;
    static {
        String key = "WFSDataStoreFactory:AXIS_ORDER_FILTER";
        String title = "Axis Order Filter";
        String description = "Indicates axis order used by the remote WFS server for filters. It applies only to WFS 1.1.0 servers. "
                + "Default is the same as AXIS_ORDER";
        List<String> options = Arrays.asList(new String[] {
                AXIS_ORDER_COMPLIANT,
                AXIS_ORDER_EAST_NORTH,
                AXIS_ORDER_NORTH_EAST });
        parametersInfo[15] = AXIS_ORDER_FILTER = new WFSFactoryParam<String>(key,
                String.class, title, description, null, Parameter.OPTIONS, options, Parameter.LEVEL, "advanced");
    }
   
    public static final WFSFactoryParam<String> OUTPUTFORMAT;
    static {
        String key = "WFSDataStoreFactory:OUTPUTFORMAT";
        String title = "Outputformat";
        String description = "This allows the user to specify an outputFormat, different from the default one.";
   
        parametersInfo[16] = OUTPUTFORMAT = new WFSFactoryParam<String>(key,
                String.class, title, description, null, "advanced");
    }

    /**
     * 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();
        // TODO: let HTTPClient be configured for gzip
        // http.setTryGzip(tryGZIP);
        http.setUser(config.getUser());
        http.setPassword(config.getPassword());
        int timeoutMillis = config.getTimeoutMillis();
        http.setConnectTimeout(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;
    }

    /**
     * Unsupported operation, can't create a WFS service.
     *
     * @throws UnsupportedOperationException
     *             always, as this operation is not applicable to WFS.
     */
    @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).";
    }

    /**
     * Returns the set of parameter descriptors needed to connect to a WFS.
     *
     * @see org.geotools.data.DataStoreFactorySpi#getParametersInfo()
     * @see #URL
     * @see #NAMESPACE
     * @see #PROTOCOL
     * @see #USERNAME
     * @see #PASSWORD
     * @see #TIMEOUT
     * @see #BUFFER_SIZE
     * @see #TRY_GZIP
     * @see #LENIENT
     * @see #ENCODING
     * @see #FILTER_COMPLIANCE
     * @see #MAXFEATURES
     * @see #WFS_STRATEGY
     */
    @Override
    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>Either both {@link #USERNAME} and {@link #PASSWORD} are provided, or none.
     * </ul>
     * </p>
     */
    @Override
    public boolean canProcess(@SuppressWarnings("rawtypes") final Map params) {
        /*
         * check required params exist and are of the correct type
         */
        boolean canProcess = super.canProcess(params);
        if (!canProcess) {
            return false;
        }
        try {
            URL url = (URL) URL.lookUp(params);
            if (!"http".equalsIgnoreCase(url.getProtocol())
                    && !"https".equalsIgnoreCase(url.getProtocol())) {
                if (!Boolean.TRUE.equals(params.get("TESTING"))) {
                    Loggers.MODULE.finest("Can't process non http or https GetCapabilities URL's");
                    return false; // must be http or https since we use SimpleHTTPClient 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;
    }
   
    /**
     * 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);
    }

}
TOP

Related Classes of org.geotools.data.wfs.WFSDataStoreFactory$WFSFactoryParam

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.