/* * 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.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import org.geotools.data.complex.AppSchemaDataAccessRegistry; import org.geotools.data.complex.AttributeMapping; import org.geotools.data.complex.FeatureTypeMapping; import org.geotools.data.complex.NestedAttributeMapping; import org.geotools.data.complex.filter.XPath; import org.geotools.data.complex.filter.XPath.Step; import org.geotools.data.complex.filter.XPath.StepList; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.Hints; import org.geotools.feature.Types; import org.geotools.filter.AttributeExpressionImpl; import org.geotools.filter.expression.FeaturePropertyAccessorFactory; import org.opengis.feature.Attribute; import org.opengis.feature.Feature; import org.opengis.feature.type.Name; import org.opengis.filter.expression.Expression; import org.xml.sax.helpers.NamespaceSupport; /** * This class represents a list of expressions broken up from a single XPath expression that is * nested in more than one feature. The purpose is to allow filtering these attributes on the parent * feature. * * @author Rini Angreani, CSIRO Earth Science and Resource Engineering * * * @source $URL$ * http://svn.osgeo.org/geotools/trunk/modules/unsupported/app-schema/app-schema/src/main * /java/org/geotools/filter/NestedAttributeExpression.java $ */ public class NestedAttributeExpression extends AttributeExpressionImpl { private FeatureTypeMapping mappings; private NamespaceSupport namespaces; private StepList fullSteps; /** * First constructor * * @param xpath * Attribute XPath * @param expressions * List of broken up expressions */ public NestedAttributeExpression(String xpath, FeatureTypeMapping mappings) { super(xpath); this.mappings = mappings; this.namespaces = mappings.getNamespaces(); fullSteps = XPath.steps(mappings.getTargetFeature(), this.attPath.toString(), namespaces); } /** * see {@link org.geotools.filter.AttributeExpressionImpl#evaluate(Object)} */ @Override public Object evaluate(Object object) { if (object == null) { return null; } // only simple/complex features are supported if (!(object instanceof Feature)) { throw new UnsupportedOperationException( "Expecting a feature to apply filter, but found: " + object); } // if (object instanceof FeatureImpl) { // AttributeExpressionImpl exp = new AttributeExpressionImpl(attPath, new Hints( // FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, namespaces)); // List<Object> values = new ArrayList<Object>(1); // values.add(exp.evaluate(object)); // return values; // } List<Feature> roots = new ArrayList<Feature>(); roots.add((Feature) object); return getValues(0, 0, roots, mappings, null); } private boolean isLastStep(int index) { return index == fullSteps.size(); } private List<Object> getValues(int startIndex, int endIndex, List<Feature> roots, FeatureTypeMapping fMapping, AttributeMapping prevMapping) { List<Object> values = new ArrayList<Object>(); if (startIndex > fullSteps.size() || endIndex > fullSteps.size()) { return values; } while (startIndex <= endIndex) { List<AttributeMapping> attMappings = new ArrayList<AttributeMapping>(); StepList steps = null; Step lastStep = null; if (isLastStep(endIndex)) { // exhausted all paths return values; } while (attMappings.isEmpty() && endIndex < fullSteps.size()) { endIndex++; steps = fullSteps.subList(startIndex, endIndex); lastStep = steps.get(steps.size() - 1); if (isLastStep(endIndex) && lastStep.isXmlAttribute()) { if (steps.size() == 1) { if (prevMapping == null) { return values; } // use previous mapping to get client properties attMappings.add(prevMapping); } else { XPath.StepList parentPath = steps.clone(); parentPath.remove(parentPath.size() - 1); if (prevMapping != null && prevMapping.getTargetXPath().equals(parentPath)) { attMappings.add(prevMapping); } else { // find the attribute mapping that carries the // client properties attMappings = fMapping.getAttributeMappingsIgnoreIndex(parentPath); } } } else { attMappings = fMapping.getAttributeMappingsIgnoreIndex(steps); } if (steps.size() == 1) { if (Types.equals(fMapping.getTargetFeature().getName(), steps.get(0) .getName())) { // skip element name startIndex++; endIndex = startIndex; continue; } } } if (attMappings.isEmpty()) { // not found here, but might be found in other nodes if multi-valued // and polymorphic continue; } if (lastStep.isXmlAttribute()) { // a client properties for (AttributeMapping mapping : attMappings) { Expression exp = getClientPropertyExpression(lastStep, mapping, this.attPath); if (exp != null) { for (Feature root : roots) { Object value = getValue(exp, root); if (value != null) { values.add(value); } } } } } else { int nextIndex = startIndex + 1; for (AttributeMapping mapping : attMappings) { if (mapping instanceof NestedAttributeMapping) { // feature chaining mapping NestedAttributeMapping nestedMapping = ((NestedAttributeMapping) mapping); for (Feature root : roots) { try { fMapping = nestedMapping.getFeatureTypeMapping(root); } catch (IOException e) { fMapping = null; } if (fMapping != null && nestedMapping.isSameSource()) { // same root/database row, different mappings, used in // polymorphism List<Feature> nestedRoots = new ArrayList<Feature>(1); nestedRoots.add(root); List<Object> nestedValues = getValues(nextIndex, nextIndex, nestedRoots, fMapping, mapping); if (nestedValues != null) { values.addAll(nestedValues); } continue; } namespaces = nestedMapping.getNamespaces(); try { List<Feature> nestedFeatures = getNestedFeatures(root, nestedMapping, fMapping); if (nestedFeatures == null || nestedFeatures.isEmpty()) { continue; } if (lastStep.isIndexed()) { int index = lastStep.getIndex() - 1; Feature f = nestedFeatures.get(index); nestedFeatures.clear(); nestedFeatures.add(f); } if (fMapping != null) { List<Object> nestedValues = getValues(nextIndex, nextIndex, nestedFeatures, fMapping, mapping); if (nestedValues != null) { values.addAll(nestedValues); } } else if (!nestedFeatures.isEmpty()) { throw new UnsupportedOperationException( "FeatureTypeMapping not found for " + attPath + ". This shouldn't happen if it's set in AppSchemaDataAccess mapping file!"); } else { List<Object> nestedValues = getValues(nextIndex, nextIndex, nestedFeatures, fMapping, mapping); if (nestedValues != null) { values.addAll(nestedValues); } } } catch (IOException e) { throw new RuntimeException("Failed evaluating filter expression: '" + attPath + "'. Caused by: " + e.getMessage()); } catch (IllegalArgumentException e) { // might be a polymorphic case where it's looking for an attribute // from another type // that doesn't match this, but might match another database row // so just continue continue; } } } else { // normal attribute mapping if (endIndex == fullSteps.size()) { Expression exp = mapping.getSourceExpression(); for (Feature f : roots) { Object value = getValue(exp, f); if (value != null) { values.add(value); } } } else { // might be a client property? List<Object> nestedValues = getValues(startIndex, endIndex, roots, fMapping, mapping); if (nestedValues != null) { values.addAll(nestedValues); } } } } } return values; } return values; } /** * Get nested features from a feature chaining attribute mapping * * @param root * Root feature being evaluated * @param nestedMapping * Attribute mapping for nested features * @param fMapping * The root feature type mapping * @return list of nested features * @throws IOException */ private List<Feature> getNestedFeatures(Feature root, NestedAttributeMapping nestedMapping, FeatureTypeMapping fMapping) throws IOException { Object fTypeName = nestedMapping.getNestedFeatureType(root); if (fTypeName == null || !(fTypeName instanceof Name)) { return null; } boolean hasSimpleFeatures = AppSchemaDataAccessRegistry.hasName((Name) fTypeName); // get foreign key Object val = getValue(nestedMapping.getSourceExpression(), root); if (val == null) { return null; } if (hasSimpleFeatures) { // normal app-schema mapping return nestedMapping.getInputFeatures(val, fMapping); } else { // app-schema with a complex feature source return nestedMapping.getFeatures(val, null, root); } } /** * Extract leaf attribute value from an xpath in a feature. * * @param subList * xpath steps * @return leaf attribute value */ private Expression getComplexFeatureValue(StepList subList) { AttributeExpressionImpl att = new AttributeExpressionImpl(subList.toString(), new Hints( FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, namespaces)); return att; } private Object getValue(Expression expression, Feature feature) { Object value = expression.evaluate(feature); return extractAttributeValue(value); } /** * Extract the value that might be wrapped in an attribute. If the value is a collection, gets * the first value. * * @param value * @return */ private Object extractAttributeValue(Object value) { if (value == null) { return null; } while (value instanceof Attribute) { // get real value value = ((Attribute) value).getValue(); } if (value == null) { return null; } if (value instanceof Collection) { if (((Collection) value).isEmpty()) { return null; } value = ((Collection) value).iterator().next(); while (value instanceof Attribute) { value = ((Attribute) value).getValue(); } } return value; } /** * Find the expression of a client property if the step is one. * * @param nextRootStep * the step * @param fMapping * feature type mapping to get namespaces from * @param mapping * attribute mapping * @param targetXPath * the full target xpath * @return */ private Expression getClientPropertyExpression(Step nextRootStep, AttributeMapping mapping, String targetXPath) { if (nextRootStep.isXmlAttribute()) { Map<Name, Expression> clientProperties = mapping.getClientProperties(); QName lastStepName = nextRootStep.getName(); Name lastStep; if (lastStepName.getPrefix() != null && lastStepName.getPrefix().length() > 0 && (lastStepName.getNamespaceURI() == null || lastStepName.getNamespaceURI() .length() == 0)) { String prefix = lastStepName.getPrefix(); String uri = namespaces.getURI(prefix); lastStep = Types.typeName(uri, lastStepName.getLocalPart()); } else { lastStep = Types.toTypeName(lastStepName); } // NC -added //FIXME - Id Checking should be done in a better way if (lastStep.getLocalPart().equals("id")) { if (mapping.getIdentifierExpression() == Expression.NIL) { return CommonFactoryFinder.getFilterFactory(null).property("@id"); } else { return mapping.getIdentifierExpression(); } } else // end NC - added if (clientProperties.containsKey(lastStep)) { return (Expression) clientProperties.get(lastStep); } } return null; } }