/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2012 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.csw.store.internal;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogFacade;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.Info;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.impl.ModificationProxy;
import org.geoserver.config.GeoServer;
import org.geoserver.csw.CSWInfo;
import org.geoserver.csw.DirectDownloadSettings;
import org.geoserver.csw.feature.sort.CatalogComparatorFactory;
import org.geoserver.csw.records.GenericRecordBuilder;
import org.geoserver.csw.records.RecordBuilder;
import org.geoserver.csw.records.RecordDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.sort.SortBy;
import org.geoserver.platform.GeoServerExtensions;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.logging.Logging;
import org.opengis.feature.Feature;
/**
* Internal Catalog Store Feature Iterator
*
* @author Niels Charlier
*/
class CatalogStoreFeatureIterator implements Iterator<Feature> {
protected static final FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
static final Logger LOGGER = Logging.getLogger(CatalogStoreFeatureIterator.class);
protected RecordBuilder builder;
protected Iterator<ResourceInfo> layerIt;
protected ResourceInfo nextResource;
protected Iterator<LayerGroupInfo> layerGroupIt;
protected LayerGroupInfo nextLayerGroup;
protected CatalogStoreMapping mapping;
protected CatalogFacade catalogFacade;
protected Map<String, String> interpolationProperties = new HashMap<String, String>();
protected int offset;
protected int count;
protected SortBy[] sortOrder;
protected Filter filter;
protected int index;
protected Comparator<Info> comparator;
private RecordDescriptor recordDescriptor;
public CatalogStoreFeatureIterator(int offset, int count, SortBy[] sortOrder, Filter filter, Catalog catalog, CatalogStoreMapping mapping, RecordDescriptor recordDescriptor, Map<String, String> interpolationProperties) {
this.interpolationProperties = interpolationProperties;
this.offset = offset;
this.count = count;
this.sortOrder = sortOrder;
this.filter = filter;
catalogFacade = catalog.getFacade();
this.mapping = mapping;
Filter advertised = ff.equals(ff.property("advertised"), ff.literal(true));
layerIt = catalogFacade.list(ResourceInfo.class, ff.and(filter, advertised), null, null, sortOrder);
nextLayer();
layerGroupIt = catalogFacade.list(LayerGroupInfo.class, filter, null, null, sortOrder);
nextLayerGroup();
comparator = sortOrder==null || sortOrder.length==0 ? null : CatalogComparatorFactory.buildComparator(sortOrder);
index = 0;
while (index < offset && hasNext()) {
nextInternal();
}
this.recordDescriptor = recordDescriptor;
builder = new GenericRecordBuilder(recordDescriptor);
}
@Override
public boolean hasNext() {
return index < offset+count && (nextResource != null || nextLayerGroup != null );
}
public ResourceInfo nextLayer() {
ResourceInfo result = nextResource;
if (layerIt.hasNext()) {
nextResource = layerIt.next();
} else {
nextResource = null;
}
return result;
}
public LayerGroupInfo nextLayerGroup() {
LayerGroupInfo result = nextLayerGroup;
if (layerGroupIt.hasNext()) {
nextLayerGroup = layerGroupIt.next();
} else {
nextLayerGroup = null;
}
return result;
}
public CatalogInfo nextInternal() {
if (!hasNext()) {
throw new NoSuchElementException("No more records to retrieve");
}
index++;
if (nextResource == null) {
return nextLayerGroup();
}
if (nextLayerGroup == null) {
return nextLayer();
}
if (comparator == null) {
return nextLayer();
}
int c = comparator.compare(nextResource, nextLayerGroup);
if (c <= 0) {
return nextLayer();
} else {
return nextLayerGroup();
}
}
@Override
public Feature next() {
CatalogInfo info = nextInternal();
if (info instanceof ResourceInfo) {
return convertToFeature( (ResourceInfo) info );
} else {
return convertToFeature( (LayerGroupInfo) info );
}
}
private String mapProperties(CatalogInfo resource) {
String id = null;
for (CatalogStoreMapping.CatalogStoreMappingElement mappingElement: mapping.elements()) {
Object value = mappingElement.getContent().evaluate(resource);
if (value != null) {
if (value instanceof Collection) {
((Collection)value).removeAll(Collections.singleton(null));
if (((Collection)value).size() > 0) {
String[] elements = new String[((Collection) value).size()];
int i = 0;
for (Object element : (Collection) value) {
elements[i++] = interpolate(interpolationProperties, element.toString());
}
builder.addElement(mappingElement.getKey(), mappingElement.getSplitIndex(), elements);
}
} else {
builder.addElement(mappingElement.getKey(), interpolate(interpolationProperties, value.toString()));
}
if (mappingElement == mapping.getIdentifierElement()) {
id = interpolate(interpolationProperties, value.toString());
}
}
}
return id;
}
/**
* Get a {@link FeatureCustomizer} for this info.
* @param info
*
*/
private FeatureCustomizer getCustomizer(CatalogInfo info) {
FeatureCustomizer customizer = null;
// DirectDownload capability is only checked for Coverage layers
if (info instanceof CoverageInfo) {
CoverageInfo coverageInfo = ((CoverageInfo) info);
MetadataMap metadata = coverageInfo.getMetadata();
boolean directDownloadEnabled = false;
// Look for specific settings for this layer
DirectDownloadSettings settings = DirectDownloadSettings
.getSettingsFromMetadata(metadata, GeoServerExtensions.bean(GeoServer.class).getService(
CSWInfo.class));
if (settings != null) {
directDownloadEnabled = settings.isDirectDownloadEnabled();
}
if (directDownloadEnabled) {
String typeName = recordDescriptor.getFeatureType().getName().getLocalPart();
// customizer = FeatureCustomizer.getCustomizer(typeName);
customizer = FeatureCustomizer.getCustomizer(typeName);
if (customizer == null) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.warning("No Mapping customizer have been found for " + typeName
+ ". Mapping customizations will not be made");
}
}
}
}
return customizer;
}
private Feature convertToFeature(ResourceInfo resource) {
String id = mapProperties(resource);
// move on to the bounding boxes
if (mapping.isIncludeEnvelope()) {
ReferencedEnvelope bbox = null;
try {
bbox = resource.boundingBox();
} catch (Exception e) {
LOGGER.log(Level.INFO, "Failed to parse original record bbox");
}
if (bbox != null) {
builder.addBoundingBox(bbox);
}
}
Feature feature = builder.build(id);
FeatureCustomizer customizer = getCustomizer(resource);
if (customizer != null) {
customizer.customizeFeature(feature, ModificationProxy.unwrap(resource));
}
return feature;
}
private Feature convertToFeature(LayerGroupInfo resource) {
String id = mapProperties(resource);
// move on to the bounding boxes
if (mapping.isIncludeEnvelope()) {
ReferencedEnvelope bbox = null;
bbox = resource.getBounds();
if (bbox != null) {
builder.addBoundingBox(bbox);
}
}
return builder.build(id);
}
@Override
public void remove() {
throw new UnsupportedOperationException("This iterator is read only");
}
/**
* Pattern to match a property to be substituted. Note the reluctant quantifier.
*/
protected static final Pattern PROPERTY_INTERPOLATION_PATTERN = Pattern.compile("\\$\\{(.+?)\\}");
protected static String interpolate(Map<String, String> properties, String input) {
String result = input;
Matcher matcher = PROPERTY_INTERPOLATION_PATTERN.matcher(result);
while (matcher.find()) {
String propertyName = matcher.group(1);
String propertyValue = (String) properties.get(propertyName);
if (propertyValue == null) {
throw new RuntimeException("Interpolation failed for missing property "
+ propertyName);
} else {
result = result.replace(matcher.group(), propertyValue).trim();
matcher.reset(result);
}
}
return result;
}
}