/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
/*
* Copyright (C) 2010 therter
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.cismet.cismap.commons.wfs;
import com.vividsolutions.jts.geom.Geometry;
import org.apache.log4j.Logger;
import org.jdom.Attribute;
import org.jdom.Content;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.xml.namespace.QName;
import de.cismet.cismap.commons.CrsTransformer;
import de.cismet.cismap.commons.XBoundingBox;
import de.cismet.cismap.commons.featureservice.FeatureServiceAttribute;
import de.cismet.cismap.commons.interaction.CismapBroker;
import de.cismet.cismap.commons.wfs.capabilities.FeatureType;
import de.cismet.cismap.commons.wfs.capabilities.WFSCapabilities;
import de.cismet.commons.security.AccessHandler.ACCESS_METHODS;
import de.cismet.security.WebAccessManager;
/**
* This class invokes Web Feature services and creates WFS requests. This class considers the WFS version while creating
* WFS requests and should be the only class that is used to create and modify WFS request. The currently supported WFS
* versions are 1.0.0 and 1.1.0
*
* @author therter
* @version $Revision$, $Date$
*/
public class WFSFacade {
//~ Static fields/initializers ---------------------------------------------
private static final Logger logger = Logger.getLogger(WFSFacade.class);
/** WFS namespace-contant. */
private static final Namespace WFS = Namespace.getNamespace("wfs", "http://www.opengis.net/wfs"); // NOI18N
/** OGC namespace-contant. */
private static final Namespace OGC = Namespace.getNamespace("ogc", "http://www.opengis.net/ogc"); // NOI18N
/** GML namespace-contant. */
private static final Namespace GML = Namespace.getNamespace("gml", "http://www.opengis.net/gml"); // NOI18N
/** OWS namespace-contant. */
private static final Namespace OWS = Namespace.getNamespace("ows", "http://www.opengis.net/ows"); // NOI18N
/** XSD namespace-contant. */
private static final Namespace xsd = Namespace.getNamespace("xsd", "http://www.w3.org/2001/XMLSchema"); // NOI18N
/** name of the CismapQuery-element. */
private static final String GET_FEATURE_100 = "getFeatureQuery100"; // NOI18N
private static final String GET_FEATURE_110 = "getFeatureQuery110"; // NOI18N
private static final String CISMET_DESCRIBE_FEATURE_TYPE = "CismapDescribeFeatureType"; // NOI18N
private static final String DESCRIBE_FEATURE_TYPE = "DescribeFeatureType"; // NOI18N
private static final String GET_FEATURE = "GetFeature"; // NOI18N
private static final String QUERY = "Query"; // NOI18N
private static final String FILTER = "Filter"; // NOI18N
private static final String BBOX = "BBOX"; // NOI18N
private static final String TYPENAME = "TypeName"; // NOI18N
private static final String PROPERTY_NAME = "PropertyName"; // NOI18N
private static final String TYPE_NAME_ATTR = "typeName"; // NOI18N
private static final String VERSION_ATTR = "version"; // NOI18N
private static final String MAX_FEATURES_ATTR = "maxFeatures"; // NOI18N
private static final URL XML_FILE = WFSFacade.class.getResource("wfs.xml"); // TODO Auslagern//NOI18N
public static final String CISMAP_BOUNDING_BOX_AS_GML_PLACEHOLDER = "<cismapBoundingBoxAsGmlPlaceholder />";
public static final String CISMAP_RESULT_TYPE_PLACEHOLDER = "cismapResultTypePlaceholder";
public static final String SRS_NAME_PLACEHOLDER = "SRSNAME_PLACEHOLDER";
//~ Instance fields --------------------------------------------------------
private WFSCapabilities cap;
private ResponseParserFactory parserFactory;
private Element rootNode;
//~ Constructors -----------------------------------------------------------
/**
* this constructor should only be used by the WFSCapabilities classes. Because every WFSFacade is bounded to a
* capabilities class and should only be used with features of the given capabilities document.
*
* @param cap DOCUMENT ME!
* @param parserFactory DOCUMENT ME!
*/
public WFSFacade(final WFSCapabilities cap, final ResponseParserFactory parserFactory) {
this.cap = cap;
this.parserFactory = parserFactory;
try {
final SAXBuilder builder = new SAXBuilder();
rootNode = builder.build(XML_FILE).getRootElement();
} catch (Exception ex) {
logger.error("Error during parsing of the CismapXML-Files", ex); // NOI18N
}
}
//~ Methods ----------------------------------------------------------------
/**
* sends a describeFeatureType request to the corresponding wfs.
*
* @param feature the feature, the request should be sent for
*
* @return the wfs response as FeatureTypeDescription object
*
* @throws IOException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
public FeatureTypeDescription describeFeatureType(final FeatureType feature) throws IOException, Exception {
final String version = cap.getVersion();
final String request;
final XMLOutputter out = new XMLOutputter();
final QName featureName = feature.getName();
final Element requestElement = (Element)rootNode.getChild(CISMET_DESCRIBE_FEATURE_TYPE)
.getChild(DESCRIBE_FEATURE_TYPE, WFS)
.clone();
// set version
if (!version.equals("1.0.0") && !version.equals("1.1.0")) { // NOI18N
logger.error("unknown service version used: " + version + " service" // NOI18N
+ cap.getURL() + ". Try to use a 1.1.0 request with version string" + version); // NOI18N
}
requestElement.getAttribute(VERSION_ATTR).setValue(version); // NOI18N
// set namespace
if ((featureName.getPrefix() != null) && (featureName.getNamespaceURI() != null)) {
final Namespace ns = Namespace.getNamespace(featureName.getPrefix(), featureName.getNamespaceURI());
requestElement.addNamespaceDeclaration(ns);
}
// set feature type
requestElement.getChild(TYPENAME, WFS).setText(feature.getPrefixedNameString());
request = out.outputString(requestElement);
final String response = postRequest(cap.getURL(), request);
try {
return parserFactory.getFeatureTypeDescription(response, feature);
} catch (Exception e) {
logger.error("Error while parsing the response of a describeFeature request.", e); // NOI18N
return null;
}
}
/**
* Returns a template for a getFeature request. The template contains placeholders for the bounding box, the srs
* name and the property names. To replace the placeholders, use the following static methods:<br />
* setGetFeatureBoundingBox setGeometry changePropertyNames setMaxFeatureCount
*
* @param feature the request template will be created for this feature
*
* @return a getFeature request template. The WFS version will be considered.
*/
public Element getGetFeatureQuery(final FeatureType feature) {
final String version = cap.getVersion();
Element request;
if (version.equals("1.0.0")) { // NOI18N
request = getFeature100Request(feature);
} else if (version.equals("1.1.0")) { // NOI18N
request = getFeature110Request(feature);
} else {
logger.error("unknown service version used: " + version + " service" // NOI18N
+ cap.getURL() + ". Try to use a version 1.1.0 request"); // NOI18N
request = getFeature110Request(feature);
}
return request;
}
/**
* Set the bounding box and the srs of the given getFeature request and returns the resulting request as string.
*
* @param query the getFeature request template.
* @param bbox the bounding box that should be used in the getFeature request
* @param feature the type of the feature from the given query
* @param mapCrs the crs of the map, the features should be shown on
* @param reverseAxisOrder if true, the axis order will be lat/lon
*
* @return the new getFeature request
*/
public String setGetFeatureBoundingBox(final String query,
final XBoundingBox bbox,
final FeatureType feature,
final String mapCrs,
final boolean reverseAxisOrder) {
return setGetFeatureBoundingBox(query, bbox, feature, mapCrs, reverseAxisOrder, false);
}
/**
* Set the bounding box and the srs of the given getFeature request and returns the resulting request as string.
*
* @param query the getFeature request template.
* @param bbox the bounding box that should be used in the getFeature request
* @param feature the type of the feature from the given query
* @param mapCrs the crs of the map, the features should be shown on
* @param reverseAxisOrder if true, the axis order will be lat/lon
* @param onlyFeatureCount the resultType attribute will be set to hits, iif onlyFeatureCount is true
*
* @return the new getFeature request
*/
public String setGetFeatureBoundingBox(final String query,
final XBoundingBox bbox,
final FeatureType feature,
final String mapCrs,
final boolean reverseAxisOrder,
final boolean onlyFeatureCount) {
String request;
String envelope;
final String crs = getOptimalCrsForFeature(feature, mapCrs);
final Geometry geom = CrsTransformer.transformToGivenCrs(bbox.getGeometry(), crs);
final XBoundingBox tbbox = new XBoundingBox(geom);
final String resultType = (onlyFeatureCount ? "hits" : "results");
if (logger.isDebugEnabled()) {
logger.debug("optimal crs: " + crs);
}
if ((cap.getVersion() != null) && cap.getVersion().equals("1.0.0")) { // NOI18N
envelope = "<gml:Box><gml:coord><gml:X>" + tbbox.getX1() + "</gml:X><gml:Y>" + tbbox.getY1() // NOI18N
+ "</gml:Y></gml:coord>" + "<gml:coord><gml:X>" + tbbox.getX2() // NOI18N
+ "</gml:X><gml:Y>" + tbbox.getY2() + "</gml:Y></gml:coord>" + "</gml:Box>"; // NOI18N
} else if ((cap.getVersion() != null) && cap.getVersion().equals("1.1.0")) { // NOI18N
if (reverseAxisOrder) {
envelope = "<gml:Envelope><gml:lowerCorner>" + tbbox.getY1() // NOI18N
+ " " + tbbox.getX1() + "</gml:lowerCorner>" + "<gml:upperCorner>" // NOI18N
+ tbbox.getY2() + " " + tbbox.getX2() + "</gml:upperCorner>" + "</gml:Envelope>"; // NOI18N
} else {
envelope = "<gml:Envelope><gml:lowerCorner>" + tbbox.getX1() // NOI18N
+ " " + tbbox.getY1() + "</gml:lowerCorner>" + "<gml:upperCorner>" // NOI18N
+ tbbox.getX2() + " " + tbbox.getY2() + "</gml:upperCorner>" + "</gml:Envelope>"; // NOI18N
}
} else {
logger.error("unknown service version used: " + cap.getVersion() // NOI18N
+ ". Try to use a version 1.1.0 request"); // NOI18N
if (reverseAxisOrder) {
envelope = "<gml:Envelope><gml:lowerCorner>" + tbbox.getY1() // NOI18N
+ " " + tbbox.getX1() + "</gml:lowerCorner>" + "<gml:upperCorner>" // NOI18N
+ tbbox.getY2() + " " + tbbox.getX2() + "</gml:upperCorner>" + "</gml:Envelope>"; // NOI18N
} else {
envelope = "<gml:Envelope><gml:lowerCorner>" + tbbox.getX1() // NOI18N
+ " " + tbbox.getY1() + "</gml:lowerCorner>" + "<gml:upperCorner>" // NOI18N
+ tbbox.getX2() + " " + tbbox.getY2() + "</gml:upperCorner>" + "</gml:Envelope>"; // NOI18N
}
}
request = query.toString().replaceAll(CISMAP_BOUNDING_BOX_AS_GML_PLACEHOLDER, envelope);
request = request.replaceAll(SRS_NAME_PLACEHOLDER, crs);
request = request.replaceAll(CISMAP_RESULT_TYPE_PLACEHOLDER, resultType);
return request;
}
/**
* DOCUMENT ME!
*
* @param feature DOCUMENT ME!
* @param mapSrs DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static String getOptimalCrsForFeature(final FeatureType feature, final String mapSrs) {
String desiredSrs = mapSrs;
if (desiredSrs == null) {
desiredSrs = CismapBroker.getInstance().getSrs().getCode();
}
for (final String tmpSrs : feature.getSupportedSRS()) {
if (tmpSrs.equals(desiredSrs)) {
return tmpSrs;
}
}
if (feature.getDefaultSRS() != null) {
return feature.getDefaultSRS();
} else {
return mapSrs;
}
}
/**
* DOCUMENT ME!
*
* @param request DOCUMENT ME!
* @param cap DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static FeatureType extractRequestedFeatureType(final String request, final WFSCapabilities cap) {
try {
final SAXBuilder builder = new SAXBuilder();
final Element root = builder.build(new StringReader(request)).getRootElement();
final Attribute att = root.getChild(QUERY, WFS).getAttribute(TYPE_NAME_ATTR);
if (att != null) {
return extractFeatureTypeFromCap(att.getValue(), cap);
}
} catch (Exception ex) {
logger.error("Error during parsing of the wfs request. The feature type cannot be recognized.", ex); // NOI18N
}
logger.error(
"The feature type cannot be extracted from the wfs request. So the supported crs cannot be determined exactly."); // NOI18N
return null;
}
/**
* DOCUMENT ME!
*
* @param featureName DOCUMENT ME!
* @param cap DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static FeatureType extractFeatureTypeFromCap(final String featureName, final WFSCapabilities cap) {
try {
if (featureName != null) {
final Iterator<FeatureType> featureIterator = cap.getFeatureTypeList().iterator();
final String localFeatureName = featureName.substring(featureName.indexOf(":") + 1);
while (featureIterator.hasNext()) {
final FeatureType feature = featureIterator.next();
if (feature.getName().getLocalPart().equals(localFeatureName)) {
return feature;
}
}
}
} catch (Exception ex) {
logger.error("Error while discovering the feature list.", ex); // NOI18N
}
logger.error("The feature type with the name " + featureName + " cannot be found."); // NOI18N
return null;
}
/**
* Replaces the current max feature count with the given max feature count in the given getFeature request.
*
* @param wfsQuery query getFeature request as JDOM-element
* @param maxFeatureCount properties collection of strings
* @param version the version of the wfs request that is contained in the query paramaeter
*/
public static void setMaxFeatureCount(final Element wfsQuery, final int maxFeatureCount, String version) {
if (version == null) {
logger.error("version string is null. Try to use version 1.1.0", new Exception());
version = "1.1.0";
}
if (!(version.equals("1.0.0") || version.equals("1.1.0"))) {
logger.error("unknown wfs version: " + version // NOI18N
+ ". Try to handle this version like version 1.1.0"); // NOI18N
}
if (logger.isDebugEnabled()) {
logger.debug("setting may maxFeatureCount of WFS Query to " + maxFeatureCount); // NOI18N
}
if (wfsQuery != null) {
wfsQuery.setAttribute(MAX_FEATURES_ATTR, String.valueOf(maxFeatureCount));
} else {
logger.warn("could not set maxFeatureCount, query not yet initialised"); // NOI18N
}
}
/**
* Replaces the current property names with the given property names in the getFeature query.
*
* @param query getFeature request as JDOM-element
* @param properties the property names
* @param version the version of the wfs request that is contained in the query paramaeter
*/
public static void changePropertyNames(final Element query, final Collection<String> properties, String version) {
if (version == null) {
logger.error("version string is null. Try to use version 1.1.0", new Exception());
version = "1.1.0";
}
if (!(version.equals("1.0.0") || version.equals("1.1.0"))) { // NOI18N
logger.error("unknown wfs version: " + version // NOI18N
+ ". Try to handle this version like version 1.1.0"); // NOI18N
}
query.getChild(QUERY, WFS).removeChildren(PROPERTY_NAME, WFS);
final Collection<Content> propertyElements = new ArrayList<Content>();
for (final String s : properties) {
final Element tmp = new Element(PROPERTY_NAME, WFS);
tmp.setText(s);
propertyElements.add(tmp);
}
if (!propertyElements.isEmpty()) {
query.getChild(QUERY, WFS).addContent(0, propertyElements);
}
}
/**
* Replaces the geometry-property in the given getFeature query template.
*
* @param query getFeature request as JDOM-element
* @param geoName the name of the geometry
* @param version the version of the wfs request that is contained in the query paramaeter
*/
public static void setGeometry(final Element query, final String geoName, String version) {
if (version == null) {
logger.error("version string is null. Try to use version 1.1.0", new Exception());
version = "1.1.0";
}
if (!(version.equals("1.0.0") || version.equals("1.1.0"))) { // NOI18N
logger.error("unknown wfs version: " + version // NOI18N
+ ". Try to handle this version like version 1.1.0"); // NOI18N
}
query.getChild(QUERY, WFS)
.getChild(FILTER, OGC)
.getChild(BBOX, OGC)
.getChild(PROPERTY_NAME, OGC)
.setText(geoName);
}
/**
* Creates a getFeature request template for the given feature type. The request template is conform to a WFS of the
* version 1.0.0
*
* @param feature DOCUMENT ME!
*
* @return null, if the given feature has no geometry attribute
*/
private Element getFeature100Request(final FeatureType feature) {
final Element requestElement = (Element)rootNode.getChild(GET_FEATURE_100).getChild(GET_FEATURE, WFS).clone();
final QName featureName = feature.getName();
if (feature.getNameOfGeometryAtrtibute() == null) {
return null;
}
// set the feature name
final Element query = requestElement.getChild(QUERY, WFS);
query.getAttribute(TYPE_NAME_ATTR).setValue(feature.getPrefixedNameString());
if ((featureName.getNamespaceURI() != null) && (featureName.getPrefix() != null)) {
final Namespace ns = Namespace.getNamespace(featureName.getPrefix(), featureName.getNamespaceURI());
requestElement.addNamespaceDeclaration(ns);
}
// set geometry
final Element propertyElement = query.getChild(FILTER, OGC).getChild(BBOX, OGC).getChild(PROPERTY_NAME, OGC);
propertyElement.setText(feature.getNameOfGeometryAtrtibute());
final Collection<Content> propertyElements = new ArrayList<Content>();
for (final FeatureServiceAttribute attribute : feature.getFeatureAttributes()) {
final Element tmp = new Element(PROPERTY_NAME, WFS);
tmp.setText(attribute.getName());
propertyElements.add(tmp);
}
if (!propertyElements.isEmpty()) {
query.addContent(0, propertyElements);
}
return requestElement;
}
/**
* Creates a getFeature request template for the given feature type. The request template is conform to a WFS of the
* version 1.1.0
*
* @param feature DOCUMENT ME!
*
* @return null, if the given property has no geometry attribute
*/
private Element getFeature110Request(final FeatureType feature) {
final Element requestElement = (Element)rootNode.getChild(GET_FEATURE_110).getChild(GET_FEATURE, WFS).clone();
final QName featureName = feature.getName();
if (feature.getNameOfGeometryAtrtibute() == null) {
return null;
}
// set the feature name
final Element query = requestElement.getChild(QUERY, WFS);
query.getAttribute(TYPE_NAME_ATTR).setValue(feature.getPrefixedNameString());
if ((featureName.getNamespaceURI() != null) && (featureName.getPrefix() != null)) {
final Namespace ns = Namespace.getNamespace(featureName.getPrefix(), featureName.getNamespaceURI());
requestElement.addNamespaceDeclaration(ns);
}
// set geometry
final Element propertyElement = query.getChild(FILTER, OGC).getChild(BBOX, OGC).getChild(PROPERTY_NAME, OGC);
propertyElement.setText(feature.getNameOfGeometryAtrtibute());
final Collection<Content> propertyElements = new ArrayList<Content>();
for (final FeatureServiceAttribute attribute : feature.getFeatureAttributes()) {
final Element tmp = new Element(PROPERTY_NAME, WFS);
tmp.setText(attribute.getName());
propertyElements.add(tmp);
}
if (!propertyElements.isEmpty()) {
query.addContent(0, propertyElements);
}
return requestElement;
}
/**
* Sends a http Post request to the server and returns the response of the server as String.
*
* @param serverURL URL of the server
* @param request Request-String
*
* @return server response as string or null, if an error occurs
*
* @throws IOException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
private String postRequest(final URL serverURL, final String request) throws IOException, Exception {
logger.info("post request (" + serverURL + ")"); // NOI18N
final InputStream resp = WebAccessManager.getInstance()
.doRequest(serverURL, request, ACCESS_METHODS.POST_REQUEST);
final char[] buffer = new char[256];
final StringBuilder response = new StringBuilder();
final BufferedReader br = new BufferedReader(new InputStreamReader(resp));
int count = br.read(buffer, 0, buffer.length);
while (count != -1) {
response.append(buffer, 0, count);
count = br.read(buffer, 0, buffer.length);
}
br.close();
return response.toString();
}
}