/* * 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.data.complex; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.geotools.data.DefaultQuery; import org.geotools.data.FeatureSource; import org.geotools.data.complex.filter.XPath.StepList; import org.geotools.factory.Hints; import org.geotools.feature.FeatureCollection; import org.geotools.feature.Types; import org.geotools.filter.AttributeExpressionImpl; import org.geotools.filter.FilterFactoryImplNamespaceAware; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.identity.FeatureId; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.xml.sax.helpers.NamespaceSupport; /** * This class represents AttributeMapping for attributes that are nested inside another complex * attribute. The nested attributes would be features, or fake features, ie. complex attributes * which types are wrapped with NonFeatureTypeProxy instances. The purpose of this class is to store * nested built features so they can be retrieved when the parent feature is being built. Simple * features are also stored for caching if a filter involving these nested features is run. * * @author Rini Angreani, Curtin University of Technology * * @source $URL: * http://svn.osgeo.org/geotools/trunk/modules/unsupported/app-schema/app-schema/src/main * /java/org/geotools/data/complex/NestedAttributeMapping.java $ */ public class NestedAttributeMapping extends AttributeMapping { /** * Input feature source of the nested features */ private FeatureSource<FeatureType, Feature> source; /** * Mapped feature source of the nested features */ private FeatureSource<FeatureType, Feature> mappingSource; /** * Name of the nested features element */ private final Expression nestedFeatureType; /** * Target xpath that links to nested features */ private final StepList nestedTargetXPath; /** * Source expression of the nested features */ private Expression nestedSourceExpression; /** * Filter factory */ private FilterFactory filterFac; private NamespaceSupport namespaces; /** * Sole constructor * * @param idExpression * @param parentExpression * @param targetXPath * @param targetNodeInstance * @param isMultiValued * @param clientProperties * @param sourceElement * parent feature element type * @param sourcePath * XPath link to nested feature * @param parentSource * parent feature source * @throws IOException */ public NestedAttributeMapping(Expression idExpression, Expression parentExpression, StepList targetXPath, boolean isMultiValued, Map<Name, Expression> clientProperties, Expression sourceElement, StepList sourcePath, NamespaceSupport namespaces) throws IOException { super(idExpression, parentExpression, targetXPath, null, isMultiValued, clientProperties); this.nestedTargetXPath = sourcePath; this.nestedFeatureType = sourceElement; this.filterFac = new FilterFactoryImplNamespaceAware(namespaces); this.namespaces = namespaces; } @Override /* * @see org.geotools.data.complex.AttributeMapping#isNestedAttribute() */ public boolean isNestedAttribute() { return true; } /** * Get matching input features that are stored in this mapping using a supplied link value. * * @param foreignKeyValue * @param reprojection * Reprojected CRS or null * @return The matching input feature * @throws IOException * @throws IOException */ public List<Feature> getInputFeatures(Object foreignKeyValue, CoordinateReferenceSystem reprojection, Feature feature) throws IOException { if (isSameSource()) { // if linkField is null, this method shouldn't be called because the mapping // should use the same table, and handles it differently throw new UnsupportedOperationException( "Link field is missing from feature chaining mapping!"); } ArrayList<Feature> matchingFeatures = new ArrayList<Feature>(); if (source == null || !(nestedFeatureType instanceof AttributeExpressionImpl)) { // We can't initiate this in the constructor because the feature type mapping // might not be built yet. Object featureTypeName = getNestedFeatureType(feature); if (featureTypeName == null || !(featureTypeName instanceof Name)) { // this could be legitimate, for some null values polymorphism use case // or that it's set to be xlink:href return Collections.EMPTY_LIST; } FeatureTypeMapping featureTypeMapping = AppSchemaDataAccessRegistry .getMappingByName((Name) featureTypeName); assert featureTypeMapping != null; source = featureTypeMapping.getSource(); assert source != null; // find source expression on nested features side AttributeMapping mapping = featureTypeMapping .getAttributeMapping(this.nestedTargetXPath); assert mapping != null; nestedSourceExpression = mapping.getSourceExpression(); } assert nestedSourceExpression != null; Filter filter = filterFac.equals(this.nestedSourceExpression, filterFac .literal(foreignKeyValue)); // get all the nested features based on the link values DefaultQuery query = new DefaultQuery(); query.setCoordinateSystemReproject(reprojection); query.setFilter(filter); FeatureCollection<FeatureType, Feature> fCollection = source.getFeatures(query); Iterator<Feature> it = fCollection.iterator(); while (it.hasNext()) { Feature f = it.next(); Object value = this.nestedSourceExpression.evaluate(f); if (value != null && (value.equals(foreignKeyValue) || (foreignKeyValue instanceof FeatureId && value .equals(((FeatureId) foreignKeyValue).getID())))) { matchingFeatures.add(f); } } fCollection.close(it); return matchingFeatures; } /** * Get matching input features that are stored in this mapping using a supplied link value. * * @param foreignKeyValue * @param reprojection * Reprojected CRS or null * @return The matching input feature * @throws IOException * @throws IOException */ public List<Feature> getInputFeatures(Object foreignKeyValue, CoordinateReferenceSystem reprojection, FeatureTypeMapping fMapping) throws IOException { if (isSameSource()) { // if linkField is null, this method shouldn't be called because the mapping // should use the same table, and handles it differently throw new UnsupportedOperationException( "Link field is missing from feature chaining mapping!"); } ArrayList<Feature> matchingFeatures = new ArrayList<Feature>(); if (source == null || !(nestedFeatureType instanceof AttributeExpressionImpl)) { assert fMapping != null; source = fMapping.getSource(); assert source != null; // find source expression on nested features side AttributeMapping mapping = fMapping.getAttributeMapping(this.nestedTargetXPath); assert mapping != null; nestedSourceExpression = mapping.getSourceExpression(); } assert nestedSourceExpression != null; Filter filter = filterFac.equals(this.nestedSourceExpression, filterFac .literal(foreignKeyValue)); // get all the nested features based on the link values DefaultQuery query = new DefaultQuery(); query.setCoordinateSystemReproject(reprojection); query.setFilter(filter); FeatureCollection<FeatureType, Feature> fCollection = source.getFeatures(query); Iterator<Feature> it = fCollection.iterator(); while (it.hasNext()) { Feature f = it.next(); Object value = this.nestedSourceExpression.evaluate(f); if (value != null && (value.equals(foreignKeyValue) || (foreignKeyValue instanceof FeatureId && value .equals(((FeatureId) foreignKeyValue).getID())))) { matchingFeatures.add(f); } } fCollection.close(it); return matchingFeatures; } /** * Get the maching built features that are stored in this mapping using a supplied link value * * @param foreignKeyValue * @param reprojection * Reprojected CRS or null * @return The matching simple features * @throws IOException */ public List<Feature> getFeatures(Object foreignKeyValue, CoordinateReferenceSystem reprojection, Feature feature) throws IOException { if (isSameSource()) { // if linkField is null, this method shouldn't be called because the mapping // should use the same table, and handles it differently throw new UnsupportedOperationException( "Link field is missing from feature chaining mapping!"); } FeatureSource<FeatureType, Feature> fSource = getMappingSource(feature); if (fSource == null) { return null; } Filter filter; ArrayList<Feature> matchingFeatures = new ArrayList<Feature>(); PropertyName propertyName = filterFac.property(this.nestedTargetXPath.toString()); filter = filterFac.equals(propertyName, filterFac.literal(foreignKeyValue)); DefaultQuery query = new DefaultQuery(); query.setCoordinateSystemReproject(reprojection); query.setFilter(filter); // get all the mapped nested features based on the link values FeatureCollection<FeatureType, Feature> fCollection = fSource.getFeatures(query); Iterator<Feature> iterator = fCollection.iterator(); while (iterator.hasNext()) { matchingFeatures.add(iterator.next()); } fCollection.close(iterator); return matchingFeatures; } private FeatureSource<FeatureType, Feature> getMappingSource(Feature feature) throws IOException { if (mappingSource == null || !(nestedFeatureType instanceof AttributeExpressionImpl)) { // initiate if null, or evaluate a new one if the targetElement is a function // which value depends on the feature Object featureTypeName = getNestedFeatureType(feature); if (featureTypeName == null || !(featureTypeName instanceof Name)) { return null; } // this cannot be set in the constructor since it might not exist yet mappingSource = DataAccessRegistry.getFeatureSource((Name) featureTypeName); } return mappingSource; } /** * @return the nested feature type name */ public Object getNestedFeatureType(Feature feature) { Object fTypeValue; if (nestedFeatureType instanceof AttributeExpressionImpl) { fTypeValue = ((AttributeExpressionImpl) nestedFeatureType).getPropertyName(); } else { fTypeValue = nestedFeatureType.evaluate(feature); } if (fTypeValue == null) { // this could be legitimate, i.e. in polymorphism // to evaluate a function with a certain column value // if null, don't encode this element return null; } if (!(fTypeValue instanceof Hints)) { return Types.degloseName(fTypeValue.toString(), namespaces); } return fTypeValue; } public boolean isSameSource() { // if the linkField is null, we're meant to work out the nestedFeatureType from // the linkElement, which should contain a function. So the value could vary // feature per feature. But the linkElement would point to the same data source table // if the linkField is null. return this.nestedTargetXPath == null; } public boolean isConditional() { // true if the type is depending on a function value, i.e. could be a Function or filter return !(nestedFeatureType instanceof AttributeExpressionImpl); } public FeatureTypeMapping getFeatureTypeMapping(Feature feature) throws IOException { FeatureSource<FeatureType, Feature> fSource = getMappingSource(feature); if (fSource == null) { return null; } return (fSource instanceof MappingFeatureSource) ? ((MappingFeatureSource) fSource) .getMapping() : null; } public NamespaceSupport getNamespaces() { return namespaces; } }