/*
* 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.arcsde.data;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.arcsde.data.versioning.ArcSdeVersionHandler;
import org.geotools.arcsde.session.ISession;
import org.geotools.data.DataSourceException;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.Transaction;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.logging.Logging;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortBy;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
public class ArcSdeFeatureSource implements FeatureSource<SimpleFeatureType, SimpleFeature> {
private static final Logger LOGGER = Logging.getLogger("org.geotools.arcsde.data");
/**
* {@link Hints#FEATURE_DETACHED} and the ones supported by
* {@link ArcSDEDataStore#getGeometryFactory}
*
* @see #getSupportedHints()
*/
private static final Set<Key> supportedHints = new HashSet<Key>();
static {
supportedHints.add(Hints.FEATURE_DETACHED);
supportedHints.add(Hints.JTS_GEOMETRY_FACTORY);
supportedHints.add(Hints.JTS_COORDINATE_SEQUENCE_FACTORY);
supportedHints.add(Hints.JTS_PRECISION_MODEL);
supportedHints.add(Hints.JTS_SRID);
};
protected Transaction transaction = Transaction.AUTO_COMMIT;
protected FeatureTypeInfo typeInfo;
protected ArcSDEDataStore dataStore;
private ArcSdeResourceInfo resourceInfo;
private QueryCapabilities queryCapabilities;
public ArcSdeFeatureSource(final FeatureTypeInfo typeInfo, final ArcSDEDataStore dataStore) {
this.typeInfo = typeInfo;
this.dataStore = dataStore;
this.queryCapabilities = new QueryCapabilities() {
@Override
public boolean supportsSorting(final SortBy[] sortAttributes) {
final SimpleFeatureType featureType = typeInfo.getFeatureType();
for (SortBy sortBy : sortAttributes) {
if (SortBy.NATURAL_ORDER == sortBy || SortBy.REVERSE_ORDER == sortBy) {
// TODO: we should be able to support natural order
return false;
} else {
String attName = sortBy.getPropertyName().getPropertyName();
AttributeDescriptor descriptor = featureType.getDescriptor(attName);
if (descriptor == null) {
return false;
}
if (descriptor instanceof GeometryDescriptor) {
return false;
}
}
}
return true;
}
};
}
/**
* 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();
}
/**
* @see FeatureSource#getInfo()
*/
public synchronized ArcSdeResourceInfo getInfo() {
if (this.resourceInfo == null) {
this.resourceInfo = new ArcSdeResourceInfo(this.typeInfo, this);
}
return this.resourceInfo;
}
public QueryCapabilities getQueryCapabilities() {
return this.queryCapabilities;
}
/**
* @see FeatureSource#addFeatureListener(FeatureListener)
*/
public final void addFeatureListener(final FeatureListener listener) {
dataStore.listenerManager.addFeatureListener(this, listener);
}
/**
* @see FeatureSource#removeFeatureListener(FeatureListener)
*/
public final void removeFeatureListener(final FeatureListener listener) {
dataStore.listenerManager.removeFeatureListener(this, listener);
}
/**
* @see FeatureSource#getBounds()
*/
public final ReferencedEnvelope getBounds() throws IOException {
return getBounds(Query.ALL);
}
/**
* @return The bounding box of the query or null if unknown and too expensive for the method to
* calculate or any errors occur.
* @see FeatureSource#getBounds(Query)
*/
public final ReferencedEnvelope getBounds(final Query query) throws IOException {
final Query namedQuery = namedQuery(query);
final ISession session = getSession();
ReferencedEnvelope ev;
try {
ev = getBounds(namedQuery, session);
} finally {
session.dispose();
}
return ev;
}
/**
* @param namedQuery
* @param session
* @return The bounding box of the query or null if unknown and too expensive for the method to
* calculate or any errors occur.
* @throws DataSourceException
* @throws IOException
*/
protected ReferencedEnvelope getBounds(final Query namedQuery, final ISession session)
throws DataSourceException, IOException {
final String typeName = typeInfo.getFeatureTypeName();
final ArcSdeVersionHandler versionHandler = dataStore.getVersionHandler(typeName,
transaction);
Envelope ev = ArcSDEQuery.calculateQueryExtent(session, typeInfo, namedQuery,
versionHandler);
if (ev != null) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("ArcSDE optimized getBounds call returned: " + ev);
}
final ReferencedEnvelope envelope;
final GeometryDescriptor defaultGeometry = getSchema().getGeometryDescriptor();
if (defaultGeometry == null) {
envelope = ReferencedEnvelope.reference(ev);
} else {
envelope = new ReferencedEnvelope(ev, defaultGeometry
.getCoordinateReferenceSystem());
}
return envelope;
}
LOGGER.finer("ArcSDE couldn't process all filters in this query, "
+ "so optimized getBounds() returns null.");
return null;
}
/**
* @see FeatureSource#getCount(Query)
*/
public final int getCount(final Query query) throws IOException {
final Query namedQuery = namedQuery(query);
final ISession session = getSession();
final int count;
try {
count = getCount(namedQuery, session);
} finally {
session.dispose();
}
return count;
}
/**
* @see FeatureSource#getCount(Query)
*/
protected int getCount(final Query namedQuery, final ISession session) throws IOException {
final int count;
final String typeName = typeInfo.getFeatureTypeName();
final ArcSdeVersionHandler versionHandler = dataStore.getVersionHandler(typeName,
transaction);
count = ArcSDEQuery.calculateResultCount(session, typeInfo, namedQuery, versionHandler);
return count;
}
/**
* Returns a session appropriate for the current transaction
* <p>
* This is convenient way to get a connection for {@link #getBounds()} and
* {@link #getCount(Query)}. {@link ArcSdeFeatureStore} overrides to get the connection from the
* transaction instead of the pool.
* </p>
*
* @return
*/
protected final ISession getSession() throws IOException {
return dataStore.getSession(transaction);
}
private Query namedQuery(final Query query) {
final String localName = typeInfo.getFeatureTypeName();
final String typeName = query.getTypeName();
if (typeName != null && !localName.equals(typeName)) {
throw new IllegalArgumentException("Wrong type name: " + typeName + " (this is "
+ localName + ")");
}
DefaultQuery namedQuery = new DefaultQuery(query);
namedQuery.setTypeName(localName);
return namedQuery;
}
/**
* @see FeatureSource#getDataStore()
*/
public final ArcSDEDataStore getDataStore() {
return dataStore;
}
/**
* @see FeatureSource#getFeatures(Query)
*/
public final FeatureCollection<SimpleFeatureType, SimpleFeature> getFeatures(final Query query)
throws IOException {
final Query namedQuery = namedQuery(query);
FeatureCollection<SimpleFeatureType, SimpleFeature> collection;
SimpleFeatureType queryType = dataStore.getQueryType(namedQuery);
collection = new ArcSdeFeatureCollection(this, queryType, namedQuery);
return collection;
}
/**
* @see FeatureSource#getFeatures(Filter)
*/
public final FeatureCollection<SimpleFeatureType, SimpleFeature> getFeatures(final Filter filter)
throws IOException {
DefaultQuery query = new DefaultQuery(typeInfo.getFeatureTypeName(), filter);
return getFeatures(query);
}
/**
* @see FeatureSource#getFeatures()
*/
public final FeatureCollection<SimpleFeatureType, SimpleFeature> getFeatures()
throws IOException {
return getFeatures(Filter.INCLUDE);
}
/**
* @see FeatureSource#getSchema();
*/
public final SimpleFeatureType getSchema() {
return typeInfo.getFeatureType();
}
/**
* ArcSDE features are always "detached", so we return the FEATURE_DETACHED hint here, as well
* as the JTS related ones.
* <p>
* The JTS related hints supported are:
* <ul>
* <li>JTS_GEOMETRY_FACTORY
* <li>JTS_COORDINATE_SEQUENCE_FACTORY
* <li>JTS_PRECISION_MODEL
* <li>JTS_SRID
* </ul>
* Note, however, that if a {@link GeometryFactory} is provided through the {@code
* JTS_GEOMETRY_FACTORY} hint, that very factory is used and takes precedence over all the other
* ones.
* </p>
*
* @see FeatureSource#getSupportedHints()
* @see Hints#FEATURE_DETACHED
* @see Hints#JTS_GEOMETRY_FACTORY
* @see Hints#JTS_COORDINATE_SEQUENCE_FACTORY
* @see Hints#JTS_PRECISION_MODEL
* @see Hints#JTS_SRID
*/
public final Set<RenderingHints.Key> getSupportedHints() {
return supportedHints;
}
public ArcSdeVersionHandler getVersionHandler() throws IOException {
return dataStore.getVersionHandler(typeInfo.getFeatureTypeName(), transaction);
}
public FeatureReader<SimpleFeatureType, SimpleFeature> getfeatureReader(
SimpleFeatureType targetSchema, Query query) throws IOException {
FeatureReader<SimpleFeatureType, SimpleFeature> featureReader;
featureReader = dataStore.getFeatureReader(query, transaction, targetSchema);
return featureReader;
}
}