/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2011, 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.sfs;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import org.apache.commons.codec.binary.Base64;
import org.geotools.data.store.ContentDataStore;
import org.geotools.data.store.ContentEntry;
import org.geotools.data.store.ContentFeatureSource;
import org.geotools.feature.NameImpl;
import org.geotools.filter.FilterCapabilities;
import org.json.simple.JSONArray;
import org.json.simple.parser.JSONParser;
import org.opengis.feature.type.Name;
import org.opengis.filter.And;
import org.opengis.filter.ExcludeFilter;
import org.opengis.filter.IncludeFilter;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.Intersects;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Envelope;
/**
* This DataStore is based on OpenDataStore protocol using GeoJSON as the
* encoding format loosely based on RESTFUL principles. This code expanded on
* from GeoREST in Unsupported Modules.
* @author
*
*
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/unsupported/sfs/src/main/java/org/geotools/data/sfs/SFSDataStore.java $
*/
public class SFSDataStore extends ContentDataStore {
/**
* The store filter capabilities
* @return The filter capabilities, never <code>null</code>.
*/
static FilterCapabilities ODS_FILTER_CAPABILITIES = new FilterCapabilities() {
{
addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS);
addType(And.class);
// TODO: check these two filters are actually supported in the encoder
addType(BBOX.class);
addType(Intersects.class);
addType(IncludeFilter.class);
addType(ExcludeFilter.class);
addType(PropertyIsLike.class);
}
};
protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.data.simplefeatureservice");
String baseURL;
Map<Name, SFSLayer> layers = new LinkedHashMap<Name, SFSLayer>();
String user;
String password;
int timeout;
/**
* Constructor -- it takes in the base URl and then appends "/capabilities"
* to pull the list of available layers
* @param URL
*
*/
public SFSDataStore(URL fnURL, String namespaceURI) throws IOException {
String strURL = fnURL.toString();
this.baseURL = strURL + (strURL.endsWith("/") ? "" : "/");
if(baseURL.endsWith("capabilities/")) {
baseURL = baseURL.substring(0, baseURL.length() - "capabilities/".length());
}
this.namespaceURI = namespaceURI;
processCapabilities();
}
/**
* Constructor -- it takes in the base URl and then appends "/capabilities"
* to pull the list of available layers
* @param URL
*
*/
public SFSDataStore(URL fnURL, String namespaceURI, String user, String password, int timeout) throws IOException {
String strURL = fnURL.toString();
this.baseURL = strURL + (strURL.endsWith("/") ? "" : "/");
if(baseURL.endsWith("capabilities/")) {
baseURL = baseURL.substring(0, baseURL.length() - "capabilities/".length());
}
this.namespaceURI = namespaceURI;
this.user = user;
this.password = password;
this.timeout = timeout;
processCapabilities();
}
/**
* Constructor -- it simply uses the json response from the server to extract
* the layer information. Only used for testing, not exposing it to the whole world
* @param String
*
*/
SFSDataStore(String json, String namespaceURI) throws IOException {
this.namespaceURI = namespaceURI;
processCapabilities(json);
}
/**
* It processes the incoming URL and gets the response in the form of stream.
* It then converts the stream into a string(it should be in json format)
* @param
*
*/
public final void processCapabilities() throws IOException {
processCapabilities(resourceToString("capabilities", null));
}
/**
* This method processes the JSON from the server and extracts layer names
* and populates typeNames
* @param capabilitiesJSON
*
*/
final void processCapabilities(String capabilitiesJSON) throws IOException {
JSONParser parser = new JSONParser();
try {
Object obj = parser.parse(capabilitiesJSON);
JSONArray array = (JSONArray) obj;
Iterator itr = array.iterator();
while (itr.hasNext()) {
Map tmpMap = (HashMap) itr.next();
String strName = ((String) tmpMap.get("name")).trim();
Name name = new NameImpl(namespaceURI, strName);
boolean xyOrder = (!tmpMap.containsKey("axisorder")) ? true : (tmpMap.get("axisorder").equals("xy"));
String strCRS = null;
CoordinateReferenceSystem crs = null;
if (tmpMap.containsKey("crs")) {
strCRS = ((String) tmpMap.get("crs")).trim();
crs = SFSDataStoreUtil.decodeXY(strCRS);
}
Envelope envelope = null;
if (tmpMap.containsKey("bbox")) {
JSONArray boundingArray = (JSONArray) tmpMap.get("bbox");
if (!xyOrder) {
SFSDataStoreUtil.flipYXInsideTheBoundingBox(boundingArray);
}
envelope = new Envelope(
((Number) boundingArray.get(0)).doubleValue(),
((Number) boundingArray.get(2)).doubleValue(),
((Number) boundingArray.get(1)).doubleValue(),
((Number) boundingArray.get(3)).doubleValue());
}
SFSLayer layer = new SFSLayer(name, xyOrder, strCRS, crs, envelope);
layers.put(name, layer);
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception occurred while parsing the capabilities", e);
throw (IOException) new IOException().initCause(e);
}
}
/**
* Return the Layer Names
* @param
* @return typeNames
* @throws IOException
*
*/
public List<Name> createTypeNames() throws IOException {
return new ArrayList<Name>(layers.keySet());
}
/**
* Call FeatureSource
* @param ContentEntry
* @return ContentFeatureSource
* @throws IOException
*
*/
protected ContentFeatureSource createFeatureSource(ContentEntry entry) throws IOException {
return new SFSFeatureSource(entry);
}
/**
* Returns the layer definition for the given name
* @param name
* @return
*/
SFSLayer getLayer(Name name) {
return layers.get(name);
}
/**
* This method requests the response from the URL either through GET/POST
* and then converts the response to string and returns the string
* fnData is URL encoded string
* The request is going to be a POST one if postData is not null, a GET otherwise
* @param is
* @return String
* @throws IOException
*/
String resourceToString(String resource, String postData) throws IOException {
InputStream is = resourceToStream(resource, postData);
if (is != null) {
StringBuilder sb = new StringBuilder();
String line;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, SFSDataStoreUtil.DEFAULT_ENCODING));
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} finally {
is.close();
}
return sb.toString();
}
return "";
}
/**
* This method requests the response from the URL either through GET/POST
* and returns the response as a stream
* The request is going to be a POST one if postData is not null, a GET otherwise
* @param is
* @return String
* @throws IOException
*/
InputStream resourceToStream(String resource, String postData) throws MalformedURLException,
IOException, ProtocolException {
boolean doPost = false;
URL url;
if(postData != null && (baseURL.length() + resource.length() + postData.length() < 2048)) {
url = new URL(baseURL + resource + "?" + postData);
} else {
url = new URL(baseURL + resource);
}
// TODO: use commons http client + persistent connections instead
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("Accept-Encoding", "gzip");
// if we have username/password use them to setup basic auth
if(user != null && password != null) {
String combined = nullToEmpty(user) + ":" + nullToEmpty(password);
byte[] authBytes = combined.getBytes("US-ASCII");
String encoded = new String(Base64.encodeBase64(authBytes));
urlConnection.setRequestProperty("Authorization", "Basic " + encoded);
}
// enforce the timeout if we have one
if(timeout > 0) {
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
}
urlConnection.setDoInput(true);
if(doPost && postData != null && !"".equals(postData.trim())) {
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setDoOutput(true);
/*write the data to server*/
OutputStreamWriter wr = null;
try {
wr = new OutputStreamWriter(urlConnection.getOutputStream());
wr.write(postData);
wr.flush();
} finally {
if(wr != null) {
wr.close();
}
}
} else {
urlConnection.setRequestMethod("GET");
}
/* Get the response from the server*/
if(urlConnection.getResponseCode() != 200) {
throw new IOException("Server reported and error with code " +
urlConnection.getResponseCode() + ": " + urlConnection.getResponseMessage() + " accessing resource " + url.toExternalForm());
} else {
InputStream is = urlConnection.getInputStream();
String encoding = urlConnection.getContentEncoding();
if ("gzip".equalsIgnoreCase(encoding)) {
is = new GZIPInputStream(is);
}
return is;
}
}
private String nullToEmpty(String string) {
return string != null ? string : "";
}
}