/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-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.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.geotools.data.DataSourceException;
import org.geotools.data.DefaultFeatureResults;
import org.geotools.data.FeatureReader;
import org.geotools.data.MaxFeatureReader;
import org.geotools.data.Query;
import org.geotools.data.crs.ReprojectFeatureReader;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.visitor.AverageVisitor;
import org.geotools.feature.visitor.CountVisitor;
import org.geotools.feature.visitor.MaxVisitor;
import org.geotools.feature.visitor.MedianVisitor;
import org.geotools.feature.visitor.MinVisitor;
import org.geotools.feature.visitor.SumVisitor;
import org.geotools.feature.visitor.UniqueVisitor;
import org.geotools.filter.IllegalFilterException;
import org.geotools.filter.SQLEncoderException;
import org.geotools.filter.visitor.AbstractFilterVisitor;
import org.geotools.filter.visitor.DefaultFilterVisitor;
import org.geotools.filter.visitor.DuplicatingFilterVisitor;
import org.geotools.util.NullProgressListener;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.PropertyName;
import org.opengis.util.ProgressListener;
/**
* @since 2.2.0
* @source $URL$
*
* @deprecated scheduled for removal in 2.7, use classes in org.geotools.jdbc
*/
public class JDBCFeatureCollection extends DefaultFeatureResults {
/** The logger for the filter module. */
// private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(
// "org.geotools.data.jdbc");
public boolean isOptimized = false;
public JDBCFeatureCollection(JDBCFeatureSource source, Query query) throws IOException {
super(source, query);
}
JDBC1DataStore getDataStore() {
return (JDBC1DataStore) featureSource.getDataStore();
}
JDBCFeatureSource getFeatureSource() {
return (JDBCFeatureSource) featureSource;
}
/**
* JDBCDataStore has a more direct query method
*
* @return DOCUMENT ME!
*
* @throws IOException DOCUMENT ME!
*/
public FeatureReader<SimpleFeatureType, SimpleFeature> reader() throws IOException {
FeatureReader<SimpleFeatureType, SimpleFeature> reader = getDataStore().getFeatureReader(query,
getTransaction());
int maxFeatures = query.getMaxFeatures();
if (maxFeatures != Integer.MAX_VALUE) {
reader = new MaxFeatureReader<SimpleFeatureType, SimpleFeature>(reader, maxFeatures);
}
if( transform != null ){
reader = new ReprojectFeatureReader( reader, getSchema(), transform );
}
return reader;
}
/**
* Performs optimized count if possible.
*
*
* @throws IOException
*
* @see org.geotools.data.DefaultFeatureResults#getCount()
*/
public int getCount() throws IOException {
int count = getFeatureSource().count(query, getTransaction());
if (count != -1) {
// optimization worked, return maxFeatures if count is
// greater.
int maxFeatures = query.getMaxFeatures();
return (count < maxFeatures) ? count : maxFeatures;
}
return super.getCount();
}
/**
* Accepts FeatureVisitors.
*
* <p>
* Note for some FeatureCalc visitors an optimized code path will be used.
* </p>
*
* @param visitor DOCUMENT ME!
*
* @throws IOException DOCUMENT ME!
*/
public void accepts(FeatureVisitor visitor, ProgressListener progress) {
try {
// TODO: review use of progress monitor
if (progress == null)
progress = new NullProgressListener();
if (visitor instanceof MinVisitor) {
progress.started();
MinVisitor minCalc = (MinVisitor) visitor;
Object min = min(minCalc.getExpression());
if (min != null) {
// we had an optimized result, so tell the visitor the answer
// rather than making him visit everything
minCalc.setValue(min);
isOptimized = true;
progress.complete();
return;
} else {
progress.warningOccurred("JDBCFeatureCollection.accepts(min,)", null,
"Optimization attempt returned null");
}
} else if (visitor instanceof MaxVisitor) {
progress.started();
MaxVisitor maxCalc = (MaxVisitor) visitor;
Object max = max(maxCalc.getExpression());
if (max != null) {
maxCalc.setValue(max); // use the optimized result
isOptimized = true;
progress.complete();
return;
} else {
progress.warningOccurred("JDBCFeatureCollection.accepts(max,)", null,
"Optimization attempt returned null");
}
} else if (visitor instanceof MedianVisitor) {
// Optimization is not available (i think)
// MedianVisitor medianCalc = (MedianVisitor) visitor;
// Object median = median(medianCalc.getExpression());
// if (median != null) {
// medianCalc.setValue((Comparable) median); // use the optimized result
// isOptimized = true;
// return;
// }
} else if (visitor instanceof SumVisitor) {
progress.started();
SumVisitor sumCalc = (SumVisitor) visitor;
Object sum = sum(sumCalc.getExpression());
if (sum != null) {
sumCalc.setValue(sum); // use the optimized result
isOptimized = true;
progress.complete();
return;
} else {
progress.warningOccurred("JDBCFeatureCollection.accepts(sum,)", null,
"Optimization attempt returned null");
}
} else if (visitor instanceof CountVisitor) {
progress.started();
CountVisitor countCalc = (CountVisitor) visitor;
Object count = count(null);
if (count != null) {
countCalc.setValue(((Number) count).intValue()); // use the optimized result
isOptimized = true;
progress.complete();
return;
} else {
progress.warningOccurred("JDBCFeatureCollection.accepts(count,)", null,
"Optimization attempt returned null");
}
} else if (visitor instanceof AverageVisitor) {
progress.started();
// There is no "AVERAGE" function for SQL (to my knowledge),
// so we'll do a SUM / COUNT...
AverageVisitor averageCalc = (AverageVisitor) visitor;
Object sum = sum(averageCalc.getExpression());
Object count = count(null);
if (sum != null && count != null) {
averageCalc.setValue(((Number) count).intValue(), sum);
isOptimized = true;
progress.complete();
return;
} else {
progress.warningOccurred("JDBCFeatureCollection.accepts(average,)", null,
"Optimization attempt returned null");
}
} else if (visitor instanceof UniqueVisitor) {
progress.started();
UniqueVisitor uniqueCalc = (UniqueVisitor) visitor;
Object unique = unique(uniqueCalc.getExpression());
if (unique != null) {
uniqueCalc.setValue(unique); // use the optimized result
isOptimized = true;
progress.complete();
return;
} else {
progress.warningOccurred("JDBCFeatureCollection.accepts(unique,)", null,
"Optimization attempt returned null");
}
}
// optimization was not available, or it failed
isOptimized = false;
super.accepts(visitor, progress);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Connection getConnection() throws IOException {
return getDataStore().getConnection(getFeatureSource().getTransaction());
}
Object min(Expression expression) throws IOException {
return aggregate( "MIN", expression );
}
Object max(Expression expression) throws IOException {
return aggregate( "MAX", expression );
}
Object median(Expression expression) throws IOException {
return aggregate( "MEDIAN", expression );
}
Object sum(Expression expression) throws IOException {
return aggregate( "SUM", expression );
}
Object count(Expression expression) throws IOException {
return aggregate( "COUNT", expression );
}
Object average(Expression expression) throws IOException {
return aggregate( "AVERAGE", expression );
}
Object unique(Expression expression) throws IOException {
return aggregate( "DISTINCT", expression );
}
Object aggregate(String aggregate, Expression expression) throws IOException {
org.opengis.filter.Filter filter = (org.opengis.filter.Filter) query.getFilter();
if (org.opengis.filter.Filter.EXCLUDE.equals(filter)) {
return null;
}
JDBC1DataStore jdbc = getDataStore();
SQLBuilder sqlBuilder = jdbc.getSqlBuilder(this.getSchema().getTypeName());
org.opengis.filter.Filter postFilter = (org.opengis.filter.Filter) sqlBuilder.getPostQueryFilter(query.getFilter());
if (postFilter != null && !org.opengis.filter.Filter.INCLUDE.equals(postFilter)) {
// this would require postprocessing the filter
// so we cannot optimize
return null;
}
Connection conn = null;
ResultSet results = null;
Statement statement = null;
try {
conn = jdbc.getConnection(getFeatureSource().getTransaction());
String typeName = getSchema().getTypeName();
//Filter preFilter = sqlBuilder.getPreQueryFilter(query.getFilter());
StringBuffer sql = new StringBuffer();
sql.append("SELECT ");
sql.append( aggregate );
sql.append( "(" );
if (expression == null) {
sql.append("*"); //for things like COUNT(*), where a column isn't neccesary.
} else {
//FIXME: add proper handling of expressions
//extract the column names from the expression
//(if we see "featureMembers/*/ATTRIBUTE" change to "ATTRIBUTE")
expression = (Expression)expression.accept(new ExpressionSimplifier(), null);
// if (sqlBuilder instanceof DefaultSQLBuilder) {
// DefaultSQLBuilder builder = (DefaultSQLBuilder) sqlBuilder;
// builder.encoder.encode(expression);
// //builder.sqlColumns(sql,)
// sql.append(builder.toString());
// } else {
sqlBuilder.encode(sql, expression);
// }
}
sql.append(") AS rslt");
sqlBuilder.sqlFrom(sql, typeName);
sqlBuilder.sqlWhere(sql, filter);
//LOGGER.finer("SQL: " + sql);
//System.out.println("SQL: " + sql);
statement = conn.createStatement();
results = statement.executeQuery(sql.toString());
if (results == null) return null; //SQL statement failed to execute
results.next();
Object answer = results.getObject("rslt");
results.next();
if( results.isAfterLast() ){
return answer;
}
List list = new ArrayList();
list.add( answer );
while( !results.isAfterLast() ){
list.add( results.getObject("rslt") );
results.next();
}
return list;
} catch (SQLException sqlException) {
JDBCUtils.close(conn, getFeatureSource().getTransaction(), sqlException);
conn = null;
throw new DataSourceException("Could not calculate " + aggregate + " with "
+ query.getHandle(), sqlException);
} catch (SQLEncoderException e) {
// could not encode count
// but at least we did not break the connection
return null;
} finally {
try {
if( results != null ) results.close();
if( statement != null ) statement.close();
}
catch (SQLException ignore ){
}
JDBCUtils.close(conn, getFeatureSource().getTransaction(), null);
}
}
/**
* Simplifies a filter expression from featureMember/asterisk/ATTRIBUTE to
* ATTRIBUTE.
*
* @author Cory Horner, Refractions
*
*/
class ExpressionSimplifier extends DuplicatingFilterVisitor {
public Object visit(PropertyName expression, Object extraData) {
String xpath = expression.getPropertyName();
if (xpath.startsWith("featureMembers/*/")) {
xpath = xpath.substring(17);
} else if (xpath.startsWith("featureMember/*/")) {
xpath = xpath.substring(16);
}
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
PropertyName simplified = ff.property(xpath);
return simplified;
}
}
}