/*
* 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.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.geotools.data.DataAccess;
import org.geotools.data.DataSourceException;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
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.AttributeBuilder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureImpl;
import org.geotools.feature.Types;
import org.geotools.filter.AttributeExpressionImpl;
import org.geotools.filter.FidFilterImpl;
import org.geotools.filter.FilterFactoryImpl;
import org.opengis.feature.Attribute;
import org.opengis.feature.ComplexAttribute;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
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.Attributes;
/**
* A Feature iterator that operates over the FeatureSource of a
* {@linkplain org.geotools.data.complex.FeatureTypeMapping} and produces Features of the output
* schema by applying the mapping rules to the Features of the source schema.
* <p>
* This iterator acts like a one-to-one mapping, producing a Feature of the target type for each
* feature of the source type.
*
* @author Gabriel Roldan, Axios Engineering
* @author Ben Caradoc-Davies, CSIRO Exploration and Mining
* @author Rini Angreani, Curtin University of Technology
* @author Russell Petty, GSV
* @version $Id$
* @source $URL:
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/app-schema/app-schema/src/main
* /java/org/geotools/data/complex/DataAccessMappingFeatureIterator.java $
* @since 2.4
*/
public class DataAccessMappingFeatureIterator extends AbstractMappingFeatureIterator {
/**
* Hold on to iterator to allow features to be streamed.
*/
protected Iterator<Feature> sourceFeatureIterator;
/**
* Reprojected CRS from the source simple features, or null
*/
private CoordinateReferenceSystem reprojection;
/**
* This is the feature that will be processed in next()
*/
private Feature curSrcFeature;
private FeatureSource<FeatureType, Feature> mappedSource;
private FeatureCollection<FeatureType, Feature> sourceFeatures;
/**
* Filter that has that attributes from nested features as parameter. To be applied per feature
* when computeNext() is called.
*/
private Filter nestedAttributeFilter;
/**
* True if the query is filtered by attributes other than id, this is to cater for denormalised
* view where there can be multiple rows for 1 complex feature with different values. If the
* filter is applied when the iterator is created, there's a chance some information is lost
* from rows from denormalised view not being incorporated into the target feature.
*/
private boolean isFiltered;
private ArrayList<String> filteredFeatures;
/**
*
* @param store
* @param mapping
* place holder for the target type, the surrogate FeatureSource and the mappings
* between them.
* @param query
* the query over the target feature type, that is to be unpacked to its equivalent
* over the surrogate feature type.
* @throws IOException
*/
public DataAccessMappingFeatureIterator(AppSchemaDataAccess store, FeatureTypeMapping mapping,
Query query) throws IOException {
super(store, mapping, query);
}
public boolean hasNext() {
if (isHasNextCalled()) {
return curSrcFeature != null;
}
boolean exists = false;
if ((curSrcFeature != null || sourceFeatureIterator != null)
&& featureCounter < maxFeatures) {
boolean hasNextSrc = (curSrcFeature != null || sourceFeatureIterator.hasNext());
while (hasNextSrc) {
if (this.curSrcFeature == null) {
this.curSrcFeature = sourceFeatureIterator.next();
}
if (isFiltered
&& filteredFeatures.contains(extractIdForFeature(this.curSrcFeature))) {
// get the next one as this row would've been already added to the target
// feature
// from setNextFilteredFeature
hasNextSrc = sourceFeatureIterator.hasNext();
curSrcFeature = null;
} else if (nestedAttributeFilter != null
&& !nestedAttributeFilter.evaluate(curSrcFeature)) {
// get the next one
hasNextSrc = sourceFeatureIterator.hasNext();
curSrcFeature = null;
} else {
// either there's no filter or that it passed the filter, so return it
exists = true;
hasNextSrc = false;
}
}
}
if (!exists) {
LOGGER.finest("no more features, produced " + featureCounter);
close();
curSrcFeature = null;
}
setHasNextCalled(true);
return exists;
}
protected Iterator<Feature> getSourceFeatureIterator() {
return sourceFeatureIterator;
}
protected boolean isSourceFeatureIteratorNull() {
return getSourceFeatureIterator() == null;
}
protected void initialiseSourceFeatures(FeatureTypeMapping mapping, Query inputQuery)
throws IOException {
DefaultQuery query = new DefaultQuery( inputQuery ); // make copy to allow modification
mappedSource = mapping.getSource();
this.reprojection = query.getCoordinateSystemReproject();
Filter filter = getAttributeFilter(query);
if (filter != null) {
isFiltered = true;
filteredFeatures = new ArrayList<String>();
}
try {
// we need to disable the max number of features retrieved so we can
// sort them manually just in case the data is denormalised
query.setMaxFeatures(Query.DEFAULT_MAX);
sourceFeatures = mappedSource.getFeatures(query);
this.sourceFeatureIterator = sourceFeatures.iterator();
} catch (IllegalArgumentException e) {
// HACK HACK HACK
// This is the only way we can check if the filter attributes are nested or not
// since there's no expression getter method for every implementation of filters.
// Remove the suspected filter from the query, and run it against each complex feature
// later
// because JDBCFeatureSource cannot handle nested queries, since it translates
// it to SQL first, then run the big query, but there's no way it can find the nested
// feature type name from there.
// Whereas with PropertyFeatureSource, it just runs the query first with no filters,
// then
// check every row against the filter, which is what we're trying to do here.
if (filter != null) {
query.setFilter(Filter.INCLUDE);
nestedAttributeFilter = filter;
sourceFeatures = mappedSource.getFeatures(query);
this.sourceFeatureIterator = sourceFeatures.iterator();
return;
}
throw e;
}
}
private Filter getAttributeFilter(Query query) {
Filter filter = query.getFilter();
if (filter != null && filter != Filter.INCLUDE && !(filter instanceof FidFilterImpl)) {
return filter;
}
return null;
}
protected boolean unprocessedFeatureExists() {
boolean exists = sourceFeatureIterator.hasNext();
if (exists && this.curSrcFeature == null) {
this.curSrcFeature = sourceFeatureIterator.next();
}
return exists;
}
protected String extractIdForFeature() {
return extractIdForFeature(curSrcFeature);
}
private String extractIdForFeature(Feature feature) {
ComplexAttribute sourceInstance = (ComplexAttribute) feature;
return (String) featureFidMapping.evaluate(sourceInstance, String.class);
}
protected String extractIdForAttribute(final Expression idExpression, Object sourceInstance) {
String value = (String) idExpression.evaluate(sourceInstance, String.class);
return value;
}
protected boolean isNextSourceFeatureNull() {
return curSrcFeature == null;
}
protected boolean sourceFeatureIteratorHasNext() {
return getSourceFeatureIterator().hasNext();
}
protected Object getValues(boolean isMultiValued, Expression expression,
Object sourceFeatureInput) {
if (isMultiValued && sourceFeatureInput instanceof FeatureImpl
&& expression instanceof AttributeExpressionImpl) {
// RA: Feature Chaining
// complex features can have multiple nodes of the same attribute.. and if they are used
// as input to an app-schema data access to be nested inside another feature type of a
// different XML type, it has to be mapped like this:
// <AttributeMapping>
// <targetAttribute>
// gsml:composition
// </targetAttribute>
// <sourceExpression>
// <inputAttribute>mo:composition</inputAttribute>
// <linkElement>gsml:CompositionPart</linkElement>
// <linkField>gml:name</linkField>
// </sourceExpression>
// <isMultiple>true</isMultiple>
// </AttributeMapping>
// As there can be multiple nodes of mo:composition in this case, we need to retrieve
// all of them
AttributeExpressionImpl attribExpression = ((AttributeExpressionImpl) expression);
String xpath = attribExpression.getPropertyName();
ComplexAttribute sourceFeature = (ComplexAttribute) sourceFeatureInput;
StepList xpathSteps = XPath.steps(sourceFeature.getDescriptor(), xpath, namespaces);
return getProperties(sourceFeature, xpathSteps);
}
return expression.evaluate(sourceFeatureInput);
}
/**
* Sets the values of grouping attributes.
*
* @param sourceFeature
* @param groupingMappings
* @param targetFeature
*
* @return Feature. Target feature sets with simple attributes
*/
protected void setAttributeValue(Attribute target, final Feature source,
final AttributeMapping attMapping) throws IOException {
final Expression sourceExpression = attMapping.getSourceExpression();
final AttributeType targetNodeType = attMapping.getTargetNodeInstance();
final StepList xpath = attMapping.getTargetXPath();
Map<Name, Expression> clientPropsMappings = attMapping.getClientProperties();
boolean isNestedFeature = attMapping.isNestedAttribute();
String id = null;
if (Expression.NIL != attMapping.getIdentifierExpression()) {
id = extractIdForAttribute(attMapping.getIdentifierExpression(), source);
}
if (attMapping.isNestedAttribute()) {
NestedAttributeMapping nestedMapping = ((NestedAttributeMapping) attMapping);
Object mappingName = nestedMapping.getNestedFeatureType(source);
if (mappingName != null) {
if (nestedMapping.isSameSource() && mappingName instanceof Name) {
// data type polymorphism mapping
setPolymorphicValues((Name) mappingName, target, id, nestedMapping, source,
xpath, clientPropsMappings);
return;
} else if (mappingName instanceof Hints) {
// referential polymorphism mapping
setPolymorphicReference((Hints) mappingName, clientPropsMappings, target,
xpath, targetNodeType);
return;
}
} else {
// polymorphism could result in null, to skip the attribute
return;
}
}
Object value = getValues(attMapping.isMultiValued(), sourceExpression, source);
boolean isHRefLink = isByReference(clientPropsMappings, isNestedFeature);
if (isNestedFeature) {
// get built feature based on link value
if (value instanceof Collection) {
ArrayList<Feature> nestedFeatures = new ArrayList<Feature>(((Collection) value)
.size());
for (Object val : (Collection) value) {
if (val instanceof Attribute) {
val = ((Attribute) val).getValue();
if (val instanceof Collection) {
val = ((Collection) val).iterator().next();
}
while (val instanceof Attribute) {
val = ((Attribute) val).getValue();
}
}
if (isHRefLink) {
// get the input features to avoid infinite loop in case the nested
// feature type also have a reference back to this type
// eg. gsml:GeologicUnit/gsml:occurence/gsml:MappedFeature
// and gsml:MappedFeature/gsml:specification/gsml:GeologicUnit
nestedFeatures.addAll(((NestedAttributeMapping) attMapping)
.getInputFeatures(val, reprojection, source));
} else {
nestedFeatures.addAll(((NestedAttributeMapping) attMapping).getFeatures(
val, reprojection, source));
}
}
value = nestedFeatures;
} else if (isHRefLink) {
// get the input features to avoid infinite loop in case the nested
// feature type also have a reference back to this type
// eg. gsml:GeologicUnit/gsml:occurence/gsml:MappedFeature
// and gsml:MappedFeature/gsml:specification/gsml:GeologicUnit
value = ((NestedAttributeMapping) attMapping).getInputFeatures(value, reprojection,
source);
} else {
value = ((NestedAttributeMapping) attMapping).getFeatures(value, reprojection,
source);
}
if (isHRefLink) {
// only need to set the href link value, not the nested feature properties
setXlinkReference(target, clientPropsMappings, value, xpath, targetNodeType);
return;
}
}
if (isNestedFeature) {
if (value == null) {
// polymorphism use case, if the value doesn't match anything, don't encode
return;
}
}
if (value instanceof Collection) {
// nested feature type could have multiple instances as the whole purpose
// of feature chaining is to cater for multi-valued properties
Map<Object, Object> userData;
Map<Name, Expression> valueProperties = new HashMap<Name, Expression>();
for (Object singleVal : (Collection) value) {
ArrayList valueList = new ArrayList();
// copy client properties from input features if they're complex features
// wrapped in app-schema data access
if (singleVal instanceof Attribute) {
// copy client properties from input features if they're complex features
// wrapped in app-schema data access
valueProperties = getClientProperties((Attribute) singleVal);
if (!valueProperties.isEmpty()) {
valueProperties.putAll(clientPropsMappings);
}
}
if (!isNestedFeature) {
if (singleVal instanceof Attribute) {
singleVal = ((Attribute) singleVal).getValue();
if (singleVal instanceof Collection) {
valueList.addAll((Collection) singleVal);
} else {
valueList.add(singleVal);
}
}
} else {
valueList.add(singleVal);
}
Attribute instance = xpathAttributeBuilder.set(target, xpath, valueList, id,
targetNodeType, false);
setClientProperties(instance, source, valueProperties);
}
} else {
if (value instanceof Attribute) {
// copy client properties from input features if they're complex features
// wrapped in app-schema data access
Map<Name, Expression> newClientProps = getClientProperties((Attribute) value);
if (!newClientProps.isEmpty()) {
newClientProps.putAll(clientPropsMappings);
clientPropsMappings = newClientProps;
}
value = ((Attribute) value).getValue();
}
Attribute instance = xpathAttributeBuilder.set(target, xpath, value, id,
targetNodeType, false);
setClientProperties(instance, source, clientPropsMappings);
}
}
/**
* Special handling for polymorphic mapping where the value of the attribute determines that
* this attribute should be a placeholder for an xlink:href.
*
* @param xlinkHrefHints
* the xlink:href hints holding the URI
* @param clientPropsMappings
* client properties
* @param target
* the complex feature being built
* @param xpath
* the xpath of attribute
* @param targetNodeType
* the type of the attribute to be cast to, if any
*/
private void setPolymorphicReference(Hints xlinkHrefHints,
Map<Name, Expression> clientPropsMappings, Attribute target, StepList xpath,
AttributeType targetNodeType) {
Object uri = xlinkHrefHints.get(ComplexFeatureConstants.STRING_KEY);
if (uri != null) {
Attribute instance = xpathAttributeBuilder.set(target, xpath, null, "", targetNodeType,
true);
FilterFactoryImpl ff = new FilterFactoryImpl();
Map<Name, Expression> newClientProps = new HashMap<Name, Expression>();
newClientProps.putAll(clientPropsMappings);
newClientProps.put(XLINK_HREF_NAME, ff.literal(uri));
setClientProperties(instance, null, newClientProps);
}
}
/**
* Special handling for polymorphic mapping. Works out the polymorphic type name by evaluating
* the function on the feature, then set the relevant sub-type values.
*
* @param target
* The target feature to be encoded
* @param id
* The target feature id
* @param nestedMapping
* The mapping that is polymorphic
* @param source
* The source simple feature
* @param xpath
* The xpath of polymorphic type
* @param clientPropsMappings
* Client properties
* @throws IOException
*/
private void setPolymorphicValues(Name mappingName, Attribute target, String id,
NestedAttributeMapping nestedMapping, Feature source, StepList xpath,
Map<Name, Expression> clientPropsMappings) throws IOException {
// process sub-type mapping
DataAccess<FeatureType, Feature> da = DataAccessRegistry.getDataAccess((Name) mappingName);
if (da instanceof AppSchemaDataAccess) {
// why wouldn't it be? check just to be safe
FeatureTypeMapping fTypeMapping = ((AppSchemaDataAccess) da)
.getMappingByName((Name) mappingName);
List<AttributeMapping> polymorphicMappings = fTypeMapping.getAttributeMappings();
AttributeDescriptor attDescriptor = fTypeMapping.getTargetFeature();
Name polymorphicTypeName = attDescriptor.getName();
StepList prefixedXpath = xpath.clone();
prefixedXpath.add(new Step(new QName(polymorphicTypeName.getNamespaceURI(),
polymorphicTypeName.getLocalPart(), this.namespaces
.getPrefix(polymorphicTypeName.getNamespaceURI())), 1));
Attribute instance = xpathAttributeBuilder.set(target, prefixedXpath, null, id,
attDescriptor.getType(), false, attDescriptor);
setClientProperties(instance, source, clientPropsMappings);
for (AttributeMapping mapping : polymorphicMappings) {
if (isTopLevelmapping(polymorphicTypeName, mapping.getTargetXPath())) {
// ignore the top level mapping for the Feature itself
// as it was already set
continue;
}
setAttributeValue(instance, source, mapping);
}
}
}
/**
* Set xlink:href client property for multi-valued chained features. This has to be specially
* handled because we don't want to encode the nested features attributes, since it's already an
* xLink. Also we need to eliminate duplicates.
*
* @param target
* The target attribute
* @param clientPropsMappings
* Client properties mappings
* @param value
* Nested features
* @param xpath
* Attribute xPath where the client properties are to be set
* @param targetNodeType
* Target node type
*/
protected void setXlinkReference(Attribute target, Map<Name, Expression> clientPropsMappings,
Object value, StepList xpath, AttributeType targetNodeType) {
// Make sure the same value isn't already set
// in case it comes from a denormalized view for many-to-many relationship.
// (1) Get the first existing value
Property existingAttribute = getProperty(target, xpath);
if (existingAttribute != null) {
Object existingValue = existingAttribute.getUserData().get(Attributes.class);
if (existingValue != null) {
assert existingValue instanceof HashMap;
existingValue = ((Map) existingValue).get(XLINK_HREF_NAME);
}
if (existingValue != null) {
Expression linkExpression = clientPropsMappings.get(XLINK_HREF_NAME);
for (Object singleVal : (Collection) value) {
assert singleVal instanceof Feature;
assert linkExpression != null;
Object hrefValue = linkExpression.evaluate(singleVal);
if (hrefValue != null && hrefValue.equals(existingValue)) {
// (2) if one of the new values matches the first existing value,
// that means this comes from a denormalized view,
// and this set has already been set
return;
}
}
}
}
for (Object singleVal : (Collection) value) {
assert singleVal instanceof Feature;
Attribute instance = xpathAttributeBuilder.set(target, xpath, null, null,
targetNodeType, true);
setClientProperties(instance, singleVal, clientPropsMappings);
}
}
protected void setClientProperties(final Attribute target, final Object source,
final Map<Name, Expression> clientProperties) {
if (clientProperties.size() == 0) {
return;
}
final Map<Name, Object> targetAttributes = new HashMap<Name, Object>();
for (Map.Entry<Name, Expression> entry : clientProperties.entrySet()) {
Name propName = entry.getKey();
Object propExpr = entry.getValue();
Object propValue;
if (propExpr instanceof Expression) {
propValue = getValue((Expression) propExpr, source);
} else {
propValue = propExpr;
}
targetAttributes.put(propName, propValue);
}
// FIXME should set a child Property.. but be careful for things that
// are smuggled in there internally and don't exist in the schema, like
// XSDTypeDefinition, CRS etc.
target.getUserData().put(Attributes.class, targetAttributes);
}
private Map getClientProperties(Attribute attribute) throws DataSourceException {
Map<Object, Object> userData = attribute.getUserData();
Map clientProperties = new HashMap<Name, Expression>();
if (userData != null && userData.containsKey(Attributes.class)) {
Map props = (Map) userData.get(Attributes.class);
if (!props.isEmpty()) {
clientProperties.putAll(props);
}
}
return clientProperties;
}
private void setNextFilteredFeature(String fId, ArrayList<Feature> features) throws IOException {
FeatureCollection<FeatureType, Feature> matchingFeatures;
FeatureId featureId = namespaceAwareFilterFactory.featureId(fId);
DefaultQuery query = new DefaultQuery();
if (reprojection != null) {
query.setCoordinateSystemReproject(reprojection);
}
if (featureFidMapping instanceof PropertyName
&& ((PropertyName) featureFidMapping).getPropertyName().equals("@id")) {
// no real feature id mapping,
// so trying to find it when the filter's evaluated will result in exception
Set<FeatureId> ids = new HashSet<FeatureId>();
ids.add(featureId);
query.setFilter(namespaceAwareFilterFactory.id(ids));
matchingFeatures = this.mappedSource.getFeatures(query);
} else {
// in case the expression is wrapped in a function, eg. strConcat
// that's why we don't always filter by id, but do a PropertyIsEqualTo
query.setFilter(namespaceAwareFilterFactory.equals(featureFidMapping,
namespaceAwareFilterFactory.literal(featureId)));
matchingFeatures = this.mappedSource.getFeatures(query);
}
Iterator<Feature> iterator = matchingFeatures.iterator();
while (iterator.hasNext()) {
features.add(iterator.next());
}
if (features.size() < 1) {
LOGGER.warning("This shouldn't have happened."
+ "There should be at least 1 features with id='" + fId + "'.");
}
filteredFeatures.add(fId);
matchingFeatures.close(iterator);
curSrcFeature = null;
}
private void setNextFeature(String fId, ArrayList<Feature> features) {
features.add(curSrcFeature);
curSrcFeature = null;
while (sourceFeatureIterator.hasNext()) {
Feature next = sourceFeatureIterator.next();
// RA: apply filters that involve attributes from nested features here.
// Because for JDBCFeatureSource, if the filter is included in the feature source query,
// it will try to translate the filter to SQL form, which will fail because the
// attributes may come from a different table (for nested feature type).
if (extractIdForFeature(next).equals(fId)) {
features.add(next);
} else {
curSrcFeature = next;
break;
}
}
}
protected Feature computeNext() throws IOException {
if (this.curSrcFeature == null) {
LOGGER.warning("hasNext not called before calling next() in the iterator!");
}
ArrayList<Feature> sources = new ArrayList<Feature>();
String id = extractIdForFeature(curSrcFeature);
if (isFiltered) {
setNextFilteredFeature(id, sources);
} else {
setNextFeature(id, sources);
}
if (sources.isEmpty()) {
LOGGER.warning("No features found in next()."
+ "This wouldn't have happenned if hasNext() was called beforehand.");
}
final AttributeDescriptor targetNode = mapping.getTargetFeature();
final Name targetNodeName = targetNode.getName();
final List<AttributeMapping> mappings = mapping.getAttributeMappings();
AttributeBuilder builder = new AttributeBuilder(attf);
builder.setDescriptor(targetNode);
Feature target = (Feature) builder.build(id);
for (AttributeMapping attMapping : mappings) {
if (isTopLevelmapping(targetNodeName, attMapping.getTargetXPath())) {
// ignore the top level mapping for the Feature itself
// as it was already set
continue;
}
// extract the values from multiple source features of the same id
// and set them to one built feature
if (attMapping.isMultiValued()) {
for (Feature source : sources) {
setAttributeValue(target, source, attMapping);
}
} else {
setAttributeValue(target, sources.get(0), attMapping);
}
}
if (target.getDefaultGeometryProperty() == null) {
setGeometry(target);
}
return target;
}
private boolean isTopLevelmapping(Name targetNodeName, StepList targetXPath) {
if (targetXPath.size() == 1) {
Step rootStep = targetXPath.get(0);
QName stepName = rootStep.getName();
if (Types.equals(targetNodeName, stepName)) {
return true;
}
}
return false;
}
protected Feature populateFeatureData(String id) throws IOException {
throw new UnsupportedOperationException("populateFeatureData should not be called!");
}
protected void closeSourceFeatures() {
if (sourceFeatures != null && getSourceFeatureIterator() != null) {
sourceFeatures.close(sourceFeatureIterator);
sourceFeatureIterator = null;
sourceFeatures = null;
filteredFeatures = null;
}
}
protected Object getValue(final Expression expression, Object sourceFeature) {
Object value;
value = expression.evaluate(sourceFeature);
if (value instanceof Attribute) {
value = ((Attribute) value).getValue();
}
return value;
}
/**
* Returns first matching attribute from provided root and xPath.
*
* @param root
* The root attribute to start searching from
* @param xpath
* The xPath matching the attribute
* @return The first matching attribute
*/
private Property getProperty(Attribute root, StepList xpath) {
Property property = root;
final StepList steps = new StepList(xpath);
Iterator<Step> stepsIterator = steps.iterator();
while (stepsIterator.hasNext()) {
assert property instanceof ComplexAttribute;
Step step = stepsIterator.next();
property = ((ComplexAttribute) property).getProperty(Types.toTypeName(step.getName()));
if (property == null) {
return null;
}
}
return property;
}
/**
* Return all matching properties from provided root attribute and xPath.
*
* @param root
* The root attribute to start searching from
* @param xpath
* The xPath matching the attribute
* @return The matching attributes collection
*/
private Collection<Property> getProperties(ComplexAttribute root, StepList xpath) {
final StepList steps = new StepList(xpath);
Iterator<Step> stepsIterator = steps.iterator();
Collection<Property> properties = null;
Step step = null;
if (stepsIterator.hasNext()) {
step = stepsIterator.next();
properties = ((ComplexAttribute) root).getProperties(Types.toTypeName(step.getName()));
}
while (stepsIterator.hasNext()) {
step = stepsIterator.next();
Collection<Property> nestedProperties = new ArrayList<Property>();
for (Property property : properties) {
assert property instanceof ComplexAttribute;
Collection<Property> tempProperties = ((ComplexAttribute) property)
.getProperties(Types.toTypeName(step.getName()));
if (!tempProperties.isEmpty()) {
nestedProperties.addAll(tempProperties);
}
}
properties.clear();
if (nestedProperties.isEmpty()) {
return properties;
}
properties.addAll(nestedProperties);
}
return properties;
}
/**
* Checks if client property has xlink:ref in it, if the attribute is for chained features.
*
* @param clientPropsMappings
* the client properties mappings
* @param isNested
* true if we're dealing with chained/nested features
* @return
*/
protected boolean isByReference(Map<Name, Expression> clientPropsMappings, boolean isNested) {
// only care for chained features
return isNested ? (clientPropsMappings.isEmpty() ? false : (clientPropsMappings
.get(XLINK_HREF_NAME) == null) ? false : true) : false;
}
}