/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-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.ws;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.DataAccess;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureWriter;
import org.geotools.data.LockingManager;
import org.geotools.data.Query;
import org.geotools.data.ServiceInfo;
import org.geotools.data.Transaction;
import org.geotools.data.complex.xml.XmlResponse;
import org.geotools.data.complex.xml.XmlXpathFilterData;
import org.geotools.data.view.DefaultView;
import org.geotools.data.ws.protocol.ws.WSProtocol;
import org.geotools.data.ws.protocol.ws.WSResponse;
import org.geotools.feature.SchemaException;
import org.geotools.util.XmlXpathUtilites;
import org.geotools.util.logging.Logging;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.xml.sax.helpers.NamespaceSupport;
import org.jdom.Document;
/**
* An implementation of a DS that uses XML over HTTP.
* <p>
* Unlike normal DataStores that return features, this returns xml.
* </p>
*
* @author Russell Petty
*
* @source $URL$
* @version $Id$
*/
public final class WS_DataStore implements XmlDataStore {
private static final Logger LOGGER = Logging.getLogger("org.geotools.data.ws");
private static final XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
private static final SAXBuilder sax = new SAXBuilder();
private final WSProtocol wsProtocol;
//The hard limit is the maximum number of features we are allowed to call.
//If not set it has the value of zero, which means unlimited.
private int maxFeaturesHardLimit;
private Name name;
private NamespaceSupport namespaces;
private String itemXpath;
/**
* The WFS capabilities document.
*
* @param capabilities
*/
public WS_DataStore(final WSProtocol wsProtocol) {
if (wsProtocol == null) {
throw new NullPointerException("ws protocol");
}
this.wsProtocol = wsProtocol;
}
/**
* @see XmlDataStore#setMaxFeatures(Integer)
*/
public void setMaxFeatures(int maxFeatures) {
this.maxFeaturesHardLimit = maxFeatures;
}
/**
* @see XmlDataStore#getMaxFeatures()
*/
public int getMaxFeatures() {
return this.maxFeaturesHardLimit;
}
/**
* @see XmlDataStore#getInfo()
*/
public ServiceInfo getInfo() {
throw new UnsupportedOperationException("DS not supported!");
}
public SimpleFeatureType getSchema(final String prefixedTypeName) throws IOException {
throw new UnsupportedOperationException("DS not supported!");
}
/**
* @see DataAccess#getSchema(Name)
* @see #getSchema(String)
*/
public SimpleFeatureType getSchema(Name name) throws IOException {
throw new UnsupportedOperationException("DS not supported!");
}
/**
* @see DataAccess#getNames()
*/
public List<Name> getNames() throws IOException {
throw new UnsupportedOperationException("DS not supported!");
}
/**
* @see org.geotools.data.DataStore#getTypeNames()
*/
public String[] getTypeNames() throws IOException {
throw new UnsupportedOperationException("DS not supported!");
}
/**
* @see org.geotools.data.DataStore#dispose()
*/
public void dispose() {
// do nothing
}
/**
* @see org.geotools.data.DataStore#getFeatureReader(org.geotools.data.Query,
* org.geotools.data.Transaction)
*/
public XmlResponse getXmlReader(Query query) throws IOException {
if (Filter.EXCLUDE.equals(query.getFilter())) {
return null; //empty response
}
Query callQuery = new DefaultQuery(query);
Filter[] filters = wsProtocol.splitFilters(query.getFilter());
Filter supportedFilter = filters[0];
Filter postFilter = filters[1];
LOGGER.fine("Supported filter: " + supportedFilter);
LOGGER.fine("Unupported filter: " + postFilter);
((DefaultQuery) callQuery).setFilter(supportedFilter);
int maxFeatures = getMaxFeatures(query);
//Only set maxFeatures if the back end can handle the filter.
//If it cannot, we don't want to limit the number of responses
//before filtering on the server. However a hard limit may have been specified.
if (Filter.INCLUDE.equals(postFilter)) {
((DefaultQuery) callQuery).setMaxFeatures(maxFeatures);
} else {
((DefaultQuery) callQuery).setMaxFeatures(maxFeaturesHardLimit);
}
WSResponse response = wsProtocol.issueGetFeature(callQuery);
Document doc = getXmlResponse(response);
List<Integer> validFeatureIndex = determineValidFeatures(postFilter, doc, maxFeatures);
return new XmlResponse(doc, validFeatureIndex);
}
private List<Integer> determineValidFeatures(Filter postFilter, Document doc, int maxFeatures) {
int nodeCount = XmlXpathUtilites.countXPathNodes(namespaces, itemXpath, doc);
List<Integer> validFeatureIndex = null;
if(Filter.INCLUDE.equals(postFilter)) {
validFeatureIndex = new ArrayList<Integer>(nodeCount);
//add all features to index--but no more than specified by maxFeatures.
int maxNode = nodeCount < maxFeatures ? nodeCount : maxFeatures;
for(int i = 1; i <= maxNode; i++) {
validFeatureIndex.add(i);
}
} else {
validFeatureIndex = new ArrayList<Integer>();
int nodeIndex = 1;
while (nodeIndex <= nodeCount && validFeatureIndex.size() <= maxFeatures) {
XmlXpathFilterData peek = new XmlXpathFilterData(namespaces, doc, nodeIndex, itemXpath);
if (postFilter.evaluate(peek)) {
validFeatureIndex.add(nodeIndex);
}
nodeIndex++;
}
}
return validFeatureIndex;
}
private Document getXmlResponse(WSResponse response) throws IOException {
Document doc = null;
try {
doc = sax.build(response.getInputStream());
} catch (JDOMException e1) {
throw new RuntimeException("error reading xml from http", e1);
}
if(LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer(out.outputString(doc));
}
return doc;
}
/**
* @see org.geotools.data.DataStore#getFeatureReader(org.geotools.data.Query,
* org.geotools.data.Transaction)
*/
public FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(Query query,
final Transaction transaction) throws IOException {
throw new UnsupportedOperationException("DS not supported!");
}
/**
* @see org.geotools.data.DataStore#getFeatureSource(java.lang.String)
*/
public WSFeatureSource getFeatureSource(final String typeName) throws IOException {
return new WSFeatureSource(this, typeName, name);
}
/**
* @return {@code null}, no lock support so far
* @see org.geotools.data.DataStore#getLockingManager()
*/
public LockingManager getLockingManager() {
throw new UnsupportedOperationException("DS not supported!");
}
/**
* @see org.geotools.data.DataStore#getView(org.geotools.data.Query)
* @see DefaultView
*/
public FeatureSource<SimpleFeatureType, SimpleFeature> getView(final Query query)
throws IOException, SchemaException {
throw new UnsupportedOperationException("DS not supported!");
}
/**
* Not supported.
*
* @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String,
* org.opengis.filter.Filter, org.geotools.data.Transaction)
* @throws UnsupportedOperationException
* always since this operation does not apply to a WFS backend
*/
public FeatureWriter<SimpleFeatureType, SimpleFeature> getFeatureWriter(String typeName,
Filter filter, Transaction transaction) throws IOException {
throw new UnsupportedOperationException("This is a read only DataStore");
}
/**
* Not supported.
*
* @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String,
* org.geotools.data.Transaction)
* @throws UnsupportedOperationException
* always since this operation does not apply to a WFS backend
*/
public FeatureWriter<SimpleFeatureType, SimpleFeature> getFeatureWriter(String typeName,
Transaction transaction) throws IOException {
throw new UnsupportedOperationException("This is a read only DataStore");
}
/**
* Not supported.
*
* @see org.geotools.data.DataStore#getFeatureWriterAppend(java.lang.String,
* org.geotools.data.Transaction)
* @throws UnsupportedOperationException
* always since this operation does not apply to a WFS backend
*/
public FeatureWriter<SimpleFeatureType, SimpleFeature> getFeatureWriterAppend(String typeName,
Transaction transaction) throws IOException {
throw new UnsupportedOperationException("This is a read only DataStore");
}
public FeatureSource<SimpleFeatureType, SimpleFeature> getFeatureSource(Name typeName)
throws IOException {
// this is a hack as this datastore only returns one type of response.
//set the name to what is passed in as it maybe needed later.
this.name = typeName;
return getFeatureSource(typeName.getLocalPart());
}
/**
* @see DataAccess#updateSchema(Name, org.opengis.feature.type.FeatureType)
* @throws UnsupportedOperationException
* always since this operation does not apply to a WFS backend
*/
public void updateSchema(Name typeName, SimpleFeatureType featureType) throws IOException {
throw new UnsupportedOperationException("WS does not support update schema");
}
/**
* @see org.geotools.data.DataStore#updateSchema(java.lang.String,
* org.opengis.feature.simple.SimpleFeatureType)
* @throws UnsupportedOperationException
* always since this operation does not apply to a WFS backend
*/
public void updateSchema(String typeName, SimpleFeatureType featureType) throws IOException {
throw new UnsupportedOperationException("WS does not support update schema");
}
/**
* @see org.geotools.data.DataStore#createSchema(org.opengis.feature.simple.SimpleFeatureType)
* @throws UnsupportedOperationException
* always since this operation does not apply to a WFS backend
*/
public void createSchema(SimpleFeatureType featureType) throws IOException {
throw new UnsupportedOperationException("WS DataStore does not support createSchema");
}
/**
* @param query
* @return the number of features returned by a GetFeature?resultType=hits request, or {@code
* -1} if not supported
*/
public int getCount(final Query query) {
return -1;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("WSDataStore[");
sb.append(", max features=").append(
maxFeaturesHardLimit == 0 ? "not set" : String
.valueOf(maxFeaturesHardLimit));
sb.append("]");
return sb.toString();
}
protected int getMaxFeatures(Query query) {
int maxFeaturesDataStoreLimit = getMaxFeatures();
int queryMaxFeatures = query.getMaxFeatures();
int maxFeatures = Query.DEFAULT_MAX;
if (Query.DEFAULT_MAX != queryMaxFeatures) {
maxFeatures = queryMaxFeatures;
}
if (maxFeaturesDataStoreLimit > 0) {
maxFeatures = Math.min(maxFeaturesDataStoreLimit, maxFeatures);
}
return maxFeatures;
}
public Name getName() {
return name;
}
public void setNamespaces(org.xml.sax.helpers.NamespaceSupport namespaces) {
this.namespaces = namespaces;
}
public void setItemXpath(String itemXpath) {
this.itemXpath = itemXpath;
}
}