001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.data.jdbc;
017:
018: import java.io.IOException;
019: import java.sql.Connection;
020: import java.sql.ResultSet;
021: import java.sql.SQLException;
022: import java.sql.Statement;
023: import java.util.ArrayList;
024: import java.util.List;
025:
026: import org.geotools.data.DataSourceException;
027: import org.geotools.data.DefaultFeatureResults;
028: import org.geotools.data.FeatureReader;
029: import org.geotools.data.MaxFeatureReader;
030: import org.geotools.data.Query;
031: import org.geotools.data.crs.ReprojectFeatureReader;
032: import org.geotools.factory.CommonFactoryFinder;
033: import org.geotools.feature.visitor.AverageVisitor;
034: import org.geotools.feature.visitor.CountVisitor;
035: import org.geotools.feature.visitor.FeatureVisitor;
036: import org.geotools.feature.visitor.MaxVisitor;
037: import org.geotools.feature.visitor.MedianVisitor;
038: import org.geotools.feature.visitor.MinVisitor;
039: import org.geotools.feature.visitor.SumVisitor;
040: import org.geotools.feature.visitor.UniqueVisitor;
041: import org.geotools.filter.IllegalFilterException;
042: import org.geotools.filter.SQLEncoderException;
043: import org.geotools.filter.visitor.AbstractFilterVisitor;
044: import org.geotools.filter.visitor.DefaultFilterVisitor;
045: import org.geotools.util.NullProgressListener;
046: import org.geotools.util.ProgressListener;
047: import org.opengis.filter.FilterFactory;
048: import org.opengis.filter.expression.Expression;
049: import org.opengis.filter.expression.PropertyName;
050:
051: /**
052: * @since 2.2.0
053: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/JDBCFeatureCollection.java $
054: */
055: public class JDBCFeatureCollection extends DefaultFeatureResults {
056: /** The logger for the filter module. */
057: // private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(
058: // "org.geotools.data.jdbc");
059: public boolean isOptimized = false;
060:
061: public JDBCFeatureCollection(JDBCFeatureSource source, Query query)
062: throws IOException {
063: super (source, query);
064: }
065:
066: JDBC1DataStore getDataStore() {
067: return (JDBC1DataStore) featureSource.getDataStore();
068: }
069:
070: JDBCFeatureSource getFeatureSource() {
071: return (JDBCFeatureSource) featureSource;
072: }
073:
074: /**
075: * JDBCDataStore has a more direct query method
076: *
077: * @return DOCUMENT ME!
078: *
079: * @throws IOException DOCUMENT ME!
080: */
081: public FeatureReader reader() throws IOException {
082: FeatureReader reader = getDataStore().getFeatureReader(query,
083: getTransaction());
084:
085: int maxFeatures = query.getMaxFeatures();
086: if (maxFeatures != Integer.MAX_VALUE) {
087: reader = new MaxFeatureReader(reader, maxFeatures);
088: }
089: if (transform != null) {
090: reader = new ReprojectFeatureReader(reader, schema,
091: transform);
092: }
093: return reader;
094: }
095:
096: /**
097: * Performs optimized count if possible.
098: *
099: *
100: * @throws IOException
101: *
102: * @see org.geotools.data.DefaultFeatureResults#getCount()
103: */
104: public int getCount() throws IOException {
105: int count = getFeatureSource().count(query, getTransaction());
106:
107: if (count != -1) {
108: // optimization worked, return maxFeatures if count is
109: // greater.
110: int maxFeatures = query.getMaxFeatures();
111: return (count < maxFeatures) ? count : maxFeatures;
112: }
113:
114: return super .getCount();
115: }
116:
117: /**
118: * Accepts FeatureVisitors.
119: *
120: * <p>
121: * Note for some FeatureCalc visitors an optimized code path will be used.
122: * </p>
123: *
124: * @param visitor DOCUMENT ME!
125: *
126: * @throws IOException DOCUMENT ME!
127: */
128: public void accepts(FeatureVisitor visitor,
129: ProgressListener progress) throws IOException {
130: //TODO: review use of progress monitor
131: if (progress == null)
132: progress = new NullProgressListener();
133: if (visitor instanceof MinVisitor) {
134: progress.started();
135: MinVisitor minCalc = (MinVisitor) visitor;
136: Object min = min(minCalc.getExpression());
137:
138: if (min != null) {
139: // we had an optimized result, so tell the visitor the answer
140: // rather than making him visit everything
141: minCalc.setValue(min);
142: isOptimized = true;
143: progress.complete();
144: return;
145: } else {
146: progress.warningOccurred(
147: "JDBCFeatureCollection.accepts(min,)", null,
148: "Optimization attempt returned null");
149: }
150: } else if (visitor instanceof MaxVisitor) {
151: progress.started();
152: MaxVisitor maxCalc = (MaxVisitor) visitor;
153: Object max = max(maxCalc.getExpression());
154:
155: if (max != null) {
156: maxCalc.setValue(max); // use the optimized result
157: isOptimized = true;
158: progress.complete();
159: return;
160: } else {
161: progress.warningOccurred(
162: "JDBCFeatureCollection.accepts(max,)", null,
163: "Optimization attempt returned null");
164: }
165:
166: } else if (visitor instanceof MedianVisitor) {
167: //Optimization is not available (i think)
168:
169: //MedianVisitor medianCalc = (MedianVisitor) visitor;
170: //Object median = median(medianCalc.getExpression());
171: //if (median != null) {
172: // medianCalc.setValue((Comparable) median); // use the optimized result
173: // isOptimized = true;
174: // return;
175: //}
176: } else if (visitor instanceof SumVisitor) {
177: progress.started();
178: SumVisitor sumCalc = (SumVisitor) visitor;
179: Object sum = sum(sumCalc.getExpression());
180:
181: if (sum != null) {
182: sumCalc.setValue(sum); // use the optimized result
183: isOptimized = true;
184: progress.complete();
185: return;
186: } else {
187: progress.warningOccurred(
188: "JDBCFeatureCollection.accepts(sum,)", null,
189: "Optimization attempt returned null");
190: }
191:
192: } else if (visitor instanceof CountVisitor) {
193: progress.started();
194: CountVisitor countCalc = (CountVisitor) visitor;
195: Object count = count(null);
196:
197: if (count != null) {
198: countCalc.setValue(((Number) count).intValue()); // use the optimized result
199: isOptimized = true;
200: progress.complete();
201: return;
202: } else {
203: progress.warningOccurred(
204: "JDBCFeatureCollection.accepts(count,)", null,
205: "Optimization attempt returned null");
206: }
207:
208: } else if (visitor instanceof AverageVisitor) {
209: progress.started();
210: //There is no "AVERAGE" function for SQL (to my knowledge),
211: // so we'll do a SUM / COUNT...
212: AverageVisitor averageCalc = (AverageVisitor) visitor;
213: Object sum = sum(averageCalc.getExpression());
214: Object count = count(null);
215: if (sum != null && count != null) {
216: averageCalc.setValue(((Number) count).intValue(), sum);
217: isOptimized = true;
218: progress.complete();
219: return;
220: } else {
221: progress.warningOccurred(
222: "JDBCFeatureCollection.accepts(average,)",
223: null, "Optimization attempt returned null");
224: }
225: } else if (visitor instanceof UniqueVisitor) {
226: progress.started();
227: UniqueVisitor uniqueCalc = (UniqueVisitor) visitor;
228: Object unique = unique(uniqueCalc.getExpression());
229:
230: if (unique != null) {
231: uniqueCalc.setValue(unique); // use the optimized result
232: isOptimized = true;
233: progress.complete();
234: return;
235: } else {
236: progress.warningOccurred(
237: "JDBCFeatureCollection.accepts(unique,)", null,
238: "Optimization attempt returned null");
239: }
240:
241: }
242: //optimization was not available, or it failed
243: isOptimized = false;
244: super .accepts(visitor, progress);
245: }
246:
247: Connection getConnection() throws IOException {
248: return getDataStore().getConnection(
249: getFeatureSource().getTransaction());
250: }
251:
252: Object min(Expression expression) throws IOException {
253: return aggregate("MIN", expression);
254: }
255:
256: Object max(Expression expression) throws IOException {
257: return aggregate("MAX", expression);
258: }
259:
260: Object median(Expression expression) throws IOException {
261: return aggregate("MEDIAN", expression);
262: }
263:
264: Object sum(Expression expression) throws IOException {
265: return aggregate("SUM", expression);
266: }
267:
268: Object count(Expression expression) throws IOException {
269: return aggregate("COUNT", expression);
270: }
271:
272: Object average(Expression expression) throws IOException {
273: return aggregate("AVERAGE", expression);
274: }
275:
276: Object unique(Expression expression) throws IOException {
277: return aggregate("DISTINCT", expression);
278: }
279:
280: Object aggregate(String aggregate, Expression expression)
281: throws IOException {
282: org.opengis.filter.Filter filter = (org.opengis.filter.Filter) query
283: .getFilter();
284:
285: if (org.opengis.filter.Filter.EXCLUDE.equals(filter)) {
286: return null;
287: }
288:
289: JDBC1DataStore jdbc = getDataStore();
290: SQLBuilder sqlBuilder = jdbc.getSqlBuilder(this .getSchema()
291: .getTypeName());
292:
293: org.opengis.filter.Filter postFilter = (org.opengis.filter.Filter) sqlBuilder
294: .getPostQueryFilter(query.getFilter());
295: if (postFilter != null
296: && !org.opengis.filter.Filter.INCLUDE
297: .equals(postFilter)) {
298: // this would require postprocessing the filter
299: // so we cannot optimize
300: return null;
301: }
302:
303: Connection conn = null;
304: ResultSet results = null;
305: Statement statement = null;
306: try {
307: conn = jdbc.getConnection(getFeatureSource()
308: .getTransaction());
309:
310: String typeName = getSchema().getTypeName();
311: //Filter preFilter = sqlBuilder.getPreQueryFilter(query.getFilter());
312: StringBuffer sql = new StringBuffer();
313: sql.append("SELECT ");
314: sql.append(aggregate);
315: sql.append("(");
316:
317: if (expression == null) {
318: sql.append("*"); //for things like COUNT(*), where a column isn't neccesary.
319: } else {
320: //FIXME: add proper handling of expressions
321:
322: //extract the column names from the expression
323: //(if we see "featureMembers/*/ATTRIBUTE" change to "ATTRIBUTE")
324: expression = (Expression) expression.accept(
325: new ExpressionSimplifier(), null);
326:
327: // if (sqlBuilder instanceof DefaultSQLBuilder) {
328: // DefaultSQLBuilder builder = (DefaultSQLBuilder) sqlBuilder;
329: // builder.encoder.encode(expression);
330: // //builder.sqlColumns(sql,)
331: // sql.append(builder.toString());
332: // } else {
333: sql.append(expression.toString());
334: // }
335: }
336:
337: sql.append(") AS rslt");
338: sqlBuilder.sqlFrom(sql, typeName);
339: sqlBuilder.sqlWhere(sql, filter);
340:
341: //LOGGER.finer("SQL: " + sql);
342: //System.out.println("SQL: " + sql);
343:
344: statement = conn.createStatement();
345: results = statement.executeQuery(sql.toString());
346: if (results == null)
347: return null; //SQL statement failed to execute
348: results.next();
349:
350: Object answer = results.getObject("rslt");
351: results.next();
352: if (results.isAfterLast()) {
353: return answer;
354: }
355: List list = new ArrayList();
356: list.add(answer);
357: while (!results.isAfterLast()) {
358: list.add(results.getObject("rslt"));
359: results.next();
360: }
361: return list;
362: } catch (SQLException sqlException) {
363: JDBCUtils.close(conn, getFeatureSource().getTransaction(),
364: sqlException);
365: conn = null;
366: throw new DataSourceException("Could not calculate "
367: + aggregate + " with " + query.getHandle(),
368: sqlException);
369: } catch (SQLEncoderException e) {
370: // could not encode count
371: // but at least we did not break the connection
372: return null;
373: } finally {
374: try {
375: if (results != null)
376: results.close();
377: if (statement != null)
378: statement.close();
379: } catch (SQLException ignore) {
380: }
381: JDBCUtils.close(conn, getFeatureSource().getTransaction(),
382: null);
383: }
384: }
385:
386: /**
387: * Simplifies a filter expression from featureMember/asterisk/ATTRIBUTE to
388: * ATTRIBUTE.
389: *
390: * @author Cory Horner, Refractions
391: *
392: */
393: class ExpressionSimplifier extends DefaultFilterVisitor {
394: public Object visit(PropertyName expression, Object extraData) {
395: String xpath = expression.getPropertyName();
396:
397: if (xpath.startsWith("featureMembers/*/")) {
398: xpath = xpath.substring(17);
399: } else if (xpath.startsWith("featureMember/*/")) {
400: xpath = xpath.substring(16);
401: }
402:
403: FilterFactory ff = CommonFactoryFinder
404: .getFilterFactory(null);
405: PropertyName simplified = ff.property(xpath);
406: return simplified;
407: }
408: }
409: }
|