001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-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 org.geotools.data.jdbc.fidmapper.FIDMapper;
019: import org.geotools.factory.Hints;
020: import org.geotools.feature.AttributeType;
021: import org.geotools.feature.FeatureType;
022: import org.geotools.feature.GeometryAttributeType;
023: import org.opengis.filter.Filter;
024: import org.geotools.filter.FilterCapabilities;
025: import org.geotools.filter.Filters;
026: import org.geotools.filter.SQLEncoder;
027: import org.geotools.filter.SQLEncoderException;
028: import org.geotools.filter.visitor.ClientTransactionAccessor;
029: import org.geotools.filter.visitor.PostPreProcessFilterSplittingVisitor;
030: import org.opengis.filter.sort.SortBy;
031: import org.opengis.filter.sort.SortOrder;
032:
033: /**
034: * Builds a complete SQL query to select the specified attributes for the
035: * specified feature type, using a specified filter to generate a WHERE
036: * clause.
037: *
038: * <p>
039: * The actual WHERE clause is generated by the FilterToSQL class or appropriate
040: * subclass for a particular database. If a specific encoder is to be used,
041: * it must be specified to the constructor for this class.
042: * </p>
043: *
044: * <p>
045: * In order to implement the functionality of the application-specified Filter,
046: * this is split into a 'preQueryFilter' which can be incorporated into the
047: * SQL query itself and a 'postQueryFilter. The encoder capabilities are used
048: * to determine how much of the function can be performed by the database
049: * directly and how much has to be performed on the result set.
050: * </p>
051: *
052: * @author Sean Geoghegan, Defence Science and Technology Organisation.
053: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/GeoAPISQLBuilder.java $
054: */
055: public class GeoAPISQLBuilder implements SQLBuilder {
056: // The instance of the encoder to be used to generate the WHERE clause
057: protected FilterToSQL encoder;
058:
059: protected FeatureType ft;
060:
061: protected ClientTransactionAccessor accessor;
062:
063: private Filter lastFilter = null;
064: private Filter lastPreFilter = null;
065: private Filter lastPostFilter = null;
066:
067: private Hints hints;
068:
069: /**
070: * Constructs an instance of this class with a default FilterToSQL
071: */
072: public GeoAPISQLBuilder() {
073: this (new FilterToSQL(), null, null);
074: }
075:
076: /**
077: * Constructs an instance of this class using the encoder class specified.
078: * This will typically be from the getSqlBuilder method of a JDBCDataStore
079: * subclass.
080: *
081: * @param encoder the specific encoder to be used.
082: * @param featureType
083: * @param accessor client-side transaction handler; may be null.
084: */
085: public GeoAPISQLBuilder(FilterToSQL encoder,
086: FeatureType featureType, ClientTransactionAccessor accessor) {
087: this .encoder = encoder;
088: this .ft = featureType;
089: this .accessor = accessor;
090:
091: //set the feature type on teh encoders
092: encoder.setFeatureType(featureType);
093: }
094:
095: public void setHints(Hints hints) {
096: this .hints = hints;
097: }
098:
099: /** Check the hints to see if we are forced into 2D */
100: public boolean isForce2D() {
101: if (hints == null) {
102: return false;
103: }
104: Boolean force2d = (Boolean) hints.get(Hints.FEATURE_2D);
105: if (force2d == null) {
106: return false;
107: }
108: return force2d.booleanValue();
109: }
110:
111: /**
112: * Return the postQueryFilter that must be applied to the database query
113: * result set.
114: *
115: * @param filter the application filter which must be applied
116: *
117: * @return the filter representing the functionality that must be performed
118: * on the result set.
119: */
120: public Filter getPostQueryFilter(Filter filter) {
121: if (filter != null
122: && (lastFilter == null || !filter.equals(lastFilter))) {
123: splitFilter(filter);
124: }
125: return lastPostFilter;
126: }
127:
128: /**
129: * Return the preQueryFilter that can be used to generate the WHERE clause.
130: *
131: * @param filter the application filter which must be applied
132: *
133: * @return the filter representing the functionality that can be performed
134: * by the database.
135: */
136: public Filter getPreQueryFilter(Filter filter) {
137: if (filter != null
138: && (lastFilter == null || !filter.equals(lastFilter))) {
139: splitFilter(filter);
140: }
141: return lastPreFilter;
142: }
143:
144: protected void splitFilter(Filter filter) {
145: lastFilter = filter;
146: FilterCapabilities cap = encoder.getCapabilities();
147: PostPreProcessFilterSplittingVisitor pfv = new PostPreProcessFilterSplittingVisitor(
148: cap, ft, accessor);
149:
150: filter.accept(pfv, null);
151:
152: lastPreFilter = (Filter) pfv.getFilterPre();
153: lastPostFilter = (Filter) pfv.getFilterPost();
154: }
155:
156: /**
157: * Constructs the FROM clause for a featureType
158: *
159: * <p>
160: * sql: <code>FROM typeName</code>
161: * </p>
162: *
163: * @param sql the StringBuffer that the WHERE clause should be appended to
164: * @param typeName the name of the table (feature type) to be queried
165: */
166: public void sqlFrom(StringBuffer sql, String typeName) {
167: sql.append(" FROM ");
168: sql.append(encoder.escapeName(typeName));
169: }
170:
171: /**
172: * Constructs WHERE clause, if needed, for FILTER.
173: *
174: * <p>
175: * sql: <code>WHERE filter encoding</code>
176: * </p>
177: *
178: * @param sql The StringBuffer that the WHERE clause should be appended to
179: * @param preFilter The filter to be used by the encoder class to generate
180: * the WHERE clause
181: *
182: * @throws SQLEncoderException Not thrown here but may be thrown by the
183: * encoder
184: * FIXME: Throw FilterToSQLException when the parent interface is fixed.
185: */
186: public void sqlWhere(StringBuffer sql, Filter preFilter)
187: throws SQLEncoderException {
188: if ((preFilter != null) && (preFilter != Filter.INCLUDE)) {
189: try {
190: String where = encoder.encodeToString(preFilter);
191: sql.append(" ");
192: sql.append(where);
193: } catch (FilterToSQLException fse) {
194: throw new SQLEncoderException("", fse);
195: }
196: }
197: }
198:
199: /**
200: * Constructs the full SQL SELECT statement for the supplied Filter.
201: *
202: * <p>
203: * The statement is constructed by concatenating the SELECT column list,
204: * FROM table specification and WHERE clause appropriate to the supplied
205: * Filter.
206: * </p>
207: *
208: * @param typeName The name of the table (feature type) to be queried
209: * @param mapper FIDMapper to identify the FID columns in the table
210: * @param attrTypes The specific attribute columns to be selected
211: * @param filter The Filter that will be used by the encoder to construct
212: * the WHERE clause
213: *
214: * @return The fully formed SQL SELECT statement
215: *
216: * @throws SQLEncoderException Not thrown by this method but may be thrown
217: * by the encoder class
218: * FIXME: Throw FilterToSQLException when the parent interface is fixed.
219: */
220: public String buildSQLQuery(String typeName, FIDMapper mapper,
221: AttributeType[] attrTypes, org.opengis.filter.Filter filter)
222: throws SQLEncoderException {
223: StringBuffer sqlBuffer = new StringBuffer();
224:
225: sqlBuffer.append("SELECT ");
226: sqlColumns(sqlBuffer, mapper, attrTypes);
227: sqlFrom(sqlBuffer, typeName);
228: encoder.setFIDMapper(mapper);
229: sqlWhere(sqlBuffer, filter);
230:
231: String sqlStmt = sqlBuffer.toString();
232:
233: return sqlStmt;
234: }
235:
236: /**
237: * Appends the names of the columns to be selected.
238: *
239: * <p>
240: * sqlGeometryColumn is invoked for any special handling for geometry
241: * columns.
242: * </p>
243: *
244: * @param sql StringBuffer to be appended to
245: * @param mapper FIDMapper to provide the name(s) of the FID columns
246: * @param attributes Array of columns to be selected
247: *
248: * @see postgisDataStore.SQLBuilder#sqlColumns(java.lang.StringBuffer,
249: * postgisDataStore.FIDMapper.FIDMapper,
250: * org.geotools.feature.AttributeType[])
251: */
252: public void sqlColumns(StringBuffer sql, FIDMapper mapper,
253: AttributeType[] attributes) {
254: for (int i = 0; i < mapper.getColumnCount(); i++) {
255: sql.append(encoder.escapeName(mapper.getColumnName(i))
256: + ", ");
257: }
258:
259: for (int i = 0; i < attributes.length; i++) {
260: if (attributes[i] instanceof GeometryAttributeType) {
261: sqlGeometryColumn(sql, attributes[i]);
262: } else {
263: sql.append(encoder.escapeName(attributes[i].getName()));
264: }
265:
266: if (i < (attributes.length - 1)) {
267: sql.append(", ");
268: }
269: }
270: }
271:
272: /**
273: * Generates the select column specification for a geometry column.
274: *
275: * <p>
276: * This should typically be overridden in the subclass to return a
277: * meaningful value that the attribute i/o handler can process.
278: * </p>
279: *
280: * @param sql A StringBuffer that the column specification can be appended
281: * to
282: * @param geomAttribute An AttributeType for a geometry attribute
283: */
284: public void sqlGeometryColumn(StringBuffer sql,
285: AttributeType geomAttribute) {
286: sql.append(encoder.escapeName(geomAttribute.getName()));
287: }
288:
289: /**
290: * Generates the order by clause.
291: * <p>
292: * This uses the standard ASC,DESC sql keywords to denote ascending,descending
293: * sort respectivley.
294: * </p>
295: *
296: * FIXME: Throw FilterToSQLException when the parent interface is fixed.
297: */
298: public void sqlOrderBy(StringBuffer sql, SortBy[] sortBy)
299: throws SQLEncoderException {
300: if (sortBy == null || sortBy.length == 0)
301: return; //nothing to sort on
302:
303: sql.append(" ORDER BY ");
304: for (int i = 0; i < sortBy.length; i++) {
305: AttributeType type = (AttributeType) sortBy[i]
306: .getPropertyName().evaluate(ft);
307: if (type != null) {
308: sql.append(encoder.escapeName(type.getName()));
309: } else {
310: sql.append(encoder.escapeName(sortBy[i]
311: .getPropertyName().getPropertyName()));
312: }
313:
314: if (SortOrder.DESCENDING.equals(sortBy[i].getSortOrder())) {
315: sql.append(" DESC");
316: } else {
317: sql.append(" ASC");
318: }
319:
320: if (i < sortBy.length - 1) {
321: sql.append(", ");
322: }
323: }
324:
325: }
326:
327: }
|