From: <je...@sv...> - 2006-11-30 19:55:51
|
Author: jeichar Date: 2006-11-30 11:53:43 -0800 (Thu, 30 Nov 2006) New Revision: 23145 Modified: geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/w= fs/WFSDataStore.java geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/w= fs/WFSDataStoreFactory.java geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/w= fs/WFSFeatureType.java Log: addressing issue http://jira.codehaus.org/browse/GEOT-1049 Modified: geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools= /data/wfs/WFSDataStore.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/= wfs/WFSDataStore.java 2006-11-30 19:52:08 UTC (rev 23144) +++ geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/= wfs/WFSDataStore.java 2006-11-30 19:53:43 UTC (rev 23145) @@ -115,6 +115,8 @@ private final boolean tryGZIP; protected WFSStrategy strategy; =20 + private boolean lenient; + /** * Construct <code>WFSDataStore</code>. * @@ -124,7 +126,6 @@ // not called tryGZIP=3Dtrue; } - /** * Construct <code>WFSDataStore</code>. * @@ -140,10 +141,31 @@ * @throws IOException */ protected WFSDataStore(URL host, Boolean protocol, String username, - String password, int timeout, int buffer, boolean tryGZIP) + String password, int timeout, int buffer, boolean tryGZIP ) + throws SAXException, IOException { + this( host, protocol, username, password, timeout, buffer, tryGZ= IP, false); + } + /** + * Construct <code>WFSDataStore</code>. + * + * @param host - may not yet be a capabilities url + * @param protocol - true,false,null (post,get,auto) + * @param username - iff password + * @param password - iff username + * @param timeout - default 3000 (ms) + * @param buffer - default 10 (features) + * @param tryGZIP - indicates to use GZIP if server supports it. + * @param lenient - if true the parsing will be very forgiving to ba= d data. Errors will be logged rather than exceptions. + *=20 + * @throws SAXException + * @throws IOException + */ + protected WFSDataStore(URL host, Boolean protocol, String username, + String password, int timeout, int buffer, boolean tryGZIP, boole= an lenient) throws SAXException, IOException { super(true); =20 + this.lenient=3Dlenient; if ((username !=3D null) && (password !=3D null)) { auth =3D new WFSAuthenticator(username, password); } @@ -590,8 +612,10 @@ } } =20 + WFSFeatureType schema =3D (WFSFeatureType)getSchema(request.getT= ypeName()); + =20 WFSFeatureReader ft =3D WFSFeatureReader.getFeatureReader(is, bu= fferSize, - timeout, ts, (WFSFeatureType)getSchema(request.getTypeNa= me())); + timeout, ts, new WFSFeatureType(schema.delegate, schema.= getSchemaURI(), true)); =20 return ft; } @@ -737,9 +761,10 @@ transaction.putState(this, ts); } } - + WFSFeatureType schema =3D (WFSFeatureType)getSchema(query.getTyp= eName()); + =20 WFSFeatureReader ft =3D WFSFeatureReader.getFeatureReader(is, bu= fferSize, - timeout, ts, (WFSFeatureType)getSchema(query.getTypeName= ())); + timeout, ts, new WFSFeatureType(schema.delegate, schema.= getSchemaURI(), true)); =20 return ft; } Modified: geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools= /data/wfs/WFSDataStoreFactory.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/= wfs/WFSDataStoreFactory.java 2006-11-30 19:52:08 UTC (rev 23144) +++ geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/= wfs/WFSDataStoreFactory.java 2006-11-30 19:53:43 UTC (rev 23145) @@ -19,7 +19,6 @@ import java.net.URL; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; =20 import org.geotools.data.AbstractDataStoreFactory; @@ -96,6 +95,14 @@ Boolean.class, "Indicates that datastore should use gzip to transfer data i= f the server supports it. Default is true", false); + // be lenient about parsing data -- optional + /** + * Boolean + */ + public static final Param LENIENT =3D new Param("WFSDataStoreFactory= :LENIENT", + Boolean.class, + "Indicates that datastore should do its best to create featu= res from the provided data even if it does not accurately match the schem= a. Errors will be logged but the parsing will continue if this is true. = Default is false", + false); =20 protected Map cache =3D new HashMap(); protected static final Logger logger =3D logger(); @@ -145,6 +152,7 @@ int timeout =3D 3000; int buffer =3D 10; boolean tryGZIP=3Dtrue; + boolean lenient=3Dfalse; =20 if (params.containsKey(TIMEOUT.key)) { Integer i =3D (Integer) TIMEOUT.lookUp(params); @@ -164,6 +172,12 @@ tryGZIP =3D b.booleanValue(); } =20 + if (params.containsKey(LENIENT.key)) { + Boolean b =3D (Boolean) LENIENT.lookUp(params); + if(b!=3Dnull) + lenient =3D b.booleanValue(); + } + if (params.containsKey(USERNAME.key)) { user =3D (String) USERNAME.lookUp(params); } @@ -183,7 +197,7 @@ DataStore ds =3D null; =20 try { - ds =3D new WFSDataStore(host, protocol, user, pass, timeout,= buffer, tryGZIP); + ds =3D new WFSDataStore(host, protocol, user, pass, timeout,= buffer, tryGZIP, lenient); cache.put(params, ds); } catch (SAXException e) { logger.warning(e.toString()); Modified: geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools= /data/wfs/WFSFeatureType.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/= wfs/WFSFeatureType.java 2006-11-30 19:52:08 UTC (rev 23144) +++ geotools/trunk/gt/modules/plugin/wfs/src/main/java/org/geotools/data/= wfs/WFSFeatureType.java 2006-11-30 19:53:43 UTC (rev 23145) @@ -16,13 +16,21 @@ package org.geotools.data.wfs; =20 import java.net.URI; +import java.rmi.server.UID; +import java.util.logging.Level; =20 import org.geotools.feature.AttributeType; +import org.geotools.feature.DefaultFeature; +import org.geotools.feature.DefaultFeatureType; import org.geotools.feature.Feature; +import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureType; import org.geotools.feature.GeometryAttributeType; import org.geotools.feature.IllegalAttributeException; =20 +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; + /** * A FeatureType that adds the information about the XMLSchema used to c= reate the FeatureType. =20 * @author Jesse @@ -31,22 +39,43 @@ class WFSFeatureType implements FeatureType { FeatureType delegate; private URI schemaURI; + /** + * If lenient then it will not throw exceptions on create() if the a= ttributes aren't legal. + */ + private boolean lenient; =20 public WFSFeatureType( FeatureType delegate, URI schemaURI ) { + this( delegate, schemaURI, false); + } + + public WFSFeatureType( FeatureType delegate, URI schemaURI, boolean = lenient2 ) { this.delegate =3D delegate; this.schemaURI=3DschemaURI; + lenient=3Dlenient2; } =20 public Feature create( Object[] attributes, String featureID ) throw= s IllegalAttributeException { - return delegate.create(attributes, featureID); + if( lenient && delegate instanceof DefaultFeatureType ){ + WFSFeatureType schema =3D new WFSFeatureType(delegate,schema= URI ); + return new LenientFeature(schema, attributes, featureID); + }else{ + return delegate.create(attributes, featureID); + } } + public void setLenient( boolean lenient) { + this.lenient=3Dlenient; + } =20 + boolean isLenient() { + return lenient; + } + public Feature create( Object[] attributes ) throws IllegalAttribute= Exception { - return delegate.create(attributes); + return create(attributes, null); } =20 - public Feature duplicate( Feature feature ) throws IllegalAttributeE= xception { - return delegate.duplicate(feature); + public Feature duplicate( Feature original ) throws IllegalAttribute= Exception { + return delegate.duplicate(original); } =20 public boolean equals( Object arg0 ) { @@ -123,4 +152,555 @@ public String toString() { return delegate.toString(); } + =20 + private static class LenientFeature implements Feature{ + + /** The unique id of this feature */ + protected String featureId; + + /** Flat feature type schema for this feature. */ + private final WFSFeatureType schema; + + /** Attributes for the feature. */ + private Object[] attributes; + + /** The bounds of this feature. */ + private Envelope bounds; + + /** The collection that this Feature is a member of */ + private FeatureCollection parent; + + private int defaultGeomIndex=3D-1; + + private boolean constructing; + + /** + * Creates a new instance of flat feature, which must take a fla= t feature + * type schema and all attributes as arguments. + * + * @param schema Feature type schema for this flat feature. + * @param attributes Initial attributes for this feature. + * @param featureID The unique ID for this feature. + * + * @throws IllegalAttributeException Attribtues do not conform t= o feature + * type schema. + * @throws NullPointerException if schema is null. + */ + protected LenientFeature(WFSFeatureType schema, Object[] attribu= tes, + String featureID) + throws IllegalAttributeException, NullPointerException { + constructing=3Dtrue; + if (schema =3D=3D null) { + throw new NullPointerException("schema"); + } + + this.schema =3D schema; + this.featureId =3D (featureID =3D=3D null) ? defaultID() : f= eatureID; + this.attributes =3D new Object[schema.getAttributeCount()]; + + setAttributes(attributes); + =20 + defaultGeomIndex=3Dschema.find(schema.getDefaultGeometry()); + constructing=3Dfalse; + } + + /** + * Creates a new instance of flat feature, which must take a fla= t feature + * type schema and all attributes as arguments. + * + * @param schema Feature type schema for this flat feature. + * @param attributes Initial attributes for this feature. + * + * @throws IllegalAttributeException Attribtues do not conform t= o feature + * type schema. + * + * @task REVISIT: should we allow this? Force users to explicit= ly set + * featureID to null? + */ + protected LenientFeature(WFSFeatureType schema, Object[] attribu= tes) + throws IllegalAttributeException { + this(schema, attributes, null); + } + + /** + * Creates an ID from a hashcode. + * + * @return an id for the feature. + */ + String defaultID() { + return "fid-" + (new UID()).toString(); + } + + /** + * Gets a reference to the feature type schema for this feature. + * + * @return A copy of this feature's metadata in the form of a fe= ature type + * schema. + */ + public FeatureType getFeatureType() { + return schema; + } + + /** + * Gets the unique indentification string of this Feature. + * + * @return The unique id. + */ + public String getID() { + return featureId; + } + + /** + * Copy all the attributes of this Feature into the given array.= If the + * argument array is null, a new one will be created. Gets all a= ttributes + * from this feature, returned as a complex object array. This = array + * comes with no metadata, so to interpret this collection the = caller + * class should ask for the schema as well. + * + * @param array The array to copy the attributes into. + * + * @return The array passed in, or a new one if null. + */ + public Object[] getAttributes(Object[] array) { + Object[] retArray; + + if (array =3D=3D null) { + retArray =3D new Object[attributes.length]; + } else { + retArray =3D array; + } + + System.arraycopy(attributes, 0, retArray, 0, attributes.leng= th); + + return retArray; + } + + /** + * Gets an attribute for this feature at the location specified = by xPath. + * + * @param xPath XPath representation of attribute location. + * + * @return Attribute. + */ + public Object getAttribute(String xPath) { + int idx =3D schema.find(xPath); + + if (idx =3D=3D -1) { + return null; + } + + return attributes[idx]; + } + + /** + * Gets an attribute by the given zero-based index. + * + * @param index the position of the attribute to retrieve. + * + * @return The attribute at the given index. + */ + public Object getAttribute(int index) { + return attributes[index]; + } + + /** + * Sets the attribute at position to val. + * + * @param position the index of the attribute to set. + * @param val the new value to give the attribute at position. + * + * @throws IllegalAttributeException if the passed in val does n= ot validate + * against the AttributeType at that position. + */ + public void setAttribute(int position, Object val) + throws IllegalAttributeException { + AttributeType type =3D schema.getAttributeType(position); + + try { + if ((val =3D=3D null) && !type.isNillable()) val =3D typ= e.createDefaultValue();=20 + Object parsed =3D type.parse(val); + try{ + type.validate(parsed); + }catch (Throwable e) { + if( constructing ){ + WFSDataStoreFactory.logger.logp(Level.WARNING, "= LenientFeature", "setAttribute", "Illegal Argument but ignored since we a= re being lenient", + e); + }else{ + throw new IllegalAttributeException(type, val, e= ); + } + } + setAttributeValue(position, parsed); + } catch (IllegalArgumentException iae) { + throw new IllegalAttributeException(type, val, iae); + } + } + + /** + * Sets the attribute value at a given position, performing no p= arsing or + * validation. This is so subclasses can have access to setting = the array, + * without opening it up completely. + * + * @param position the index of the a ttribute to set. + * @param val the new value to give the attribute at position. + */ + protected void setAttributeValue(int position, Object val) { + attributes[position] =3D val; + } + + /** + * Sets all attributes for this feature, passed as an array. Al= l + * attributes are checked for validity before adding. + * + * @param attributes All feature attributes. + * + * @throws IllegalAttributeException Passed attributes do not ma= tch feature + * type. + */ + public void setAttributes(Object[] attributes) + throws IllegalAttributeException { + // the passed in attributes were null, lets make that a null= array + Object[] newAtts =3D attributes; + + if (attributes =3D=3D null) { + newAtts =3D new Object[this.attributes.length]; + } + + if( constructing ){ + + // We're trying to make this work no matter what=20 + // so try to figure out some mapping + Object[] tmp =3D assumeCorrectOrder( newAtts ); + if( tmp=3D=3Dnull ) + newAtts =3D greedyMatch(newAtts); + else + newAtts=3Dtmp; + }else{ + if (newAtts.length !=3D this.attributes.length) { + throw new IllegalAttributeException( + "Wrong number of attributes expected " + + schema.getAttributeCount() + " got " + newAtts= .length); + } + } + =20 + for (int i =3D 0, ii =3D newAtts.length; i < ii; i++) { + setAttribute(i, newAtts[i]); + } + } + + private Object[] assumeCorrectOrder( Object[] newAtts ) { + Object[] tmp=3Dnew Object[schema.getAttributeCount()]; + for( int i =3D 0; i < newAtts.length && i<schema.getAttribut= eCount(); i++ ) { + Object object =3D newAtts[i]; + AttributeType att =3D schema.getAttributeType(i); + if( object=3D=3Dnull ) + continue; + Class requiredClass =3D att.getType(); + Class realClass =3D object.getClass(); + if( !requiredClass.isAssignableFrom(realClass) ) + return null; + else + tmp[i]=3Dobject; + =20 + } + return tmp; + } + + private Object[] greedyMatch( Object[] newAtts ) { + Object[] relaxedAttrs=3Dnew Object[this.attributes.length]; + boolean inValid =3D false; + for( int i =3D 0; i < newAtts.length; i++ ) { + Object object =3D newAtts[i]; + boolean found =3D false; + if( object=3D=3Dnull ) + continue; + Class realClass =3D object.getClass(); + for( int j =3D 0; j < schema.getAttributeCount(); j++ ) = { + AttributeType att =3D schema.getAttributeType(j); + Class requiredClass =3D att.getType(); + if( relaxedAttrs[j]=3D=3Dnull && requiredClass.isAss= ignableFrom(realClass) ){ + relaxedAttrs[j]=3Dobject; + found=3Dtrue; + break; + } + } + if( !found ) inValid=3Dtrue; + } + newAtts=3DrelaxedAttrs; + if( inValid ){ + StringBuffer buf=3Dnew StringBuffer(); + buf.append("WFSFeatureType#setAttributes(Object[]):"); + buf.append("\nAttributes were not correct for the featur= e Type:"); + buf.append(schema.getTypeName()); + buf.append(". Made best guess:\n Recieved: "); + for( int i =3D 0; i < newAtts.length; i++ ) { + Object object =3D newAtts[i]; + buf.append(object=3D=3Dnull?"null":object.toString()= ); + buf.append(","); + } + buf.append("\nBest Guess: \n"); + for( int i =3D 0; i < relaxedAttrs.length; i++ ) { + Object object =3D relaxedAttrs[i]; + buf.append(object=3D=3Dnull?"null":object.toString()= ); + buf.append(","); + } + + WFSDataStoreFactory.logger.warning(buf.toString()); + } + return newAtts; + } + + /** + * Sets a single attribute for this feature, passed as a complex= object. If + * the attribute does not exist or the object does not conform t= o the + * internal feature type, an exception is thrown. + * + * @param xPath XPath representation of attribute location. + * @param attribute Feature attribute to set. + * + * @throws IllegalAttributeException Passed attribute does not m= atch + * feature type + */ + public void setAttribute(String xPath, Object attribute) + throws IllegalAttributeException { + int idx =3D schema.find(xPath); + + if (idx < 0) { + throw new IllegalAttributeException("No attribute named = " + xPath); + } + + setAttribute(idx, attribute); + } + + /** + * Gets the geometry for this feature. + * + * @return Geometry for this feature. + */ + public Geometry getDefaultGeometry() { + if (defaultGeomIndex =3D=3D -1) { + return null; + } + + return (Geometry) attributes[defaultGeomIndex]; + } + + /** + * Modifies the geometry. + * + * @param geometry All feature attributes. + * + * @throws IllegalAttributeException if the feature does not hav= e a + * geometry. + */ + public void setDefaultGeometry(Geometry geometry) + throws IllegalAttributeException { + + if (defaultGeomIndex < 0) { + throw new IllegalAttributeException( + "Feature does not have geometry"); + } + + attributes[defaultGeomIndex] =3D geometry; + bounds =3D null; + } + + /** + * Get the number of attributes this feature has. This is simply= a + * convenience method for calling + * getFeatureType().getNumberOfAttributes(); + * + * @return The total number of attributes this Feature contains. + */ + public int getNumberOfAttributes() { + return attributes.length; + } + + /** + * Get the total bounds of this feature which is calculated by d= oing a + * union of the bounds of each geometry this feature is associat= ed with. + * + * @return An Envelope containing the total bounds of this Featu= re. + * + * @task REVISIT: what to return if there are no geometries in t= he feature? + * For now we'll return a null envelope, make this part of + * interface? (IanS - by OGC standards, all Feature must h= ave geom) + */ + public Envelope getBounds() { + if (bounds =3D=3D null) { + bounds =3D new Envelope(); + + for (int i =3D 0, n =3D schema.getAttributeCount(); i < = n; i++) { + if (schema.getAttributeType(i) instanceof GeometryAt= tributeType ) { + Geometry g =3D (Geometry) attributes[i]; + + // IanS - check for null geometry! + if (g =3D=3D null) { + continue; + } + + Envelope e =3D g.getEnvelopeInternal(); + + // IanS + // as of JTS 1.3, expandToInclude does not check= to see if + // Envelope is "null", and simply adds the flagg= ed values. + // This ensures that this behavior does not occu= r. + if (!e.isNull()) { + bounds.expandToInclude(e); + } + } + } + } + + // lets be defensive + return new Envelope(bounds); + } + + /** + * Creates an exact copy of this feature. + * + * @return A default feature. + * + * @throws RuntimeException DOCUMENT ME! + */ + public Object clone() { + try { + DefaultFeature clone =3D (DefaultFeature) super.clone(); + + for (int i =3D 0; i < attributes.length; i++) { + try { + clone.setAttribute(i, attributes[i]); + } catch (IllegalAttributeException e1) { + throw new RuntimeException("The impossible has h= appened", e1); + } + } + + return clone; + } catch (CloneNotSupportedException e) { + throw new RuntimeException("The impossible has happened"= , e); + } + } + + /** + * Returns a string representation of this feature. + * + * @return A representation of this feature as a string. + */ + public String toString() { + String retString =3D "Feature[ id=3D" + getID() + " , "; + FeatureType featType =3D getFeatureType(); + + for (int i =3D 0, n =3D attributes.length; i < n; i++) { + retString +=3D (featType.getAttributeType(i).getName() += "=3D"); + retString +=3D attributes[i]; + + if ((i + 1) < n) { + retString +=3D " , "; + } + } + + return retString +=3D " ]"; + } + + /** + * returns a unique code for this feature + * + * @return A unique int + */ + public int hashCode() { + return featureId.hashCode() * schema.hashCode(); + } + + /** + * override of equals. Returns if the passed in object is equal= to this. + * + * @param obj the Object to test for equality. + * + * @return <code>true</code> if the object is equal, <code>false= </code> + * otherwise. + */ + public boolean equals(Object obj) { + if (obj =3D=3D null) { + return false; + } + + if (obj =3D=3D this) { + return true; + } + + if (!(obj instanceof Feature)) { + return false; + } + + Feature feat =3D (Feature) obj; + + if (!feat.getFeatureType().equals(schema)) { + return false; + } + + // this check shouldn't exist, by contract,=20 + //all features should have an ID. + if (featureId =3D=3D null) { + if (feat.getID() !=3D null) { + return false; + } + } + + if (!featureId.equals(feat.getID())) { + return false; + } + + for (int i =3D 0, ii =3D attributes.length; i < ii; i++) { + Object otherAtt =3D feat.getAttribute(i); + + if (attributes[i] =3D=3D null) { + if (otherAtt !=3D null) { + return false; + } + } else { + if (!attributes[i].equals(otherAtt)) { + if (attributes[i] instanceof Geometry + && otherAtt instanceof Geometry) { + // we need to special case Geometry + // as JTS is broken + // Geometry.equals( Object ) and Geometry.eq= uals( Geometry ) + // are different=20 + // (We should fold this knowledge into Attri= buteType...) + //=20 + if (!((Geometry) attributes[i]).equals( + (Geometry) otherAtt)) { + return false; + } + } else { + return false; + } + } + } + } + + return true; + } + + /** + * Gets the feature collection this feature is stored in. + * + * @return the collection that is the parent of this feature. + */ + public FeatureCollection getParent() { + return parent; + } + + /** + * Sets the parent collection this feature is stored in, if it i= s not + * already set. If it is set then this method does nothing. + * + * @param collection the collection to be set as parent. + */ + public void setParent(FeatureCollection collection) { + if (parent =3D=3D null) { + parent =3D collection; + } + } + + =20 + } } |