/*
* 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.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
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.Types;
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.type.Name;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.xml.sax.helpers.NamespaceSupport;
/**
* 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;
/**
* 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;
protected Set<String> propertyNames;
public AbstractMappingFeatureIterator(AppSchemaDataAccess store, FeatureTypeMapping mapping,
Query query) throws IOException {
this(store, mapping, query, false);
}
public AbstractMappingFeatureIterator(AppSchemaDataAccess store, FeatureTypeMapping mapping,
Query query, boolean isQueryUnrolled) throws IOException {
this.store = store;
this.attf = new AppSchemaFeatureFactoryImpl();
Name name = mapping.getTargetFeature().getName();
propertyNames = query.getPropertyNames()==null? null: new HashSet<String>(Arrays.asList(query.getPropertyNames()));
this.mapping = mapping;
this.maxFeatures = query.getMaxFeatures();
if (!isQueryUnrolled) {
query = getUnrolledQuery(query);
}
xpathAttributeBuilder = new XPath();
xpathAttributeBuilder.setFeatureFactory(attf);
initialiseSourceFeatures(mapping, query);
namespaces = mapping.getNamespaces();
namespaceAwareFilterFactory = new FilterFactoryImplNamespaceAware(namespaces);
xpathAttributeBuilder.setFilterFactory(namespaceAwareFilterFactory);
}
/**
* 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 (!isHasNextCalled()) {
LOGGER.warning("hasNext not called before calling next() in the iterator!");
if (!hasNext()) {
return null;
}
}
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();
}
setHasNextCalled(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();
return populateFeatureData(id);
}
protected boolean isHasNextCalled() {
return hasNextCalled;
}
protected void setHasNextCalled(boolean hasNextCalled) {
this.hasNextCalled = hasNextCalled;
}
protected abstract void closeSourceFeatures();
protected abstract Iterator<Feature> 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);
}