/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2017, 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.mongodb.complex; import com.mongodb.DBObject; import org.geotools.data.DataAccess; import org.geotools.data.FeatureListener; import org.geotools.data.FeatureSource; import org.geotools.data.Query; import org.geotools.data.QueryCapabilities; import org.geotools.data.ResourceInfo; import org.geotools.data.complex.AppSchemaDataAccess; import org.geotools.data.complex.FeatureTypeMapping; import org.geotools.data.complex.MappingFeatureCollection; import org.geotools.data.complex.MappingFeatureSource; import org.geotools.data.complex.NestedAttributeMapping; import org.geotools.data.complex.filter.XPathUtil; import org.geotools.data.memory.MemoryFeatureCollection; import org.geotools.data.mongodb.MongoFeature; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.feature.Feature; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.xml.sax.helpers.NamespaceSupport; import java.awt.*; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; /** * MongoDB custom nested attribute mapping for app-schema. */ public class MongoNestedMapping extends NestedAttributeMapping { public MongoNestedMapping(Expression idExpression, Expression parentExpression, XPathUtil.StepList targetXPath, boolean isMultiValued, Map<Name, Expression> clientProperties, Expression sourceElement, XPathUtil.StepList sourcePath, NamespaceSupport namespaces) throws IOException { super(idExpression, parentExpression, targetXPath, isMultiValued, clientProperties, sourceElement, sourcePath, namespaces); } @Override public List<Feature> getFeatures(Object source, Object foreignKeyValue, List<Object> idValues, CoordinateReferenceSystem reprojection, Object feature, List<PropertyName> selectedProperties, boolean includeMandatory, int resolveDepth, Integer resolveTimeOut) throws IOException { if (!(foreignKeyValue instanceof CollectionLinkFunction.LinkCollection)) { throw new RuntimeException("MongoDB nesting only supports foreign keys of 'CollectionLink' type."); } CollectionLinkFunction.LinkCollection linkCollection = (CollectionLinkFunction.LinkCollection) foreignKeyValue; List collection = getSubCollection(feature, linkCollection.getCollectionPath()); if (collection == null) { return Collections.emptyList(); } List<SimpleFeature> features = new ArrayList<>(); for (int i = 0; i < collection.size(); i++) { features.add(MongoCollectionFeature.build(feature, linkCollection.getCollectionPath(), i)); } FeatureSource fSource = buildMappingFeatureSource(feature, features); ArrayList<Feature> matchingFeatures = new ArrayList<Feature>(); // get all the mapped nested features based on the link values FeatureCollection<FeatureType, Feature> fCollection = fSource.getFeatures(Query.ALL); if (fCollection instanceof MappingFeatureCollection) { FeatureIterator<Feature> iterator = fCollection.features(); while (iterator.hasNext()) { matchingFeatures.add(iterator.next()); } iterator.close(); } return matchingFeatures; } private MappingFeatureSource buildMappingFeatureSource(Object feature, List<SimpleFeature> features) throws IOException { MappingFeatureSource originalFeatureSource = (MappingFeatureSource) getMappingSource(feature); FeatureTypeMapping mapping = originalFeatureSource.getMapping(); AppSchemaDataAccess dataAccess = (AppSchemaDataAccess) originalFeatureSource.getDataStore(); MemoryFeatureCollection collection = new MemoryFeatureCollection(null); collection.addAll(features); MongoStaticFeatureSource staticSource = new MongoStaticFeatureSource(collection, mapping.getSource()); FeatureTypeMapping staticMapping = new FeatureTypeMapping(staticSource, mapping.getTargetFeature(), mapping.getAttributeMappings(), mapping.getNamespaces(), mapping.isDenormalised()); return new MappingFeatureSource(dataAccess, staticMapping); } private List getSubCollection(Object feature, String collectionPath) { feature = MongoComplexUtilities.extractFeature(feature, collectionPath); if (feature instanceof MongoFeature) { DBObject mongoObject = ((MongoFeature) feature).getMongoObject(); return getSubCollection(mongoObject, collectionPath, Collections.emptyMap()); } else if (feature instanceof MongoCollectionFeature) { MongoCollectionFeature collectionFeature = (MongoCollectionFeature) feature; return getSubCollection(collectionFeature.getMongoFeature().getMongoObject(), collectionPath, collectionFeature.getCollectionsIndexes()); } throw new RuntimeException("MongoDB nesting only works with MongoDB features."); } private List getSubCollection(DBObject mongoObject, String collectionPath, Map<String, Integer> collectionsIndexes) { Object value = MongoComplexUtilities.getValue(mongoObject, collectionsIndexes, collectionPath); if (value == null) { return Collections.emptyList(); } if (value instanceof List) { return (List) value; } throw new RuntimeException("Could not extract collection from path."); } private static final class MongoStaticFeatureSource implements FeatureSource { private final FeatureCollection features; private final FeatureSource originalFeatureSource; public MongoStaticFeatureSource(FeatureCollection features, FeatureSource originalFeatureSource) { this.features = features; this.originalFeatureSource = originalFeatureSource; } @Override public Name getName() { return originalFeatureSource.getName(); } @Override public ResourceInfo getInfo() { return originalFeatureSource.getInfo(); } @Override public DataAccess getDataStore() { return originalFeatureSource.getDataStore(); } @Override public QueryCapabilities getQueryCapabilities() { return originalFeatureSource.getQueryCapabilities(); } @Override public void addFeatureListener(FeatureListener listener) { } @Override public void removeFeatureListener(FeatureListener listener) { } @Override public FeatureCollection getFeatures(Filter filter) throws IOException { return features; } @Override public FeatureCollection getFeatures(Query query) throws IOException { return features; } @Override public FeatureCollection getFeatures() throws IOException { return features; } @Override public FeatureType getSchema() { return originalFeatureSource.getSchema(); } @Override public ReferencedEnvelope getBounds() throws IOException { return features.getBounds(); } @Override public ReferencedEnvelope getBounds(Query query) throws IOException { return features.getBounds(); } @Override public int getCount(Query query) throws IOException { return features.size(); } @Override public Set<RenderingHints.Key> getSupportedHints() { return originalFeatureSource.getSupportedHints(); } } }