/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-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.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import org.geotools.data.BatchFeatureEvent; import org.geotools.data.Diff; import org.geotools.data.FeatureEvent; import org.geotools.data.FeatureEvent.Type; import org.geotools.data.FeatureListener; import org.geotools.data.FeatureSource; import org.geotools.data.Transaction; import org.geotools.data.Transaction.State; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.feature.Feature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.identity.FeatureId; /** * The state of an entry in a datastore, maintained on a per-transaction basis. For information * maintained on a typeName basis see {@link ContentEntry}. * * <h3>Data Cache Synchronization Required</h2> * <p> * The default ContentState implementation maintains cached values on a per transaction * basis: * <ul> * <li>feature type ({@link #getFeatureType()} * <li>number of features ({@link #getCount()} * <li>spatial extent ({@link #getBounds()} * </ul> * Other types of state depend on the data format and must be handled by a subclass. * <p> * This class is a "data object" used to store values and is not thread safe. It is up to * clients of this class to ensure that values are set in a thread-safe / synchronized * manner. For example: * <pre> * <code> * ContentState state = ...; * * //get the count * int count = state.getCount(); * if ( count == -1 ) { * synchronized ( state ) { * count = calculateCount(); * state.setCount( count ); * } * }</code></pre> * </p> * <h3>Event Notification</h3> * The list of listeners interested in event notification as features are modified and edited * is stored as part of ContentState. Each notification is considered to be broadcast from a * specific FeatureSource. Since several ContentFeatureStores can be on the same transaction * (and thus share a ContentState) the fire methods listed here require you pass in the * FeatureSource making the change; this requires that your individual FeatureWriters keep a * pointer to the ContentFeatureStore which created them. * <p> * You may also make use of {@link ContentFeatureSource#canEvent} value of {@code false} allowing * the base ContentFeatureStore class to take responsibility for sending event notifications. * * <h3>Transaction Independence</h3> * <p> * The default ContentState implementation also supports the handling of * {@link ContentFeatureSource#canTransaction} value of {@code false}. The implementation asks * ContentState to store a {@link Diff} which is used to record any modifications made until * commit is called. * <p> * Internally a {@link Transaction.State} is used to notify the implementation of * {{@link Transaction#commit} and {@link Transaction#rollback()}. * * <h3>Extension</h3> * This class may be extended if your implementaiton wishes to capture additional information * per transaction. A database implementation using a JDBCContentState to store a JDBC Connection * remains a good example. * <p> * Subclasses may extend (not override) the following methods: * <ul> * <li>{@link #flush()} - remember to call super.flush() * <li>{@link #close()} - remember to call super.close() * </ul> * Subclasses should also override {@link #copy()} to ensure any additional state they are keeping * is correctly accounted for. * </p> * * @author Jody Garnett (LISASoft) * @author Justin Deoliveira, The Open Planning Project * * @version 8.0 * @since 2.6 * @source $URL$ */ public class ContentState { /** * Transaction the state works from. */ protected Transaction tx; /** * entry maintaining the state */ protected ContentEntry entry; // CACHE /** * cached feature type */ protected SimpleFeatureType featureType; /** * cached number of features */ protected int count = -1; /** * cached bounds of features */ protected ReferencedEnvelope bounds; // EVENT NOTIFICATION SUPPORT /** * Even used for batch notification; used to collect the bounds and feature ids generated * over the course of a transaction. */ protected BatchFeatureEvent batchFeatureEvent; /** * observers */ protected List<FeatureListener> listeners = Collections.synchronizedList(new ArrayList<FeatureListener>()); // TRANSACTION SUPPORT /** * Callback used to issue batch feature events when commit/rollback issued * on the transaction. */ protected DiffTransactionState transactionState = new DiffTransactionState(this); /** * In memory Diff used to collect modifications made on this transaction. * * @see ContentFeatureSource#canTransaction */ Diff diff; /** * Creates a new state. * * @param entry The entry for the state. */ public ContentState(ContentEntry entry) { this.entry = entry; } /** * Creates a new state from a previous one. * <p> * All state from the specified <tt>state</tt> is copied. Therefore subclasses * extending this constructor should clone all mutable objects. * </p> * * @param state The existing state. */ protected ContentState(ContentState state) { this(state.getEntry()); featureType = state.featureType; count = state.count; bounds = state.bounds == null ? null : new ReferencedEnvelope( state.bounds ); batchFeatureEvent = null; } /** * The entry which maintains the state. */ public ContentEntry getEntry() { return entry; } /** * The transaction associated with the state. */ public Transaction getTransaction() { return tx; } /** * Sets the transaction associated with the state. */ public void setTransaction(Transaction tx) { this.tx = tx; if (tx != Transaction.AUTO_COMMIT) { tx.putState(this.entry, transactionState); } } /** * The cached feature type. */ public final SimpleFeatureType getFeatureType() { return featureType; } /** * Sets the cached feature type. */ public final void setFeatureType(SimpleFeatureType featureType) { this.featureType = featureType; } /** * The cached number of features. * */ public final int getCount() { return count; } /** * Sets the cached number of features. */ public final void setCount(int count) { this.count = count; } /** * The cached spatial extent. */ public final ReferencedEnvelope getBounds() { return bounds; } /** * Sets the cached spatial extent. */ public final void setBounds(ReferencedEnvelope bounds) { this.bounds = bounds; } /** * Adds a listener for collection events. * * @param listener The listener to add */ public final void addListener(FeatureListener listener) { listeners.add(listener); } /** * Removes a listener for collection events. * * @param listener The listener to remove */ public final void removeListener(FeatureListener listener) { listeners.remove(listener); } public BatchFeatureEvent getBatchFeatureEvent() { return batchFeatureEvent; } /** * Used to quickly test if any listeners are available. * * @return */ public final boolean hasListener() { if (!listeners.isEmpty()) { return true; } if (this.tx == Transaction.AUTO_COMMIT && this.entry.state.size() > 1) { // We are the auto commit state; and there is at least one other thread to notify return true; } return false; } /** * Creates a FeatureEvent indicating that the provided feature has been changed. * <p> * This method is provided to simplify event notification for implementors by constructing * a FeatureEvent internally (and then only if listeners are interested). You may find it * easier to construct your own FeatureEvent using {{@link #hasListener()} to check if * you need to fire events at all. * * @param source FeatureSource responsible for the change * @param feature The updated feature * @param before the bounds of the feature before the change */ public void fireFeatureUpdated(FeatureSource<?, ?> source, Feature feature, ReferencedEnvelope before) { if( feature == null){ return; // nothing changed } if (listeners.isEmpty() && tx != Transaction.AUTO_COMMIT) return; // nobody is listenting Filter filter = idFilter(feature); ReferencedEnvelope bounds = new ReferencedEnvelope(feature.getBounds()); if( bounds != null ){ bounds.expandToInclude(before); } FeatureEvent event = new FeatureEvent(source, Type.CHANGED, bounds, filter); fireFeatureEvent(event); } /** * Used to issue a Type.ADDED FeatureEvent indicating a new feature being created * * @param source * @param feature */ public final void fireFeatureAdded(FeatureSource<?, ?> source, Feature feature) { if (listeners.isEmpty() && tx != Transaction.AUTO_COMMIT) return; Filter filter = idFilter(feature); ReferencedEnvelope bounds = new ReferencedEnvelope(feature.getBounds()); FeatureEvent event = new FeatureEvent(source, Type.ADDED, bounds, filter); fireFeatureEvent(event); } public void fireFeatureRemoved(FeatureSource<?, ?> source, Feature feature) { if (listeners.isEmpty() && tx != Transaction.AUTO_COMMIT) return; Filter filter = idFilter(feature); ReferencedEnvelope bounds = new ReferencedEnvelope(feature.getBounds()); FeatureEvent event = new FeatureEvent(source, Type.REMOVED, bounds, filter); fireFeatureEvent(event); } /** * Helper method or building fid filters. */ Filter idFilter(Feature feature) { FilterFactory ff = this.entry.dataStore.getFilterFactory(); Set<FeatureId> fids = new HashSet<FeatureId>(); fids.add(feature.getIdentifier()); return ff.id(fids); } /** * Used to issue a single FeatureEvent. * <p> * If this content state is used for Transaction.AUTO_COMMIT the notification will be passed to * all interested parties. * <p> * If not this event will be recored as part of a BatchFeatureEvent that will to be issued using * issueBatchFeatureEvent() * * @param event */ public final void fireFeatureEvent(FeatureEvent event) { if (this.tx == Transaction.AUTO_COMMIT) { this.entry.notifiyFeatureEvent(this, event); } else { // we are not in auto-commit mode so we need to batch // up the changes for when the commit goes out if (batchFeatureEvent == null) { batchFeatureEvent = new BatchFeatureEvent(event.getFeatureSource()); } batchFeatureEvent.add(event); } if (listeners.isEmpty()) { return; } for (FeatureListener listener : listeners) { try { listener.changed(event); } catch (Throwable t) { this.entry.dataStore.LOGGER.log(Level.WARNING, "Problem issuing batch feature event " + event, t); } } } /** * Notifies all waiting listeners that a commit has been issued; this notification is also sent * to our */ public final void fireBatchFeatureEvent(boolean isCommit) { if (batchFeatureEvent == null) { return; } if (listeners.isEmpty()) { return; } if (isCommit) { batchFeatureEvent.setType(Type.COMMIT); } else { batchFeatureEvent.setType(Type.ROLLBACK); } for (FeatureListener listener : listeners) { try { listener.changed(batchFeatureEvent); } catch (Throwable t) { this.entry.dataStore.LOGGER.log(Level.WARNING, "Problem issuing batch feature event " + batchFeatureEvent, t); } } // Let others know a modifications was made this.entry.notifiyFeatureEvent(this, batchFeatureEvent); batchFeatureEvent = null; } /** * Clears cached state. * <p> * This method does not affect any non-cached state. This method may be extended by subclasses, * but not overiden. * </p> */ public void flush() { featureType = null; count = -1; bounds = null; } /** * Clears all state. * <p> * Any resources that the state holds onto (like a database connection) should be closed or * disposes when this method is called. This method may be extended by subclasses, but not * overiden. * </p> */ public void close() { featureType = null; if (listeners != null) { listeners.clear(); listeners = null; } } /** * Copies the state. * <p> * Subclasses shold override this method. Any mutable state objects should be cloned. * </p> * * @return A copy of the state. */ public ContentState copy() { return new ContentState(this); } }