/*
* 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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.geotools.data.DefaultQuery;
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.CommonFactoryFinder;
import org.geotools.feature.AppSchemaFeatureFactoryImpl;
import org.geotools.feature.GeometryAttributeImpl;
import org.geotools.feature.Types;
import org.geotools.feature.type.GeometryDescriptorImpl;
import org.geotools.feature.type.GeometryTypeImpl;
import org.geotools.filter.FilterFactoryImplNamespaceAware;
import org.geotools.xlink.XLINK;
import org.opengis.feature.Attribute;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureFactory;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.Property;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.Name;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.xml.sax.helpers.NamespaceSupport;
import com.vividsolutions.jts.geom.Geometry;
/**
* Base class for several MappingFeatureImplementation's.
*
* @author Russell Petty, GSV
* @version $Id$
* @source $URL$
*/
public abstract class AbstractMappingFeatureIterator implements IMappingFeatureIterator {
/** The logger for the filter module. */
protected static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geotools.data.complex");
/**
* Name representation of xlink:href
*/
public static final Name XLINK_HREF_NAME = Types.toTypeName(XLINK.HREF);
/**
* The mappings for the source and target schemas
*/
protected FeatureTypeMapping mapping;
/**
* Expression to evaluate the feature id
*/
protected Expression featureFidMapping;
/**
* Factory used to create the target feature and attributes
*/
protected FeatureFactory attf;
protected AppSchemaDataAccess store;
final protected XPath xpathAttributeBuilder;
protected FilterFactory namespaceAwareFilterFactory;
/**
* maxFeatures restriction value as provided by query
*/
protected final int maxFeatures;
/** counter to ensure maxFeatures is not exceeded */
protected int featureCounter;
protected NamespaceSupport namespaces;
/**
* True if hasNext has been called prior to calling next()
*/
private boolean hasNextCalled = false;
public AbstractMappingFeatureIterator(AppSchemaDataAccess store, FeatureTypeMapping mapping,
Query query) throws IOException {
this.store = store;
this.attf = new AppSchemaFeatureFactoryImpl();
Name name = mapping.getTargetFeature().getName();
List<AttributeMapping> attributeMappings = mapping.getAttributeMappings();
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(name, stepName)) {
featureFidMapping = attMapping.getIdentifierExpression();
break;
}
}
this.mapping = mapping;
if (featureFidMapping == null || Expression.NIL.equals(featureFidMapping)) {
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
featureFidMapping = ff.property("@id");
}
Query unrolledQuery = getUnrolledQuery(query);
initialiseSourceFeatures(mapping, unrolledQuery);
xpathAttributeBuilder = new XPath();
xpathAttributeBuilder.setFeatureFactory(attf);
namespaces = mapping.getNamespaces();
namespaceAwareFilterFactory = new FilterFactoryImplNamespaceAware(namespaces);
xpathAttributeBuilder.setFilterFactory(namespaceAwareFilterFactory);
this.maxFeatures = query.getMaxFeatures();
}
/**
* Shall not be called, just throws an UnsupportedOperationException
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Closes the underlying FeatureIterator
*/
public void close() {
closeSourceFeatures();
}
/**
* Based on the set of xpath expression/id extracting expression, finds the ID for the attribute
* <code>idExpression</code> from the source complex attribute.
*
* @param idExpression
* the location path of the attribute to be created, for which to obtain the id by
* evaluating the corresponding <code>org.geotools.filter.Expression</code> from
* <code>sourceInstance</code>.
* @param sourceInstance
* a complex attribute which is the source of the mapping.
* @return the ID to be applied to a new attribute instance addressed by
* <code>attributeXPath</code>, or <code>null</code> if there is no an id mapping for
* that attribute.
*/
protected abstract String extractIdForAttribute(final Expression idExpression,
Object sourceInstance);
/**
* Return next feature.
*
* @see java.util.Iterator#next()
*/
public Feature next() {
if (!hasNext()) {
throw new IllegalStateException("there are no more features in this iterator");
}
hasNextCalled = false;
Feature next;
try {
next = computeNext();
} catch (IOException e) {
close();
throw new RuntimeException(e);
}
++featureCounter;
return next;
}
/**
* Return true if there are more features.
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
if (hasNextCalled) {
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();
hasNextCalled = true;
}
return exists;
}
/**
* Return a query appropriate to its underlying feature source.
*
* @param query
* the original query against the output schema
* @return a query appropriate to be executed over the underlying feature source.
*/
protected Query getUnrolledQuery(Query query) {
return store.unrollQuery(query, mapping);
}
protected Feature computeNext() throws IOException {
if (!hasNextCalled) {
// hasNext needs to be called to set nextSrcFeature
if (!hasNext()) {
return null;
}
}
hasNextCalled = false;
if (isNextSourceFeatureNull()) {
throw new UnsupportedOperationException("No more features produced!");
}
String id = extractIdForFeature();
Feature target = populateFeatureData(id);
if (target.getDefaultGeometryProperty() == null) {
setGeometry(target);
}
return target;
}
/**
* Set the feature geometry to that of the first property bound to a JTS geometry
*
* @param feature
*/
protected void setGeometry(Feature feature) {
// FIXME an ugly, ugly hack to smuggle a geometry into a feature
// FeatureImpl.getBounds and GMLSchema do not work together
for (final Property property : feature.getProperties()) {
if (Geometry.class.isAssignableFrom(property.getType().getBinding())) {
// need to manufacture a GeometryDescriptor so we can make a GeometryAttribute
// in which we can store the Geometry
AttributeType type = (AttributeType) property.getType();
GeometryType geometryType = new GeometryTypeImpl(type.getName(), type.getBinding(),
null, type.isIdentified(), type.isAbstract(), type.getRestrictions(), type
.getSuper(), type.getDescription());
AttributeDescriptor descriptor = (AttributeDescriptor) property.getDescriptor();
GeometryDescriptor geometryDescriptor = new GeometryDescriptorImpl(geometryType,
descriptor.getName(), descriptor.getMinOccurs(), descriptor.getMaxOccurs(),
property.isNillable(), null);
GeometryAttribute geometryAttribute = new GeometryAttributeImpl(
property.getValue(), geometryDescriptor, null);
List<Property> properties = new ArrayList<Property>(feature.getProperties());
properties.remove(property);
properties.add(geometryAttribute);
feature.setValue(properties);
feature.setDefaultGeometryProperty(geometryAttribute);
break;
}
}
}
protected boolean isHasNextCalled() {
return hasNextCalled;
}
protected void setHasNextCalled(boolean hasNextCalled) {
this.hasNextCalled = hasNextCalled;
}
protected abstract void closeSourceFeatures();
protected abstract Iterator<?> getSourceFeatureIterator();
protected abstract void initialiseSourceFeatures(FeatureTypeMapping mapping, Query query)
throws IOException;
protected abstract boolean unprocessedFeatureExists();
protected abstract boolean sourceFeatureIteratorHasNext();
protected abstract String extractIdForFeature();
protected abstract boolean isNextSourceFeatureNull();
protected abstract Feature populateFeatureData(String id) throws IOException;
protected abstract Object getValue(Expression expression, Object sourceFeature);
protected abstract boolean isSourceFeatureIteratorNull();
abstract protected void setClientProperties(final Attribute target, final Object source,
final Map<Name, Expression> clientProperties);
}