/* * 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.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.apache.commons.jxpath.JXPathException; import org.geotools.data.Query; import org.geotools.data.complex.PathAttributeList.Pair; import org.geotools.data.complex.filter.XPath.*; import org.geotools.data.complex.xml.*; import org.geotools.feature.AttributeBuilder; import org.geotools.feature.Types; import org.geotools.feature.type.ComplexTypeImpl; import org.geotools.filter.LiteralExpressionImpl; import org.geotools.util.XmlXpathUtilites; import org.opengis.feature.Attribute; import org.opengis.feature.Feature; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.Name; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Function; import org.xml.sax.Attributes; /** * An implementation of AbstractMappingFeatureIterator to handle XML datasources. * * @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/AppSchemaDataAccess.java $ */ public class XmlMappingFeatureIterator extends DataAccessMappingFeatureIterator { /** * Constants for manipulating XPath Expressions */ public static final String XPATH_SEPARATOR = "/"; private static final String XPATH_LEFT_INDEX_BRACKET = "["; private static final String XPATH_RIGHT_INDEX_BRACKET = "]"; protected XmlResponse xmlResponse; private AttributeCreateOrderList attOrderedTypeList = null; private int count = 0; private int indexCounter = 1; private String idXpath; /** * * @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 XmlMappingFeatureIterator(AppSchemaDataAccess store, FeatureTypeMapping mapping, Query query) throws IOException { super(store, mapping, query, false); setIdXPath(); if (xmlResponse == null) { this.xmlResponse = ((XmlFeatureCollection) sourceFeatures).xmlResponse(); } List<Integer> ls = xmlResponse.getValidFeatureIndex(); count = ls.size(); } public XmlMappingFeatureIterator(AppSchemaDataAccess store, FeatureTypeMapping mapping, Query query, String xpath, String value) throws IOException { super(store, mapping, query, false); setIdXPath(); if (xmlResponse == null) { this.xmlResponse = ((XmlFeatureCollection) sourceFeatures).xmlResponse(xpath, value); } List<Integer> ls = xmlResponse.getValidFeatureIndex(); count = ls.size(); } private void setIdXPath() { idXpath = mapping.getFeatureIdExpression().equals(Expression.NIL) ? "@id" : mapping .getFeatureIdExpression().toString(); } protected Iterator<SimpleFeature> getSourceFeatureIterator() { return null; } protected boolean isSourceFeatureIteratorNull() { return xmlResponse == null; } @Override protected String extractIdForAttribute(final Expression idExpression, Object sourceInstance) { try { if (idExpression instanceof Function) { // special handling for functions XmlXpathFilterData data = new XmlXpathFilterData(namespaces, xmlResponse.getDoc(), -1, XmlMappingFeatureIterator.createIndexedItemXpathString( (XmlFeatureTypeMapping) mapping, xmlResponse, indexCounter)); Object value = idExpression.evaluate(data); return (value == null ? "" : value.toString()); } else { return XmlXpathUtilites.getSingleXPathValue(mapping.getNamespaces(), createIndexedItemXpathString((XmlFeatureTypeMapping) mapping, xmlResponse, indexCounter) + XPATH_SEPARATOR + idXpath, xmlResponse.getDoc()); } } catch (RuntimeException e) { if (e.getCause() instanceof JXPathException) { // only log info since id is not always compulsory LOGGER.info("Feature id is not mapped for: " + mapping.getTargetFeature().getName()); } else { throw e; } } return null; } public static String createIndexedItemXpathString(XmlFeatureTypeMapping mapping, XmlResponse xmlResponse, int indexCounter) { String rootXpath = mapping.itemXpath + XPATH_LEFT_INDEX_BRACKET + xmlResponse.getValidFeatureIndex().get(indexCounter - 1) + XPATH_RIGHT_INDEX_BRACKET; if (mapping.rootAttribute.getInstanceXpath() != null) { rootXpath += XPATH_SEPARATOR + mapping.rootAttribute.getInstanceXpath(); } return rootXpath; } protected Feature populateFeatureData() throws IOException { final AttributeDescriptor targetNode = mapping.getTargetFeature(); AttributeBuilder builder = new AttributeBuilder(attf); builder.setDescriptor(targetNode); Feature target = (Feature) builder.build(extractIdForAttribute(mapping .getFeatureIdExpression(), null)); if (attOrderedTypeList == null) { initialiseAttributeLists(mapping.getAttributeMappings()); } // create required elements PathAttributeList elements = populateAttributeList(target); setAttributeValues(elements, target); indexCounter++; return target; } @SuppressWarnings("unchecked") private void setAttributeValues(PathAttributeList elements, Feature target) throws IOException { for (AttributeMapping attMapping : ((XmlFeatureTypeMapping)mapping).setterAttributes) { String parentLabel = attMapping.getParentLabel(); List<Pair> ls = elements.get(parentLabel); if (ls != null) { final Expression sourceExpression = attMapping.getSourceExpression(); final Expression idExpression = attMapping.getIdentifierExpression(); for (int i = 0; i < ls.size(); i++) { Pair parentPair = ls.get(i); Attribute setterTarget = parentPair.getAttribute(); // find the root attribute mapping from the xpath // get the full xpath query including StepList xpath = attMapping.getTargetXPath().clone(); Object value = getValue(parentPair.getXpath(), sourceExpression, target); xpath.get(0).setIndex(i+1); if (value != null) { if (value instanceof Collection) { Collection<Object> values = (Collection) value; for (Object val : values) { setValues(val, setterTarget, parentPair, attMapping, target, xpath, idExpression); } } else { setValues(value, setterTarget, parentPair, attMapping, target, xpath, idExpression); } } } } } } private void setValues(Object value, Attribute setterTarget, Pair parentPair, AttributeMapping attMapping, Feature target, StepList xpath, Expression idExpression) throws IOException { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer("setting target=" + setterTarget.getName() + ", targetXpath=" + attMapping.getTargetXPath() + ", value=" + value); } String featureId = getId(idExpression, parentPair, attMapping, ""); // Attribute att = xpathAttributeBuilder.set(target, // xpath, value, featureId, attMapping.getTargetNodeInstance(), false, attMapping // .getSourceExpression()); Attribute att = setAttributeValue(target, featureId, null, attMapping, value, xpath, null); setClientProperties(att, parentPair.getXpath(), attMapping.getClientProperties()); } private void setMappedIndex(Attribute att, int index) { att.getUserData().put(ComplexFeatureConstants.MAPPED_ATTRIBUTE_INDEX, index); } protected void setClientProperties(final Attribute target, final Object xpathPrefix, final Map<Name, Expression> clientProperties) { if (target == null) { return; } // NC - first calculate target attributes final Map<Name, Object> targetAttributes = new HashMap<Name, Object>(); if (target.getUserData().containsValue(Attributes.class)) { targetAttributes.putAll((Map<? extends Name, ? extends Object>) target.getUserData() .get(Attributes.class)); } for (Map.Entry<Name, Expression> entry : clientProperties.entrySet()) { Name propName = entry.getKey(); Object propExpr = entry.getValue(); Object propValue; if (propExpr instanceof Expression) { propValue = getValue((xpathPrefix == null ? "" : xpathPrefix.toString()), (Expression) propExpr, target); } else { propValue = propExpr; } if (propValue != null) { if (propValue instanceof Collection) { if (!((Collection)propValue).isEmpty()) { propValue = ((Collection)propValue).iterator().next(); targetAttributes.put(propName, propValue); } } else { 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. if (targetAttributes.size() > 0) { target.getUserData().put(Attributes.class, targetAttributes); } setGeometryUserData(target, targetAttributes); } private String bracketedIndex(int j) { return XPATH_LEFT_INDEX_BRACKET + Integer.toString(j) + XPATH_RIGHT_INDEX_BRACKET; } private PathAttributeList populateAttributeList(Feature target) throws IOException { PathAttributeList elements = new PathAttributeList(); String rootPrefix = createIndexedItemXpathString((XmlFeatureTypeMapping) mapping, xmlResponse, indexCounter); elements .put(((XmlFeatureTypeMapping) mapping).rootAttribute.getLabel(), rootPrefix, target); setClientProperties(target, rootPrefix, ((XmlFeatureTypeMapping) mapping).rootAttribute .getClientProperties()); // iterator returns the attribute mappings starting from the root of the tree. // parents are always returned before children elements. Iterator<AttributeMapping> it = attOrderedTypeList.iterator(); while (it.hasNext()) { AttributeMapping attMapping = it.next(); final Expression idExpression = 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 countXpath = parentAttribute.getXpath(); // if instance path not set, then only count the root node if (attMapping.getInstanceXpath() != null) { countXpath += XPATH_SEPARATOR + attMapping.getInstanceXpath(); } int count = XmlXpathUtilites.countXPathNodes(mapping.getNamespaces(), countXpath, xmlResponse.getDoc()); createSubFeaturesAndAddToAttributeList(elements, attMapping, idExpression, parentAttribute, count, countXpath, target); } } } return elements; } private String getId(Expression idExpression, Pair parentAttribute, AttributeMapping attMapping, String bracketIndex) { String featureId = null; String idPath; if (idExpression instanceof Function) { // get parent attribute xpath and append this instance xpath + index idPath = parentAttribute.getXpath() + XPATH_SEPARATOR; if (attMapping.getInstanceXpath() != null) { idPath += attMapping.getInstanceXpath() + bracketIndex; } XmlXpathFilterData data = new XmlXpathFilterData(namespaces, xmlResponse.getDoc(), -1, idPath); featureId = idExpression.evaluate(data, String.class); } else { idPath = setFeatureXpath(attMapping, idExpression, parentAttribute, bracketIndex); List<String> featureIdList = getValue(idPath); if (!featureIdList.isEmpty()) { featureId = featureIdList.get(0); } } return featureId; } private void createSubFeaturesAndAddToAttributeList(PathAttributeList elements, AttributeMapping attMapping, final Expression idExpression, Pair parentAttribute, int count, String countXpath, Feature target) throws IOException { StepList sl = attMapping.getTargetXPath().clone(); setPathIndex(parentAttribute.getAttribute(), sl); for (int j = 1; j <= count; j++) { final String bracketIndex = bracketedIndex(j); String featureId = getId(idExpression, parentAttribute, attMapping, bracketIndex); setLastElementIndex(parentAttribute.getAttribute(), sl, j); Attribute subFeature = xpathAttributeBuilder.set(target, sl, null, featureId, attMapping.getTargetNodeInstance(), false, attMapping .getSourceExpression()); // Attribute subFeature = setAttributeValue(parentAttribute.getAttribute(), featureId, // null, attMapping, null, sl, null); String xpath = countXpath + bracketIndex; setClientProperties(subFeature, xpath, attMapping.getClientProperties()); setMappedIndex(subFeature, j); elements.put(attMapping.getLabel(), xpath, subFeature); } } /** * Find the last element in the given path and set the index. * * @param parent * Parent attribute where the path is going to be set in. * @param sl * The path to be set. * @param index * The index for the last element. */ private void setLastElementIndex(Attribute parent, StepList sl, int index) { if (!(parent.getType() instanceof ComplexTypeImpl)) { // not a complex type, so just set the first index as a simple type // can't have another complex type as children anyway sl.get(0).setIndex(index); return; } ComplexTypeImpl type = (ComplexTypeImpl) parent.getType(); // check the last step first and gradually move to the previous one int lastIndex = sl.size() - 1; Name lastStep = Types.toTypeName(sl.get(lastIndex).getName()); while (!Types.isElement(type, lastStep)) { lastIndex--; if (lastIndex < 0) { return; } lastStep = Types.toTypeName(sl.get(lastIndex).getName()); } sl.get(lastIndex).setIndex(index); } private String setFeatureXpath(AttributeMapping attMapping, final Expression sourceExpression, Pair parentAttribute, final String bracketIndex) { String xpath; if (attMapping.getInstanceXpath() == null) { xpath = parentAttribute.getXpath() + XPATH_SEPARATOR + sourceExpression.toString(); } else { xpath = parentAttribute.getXpath() + XPATH_SEPARATOR + attMapping.getInstanceXpath() + bracketIndex + XPATH_SEPARATOR + sourceExpression.toString(); } return xpath; } private void setPathIndex(Attribute att, StepList sl) { Object index = att.getUserData().get(ComplexFeatureConstants.MAPPED_ATTRIBUTE_INDEX); if (index != null) { int mappedIndex = Integer.parseInt(String.valueOf(index)); // get prefixed attribute name Name attName = att.getName(); String nsPrefix = namespaces.getPrefix(attName.getNamespaceURI()); String xpath = nsPrefix + ":" + attName.getLocalPart(); // set the index of the attribute in the attribute mapping steps sl.setIndex(mappedIndex, xpath, XPATH_SEPARATOR); } } protected boolean unprocessedFeatureExists() { if (indexCounter <= count) { return true; } else { return false; } } protected boolean sourceFeatureIteratorHasNext() { return indexCounter <= count; } protected boolean isNextSourceFeatureNull() { return indexCounter > count; } private Object getValue(String xpathPrefix, Expression node, Attribute target) { final String EMPTY_STRING = ""; String expressionString = node.toString(); boolean isUnsetNode = Expression.NIL.equals(node) || expressionString.equals("''"); if (isUnsetNode || expressionString.startsWith("'") || node instanceof LiteralExpressionImpl) { String editedValue = EMPTY_STRING; if (!isUnsetNode) { editedValue = expressionString.replace("'", ""); } return editedValue; } else { if (node instanceof Function) { // special handling for functions XmlXpathFilterData data = new XmlXpathFilterData(namespaces, xmlResponse.getDoc(), -1, xpathPrefix); return node.evaluate(data); } else if (xpathPrefix.length() > 0) { expressionString = xpathPrefix + XPATH_SEPARATOR + expressionString; } } return getValue(expressionString); } protected List<String> getValue(String expressionValue) { return XmlXpathUtilites.getXPathValues(mapping.getNamespaces(), expressionValue, xmlResponse.getDoc()); } protected void closeSourceFeatures() { if (sourceFeatures != null) { xmlResponse = null; sourceFeatures = null; } } private void initialiseAttributeLists(List<AttributeMapping> mappings) { attOrderedTypeList = new AttributeCreateOrderList( ((XmlFeatureTypeMapping) mapping).rootAttribute.getLabel()); for (AttributeMapping attMapping : mappings) { if (attMapping.equals(((XmlFeatureTypeMapping) mapping).rootAttribute)) { // exclude root continue; } if (attMapping.getLabel() != null && attMapping.getParentLabel() != null) { attOrderedTypeList.put(attMapping); } } } /** * Return true if there are more features. * * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { if (isHasNextCalled()) { return !isNextSourceFeatureNull(); } boolean exists = false; if (featureCounter >= maxFeatures) { return false; } if (isSourceFeatureIteratorNull()) { return false; } // make sure features are unique by mapped id exists = unprocessedFeatureExists(); if (!exists) { LOGGER.finest("no more features, produced " + featureCounter); close(); } setHasNextCalled(true); return exists; } @Override protected Feature computeNext() throws IOException { if (!isHasNextCalled()) { // hasNext needs to be called to set nextSrcFeature if (!hasNext()) { return null; } } setHasNextCalled(false); if (isNextSourceFeatureNull()) { throw new UnsupportedOperationException("No more features produced!"); } Feature f = populateFeatureData(); return f; } }