package org.geoserver.wfs; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.geoserver.catalog.FeatureTypeInfo; import org.geotools.data.Join; import org.geotools.factory.CommonFactoryFinder; import org.geotools.filter.visitor.DuplicatingFilterVisitor; import org.geotools.filter.visitor.FilterVisitorSupport; 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.FilterFactory; import org.opengis.filter.FilterFactory2; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; import org.opengis.filter.Not; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsLike; import org.opengis.filter.PropertyIsNil; import org.opengis.filter.PropertyIsNull; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BinarySpatialOperator; import org.opengis.filter.temporal.BinaryTemporalOperator; public class JoinExtractingVisitor extends FilterVisitorSupport { static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null); FeatureTypeInfo primaryFeatureType; String primaryAlias; List<FeatureTypeInfo> featureTypes; List<String> aliases; List<Filter> joinFilters = new ArrayList<Filter>(); List<Filter> filters = new ArrayList<Filter>(); public JoinExtractingVisitor(List<FeatureTypeInfo> featureTypes, List<String> aliases) { this.primaryFeatureType = featureTypes.get(0); this.featureTypes = featureTypes.subList(1, featureTypes.size()); if (aliases == null || aliases.isEmpty()) { //assign prefixes aliases = new ArrayList<String>(); for (int i = 0; i < featureTypes.size(); i++) { aliases.add(String.valueOf((char)('a' + i))); } } this.primaryAlias = aliases.get(0); this.aliases = aliases.subList(1, aliases.size()); } public Object visitNullFilter(Object extraData) { return null; } public Object visit(ExcludeFilter filter, Object extraData) { return handleOther(filter, extraData); } public Object visit(IncludeFilter filter, Object extraData) { return handleOther(filter, extraData); } public Object visit(Id filter, Object extraData) { return handleOther(filter, extraData); } public Object visit(Not filter, Object extraData) { return handleOther(filter, extraData); } public Object visit(PropertyIsBetween filter, Object extraData) { return handleOther(filter, extraData); } public Object visit(PropertyIsLike filter, Object extraData) { return handleOther(filter, extraData); } public Object visit(PropertyIsNull filter, Object extraData) { return handleOther(filter, extraData); } public Object visit(PropertyIsNil filter, Object extraData) { return handleOther(filter, extraData); } @Override protected Object visit(BinaryLogicOperator op, Object extraData) { for (Filter f : op.getChildren()) { f.accept(this, extraData); } return extraData; } @Override protected Object visit(BinaryComparisonOperator op, Object extraData) { return handle(op, op.getExpression1(), op.getExpression2(), extraData); } @Override protected Object visit(BinarySpatialOperator op, Object extraData) { return handle(op, op.getExpression1(), op.getExpression2(), extraData); } @Override protected Object visit(BinaryTemporalOperator op, Object extraData) { return handle(op, op.getExpression1(), op.getExpression2(), extraData); } // void handle(Filter f) { // if (f instanceof And) { // for (Filter g : ((And)f).getChildren()) { // handle(g); // } // } // else { // //check if this is a join filter // boolean join = false; // if (f instanceof BinaryComparisonOperator) { // join = isJoinFilter(((BinaryComparisonOperator)f).getExpression1(), // ((BinaryComparisonOperator)f).getExpression2()); // } // else if (f instanceof BinarySpatialOperator) { // join = isJoinFilter(((BinarySpatialOperator)f).getExpression1(), // ((BinarySpatialOperator)f).getExpression2()); // } // else if (f instanceof BinaryTemporalOperator) { // join = isJoinFilter(((BinaryTemporalOperator)f).getExpression1(), // ((BinaryTemporalOperator)f).getExpression2()); // } // // if (join) { // joinFilters.add(f); // } // else { // filters.add(f); // } // } // } Object handle(Filter f, Expression e1, Expression e2, Object extraData) { if (isJoinFilter(e1, e2)) { joinFilters.add(f); } else { handleOther(f, extraData); } return null; } Object handleOther(Filter f, Object extraData) { filters.add(f); return null; } boolean isJoinFilter(Expression e1, Expression e2) { return e1 instanceof PropertyName && e2 instanceof PropertyName; } public List<Join> getJoins() { List<Join> joins = new ArrayList(); //unroll the contents of the join filters and rewrite them and and assign to correct //feature type List<Filter> joinFilters = rewriteAndSort(unroll(this.joinFilters), true); //do same for other secondary filters List<Filter> otherFilters = rewriteAndSort(unroll(this.filters), false); for (int i = 0; i < featureTypes.size(); i++) { Join join = new Join(featureTypes.get(i).getNativeName(), joinFilters.get(i+1)); if (aliases != null) { join.setAlias(aliases.get(i)); } if (otherFilters.get(i+1) != null) { join.setFilter(otherFilters.get(i+1)); } joins.add(join); } return joins; } public Filter getPrimaryFilter() { List<Filter> otherFilters = rewriteAndSort(unroll(this.filters), false); return otherFilters.get(0); } List<Filter> unroll(List<Filter> filters) { JoinFilterUnroller unroller = new JoinFilterUnroller(); for (Filter f : filters) { f.accept(unroller, null); } return unroller.getFilters(); } List<Filter> rewriteAndSort(List<Filter> filters, boolean prefix) { Filter[] sorted = new Filter[featureTypes.size()+1]; O: for (Filter f : filters) { PropertyName[] names = names(f); //find the secondary feature type referenced for (int i = 0; i < featureTypes.size(); i++) { PropertyName[] rewritten = rewrite(i, names[0], names[1], prefix); if (rewritten != null) { Filter newFilter = rewrite(f, names, rewritten); updateFilter(sorted, i+1, newFilter); continue O; } } //this could be a filter against the primary PropertyName[] rewritten = rewrite(primaryFeatureType, primaryAlias,names[0],names[1],prefix); if (rewritten != null) { Filter newFilter = rewrite(f, names, rewritten); updateFilter(sorted, 0, newFilter); } else { throw new IllegalStateException("Join filter inconsistent with regard to feature types"); } } return Arrays.asList(sorted); } void updateFilter(Filter[] filters, int i, Filter filter) { if (filters[i] == null) { filters[i] = filter; } else { filters[i] = ff.and(filters[i], filter); } } PropertyName[] rewrite(int i, PropertyName n1, PropertyName n2, boolean prefix) { FeatureTypeInfo featureType = featureTypes.get(i); String alias = aliases != null ? aliases.get(i) : null; return rewrite(featureType, alias, n1, n2, prefix); } PropertyName[] rewrite(FeatureTypeInfo featureType, String alias, PropertyName n1, PropertyName n2, boolean prefix) { if (n1 != null) { PropertyName n = rewrite(featureType, alias, n1, prefix); if (n != null) { return new PropertyName[]{n, n2 != null ? rewrite(primaryFeatureType, primaryAlias, n2, prefix) : null}; } } if (n2 != null) { PropertyName n = rewrite(featureType, alias, n2, prefix); if (n != null) { return new PropertyName[]{n1 != null ? rewrite(primaryFeatureType, primaryAlias, n1, prefix) : null, n}; } } return null; } PropertyName rewrite(FeatureTypeInfo featureType, String alias, PropertyName name, boolean prefix) { String n = name.getPropertyName(); if (n.startsWith(featureType.getPrefixedName()+"/")) { n = n.substring((featureType.getPrefixedName()+"/").length()); } else if (n.startsWith(featureType.getName()+"/")) { n = n.substring((featureType.getName()+"/").length()); } else if (alias != null && n.startsWith(alias+"/")) { n = n.substring((alias+"/").length()); } else { n = null; } if (n != null) { if (prefix) { n = (alias != null ? alias : "") + "."+ n; } return ff.property(n, name.getNamespaceContext()); } return null; } Filter rewrite(Filter f, PropertyName[] names, PropertyName[] rewritten) { //create a new filter with the rewritten property names Filter newFilter = null; if (names[0] != null) { newFilter = (Filter)f.accept(new PropertyNameRewriter(names[0],rewritten[0]), null); } if (names[1] != null) { newFilter = (Filter) (newFilter != null ? newFilter : f).accept( new PropertyNameRewriter(names[1],rewritten[1]), null); } return newFilter; } PropertyName[] names(Filter f) { //TODO: use a filter visitor Expression e1 = null; Expression e2 = null; if (f instanceof BinaryComparisonOperator) { e1 = ((BinaryComparisonOperator) f).getExpression1(); e2 = ((BinaryComparisonOperator) f).getExpression2(); } else if (f instanceof BinarySpatialOperator) { e1 = ((BinarySpatialOperator) f).getExpression1(); e2 = ((BinarySpatialOperator) f).getExpression2(); } else if (f instanceof BinaryTemporalOperator) { e1 = ((BinaryTemporalOperator) f).getExpression1(); e2 = ((BinaryTemporalOperator) f).getExpression2(); } else if (f instanceof PropertyIsNil){ e1 = ((PropertyIsNil) f).getExpression(); } else if (f instanceof PropertyIsNull) { e1 = ((PropertyIsNull) f).getExpression(); } else if (f instanceof PropertyIsLike) { e1 = ((PropertyIsLike) f).getExpression(); } else if (f instanceof PropertyIsBetween) { e1 = ((PropertyIsBetween) f).getExpression(); } else { throw new IllegalStateException(); } return new PropertyName[]{e1 instanceof PropertyName ? (PropertyName) e1 : null, e2 instanceof PropertyName ? (PropertyName) e2 : null}; } class JoinFilterUnroller extends FilterVisitorSupport { List<Filter> unrolled = new ArrayList(); public Object visitNullFilter(Object extraData) { return null; } public Object visit(ExcludeFilter filter, Object extraData) { return handle(filter, extraData); } public Object visit(IncludeFilter filter, Object extraData) { return handle(filter, extraData); } public Object visit(Id filter, Object extraData) { return handle(filter, extraData); } public Object visit(PropertyIsBetween filter, Object extraData) { return handle(filter, extraData); } public Object visit(PropertyIsLike filter, Object extraData) { return handle(filter, extraData); } public Object visit(PropertyIsNil filter, Object extraData) { return handle(filter, extraData); } public Object visit(PropertyIsNull filter, Object extraData) { return handle(filter, extraData); } public Object visit(Not filter, Object extraData) { return null; } @Override protected Object visit(BinaryLogicOperator op, Object extraData) { if (op instanceof And) { for (Filter f : op.getChildren()) { f.accept(this, extraData); } } return null; } @Override protected Object visit(BinaryComparisonOperator op, Object extraData) { return handle(op, extraData); } @Override protected Object visit(BinarySpatialOperator op, Object extraData) { return handle(op, extraData); } @Override protected Object visit(BinaryTemporalOperator op, Object extraData) { return handle(op, extraData); } protected Object handle(Filter filter, Object extraData) { unrolled.add(filter); return extraData; } public List<Filter> getFilters() { return unrolled; } } class PropertyNameRewriter extends DuplicatingFilterVisitor { PropertyName from, to; PropertyNameRewriter(PropertyName from, PropertyName to) { this.from = from; this.to = to; } @Override public Object visit(PropertyName expression, Object extraData) { if (expression.equals(from)) { return to; } return super.visit(expression, extraData); } } }