/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.visitor; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import org.geotools.filter.FilterAttributeExtractor; import org.opengis.filter.And; import org.opengis.filter.Filter; import org.opengis.filter.Id; import org.opengis.filter.Not; import org.opengis.filter.Or; import org.opengis.filter.expression.VolatileFunction; import org.opengis.filter.identity.FeatureId; import org.opengis.filter.identity.GmlObjectId; import org.opengis.filter.identity.Identifier; /** * Takes a filter and returns a simplified, equivalent one. At the moment the filter: * <ul> * <li>simplifies out {@link Filter#INCLUDE} and {@link Filter#EXCLUDE} in logical expressions</li> * <li>removes double logic negations</li> * <li>deal with FID filter validation removing invalid fids</li> * <li>optimize out all non volatile functions that do not happen to use attributes, * replacing them with literals</li> * </ul> * <p> * FID filter validation is meant to wipe out non valid feature ids from {@link Id} filters. This is * so in order to avoid sending feature ids down to DataStores that are not valid as per the * specific FeatureType fid structure. Since this is structure is usually DataStore specific, some * times being a strategy based on how the feature type primary key is generated, fid validation is * abstracted out to the {@link FIDValidator} interface so when a DataStore is about to send a query * down to the backend it van provide this visitor with a validator specific for the feature type * fid structure being queried. * </p> * <p> * By default all feature ids are valid. DataStores that want non valid fids to be wiped out should * set a {@link FIDValidator} through the {@link #setFIDValidator(FIDValidator)} method. * </p> * * @author Andrea Aime - OpenGeo * @author Gabriel Roldan (OpenGeo) * @since 2.5.x * @version $Id$ * * @source $URL$ */ public class SimplifyingFilterVisitor extends DuplicatingFilterVisitor { FilterAttributeExtractor attributeExtractor; /** * Defines a simple means of assessing whether a feature id in an {@link Id} filter is * structurally valid and hence can be send down to the backend with confidence it will not * cause trouble, the most common one being filtering by pk number even if the type name prefix * does not match. */ public static interface FIDValidator { public boolean isValid(String fid); } /** * A 'null-object' fid validator that assumes any feature id in an {@link Id} filter is valid */ public static final FIDValidator ANY_FID_VALID = new FIDValidator() { public boolean isValid(String fid) { return true; } }; /** * A FID validator that matches the fids with a given regular expression to determine the fid's * validity. * * @author Gabriel Roldan (OpenGeo) */ public static class RegExFIDValidator implements FIDValidator { private Pattern pattern; /** * @param regularExpression * a regular expression as used by the {@code java.util.regex} package */ public RegExFIDValidator(String regularExpression) { pattern = Pattern.compile(regularExpression); } public boolean isValid(String fid) { return pattern.matcher(fid).matches(); } } /** * A convenient fid validator for the common case of a feature id being a composition of a * {@code <typename>.<number>} */ public static class TypeNameDotNumberFidValidator extends RegExFIDValidator { /** * @param typeName * the typename that will be used for a regular expression match in the form of * {@code <typename>.<number>} */ public TypeNameDotNumberFidValidator(final String typeName) { super(typeName + "\\.\\d+"); } } private FIDValidator fidValidator = ANY_FID_VALID; public void setFIDValidator(FIDValidator validator) { this.fidValidator = validator == null ? ANY_FID_VALID : validator; } @Override public Object visit(And filter, Object extraData) { // scan, clone and simplify the children List<Filter> newChildren = new ArrayList<Filter>(filter.getChildren().size()); for (Filter child : filter.getChildren()) { Filter cloned = (Filter) child.accept(this, extraData); // if any of the child filters is exclude, // the whole chain of AND is equivalent to // EXCLUDE if(cloned == Filter.EXCLUDE) return Filter.EXCLUDE; // these can be skipped if(cloned == Filter.INCLUDE) continue; newChildren.add(cloned); } // we might end up with an empty list if(newChildren.size() == 0) return Filter.INCLUDE; // remove the logic we have only one filter if(newChildren.size() == 1) return newChildren.get(0); // else return the cloned and simplified up list return getFactory(extraData).and(newChildren); } @Override public Object visit(Or filter, Object extraData) { // scan, clone and simplify the children List<Filter> newChildren = new ArrayList<Filter>(filter.getChildren().size()); for (Filter child : filter.getChildren()) { Filter cloned = (Filter) child.accept(this, extraData); // if any of the child filters is include, // the whole chain of OR is equivalent to // INCLUDE if(cloned == Filter.INCLUDE) return Filter.INCLUDE; // these can be skipped if(cloned == Filter.EXCLUDE) continue; newChildren.add(cloned); } // we might end up with an empty list if(newChildren.size() == 0) return Filter.EXCLUDE; // remove the logic we have only one filter if(newChildren.size() == 1) return newChildren.get(0); // else return the cloned and simplified up list return getFactory(extraData).or(newChildren); } /** * Uses the current {@link FIDValidator} to wipe out illegal feature ids from the returned * filters. * * @return a filter containing only valid fids as per the current {@link FIDValidator}, may be * {@link Filter#EXCLUDE} if none matches or the filter is already empty */ @Override public Object visit(Id filter, Object extraData) { // if the set of ID is empty, it's actually equivalent to Filter.EXCLUDE if (filter.getIDs().size() == 0) { return Filter.EXCLUDE; } Set<Identifier> validFids = new HashSet<Identifier>(); for (Identifier id : filter.getIdentifiers()) { if(id instanceof FeatureId || id instanceof GmlObjectId){ // both FeatureId an GmlObjectId.getID() return String, but Identifier.getID() // returns Object. Yet, FeatureId and GmlObjectId are the only known subclasses of // Identifier that apply to Feature land if (fidValidator.isValid((String)id.getID())) { validFids.add(id); } } } Filter validIdFilter; if (validFids.size() == 0) { validIdFilter = Filter.EXCLUDE; } else { validIdFilter = getFactory(extraData).id(validFids); } return validIdFilter; } public Object visit(Not filter, Object extraData) { if (filter.getFilter() instanceof Not) { // simplify out double negation Not inner = (Not) filter.getFilter(); return inner.getFilter().accept(this, extraData); } else { return super.visit(filter, extraData); } } public Object visit(org.opengis.filter.expression.Function function, Object extraData) { // can't optimize out volatile functions if (function instanceof VolatileFunction) { return super.visit(function, extraData); } // stable function, is it using attributes? if (attributeExtractor == null) { attributeExtractor = new FilterAttributeExtractor(); } else { attributeExtractor.clear(); } function.accept(attributeExtractor, null); // if so we can replace it with a literal if (attributeExtractor.isConstantExpression()) { Object result = function.evaluate(null); return ff.literal(result); } else { return super.visit(function, extraData); } } }