/*
* 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.filter;
import java.io.IOException;
import java.util.logging.Logger;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTWriter;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.Contains;
import org.opengis.filter.spatial.Crosses;
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.expression.Add;
import org.opengis.filter.expression.Divide;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.Multiply;
import org.opengis.filter.expression.Subtract;
import org.opengis.filter.ExcludeFilter;
import org.opengis.filter.Id;
import org.opengis.filter.IncludeFilter;
import org.opengis.filter.PropertyIsBetween;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.PropertyIsNull;
/**
* Encodes a filter into a SQL WHERE statement for MySQL. This class adds
* the ability to turn geometry filters into sql statements if they are
* bboxes.
*
* @author Chris Holmes, TOPP
* @author Debasish Sahu, debasish.sahu@rmsi.com
* @author Harry Bullen
*
* @source $URL$
*/
public class SQLEncoderMySQL extends org.geotools.data.jdbc.FilterToSQL implements org.opengis.filter.FilterVisitor {
/** Standard java logger */
private static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter");
/** To write geometry so mysql can read it. */
private static WKTWriter wkt = new WKTWriter();
/** The standard SQL multicharacter wild card. */
private static final String SQL_WILD_MULTI = "%";
/** The standard SQL single character wild card. */
private static final String SQL_WILD_SINGLE = "_";
/**
* The srid of the schema, so the bbox conforms. Could be better to have
* it in the bbox filter itself, but this works for now.
*/
private int srid;
/** The geometry attribute to use if none is specified. */
private String defaultGeom;
/** The escaped version of the multiple wildcard for the REGEXP pattern. */
private String escapedWildcardMulti = "\\.\\*";
/** The escaped version of the single wildcard for the REGEXP pattern. */
private String escapedWildcardSingle = "\\.\\?";
/**
* Empty constructor TODO: rethink empty constructor, as BBOXes _need_ an
* SRID, must make client set it somehow. Maybe detect when encode is
* called?
*/
public SQLEncoderMySQL() {
capabilities = createFilterCapabilities();
setSqlNameEscape("");
}
public SQLEncoderMySQL(int srid) {
this.srid = srid;
}
/**
* @see org.geotools.filter.SQLEncoder#createFilterCapabilities()
*/
protected FilterCapabilities createFilterCapabilities() {
FilterCapabilities capabilities = new FilterCapabilities();
capabilities.addType(FilterCapabilities.LOGIC_OR);
capabilities.addType(FilterCapabilities.LOGIC_AND);
capabilities.addType(FilterCapabilities.LOGIC_NOT);
capabilities.addAll(FilterCapabilities.LOGICAL_OPENGIS);
capabilities.addType(FilterCapabilities.COMPARE_EQUALS);
capabilities.addType(FilterCapabilities.COMPARE_NOT_EQUALS);
capabilities.addType(FilterCapabilities.COMPARE_LESS_THAN);
capabilities.addType(FilterCapabilities.COMPARE_GREATER_THAN);
capabilities.addType(FilterCapabilities.COMPARE_LESS_THAN_EQUAL);
capabilities.addType(FilterCapabilities.COMPARE_GREATER_THAN_EQUAL);
capabilities.addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS);
capabilities.addType(FilterCapabilities.NULL_CHECK);
capabilities.addType(PropertyIsNull.class);
capabilities.addType(FilterCapabilities.BETWEEN);
capabilities.addType(PropertyIsBetween.class);
capabilities.addType(FilterCapabilities.NONE);
capabilities.addType(FilterCapabilities.ALL);
capabilities.addType(FilterCapabilities.SPATIAL_BBOX);
capabilities.addType(BBOX.class);
capabilities.addType(FilterCapabilities.FID);
capabilities.addType(Id.class);
capabilities.addType(FilterCapabilities.LIKE);
capabilities.addType(PropertyIsLike.class);
return capabilities;
}
/**
* Sets a spatial reference system ESPG number, so that the geometry can be
* properly encoded for postgis. If geotools starts actually creating
* geometries with valid srids then this method will no longer be needed.
*
* @param srid the integer code for the EPSG spatial reference system.
*/
public void setSRID(int srid) {
this.srid = srid;
}
/**
* Sets the default geometry, so that filters with null for one of their
* expressions can assume that the default geometry is intended.
*
* @param name the name of the default geometry Attribute.
*
* @task REVISIT: pass in a featureType so that geometries can figure out
* their own default geometry?
*/
public void setDefaultGeometry(String name) {
//Do we really want clients to be using malformed filters?
//I mean, this is a useful method for unit tests, but shouldn't
//fully formed filters usually be used? Though I guess adding
//the option wouldn't hurt. -ch
this.defaultGeom = name;
}
/**
* Turns a geometry filter into the mySQL bbox statement.
*
* @param filter the geometry filter to be encoded.
*
* @throws RuntimeException for IO exception (need a better error)
*/
public Object visit(BBOX filter, Object extra)
{
DefaultExpression left = (DefaultExpression) filter.getExpression1();
DefaultExpression right = (DefaultExpression) filter.getExpression2();
// left and right have to be valid expressions
try {
out.write("MBRIntersects(");
if (left == null) {
out.write(defaultGeom);
} else {
// we want this to be returned as a Geometry
left.accept(this,Geometry.class);
}
out.write(", ");
if (right == null) {
out.write(defaultGeom);
} else {
// we want this to be returned as a Geometry
right.accept(this,Geometry.class);
}
out.write(")");
} catch (java.io.IOException ioe) {
LOGGER.warning("Unable to export filter" + ioe);
}
return extra;
}
/**
* Checks to see if the literal is a geometry, and encodes it if it is, if
* not just sends to the parent class.
*
* @param expression the expression to visit and encode.
*
* @throws IOException for IO exception (need a better error)
* TODO: this should use WKB
*/
public void visitLiteralGeometry(Literal expression)
throws IOException {
Geometry bbox = (Geometry) expression.getValue();
String geomText = wkt.write(bbox);
out.write("GeometryFromText('" + geomText + "', " + srid + ")");
}
public void visit(PropertyIsLike filter) {
try {
String pattern = filter.getLiteral();
pattern = pattern.replaceAll(escapedWildcardMulti, SQL_WILD_MULTI);
pattern = pattern.replaceAll(escapedWildcardSingle, SQL_WILD_SINGLE);
//pattern = pattern.replace('\\', ''); //get rid of java escapes.
out.write("UPPER(");
filter.getExpression().accept(this,null);
out.write(") LIKE ");
out.write("UPPER('" + pattern + "')");
String esc = filter.getEscape();
if (pattern.indexOf(esc) != -1) { //if it uses the escape char
out.write(" ESCAPE " + "'" + esc + "'"); //this needs testing
}
//TODO figure out when to add ESCAPE clause, probably just for the
// '_' char.
} catch (java.io.IOException ioe) {
LOGGER.warning("Unable to export filter" + ioe);
}
}
}