/* (c) 2015 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.feature;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.store.ContentDataStore;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.collection.DecoratingSimpleFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
/**
* A DecoratingSimpleFeatureCollection that is used for "flattening" SimpleFeatures that may contain
* other SimpleFeatures as attributes (used in WFS 2.0 joins to store the tuple as a simple
* feature). This allows to generate outputs for formats do not support nested structures (e.g,
* CSV).-
*/
public class FlatteningFeatureCollection extends DecoratingSimpleFeatureCollection {
private SimpleFeatureType flattenedType;
private FlatteningFeatureCollection(SimpleFeatureCollection delegate,
SimpleFeatureType flattenedType) {
super(delegate);
this.flattenedType = flattenedType;
}
/**
* Flattens a SimpleFeatureCollection that may contain SimpleFeatures as attributes of other
* features.
*
* @param collection The input SimpleFeatureCollection
* @return A SimpleFeatureCollection whose features have no SimpleFeature attributes, or the
* original one, if no SimpleFeature attributes were found
*/
public static SimpleFeatureCollection flatten(SimpleFeatureCollection collection) {
SimpleFeatureType schema = collection.getSchema();
// collect the attributes
List<AttributeDescriptor> attributeDescriptors = new ArrayList<AttributeDescriptor>();
scanAttributeDescriptors(attributeDescriptors, schema, null);
// build the flattened feature type
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName(schema.getName());
for (AttributeDescriptor desc : attributeDescriptors)
builder.add(desc);
SimpleFeatureType flattenedType = builder.buildFeatureType();
// if the number of attributes is the same, we did not encounter a new attribute
if (collection.getSchema().getAttributeCount() == flattenedType.getAttributeCount()) {
return collection;
}
return new FlatteningFeatureCollection(collection, flattenedType);
}
/**
* Recursively scans a SimpleFeature for SimpleFeature attributes in order to build a
* "flattened" list of attributes
*
* @param attributeDescriptors A List of attribute descriptors, populated recursively
* @param featuretype The feature type to scan
* @param attrAlias An alias for adding as a prefix to the simple attribute names
*/
private static void scanAttributeDescriptors(List<AttributeDescriptor> attributeDescriptors,
SimpleFeatureType featureType, String attrAlias) {
List<AttributeDescriptor> descriptors = featureType.getAttributeDescriptors();
for (int i = 0; i < descriptors.size(); i++) {
AttributeDescriptor ad = descriptors.get(i);
SimpleFeatureType joinedSchema = (SimpleFeatureType) ad.getUserData()
.get(ContentDataStore.JOINED_FEATURE_TYPE);
String name = (attrAlias != null ? attrAlias + "." : "") + ad.getLocalName();
if (joinedSchema != null) {
// go forth and harvest feature attribute types
scanAttributeDescriptors(attributeDescriptors, joinedSchema, name);
} else {
// this is a common (non-feature) attribute type
AttributeTypeBuilder build = new AttributeTypeBuilder();
build.init(ad);
AttributeDescriptor descriptor = build.buildDescriptor(name);
attributeDescriptors.add(descriptor);
}
}
}
@Override
public SimpleFeatureType getSchema() {
return flattenedType;
}
public SimpleFeatureIterator features() {
return new FlatteningFeatureIterator(delegate.features(), flattenedType);
}
/**
* Flattens the features in a streaming fashion
*/
class FlatteningFeatureIterator implements SimpleFeatureIterator {
private SimpleFeatureIterator delegate;
private SimpleFeatureBuilder builder;
public FlatteningFeatureIterator(SimpleFeatureIterator delegate,
SimpleFeatureType flattenedType) {
this.delegate = delegate;
this.builder = new SimpleFeatureBuilder(flattenedType);
}
@Override
public void close() {
delegate.close();
}
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public SimpleFeature next() throws NoSuchElementException {
SimpleFeature next = delegate.next();
accumulateAttributes(next);
return builder.buildFeature(next.getID());
}
/**
* Recursively breaks down SimpleFeatures that may contain other features as attributes to
* accumulate simple attribute values to a List
*
* @param attributeValues The List of attribute values
* @param feature A SimpleFeature to harvest attributes
*/
private void accumulateAttributes(SimpleFeature feature) {
for (int i = 0; i < feature.getAttributes().size(); i++) {
Object attr = feature.getAttribute(i);
if (attr instanceof SimpleFeature) {
// go forth and harvest attrubutes
accumulateAttributes((SimpleFeature) attr);
} else {
builder.add(attr);
}
}
}
}
}