/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package de.cismet.cismap.commons.featureservice.factory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateFilter;
import com.vividsolutions.jts.geom.Geometry;
import org.apache.commons.httpclient.methods.PostMethod;
import org.deegree.model.feature.Feature;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.feature.FeatureProperty;
import org.deegree.model.feature.GMLFeatureCollectionDocument;
import org.deegree.model.spatialschema.JTSAdapter;
import org.jdom.Element;
import org.w3c.dom.Document;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.swing.SwingWorker;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import de.cismet.cismap.commons.BoundingBox;
import de.cismet.cismap.commons.Crs;
import de.cismet.cismap.commons.CrsTransformer;
import de.cismet.cismap.commons.XBoundingBox;
import de.cismet.cismap.commons.features.WFSFeature;
import de.cismet.cismap.commons.featureservice.*;
import de.cismet.cismap.commons.featureservice.factory.FeatureFactory.TooManyFeaturesException;
import de.cismet.cismap.commons.wfs.WFSFacade;
import de.cismet.cismap.commons.wfs.capabilities.FeatureType;
import de.cismet.commons.security.AccessHandler.ACCESS_METHODS;
import de.cismet.security.WebAccessManager;
/**
* A FeatureFactory that creates WFSFeatures obtained from a Web Feature Service.<br/>
* The factory is non-caching, which means that each request to {@code createFeatures} leads to a new WFS request, even
* if the bounding box is the same. However, it is possible to obtain the features created by the latesd WFS request via
* the {@code getLastCreatedFeatures()} operation.
*
* @author Pascal Dihé
* @version $Revision$, $Date$
*/
public class WFSFeatureFactory extends DegreeFeatureFactory<WFSFeature, String> {
//~ Instance fields --------------------------------------------------------
protected String hostname = null;
protected FeatureType featureType;
private Crs crs;
private boolean reverseAxisOrder = false;
//~ Constructors -----------------------------------------------------------
/**
* private Vector<WFSFeature> wfsFeatureVector = new Vector(); private PostMethod httppost; private
* InputStreamReader reader;
*
* @param layerProperties DOCUMENT ME!
* @param hostname DOCUMENT ME!
* @param featureType wfsVersion DOCUMENT ME!
* @param crs DOCUMENT ME!
* @param styles DOCUMENT ME!
*/
public WFSFeatureFactory(final LayerProperties layerProperties,
final String hostname,
final FeatureType featureType,
final Crs crs,
final Map<String, LinkedList<org.deegree.style.se.unevaluated.Style>> styles) {
this(layerProperties, hostname, featureType, crs, styles, false);
}
/**
* private Vector<WFSFeature> wfsFeatureVector = new Vector(); private PostMethod httppost; private
* InputStreamReader reader;
*
* @param layerProperties DOCUMENT ME!
* @param hostname DOCUMENT ME!
* @param featureType wfsVersion DOCUMENT ME!
* @param crs DOCUMENT ME!
* @param styles DOCUMENT ME!
* @param reverseAxisOrder DOCUMENT ME!
*/
public WFSFeatureFactory(final LayerProperties layerProperties,
final String hostname,
final FeatureType featureType,
final Crs crs,
final Map<String, LinkedList<org.deegree.style.se.unevaluated.Style>> styles,
final boolean reverseAxisOrder) {
logger.info("initialising WFSFeatureFactory with hostname: '" + hostname + "'");
this.layerProperties = layerProperties;
this.hostname = hostname;
this.featureType = featureType;
this.crs = crs;
this.styles = styles;
this.reverseAxisOrder = reverseAxisOrder;
}
/**
* Creates a new WFSFeatureFactory object.
*
* @param wfsff DOCUMENT ME!
*/
protected WFSFeatureFactory(final WFSFeatureFactory wfsff) {
super(wfsff);
this.hostname = wfsff.hostname;
this.featureType = wfsff.featureType;
this.crs = wfsff.crs;
}
//~ Methods ----------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @param hostname DOCUMENT ME!
*/
public void setHostname(final String hostname) {
this.hostname = hostname;
}
/**
* TODO: Track Progress?
*
* @param query DOCUMENT ME!
* @param boundingBox DOCUMENT ME!
* @param workerThread DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws TooManyFeaturesException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
@Override
public Vector<WFSFeature> createFeatures(final String query,
final BoundingBox boundingBox,
final SwingWorker workerThread) throws TooManyFeaturesException, Exception {
return createFeatures_internal(query, boundingBox, workerThread, true);
}
/**
* TODO: Track Progress?
*
* @param query DOCUMENT ME!
* @param boundingBox DOCUMENT ME!
* @param workerThread DOCUMENT ME!
* @param saveAsLastCreated DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws TooManyFeaturesException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
private Vector<WFSFeature> createFeatures_internal(final String query,
final BoundingBox boundingBox,
final SwingWorker workerThread,
final boolean saveAsLastCreated) throws TooManyFeaturesException, Exception {
// check if canceled .......................................................
if (this.checkCancelled(workerThread, "createFeatures()")) {
return null;
}
// check if canceled .......................................................
final XBoundingBox bbox = new XBoundingBox(boundingBox.getX1(),
boundingBox.getY1(),
boundingBox.getX2(),
boundingBox.getY2(),
getCrs().getCode(),
getCrs().isMetric());
long start = System.currentTimeMillis();
final Vector<WFSFeature> features;
if (featuresAlreadyInMemory(bbox.getGeometry(), query)) {
features = createFeaturesFromMemory(query, bbox.getGeometry());
} else {
final WFSFacade facade = featureType.getWFSCapabilities().getServiceFacade();
final String postString = facade.setGetFeatureBoundingBox(
query,
bbox,
featureType,
getCrs().getCode(),
reverseAxisOrder);
featureSrid = CrsTransformer.extractSridFromCrs(WFSFacade.getOptimalCrsForFeature(
featureType,
getCrs().getCode()));
// check if canceled .......................................................
if (this.checkCancelled(workerThread, "creating post string")) {
return null;
}
// check if canceled .......................................................
if (logger.isDebugEnabled()) {
logger.debug("FRW[" + workerThread + "]: Host name: " + hostname + "\nWFS Query: \n" + postString);
}
final InputStream respIs = WebAccessManager.getInstance()
.doRequest(new URL(hostname), postString, ACCESS_METHODS.POST_REQUEST);
// check if canceled .......................................................
if (this.checkCancelled(workerThread, "executing http request")) {
return null;
}
// check if canceled .......................................................
logger.info("FRW[" + workerThread + "]: WFS request took " + (System.currentTimeMillis() - start) + " ms");
final InputStreamReader reader = new InputStreamReader(new BufferedInputStream(respIs));
// check if canceled .......................................................
if (this.checkCancelled(workerThread, "creating InputStreamReader")) {
return null;
}
// check if canceled .......................................................
final GMLFeatureCollectionDocument featureCollectionDocument = new GMLFeatureCollectionDocument();
final FeatureCollection featureCollection;
try {
start = System.currentTimeMillis();
// check if canceled .......................................................
if (this.checkCancelled(workerThread, "creating GMLFeatureCollectionDocument")) {
return null;
}
// check if canceled .......................................................
// debug
String res = "";
String tmp;
final BufferedReader br = new BufferedReader(reader);
while ((tmp = br.readLine()) != null) {
res += tmp;
}
if (logger.isDebugEnabled()) {
logger.debug("wfs response: " + res);
}
final StringReader re = new StringReader(res);
// debug
featureCollectionDocument.load(re, "http://dummyID");
// check if canceled .......................................................
if (this.checkCancelled(workerThread, "loading features")) {
return null;
}
// check if canceled .......................................................
// getFeatureCount() stimmt nicht mit der zahl der geparsten features überein!?
/*
* if (featureCollectionDocument.getFeatureCount() > this.getMaxFeatureCount()) { throw new
* TooManyFeaturesException("feature in feature document " + featureCollectionDocument.getFeatureCount()
* + " exceeds max feature count " + this.getMaxFeatureCount()); } else
*/
if (featureCollectionDocument.getFeatureCount() == 0) {
logger.warn("FRW[" + workerThread + "]: no features found before parsing");
// if(DEBUG)logger.debug(featureCollectionDocument.getAsString());
return null;
}
if (DEBUG) {
if (logger.isDebugEnabled()) {
logger.debug("FRW[" + workerThread + "]: parsing " + featureCollectionDocument
.getFeatureCount()
+ " features");
}
}
// StringWriter sw = new StringWriter();
// featureCollectionDocument.write(sw);
featureCollection = featureCollectionDocument.parse();
// check if canceled .......................................................
if (this.checkCancelled(workerThread, "parsing features")) {
return null;
}
// check if canceled .......................................................
if ((featureCollection.size() == 1) && (featureCollection.getFeature(0).getName() != null)
&& featureCollection.getFeature(0).getName().getLocalName().equals("ExceptionText")) {
logger.warn(
"The wfs response contains only one feature with the name ExceptionText. "
+ "So an error occured. Trying to extract the error message.");
try {
final String errorMessage = featureCollectionDocument.getRootElement()
.getFirstChild()
.getFirstChild()
.getTextContent();
throw new Exception(errorMessage);
} catch (NullPointerException e) {
logger.error("Cannot extract the error message from the wfs response.");
throw new Exception(
"The wfs replies with an Exception, but the error text cannot be extracted.");
}
}
logger.info("FRW[" + workerThread + "]: parsing " + featureCollection.size() + " features took "
+ (System.currentTimeMillis() - start) + " ms");
if (featureCollection.size() > this.getMaxFeatureCount()) {
throw new TooManyFeaturesException("FRW[" + workerThread + "]: feature in feature document "
+ featureCollection.size() + " exceeds max feature count " + this.getMaxFeatureCount());
} else if (featureCollection.size() == 0) {
logger.warn("FRW[" + workerThread + "]: no features found after parsing");
return null;
}
features = processFeatureCollection(
workerThread,
featureCollection.toArray(),
true);
} catch (Exception t) {
logger.error("FRW[" + workerThread + "]: error parsing features: " + t.getMessage(), t);
throw t;
}
}
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " saving LastCreatedFeatures ")) {
return null;
}
// check if thread is canceled .........................................
if (saveAsLastCreated) {
this.updateLastCreatedFeatures(features, boundingBox.getGeometry(featureSrid), query);
}
return features;
}
/**
* DOCUMENT ME!
*
* @param workerThread DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws TooManyFeaturesException DOCUMENT ME!
* @throws UnsupportedOperationException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
@Override
public Vector createAttributes(final SwingWorker workerThread) throws TooManyFeaturesException,
UnsupportedOperationException,
Exception {
throw new UnsupportedOperationException("LIW[" + workerThread
+ "]: WFSFeatureFactory does not support Attributes");
}
@Override
protected void initialiseFeature(final WFSFeature featureServiceFeature,
final Feature degreeFeature,
final boolean evaluateExpressions,
final int index) throws Exception {
// perform standard initialisation
featureServiceFeature.setLayerProperties(this.getLayerProperties());
// creating geometry
if (featureServiceFeature.getGeometry() == null) {
try {
Geometry geom = JTSAdapter.export(
degreeFeature.getGeometryPropertyValues()[geometryIndex]);
if (reverseAxisOrder) {
geom = reverseGeometryCoordinates(geom);
}
featureServiceFeature.setGeometry(geom);
} catch (Exception e) {
Geometry geom = JTSAdapter.export(
degreeFeature.getGeometryPropertyValues()[geometryIndex]);
if (reverseAxisOrder) {
geom = reverseGeometryCoordinates(geom);
}
featureServiceFeature.setGeometry(geom);
}
}
if ((featureServiceFeature.getGeometry() != null) && (featureSrid != null)) {
featureServiceFeature.getGeometry().setSRID(featureSrid);
}
// adding properties
final FeatureProperty[] featureProperties = degreeFeature.getProperties();
for (final FeatureProperty fp : featureProperties) {
if (featureServiceFeature.getProperty(fp.getName().getAsString()) == null) {
featureServiceFeature.addProperty(fp.getName().getAsString(), fp.getValue());
}
}
if (evaluateExpressions) {
this.evaluateExpressions(featureServiceFeature, index);
}
}
/**
* The axis order of the coordinates of the given geometry will be changed.
*
* @param g the geometry to change the axis order
*
* @return the given geometry with a changed axis order.
*/
private Geometry reverseGeometryCoordinates(final Geometry g) {
g.apply(new CoordinateFilter() {
@Override
public void filter(final Coordinate crdnt) {
final double newX = crdnt.y;
crdnt.y = crdnt.x;
crdnt.x = newX;
}
});
return g;
}
/**
* DOCUMENT ME!
*
* @param reader DOCUMENT ME!
* @param httppost DOCUMENT ME!
*/
protected void cleanup(InputStreamReader reader, PostMethod httppost) {
if (reader != null) {
try {
reader.close();
} catch (Exception silent) {
}
reader = null;
}
if (httppost != null) {
httppost.releaseConnection();
httppost = null;
}
}
/**
* DOCUMENT ME!
*
* @param degreeFeature DOCUMENT ME!
* @param index DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
@Override
protected WFSFeature createFeatureInstance(final Feature degreeFeature, final int index) throws Exception {
final WFSFeature f = new WFSFeature();
String name = null;
if ((layerProperties != null) && (layerProperties.getFeatureService() != null)) {
name = layerProperties.getFeatureService().getName();
}
if ((name == null) && (featureType != null) && (featureType.getName() != null)) {
name = featureType.getName().getPrefix() + ":" + featureType.getName().getLocalPart();
}
f.setSLDStyles(getStyle(name));
return f;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
protected boolean isGenerateIds() {
return false;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
public WFSFeatureFactory clone() {
return new WFSFeatureFactory(this);
}
/**
* DOCUMENT ME!
*
* @return the crs
*/
public Crs getCrs() {
return crs;
}
/**
* DOCUMENT ME!
*
* @param crs the crs to set
*/
public void setCrs(final Crs crs) {
this.crs = crs;
}
@Override
public int getFeatureCount(final String query, final BoundingBox bb) {
final XBoundingBox bbox = new XBoundingBox(bb.getX1(),
bb.getY1(),
bb.getX2(),
bb.getY2(),
getCrs().getCode(),
getCrs().isMetric());
final WFSFacade facade = featureType.getWFSCapabilities().getServiceFacade();
final Element queryElement = facade.getGetFeatureQuery(featureType);
String wfsQuery = query;
if (wfsQuery == null) {
wfsQuery = FeatureServiceUtilities.elementToString(queryElement);
}
final String postString = facade.setGetFeatureBoundingBox(
wfsQuery,
bbox,
featureType,
getCrs().getCode(),
reverseAxisOrder,
true);
featureSrid = CrsTransformer.extractSridFromCrs(WFSFacade.getOptimalCrsForFeature(
featureType,
getCrs().getCode()));
if (logger.isDebugEnabled()) {
logger.debug("Host name: " + hostname + "\nWFS Query: \n" + postString);
}
final long start = System.currentTimeMillis();
try {
final InputStream respIs = WebAccessManager.getInstance()
.doRequest(new URL(hostname), postString, ACCESS_METHODS.POST_REQUEST);
logger.info("WFS request took " + (System.currentTimeMillis() - start) + " ms");
final InputStreamReader reader = new InputStreamReader(new BufferedInputStream(respIs));
final GMLFeatureCollectionDocument featureCollectionDocument = new GMLFeatureCollectionDocument();
final FeatureCollection featureCollection;
// debug
String res = "";
String tmp;
final BufferedReader br = new BufferedReader(reader);
while ((tmp = br.readLine()) != null) {
res += tmp;
}
if (logger.isDebugEnabled()) {
logger.debug("wfs response: " + res);
}
final StringReader re = new StringReader(res);
final DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = fac.newDocumentBuilder();
final Document doc = builder.parse(new ByteArrayInputStream(res.getBytes("UTF-8")));
final String numberOfFeatures = doc.getDocumentElement().getAttribute("numberOfFeatures");
return Integer.parseInt(numberOfFeatures);
// featureCollectionDocument.load(re, "http://dummyID");
//
// return featureCollectionDocument.getFeatureCount();
} catch (Exception t) {
logger.error("error parsing features: " + t.getMessage(), t);
return 0;
}
}
@Override
public List<WFSFeature> createFeatures(final String query,
final BoundingBox boundingBox,
final SwingWorker workerThread,
final int offset,
final int limit,
final FeatureServiceAttribute[] orderBy) throws TooManyFeaturesException, Exception {
return createFeatures_internal(query, boundingBox, workerThread, false);
}
}