/* * 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.jdbc; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.DataSourceException; import org.geotools.data.DataStore; import org.geotools.data.DefaultQuery; import org.geotools.data.FeatureListener; import org.geotools.data.FeatureSource; import org.geotools.data.Query; import org.geotools.data.QueryCapabilities; import org.geotools.data.ResourceInfo; import org.geotools.data.Transaction; import org.geotools.data.jdbc.fidmapper.FIDMapper; import org.geotools.data.jdbc.fidmapper.NullFIDMapper; import org.geotools.data.jdbc.fidmapper.TypedFIDMapper; import org.geotools.feature.FeatureCollection; import org.geotools.filter.SQLEncoderException; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Envelope; /** * A JDBCFeatureSource that can opperate as a starting point for your own * implementations. * *
* This class is distinct from the AbstractFeatureSource implementations as * JDBC provides us with so many opertunities for optimization. *
* Client code must implement: * *
* This default implementation assumes sorting is supported both in ascending and descending
* order by any FeatureType attribute. Sorting by {@link SortBy#NATURAL_ORDER} and
* {@link SortBy#REVERSE_ORDER}, by the other hand, defaults to false
, since a
* datastore may take explicit actions in orther to support those concepts, though most of the
* time it implies sorting by the primary key for JDBC datastores.
*
false
.
*
* @return false, override if NATURAL_ORDER sorting is supported.
*/
protected boolean supportsNaturalOrderSorting() {
return false;
}
/**
* Indicates whether sorting by {@link SortBy#REVERSE_ORDER} is supported; defaults to
* false
.
*
* @return false, override if REVERSE_ORDER sorting is supported.
*/
protected boolean supportsReverseOrderSorting() {
return false;
}
/**
* Template method to check for sorting support in the given sort order for a specific
* attribute type, given by a PropertyName expression.
* * This default implementation assumes both orders are supported as long as the property * name corresponds to the name of one of the attribute types in the complete FeatureType. *
* * @param propertyName the expression holding the property name to check for sortability * support * @param sortOrder the order, ascending or descending, to check for sortability support * over the given property name. * @return true if propertyName refers to one of the FeatureType attributes */ protected boolean supportsPropertySorting(PropertyName propertyName, SortOrder sortOrder) { AttributeDescriptor descriptor = (AttributeDescriptor) propertyName.evaluate(featureType); if (descriptor != null) { return true; } String attName = propertyName.getPropertyName(); AttributeDescriptor attribute = featureType.getDescriptor(attName); return attribute != null; } /** * Consults the fid mapper for the feature source, if the null feature map reliable fids * not supported. */ @Override public boolean isReliableFIDSupported() { FIDMapper mapper; try { mapper = JDBCFeatureSource.this.dataStore.getFIDMapper(featureType.getTypeName()); } catch (IOException e) { LOGGER.warning( "Unable to access fid mapper" ); LOGGER.log( Level.FINE, "", e ); return super.isReliableFIDSupported(); } return !isNullFidMapper( mapper ); } /** * Helper method to test if a fid mapper is a null fid mapper. */ protected boolean isNullFidMapper( FIDMapper mapper ) { if ( mapper instanceof TypedFIDMapper ) { mapper = ((TypedFIDMapper)mapper).getWrappedMapper(); } return mapper instanceof NullFIDMapper; } } /** FeatureType being provided */ private SimpleFeatureType featureType; /** JDBCDataStore based dataStore used to aquire content */ private JDBC1DataStore dataStore; protected QueryCapabilities queryCapabilities; /** * JDBCFeatureSource creation. * ** Constructs a FeatureStore that opperates against the provided * jdbcDataStore to serve up the contents of featureType. *
* * @param jdbcDataStore DataStore containing contents * @param featureType FeatureType being served */ public JDBCFeatureSource(JDBC1DataStore jdbcDataStore, SimpleFeatureType featureType) { this.featureType = featureType; this.dataStore = jdbcDataStore; //We assume jdbc datastores can sort by any field. Be sure to override //if you support @id, NATURAL_ORDER, or REVERSE_ORDER this.queryCapabilities = new JDBCQueryCapabilities(featureType); } /** * 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(); } public ResourceInfo getInfo() { return new ResourceInfo(){ final Set* Subclass must implement *
* * @return JDBDataStore managing this FeatureSource */ public JDBC1DataStore getJDBCDataStore() { return dataStore; } /** * Adds FeatureListener to the JDBCDataStore against this FeatureSource. * * @param listener * * @see org.geotools.data.FeatureSource#addFeatureListener(org.geotools.data.FeatureListener) */ public void addFeatureListener(FeatureListener listener) { getJDBCDataStore().listenerManager.addFeatureListener(this, listener); } /** * Remove FeatureListener to the JDBCDataStore against this FeatureSource. * * @param listener * * @see org.geotools.data.FeatureSource#removeFeatureListener(org.geotools.data.FeatureListener) */ public void removeFeatureListener(FeatureListener listener) { getJDBCDataStore().listenerManager.removeFeatureListener(this, listener); } /** * Retrieve the Transaction this FeatureSource* For a plain JDBCFeatureSource that cannot modify this will always be * Transaction.AUTO_COMMIT. *
* * @return DOCUMENT ME! */ public Transaction getTransaction() { return Transaction.AUTO_COMMIT; } /** * Provides an interface to for the Resutls of a Query. * ** Various queries can be made against the results, the most basic being to * retrieve Features. *
* * @param request * * * @throws IOException * * @see org.geotools.data.FeatureSource#getFeatures(org.geotools.data.Query) */ public FeatureCollection* Currently returns null, consider getFeatures().getBounds() instead. *
* ** Subclasses may override this method to perform the appropriate * optimization for this result. *
* * @return null representing the lack of an optimization * * @throws IOException DOCUMENT ME! */ public ReferencedEnvelope getBounds() throws IOException { return getBounds(Query.ALL); } /** * Retrieve Bounds of Query results. * ** Currently returns null, consider getFeatures( query ).getBounds() * instead. *
* ** Subclasses may override this method to perform the appropriate * optimization for this result. *
* * @param query Query we are requesting the bounds of * * @return null representing the lack of an optimization * * @throws IOException DOCUMENT ME! */ public ReferencedEnvelope getBounds(Query query) throws IOException { if (query.getFilter() == Filter.EXCLUDE) { if(featureType!=null) return new ReferencedEnvelope(new Envelope(),featureType.getGeometryDescriptor().getCoordinateReferenceSystem()); return new ReferencedEnvelope(); } return null; // too expensive right now :-) } /** * Retrieve total number of Query results. ** SQL: SELECT COUNT(*) as cnt FROM table WHERE filter *
* * @param query Query we are requesting the count of * @return Count of indicated query */ public int getCount(Query query) throws IOException { return count(query, getTransaction() ); } /** * Direct SQL query number of rows in query. * ** Note this is a low level SQL statement and if it fails the provided * Transaction will be rolled back. *
** SQL: SELECT COUNT(*) as cnt FROM table WHERE filter *
* @param query * @param transaction * * @return Number of rows in query, or -1 if not optimizable. * * @throws IOException Usual on the basis of a filter error */ public int count(Query query, Transaction transaction) throws IOException { Filter filter = query.getFilter(); if (Filter.EXCLUDE.equals(filter)) { return 0; } JDBC1DataStore jdbc = getJDBCDataStore(); SQLBuilder sqlBuilder = jdbc.getSqlBuilder(featureType.getTypeName()); Filter preFilter = (Filter) sqlBuilder.getPreQueryFilter(filter); Filter postFilter = (Filter) sqlBuilder.getPostQueryFilter(filter); if (postFilter != null && !Filter.INCLUDE.equals(postFilter)) { // this would require postprocessing the filter // so we cannot optimize return -1; } Connection conn = null; try { conn = jdbc.getConnection(transaction); String typeName = getSchema().getTypeName(); StringBuffer sql = new StringBuffer(); //chorner: we should hit an indexed column, * will likely tablescan sql.append("SELECT COUNT(*) as cnt"); sqlBuilder.sqlFrom(sql, typeName); sqlBuilder.sqlWhere(sql, preFilter); LOGGER.finer("SQL: " + sql); Statement statement = conn.createStatement(); ResultSet results = statement.executeQuery(sql.toString()); results.next(); int count = results.getInt("cnt"); results.close(); statement.close(); return count; } catch (SQLException sqlException) { JDBCUtils.close(conn, transaction, sqlException); conn = null; throw new DataSourceException("Could not count " + query.getHandle(), sqlException); } catch (SQLEncoderException e) { // could not encode count // but at least we did not break the connection return -1; } finally { JDBCUtils.close(conn, transaction, null); } } /** * Retrieve FeatureType represented by this FeatureSource * * @return FeatureType for FeatureSource * * @see org.geotools.data.FeatureSource#getSchema() */ public SimpleFeatureType getSchema() { return featureType; } protected Connection getConnection() throws IOException { return getJDBCDataStore().getConnection(getTransaction()); } protected void close(Connection conn, Transaction trans, SQLException sqle) { JDBCUtils.close(conn, trans, sqle); } protected void close(ResultSet rs) { JDBCUtils.close(rs); } protected void close(Statement statement) { JDBCUtils.close(statement); } /** * By default, only detached feature is supported */ public Set getSupportedHints() { return dataStore.getSupportedHints(); } /** * Returns the default jdbc query capabilities, subclasses should * override to advertise their specific capabilities where they differ. ** For instance, the capabilities returned: *