/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-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.lang.reflect.Constructor;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.AbstractDataStore;
import org.geotools.data.DataUtilities;
import org.geotools.data.Diff;
import org.geotools.data.DiffFeatureReader;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureLock;
import org.geotools.data.FeatureLockException;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureSource;
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.QueryCapabilities;
import org.geotools.data.ReTypeFeatureReader;
import org.geotools.data.ResourceInfo;
import org.geotools.data.Transaction;
import org.geotools.data.TransactionStateDiff;
import org.geotools.data.crs.ReprojectFeatureReader;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.sort.SortedFeatureReader;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.filter.function.Collection_AverageFunction;
import org.geotools.filter.function.Collection_BoundsFunction;
import org.geotools.filter.function.Collection_MaxFunction;
import org.geotools.filter.function.Collection_MedianFunction;
import org.geotools.filter.function.Collection_MinFunction;
import org.geotools.filter.function.Collection_SumFunction;
import org.geotools.filter.function.Collection_UniqueFunction;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.NullProgressListener;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.Id;
import org.opengis.filter.identity.FeatureId;
import org.opengis.filter.sort.SortBy;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Abstract implementation of FeatureSource.
* <p>
* This feature source works off of operations provided by {@link FeatureCollection}.
* Individual SimpleFeatureCollection implementations are provided by subclasses:
* <ul>
* {@link #all(ContentState)}: Access to entire dataset
* {@link #filtered(ContentState, Filter)}: Access to filtered dataset
* </ul>
* </p>
* <p>
* Even though a feature source is read-only, this class is transaction aware.
* (see {@link #setTransaction(Transaction)}. The transaction is taken into
* account during operations such as {@link #getCount()} and {@link #getBounds()}
* since these values may be affected by another operation (like writing to
* a FeautreStore) working against the same transaction.
* </p>
* <p>
* Subclasses must also implement the {@link #buildFeatureType()} method which
* builds the schema for the feature source.
* </p>
*
* @author Jody Garnett, Refractions Research Inc.
* @author Justin Deoliveira, The Open Planning Project
*
*
*
* @source $URL$
*/
public abstract class ContentFeatureSource implements SimpleFeatureSource {
/**
* The entry for the feature source.
*/
protected ContentEntry entry;
/**
* The transaction to work from
*/
protected Transaction transaction;
/**
* current feature lock
*/
protected FeatureLock lock = FeatureLock.TRANSACTION;
/**
* hints
*/
protected Set<Hints.Key> hints;
/**
* The query defining the feature source
*/
protected Query query;
/**
* cached feature type (only set if this instance is a view)
*/
protected SimpleFeatureType schema;
/**
* The query capabilities returned by this feature source
*/
protected QueryCapabilities queryCapabilities;
/**
* Creates the new feature source from a query.
* <p>
* The <tt>query</tt> is taken into account for any operations done against
* the feature source. For example, when getReader(Query) is called the
* query specified is "joined" to the query specified in the constructor.
* The <tt>query</tt> parameter may be <code>null</code> to specify that the
* feature source represents the entire set of features.
* </p>
*/
public ContentFeatureSource(ContentEntry entry, Query query) {
this.entry = entry;
this.query = query;
//set up hints
hints = new HashSet<Hints.Key>();
hints.add( Hints.JTS_GEOMETRY_FACTORY );
hints.add( Hints.JTS_COORDINATE_SEQUENCE_FACTORY );
//add subclass specific hints
addHints( hints );
//make hints unmodifiable
hints = Collections.unmodifiableSet( hints );
}
/**
* The entry for the feature source.
*/
public ContentEntry getEntry() {
return entry;
}
/**
* The current transaction the feature source is working against.
* <p>
* This transaction is used to derive the state for the feature source. A
* <code>null</code> value for a transaction represents the auto commit
* transaction: {@link Transaction#AUTO_COMMIT}.
* </p>
* @see {@link #getState()}.
*/
public Transaction getTransaction() {
return transaction;
}
/**
* Sets the current transaction the feature source is working against.
* <p>
* <tt>transaction</tt> may be <code>null</code>. This signifies that the
* auto-commit transaction is used: {@link Transaction#AUTO_COMMIT}.
* </p>
* @param transaction The new transaction, or <code>null</code>.
*/
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
}
/**
* The current state for the feature source.
* <p>
* This value is derived from current transaction of the feature source.
* </p>
*
* @see {@link #setTransaction(Transaction)}.
*/
public ContentState getState() {
return entry.getState(transaction);
}
/**
* The datastore that this feature source originated from.
* <p>
* Subclasses may wish to extend this method in order to type narrow its
* return type.
* </p>
*/
public ContentDataStore getDataStore() {
return entry.getDataStore();
}
/**
* Indicates if this feature source is actually a view.
*/
public final boolean isView() {
return query != null && query != Query.ALL;
}
/**
* A default ResourceInfo with a generic description.
* <p>
* Subclasses should override to provide an explicit ResourceInfo
* object for their content.
* @return description of features contents
*/
public ResourceInfo getInfo() {
return new ResourceInfo(){
final Set<String> words = new HashSet<String>();
{
words.add("features");
words.add( ContentFeatureSource.this.getSchema().getTypeName() );
}
public ReferencedEnvelope getBounds() {
try {
return ContentFeatureSource.this.getBounds();
} catch (IOException e) {
return null;
}
}
public CoordinateReferenceSystem getCRS() {
return ContentFeatureSource.this.getSchema().getCoordinateReferenceSystem();
}
public String getDescription() {
return null;
}
public Set<String> getKeywords() {
return words;
}
public String getName() {
return ContentFeatureSource.this.getSchema().getTypeName();
}
public URI getSchema() {
Name name = ContentFeatureSource.this.getSchema().getName();
URI namespace;
try {
namespace = new URI( name.getNamespaceURI() );
return namespace;
} catch (URISyntaxException e) {
return null;
}
}
public String getTitle() {
Name name = ContentFeatureSource.this.getSchema().getName();
return name.getLocalPart();
}
};
}
/**
* Returns the same name than the feature type (ie,
* {@code getSchema().getName()} to honor the simple feature land common
* practice of calling the same both the Features produces and their types
*
* @since 2.5
* @see FeatureSource#getName()
*/
public Name getName() {
return getSchema().getName();
}
/**
* Returns the feature type or the schema of the feature source.
* <p>
* This method delegates to {@link #buildFeatureType()}, which must be
* implemented by subclasses. The result is cached in
* {@link ContentState#getFeatureType()}.
* </p>
*/
public final SimpleFeatureType getSchema() {
//check schema override
if ( schema != null ) {
return schema;
}
SimpleFeatureType featureType = getAbsoluteSchema();
//this may be a view
if ( query != null && query.getPropertyNames() != Query.ALL_NAMES) {
synchronized ( this ) {
if ( schema == null ) {
schema = SimpleFeatureTypeBuilder.retype(featureType, query.getPropertyNames() );
}
}
return schema;
}
return featureType;
}
/**
* Helper method for returning the underlying schema of the feature source.
* This is a non-view this is the same as calling getSchema(), but in the
* view case the underlying "true" schema is returned.
*/
protected final SimpleFeatureType getAbsoluteSchema() {
//load the type from the state shared among feature sources
ContentState state = entry.getState(transaction);
SimpleFeatureType featureType = state.getFeatureType();
if (featureType == null) {
//build and cache it
synchronized (state) {
if (featureType == null) {
try {
featureType = buildFeatureType();
} catch (IOException e) {
throw new RuntimeException(e);
}
state.setFeatureType( featureType);
}
}
}
return featureType;
}
/**
* Returns the bounds of the entire feature source.
* <p>
* This method delegates to {@link #getBounds(Query)}:
* <pre>
* <code>return getBounds(Query.ALL)</code>.
* </pre>
* </p>
*/
public final ReferencedEnvelope getBounds() throws IOException {
//return all(entry.getState(transaction)).getBounds();
return getBounds(Query.ALL);
}
/**
* Returns the bounds of the results of the specified query against the
* feature source.
* <p>
* This method calls through to {@link #getBoundsInternal(Query)} which
* subclasses must implement. It also contains optimizations which check
* state for cached values.
* </p>
*/
public final ReferencedEnvelope getBounds(Query query) throws IOException {
query = joinQuery( query );
query = resolvePropertyNames(query);
//
//calculate the bounds
//
ReferencedEnvelope bounds;
if(!canTransact() && transaction != null && transaction != Transaction.AUTO_COMMIT) {
// grab the in memory transaction diff
DiffTransactionState state = (DiffTransactionState) getTransaction().getState(getEntry());
Diff diff = state.getDiff();
// don't compute the bounds of the features that are modified or removed in the diff
Iterator it = diff.getModified().values().iterator();
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
Set<FeatureId> modifiedFids = new HashSet<FeatureId>();
while(it.hasNext()){
SimpleFeature feature = (SimpleFeature) it.next();
modifiedFids.add(ff.featureId(feature.getID()));
}
Id idFilter = ff.id(modifiedFids);
Query q = new Query(query);
q.setFilter(ff.and(idFilter, query.getFilter()));
bounds = getBoundsInternal(q);
// update with the diff contents, all added feaatures and all modified, not deleted ones
if(bounds != null) {
// new ones
it = diff.getAdded().values().iterator();
while(it.hasNext()){
SimpleFeature feature = (SimpleFeature) it.next();
BoundingBox fb = feature.getBounds();
if(fb != null) {
bounds.expandToInclude(ReferencedEnvelope.reference(fb));
}
}
// modified ones
it = diff.getModified().values().iterator();
while(it.hasNext()){
SimpleFeature feature = (SimpleFeature) it.next();
if(feature != TransactionStateDiff.NULL) {
BoundingBox fb = feature.getBounds();
if(fb != null) {
bounds.expandToInclude(ReferencedEnvelope.reference(fb));
}
}
}
}
} else {
bounds = getBoundsInternal(query);
}
return bounds;
}
/**
* Calculates the bounds of a specified query. Subclasses must implement this
* method.
*/
protected abstract ReferencedEnvelope getBoundsInternal(Query query) throws IOException;
/**
* Returns the count of the number of features of the feature source.
* <p>
* This method calls through to {@link #getCount(Query)} which
* subclasses must implement. It also contains optimizations which check
* state for cached values.
* </p>
*/
public final int getCount(Query query) throws IOException {
query = joinQuery( query );
query = resolvePropertyNames( query );
//calculate the count
int count = getCountInternal( query );
// if the internal actually counted, consider transactions
if(count >= 0 && !canTransact() && transaction != null && transaction != Transaction.AUTO_COMMIT) {
DiffTransactionState state = (DiffTransactionState) getTransaction().getState(getEntry());
Diff diff = state.getDiff();
synchronized (diff) {
// consider newly added features that satisfy the filter
Iterator it = diff.getAdded().values().iterator();
Filter filter = query.getFilter();
while(it.hasNext()){
Object feature = it.next();
if(filter.evaluate(feature)) {
count++;
}
}
// consider removed features that satisfy the filter
it = diff.getModified().values().iterator();
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
Set<FeatureId> modifiedFids = new HashSet<FeatureId>();
int modifiedPostCount = 0;
while(it.hasNext()){
SimpleFeature feature = (SimpleFeature) it.next();
if(feature == TransactionStateDiff.NULL) {
count--;
} else {
modifiedFids.add(ff.featureId(feature.getID()));
if(filter.evaluate(feature)) {
modifiedPostCount++;
}
}
}
// consider the updated feature if any, we need to know how
// many of those matched the filter before
if(modifiedFids.size() > 0) {
Id idFilter = ff.id(modifiedFids);
Query q = new Query(query);
q.setFilter(ff.and(idFilter, query.getFilter()));
int modifiedPreCount = getCountInternal(q);
if(modifiedPreCount == -1) {
return -1;
} else {
count = count + modifiedPostCount - modifiedPreCount;
}
}
}
}
return count;
}
/**
* Calculates the number of features of a specified query. Subclasses must
* implement this method.
*/
protected abstract int getCountInternal(Query query) throws IOException;
/**
* Returns the feature collection of all the features of the feature source.
*/
public final ContentFeatureCollection getFeatures() throws IOException {
Query query = joinQuery(Query.ALL);
return new ContentFeatureCollection( this, query );
}
/**
* Returns a feature reader for all features.
* <p>
* This method calls through to {@link #getReader(Query)}.
* </p>
*/
public final FeatureReader<SimpleFeatureType, SimpleFeature> getReader() throws IOException {
return getReader(Query.ALL);
}
/**
* Returns the feature collection if the features of the feature source which
* meet the specified query criteria.
*/
public final ContentFeatureCollection getFeatures(Query query)
throws IOException {
query = joinQuery( query );
return new ContentFeatureCollection( this, query );
}
/**
* Returns a reader for the features specified by a query.
*
*/
public final FeatureReader<SimpleFeatureType, SimpleFeature> getReader(Query query) throws IOException {
query = joinQuery( query );
query = resolvePropertyNames(query);
// see if we need to enable native sorting in order to support stable paging
final int offset = query.getStartIndex() != null ? query.getStartIndex() : 0;
if(offset > 0 & query.getSortBy() == null) {
Query dq = new Query(query);
dq.setSortBy(new SortBy[] {SortBy.NATURAL_ORDER});
query = dq;
}
//check for a join
if (!query.getJoins().isEmpty() && getQueryCapabilities().isJoiningSupported()) {
throw new IOException("Feature source does not support joins");
}
FeatureReader<SimpleFeatureType, SimpleFeature> reader = getReaderInternal( query );
//
//apply wrappers based on subclass capabilities
//
// transactions
if( !canTransact() && transaction != null && transaction != Transaction.AUTO_COMMIT) {
DiffTransactionState state = (DiffTransactionState) getTransaction().getState(getEntry());
reader = new DiffFeatureReader<SimpleFeatureType, SimpleFeature>(reader, state.getDiff());
}
//filtering
if ( !canFilter() ) {
if (query.getFilter() != null && query.getFilter() != Filter.INCLUDE ) {
reader = new FilteringFeatureReader<SimpleFeatureType, SimpleFeature>( reader, query.getFilter() );
}
}
//retyping
if ( !canRetype() ) {
if ( query.getPropertyNames() != Query.ALL_NAMES ) {
//rebuild the type and wrap the reader
SimpleFeatureType target =
SimpleFeatureTypeBuilder.retype(getSchema(), query.getPropertyNames());
// do an equals check because we may have needlessly retyped (that is,
// the subclass might be able to only partially retype)
if ( !target.equals( reader.getFeatureType() ) ) {
reader = new ReTypeFeatureReader( reader, target, false );
}
}
}
// sorting
if ( query.getSortBy() != null && query.getSortBy().length != 0 ) {
if ( !canSort() ) {
reader = new SortedFeatureReader(DataUtilities.simple(reader), query);
}
}
// offset
if( !canOffset() && offset > 0 ) {
// skip the first n records
for(int i = 0; i < offset && reader.hasNext(); i++) {
reader.next();
}
}
// max feature limit
if ( !canLimit() ) {
if (query.getMaxFeatures() != -1 && query.getMaxFeatures() < Integer.MAX_VALUE ) {
reader = new MaxFeatureReader<SimpleFeatureType, SimpleFeature>(reader, query.getMaxFeatures());
}
}
// reprojection
if ( !canReproject() ) {
if (query.getCoordinateSystemReproject() != null) {
try {
reader = new ReprojectFeatureReader(reader, query.getCoordinateSystemReproject());
} catch (Exception e) {
if(e instanceof IOException)
throw (IOException) e;
else
throw (IOException) new IOException("Error occurred trying to reproject data").initCause(e);
}
}
}
// TODO: Use InProcessLockingManager to assert read locks?
if(!canLock()) {
// LockingManager lockingManager = getDataStore().getLockingManager();
// return ((InProcessLockingManager)lockingManager).checkedReader(reader, transaction);
}
return reader;
}
/**
* Visit the features matching the provided query.
* <p>
* The default information will use getReader( query ) and pass each feature to the provided visitor.
* Subclasses should override this method to optimise common visitors:
* <ul>
* <li> {@link Collection_AverageFunction}
* <li> {@link Collection_BoundsFunction}
* <li> (@link Collection_CountFunction}
* <li> {@link Collection_MaxFunction}
* <li> {@link Collection_MedianFunction}
* <li> {@link Collection_MinFunction}
* <li> {@link Collection_SumFunction}
* <li> {@link Collection_UniqueFunction}
* </ul>
* Often in the case of Filter.INCLUDES the information can be determined from a file header or metadata table.
* <p>
*
* @param visitor Visitor called for each feature
* @param progress Used to report progress; and errors on a feature by feature basis
* @throws IOException
*/
public void accepts( Query query, org.opengis.feature.FeatureVisitor visitor,
org.opengis.util.ProgressListener progress) throws IOException {
if( progress == null ) {
progress = new NullProgressListener();
}
if ( handleVisitor(query,visitor) ) {
//all good, subclass handled
return;
}
//subclass could not handle, resort to manually walkign through
FeatureReader<SimpleFeatureType, SimpleFeature> reader = getReader(query);
try{
float size = progress instanceof NullProgressListener ? 0.0f : (float) getCount( query );
float position = 0;
progress.started();
while( reader.hasNext() ){
if (size > 0) progress.progress( position++/size );
try {
SimpleFeature feature = reader.next();
visitor.visit(feature);
}
catch( Exception erp ){
progress.exceptionOccurred( erp );
}
}
}
finally {
progress.complete();
reader.close();
}
}
/**
* Subclass method which allows subclasses to natively handle a visitor.
* <p>
* Subclasses would override this method and return true in cases where the specific
* visitor could be handled without iterating over the entire result set of query. An
* example would be handling visitors that calculate aggregate values.
* </p>
* @param query The query being made.
* @param visitor The visitor to
*
* @return true if the visitor can be handled natively, otherwise false.
*/
protected boolean handleVisitor( Query query, FeatureVisitor visitor ) throws IOException {
return false;
}
/**
* Subclass method for returning a native reader from the datastore.
* <p>
* It is important to note that if the native reader intends to handle any
* of the following natively:
* <ul>
* <li>reprojection</li>
* <li>filtering</li>
* <li>max feature limiting</li>
* <li>sorting</li>
* <li>locking</li>
* <li>transactions</li>
* </ul>
* Then it <b>*must*</b> set the corresponding flags to <code>true</code>:
* <ul>
* <li>{@link #canReproject()} - handles {@link Query#getCoordinateSystemReproject()} internally.
* Example would be PostGIS using Proj to handle reproejction internally</li>
* <li>{@link #canFilter()} - handles {@link Query#getFilter() internally.</li>
* <li>{@link #canLimit()} - handles {@link Query#getMaxFeatures()} and {@link Query#getStartIndex()} internally.</li>
* <li>{@link #canSort()} - handles {@link Query#getSortBy()} natively.</li>
* <li>{@link #canRetype()} - handles {@link Query#getProperties()} natively. Example would
* be only parsing the properties the user asks for from an XML file</li>
* <li>{@link #canLock()} - handles read-locks natively</li>
* <li>{@link #canTransact()} - handles transactions natively</li>
* </ul>
* </p>
*
*/
protected abstract FeatureReader<SimpleFeatureType, SimpleFeature> getReaderInternal( Query query ) throws IOException;
/**
* Determines if the datastore can natively perform reprojection..
* <p>
* If the subclass can handle reprojection natively then it should override
* this method to return <code>true</code>. In this case it <b>must</b> do
* the reprojection or throw an exception.
* </p>
* <p>
* Not overriding this method or returning <code>false</code> will case the
* feature reader created by the subclass to be wrapped in a reprojecting
* decorator when the query specifies a coordinate system reproject.
* </p>
* @see ReprojectFeatureReader
*/
protected boolean canReproject() {
return false;
}
/**
* Determines if the datastore can natively limit the number of features
* returned in a query.
* <p>
* If the subclass can handle a map feature cap natively then it should override
* this method to return <code>true</code>. In this case it <b>must</b> do
* the cap or throw an exception.
* </p>
* <p>
* Not overriding this method or returning <code>false</code> will case the
* feature reader created by the subclass to be wrapped in a max feature capping
* decorator when the query specifies a max feature cap.
* </p>
* @see MaxFeatureReader
*/
protected boolean canLimit() {
return false;
}
/**
* Determines if the datastore can natively skip the first <code>offset</code> number of features
* returned in a query.
* <p>
* If the subclass can handle a map feature cap natively then it should override
* this method to return <code>true</code>. In this case it <b>must</b> do
* the cap or throw an exception.
* </p>
* <p>
* Not overriding this method or returning <code>false</code> will case the
* feature reader created by the subclass to be be accesset offset times before
* being returned to the caller.
* </p>
*/
protected boolean canOffset() {
return false;
}
/**
* Determines if the datastore can natively perform a filtering.
* <p>
* If the subclass can handle filtering natively it should override this method
* to return <code>true</code>. In this case it <b>must</b> do the filtering
* or throw an exception. This includes the case of partial native filtering
* where the datastore can only handle part of the filter natively. In these
* cases it is up to the subclass to apply a decorator to the reader it returns
* which will handle any part of the filter can was not applied natively. See
* {@link FilteringFeatureReader}.
* </p>
* <p>
* Not overriding this method or returning <code>false</code> will cause the
* feature reader created by the subclass to be wrapped in a filtering feature
* reader when the query specifies a filter. See {@link FilteringFeatureReader}.
* </p>
*/
protected boolean canFilter() {
return false;
}
/**
* Determines if the datasatore can natively perform "retyping" which includes
* limiting the number of attributes returned and reordering of those attributes
* <p>
* If the subclass can handle retyping natively it should override this method
* to return <code>true</code>. In this case it <b>must</b> do the retyping
* or throw an exception.
* </p>
* <p>
* Not overriding this method or returning <code>false</code> will cause the
* feature reader created by the subclass to be wrapped in a retyping feature
* reader when the query specifies a retype.
* </p>
* @see ReTypeFeatureReader
*/
protected boolean canRetype() {
return false;
}
/**
* Determines if the datastore can natively perform sorting.
* <p>
* If the subclass can handle retyping natively it should override this method
* to return <code>true</code>. In this case it <b>must</b> do the retyping
* or throw an exception.
* </p>
* <p>
* Not overriding this method or returning <code>false</code> will cause an
* exception to be thrown when the query specifies sorting.
* @see SortedFeatureReader
*/
protected boolean canSort() {
return false;
}
/**
* Determines if the store can natively manage transactions.
* <p>
* If a subclass can handle transactions natively it should override this
* method to return <code>true</code> and deal with transactions on its own,
* including firing feature modifications events.
* @return
*/
protected boolean canTransact() {
return false;
}
/**
* Creates a new feature source for the specified query.
* <p>
* If the current feature source already has a defining query it is joined
* to the specified query.
* </p>
* @param query
* @return
* @throws IOException
*/
public final ContentFeatureSource getView(Query query) throws IOException {
query = joinQuery(query);
query = resolvePropertyNames(query);
//reflectively create subclass
Class clazz = getClass();
try {
Constructor c = clazz.getConstructor(ContentEntry.class,Query.class);
ContentFeatureSource source = (ContentFeatureSource) c.newInstance(getEntry(),query);
//set the transaction
source.setTransaction( transaction );
return source;
}
catch( Exception e ) {
String msg = "Subclass must implement Constructor(ContentEntry,Query)";
throw (IOException) new IOException( msg ).initCause(e);
}
}
/**
* Returns the feature collection for the features which match the specified
* filter.
* <p>
* This method calls through to {@link #getFeatures(Query)}.
* </p>
*/
public final ContentFeatureCollection getFeatures(Filter filter)
throws IOException {
return getFeatures( new Query( getSchema().getTypeName(), filter ) );
}
/**
* Returns a reader for features specified by a particular filter.
* <p>
* This method calls through to {@link #getReader(Query)}.
* </p>
*/
public final FeatureReader<SimpleFeatureType, SimpleFeature> getReader(Filter filter) throws IOException {
return getReader( new Query( getSchema().getTypeName(), filter ));
}
public final ContentFeatureSource getView(Filter filter) throws IOException {
return getView( new Query( getSchema().getTypeName(), filter ) );
}
/**
* Adds an listener or observer to the feature source.
* <p>
* Listeners are stored on a per-transaction basis.
* </p>
*/
public final void addFeatureListener(FeatureListener listener) {
entry.getState(transaction).addListener(listener);
}
/**
* Removes a listener from the feature source.
*/
public final void removeFeatureListener(FeatureListener listener) {
entry.getState(transaction).removeListener(listener);
}
/**
* The hints provided by the feature store.
* <p>
* Subclasses should implement {@link #addHints(Set)} to provide additional
* hints.
* </p>
*
* @see FeatureSource#getSupportedHints()
*/
public final Set getSupportedHints() {
return hints;
}
//
// Internal API
//
/**
* Subclass hook too add additional hints.
* <p>
* By default, the followings are already present:
* <ul>
* <li>{@link Hints#JTS_COORDINATE_SEQUENCE_FACTORY}
* <li>{@link Hints#JTS_GEOMETRY_FACTORY}
* </ul>
*
* </p>
* @param hints The set of hints supported by the feature source.
*/
protected void addHints( Set<Hints.Key> hints ) {
}
/**
* Convenience method for joining a query with the definining query of the
* feature source.
*/
protected Query joinQuery( Query query ) {
// join the queries
return DataUtilities.mixQueries(this.query, query, null);
}
/**
* This method changes the query object so that all propertyName references are resolved
* to simple attribute names against the schema of the feature source.
* <p>
* For example, this method ensures that propertyName's such as "gml:name" are rewritten as
* simply "name".
*</p>
*/
protected Query resolvePropertyNames( Query query ) {
return DataUtilities.resolvePropertyNames(query, getSchema() );
}
/** Transform provided filter; resolving property names */
protected Filter resolvePropertyNames( Filter filter ) {
return DataUtilities.resolvePropertyNames(filter, getSchema() );
}
/**
* Creates the feature type or schema for the feature source.
* <p>
* Implementations should use {@link SimpleFeatureTypeBuilder} to build the
* feature type. Also, the builder should be injected with the feature factory
* which has been set on the datastore (see {@link ContentDataStore#getFeatureFactory()}.
* Example:
* <pre>
* SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder();
* b.setFeatureTypeFactory( getDataStore().getFeatureTypeFactory() );
*
* //build the feature type
* ...
* </pre>
* </p>
*/
protected abstract SimpleFeatureType buildFeatureType() throws IOException;
/**
* Builds the query capabilities for this feature source. The default
* implementation returns a newly built QueryCapabilities, subclasses
* are advised to build their own.
* @return
* @throws IOException
*/
protected QueryCapabilities buildQueryCapabilities() {
return new QueryCapabilities();
}
/**
* SimpleFeatureCollection optimized for read-only access.
* <p>
* Available via getView( filter ):
* <ul>
* <li>getFeatures().sort( sort )
* <li>getFeatures( filter ).sort( sort )
* </ul>
* <p>
* In particular this method of data access is intended for rendering and other high speed
* operations; care should be taken to optimize the use of FeatureVisitor.
* <p>
* @param transactionState
* @param filter
* @return readonly access
*/
//protected abstract SimpleFeatureCollection readonly(ContentState state, Filter filter);
public QueryCapabilities getQueryCapabilities() {
// lazy initialization, so that the subclass has all its data structures ready
// when the method is called (it might need to consult them in order to decide
// what query capabilities are really supported)
if(queryCapabilities == null) {
queryCapabilities = buildQueryCapabilities();
}
// we have to glaze the subclass query capabilities since we always support offset
// and we support more sorting cases using the sorting wrappers
return new QueryCapabilities() {
public boolean isOffsetSupported() {
// we always support offset since we support sorting
return true;
}
public boolean supportsSorting(SortBy[] sortAttributes) {
if(queryCapabilities.supportsSorting(sortAttributes)) {
// natively supported
return true;
} else {
// check if we can use the merge-sort support
return SortedFeatureReader.canSort(getSchema(), sortAttributes);
}
}
public boolean isReliableFIDSupported() {
return queryCapabilities.isReliableFIDSupported();
}
public boolean isUseProvidedFIDSupported() {
return queryCapabilities.isUseProvidedFIDSupported();
}
};
}
// -----------------------------------------------------
// Locking support
// -----------------------------------------------------
/**
* Sets the feature lock of the feature store.
*/
public final void setFeatureLock(FeatureLock lock) {
if(canLock()) lock = processLock(lock);
this.lock = lock;
}
/**
* Locks all features.
* <p>
* This method calls through to {@link #lockFeatures(Filter)}.
* </p>
*/
public final int lockFeatures() throws IOException {
return lockFeatures(Filter.INCLUDE);
}
/**
* Locks features specified by a query.
* <p>
* This method calls through to {@link #lockFeatures(Filter)}.
* </p>
*/
public final int lockFeatures(Query query) throws IOException {
return lockFeatures( query.getFilter() );
}
/**
* Locks features specified by a filter.
*/
public final int lockFeatures(Filter filter) throws IOException {
Logger logger = getDataStore().getLogger();
String typeName = getSchema().getTypeName();
FeatureReader<SimpleFeatureType, SimpleFeature> reader = getReader(filter);
try {
int locked = 0;
while( reader.hasNext() ) {
SimpleFeature feature = reader.next();
try {
//
// Use native locking?
//
if(canLock()) {
doLockInternal(typeName,feature);
} else {
getDataStore().getLockingManager()
.lockFeatureID(typeName, feature.getID(), transaction, lock);
}
logger.fine( "Locked feature: " + feature.getID() );
locked++;
}
catch( FeatureLockException e ) {
//ignore
String msg = "Unable to lock feature:" + feature.getID() + "." +
" Change logging to FINEST for stack trace";
logger.fine( msg );
logger.log( Level.FINEST, "Unable to lock feature: " + feature.getID(), e );
}
}
return locked;
}
finally {
reader.close();
}
}
/**
* Unlocks all features.
* <p>
* This method calls through to {@link #unLockFeatures(Filter)}.
* </p>
*
*/
public final void unLockFeatures() throws IOException {
unLockFeatures(Filter.INCLUDE);
}
/**
* Unlocks features specified by a query.
* <p>
* This method calls through to {@link #unLockFeatures(Filter)}.
* </p>
*
*/
public final void unLockFeatures(Query query) throws IOException {
unLockFeatures(query.getFilter());
}
/**
* Unlocks features specified by a filter.
*/
public final void unLockFeatures(Filter filter) throws IOException {
filter = resolvePropertyNames(filter);
String typeName = getSchema().getTypeName();
FeatureReader<SimpleFeatureType, SimpleFeature> reader = getReader(filter);
try {
while( reader.hasNext() ) {
SimpleFeature feature = reader.next();
//
// Use native locking?
//
if(canLock()) {
doLockInternal(typeName,feature);
} else {
getDataStore().getLockingManager()
.unLockFeatureID(typeName, feature.getID(), transaction, lock);
}
}
}
finally {
reader.close();
}
}
/**
* Determines if the {@link #getDataStore() datastore} can perform
* feature locking natively.
* <p>
* If {@link #getWriterInternal(Query, int)} returns a
* {@link FeatureWriter feature writer} that supports feature
* locking natively, it should override this method
* to return <code>true</code>.
* </p>
* <p>
* Not overriding this method or returning <code>false</code> will cause
* {@link ContentFeatureStore} to use the {@link InProcessLockingManager}
* to return a {@link #getWriter FeatureWriter} that honors locks.
*/
protected boolean canLock() {
return false;
}
/**
* If the subclass implements native locking, this
* method is invoked before the feature lock is
* (re)assigned to this store.
* @param lock - a {@link FeatureLock} instance
* @return a processed {@link FeatureLock} instance
*/
protected FeatureLock processLock(FeatureLock lock) {
return lock;
}
/**
* This method must be implemented overridden when
* native locking is indicated by {@link #canLock()}.
* @param typeName {@link SimpleFeature} type name
* @param feature {@link SimpleFeature} instance
*/
protected void doLockInternal(String typeName, SimpleFeature feature) throws IOException {
throw new UnsupportedOperationException("native locking not implemented");
}
/**
* This method must be implemented overridden when
* native locking is indicated by {@link #canLock()}.
* @param typeName {@link Feature} type name
* @param feature {@link Feature} instance
*/
protected void doUnlockInternal(String typeName, SimpleFeature feature) throws IOException {
throw new UnsupportedOperationException("native locking not implemented");
}
}