/* * 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.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.Hints; import org.geotools.feature.FeatureImpl; 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$ */ public class NestedAttributeExpression extends AttributeExpressionImpl { private FeatureTypeMapping mappings; private NamespaceSupport namespaces; /** * 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(); } /** * 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); } Object value = null; FeatureTypeMapping fMapping = mappings; Feature root = (Feature) object; StepList fullSteps = XPath.steps(fMapping.getTargetFeature(), this.attPath.toString(), namespaces); AttributeMapping attMapping = null; AttributeMapping prevMapping = null; int startIndex = 0; int endIndex = startIndex; int lastIndex = fullSteps.size() - 1; while (startIndex <= fullSteps.size() - 1 && endIndex <= lastIndex) { boolean isElementName = false; StepList steps = null; prevMapping = attMapping; attMapping = null; while (!isElementName && attMapping == null && endIndex <= lastIndex) { endIndex++; steps = fullSteps.subList(startIndex, endIndex); if (steps.size() == 1) { Step step = steps.get(0); if (step.isXmlAttribute()) { // use previous mapping to get client properties attMapping = prevMapping; break; } if (Types.equals(fMapping.getTargetFeature().getName(), step.getName())) { // skip element name isElementName = true; break; } } attMapping = fMapping.getAttributeMapping(steps); if (attMapping == null) { if (prevMapping instanceof NestedAttributeMapping) { if (((NestedAttributeMapping) prevMapping).isConditional()) { // polymorphic type, that doesn't match the filter attributes return null; } } continue; } if (!(attMapping instanceof NestedAttributeMapping) && attMapping.getSourceExpression().equals(Expression.NIL)) { // might be an inline element name inside the feature type mapping attMapping = null; } } startIndex++; if (isElementName) { // get the next one continue; } if (attMapping == null) { throw new IllegalArgumentException("Can't find source expression for: " + attPath); } else if (steps.size() == 1 && steps.get(0).isXmlAttribute()) { // a client properties value = getClientPropertyExpression(steps.get(0), attMapping, this.attPath) .evaluate(root); } else if (attMapping instanceof NestedAttributeMapping) { // feature chaining mapping NestedAttributeMapping nestedMapping = ((NestedAttributeMapping) attMapping); try { fMapping = nestedMapping.getFeatureTypeMapping(root); } catch (IOException e) { fMapping = null; } if (fMapping != null && nestedMapping.isSameSource()) { // same root/database row, different mappings, used in polymorphism continue; } namespaces = nestedMapping.getNamespaces(); try { int index = steps.get(steps.size() - 1).getIndex() - 1; List<Feature> nestedFeatures = getNestedFeatures(root, nestedMapping, fMapping); if (nestedFeatures == null || nestedFeatures.isEmpty()) { return null; } root = nestedFeatures.get(index); } catch (IOException e) { throw new RuntimeException("Failed evaluating filter expression: '" + attPath + "'. Caused by: " + e.getMessage()); } if (fMapping == null) { if (root instanceof FeatureImpl) { // has a complex features backend, therefore doesn't necessarily have a // FeatureTypeMapping, because it's not an app-schema data access return getComplexFeatureValue(root, fullSteps.subList(startIndex, fullSteps .size())); } else { throw new UnsupportedOperationException( "FeatureTypeMapping not found for " + attPath + ". This shouldn't happen if it's set in AppSchemaDataAccess mapping file!"); } } } else { // normal attribute mapping Object val = attMapping.getSourceExpression().evaluate(root); if (val == null) { return null; } if (val instanceof Feature) { root = ((Feature) val); } else { value = extractAttributeValue(val); } } } return value; } /** * 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, null, 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 root * the feature * @param subList * xpath steps * @return leaf attribute value */ private Object getComplexFeatureValue(Feature root, StepList subList) { AttributeExpressionImpl att = new AttributeExpressionImpl(subList.toString(), new Hints( FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, namespaces)); return getValue(att, root); } 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); } if (clientProperties.containsKey(lastStep)) { return (Expression) clientProperties.get(lastStep); } else { throw new IllegalArgumentException("Client property mapping is missing for: " + targetXPath); } } return null; } }