001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-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: */
017: package org.geotools.arcsde.filter;
018:
019: import java.util.Collections;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.logging.Level;
025: import java.util.logging.Logger;
026:
027: import net.sf.jsqlparser.schema.Column;
028: import net.sf.jsqlparser.statement.select.PlainSelect;
029: import net.sf.jsqlparser.statement.select.SelectExpressionItem;
030: import net.sf.jsqlparser.statement.select.SelectItem;
031:
032: import org.geotools.arcsde.data.ArcSDEAdapter;
033: import org.geotools.data.jdbc.FilterToSQL;
034: import org.geotools.data.jdbc.FilterToSQLException;
035: import org.geotools.feature.FeatureType;
036: import org.geotools.filter.FilterCapabilities;
037: import org.opengis.filter.ExcludeFilter;
038: import org.opengis.filter.Filter;
039: import org.opengis.filter.FilterVisitor;
040: import org.opengis.filter.Id;
041: import org.opengis.filter.IncludeFilter;
042: import org.opengis.filter.PropertyIsBetween;
043: import org.opengis.filter.PropertyIsLike;
044: import org.opengis.filter.PropertyIsNull;
045: import org.opengis.filter.expression.PropertyName;
046:
047: /**
048: * Encodes an attribute filter into a SQL WHERE statement for arcsde.
049: *
050: * <p>
051: * Although not all filters support is coded yet, the strategy to filtering
052: * queries for ArcSDE datasources is separated in two parts, the SQL where
053: * clause construction, provided here and the spatial filters (or spatial
054: * constraints, in SDE vocabulary) provided by <code>GeometryEncoderSDE</code>;
055: * mirroring the java SDE api approach for easy programing
056: * </p>
057: *
058: * @author Saul Farber
059: * @author Gabriel Roldan
060: *
061: * @see org.geotools.data.sde.GeometryEncoderSDE
062: * @source $URL:
063: * http://gtsvn.refractions.net/geotools/branches/2.4.x/modules/unsupported/arcsde/datastore/src/main/java/org/geotools/arcsde/filter/FilterToSQLSDE.java $
064: */
065: public class FilterToSQLSDE extends FilterToSQL implements
066: FilterVisitor {
067: /** Standard java logger */
068: private static Logger LOGGER = org.geotools.util.logging.Logging
069: .getLogger("org.geotools.filter");
070:
071: private String layerQualifiedName;
072:
073: private String layerFidFieldName;
074:
075: /** DOCUMENT ME! */
076: private PlainSelect definitionQuery;
077:
078: /**
079: * If definitionQuery != null, holds alias/unaliased attribute names from
080: * the sql query
081: */
082: private Map attributeNames = Collections.EMPTY_MAP;
083:
084: /**
085: *
086: * @param layerQName
087: * full qualified name of the ArcSDE layer
088: * @param layerFidColName
089: * name of the column that holds fids
090: * @param ft
091: * @param definitionQuery
092: */
093: public FilterToSQLSDE(String layerQName, String layerFidColName,
094: FeatureType ft, PlainSelect definitionQuery) {
095: this .layerQualifiedName = layerQName;
096: this .layerFidFieldName = layerFidColName;
097: this .featureType = ft;
098: this .definitionQuery = definitionQuery;
099:
100: if (definitionQuery != null) {
101: attributeNames = new HashMap();
102:
103: List selectItems = definitionQuery.getSelectItems();
104: SelectItem item;
105:
106: for (Iterator it = selectItems.iterator(); it.hasNext();) {
107: item = (SelectItem) it.next();
108:
109: if (!(item instanceof SelectExpressionItem)) {
110: String msg = "for item '"
111: + item
112: + "': only SelectExpressionItems should be in query at this stage."
113: + " AllColumns and AllTableColumns instances should be resolved to their list "
114: + " of column names at view registration time.";
115: LOGGER.severe(msg);
116: throw new IllegalStateException(msg);
117: }
118: SelectExpressionItem colDef = (SelectExpressionItem) item;
119: String alias = colDef.getAlias();
120: if (alias == null) {
121: if (!(colDef.getExpression() instanceof Column)) {
122: throw new RuntimeException(
123: "if select item is not a plain column an alias should be provided: "
124: + colDef);
125: }
126: Column column = (Column) colDef.getExpression();
127: alias = column.getColumnName();
128: }
129:
130: attributeNames.put(alias, colDef);
131: }
132: }
133: }
134:
135: /**
136: * Returns the full qualifed name of sql expression that is registered as
137: * the source of the attribute named <code>alias</code>.
138: *
139: * @param alias
140: * @return
141: */
142: public String getColumnDefinition(String alias) {
143: final String encodedColumnDefinition;
144: if (this .definitionQuery != null) {
145: // its an inprocess view
146: SelectExpressionItem colDef = (SelectExpressionItem) attributeNames
147: .get(alias);
148: // String alias = colDef.getAlias();
149: String sqlExpression = String.valueOf(colDef);
150:
151: // encodedColumnDefinition = sqlExpression + " AS " + alias;
152: encodedColumnDefinition = sqlExpression;
153: } else {
154: if (alias.indexOf(":") != -1) {
155: // we've got to 'de-namespaceify' this attribute, if neccesary
156: alias = alias.substring(alias.indexOf(":") + 1);
157: }
158: encodedColumnDefinition = layerQualifiedName + "." + alias;
159: }
160: return encodedColumnDefinition;
161: }
162:
163: /**
164: * Overrides the superclass implementation to indicate that we support
165: * pushing FeatureId filters down into the data store.
166: *
167: * @return DOCUMENT ME!
168: */
169: protected FilterCapabilities createFilterCapabilities() {
170: FilterCapabilities capabilities = new FilterCapabilities();
171:
172: capabilities.addAll(FilterCapabilities.LOGICAL_OPENGIS);
173: capabilities
174: .addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS);
175: capabilities.addType(PropertyIsNull.class);
176: capabilities.addType(PropertyIsBetween.class);
177: capabilities.addType(Id.class);
178: capabilities.addType(IncludeFilter.class);
179: capabilities.addType(ExcludeFilter.class);
180: capabilities.addType(PropertyIsLike.class);
181:
182: return capabilities;
183: }
184:
185: /**
186: * overriden just to avoid the "WHERE" keyword
187: *
188: * @param out
189: * DOCUMENT ME!
190: * @param filter
191: * DOCUMENT ME!
192: *
193: * @throws GeoAPIFilterToSQLEncoderException
194: * DOCUMENT ME!
195: */
196: public void encode(Filter filter) throws FilterToSQLException {
197: if (getCapabilities().fullySupports(filter)) {
198: filter.accept(this , null);
199: } else {
200: throw new FilterToSQLException("Filter type not supported");
201: }
202: }
203:
204: /**
205: * This only exists the fulfill the interface - unless There is a way of
206: * determining the FID column in the database...
207: *
208: * @param filter
209: * the Fid Filter.
210: *
211: * @throws RuntimeException
212: * DOCUMENT ME!
213: */
214: public Object visit(Id filter, Object unused) {
215: long[] fids = ArcSDEAdapter.getNumericFids(filter
216: .getIdentifiers());
217: int nFids = fids.length;
218:
219: if (nFids == 0) {
220: return unused;
221: }
222:
223: String fidField = layerFidFieldName;
224:
225: try {
226: StringBuffer sb = new StringBuffer();
227: sb.append("(" + fidField + " IN(");
228:
229: for (int i = 0; i < nFids; i++) {
230: sb.append(fids[i]);
231:
232: if (i < (nFids - 1)) {
233: sb.append(", ");
234: }
235: if (i == 999) {
236: sb.deleteCharAt(sb.length() - 1); // delete the trailing
237: // space
238: sb.deleteCharAt(sb.length() - 1); // delete the trailing
239: // comma
240: sb.append(")) OR (" + fidField + " IN(");
241: }
242: }
243:
244: sb.append("))");
245:
246: if (LOGGER.isLoggable(Level.FINER)) {
247: LOGGER.finer("added fid filter: " + sb.toString());
248: }
249:
250: this .out.write(sb.toString());
251: } catch (Exception ex) {
252: throw new RuntimeException(ex.getMessage(), ex);
253: }
254: return unused;
255: }
256:
257: /**
258: * Writes the SQL for the attribute Expression.
259: *
260: * NOTE: If the feature type is the product of an in process sql query, the
261: * attribute name encoded will be the actual one, not the alias (if any)
262: * used in the sql query.
263: *
264: * @param expression
265: * the attribute to turn to SQL.
266: *
267: * @throws RuntimeException
268: * for io exception with writer
269: */
270: public Object visit(PropertyName expression, Object extraData)
271: throws RuntimeException {
272: LOGGER.finer("exporting PropertyName");
273: final String attName = expression.getPropertyName();
274:
275: final String encodedColumnDefinition = getColumnDefinition(attName);
276:
277: try {
278: out.write(encodedColumnDefinition);
279: } catch (java.io.IOException ioe) {
280: throw new RuntimeException(
281: "IO problems writing attribute exp", ioe);
282: }
283:
284: return extraData;
285: }
286: }
|