/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-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.ogr; import java.io.IOException; import java.io.StringWriter; import java.util.Iterator; import org.geotools.factory.Hints; import org.geotools.util.ConverterFactory; import org.geotools.util.Converters; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.filter.And; import org.opengis.filter.BinaryComparisonOperator; import org.opengis.filter.BinaryLogicOperator; import org.opengis.filter.ExcludeFilter; import org.opengis.filter.Filter; import org.opengis.filter.FilterVisitor; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; import org.opengis.filter.Not; import org.opengis.filter.Or; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsGreaterThan; import org.opengis.filter.PropertyIsGreaterThanOrEqualTo; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.filter.PropertyIsLike; import org.opengis.filter.PropertyIsNil; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.PropertyIsNull; import org.opengis.filter.expression.Add; import org.opengis.filter.expression.Divide; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.ExpressionVisitor; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.Multiply; import org.opengis.filter.expression.NilExpression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.expression.Subtract; import org.opengis.filter.spatial.BBOX; import org.opengis.filter.spatial.Beyond; import org.opengis.filter.spatial.Contains; import org.opengis.filter.spatial.Crosses; import org.opengis.filter.spatial.DWithin; import org.opengis.filter.spatial.Disjoint; import org.opengis.filter.spatial.Equals; import org.opengis.filter.spatial.Intersects; import org.opengis.filter.spatial.Overlaps; import org.opengis.filter.spatial.Touches; import org.opengis.filter.spatial.Within; import org.opengis.filter.temporal.After; import org.opengis.filter.temporal.AnyInteracts; import org.opengis.filter.temporal.Before; import org.opengis.filter.temporal.Begins; import org.opengis.filter.temporal.BegunBy; import org.opengis.filter.temporal.During; import org.opengis.filter.temporal.EndedBy; import org.opengis.filter.temporal.Ends; import org.opengis.filter.temporal.Meets; import org.opengis.filter.temporal.MetBy; import org.opengis.filter.temporal.OverlappedBy; import org.opengis.filter.temporal.TContains; import org.opengis.filter.temporal.TEquals; import org.opengis.filter.temporal.TOverlaps; /** * Encodes a compliant filter to the "restricted where" syntax supported by OGR: * * <pre> * @condition@ = @field_name@ @binary_operator@ @value@ | "(" @condition@ ")" @binary_logical_operator@ * "(" @condition@ ")" * @binary_operator@ = "<" | ">" | "<=" | ">=" | "<>" | "=" * @binary_logical_operator@ = "AND" | "OR" * @field_name@ = @string_token@ * @value@ = @string_token@ | @numeric_value@ | @string_value@ * @string_value@ = "'" @characters@ "'" * </pre> * * Implementation wise this is a widely cut down version of JDBC's module FilterToSQL * * @author Andrea Aime - GeoSolutions */ class FilterToRestrictedWhere implements FilterVisitor, ExpressionVisitor { /** error message for exceptions */ protected static final String IO_ERROR = "io problem writing filter"; /** the schema the encoder will be used to be encode sql for */ protected SimpleFeatureType featureType; StringWriter out = new StringWriter(); public FilterToRestrictedWhere(SimpleFeatureType featureType) { this.featureType = featureType; } public String getRestrictedWhere() { return out.toString(); } /** * Writes the SQL for the PropertyIsBetween Filter. * * @param filter the Filter to be visited. * * @throws RuntimeException for io exception with writer */ public Object visit(PropertyIsBetween filter, Object extraData) throws RuntimeException { Expression expr = (Expression) filter.getExpression(); Expression lowerbounds = (Expression) filter.getLowerBoundary(); Expression upperbounds = (Expression) filter.getUpperBoundary(); Class context; AttributeDescriptor attType = (AttributeDescriptor) expr.evaluate(featureType); if (attType != null) { context = attType.getType().getBinding(); } else { context = String.class; } out.write("(("); expr.accept(this, extraData); out.write(">="); lowerbounds.accept(this, context); out.write(") AND ("); expr.accept(this, extraData); out.write("<="); upperbounds.accept(this, context); out.write("))"); return extraData; } /** * Write the SQL for an And filter * * @param filter the filter to visit * @param extraData extra data (unused by this method) * */ public Object visit(And filter, Object extraData) { return visit((BinaryLogicOperator) filter, "AND"); } /** * Write the SQL for an Or filter * * @param filter the filter to visit * @param extraData extra data (unused by this method) * */ public Object visit(Or filter, Object extraData) { return visit((BinaryLogicOperator) filter, "OR"); } /** * Common implementation for BinaryLogicOperator filters. This way they're all handled * centrally. * * @param filter the logic statement to be turned into SQL. * @param extraData extra filter data. Not modified directly by this method. */ protected Object visit(BinaryLogicOperator filter, Object extraData) { String type = (String) extraData; out.write("("); Iterator<Filter> list = filter.getChildren().iterator(); while (list.hasNext()) { list.next().accept(this, extraData); if (list.hasNext()) { out.write(" " + type + " "); } } out.write(")"); return extraData; } public Object visit(PropertyIsEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator) filter, "="); return extraData; } public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator) filter, ">="); return extraData; } public Object visit(PropertyIsGreaterThan filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator) filter, ">"); return extraData; } public Object visit(PropertyIsLessThan filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator) filter, "<"); return extraData; } public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator) filter, "<="); return extraData; } public Object visit(PropertyIsNotEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator) filter, "!="); return extraData; } protected void visitBinaryComparisonOperator(BinaryComparisonOperator filter, Object extraData) throws RuntimeException { Expression left = filter.getExpression1(); Expression right = filter.getExpression2(); // see if we can get some indication on how to evaluate literals Class leftContext = null, rightContext = null; if (left instanceof PropertyName) { AttributeDescriptor attType = (AttributeDescriptor) left.evaluate(featureType); if (attType != null) { rightContext = attType.getType().getBinding(); } } if (rightContext == null && right instanceof PropertyName) { AttributeDescriptor attType = (AttributeDescriptor) right.evaluate(featureType); if (attType != null) { leftContext = attType.getType().getBinding(); } } String type = (String) extraData; left.accept(this, leftContext); out.write(" " + type + " "); right.accept(this, rightContext); } public Object visit(PropertyName expression, Object extraData) throws RuntimeException { AttributeDescriptor attribute = null; try { attribute = (AttributeDescriptor) expression.evaluate(featureType); } catch (Exception e) { // just log and fall back on just encoding propertyName straight up String msg = "Error occured mapping " + expression + " to feature type"; } String name = null; if (attribute != null) { name = attribute.getLocalName(); } else { name = expression.getPropertyName(); } out.write(name); return extraData; } /** * Export the contents of a Literal Expresion * * @param expression the Literal to export * * @throws RuntimeException for io exception with writer */ public Object visit(Literal expression, Object context) throws RuntimeException { // type to convert the literal to Class target = null; if (context instanceof Class) { target = (Class) context; } // evaluate the expression Object literal = evaluateLiteral(expression, target); writeLiteral(literal); return context; } protected Object evaluateLiteral(Literal expression, Class target) { Object literal = null; // HACK: let expression figure out the right value for numbers, // since the context is almost always improperly set and the // numeric converters try to force floating points to integrals // JD: the above is no longer true, so instead do a safe conversion if (target != null) { // use the target type if (Number.class.isAssignableFrom(target)) { literal = Converters.convert(expression.evaluate(null), target, new Hints( ConverterFactory.SAFE_CONVERSION, true)); } else { literal = expression.evaluate(null, target); } } // if the target was not known, of the conversion failed, try the // type guessing dance literal expression does only for the following // method call if (literal == null) literal = expression.evaluate(null); // if that failed as well, grab the value as is if (literal == null) literal = expression.getValue(); return literal; } /** * Writes out a non null, non geometry literal. The base class properly handles null, numeric * and booleans (true|false), and turns everything else into a string. Subclasses are expected * to override this shall they need a different treatment (e.g. for dates) * * @param literal * @throws IOException */ protected void writeLiteral(Object literal) { if (literal == null) { out.write("NULL"); } else if (literal instanceof Number || literal instanceof Boolean) { out.write(String.valueOf(literal)); } else { // we don't know what this is, let's convert back to a string String encoding = (String) Converters.convert(literal, String.class, null); if (encoding == null) { // could not convert back to string, use original l value encoding = literal.toString(); } // sigle quotes must be escaped to have a valid sql string String escaped = encoding.replaceAll("'", "''"); out.write("'" + escaped + "'"); } } @Override public Object visit(NilExpression expression, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Add expression, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Divide expression, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Function expression, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Multiply expression, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Subtract expression, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visitNullFilter(Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(ExcludeFilter filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(IncludeFilter filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Id filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Not filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(PropertyIsLike filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(PropertyIsNull filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(BBOX filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Beyond filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Contains filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Crosses filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Disjoint filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(DWithin filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Equals filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Intersects filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Overlaps filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Touches filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Within filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(After after, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(AnyInteracts anyInteracts, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Before before, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Begins begins, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(BegunBy begunBy, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(During during, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(EndedBy endedBy, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Ends ends, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(Meets meets, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(MetBy metBy, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(OverlappedBy overlappedBy, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(TContains contains, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(TEquals equals, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(TOverlaps contains, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } @Override public Object visit(PropertyIsNil filter, Object extraData) { throw new UnsupportedOperationException("Can't encode this expression"); } }