/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.store; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.AbstractFeatureLocking; import org.geotools.data.AbstractFeatureSource; import org.geotools.data.AbstractFeatureStore; import org.geotools.data.DataSourceException; import org.geotools.data.DataStore; import org.geotools.data.DataUtilities; import org.geotools.data.Diff; import org.geotools.data.DiffFeatureReader; import org.geotools.data.EmptyFeatureReader; import org.geotools.data.EmptyFeatureWriter; import org.geotools.data.FeatureListener; import org.geotools.data.FeatureListenerManager; import org.geotools.data.FeatureLocking; import org.geotools.data.FeatureReader; import org.geotools.data.FeatureSource; import org.geotools.data.FeatureStore; import org.geotools.data.FeatureWriter; import org.geotools.data.FilteringFeatureReader; import org.geotools.data.InProcessLockingManager; import org.geotools.data.MaxFeatureReader; import org.geotools.data.Query; import org.geotools.data.ReTypeFeatureReader; import org.geotools.data.Transaction; import org.geotools.data.collection.DelegateFeatureReader; import org.geotools.feature.FeatureCollection; import org.geotools.feature.SchemaException; import org.geotools.feature.collection.DelegateFeatureIterator; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.util.SimpleInternationalString; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.InternationalString; import com.vividsolutions.jts.geom.Envelope; /** * Starting place for holding information about a FeatureType. * <p> * Like say for instance the FeatureType, its metadata and so on. * </p> * <p> * The default implemenation should contain enough information to wean * us off of AbstractDataStore. That is it should provide its own locking * and event notification. * </p> * <p> * There is a naming convention: * <ul> * <li> data access follows bean conventions: getTypeName(), getSchema() * <li> resource access methods follow Collections conventions reader(), * writer(), etc... * <li> overrrides are all protected and follow factory conventions: * createWriter(), createAppend(), createFeatureSource(), * createFeatureStore(), etc... * </ul> * <li> * </p> * <p> * Feedback: * <ul> * <li>even notification yes * <li>locking not - locking needs to be rejuggled * <li>naming convention really helps when subclassing * </ul> * </p> * * @author jgarnett * @source $URL$ */ public abstract class ActiveTypeEntry implements TypeEntry { protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.data.store"); /** * Remember parent. * <p> * We only refer to partent as a DataSource to keep hacks down. * </p> */ protected DataStore parent; private final SimpleFeatureType schema; private final Map metadata; private FeatureListenerManager listeners = new FeatureListenerManager(); /** Cached count */ int count; /** Cached bounds */ Envelope bounds; public ActiveTypeEntry( DataStore parent, SimpleFeatureType schema, Map metadata ) { this.schema = schema; this.metadata = metadata; this.parent = parent; // TODO: Should listen to events and empty the cache when edits occur } /** * TODO summary sentence for getDisplayName ... * * @see org.geotools.data.TypeEntry#getDisplayName() */ public InternationalString getDisplayName() { return new SimpleInternationalString( schema.getTypeName() ); } /** * TODO summary sentence for getDescription ... * * @see org.geotools.data.TypeEntry#getDescription() */ public InternationalString getDescription() { return null; } /** * TODO summary sentence for getFeatureType ... * * @see org.geotools.data.TypeEntry#getFeatureType() * @throws IOException */ public SimpleFeatureType getFeatureType() { return schema; } /** * Bounding box for associated Feature Collection, will be calcualted as needed. * <p> * Note bounding box is returned in lat/long - the coordinate system of the default geometry * is used to provide this reprojection. * </p> */ // public synchronized Envelope getBounds() { // if( bounds != null ) { // bounds = createBounds(); // } // return bounds; // } /** * Override to provide your own optimized calculation of bbox. * <p> * Default impelmenation uses the a feature source. * * @return BBox in lat long */ // protected Envelope createBounds() { // Envelope bbox; // try { // FeatureSource<SimpleFeatureType, SimpleFeature> source = getFeatureSource(); // bbox = source.getBounds(); // if( bbox == null ){ // bbox = source.getFeatures().getBounds(); // } // try { // CoordinateReferenceSystem cs = source.getSchema().getDefaultGeometry().getCoordinateSystem(); // bbox = JTS.toGeographic(bbox,cs); // } // catch (Error badRepoject ) { // badRepoject.printStackTrace(); // } // } catch (Exception e) { // bbox = new Envelope(); // } // return bbox; // } /** * TODO summary sentence for getCount ... * * @see org.geotools.data.TypeEntry#getCount() */ // public int getCount() { // if( count != -1 ) return count; // try { // FeatureSource<SimpleFeatureType, SimpleFeature> source = getFeatureSource(); // count = source.getCount( Query.ALL ); // if( count == -1 ){ // count = source.getFeatures().size(); // } // } catch (IOException e) { // bounds = new Envelope(); // } // return count; // } /** * Get unique data name for this CatalogEntry. * * @return namespace:typeName */ // public String getDataName() { // return schema.getNamespace().toString() + ":"+ schema.getTypeName(); // } // // public Object resource() { // try { // return getFeatureSource(); // } catch (IOException e) { // return null; // } // } /** * Metadata names from metadata.keySet(). * * @return metadata names mentioned in metadata.keySet(); */ public String[] getMetadataNames() { return (String[]) metadata.keySet().toArray( new String[ metadata.size() ] ); } /** * Map of metadata by name. * * @return Map of metadata by name */ public Map metadata() { return Collections.unmodifiableMap( metadata ); } /** Manages listener lists for FeatureSource<SimpleFeatureType, SimpleFeature> implementation */ public FeatureListenerManager listenerManager = new FeatureListenerManager(); // // Start of TypeEntry framework // public String getTypeName() { return schema.getTypeName(); } // public FeatureType getSchema() { // return schema; // } /** * Create a new FeatueSource allowing interaction with content. * <p> * Subclass may optionally implement: * <ul> * <li>FeatureStore - to allow read/write access * <li>FeatureLocking - for locking support * </ul> * This choice may even be made a runtime (allowing the api * to represent a readonly file). * </p> * <p> * Several default implemenations are provided * * @return FeatureLocking allowing access to content. */ // public FeatureSource<SimpleFeatureType, SimpleFeature> getFeatureSource() throws IOException { // return createFeatureSource(); // } /** * Access a FeatureReader<SimpleFeatureType, SimpleFeature> providing access to Feature information. * <p> * This implementation passes off responsibility to the following overrideable methods: * <ul> * <li>getFeatureReader(String typeName) - subclass *required* to implement * </ul> * </p> * <p>If you can handle some aspects of Query natively (say expressions or reprojection) override the following: * <li> * <li>getFeatureReader(typeName, query) - override to handle query natively * <li>getUnsupportedFilter(typeName, filter) - everything you cannot handle natively * <li>getFeatureReader(String typeName) - you must implement this, but you could point it back to getFeatureReader( typeName, Query.ALL ); * </ul> * </p> */ public FeatureReader<SimpleFeatureType, SimpleFeature> reader(Query query, Transaction transaction) throws IOException { if ( transaction == null ) { throw new NullPointerException( "Transaction null, did you mean Transaction.AUTO_COMMIT" ); } FeatureCollection<SimpleFeatureType, SimpleFeature> features = createFeatureSource().getFeatures( query ); FeatureReader<SimpleFeatureType, SimpleFeature> reader = new DelegateFeatureReader<SimpleFeatureType, SimpleFeature>( features.getSchema(), features.features() ); //wrap in diff reader if transaction specified if ( !transaction.equals( Transaction.AUTO_COMMIT) ) { reader = new DiffFeatureReader<SimpleFeatureType, SimpleFeature>( reader, state( transaction ).diff() ); } return reader; // Filter filter = query.getFilter(); // String typeName = query.getTypeName(); // String propertyNames[] = query.getPropertyNames(); // // if (filter == null) { // throw new NullPointerException("getFeatureReader requires Filter: " // + "did you mean Filter.INCLUDE?"); // } // if( typeName == null ){ // throw new NullPointerException( // "getFeatureReader requires typeName: " // + "use getTypeNames() for a list of available types"); // } // if (transaction == null) { // throw new NullPointerException( // "getFeatureReader requires Transaction: " // + "did you mean to use Transaction.AUTO_COMMIT?"); // } // FeatureType featureType = schema; // // if( propertyNames != null || query.getCoordinateSystem() != null ){ // try { // featureType = DataUtilities.createSubType( featureType, propertyNames, query.getCoordinateSystem() ); // } catch (SchemaException e) { // LOGGER.log( Level.FINEST, e.getMessage(), e); // throw new DataSourceException( "Could not create Feature Type for query", e ); // // } // } // if ( filter == Filter.EXCLUDE || filter.equals( Filter.EXCLUDE )) { // return new EmptyFeatureReader(featureType); // } // //GR: allow subclases to implement as much filtering as they can, // //by returning just it's unsupperted filter // filter = getUnsupportedFilter( filter); // if(filter == null){ // throw new NullPointerException("getUnsupportedFilter shouldn't return null. Do you mean Filter.INCLUDE?"); // } // // // This calls our subclass "simple" implementation // // All other functionality will be built as a reader around // // this class // // // FeatureReader<SimpleFeatureType, SimpleFeature> reader = createReader( query); // // if (!filter.equals( Filter.INCLUDE ) ) { // reader = new FilteringFeatureReader<SimpleFeatureType, SimpleFeature>(reader, filter); // } // // if (transaction != Transaction.AUTO_COMMIT) { // Diff diff = state(transaction).diff(); // reader = new DiffFeatureReader(reader, diff); // } // // if (!featureType.equals(reader.getFeatureType())) { // LOGGER.fine("Recasting feature type to subtype by using a ReTypeFeatureReader"); // reader = new ReTypeFeatureReader(reader, featureType); // } // // if (query.getMaxFeatures() != Query.DEFAULT_MAX) { // reader = new MaxFeatureReader(reader, query.getMaxFeatures()); // } // return reader; } TypeDiffState state(Transaction transaction) { synchronized (transaction) { TypeDiffState state = (TypeDiffState) transaction.getState(this); if (state == null) { state = new TypeDiffState(this); transaction.putState(this, state); } return state; } } public void fireAdded( SimpleFeature newFeature, Transaction transaction ){ ReferencedEnvelope bounds = ReferencedEnvelope.reference(newFeature != null ? newFeature.getBounds() : null); listenerManager.fireFeaturesAdded( schema.getTypeName(), transaction, bounds, false ); } public void fireRemoved( SimpleFeature removedFeature, Transaction transaction ){ ReferencedEnvelope bounds = ReferencedEnvelope.reference(removedFeature != null ? removedFeature.getBounds() : null); listenerManager.fireFeaturesRemoved( schema.getTypeName(), transaction, bounds, false ); } public void fireChanged( SimpleFeature before, SimpleFeature after, Transaction transaction ){ String typeName = after.getFeatureType().getTypeName(); ReferencedEnvelope bounds = new ReferencedEnvelope(); bounds.include( before.getBounds() ); bounds.include( after.getBounds() ); listenerManager.fireFeaturesChanged( typeName, transaction, bounds, false ); } // // Start of Overrides // public abstract FeatureSource<SimpleFeatureType, SimpleFeature> createFeatureSource(); /** * Override to provide readonly access * @param schema * @return FeatureSource<SimpleFeatureType, SimpleFeature> backed by this TypeEntry. */ // protected FeatureSource<SimpleFeatureType, SimpleFeature> createFeatureSource() { // return new AbstractFeatureSource() { // public DataStore getDataStore() { // return parent; // } // public void addFeatureListener( FeatureListener listener ) { // listeners.addFeatureListener( this, listener ); // } // public void removeFeatureListener( FeatureListener listener ) { // listeners.addFeatureListener( this, listener ); // } // public FeatureType getSchema() { // return schema; // } // }; // } /** * Create the FeatureSource, override for your own custom implementation. * <p> * Default implementation makes use of DataStore getReader( ... ), and listenerManager. * </p> */ protected FeatureSource<SimpleFeatureType, SimpleFeature> createFeatureSource( final SimpleFeatureType featureType ) { return new AbstractFeatureSource() { public DataStore getDataStore() { return parent; } public void addFeatureListener(FeatureListener listener) { listenerManager.addFeatureListener(this, listener); } public void removeFeatureListener(FeatureListener listener) { listenerManager.removeFeatureListener(this, listener); } public SimpleFeatureType getSchema() { return featureType; } }; } /** * Create the FeatureStore, override for your own custom implementation. */ protected FeatureStore<SimpleFeatureType, SimpleFeature> createFeatureStore() { // This implementation needs FeatureWriters to work // please provide your own override for a datastore that does not // support FeatureWriters (like WFS). // return new AbstractFeatureStore() { public DataStore getDataStore() { return parent; } public void addFeatureListener(FeatureListener listener) { listenerManager.addFeatureListener(this, listener); } public void removeFeatureListener( FeatureListener listener) { listenerManager.removeFeatureListener(this, listener); } public SimpleFeatureType getSchema() { return schema; } }; } /** * Create the FeatureLocking, override for your own custom implementation. * <p> * Warning: The default implementation of this method uses lockingManger. * You must override this method if you support your own locking system (like WFS). * <p> */ protected FeatureLocking<SimpleFeatureType, SimpleFeature> createFeatureLocking() { return new AbstractFeatureLocking() { public DataStore getDataStore() { return parent; } public void addFeatureListener(FeatureListener listener) { listenerManager.addFeatureListener(this, listener); } public void removeFeatureListener( FeatureListener listener) { listenerManager.removeFeatureListener(this, listener); } public SimpleFeatureType getSchema() { return schema; } }; } /** * Create a reader for this query. * <p> * Subclass must override this to actually aquire content. * </p> * @param typeName * @param query * @return FeatureReader<SimpleFeatureType, SimpleFeature> for all content */ public FeatureReader<SimpleFeatureType, SimpleFeature> createReader() { return new EmptyFeatureReader<SimpleFeatureType, SimpleFeature>( schema ); } /** * GR: this method is called from inside getFeatureReader(Query ,Transaction ) * to allow subclasses return an optimized FeatureReader<SimpleFeatureType, SimpleFeature> wich supports the * filter and attributes truncation specified in <code>query</code> * <p> * A subclass that supports the creation of such an optimized FeatureReader * shold override this method. Otherwise, it just returns * <code>getFeatureReader(typeName)</code> * <p> */ protected FeatureReader<SimpleFeatureType, SimpleFeature> createReader(Query query) throws IOException { return createReader(); } /** * GR: if a subclass supports filtering, it should override this method * to return the unsupported part of the passed filter, so a * FilteringFeatureReader will be constructed upon it. Otherwise it will * just return the same filter. * <p> * If the complete filter is supported, the subclass must return <code>Filter.INCLUDE</code> * </p> */ protected Filter getUnsupportedFilter( Filter filter ) { return filter; } /* (non-Javadoc) * @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String, org.geotools.data.Transaction) */ public FeatureWriter<SimpleFeatureType, SimpleFeature> writer( Transaction transaction) throws IOException { if (transaction == null) { throw new NullPointerException( "getFeatureWriter requires Transaction: " + "did you mean to use Transaction.AUTO_COMMIT?"); } FeatureWriter<SimpleFeatureType, SimpleFeature> writer; if (transaction == Transaction.AUTO_COMMIT) { writer = createWriter(); } else { writer = state(transaction).writer(); } if (parent.getLockingManager() != null && parent.getLockingManager() instanceof InProcessLockingManager ) { // subclass has not provided locking so we will // fake it with InProcess locks InProcessLockingManager lockingManger = (InProcessLockingManager) parent.getLockingManager(); writer = lockingManger.checkedWriter(writer, transaction); } return writer; } /** * Low level feature writer access. * <p> * This is the only method you must implement to aquire content. * </p> * @return Subclass must supply a FeatureWriter */ protected FeatureWriter<SimpleFeatureType, SimpleFeature> createWriter() { return new EmptyFeatureWriter( schema ); } /** * It would be great to kill this method, and add a "skipToEnd" method to featureWriter? * <p> * Override this if you can provide a native optimization for this. * (aka copy file, open the file in append mode, replace origional on close). * </p> */ protected FeatureWriter<SimpleFeatureType, SimpleFeature> createAppend( Transaction transaction) throws IOException { FeatureWriter<SimpleFeatureType, SimpleFeature> writer = writer( transaction ); while (writer.hasNext()) { writer.next(); // Hmmm this would be a use for skip() then? } return writer; } }