/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2011, 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.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.geotools.data.FeatureSource;
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.feature.Types;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.expression.Expression;
import org.xml.sax.helpers.NamespaceSupport;
/**
* @author Gabriel Roldan (Axios Engineering)
* @author Rini Angreani (CSIRO Earth Science and Resource Engineering)
* @version $Id$
*
*
* @source $URL$
* @since 2.4
*/
public class FeatureTypeMapping {
/**
* It's bad to leave this with no parameters type, but we should allow for both complex and
* simple feature source as we could now take in a data access instead of a data store as the
* source data store
*/
private FeatureSource<?,?> source;
/**
* Encapsulates the name and type of target Features
*/
private AttributeDescriptor target;
/**
* Map of <source expression>/<target property>, where target property is an XPath expression
* addressing the mapped property of the target schema.
*/
List<AttributeMapping> attributeMappings;
NamespaceSupport namespaces;
/**
* A user-defined name for the mapping. This is optional, and used when there are more than one
* mapping for the same type. When defined, this overrides the targetElement as the identifier.
*/
private Name mappingName;
private Expression featureFidMapping;
/**
* No parameters constructor for use by the digester configuration engine as a JavaBean
*/
public FeatureTypeMapping() {
this(null, null, new LinkedList<AttributeMapping>(), new NamespaceSupport());
}
public FeatureTypeMapping(FeatureSource<?,?> source, AttributeDescriptor target,
List<AttributeMapping> mappings, NamespaceSupport namespaces) {
this.source = source;
this.target = target;
this.attributeMappings = new LinkedList<AttributeMapping>(mappings);
this.namespaces = namespaces;
// find id expression
for (AttributeMapping attMapping : attributeMappings) {
StepList targetXPath = attMapping.getTargetXPath();
if (targetXPath.size() > 1) {
continue;
}
Step step = (Step) targetXPath.get(0);
QName stepName = step.getName();
if (Types.equals(target.getName(), stepName)) {
featureFidMapping = attMapping.getIdentifierExpression();
break;
}
}
if (featureFidMapping == null) {
featureFidMapping = Expression.NIL;
}
}
public List<AttributeMapping> getAttributeMappings() {
return Collections.unmodifiableList(attributeMappings);
}
public Expression getFeatureIdExpression() {
return featureFidMapping;
}
/**
* Finds the attribute mappings for the given target location path.
* If the exactPath is not indexed, it will get all the matching mappings ignoring index.
* If it is indexed, it will get the one with matching index only.
*
* @param targetPath
* @return
*/
public List<AttributeMapping> getAttributeMappingsIgnoreIndex(final StepList targetPath) {
AttributeMapping attMapping;
List<AttributeMapping> mappings = new ArrayList<AttributeMapping>();
for (Iterator<AttributeMapping> it = attributeMappings.iterator(); it.hasNext();) {
attMapping = (AttributeMapping) it.next();
if (targetPath.equalsIgnoreIndex(attMapping.getTargetXPath())) {
mappings.add(attMapping);
}
}
return mappings;
}
/**
* Finds the attribute mappings for the given source expression.
*
* @param sourceExpression
* @return list of matching attribute mappings
*/
public List<AttributeMapping> getAttributeMappingsByExpression(final Expression sourceExpression) {
AttributeMapping attMapping;
List<AttributeMapping> mappings = new ArrayList<AttributeMapping>();
for (Iterator<AttributeMapping> it = attributeMappings.iterator(); it.hasNext();) {
attMapping = (AttributeMapping) it.next();
if (sourceExpression.equals(attMapping.getSourceExpression())) {
mappings.add(attMapping);
}
}
return mappings;
}
/**
* Finds the attribute mapping for the target expression <code>exactPath</code>
*
* @param exactPath
* the xpath expression on the target schema to find the mapping for
* @return the attribute mapping that match 1:1 with <code>exactPath</code> or <code>null</code>
* if
*/
public AttributeMapping getAttributeMapping(final StepList exactPath) {
AttributeMapping attMapping;
for (Iterator<AttributeMapping> it = attributeMappings.iterator(); it.hasNext();) {
attMapping = (AttributeMapping) it.next();
if (exactPath.equals(attMapping.getTargetXPath())) {
return attMapping;
}
}
return null;
}
public NamespaceSupport getNamespaces() {
return namespaces;
}
/**
* Has to be called after {@link #setTargetType(FeatureType)}
*
* @param elementName
* @param featureTypeName
*/
public void setTargetFeature(AttributeDescriptor feature) {
this.target = feature;
}
public AttributeDescriptor getTargetFeature() {
return this.target;
}
@SuppressWarnings("unchecked")
public FeatureSource getSource() {
return this.source;
}
public FeatureTypeMapping getUnderlyingComplexMapping() {
if (source instanceof MappingFeatureSource) {
return ((MappingFeatureSource) source).getMapping();
}
return null;
}
public void setName(Name name) {
this.mappingName = name;
}
public Name getMappingName() {
return mappingName;
}
/**
* Looks up for attribute mappings matching the xpath expression <code>propertyName</code>.
* <p>
* If any step in <code>propertyName</code> has index greater than 1, any mapping for the same
* property applies, regardless of the mapping. For example, if there are mappings for
* <code>gml:name[1]</code>, <code>gml:name[2]</code> and <code>gml:name[3]</code>, but
* propertyName is just <code>gml:name</code>, all three mappings apply.
* </p>
*
* @param mappings
* Feature type mapping to search for
* @param simplifiedSteps
* @return
*/
public List<Expression> findMappingsFor(final StepList propertyName) {
// collect all the mappings for the given property
List candidates;
// get all matching mappings if index is not specified, otherwise
// get the specified mapping
if (!propertyName.toString().contains("[")) {
candidates = getAttributeMappingsIgnoreIndex(propertyName);
} else {
candidates = new ArrayList<AttributeMapping>();
AttributeMapping mapping = getAttributeMapping(propertyName);
if (mapping != null) {
candidates.add(mapping);
}
}
List expressions = getExpressions(candidates);
// does the last step refer to a client property of the parent step?
// i.e. a client property maps to an xml attribute, and the step list
// could have been generated from an xpath of the form
// propA/propB@attName
if (candidates.size() == 0 && propertyName.size() > 1) {
XPath.Step clientPropertyStep = (Step) propertyName.get(propertyName.size() - 1);
Name clientPropertyName = Types.toTypeName(clientPropertyStep.getName());
XPath.StepList parentPath = new XPath.StepList(propertyName);
parentPath.remove(parentPath.size() - 1);
candidates = getAttributeMappingsIgnoreIndex(parentPath);
expressions = getClientPropertyExpressions(candidates, clientPropertyName);
if (expressions.isEmpty()) {
// this might be a wrapper mapping for another complex mapping
// look for the client properties there
FeatureTypeMapping inputMapping = getUnderlyingComplexMapping();
if (inputMapping != null) {
return getClientPropertyExpressions(inputMapping
.getAttributeMappingsIgnoreIndex(parentPath), clientPropertyName);
}
}
}
return expressions;
}
private static List getClientPropertyExpressions(final List attributeMappings,
final Name clientPropertyName) {
List clientPropertyExpressions = new ArrayList(attributeMappings.size());
AttributeMapping attMapping;
Map clientProperties;
Expression propertyExpression;
for (Iterator it = attributeMappings.iterator(); it.hasNext();) {
attMapping = (AttributeMapping) it.next();
clientProperties = attMapping.getClientProperties();
// NC -added
//FIXME - Id Checking should be done in a better way
if (clientPropertyName.getLocalPart().equals("id")) {
clientPropertyExpressions.add(attMapping.getIdentifierExpression());
} else // end NC - added
if (clientProperties.containsKey(clientPropertyName)) {
propertyExpression = (Expression) clientProperties.get(clientPropertyName);
clientPropertyExpressions.add(propertyExpression);
}
}
return clientPropertyExpressions;
}
/**
* Extracts the source Expressions from a list of {@link AttributeMapping}s
*
* @param attributeMappings
*/
private static List getExpressions(List attributeMappings) {
List expressions = new ArrayList(attributeMappings.size());
AttributeMapping mapping;
Expression sourceExpression;
for (Iterator it = attributeMappings.iterator(); it.hasNext();) {
mapping = (AttributeMapping) it.next();
sourceExpression = mapping.getSourceExpression();
expressions.add(sourceExpression);
}
return expressions;
}
}