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 SQLEncoder 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/DefaultSQLBuilder.java $
054: * @deprecated Use GeoAPISQLBuilder instead
055: */
056: public class DefaultSQLBuilder implements SQLBuilder {
057: // The instance of the encoder to be used to generate the WHERE clause
058: protected SQLEncoder encoder;
059:
060: protected FeatureType ft;
061:
062: protected ClientTransactionAccessor accessor;
063:
064: private Filter lastFilter = null;
065: private Filter lastPreFilter = null;
066: private Filter lastPostFilter = null;
067:
068: private Hints hints;
069:
070: /**
071: * Constructs an instance of this class with a default SQLEncoder
072: */
073: public DefaultSQLBuilder() {
074: this (new SQLEncoder());
075: }
076:
077: /**
078: * Constructs an instance of this class using the encoder class specified.
079: * This will typically be from the getSqlBuilder method of a JDBCDataStore
080: * subclass.
081: * <p>
082: * This constructor should not be used to obtain Pre/Post filters, as these
083: * methods require a FeatureType to function properly.
084: *
085: * @deprecated
086: * @param encoder the specific encoder to be used.
087: */
088: public DefaultSQLBuilder(SQLEncoder encoder) {
089: this (encoder, null, null);
090: }
091:
092: /**
093: * Constructs an instance of this class using the encoder class specified.
094: * This will typically be from the getSqlBuilder method of a JDBCDataStore
095: * subclass.
096: *
097: * @param encoder the specific encoder to be used.
098: * @param featureType
099: * @param accessor client-side transaction handler; may be null.
100: */
101: public DefaultSQLBuilder(SQLEncoder encoder,
102: FeatureType featureType, ClientTransactionAccessor accessor) {
103: this .encoder = encoder;
104: this .ft = featureType;
105: this .accessor = accessor;
106:
107: //set the feature type on teh encoders
108: encoder.setFeatureType(featureType);
109: }
110:
111: public void setHints(Hints hints) {
112: this .hints = hints;
113: }
114:
115: /** Check the hints to see if we are forced into 2D */
116: public synchronized boolean isForce2D() {
117: if (hints == null) {
118: return false;
119: }
120: Boolean force2d = (Boolean) hints.get(Hints.FEATURE_2D);
121: if (force2d == null) {
122: return false;
123: }
124: return force2d.booleanValue();
125: }
126:
127: /**
128: * Return the postQueryFilter that must be applied to the database query
129: * result set.
130: *
131: * @param filter the application filter which must be applied
132: *
133: * @return the filter representing the functionality that must be performed
134: * on the result set.
135: */
136: public Filter getPostQueryFilter(Filter filter) {
137: if (filter != null
138: && (lastFilter == null || filter != lastFilter)) {
139: splitFilter(filter);
140: }
141: return lastPostFilter;
142:
143: // SQLUnpacker unpacker = new SQLUnpacker(cap);
144: //
145: // //figure out which of the filter we can use.
146: // unpacker.unPackAND(filter);
147: //
148: // return unpacker.getUnSupported();
149: }
150:
151: /**
152: * Return the preQueryFilter that can be used to generate the WHERE clause.
153: *
154: * @param filter the application filter which must be applied
155: *
156: * @return the filter representing the functionality that can be performed
157: * by the database.
158: */
159: public Filter getPreQueryFilter(Filter filter) {
160: if (filter != null
161: && (lastFilter == null || !filter.equals(lastFilter))) {
162: splitFilter(filter);
163: }
164: return lastPreFilter;
165: // SQLUnpacker unpacker = new SQLUnpacker(encoder.getCapabilities());
166: //
167: // //figure out which of the filter we can use.
168: // unpacker.unPackAND(filter);
169: //
170: // return unpacker.getSupported();
171: }
172:
173: protected void splitFilter(Filter filter) {
174: lastFilter = filter;
175: FilterCapabilities cap = encoder.getCapabilities();
176: PostPreProcessFilterSplittingVisitor pfv = new PostPreProcessFilterSplittingVisitor(
177: cap, ft, accessor);
178:
179: filter.accept(pfv, null);
180:
181: lastPreFilter = (Filter) pfv.getFilterPre();
182: lastPostFilter = (Filter) pfv.getFilterPost();
183: }
184:
185: /**
186: * Constructs the FROM clause for a featureType
187: *
188: * <p>
189: * sql: <code>FROM typeName</code>
190: * </p>
191: *
192: * @param sql the StringBuffer that the WHERE clause should be appended to
193: * @param typeName the name of the table (feature type) to be queried
194: */
195: public void sqlFrom(StringBuffer sql, String typeName) {
196: sql.append(" FROM ");
197: sql.append(encoder.escapeName(typeName));
198: }
199:
200: /**
201: * Constructs WHERE clause, if needed, for FILTER.
202: *
203: * <p>
204: * sql: <code>WHERE filter encoding</code>
205: * </p>
206: *
207: * @param sql The StringBuffer that the WHERE clause should be appended to
208: * @param preFilter The filter to be used by the encoder class to generate
209: * the WHERE clause
210: *
211: * @throws SQLEncoderException Not thrown here but may be thrown by the
212: * encoder
213: */
214: public void sqlWhere(StringBuffer sql, Filter preFilter)
215: throws SQLEncoderException {
216: if ((preFilter != null) && (preFilter != Filter.INCLUDE)) {
217: String where = encoder.encode(preFilter);
218: sql.append(" ");
219: sql.append(where);
220: }
221: }
222:
223: /**
224: * Constructs the full SQL SELECT statement for the supplied Filter.
225: *
226: * <p>
227: * The statement is constructed by concatenating the SELECT column list,
228: * FROM table specification and WHERE clause appropriate to the supplied
229: * Filter.
230: * </p>
231: *
232: * @param typeName The name of the table (feature type) to be queried
233: * @param mapper FIDMapper to identify the FID columns in the table
234: * @param attrTypes The specific attribute columns to be selected
235: * @param filter The Filter that will be used by the encoder to construct
236: * the WHERE clause
237: *
238: * @return The fully formed SQL SELECT statement
239: *
240: * @throws SQLEncoderException Not thrown by this method but may be thrown
241: * by the encoder class
242: */
243: public String buildSQLQuery(String typeName, FIDMapper mapper,
244: AttributeType[] attrTypes, org.opengis.filter.Filter filter)
245: throws SQLEncoderException {
246: StringBuffer sqlBuffer = new StringBuffer();
247:
248: sqlBuffer.append("SELECT ");
249: sqlColumns(sqlBuffer, mapper, attrTypes);
250: sqlFrom(sqlBuffer, typeName);
251: encoder.setFIDMapper(mapper);
252: sqlWhere(sqlBuffer, filter);
253:
254: String sqlStmt = sqlBuffer.toString();
255:
256: return sqlStmt;
257: }
258:
259: /**
260: * Appends the names of the columns to be selected.
261: *
262: * <p>
263: * sqlGeometryColumn is invoked for any special handling for geometry
264: * columns.
265: * </p>
266: *
267: * @param sql StringBuffer to be appended to
268: * @param mapper FIDMapper to provide the name(s) of the FID columns
269: * @param attributes Array of columns to be selected
270: *
271: * @see postgisDataStore.SQLBuilder#sqlColumns(java.lang.StringBuffer,
272: * postgisDataStore.FIDMapper.FIDMapper,
273: * org.geotools.feature.AttributeType[])
274: */
275: public void sqlColumns(StringBuffer sql, FIDMapper mapper,
276: AttributeType[] attributes) {
277: for (int i = 0; i < mapper.getColumnCount(); i++) {
278: sql.append(encoder.escapeName(mapper.getColumnName(i))
279: + ", ");
280: }
281:
282: for (int i = 0; i < attributes.length; i++) {
283: if (attributes[i] instanceof GeometryAttributeType) {
284: sqlGeometryColumn(sql, attributes[i]);
285: } else {
286: sql.append(encoder.escapeName(attributes[i].getName()));
287: }
288:
289: if (i < (attributes.length - 1)) {
290: sql.append(", ");
291: }
292: }
293: }
294:
295: /**
296: * Generates the select column specification for a geometry column.
297: *
298: * <p>
299: * This should typically be overridden in the subclass to return a
300: * meaningful value that the attribute i/o handler can process.
301: * </p>
302: *
303: * @param sql A StringBuffer that the column specification can be appended
304: * to
305: * @param geomAttribute An AttributeType for a geometry attribute
306: */
307: public void sqlGeometryColumn(StringBuffer sql,
308: AttributeType geomAttribute) {
309: sql.append(encoder.escapeName(geomAttribute.getName()));
310: }
311:
312: /**
313: * Generates the order by clause.
314: * <p>
315: * This uses the standard ASC,DESC sql keywords to denote ascending,descending
316: * sort respectivley.
317: * </p>
318: */
319: public void sqlOrderBy(StringBuffer sql, SortBy[] sortBy)
320: throws SQLEncoderException {
321: if (sortBy == null || sortBy.length == 0)
322: return; //nothing to sort on
323:
324: sql.append(" ORDER BY ");
325: for (int i = 0; i < sortBy.length; i++) {
326: AttributeType type = (AttributeType) sortBy[i]
327: .getPropertyName().evaluate(ft);
328: if (type != null) {
329: sql.append(encoder.escapeName(type.getName()));
330: } else {
331: sql.append(encoder.escapeName(sortBy[i]
332: .getPropertyName().getPropertyName()));
333: }
334:
335: if (SortOrder.DESCENDING.equals(sortBy[i].getSortOrder())) {
336: sql.append(" DESC");
337: } else {
338: sql.append(" ASC");
339: }
340:
341: if (i < sortBy.length - 1) {
342: sql.append(", ");
343: }
344: }
345:
346: }
347:
348: }
|