/*
* 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.List;
import java.util.logging.Logger;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.complex.config.AppSchemaDataAccessConfigurator;
import org.geotools.data.complex.config.Types;
import org.geotools.data.complex.filter.ComplexFilterSplitter;
import org.geotools.data.complex.filter.XPath;
import org.geotools.data.complex.filter.XPathUtil.StepList;
import org.geotools.data.joining.JoiningQuery;
import org.geotools.filter.FilterAttributeExtractor;
import org.geotools.filter.FilterCapabilities;
import org.geotools.filter.visitor.DefaultFilterVisitor;
import org.geotools.jdbc.JDBCFeatureSource;
import org.geotools.jdbc.JDBCFeatureStore;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
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)
*
* @source $URL:
* http://svn.osgeo.org/geotools/trunk/modules/extension/app-schema/app-schema/src/main
* /java/org/geotools/data/complex/MappingFeatureIteratorFactory.java $
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/app-schema/app-schema/src/main
* /java/org/geotools/data/complex/MappingFeatureIteratorFactory.java $
*/
public class MappingFeatureIteratorFactory {
protected static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geotools.data.complex");
/**
* Temporary filter visitor to determine whether the filter concerns any attribute mapping that
* has isList enabled. This is because Bureau of Meteorology requires a subset of the property
* value to be returned, instead of the full value. This should be a temporary solution. This
* won't work with feature chaining at the moment.
*
* @author Rini Angreani (CSIRO Earth Science and Resource Engineering)
*
*/
static class IsListFilterVisitor extends DefaultFilterVisitor {
// Attribute mappings that have isList enabled
private List<AttributeMapping> listMappings;
// True if the filter has properties that are configured as isList
private boolean isListFilter;
private FeatureTypeMapping mappings;
public IsListFilterVisitor(List<AttributeMapping> listMappings, FeatureTypeMapping mappings) {
this.listMappings = listMappings;
this.mappings = mappings;
isListFilter = false;
}
@Override
public Object visit(PropertyName expression, Object extraData) {
AttributeDescriptor root = mappings.getTargetFeature();
String attPath = expression.getPropertyName();
NamespaceSupport namespaces = mappings.getNamespaces();
StepList simplifiedSteps = XPath.steps(root, attPath, namespaces);
StepList targetXpath;
for (AttributeMapping mapping : listMappings) {
targetXpath = mapping.getTargetXPath();
if (targetXpath.equals(simplifiedSteps)) {
// TODO: support feature chaining too?
isListFilter = true;
return extraData;
}
}
return extraData;
}
public boolean isListFilterExists() {
return isListFilter;
}
}
public static IMappingFeatureIterator getInstance(AppSchemaDataAccess store,
FeatureTypeMapping mapping, Query query, Filter unrolledFilter) throws IOException {
if (mapping instanceof XmlFeatureTypeMapping) {
return new XmlMappingFeatureIterator(store, mapping, query);
}
boolean isJoining = AppSchemaDataAccessConfigurator.isJoining();
boolean removeQueryLimitIfDenormalised = false;
FeatureSource mappedSource = mapping.getSource();
if (isJoining
&& !(mappedSource instanceof JDBCFeatureSource || mappedSource instanceof JDBCFeatureStore)) {
// check if joining is explicitly set for non database backends
if (AppSchemaDataAccessConfigurator.isJoiningSet()) {
throw new IllegalArgumentException(
"Joining queries are only supported on JDBC data stores");
} else {
// override default behaviour
// this is not intended
isJoining = false;
}
}
if (isJoining) {
if (!(query instanceof JoiningQuery)) {
boolean hasIdColumn = !Expression.NIL.equals(mapping.getFeatureIdExpression())
&& !(mapping.getFeatureIdExpression() instanceof Literal);
query = new JoiningQuery(query);
if (hasIdColumn) {
FilterAttributeExtractor extractor = new FilterAttributeExtractor();
mapping.getFeatureIdExpression().accept(extractor, null);
for (String pn : extractor.getAttributeNameSet()) {
((JoiningQuery) query).addId(pn);
}
}
((JoiningQuery) query).setRootMapping(mapping);
}
}
IMappingFeatureIterator iterator;
if (unrolledFilter != null) {
// unrolledFilter is set in JoiningNestedAttributeMapping
// so this is for nested features with joining
query.setFilter(Filter.INCLUDE);
Query unrolledQuery = store.unrollQuery(query, mapping);
unrolledQuery.setFilter(unrolledFilter);
if (query instanceof JoiningQuery && unrolledQuery instanceof JoiningQuery) {
((JoiningQuery) unrolledQuery).setRootMapping(((JoiningQuery) query)
.getRootMapping());
}
if (isSimpleType(mapping)) {
iterator = new MappingAttributeIterator(store, mapping, query, unrolledQuery);
} else {
iterator = new DataAccessMappingFeatureIterator(store, mapping, query,
unrolledQuery, false);
}
} else {
// HACK HACK HACK
// experimental/temporary solution for isList subsetting by filtering
List<AttributeMapping> listMappings = mapping.getIsListMappings();
Filter isListFilter = null;
if (!listMappings.isEmpty()) {
IsListFilterVisitor listChecker = new IsListFilterVisitor(listMappings, mapping);
Filter complexFilter = query.getFilter();
complexFilter.accept(listChecker, null);
if (listChecker.isListFilterExists()) {
isListFilter = AppSchemaDataAccess.unrollFilter(complexFilter, mapping);
}
}
// END OF HACK
if (isJoining || mappedSource instanceof JDBCFeatureSource
|| mappedSource instanceof JDBCFeatureStore) {
// has database as data source, we can use the data source filter capabilities
FilterCapabilities capabilities = getFilterCapabilities(mappedSource);
ComplexFilterSplitter splitter = new ComplexFilterSplitter(capabilities, mapping);
Filter filter = query.getFilter();
filter.accept(splitter, null);
Filter preFilter = splitter.getFilterPre();
query.setFilter(preFilter);
filter = splitter.getFilterPost();
if (isJoining) {
((JoiningQuery)query).setDenormalised(mapping.isDenormalised());
}
if (isJoining && isListFilter != null) {
// pass it on in JoiningQuery so it can be handled when the SQL is prepared
// in JoiningJDBCSource
((JoiningQuery) query).setSubset(true);
// also reset isListFilter to null so it doesn't perform the filtering in
// DataAccessMappingFeatureIterator except when post filtering is involved
// i.e. feature chaining is involved
if (filter == null || filter.equals(Filter.INCLUDE)) {
isListFilter = null;
}
}
// need to flag if this is non joining and has pre filter because it needs
// to find denormalised rows that match the id (but doesn't match pre filter)
boolean isFiltered = !isJoining && preFilter != null && preFilter != Filter.INCLUDE;
// HACK HACK HACK
// experimental/temporary solution for isList subsetting by filtering
// Because subsetting should be done before the feature is built.. so we're not
// using PostFilteringMappingFeatureIterator
boolean hasPostFilter = false;
if (isListFilter == null) {
// END OF HACK
if (filter != null && filter != Filter.INCLUDE) {
hasPostFilter = true;
}
}
int offset = 0;
int maxFeatures = 1000000;
if (hasPostFilter) {
// can't apply offset to the SQL query if using post filters
// it has to be applied to the post filter
offset = query.getStartIndex() == null ? 0 : query.getStartIndex();
query.setStartIndex(null);
maxFeatures = query.getMaxFeatures();
removeQueryLimitIfDenormalised = true;
}
iterator = new DataAccessMappingFeatureIterator(store, mapping, query, isFiltered,
removeQueryLimitIfDenormalised, hasPostFilter);
if (isListFilter != null) {
((DataAccessMappingFeatureIterator) iterator).setListFilter(isListFilter);
}
if (hasPostFilter) {
iterator = new PostFilteringMappingFeatureIterator(iterator, filter,
maxFeatures, offset);
}
} else if (mappedSource instanceof MappingFeatureSource) {
// web service data access wrapper
iterator = new DataAccessMappingFeatureIterator(store, mapping, query);
if (isListFilter != null) {
((DataAccessMappingFeatureIterator) iterator).setListFilter(isListFilter);
}
} else {
// non database sources e.g. property data store
Filter filter = query.getFilter();
iterator = new DataAccessMappingFeatureIterator(store, mapping, query,
!Filter.INCLUDE.equals(filter), true);
// HACK HACK HACK
// experimental/temporary solution for isList subsetting by filtering
if (isListFilter != null) {
((DataAccessMappingFeatureIterator) iterator).setListFilter(isListFilter);
}
// END OF HACK
}
}
return iterator;
}
private static boolean isSimpleType(FeatureTypeMapping mapping) {
return Types.isSimpleContentType(mapping.getTargetFeature().getType());
}
private static FilterCapabilities getFilterCapabilities(FeatureSource mappedSource)
throws IllegalArgumentException {
FilterCapabilities capabilities = null;
if (mappedSource instanceof JDBCFeatureSource) {
capabilities = ((JDBCFeatureSource) mappedSource).getDataStore()
.getFilterCapabilities();
} else if (mappedSource instanceof JDBCFeatureStore) {
capabilities = ((JDBCFeatureStore) mappedSource).getDataStore().getFilterCapabilities();
} else {
throw new IllegalArgumentException(
"Joining queries are only supported on JDBC data stores");
}
return capabilities;
}
}