/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011, 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.complex.filter;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import org.geotools.data.complex.FeatureTypeMapping;
import org.geotools.data.complex.NestedAttributeMapping;
import org.geotools.data.complex.config.AppSchemaDataAccessConfigurator;
import org.geotools.data.complex.filter.FeatureChainedAttributeVisitor.FeatureChainLink;
import org.geotools.data.complex.filter.FeatureChainedAttributeVisitor.FeatureChainedAttributeDescriptor;
import org.geotools.data.complex.filter.XPathUtil.StepList;
import org.geotools.filter.FilterCapabilities;
import org.geotools.filter.visitor.PostPreProcessFilterSplittingVisitor;
import org.geotools.util.logging.Logging;
import org.opengis.filter.BinaryComparisonOperator;
import org.opengis.filter.Id;
import org.opengis.filter.PropertyIsBetween;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.expression.Add;
import org.opengis.filter.expression.BinaryExpression;
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.BinarySpatialOperator;
import org.opengis.filter.temporal.BinaryTemporalOperator;
/**
* @author Niels Charlier (Curtin University of Technology)
*
* @source $URL$
*/
public class ComplexFilterSplitter extends PostPreProcessFilterSplittingVisitor {
private static final Logger LOGGER = Logging.getLogger(ComplexFilterSplitter.class);
private int nestedAttributes = 0;
public class CapabilitiesExpressionVisitor implements ExpressionVisitor {
protected boolean capable = true;
public boolean isCapable(){
return capable;
}
public Object visit(NilExpression expr, Object extraData) {
return null;
}
public Object visit(Add expr, Object extraData) {
visitMathExpression(expr);
return null;
}
public Object visit(Subtract expr, Object extraData) {
visitMathExpression(expr);
return null;
}
public Object visit(Divide expr, Object extraData) {
visitMathExpression(expr);
return null;
}
public Object visit(Multiply expr, Object extraData) {
visitMathExpression(expr);
return null;
}
public Object visit(Function expr, Object extraData) {
for (int i = 0; i < expr.getParameters().size(); i++) {
((Expression)expr.getParameters().get(i)).accept(this, null);
}
capable = capable && fcs.supports(expr.getClass());
return null;
}
public Object visit(Literal expr, Object extraData) {
return null;
}
public Object visit(PropertyName expr, Object extraData) {
return null;
}
private void visitMathExpression(BinaryExpression expression) {
expression.getExpression1().accept(this, null);
expression.getExpression2().accept(this, null);
capable = capable && fcs.supports(expression.getClass());
}
}
private FeatureTypeMapping mappings;
public ComplexFilterSplitter(FilterCapabilities fcs, FeatureTypeMapping mappings) {
super(fcs, null, null);
this.mappings = mappings;
}
public Object visit(Id filter, Object notUsed) {
CapabilitiesExpressionVisitor visitor = new CapabilitiesExpressionVisitor();
mappings.getFeatureIdExpression().accept(visitor, null);
if (visitor.isCapable()) {
super.visit(filter, notUsed);
} else {
postStack.push(filter);
}
return null;
}
@Override
public Object visit(Function expression, Object notUsed) {
nestedAttributes = 0;
int i = preStack.size();
Object data = super.visit(expression, notUsed);
// encoding of functions with nested attributes as arguments is not supported
if (nestedAttributes > 0 && preStack.size() == i + 1) {
Object o = preStack.pop();
postStack.push(o);
}
return data;
}
@Override
protected void visitBinarySpatialOperator(BinarySpatialOperator filter) {
nestedAttributes = 0;
int i = preStack.size();
super.visitBinarySpatialOperator(filter);
// encoding of binary spatial operators operating on nested attributes is not supported
if (nestedAttributes > 0 && preStack.size() == i + 1) {
Object o = preStack.pop();
postStack.push(o);
}
}
@Override
protected Object visit(BinaryTemporalOperator filter, Object data) {
nestedAttributes = 0;
int i = preStack.size();
Object ret = super.visit(filter, data);
// encoding of temporal operators involving nested attributes is not supported
if (nestedAttributes > 0 && preStack.size() == i + 1) {
Object o = preStack.pop();
postStack.push(o);
}
return ret;
}
@Override
protected void visitMathExpression(BinaryExpression expression) {
nestedAttributes = 0;
int i = preStack.size();
super.visitMathExpression(expression);
// encoding of math expressions involving nested attributes is not supported
if (nestedAttributes > 0 && preStack.size() == i + 1) {
Object o = preStack.pop();
postStack.push(o);
}
}
@Override
public Object visit(BBOX filter, Object notUsed) {
nestedAttributes = 0;
int i = preStack.size();
if (filter.getExpression1() instanceof PropertyName) {
PropertyName bboxProperty = (PropertyName)filter.getExpression1();
if (!bboxProperty.getPropertyName().isEmpty()) {
Object ret = this.visit(bboxProperty, notUsed);
if (preStack.size() == i+1) {
preStack.pop();
}
// encoding bbox on nested geometry is not supported
if (nestedAttributes > 0) {
postStack.push(filter);
return ret;
}
}
}
return super.visit(filter, notUsed);
}
@Override
protected void visitBinaryComparisonOperator(BinaryComparisonOperator filter) {
nestedAttributes = 0;
int i = preStack.size();
super.visitBinaryComparisonOperator(filter);
// encoding a comparison between multiple nested attributes is not supported
if (nestedAttributes > 1 && preStack.size() == i + 1) {
Object o = preStack.pop();
postStack.push(o);
}
}
@Override
public Object visit(PropertyIsBetween filter, Object extradata) {
nestedAttributes = 0;
int i = preStack.size();
Object ret = super.visit(filter, extradata);
// encoding a comparison between multiple nested attributes is not supported
if (nestedAttributes > 1 && preStack.size() == i + 1) {
Object o = preStack.pop();
postStack.push(o);
}
return ret;
}
@Override
public Object visit(PropertyIsLike filter, Object notUsed) {
nestedAttributes = 0;
int i = preStack.size();
Object ret = super.visit(filter, notUsed);
// encoding a comparison between multiple nested attributes is not supported
if (nestedAttributes > 1 && preStack.size() == i + 1) {
Object o = preStack.pop();
postStack.push(o);
}
return ret;
}
public Object visit(PropertyName expression, Object notUsed) {
// break into single steps
StepList exprSteps = XPath.steps(mappings.getTargetFeature(), expression.getPropertyName(), this.mappings.getNamespaces());
if (exprSteps.containsPredicate()) {
postStack.push(expression);
return null;
}
List<Expression> matchingMappings = mappings.findMappingsFor(exprSteps, false);
if (AppSchemaDataAccessConfigurator.shouldEncodeNestedFilters()) {
// check nested mappings
FeatureChainedAttributeVisitor nestedAttrExtractor = new FeatureChainedAttributeVisitor(
mappings);
nestedAttrExtractor.visit(expression, null);
List<FeatureChainedAttributeDescriptor> attributes = nestedAttrExtractor
.getFeatureChainedAttributes();
// encoding of filters on multiple nested attributes is not (yet) supported
if (attributes.size() == 1) {
FeatureChainedAttributeDescriptor nestedAttrDescr = attributes.get(0);
if (nestedAttrDescr.chainSize() > 1 && nestedAttrDescr.isJoiningEnabled()) {
nestedAttributes++;
FeatureTypeMapping featureMapping = nestedAttrDescr.getFeatureTypeOwningAttribute();
// add source expressions for target attribute
List<Expression> nestedMappings = featureMapping.findMappingsFor(
nestedAttrDescr.getAttributePath(), false);
Iterator<Expression> it = matchingMappings.iterator();
while (it.hasNext()) {
if (it.next() == null) {
// necessary to enable encoding of nested filters when joining simple content
it.remove();
}
}
matchingMappings.addAll(nestedMappings);
// add source expressions for mappings used in join conditions, as they too must be encoded
for (int i = nestedAttrDescr.chainSize() - 2; i > 0; i--) {
FeatureChainLink mappingStep = nestedAttrDescr.getLink(i);
if (mappingStep.hasNestedFeature()) {
FeatureChainLink parentStep = nestedAttrDescr.getLink(i);
NestedAttributeMapping nestedAttr = parentStep.getNestedFeatureAttribute();
FeatureTypeMapping nestedFeature = null;
try {
nestedFeature = nestedAttr.getFeatureTypeMapping(null);
} catch (IOException e) {
LOGGER.warning("Exception occurred processing nested filter, encoding"
+ "will be disabled: " + e.getMessage());
postStack.push(expression);
return null;
}
Expression nestedExpr = nestedAttr.getMapping(nestedFeature).getSourceExpression();
matchingMappings.add(nestedExpr);
}
}
}
}
}
if (matchingMappings.isEmpty()) {
postStack.push(expression);
return null;
} else {
for (Expression expr : matchingMappings) {
if (expr == null) {
// this is joining for simple content
// has to go to post stack because it comes from another table
postStack.push(expression);
return null;
} else {
CapabilitiesExpressionVisitor visitor = new CapabilitiesExpressionVisitor();
expr.accept(visitor, null);
if (!visitor.isCapable()) {
postStack.push(expression);
return null;
}
}
}
}
return super.visit(expression, notUsed);
}
}