0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2.1 of the License, or (at your option) any later version.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: */
0016: package org.geotools.data.wfs;
0017:
0018: import java.io.BufferedInputStream;
0019: import java.io.IOException;
0020: import java.io.InputStream;
0021: import java.io.OutputStream;
0022: import java.io.OutputStreamWriter;
0023: import java.io.StringWriter;
0024: import java.io.UnsupportedEncodingException;
0025: import java.io.Writer;
0026: import java.net.Authenticator;
0027: import java.net.HttpURLConnection;
0028: import java.net.MalformedURLException;
0029: import java.net.PasswordAuthentication;
0030: import java.net.URI;
0031: import java.net.URISyntaxException;
0032: import java.net.URL;
0033: import java.net.URLEncoder;
0034: import java.util.Arrays;
0035: import java.util.HashMap;
0036: import java.util.Iterator;
0037: import java.util.List;
0038: import java.util.Map;
0039: import java.util.logging.Level;
0040: import java.util.logging.Logger;
0041: import java.util.zip.GZIPInputStream;
0042:
0043: import javax.naming.OperationNotSupportedException;
0044:
0045: import org.geotools.data.AbstractDataStore;
0046: import org.geotools.data.DataSourceException;
0047: import org.geotools.data.DataUtilities;
0048: import org.geotools.data.DefaultQuery;
0049: import org.geotools.data.EmptyFeatureReader;
0050: import org.geotools.data.FeatureReader;
0051: import org.geotools.data.FeatureSource;
0052: import org.geotools.data.FilteringFeatureReader;
0053: import org.geotools.data.Query;
0054: import org.geotools.data.ReTypeFeatureReader;
0055: import org.geotools.data.Transaction;
0056: import org.geotools.data.crs.ForceCoordinateSystemFeatureReader;
0057: import org.geotools.data.ows.FeatureSetDescription;
0058: import org.geotools.data.ows.WFSCapabilities;
0059: import org.geotools.feature.AttributeType;
0060: import org.geotools.feature.AttributeTypeFactory;
0061: import org.geotools.feature.FeatureType;
0062: import org.geotools.feature.FeatureTypeBuilder;
0063: import org.geotools.feature.GeometryAttributeType;
0064: import org.geotools.feature.SchemaException;
0065: import org.geotools.filter.ExpressionType;
0066: import org.geotools.filter.FidFilter;
0067: import org.opengis.filter.Filter;
0068: import org.geotools.filter.FilterType;
0069: import org.geotools.filter.Filters;
0070: import org.geotools.filter.GeometryFilter;
0071: import org.geotools.filter.LiteralExpression;
0072: import org.geotools.filter.visitor.PostPreProcessFilterSplittingVisitor;
0073: import org.geotools.filter.visitor.PostPreProcessFilterSplittingVisitor.WFSBBoxFilterVisitor;
0074: import org.geotools.geometry.jts.JTS;
0075: import org.geotools.geometry.jts.ReferencedEnvelope;
0076: import org.geotools.referencing.CRS;
0077: import org.geotools.referencing.crs.DefaultGeographicCRS;
0078: import org.geotools.util.logging.Logging;
0079: import org.geotools.xml.DocumentFactory;
0080: import org.geotools.xml.DocumentWriter;
0081: import org.geotools.xml.SchemaFactory;
0082: import org.geotools.xml.filter.FilterSchema;
0083: import org.geotools.xml.gml.GMLComplexTypes;
0084: import org.geotools.xml.gml.WFSFeatureTypeTransformer;
0085: import org.geotools.xml.schema.Element;
0086: import org.geotools.xml.schema.Schema;
0087: import org.geotools.xml.wfs.WFSSchema;
0088: import org.opengis.referencing.FactoryException;
0089: import org.opengis.referencing.NoSuchAuthorityCodeException;
0090: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0091: import org.opengis.referencing.operation.MathTransform;
0092: import org.opengis.referencing.operation.TransformException;
0093: import org.opengis.geometry.MismatchedDimensionException;
0094: import org.xml.sax.SAXException;
0095:
0096: import com.vividsolutions.jts.geom.Envelope;
0097: import com.vividsolutions.jts.geom.Geometry;
0098:
0099: /**
0100: * <p>
0101: * DOCUMENT ME!
0102: * </p>
0103: *
0104: * @author dzwiers
0105: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/wfs/src/main/java/org/geotools/data/wfs/WFSDataStore.java $
0106: */
0107: public class WFSDataStore extends AbstractDataStore {
0108:
0109: protected WFSCapabilities capabilities = null;
0110:
0111: protected static final int AUTO_PROTOCOL = 3;
0112: protected static final int POST_PROTOCOL = 1;
0113: protected static final int GET_PROTOCOL = 2;
0114:
0115: protected int protocol = AUTO_PROTOCOL; // visible for transaction
0116: protected Authenticator auth = null; // visible for transaction
0117:
0118: private int bufferSize = 10;
0119: private int timeout = 10000;
0120: private final boolean tryGZIP;
0121: protected WFSStrategy strategy;
0122:
0123: protected String encoding = "UTF-8";
0124:
0125: private boolean lenient;
0126:
0127: /**
0128: * Construct <code>WFSDataStore</code>.
0129: *
0130: * Should NEVER be called!
0131: */
0132: private WFSDataStore() {
0133: // not called
0134: tryGZIP = true;
0135: }
0136:
0137: protected WFSDataStore(URL host, Boolean protocol, String username,
0138: String password, int timeout, int buffer, boolean tryGZIP,
0139: boolean lenient, String encoding) throws SAXException,
0140: IOException {
0141: this (host, protocol, username, password, timeout, buffer,
0142: tryGZIP, lenient);
0143: if (encoding != null) {
0144: this .encoding = encoding;
0145: }
0146: }
0147:
0148: /**
0149: * Construct <code>WFSDataStore</code>.
0150: *
0151: * @param host - may not yet be a capabilities url
0152: * @param protocol - true,false,null (post,get,auto)
0153: * @param username - iff password
0154: * @param password - iff username
0155: * @param timeout - default 3000 (ms)
0156: * @param buffer - default 10 (features)
0157: * @param tryGZIP - indicates to use GZIP if server supports it.
0158: *
0159: * @throws SAXException
0160: * @throws IOException
0161: */
0162: protected WFSDataStore(URL host, Boolean protocol, String username,
0163: String password, int timeout, int buffer, boolean tryGZIP)
0164: throws SAXException, IOException {
0165: this (host, protocol, username, password, timeout, buffer,
0166: tryGZIP, false);
0167: }
0168:
0169: /**
0170: * Construct <code>WFSDataStore</code>.
0171: *
0172: * @param host - may not yet be a capabilities url
0173: * @param protocol - true,false,null (post,get,auto)
0174: * @param username - iff password
0175: * @param password - iff username
0176: * @param timeout - default 3000 (ms)
0177: * @param buffer - default 10 (features)
0178: * @param tryGZIP - indicates to use GZIP if server supports it.
0179: * @param lenient - if true the parsing will be very forgiving to bad data. Errors will be logged rather than exceptions.
0180: *
0181: * @throws SAXException
0182: * @throws IOException
0183: */
0184: protected WFSDataStore(URL host, Boolean protocol, String username,
0185: String password, int timeout, int buffer, boolean tryGZIP,
0186: boolean lenient) throws SAXException, IOException {
0187: super (true);
0188:
0189: this .lenient = lenient;
0190: if ((username != null) && (password != null)) {
0191: auth = new WFSAuthenticator(username, password);
0192: }
0193:
0194: if (protocol == null) {
0195: this .protocol = AUTO_PROTOCOL;
0196: } else {
0197: if (protocol.booleanValue()) {
0198: this .protocol = POST_PROTOCOL;
0199: } else {
0200: this .protocol = GET_PROTOCOL;
0201: }
0202: }
0203:
0204: this .timeout = timeout;
0205: this .bufferSize = buffer;
0206: this .tryGZIP = tryGZIP;
0207: findCapabilities(host);
0208: determineCorrectStrategy(host);
0209: }
0210:
0211: private void determineCorrectStrategy(URL host) {
0212: if (host.toString().indexOf("mapserv") != -1)
0213: strategy = new MapServerWFSStrategy(this );
0214: else
0215: strategy = new StrictWFSStrategy(this );
0216: }
0217:
0218: private void findCapabilities(URL host) throws SAXException,
0219: IOException {
0220:
0221: Object t = null;
0222: Map hints = new HashMap();
0223: hints.put(DocumentFactory.VALIDATION_HINT, Boolean.FALSE);
0224:
0225: if ((protocol & GET_PROTOCOL) == GET_PROTOCOL) {
0226: HttpURLConnection hc = getConnection(
0227: createGetCapabilitiesRequest(host), auth, false);
0228: InputStream is = getInputStream(hc);
0229: t = DocumentFactory.getInstance(is, hints,
0230: WFSDataStoreFactory.logger.getLevel());
0231: }
0232:
0233: if ((false == (t instanceof WFSCapabilities))
0234: && ((protocol & POST_PROTOCOL) == POST_PROTOCOL)) {
0235: HttpURLConnection hc = getConnection(host, auth, true);
0236:
0237: // write request
0238: Writer osw = getOutputStream(hc);
0239: hints.put(DocumentWriter.BASE_ELEMENT, WFSSchema
0240: .getInstance().getElements()[0]); // GetCapabilities
0241:
0242: try {
0243: DocumentWriter.writeDocument((Object) null, WFSSchema
0244: .getInstance(), osw, hints);
0245: } catch (OperationNotSupportedException e) {
0246: WFSDataStoreFactory.logger.warning(e.getMessage());
0247: throw new SAXException(e);
0248: }
0249:
0250: osw.flush();
0251: osw.close();
0252:
0253: InputStream is = getInputStream(hc);
0254: t = DocumentFactory.getInstance(is, hints,
0255: WFSDataStoreFactory.logger.getLevel());
0256: }
0257:
0258: if (t instanceof WFSCapabilities) {
0259: capabilities = (WFSCapabilities) t;
0260: } else {
0261: throw new SAXException(
0262: "The specified URL Should have returned a 'WFSCapabilities' object. Returned a "
0263: + ((t == null) ? "null value."
0264: : (t.getClass().getName() + " instance.")));
0265: }
0266: }
0267:
0268: protected HttpURLConnection getConnection(URL url,
0269: Authenticator auth, boolean isPost) throws IOException {
0270:
0271: HttpURLConnection connection = (HttpURLConnection) url
0272: .openConnection();
0273:
0274: if (isPost) {
0275: connection.setRequestMethod("POST");
0276: connection.setDoOutput(true);
0277: connection.setRequestProperty("Content-type",
0278: "text/xml, application/xml");
0279: } else {
0280: connection.setRequestMethod("GET");
0281: }
0282: connection.setDoInput(true);
0283: /*
0284: * FIXME this could breaks uDig. Not quite sure what to do otherwise.
0285: * Maybe have a mechanism that would allow an authenticator to ask the
0286: * datastore itself for a previously supplied user/pass.
0287: */
0288: if (auth != null) {
0289: synchronized (Authenticator.class) {
0290: Authenticator.setDefault(auth);
0291: connection.connect();
0292: Authenticator.setDefault(null);
0293: }
0294: }
0295:
0296: if (this .tryGZIP) {
0297: connection.addRequestProperty("Accept-Encoding", "gzip");
0298: }
0299:
0300: return connection;
0301: }
0302:
0303: protected static URL createGetCapabilitiesRequest(URL host) {
0304: if (host == null) {
0305: return null;
0306: }
0307:
0308: String url = host.toString();
0309:
0310: if (host.getQuery() == null) {
0311: url += "?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetCapabilities";
0312: } else {
0313: String t = host.getQuery().toUpperCase();
0314:
0315: if (t.indexOf("SERVICE") == -1) {
0316: url += "&SERVICE=WFS";
0317: }
0318:
0319: if (t.indexOf("VERSION") == -1) {
0320: url += "&VERSION=1.0.0";
0321: }
0322:
0323: if (t.indexOf("REQUEST") == -1) {
0324: url += "&REQUEST=GetCapabilities";
0325: }
0326: }
0327:
0328: try {
0329: return new URL(url);
0330: } catch (MalformedURLException e) {
0331: WFSDataStoreFactory.logger.warning(e.toString());
0332:
0333: return host;
0334: }
0335: }
0336:
0337: private String[] typeNames = null;
0338: private Map featureTypeCache = new HashMap();
0339:
0340: private Map fidMap = new HashMap();
0341:
0342: private Map xmlSchemaCache = new HashMap();
0343:
0344: /**
0345: * @see org.geotools.data.AbstractDataStore#getTypeNames()
0346: */
0347: public String[] getTypeNames() {
0348: if (typeNames == null) {
0349: List l = capabilities.getFeatureTypes();
0350: typeNames = new String[l.size()];
0351:
0352: for (int i = 0; i < l.size(); i++) {
0353: typeNames[i] = ((FeatureSetDescription) l.get(i))
0354: .getName();
0355: }
0356: }
0357: // protect the cache against external modifications
0358: String[] retVal = new String[typeNames.length];
0359: System.arraycopy(typeNames, 0, retVal, 0, typeNames.length);
0360: return retVal;
0361: }
0362:
0363: /**
0364: * DOCUMENT ME!
0365: *
0366: * @param typeName DOCUMENT ME!
0367: *
0368: * @return DOCUMENT ME!
0369: *
0370: * @throws IOException
0371: *
0372: * @see org.geotools.data.AbstractDataStore#getSchema(java.lang.String)
0373: */
0374: public FeatureType getSchema(String typeName) throws IOException {
0375: if (featureTypeCache.containsKey(typeName)) {
0376: return (FeatureType) featureTypeCache.get(typeName);
0377: }
0378:
0379: // TODO sanity check for request with capabilities obj
0380:
0381: FeatureType t = null;
0382: SAXException sax = null;
0383: IOException io = null;
0384: if (((protocol & POST_PROTOCOL) == POST_PROTOCOL)
0385: && (t == null)) {
0386: try {
0387: t = getSchemaPost(typeName);
0388: } catch (SAXException e) {
0389: WFSDataStoreFactory.logger.warning(e.toString());
0390: sax = e;
0391: } catch (IOException e) {
0392: WFSDataStoreFactory.logger.warning(e.toString());
0393: io = e;
0394: }
0395: }
0396:
0397: if (((protocol & GET_PROTOCOL) == GET_PROTOCOL) && (t == null)) {
0398: try {
0399: t = getSchemaGet(typeName);
0400: } catch (SAXException e) {
0401: WFSDataStoreFactory.logger.warning(e.toString());
0402: sax = e;
0403: } catch (IOException e) {
0404: WFSDataStoreFactory.logger.warning(e.toString());
0405: io = e;
0406: }
0407: }
0408:
0409: if (t == null && sax != null)
0410: throw new DataSourceException(sax);
0411:
0412: if (t == null && io != null)
0413: throw io;
0414:
0415: //set crs?
0416: FeatureSetDescription fsd = WFSCapabilities
0417: .getFeatureSetDescription(capabilities, typeName);
0418: String crsName = null;
0419: String ftName = null;
0420: if (fsd != null) {
0421: crsName = fsd.getSRS();
0422: ftName = fsd.getName();
0423:
0424: CoordinateReferenceSystem crs;
0425: try {
0426: if (crsName != null) {
0427: crs = CRS.decode(crsName);
0428: t = WFSFeatureTypeTransformer.transform(t, crs);
0429: }
0430: } catch (FactoryException e) {
0431: WFSDataStoreFactory.logger.warning(e.getMessage());
0432: } catch (SchemaException e) {
0433: WFSDataStoreFactory.logger.warning(e.getMessage());
0434: }
0435: }
0436:
0437: if (ftName != null) {
0438: try {
0439: t = FeatureTypeBuilder.newFeatureType(t
0440: .getAttributeTypes(), ftName == null ? typeName
0441: : ftName, t.getNamespace(), t.isAbstract(), t
0442: .getAncestors(), t.getDefaultGeometry());
0443:
0444: } catch (SchemaException e1) {
0445: WFSDataStoreFactory.logger.warning(e1.getMessage());
0446: }
0447: }
0448: try {
0449: URL url = getDescribeFeatureTypeURLGet(typeName);
0450: if (url != null) {
0451: t = new WFSFeatureType(t, new URI(url.toString()));
0452: }
0453: } catch (URISyntaxException e) {
0454: throw (RuntimeException) new RuntimeException(e);
0455: }
0456: if (t != null) {
0457: featureTypeCache.put(typeName, t);
0458: }
0459:
0460: return t;
0461: }
0462:
0463: // protected for testing
0464: protected FeatureType getSchemaGet(String typeName)
0465: throws SAXException, IOException {
0466: URL getUrl = getDescribeFeatureTypeURLGet(typeName);
0467: Logging.getLogger("org.geotools.data.communication").fine(
0468: "Output: " + getUrl);
0469: if (getUrl == null)
0470: return null;
0471: HttpURLConnection hc = getConnection(getUrl, auth, false);
0472:
0473: InputStream is = getInputStream(hc);
0474: Schema schema;
0475: try {
0476: schema = SchemaFactory.getInstance(null, is);
0477: } finally {
0478: is.close();
0479: }
0480: return parseDescribeFeatureTypeResponse(typeName, schema);
0481: }
0482:
0483: private URL getDescribeFeatureTypeURLGet(String typeName)
0484: throws MalformedURLException {
0485: URL getUrl = capabilities.getDescribeFeatureType().getGet();
0486: Logging.getLogger("org.geotools.data.communication").fine(
0487: "Output: " + getUrl);
0488:
0489: if (getUrl == null) {
0490: return null;
0491: }
0492:
0493: String query = getUrl.getQuery();
0494: query = query == null ? null : query.toUpperCase();
0495: String url = getUrl.toString();
0496:
0497: if ((query == null) || "".equals(query)) {
0498: if ((url == null) || !url.endsWith("?")) {
0499: url += "?";
0500: }
0501:
0502: url += "SERVICE=WFS";
0503: } else {
0504: if (query.indexOf("SERVICE=WFS") == -1) {
0505: url += "&SERVICE=WFS";
0506: }
0507: }
0508:
0509: if ((query == null) || (query.indexOf("VERSION") == -1)) {
0510: url += "&VERSION=1.0.0";
0511: }
0512:
0513: if ((query == null) || (query.indexOf("REQUEST") == -1)) {
0514: url += "&REQUEST=DescribeFeatureType";
0515: }
0516:
0517: url += ("&TYPENAME=" + typeName);
0518:
0519: getUrl = new URL(url);
0520: return getUrl;
0521: }
0522:
0523: static FeatureType parseDescribeFeatureTypeResponse(
0524: String typeName, Schema schema) throws SAXException {
0525: Element[] elements = schema.getElements();
0526:
0527: if (elements == null) {
0528: return null; // not found
0529: }
0530:
0531: Element element = null;
0532:
0533: String ttname = typeName.substring(typeName.indexOf(":") + 1);
0534:
0535: for (int i = 0; (i < elements.length) && (element == null); i++) {
0536: // HACK -- namspace related -- should be checking ns as opposed to removing prefix
0537: if (typeName.equals(elements[i].getName())
0538: || ttname.equals(elements[i].getName())) {
0539: element = elements[i];
0540: }
0541: }
0542:
0543: if (element == null) {
0544: return null;
0545: }
0546:
0547: FeatureType ft = GMLComplexTypes.createFeatureType(element);
0548:
0549: return ft;
0550: }
0551:
0552: // protected for testing
0553: protected FeatureType getSchemaPost(String typeName)
0554: throws IOException, SAXException {
0555: URL postUrl = capabilities.getDescribeFeatureType().getPost();
0556:
0557: if (postUrl == null) {
0558: return null;
0559: }
0560:
0561: HttpURLConnection hc = getConnection(postUrl, auth, true);
0562:
0563: // write request
0564: Writer osw = getOutputStream(hc);
0565: Map hints = new HashMap();
0566: hints.put(DocumentWriter.BASE_ELEMENT, WFSSchema.getInstance()
0567: .getElements()[1]); // DescribeFeatureType
0568: List l = capabilities.getFeatureTypes();
0569: Iterator it = l.iterator();
0570: URI uri = null;
0571: while (it.hasNext() && uri == null) {
0572: FeatureSetDescription fsd = (FeatureSetDescription) it
0573: .next();
0574: if (typeName.equals(fsd.getName()))
0575: uri = fsd.getNamespace();
0576: }
0577: if (uri != null)
0578: hints.put(DocumentWriter.SCHEMA_ORDER, new String[] {
0579: WFSSchema.NAMESPACE.toString(), uri.toString() });
0580:
0581: hints.put(DocumentWriter.ENCODING, encoding);
0582: try {
0583: DocumentWriter.writeDocument(new String[] { typeName },
0584: WFSSchema.getInstance(), osw, hints);
0585: } catch (OperationNotSupportedException e) {
0586: WFSDataStoreFactory.logger.warning(e.getMessage());
0587: throw new SAXException(e);
0588: }
0589:
0590: osw.flush();
0591: osw.close();
0592: InputStream is = getInputStream(hc);
0593:
0594: Schema schema;
0595: try {
0596: schema = SchemaFactory.getInstance(null, is);
0597: } finally {
0598: is.close();
0599: }
0600:
0601: return parseDescribeFeatureTypeResponse(typeName, schema);
0602: }
0603:
0604: // protected for testing
0605: protected FeatureReader getFeatureReaderGet(Query request,
0606: Transaction transaction)
0607: throws UnsupportedEncodingException, IOException,
0608: SAXException {
0609: URL getUrl = capabilities.getGetFeature().getGet();
0610:
0611: if (getUrl == null) {
0612: return null;
0613: }
0614:
0615: String query = getUrl.getQuery();
0616: query = query == null ? null : query.toUpperCase();
0617: String url = getUrl.toString();
0618:
0619: if ((query == null) || "".equals(query)) {
0620: if ((url == null) || !url.endsWith("?")) {
0621: url += "?";
0622: }
0623:
0624: url += "SERVICE=WFS";
0625: } else {
0626: if (query.indexOf("SERVICE=WFS") == -1) {
0627: url += "&SERVICE=WFS";
0628: }
0629: }
0630:
0631: if ((query == null) || (query.indexOf("VERSION") == -1)) {
0632: url += "&VERSION=1.0.0";
0633: }
0634:
0635: if ((query == null) || (query.indexOf("REQUEST") == -1)) {
0636: url += "&REQUEST=GetFeature";
0637: }
0638:
0639: if (request != null) {
0640: if (request.getMaxFeatures() != Query.DEFAULT_MAX) {
0641: url += ("&MAXFEATURES=" + request.getMaxFeatures());
0642: }
0643:
0644: if (request.getFilter() != null) {
0645: if (Filters.getFilterType(request.getFilter()) == FilterType.GEOMETRY_BBOX) {
0646: String bb = printBBoxGet(((GeometryFilter) request
0647: .getFilter()), request.getTypeName());
0648: if (bb != null)
0649: url += ("&BBOX=" + URLEncoder.encode(bb,
0650: this .encoding));
0651: } else {
0652: if (Filters.getFilterType(request.getFilter()) == FilterType.FID) {
0653: FidFilter ff = (FidFilter) request.getFilter();
0654:
0655: if ((ff.getFids() != null)
0656: && (ff.getFids().length > 0)) {
0657: url += ("&FEATUREID=" + ff.getFids()[0]);
0658:
0659: for (int i = 1; i < ff.getFids().length; i++) {
0660: url += ("," + ff.getFids()[i]);
0661: }
0662: }
0663: } else {
0664: // rest
0665: if (request.getFilter() != Filter.INCLUDE
0666: && request.getFilter() != Filter.EXCLUDE) {
0667: url += "&FILTER="
0668: + URLEncoder.encode(
0669: printFilter(request
0670: .getFilter()),
0671: this .encoding);
0672: }
0673: }
0674: }
0675: }
0676: }
0677:
0678: url += ("&TYPENAME=" + URLEncoder.encode(request.getTypeName(),
0679: this .encoding));
0680:
0681: Logging.getLogger("org.geotools.data.wfs").fine(url);
0682: Logging.getLogger("org.geotools.data.communication").fine(
0683: "Output: " + url);
0684: getUrl = new URL(url);
0685: HttpURLConnection hc = getConnection(getUrl, auth, false);
0686:
0687: InputStream is = getInputStream(hc);
0688: WFSTransactionState ts = null;
0689:
0690: if (!(transaction == Transaction.AUTO_COMMIT)) {
0691: ts = (WFSTransactionState) transaction.getState(this );
0692:
0693: if (ts == null) {
0694: ts = new WFSTransactionState(this );
0695: transaction.putState(this , ts);
0696: }
0697: }
0698:
0699: WFSFeatureType schema = (WFSFeatureType) getSchema(request
0700: .getTypeName());
0701:
0702: FeatureType featureType;
0703: try {
0704: featureType = DataUtilities.createSubType(schema.delegate,
0705: request.getPropertyNames(), request
0706: .getCoordinateSystem());
0707: } catch (SchemaException e) {
0708: featureType = schema.delegate;
0709: }
0710: WFSFeatureReader ft = WFSFeatureReader.getFeatureReader(is,
0711: bufferSize, timeout, ts,
0712: new WFSFeatureType(schema.delegate, schema
0713: .getSchemaURI(), lenient));
0714:
0715: if (!featureType.equals(ft.getFeatureType())) {
0716: LOGGER
0717: .fine("Recasting feature type to subtype by using a ReTypeFeatureReader");
0718: return new ReTypeFeatureReader(ft, featureType, false);
0719: } else
0720: return ft;
0721:
0722: }
0723:
0724: Writer getOutputStream(HttpURLConnection hc) throws IOException {
0725: OutputStream os = hc.getOutputStream();
0726:
0727: Writer w = new OutputStreamWriter(os);
0728: // write request
0729: Logger logger = Logging.getLogger("org.geotools.data.wfs");
0730: if (logger.isLoggable(Level.FINE)) {
0731: w = new LogWriterDecorator(w, logger, Level.FINE);
0732: }
0733: // special logger for communication information only.
0734: logger = Logging.getLogger("org.geotools.data.communication");
0735: if (logger.isLoggable(Level.FINE)) {
0736: w = new LogWriterDecorator(w, logger, Level.FINE);
0737: }
0738: return w;
0739: }
0740:
0741: /**
0742: * If the field useGZIP is true Adds gzip to the connection accept-encoding property and creates a gzip inputstream
0743: * (if server supports it). Otherwise returns a normal buffered input stream.
0744: * @param hc the connection to use to create the stream
0745: * @return an input steam from the provided connection
0746: */
0747: InputStream getInputStream(HttpURLConnection hc) throws IOException {
0748: InputStream is = hc.getInputStream();
0749:
0750: if (tryGZIP) {
0751: if (hc.getContentEncoding() != null
0752: && hc.getContentEncoding().indexOf("gzip") != -1) {
0753: is = new GZIPInputStream(is);
0754: }
0755: }
0756: is = new BufferedInputStream(is);
0757: if (WFSDataStoreFactory.logger.isLoggable(Level.FINE)) {
0758: is = new LogInputStream(is, WFSDataStoreFactory.logger,
0759: Level.FINE);
0760: }
0761: // special logger for communication information only.
0762: Logger logger = Logging
0763: .getLogger("org.geotools.data.communication");
0764: if (logger.isLoggable(Level.FINE)) {
0765: is = new LogInputStream(is, logger, Level.FINE);
0766: }
0767: return is;
0768: }
0769:
0770: private String printFilter(Filter f) throws IOException,
0771: SAXException {
0772: // ogc filter
0773: Map hints = new HashMap();
0774: hints.put(DocumentWriter.BASE_ELEMENT, FilterSchema
0775: .getInstance().getElements()[2]); // Filter
0776:
0777: StringWriter w = new StringWriter();
0778:
0779: try {
0780: DocumentWriter.writeFragment(f, FilterSchema.getInstance(),
0781: w, hints);
0782: } catch (OperationNotSupportedException e) {
0783: WFSDataStoreFactory.logger.warning(e.toString());
0784: throw new SAXException(e);
0785: }
0786:
0787: return w.toString();
0788: }
0789:
0790: private String printBBoxGet(GeometryFilter gf, String typename)
0791: throws IOException {
0792: Envelope e = null;
0793:
0794: if (gf.getLeftGeometry().getType() == ExpressionType.LITERAL_GEOMETRY) {
0795: e = ((Geometry) ((LiteralExpression) gf.getLeftGeometry())
0796: .getLiteral()).getEnvelopeInternal();
0797: } else {
0798: if (gf.getRightGeometry().getType() == ExpressionType.LITERAL_GEOMETRY) {
0799: LiteralExpression literal = (LiteralExpression) gf
0800: .getRightGeometry();
0801: Geometry geometry = (Geometry) literal.getLiteral();
0802: e = geometry.getEnvelopeInternal();
0803: } else {
0804: throw new IOException("Cannot encode BBOX:" + gf);
0805: }
0806: }
0807:
0808: if (e == null || e.isNull())
0809: return null;
0810:
0811: // Cannot check against layer bbounding box because they may be in different CRS
0812: // We could insert ReferencedEnvelope fun here - note a check is already performed
0813: // as part clipping the request bounding box.
0814:
0815: /*
0816: // find layer's bbox
0817: Envelope lbb = null;
0818: if(capabilities != null && capabilities.getFeatureTypes() != null && typename!=null && !"".equals(typename)){
0819: List fts = capabilities.getFeatureTypes();
0820: if(!fts.isEmpty()){
0821: for(Iterator i=fts.iterator();i.hasNext() && lbb == null;){
0822: FeatureSetDescription fsd = (FeatureSetDescription)i.next();
0823: if(fsd!=null && typename.equals(fsd.getName())){
0824: lbb = fsd.getLatLongBoundingBox();
0825: }
0826: }
0827: }
0828: }
0829: if(lbb == null || lbb.contains(e))
0830: */
0831: return e.getMinX() + "," + e.getMinY() + "," + e.getMaxX()
0832: + "," + e.getMaxY();
0833: //return null;
0834: }
0835:
0836: // protected for testing
0837: protected FeatureReader getFeatureReaderPost(Query query,
0838: Transaction transaction) throws SAXException, IOException {
0839: URL postUrl = capabilities.getGetFeature().getPost();
0840:
0841: if (postUrl == null) {
0842: return null;
0843: }
0844:
0845: HttpURLConnection hc = getConnection(postUrl, auth, true);
0846:
0847: Writer w = getOutputStream(hc);
0848:
0849: Map hints = new HashMap();
0850: hints.put(DocumentWriter.BASE_ELEMENT, WFSSchema.getInstance()
0851: .getElements()[2]); // GetFeature
0852: hints.put(DocumentWriter.ENCODING, encoding);
0853: try {
0854: DocumentWriter.writeDocument(query,
0855: WFSSchema.getInstance(), w, hints);
0856: } catch (OperationNotSupportedException e) {
0857: WFSDataStoreFactory.logger.warning(e.toString());
0858: throw new SAXException(e);
0859: } finally {
0860: w.flush();
0861: w.close();
0862: }
0863:
0864: // JE: permit possibility for GZipped data.
0865: InputStream is = getInputStream(hc);
0866:
0867: WFSTransactionState ts = null;
0868:
0869: if (!(transaction == Transaction.AUTO_COMMIT)) {
0870: ts = (WFSTransactionState) transaction.getState(this );
0871:
0872: if (ts == null) {
0873: ts = new WFSTransactionState(this );
0874: transaction.putState(this , ts);
0875: }
0876: }
0877: WFSFeatureType schema = (WFSFeatureType) getSchema(query
0878: .getTypeName());
0879:
0880: FeatureType featureType;
0881: try {
0882: featureType = DataUtilities.createSubType(schema.delegate,
0883: query.getPropertyNames(), query
0884: .getCoordinateSystem());
0885: } catch (SchemaException e) {
0886: featureType = schema.delegate;
0887: }
0888:
0889: WFSFeatureReader ft = WFSFeatureReader.getFeatureReader(is,
0890: bufferSize, timeout, ts,
0891: new WFSFeatureType(schema.delegate, schema
0892: .getSchemaURI(), lenient));
0893:
0894: if (!featureType.equals(ft.getFeatureType())) {
0895: LOGGER
0896: .fine("Recasting feature type to subtype by using a ReTypeFeatureReader");
0897: return new ReTypeFeatureReader(ft, featureType, false);
0898: } else
0899: return ft;
0900: }
0901:
0902: protected FeatureReader getFeatureReader(String typeName)
0903: throws IOException {
0904: return getFeatureReader(typeName, new DefaultQuery(typeName));
0905: }
0906:
0907: protected FeatureReader getFeatureReader(String typeName,
0908: Query query) throws IOException {
0909: if ((query.getTypeName() == null)
0910: || !query.getTypeName().equals(typeName)) {
0911: Query q = new DefaultQuery(query);
0912: ((DefaultQuery) q).setTypeName(typeName);
0913:
0914: return getFeatureReader(q, Transaction.AUTO_COMMIT);
0915: }
0916:
0917: return getFeatureReader(query, Transaction.AUTO_COMMIT);
0918: }
0919:
0920: /**
0921: * @see org.geotools.data.DataStore#getFeatureReader(org.geotools.data.Query, org.geotools.data.Transaction)
0922: */
0923: public FeatureReader getFeatureReader(Query query,
0924: Transaction transaction) throws IOException {
0925: return strategy.getFeatureReader(query, transaction);
0926: }
0927:
0928: /* (non-Javadoc)
0929: * @see org.geotools.data.AbstractDataStore#getBounds(org.geotools.data.Query)
0930: */
0931: protected Envelope getBounds(Query query) throws IOException {
0932: if ((query == null) || (query.getTypeName() == null)) {
0933: return super .getBounds(query);
0934: }
0935:
0936: List fts = capabilities.getFeatureTypes(); // FeatureSetDescription
0937: Iterator i = fts.iterator();
0938: String desiredType = query.getTypeName().substring(
0939: query.getTypeName().indexOf(":") + 1);
0940:
0941: while (i.hasNext()) {
0942: FeatureSetDescription fsd = (FeatureSetDescription) i
0943: .next();
0944: String fsdName = (fsd.getName() == null) ? null : fsd
0945: .getName()
0946: .substring(fsd.getName().indexOf(":") + 1);
0947:
0948: if (desiredType.equals(fsdName)) {
0949: Envelope env = fsd.getLatLongBoundingBox();
0950:
0951: ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope(
0952: env, DefaultGeographicCRS.WGS84);
0953:
0954: try {
0955: return referencedEnvelope.transform(CRS.decode(fsd
0956: .getSRS()), true);
0957: } catch (NoSuchAuthorityCodeException e) {
0958: return referencedEnvelope;
0959: } catch (TransformException e) {
0960: return referencedEnvelope;
0961: } catch (FactoryException e) {
0962: return referencedEnvelope;
0963: }
0964: }
0965: }
0966:
0967: return super .getBounds(query);
0968: }
0969:
0970: protected Filter[] splitFilters(Query q, Transaction t)
0971: throws IOException {
0972: // have to figure out which part of the request the server is capable of after removing the parts in the update / delete actions
0973: // [server][post]
0974: if (q.getFilter() == null)
0975: return new Filter[] { Filter.INCLUDE, Filter.INCLUDE };
0976: if (q.getTypeName() == null || t == null)
0977: return new Filter[] { Filter.INCLUDE, q.getFilter() };
0978:
0979: FeatureType ft = getSchema(q.getTypeName());
0980:
0981: List fts = capabilities.getFeatureTypes(); //FeatureSetDescription
0982: boolean found = false;
0983: for (int i = 0; i < fts.size(); i++)
0984: if (fts.get(i) != null) {
0985: FeatureSetDescription fsd = (FeatureSetDescription) fts
0986: .get(i);
0987: if (ft.getTypeName().equals(fsd.getName())) {
0988: found = true;
0989: } else {
0990: String fsdName = (fsd.getName() == null) ? null
0991: : fsd.getName().substring(
0992: fsd.getName().indexOf(":") + 1);
0993: if (ft.getTypeName().equals(fsdName)) {
0994: found = true;
0995: }
0996: }
0997: }
0998:
0999: if (!found) {
1000: WFSDataStoreFactory.logger
1001: .warning("Could not find typeName: "
1002: + ft.getTypeName());
1003: return new Filter[] { Filter.INCLUDE, q.getFilter() };
1004: }
1005: WFSTransactionState state = (t == Transaction.AUTO_COMMIT) ? null
1006: : (WFSTransactionState) t.getState(this );
1007: WFSTransactionAccessor transactionAccessor = null;
1008: if (state != null)
1009: transactionAccessor = new WFSTransactionAccessor(state
1010: .getActions(ft.getTypeName()));
1011: PostPreProcessFilterSplittingVisitor wfsfv = new PostPreProcessFilterSplittingVisitor(
1012: capabilities.getFilterCapabilities(), ft,
1013: transactionAccessor);
1014:
1015: q.getFilter().accept(wfsfv, null);
1016:
1017: Filter[] f = new Filter[2];
1018: f[0] = wfsfv.getFilterPre(); // server
1019: f[1] = wfsfv.getFilterPost();
1020:
1021: return f;
1022: }
1023:
1024: /**
1025: * @see org.geotools.data.AbstractDataStore#getUnsupportedFilter(java.lang.String,
1026: * org.geotools.filter.Filter)
1027: */
1028: protected Filter getUnsupportedFilter(String typeName, Filter filter) {
1029: try {
1030: return splitFilters(new DefaultQuery(typeName, filter),
1031: Transaction.AUTO_COMMIT)[1];
1032: } catch (IOException e) {
1033: return filter;
1034: }
1035: }
1036:
1037: /**
1038: *
1039: * @see org.geotools.data.DataStore#getFeatureSource(java.lang.String)
1040: */
1041: public FeatureSource getFeatureSource(String typeName)
1042: throws IOException {
1043: if (capabilities.getTransaction() != null) {
1044: // if(capabilities.getLockFeature()!=null){
1045: // return new WFSFeatureLocking(this,getSchema(typeName));
1046: // }
1047: return new WFSFeatureStore(this , typeName);
1048: }
1049:
1050: return new WFSFeatureSource(this , typeName);
1051: }
1052:
1053: /**
1054: * Runs {@link FidFilterVisitor} on the filter and returns the result as long as transaction is not AUTO_COMMIT or null.
1055: * @param filter filter to process.
1056: * @return Runs {@link FidFilterVisitor} on the filter and returns the result as long as transaction is not AUTO_COMMIT or null.
1057: */
1058: public Filter processFilter(Filter filter) {
1059: FidFilterVisitor visitor = new FidFilterVisitor(fidMap);
1060: Filters.accept(filter, visitor);
1061: return visitor.getProcessedFilter();
1062: }
1063:
1064: private static class WFSAuthenticator extends Authenticator {
1065: private PasswordAuthentication pa;
1066:
1067: private WFSAuthenticator() {
1068: // not called
1069: }
1070:
1071: /**
1072: *
1073: * @param user
1074: * @param pass
1075: * @param host
1076: */
1077: public WFSAuthenticator(String user, String pass) {
1078: pa = new PasswordAuthentication(user, pass.toCharArray());
1079: }
1080:
1081: protected PasswordAuthentication getPasswordAuthentication() {
1082: return pa;
1083: }
1084: }
1085:
1086: /**
1087: * Adds a new fid mapping to the fid map.
1088: * @param original the before fid
1089: * @param finalFid the final fid;
1090: */
1091: public synchronized void addFidMapping(String original,
1092: String finalFid) {
1093: if (original == null)
1094: throw new NullPointerException();
1095: fidMap.put(original, finalFid);
1096: }
1097:
1098: }
|