/*
* 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.
*
* Created on 10 July 2002, 17:14
*/
package org.geotools.filter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.PropertyIsNull;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.identity.FeatureId;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.helpers.NamespaceSupport;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
/**
* A dom based parser to build filters as per OGC 01-067
*
* @author Ian Turton, CCG
*
* @source $URL$
* @version $Id$
*
* @task TODO: split this class up into multiple methods.
*/
public final class FilterDOMParser {
/** The logger for the filter module. */
private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter");
/** Factory to create filters. */
private static final FilterFactory2 FILTER_FACT = CommonFactoryFinder.getFilterFactory2(null);
/** Number of children in a between filter. */
private static final int NUM_BETWEEN_CHILDREN = 3;
/** Map of comparison names to their filter types. */
private static java.util.Map comparisions = new java.util.HashMap();
/** Map of spatial filter names to their filter types. */
private static java.util.Map spatial = new java.util.HashMap();
/** Map of logical filter names to their filter types. */
private static java.util.Map logical = new java.util.HashMap();
static {
comparisions.put("PropertyIsEqualTo",
new Integer(FilterType.COMPARE_EQUALS));
comparisions.put("PropertyIsNotEqualTo",
new Integer(FilterType.COMPARE_NOT_EQUALS));
comparisions.put("PropertyIsGreaterThan",
new Integer(FilterType.COMPARE_GREATER_THAN));
comparisions.put("PropertyIsGreaterThanOrEqualTo",
new Integer(FilterType.COMPARE_GREATER_THAN_EQUAL));
comparisions.put("PropertyIsLessThan",
new Integer(FilterType.COMPARE_LESS_THAN));
comparisions.put("PropertyIsLessThanOrEqualTo",
new Integer(FilterType.COMPARE_LESS_THAN_EQUAL));
comparisions.put("PropertyIsLike", new Integer(AbstractFilter.LIKE));
comparisions.put("PropertyIsNull", new Integer(AbstractFilter.NULL));
comparisions.put("PropertyIsBetween",
new Integer(Filter.BETWEEN));
comparisions.put("FeatureId", new Integer(AbstractFilter.FID));
spatial.put("Equals", new Integer(AbstractFilter.GEOMETRY_EQUALS));
spatial.put("Disjoint", new Integer(AbstractFilter.GEOMETRY_DISJOINT));
spatial.put("Intersects",
new Integer(Filter.GEOMETRY_INTERSECTS));
spatial.put("Touches", new Integer(AbstractFilter.GEOMETRY_TOUCHES));
spatial.put("Crosses", new Integer(AbstractFilter.GEOMETRY_CROSSES));
spatial.put("Within", new Integer(AbstractFilter.GEOMETRY_WITHIN));
spatial.put("Contains", new Integer(AbstractFilter.GEOMETRY_CONTAINS));
spatial.put("Overlaps", new Integer(AbstractFilter.GEOMETRY_OVERLAPS));
spatial.put("BBOX", new Integer(AbstractFilter.GEOMETRY_BBOX));
// Beyond and DWithin not handled well
spatial.put("Beyond", new Integer(AbstractFilter.GEOMETRY_BEYOND));
spatial.put("DWithin", new Integer(AbstractFilter.GEOMETRY_DWITHIN));
logical.put("And", new Integer(AbstractFilter.LOGIC_AND));
logical.put("Or", new Integer(AbstractFilter.LOGIC_OR));
logical.put("Not", new Integer(AbstractFilter.LOGIC_NOT));
}
/**
* Creates a new instance of FilterXMLParser
*/
private FilterDOMParser() {
}
private static NamespaceSupport getNameSpaces(Node node)
{
NamespaceSupport namespaces = new NamespaceSupport();
while (node != null)
{
NamedNodeMap atts = node.getAttributes();
if (atts != null) {
for (int i=0; i<atts.getLength(); i++){
Node att = atts.item(i);
if (att.getNamespaceURI() != null
&& att.getNamespaceURI().equals("http://www.w3.org/2000/xmlns/")
&& namespaces.getURI(att.getLocalName()) == null){
namespaces.declarePrefix(att.getLocalName(), att.getNodeValue());
}
}
}
node = node.getParentNode();
}
return namespaces;
}
/**
* Parses the filter using DOM.
*
* @param root a dom node containing FILTER as the root element.
*
* @return DOCUMENT ME!
*
* @task TODO: split up this insanely long method.
*/
public static org.opengis.filter.Filter parseFilter(Node root) {
//NC - NameSpaceSupport
NamespaceSupport namespaces = getNameSpaces(root);
final ExpressionDOMParser expressionDOMParser = new ExpressionDOMParser(FILTER_FACT);
expressionDOMParser.setNamespaceContext(namespaces);
LOGGER.finer("parsingFilter " + root.getLocalName());
//NodeList children = root.getChildNodes();
//LOGGER.finest("children "+children);
if ((root == null) || (root.getNodeType() != Node.ELEMENT_NODE)) {
LOGGER.finest("bad node input ");
return null;
}
LOGGER.finest("processing root " + root.getLocalName() + " " + root.getNodeName());
Node child = root;
String childName = child.getLocalName();
if(childName==null){
childName= child.getNodeName();//HACK ?
}
if (childName.indexOf(':') != -1)
{
//the DOM parser wasnt properly set to handle namespaces...
childName = childName.substring(childName.indexOf(':')+1);
}
LOGGER.finest("looking up " + childName);
if (comparisions.containsKey(childName)) {
LOGGER.finer("a comparision filter " + childName);
//boolean like = false;
//boolean between = false;
try {
short type = ((Integer) comparisions.get(childName)).shortValue();
//CompareFilter filter = null;
LOGGER.finer("type is " + type);
if (type == AbstractFilter.FID) {
Set<FeatureId> ids = new HashSet<FeatureId>();
Element fidElement = (Element) child;
ids.add(FILTER_FACT.featureId(fidElement.getAttribute("fid")));
Node sibling = fidElement.getNextSibling();
while (sibling != null) {
LOGGER.finer("Parsing another FidFilter");
if (sibling.getNodeType() == Node.ELEMENT_NODE) {
fidElement = (Element) sibling;
String fidElementName = fidElement.getLocalName();
if(fidElementName==null){
fidElementName= fidElement.getNodeName();//HACK ?
}
if (fidElementName.indexOf(':') != -1)
{
//the DOM parser wasnt properly set to handle namespaces...
fidElementName = fidElementName.substring(fidElementName.indexOf(':')+1);
}
if ("FeatureId".equals(fidElementName)) {
ids.add(FILTER_FACT.featureId(fidElement.getAttribute("fid")));
}
}
sibling = sibling.getNextSibling();
}
return FILTER_FACT.id(ids);
} else if (type == AbstractFilter.BETWEEN) {
//BetweenFilter bfilter = FILTER_FACT.createBetweenFilter();
NodeList kids = child.getChildNodes();
if (kids.getLength() < NUM_BETWEEN_CHILDREN) {
throw new IllegalFilterException(
"wrong number of children in Between filter: expected 3 got "
+ kids.getLength());
}
Node value = child.getFirstChild();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
// first expression
//value = kid.getFirstChild();
//while(value.getNodeType() != Node.ELEMENT_NODE )
//value = value.getNextSibling();
LOGGER.finer("add middle value -> " + value + "<-");
Expression middle = expressionDOMParser.expression(value);
Expression lower = null;
Expression upper = null;
for (int i = 0; i < kids.getLength(); i++) {
Node kid = kids.item(i);
String kidName = (kid.getLocalName()!=null)?kid.getLocalName():kid.getNodeName();
if (kidName.indexOf(':') != -1)
{
//the DOM parser wasnt properly set to handle namespaces...
kidName = kidName.substring(kidName.indexOf(':')+1);
}
if (kidName.equalsIgnoreCase("LowerBoundary")) {
value = kid.getFirstChild();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
LOGGER.finer("add left value -> " + value + "<-");
lower = expressionDOMParser.expression(value);
}
if (kidName.equalsIgnoreCase("UpperBoundary")) {
value = kid.getFirstChild();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
LOGGER.finer("add right value -> " + value + "<-");
upper = expressionDOMParser.expression(value);
}
}
return FILTER_FACT.between( middle, lower, upper );
} else if (type == AbstractFilter.LIKE) {
String wildcard = null;
String single = null;
String escape = null;
String pattern = null;
Expression value = null;
NodeList map = child.getChildNodes();
for (int i = 0; i < map.getLength(); i++) {
Node kid = map.item(i);
if ((kid == null)
|| (kid.getNodeType() != Node.ELEMENT_NODE)) {
continue;
}
String res = (kid.getLocalName()!=null)?kid.getLocalName():kid.getNodeName();
if (res.indexOf(':') != -1)
{
//the DOM parser wasnt properly set to handle namespaces...
res = res.substring(res.indexOf(':')+1);
}
if (res.equalsIgnoreCase("PropertyName")) {
value = expressionDOMParser.expression(kid);
}
if (res.equalsIgnoreCase("Literal")) {
pattern = expressionDOMParser.expression(kid)
.toString();
}
}
NamedNodeMap kids = child.getAttributes();
for (int i = 0; i < kids.getLength(); i++) {
Node kid = kids.item(i);
//if(kid == null || kid.getNodeType() != Node.ELEMENT_NODE) continue;
String res = (kid.getLocalName()!=null)?kid.getLocalName():kid.getNodeName();
if (res.indexOf(':') != -1)
{
//the DOM parser wasnt properly set to handle namespaces...
res = res.substring(res.indexOf(':')+1);
}
if (res.equalsIgnoreCase("wildCard")) {
wildcard = kid.getNodeValue();
}
if (res.equalsIgnoreCase("singleChar")) {
single = kid.getNodeValue();
}
if (res.equalsIgnoreCase("escapeChar")
|| res.equalsIgnoreCase("escape")) {
escape = kid.getNodeValue();
}
}
if (!((wildcard == null) || (single == null)
|| (escape == null) || (pattern == null))) {
//LikeFilter lfilter = FILTER_FACT.createLikeFilter();
LOGGER.finer("Building like filter " + value.toString()
+ "\n" + pattern + " " + wildcard + " " + single
+ " " + escape);
//lfilter.setValue(value);
//lfilter.setPattern(pattern, wildcard, single, escape);
return FILTER_FACT.like( value, pattern, wildcard, single, escape );
}
LOGGER.finer("Problem building like filter\n" + pattern
+ " " + wildcard + " " + single + " " + escape);
return null;
} else if (type == AbstractFilter.NULL) {
return parseNullFilter(child);
}
// find and parse left and right values
Node value = child.getFirstChild();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
LOGGER.finest("add left value -> " + value + "<-");
Expression left = expressionDOMParser.expression(value);
value = value.getNextSibling();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
LOGGER.finest("add right value -> " + value + "<-");
Expression right = expressionDOMParser.expression(value);
switch (type){
case FilterType.COMPARE_EQUALS:
return FILTER_FACT.equals( left, right );
case FilterType.COMPARE_GREATER_THAN:
return FILTER_FACT.greater( left, right );
case FilterType.COMPARE_GREATER_THAN_EQUAL:
return FILTER_FACT.greaterOrEqual( left, right );
case FilterType.COMPARE_LESS_THAN:
return FILTER_FACT.less( left, right );
case FilterType.COMPARE_LESS_THAN_EQUAL:
return FILTER_FACT.lessOrEqual(left, right );
case FilterType.COMPARE_NOT_EQUALS:
return FILTER_FACT.notEqual(left, right, true);
default:
LOGGER.warning("Unable to build filter for " + childName);
return null;
}
} catch (IllegalFilterException ife) {
LOGGER.warning("Unable to build filter: " + ife);
return null;
}
} else if (spatial.containsKey(childName)) {
LOGGER.finest("a spatial filter " + childName);
try {
short type = ((Integer) spatial.get(childName)).shortValue();
// GeometryFilter filter = FILTER_FACT.createGeometryFilter(type);
Node value = child.getFirstChild();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
LOGGER.finest("add left value -> " + value + "<-");
Expression left = expressionDOMParser.expression(value);
value = value.getNextSibling();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
LOGGER.finest("add right value -> " + value + "<-");
String valueName = (value.getLocalName()!=null)?value.getLocalName():value.getNodeName();
if (valueName.indexOf(':') != -1)
{
//the DOM parser was not properly set to handle namespaces...
valueName = valueName.substring(valueName.indexOf(':')+1);
}
// need to cache the next node as the following parsing trick will
// ruin the DOM hierarchy
Node nextNode = value.getNextSibling();
Expression right;
if (!(valueName.equalsIgnoreCase("Literal")
|| valueName.equalsIgnoreCase("propertyname"))) {
Element literal = value.getOwnerDocument().createElement("literal");
literal.appendChild(value);
LOGGER.finest("Built new literal " + literal);
right = expressionDOMParser.expression(literal);
} else {
right = expressionDOMParser.expression(value);
}
double distance;
String units = null;
String nodeName = null;
switch ( type ){
case FilterType.GEOMETRY_EQUALS:
return FILTER_FACT.equal( left, right );
case FilterType.GEOMETRY_DISJOINT:
return FILTER_FACT.disjoint( left, right );
case FilterType.GEOMETRY_INTERSECTS:
return FILTER_FACT.intersects( left, right );
case FilterType.GEOMETRY_TOUCHES:
return FILTER_FACT.touches( left, right );
case FilterType.GEOMETRY_CROSSES:
return FILTER_FACT.touches( left, right );
case FilterType.GEOMETRY_WITHIN:
return FILTER_FACT.within( left, right );
case FilterType.GEOMETRY_CONTAINS:
return FILTER_FACT.contains( left, right );
case FilterType.GEOMETRY_OVERLAPS:
return FILTER_FACT.overlaps( left, right );
case FilterType.GEOMETRY_DWITHIN:
value = nextNode;
while (value != null && value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
if(value == null) {
throw new IllegalFilterException("DWithin is missing the Distance element");
}
nodeName = value.getNodeName();
if(nodeName.indexOf(':') > 0) {
nodeName = nodeName.substring(nodeName.indexOf(":") + 1);
}
if(!"Distance".equals(nodeName)) {
throw new IllegalFilterException("Parsing DWithin, was expecting to find Distance but found " + value.getLocalName());
}
distance = Double.parseDouble(value.getTextContent());
if(value.getAttributes().getNamedItem("units") != null)
units = value.getAttributes().getNamedItem("units").getTextContent();
return FILTER_FACT.dwithin(left, right, distance, units );
case FilterType.GEOMETRY_BEYOND:
value = nextNode;
while (value != null && value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
if(value == null) {
throw new IllegalFilterException("Beyond is missing the Distance element");
}
nodeName = value.getNodeName();
if(nodeName.indexOf(':') > 0) {
nodeName = nodeName.substring(nodeName.indexOf(":") + 1);
}
if(!"Distance".equals(nodeName)) {
throw new IllegalFilterException("Parsing Beyond, was expecting to find Distance but found " + value.getLocalName());
}
distance = Double.parseDouble(value.getTextContent());
if(value.getAttributes().getNamedItem("units") != null)
units = value.getAttributes().getNamedItem("units").getTextContent();
return FILTER_FACT.beyond(left, right, distance, units );
case FilterType.GEOMETRY_BBOX:
{
Literal literal = (Literal) right;
Object obj = literal.getValue();
ReferencedEnvelope bbox = null;
if( obj instanceof Geometry){
bbox = JTS.toEnvelope( (Geometry) obj );
}
else if (obj instanceof ReferencedEnvelope){
bbox = (ReferencedEnvelope) obj;
}
else if (obj instanceof Envelope){
// no clue about CRS / srsName so we should guess
bbox = new ReferencedEnvelope( (Envelope) obj, null );
}
return FILTER_FACT.bbox( left, bbox );
}
default:
LOGGER.warning("Unable to build filter: " + childName);
return null;
}
} catch (IllegalFilterException ife) {
LOGGER.warning("Unable to build filter: " + ife);
return null;
}
} else if (logical.containsKey(childName)) {
LOGGER.finest("a logical filter " + childName);
try {
List<org.opengis.filter.Filter> children = new ArrayList<org.opengis.filter.Filter>();
NodeList map = child.getChildNodes();
for (int i = 0; i < map.getLength(); i++) {
Node kid = map.item(i);
if ((kid == null)
|| (kid.getNodeType() != Node.ELEMENT_NODE)) {
continue;
}
LOGGER.finest("adding to logic filter " + kid.getLocalName());
children.add(parseFilter(kid));
}
if(childName.equals("And"))
return FILTER_FACT.and(children);
else if(childName.equals("Or"))
return FILTER_FACT.or(children);
else if(childName.equals("Not")) {
if(children.size() != 1)
throw new IllegalFilterException("Filter negation can be " +
"applied to one and only one child filter");
return FILTER_FACT.not(children.get(0));
} else {
throw new RuntimeException("Logical filter, but not And, Or, Not? " +
"This should not happen");
}
} catch (IllegalFilterException ife) {
LOGGER.warning("Unable to build filter: " + ife);
return null;
}
}
LOGGER.warning("unknown filter " + root);
return null;
}
/**
* Parses a null filter from a node known to be a null node.
*
* @param nullNode the PropertyIsNull node.
*
* @return a null filter of the expression contained in null node.
*
* @throws IllegalFilterException DOCUMENT ME!
*/
private static PropertyIsNull parseNullFilter(Node nullNode)
throws IllegalFilterException {
//NC - NameSpaceSupport
NamespaceSupport namespaces = getNameSpaces(nullNode);
final ExpressionDOMParser expressionDOMParser = new ExpressionDOMParser(FILTER_FACT);
expressionDOMParser.setNamespaceContext(namespaces);
LOGGER.finest("parsing null node: " + nullNode);
Node value = nullNode.getFirstChild();
while (value.getNodeType() != Node.ELEMENT_NODE) {
value = value.getNextSibling();
}
LOGGER.finest("add null value -> " + value + "<-");
Expression expr = expressionDOMParser.expression(value);
return FILTER_FACT.isNull( expr );
}
}