/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2009, 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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
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.XmlFeatureCollection;
import org.geotools.data.complex.xml.XmlFeatureSource;
import org.geotools.data.complex.xml.XmlResponse;
import org.geotools.feature.AttributeBuilder;
import org.geotools.feature.FeatureCollection;
import org.geotools.filter.LiteralExpressionImpl;
import org.geotools.util.XmlXpathUtilites;
import org.jdom.Element;
import org.opengis.feature.Attribute;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.Name;
import org.opengis.filter.expression.Expression;
import org.xml.sax.Attributes;
/**
* An implementation of AbstractMappingFeatureIterator to handle XML datasources.
*
* @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/AppSchemaDataAccess.java $
*/
public class XmlMappingFeatureIterator extends AbstractMappingFeatureIterator {
/**
* Constants for manipulating XPath Expressions
*/
private 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 List<Element> sources;
private FeatureSource<SimpleFeatureType, SimpleFeature> mappedSource;
private FeatureCollection<SimpleFeatureType, SimpleFeature> sourceFeatures;
private AttributeCreateOrderList attOrderedTypeList = null;
private List<TreeAttributeMapping> setterAttributes = new ArrayList<TreeAttributeMapping>();
private TreeAttributeMapping rootAttribute;
private int count = 0;
private int indexCounter = 1;
private String idXpath;
private static Query cachedQuery = null;
protected static XmlResponse cachedXmlResponse = null;
/**
*
* @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);
idXpath = featureFidMapping.toString();
List<Integer> ls = xmlResponse.getValidFeatureIndex();
count = ls.size();
}
protected Iterator<Element> getSourceFeatureIterator() {
return null;
}
protected boolean isSourceFeatureIteratorNull() {
return xmlResponse == null;
}
protected void setSourceFeatureIterator(Iterator<Element> xmlSourceFeatureIterator) {
}
protected void initialiseSourceFeatures(FeatureTypeMapping mapping, Query query)
throws IOException {
mappedSource = mapping.getSource();
// check if the previous query was the same as this one. If not retrieve the new data.
// Otherwise use the same data. We want to do this once only--Geoserver does a count
// operation, before getting the data. This results in two identical queries. We are simply
// trying to save on the second query here. We don't want to cache beyond this.
if (cachedQuery == null || !cachedQuery.equals(query)) {
sourceFeatures = mappedSource.getFeatures(query);
XmlFeatureSource xmlFeatureSource = (XmlFeatureSource) mappedSource;
xmlFeatureSource.setNamespaces(mapping.getNamespaces());
xmlFeatureSource.setItemXpath(mapping.getItemXpath());
this.xmlResponse = ((XmlFeatureCollection) sourceFeatures).xmlResponse();
XmlMappingFeatureIterator.cachedQuery = query;
XmlMappingFeatureIterator.cachedXmlResponse = xmlResponse;
} else {
this.xmlResponse = cachedXmlResponse;
XmlMappingFeatureIterator.cachedXmlResponse = null;
XmlMappingFeatureIterator.cachedQuery = null;
}
}
protected String extractIdForFeature() {
return XmlXpathUtilites.getSingleXPathValue(mapping.getNamespaces(),
createIndexedItemXpathString() + XPATH_SEPARATOR + idXpath, xmlResponse.getDoc());
}
private String createIndexedItemXpathString() {
return mapping.itemXpath + XPATH_LEFT_INDEX_BRACKET
+ xmlResponse.getValidFeatureIndex().get(indexCounter - 1)
+ XPATH_RIGHT_INDEX_BRACKET;
}
protected String extractIdForAttribute(final Expression idExpression, Object sourceInstance) {
String value = (String) idExpression.evaluate(sourceInstance, String.class);
return value;
}
protected Feature populateFeatureData(String id) throws IOException {
final AttributeDescriptor targetNode = mapping.getTargetFeature();
AttributeBuilder builder = new AttributeBuilder(attf);
builder.setDescriptor(targetNode);
Feature target = (Feature) builder.build(id);
if (attOrderedTypeList == null) {
initialiseAttributeLists(mapping.getAttributeMappings());
}
// create required elements
PathAttributeList elements = populateAttributeList(target);
setAttributeValues(elements);
indexCounter++;
return target;
}
private void setAttributeValues(PathAttributeList elements) {
for (TreeAttributeMapping attMapping : setterAttributes) {
final Expression sourceExpression = attMapping.getSourceExpression();
List<Pair> ls = elements.get(attMapping.getParentLabel());
if (ls != null) {
for (int i = 0; i < ls.size(); i++) {
Pair parentPair = ls.get(i);
Attribute setterTarget = parentPair.getAttribute();
StringBuffer usedXpath = new StringBuffer();
List<String> values = getValue(parentPair.getXpath(), sourceExpression,
usedXpath);
setValues(attMapping, setterTarget, usedXpath, values);
}
}
}
}
private void setValues(TreeAttributeMapping attMapping, Attribute setterTarget,
StringBuffer usedXpath, List<String> values) {
for (int j = 0; j < values.size(); j++) {
String value = values.get(j);
if(LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("setting target=" + setterTarget.getName() + ", targetXpath=" + attMapping
.getTargetXPath() + ", value=" + value);
}
Attribute subFeature = xpathAttributeBuilder.set(setterTarget, attMapping
.getTargetXPath(), value, null, null, false);
setClientProperties(subFeature, j == 0 ? usedXpath : usedXpath
.append(bracketedIndex(j)), attMapping.getClientProperties());
}
}
private String bracketedIndex(int j) {
return XPATH_LEFT_INDEX_BRACKET + Integer.toString(j + 1)
+ XPATH_RIGHT_INDEX_BRACKET;
}
private PathAttributeList populateAttributeList(Feature target) {
PathAttributeList elements = new PathAttributeList();
elements.put(rootAttribute.getLabel(), createIndexedItemXpathString(), target);
// iterator returns the attribute mappings starting from the root of the tree.
// parents are always returned before children elements.
Iterator<TreeAttributeMapping> it = attOrderedTypeList.iterator();
while (it.hasNext()) {
TreeAttributeMapping 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);
int count = 1;
String countXpath = null;
// if instance path not set, then element exists, with one instance
if (attMapping.getInstanceXpath() != null) {
countXpath = parentAttribute.getXpath() + XPATH_SEPARATOR
+ attMapping.getInstanceXpath();
count = XmlXpathUtilites.countXPathNodes(mapping.getNamespaces(),
countXpath, xmlResponse.getDoc());
}
createSubFeaturesAndAddToAttributeList(elements, attMapping, sourceExpression,
parentAttribute, count, countXpath);
}
}
}
return elements;
}
private void createSubFeaturesAndAddToAttributeList(PathAttributeList elements,
TreeAttributeMapping attMapping, final Expression sourceExpression,
Pair parentAttribute, int count, String countXpath) {
for (int j = 0; j < count; j++) {
final String bracketIndex = bracketedIndex(j);
final String xpath = setFeatureXpath(attMapping, sourceExpression, parentAttribute,
bracketIndex);
List<String> featureIdList = getValue(xpath);
String featureId = null;
if (!featureIdList.isEmpty()) {
featureId = featureIdList.get(0);
}
StepList sl = attMapping.getTargetXPath();
setPathIndex(j, sl);
Attribute subFeature = xpathAttributeBuilder.set(parentAttribute.getAttribute(),
sl, null, featureId, attMapping.getTargetNodeInstance(), false);
elements.put(attMapping.getLabel(), countXpath + bracketIndex, subFeature);
}
}
private String setFeatureXpath(TreeAttributeMapping 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(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 boolean unprocessedFeatureExists() {
if (indexCounter <= count) {
return true;
} else {
return false;
}
}
protected boolean sourceFeatureIteratorHasNext() {
return indexCounter <= count;
}
protected boolean isNextSourceFeatureNull() {
return indexCounter > count;
}
protected List<String> getValue(Expression expression, Object data) {
return getValue(((StringBuffer) data).toString(), expression, new StringBuffer());
}
protected List<String> getValue(String xpathPrefix, Expression node, StringBuffer usedXpath) {
final String EMPTY_STRING = "";
String expressionValue = node.toString();
boolean isUnsetNode = Expression.NIL.equals(node) || expressionValue.equals("''");
if (isUnsetNode || expressionValue.startsWith("'") || node instanceof LiteralExpressionImpl) {
usedXpath.append(xpathPrefix);
String editedValue = EMPTY_STRING;
List<String> ls = new ArrayList<String>(1);
if (!isUnsetNode) {
editedValue = expressionValue.replace("'", "");
}
ls.add(editedValue);
return ls;
} else {
expressionValue = xpathPrefix + XPATH_SEPARATOR + expressionValue;
}
usedXpath.append(expressionValue);
return getValue(expressionValue);
}
protected List<String> getValue(String expressionValue) {
return XmlXpathUtilites.getXPathValues(mapping.getNamespaces(), expressionValue,
xmlResponse.getDoc());
}
protected void setAttributeValueFromSources(Feature target, AttributeMapping attMapping)
throws IOException {
for (Element source : sources) {
setAttributeValue(target, source, attMapping);
}
}
protected void closeSourceFeatures() {
if (sourceFeatures != null) {
xmlResponse = null;
sourceFeatures = null;
}
}
private void initialiseAttributeLists(List<AttributeMapping> mappings) {
for (AttributeMapping attMapping : mappings) {
if (attMapping.isTreeAttribute()) {
TreeAttributeMapping treeAttMapping = (TreeAttributeMapping) attMapping;
if (treeAttMapping.getLabel() != null && treeAttMapping.getParentLabel() == null
&& attMapping.getTargetNodeInstance() == null) {
rootAttribute = treeAttMapping;
break;
}
}
}
attOrderedTypeList = new AttributeCreateOrderList(rootAttribute.getLabel());
for (AttributeMapping attMapping : mappings) {
if (attMapping.isTreeAttribute()) {
TreeAttributeMapping treeAttMapping = (TreeAttributeMapping) attMapping;
if (treeAttMapping.getLabel() == null) {
setterAttributes.add(treeAttMapping);
} else if (treeAttMapping.getParentLabel() != null) {
attOrderedTypeList.put(treeAttMapping);
}
}
}
}
protected void setAttributeValue(Feature target, final Feature source, StepList xpath, String id)
throws IOException {
xpathAttributeBuilder.set(target, xpath, source, id, source.getType(), false);
}
/**
* Sets the values of grouping attributes.
*
* @param sourceFeature
* @param groupingMappings
* @param targetFeature
*
* @return Feature. Target feature sets with simple attributes
*/
protected void setAttributeValue(Feature target, final Object source,
final AttributeMapping attMapping) throws IOException {
final Expression sourceExpression = attMapping.getSourceExpression();
Object value = getValue(sourceExpression, source);
String id = null;
if (Expression.NIL != attMapping.getIdentifierExpression()) {
id = extractIdForAttribute(attMapping.getIdentifierExpression(), source);
}
final AttributeType targetNodeType = attMapping.getTargetNodeInstance();
final StepList xpath = attMapping.getTargetXPath();
Attribute instance = xpathAttributeBuilder.set(target, xpath, value, id, targetNodeType,
attMapping.isMultiValued());
Map<Name, Expression> clientPropsMappings = attMapping.getClientProperties();
setClientProperties(instance, source, 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();
Expression propExpr = entry.getValue();
String propValue = "";
List<String> ls = getValue(propExpr, source);
if (!ls.isEmpty()) {
propValue = ls.get(0);
}
if(LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("setting target=" + target.getName() + ", property Name=" + propName
+ ", value=" + propValue);
}
targetAttributes.put(propName, propValue);
}
// FIXME should set a child Property
target.getUserData().put(Attributes.class, targetAttributes);
}
}