/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2009-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.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.geotools.data.FeatureSource; import org.geotools.data.complex.PathAttributeList.Pair; import org.geotools.data.complex.filter.XPath.Step; import org.geotools.data.complex.filter.XPath.StepList; import org.geotools.data.complex.xml.XmlFeatureSource; import org.geotools.factory.CommonFactoryFinder; import org.geotools.filter.LiteralExpressionImpl; import org.opengis.feature.Attribute; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.Name; import org.opengis.filter.FilterFactory; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.PropertyName; import org.xml.sax.helpers.NamespaceSupport; /** * @author Russell Petty (GeoScience Victoria) * @author Rini Angreani (CSIRO Earth Science and Resource Engineering) * @version $Id$ * * * @source $URL$ * http://svn.osgeo.org/geotools/trunk/modules/unsupported/app-schema/app-schema/src/main * /java/org/geotools/data/complex/FeatureTypeMapping.java $ */ public class XmlFeatureTypeMapping extends FeatureTypeMapping { /** * Constants for manipulating XPath Expressions */ private static final String XPATH_SEPARATOR = "/"; private static final String XPATH_PROPERTY_SEPARATOR = "/@"; private static final String XPATH_LEFT_INDEX_BRACKET = "["; private static final String XPATH_RIGHT_INDEX_BRACKET = "]"; private static final String AS_XPATH_FUNCTION = "asXpath"; /** * Output xpath to input xpath map */ private Map<String, Expression> mapping = new HashMap<String, Expression>(); /** * List of labelled AttributeMappings */ private AttributeCreateOrderList attOrderedTypeList = null; /** * Label to AttributeMapping map */ private Map<String, AttributeMapping> indexAttributeList; AttributeMapping rootAttribute; private int index = 1; private FilterFactory ff = CommonFactoryFinder.getFilterFactory(null); /** * Attributes that don't have their own label, therefore are children of another node. */ List<AttributeMapping> setterAttributes = new ArrayList<AttributeMapping>(); PathAttributeList elements; protected String itemXpath; /** * No parameters constructor for use by the digester configuration engine as a JavaBean */ public XmlFeatureTypeMapping() { super(null, null, new LinkedList<AttributeMapping>(), new NamespaceSupport()); } public XmlFeatureTypeMapping(FeatureSource source, AttributeDescriptor target, List<AttributeMapping> mappings, NamespaceSupport namespaces, String itemXpath) { super(source, target, mappings, namespaces); this.itemXpath = itemXpath; elements = new PathAttributeList(); ((XmlFeatureSource) source).setItemXpath(itemXpath); try { populateFeatureData(); } catch (IOException ex) { throw new RuntimeException("Error occured when trying to create attribute mappings", ex); } } public List<Expression> getExpressionsIgnoreIndex(final StepList targetPath) { List<Expression> mappings = new ArrayList<Expression>(); String path = targetPath.toString(); Collection<String> c = mapping.keySet(); // obtain an Iterator for Collection Iterator<String> itr = c.iterator(); // iterate through HashMap values iterator while (itr.hasNext()) { String listPath = itr.next(); String unindexedListPath = removeIndexFromPath(listPath); if (path.equals(unindexedListPath)) { mappings.add(mapping.get(listPath)); } } if (mappings.isEmpty()) { // look in the setter attributes Iterator<AttributeMapping> leafAtts = setterAttributes.iterator(); while (leafAtts.hasNext()) { AttributeMapping att = leafAtts.next(); String listPath = att.getTargetXPath().toString(); String unindexedListPath = removeIndexFromPath(listPath); if (path.equals(unindexedListPath)) { mappings.add(att.getSourceExpression()); } } } return mappings; } private String removeIndexFromPath(String inputPath) { String tempPath = inputPath; while (tempPath.contains(XPATH_LEFT_INDEX_BRACKET)) { int leftIndex = tempPath.indexOf(XPATH_LEFT_INDEX_BRACKET); int rightIndex = tempPath.indexOf(XPATH_RIGHT_INDEX_BRACKET, leftIndex); String leftTempPath = tempPath.substring(0, leftIndex); String rightTempPath = tempPath.substring(rightIndex + 1); tempPath = leftTempPath + rightTempPath; } return tempPath; } /** * 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 = Collections.emptyList(); for (Iterator<AttributeMapping> it = attributeMappings.iterator(); it.hasNext();) { attMapping = (AttributeMapping) it.next(); if (sourceExpression.equals(attMapping.getSourceExpression())) { if (mappings.size() == 0) { mappings = new ArrayList<AttributeMapping>(2); } mappings.add(attMapping); } } return mappings; } /** * Finds an attribute mapping by label. * * @param label * The attribute mapping label. * @return Attribute mapping that matches the label, or null. */ public AttributeMapping getAttributeMappingByLabel(String label) { AttributeMapping attMapping; for (Iterator<AttributeMapping> it = attributeMappings.iterator(); it.hasNext();) { attMapping = (AttributeMapping) it.next(); if (label.equals(attMapping.getLabel())) { return attMapping; } } return null; } /** * 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 getStringMapping(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 void populateFeatureData() throws IOException { List<AttributeMapping> attMap = getAttributeMappings(); if (attOrderedTypeList == null) { initialiseAttributeLists(attMap); } // create required elements String xpath = rootAttribute.getInstanceXpath() == null ? itemXpath : itemXpath + XPATH_SEPARATOR + rootAttribute.getInstanceXpath(); elements.put(rootAttribute.getLabel(), xpath, null); Expression idExpression = rootAttribute.getIdentifierExpression(); if (!idExpression.equals(Expression.NIL)) { Expression id; if (!(idExpression instanceof Function) && rootAttribute.getInstanceXpath() != null) { id = ff.property(rootAttribute.getInstanceXpath() + XPATH_SEPARATOR + idExpression); } else { id = idExpression; } mapping.put("@gml:id", id); } // iterator returns the attribute mappings starting from the root of the tree. // parents are always returned before children elements. Iterator<AttributeMapping> it = attOrderedTypeList.iterator(); addComplexAttributes(elements, it); addSetterAttributes(elements); index++; removeAllRelativePaths(); } private void addComplexAttributes(PathAttributeList elements, Iterator<AttributeMapping> it) { while (it.hasNext()) { AttributeMapping attMapping = it.next(); final Expression sourceExpression = attMapping.getIdentifierExpression(); List<Pair> ls = elements.get(attMapping.getParentLabel()); if (ls != null) { for (int i = 0; i < ls.size(); i++) { Pair parentAttribute = ls.get(i); String instancePath = attMapping.getInstanceXpath(); int count = 1; String countXpath = parentAttribute.getXpath(); // if instance path not set, then element exists, with one instance if (instancePath != null) { countXpath = countXpath + XPATH_SEPARATOR + instancePath; } for (int j = 0; j < count; j++) { final String bracketIndex = ""; String xpath; if (instancePath == null) { xpath = parentAttribute.getXpath() + XPATH_SEPARATOR + sourceExpression.toString(); } else { xpath = parentAttribute.getXpath() + XPATH_SEPARATOR + instancePath + bracketIndex + XPATH_SEPARATOR + sourceExpression.toString(); } String label = getFullQueryPath(attMapping); mapping.put(label + XPATH_PROPERTY_SEPARATOR + "gml:id", ff .property(xpath)); StepList sl = attMapping.getTargetXPath(); setPathIndex(j, sl); Attribute subFeature = null; elements.put(attMapping.getLabel(), countXpath + bracketIndex, subFeature); } } } } } private void addSetterAttributes(PathAttributeList elements) { for (AttributeMapping attMapping : setterAttributes) { List<Pair> ls = elements.get(attMapping.getParentLabel()); if (ls != null) { for (int i = 0; i < ls.size(); i++) { Pair parentPair = ls.get(i); final Expression sourceExpression = attMapping.getSourceExpression(); String prefix = parentPair.getXpath(); Expression usedXpath = getValue(prefix, sourceExpression, attMapping); String label = getFullQueryPath(attMapping); mapping.put(label, usedXpath); if (usedXpath instanceof PropertyName) { // if current source expression is an inputAttribute // work out the client properties value from the inputAttribute // xpath, which would already have the parent prefix in it addClientProperties(attMapping, usedXpath.toString(), label); } else { // if source expression is a constant or function // just add the parent prefix to work out client properties values addClientProperties(attMapping, prefix, label); } } } } } private void addClientProperties(AttributeMapping attMapping, String prefix, String label) { Map<Name, Expression> clientProperties = attMapping.getClientProperties(); if (clientProperties.size() != 0) { for (Map.Entry<Name, Expression> entry : clientProperties.entrySet()) { Name propName = entry.getKey(); Expression propExpr = entry.getValue(); Expression xPath = getValue(prefix, propExpr, attMapping); mapping.put(label + XPATH_PROPERTY_SEPARATOR + getPropertyNameXpath(propName), xPath); } } } private String getPropertyNameXpath(Name propName) { String xpath; String namespaceUri = propName.getNamespaceURI(); if (namespaceUri != null) { String namespace = namespaces.getPrefix(namespaceUri); xpath = namespace + propName.getSeparator() + propName.getLocalPart(); } else { xpath = propName.getLocalPart(); } return xpath; } private void setPathIndex(int j, StepList sl) { if (j > 0) { Step st = sl.get(sl.size() - 1); Step st2 = new Step(st.getName(), j + 1, st.isXmlAttribute()); sl.remove(sl.size() - 1); sl.add(st2); } } protected Expression getValue(String xpathPrefix, Expression node, AttributeMapping mapping) { Expression value = null; if (node instanceof Function) { // function Function func = (Function) node; Expression exp = getAsXpathExpression(func, mapping); if (exp != null) { // this function must be an AsXpath // return the extracted expression value = exp; } else { // not an asXpath, but might have it in the param somewhere // the param would already be turned into the extracted expression // return the function value = func; } } else if (node instanceof LiteralExpressionImpl) { // constant value = node; } else { // input attribute String expressionValue = node.toString(); if (xpathPrefix.length() > 0) { value = ff.property(xpathPrefix + XPATH_SEPARATOR + expressionValue); } else { value = node; } } return value; } /** * Find asXpath in a function, which might be the function itself or a parameter of the * function, and extract the xpath value including itemXpath and instancePath prefixes. * * @param func * The function * @param mapping * The attribute mapping * @return xpath expression or null */ private Expression getAsXpathExpression(Function func, AttributeMapping mapping) { if (func.getName().equals(AS_XPATH_FUNCTION)) { // get original filter xpath Expression queryXpath = func.getParameters().get(0); // get the attribute mapping full xpath and append to the function param String prefix; String parentLabel = mapping.getParentLabel(); if (parentLabel == null) { // must be root prefix = elements.getPath(rootAttribute.getLabel()); } else { prefix = elements.getPath(parentLabel); String instancePath = mapping.getInstanceXpath(); if (instancePath != null) { prefix += XPATH_SEPARATOR + instancePath; } } Expression fullXpath = ff.property( prefix + XPATH_SEPARATOR + queryXpath); return fullXpath; } else { List<Expression> params = func.getParameters(); for (int i = 0; i < params.size(); i++) { Expression param = params.get(i); if (param instanceof Function) { Expression expr = getAsXpathExpression((Function) param, mapping); if (expr != null) { // found asXpath and returned an expression // set as the new parameter func.getParameters().remove(i); func.getParameters().add(expr); } } } return null; } } private void initialiseAttributeLists(List<AttributeMapping> mappings) { for (AttributeMapping attMapping : mappings) { if (attMapping.getLabel() != null && attMapping.getParentLabel() == null && attMapping.getTargetNodeInstance() == null) { rootAttribute = attMapping; break; } } attOrderedTypeList = new AttributeCreateOrderList(rootAttribute.getLabel()); indexAttributeList = new HashMap<String, AttributeMapping>(); indexAttributeList.put(rootAttribute.getLabel(), rootAttribute); for (AttributeMapping attMapping : mappings) { if (attMapping.getLabel() == null) { setterAttributes.add(attMapping); } else if (attMapping.getParentLabel() != null) { attOrderedTypeList.put(attMapping); indexAttributeList.put(attMapping.getLabel(), attMapping); } } } 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(); Expression propExpr = entry.getValue(); Object propValue = null; // getValue(propExpr, source); if (propValue != null) { List<String> ls = (List<String>) propValue; if (ls.size() != 0) { propValue = ls.get(0); } else { propValue = ""; } } targetAttributes.put(propName, propValue); } } private String getFullQueryPath(AttributeMapping attMapping) { return attMapping.getTargetXPath().toString(); } private void removeAllRelativePaths() { Collection<String> c = mapping.keySet(); Iterator<String> itr = c.iterator(); while (itr.hasNext()) { String key = itr.next(); Expression xPath = mapping.get(key); Expression xPath2 = removeRelativePaths(xPath); if (!xPath.toString().equals(xPath2.toString())) { mapping.put(key, xPath2); } } } private PropertyName removeRelativePaths(Expression xPath) { String xPathTemp = xPath.toString(); final int NOT_FOUND = -1; final String RELATIVE_PATH = "/../"; int i = xPathTemp.indexOf(RELATIVE_PATH); while (i != NOT_FOUND) { int slashPos = xPathTemp.lastIndexOf(XPATH_SEPARATOR, i - 1); if (slashPos != NOT_FOUND) { String left = xPathTemp.substring(0, slashPos + 1); String right = xPathTemp.substring(i + RELATIVE_PATH.length()); xPathTemp = left + right; } else { break; } i = xPathTemp.indexOf(RELATIVE_PATH); } return ff.property(xPathTemp); } /** * 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 */ @Override public List<Expression> findMappingsFor(final StepList propertyName) { List<Expression> expressions = null; // get all matching mappings if index is not specified, otherwise // get the specified mapping if (!propertyName.toString().contains("[")) { // collect all the mappings for the given property expressions = getExpressionsIgnoreIndex(propertyName); } else { // get specified mapping if indexed expressions = new ArrayList<Expression>(1); AttributeMapping mapping = getStringMapping(propertyName); if (mapping != null) { expressions.add(mapping.getSourceExpression()); } } return expressions; } }