/projects/geotools-9.2/modules/library/data/src/main/java/org/geotools/data/store/ContentState.java
Java | 467 lines | 184 code | 45 blank | 238 comment | 35 complexity | 91e2b18cc0c2d794ddc75b4518786b53 MD5 | raw file
- /*
- * 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.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.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);
-
- /**
- * 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);
- }
- }