/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2014 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.dimension;
import java.io.IOException;
import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.DimensionInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.ows.AbstractDispatcherCallback;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.Request;
import org.geoserver.ows.kvp.ElevationKvpParser;
import org.geoserver.ows.kvp.TimeKvpParser;
import org.geoserver.ows.util.CaseInsensitiveMap;
import org.geoserver.platform.ExtensionPriority;
import org.geoserver.platform.Operation;
import org.geoserver.platform.ServiceException;
import org.geoserver.security.decorators.DecoratingFeatureTypeInfo;
import org.geoserver.wms.GetFeatureInfoRequest;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WMS;
import org.geoserver.wms.dimension.DefaultValueConfiguration.DefaultValuePolicy;
import org.geoserver.wms.dimension.impl.CoverageMaximumValueSelectionStrategyImpl;
import org.geoserver.wms.dimension.impl.CoverageMinimumValueSelectionStrategyImpl;
import org.geoserver.wms.dimension.impl.CoverageNearestValueSelectionStrategyImpl;
import org.geoserver.wms.dimension.impl.DimensionDefaultValueSelectionStrategyFactoryImpl;
import org.geoserver.wms.dimension.impl.FixedValueStrategyImpl;
import org.geotools.coverage.grid.io.DimensionDescriptor;
import org.geotools.coverage.grid.io.GranuleSource;
import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.view.DefaultView;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.feature.visitor.FeatureCalc;
import org.geotools.feature.visitor.MaxVisitor;
import org.geotools.feature.visitor.MinVisitor;
import org.geotools.feature.visitor.NearestVisitor;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.util.DateRange;
import org.geotools.util.NumberRange;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.GridCoverageReader;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.util.ProgressListener;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A {@link DimensionDefaultValueSelectionStrategyFactory} implementation using the dynamic default
* values configurations, is present
*
* @author Andrea Aime - GeoSolutions
*/
public class DynamicDefaultValueSelectionFactory extends AbstractDispatcherCallback implements
DimensionDefaultValueSelectionStrategyFactory, ExtensionPriority, ApplicationContextAware {
static final Logger LOGGER = Logging.getLogger(DynamicDefaultValueSelectionFactory.class);
static final ThreadLocal<Map<String, Map<String, Object>>> DYNAMIC_DEFAULTS = new ThreadLocal<Map<String, Map<String, Object>>>() {
protected java.util.Map<String, java.util.Map<String, Object>> initialValue() {
return new HashMap<String, Map<String, Object>>();
};
};
FilterFactory ff = CommonFactoryFinder.getFilterFactory();
DimensionDefaultValueSelectionStrategyFactory delegate;
WMS wms;
@Override
public DimensionDefaultValueSelectionStrategy getStrategy(ResourceInfo resource,
String dimensionName, DimensionInfo dimension) {
Request request = Dispatcher.REQUEST.get();
DefaultValueConfigurations config = getConfigurations(resource);
if (config != null
&& request != null
&& ("GetMap".equalsIgnoreCase(request.getRequest()) || "GetFeatureInfo"
.equalsIgnoreCase(request.getRequest()))) {
GetMapRequest getMap = getGetMap(request);
try {
Map<String, Object> defaults = getDefaultValues(resource, getMap, config);
String key = dimensionName;
if (dimensionName.startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)) {
key = dimensionName.substring(ResourceInfo.CUSTOM_DIMENSION_PREFIX.length());
}
if (defaults.containsKey(key)) {
Object defaultValue = defaults.get(key);
Object simple = getSimpleValue(defaultValue);
if (simple != null) {
return new DefaultFixedValueStrategyFactory()
.createFixedValueStrategy(simple);
}
}
} catch (IOException e) {
throw new ServiceException("Failed to setup dynamic dimension default values", e);
}
}
// was not a GetMap/GetFeatureInfo, or we did not have a dynamic default for this dimension
return delegate.getStrategy(resource, dimensionName, dimension);
}
/**
* Returns the default values configuration if present, not empty, and has at least a dynamic
* dimension to compute
*
* @param resource
* @return
*/
private DefaultValueConfigurations getConfigurations(ResourceInfo resource) {
DefaultValueConfigurations configurations = resource.getMetadata().get(
DefaultValueConfigurations.KEY, DefaultValueConfigurations.class);
if (configurations == null) {
return null;
}
List<DefaultValueConfiguration> list = configurations.getConfigurations();
if (list == null || list.isEmpty()) {
LOGGER.fine("Skipping dynamic dimension configuration as it is empty");
return null;
}
for (DefaultValueConfiguration config : list) {
if (config.getPolicy() != null && config.getPolicy() != DefaultValuePolicy.STANDARD) {
return configurations;
}
}
LOGGER.fine("Skipping dynamic dimension cconfiguration as all dimensions are "
+ "using the standard policy (e.g. there is nothing dynamic there");
return null;
}
private GetMapRequest getGetMap(Request request) {
Operation op = request.getOperation();
Object parsedRequest = op.getParameters()[0];
GetMapRequest getMap;
if (parsedRequest instanceof GetMapRequest) {
getMap = (GetMapRequest) parsedRequest;
} else if (parsedRequest instanceof GetFeatureInfoRequest) {
getMap = ((GetFeatureInfoRequest) parsedRequest).getGetMapRequest();
} else {
throw new IllegalArgumentException(
"Could not get a GetMapRequest out of the parsed request, the parsed request object was:_"
+ parsedRequest);
}
return getMap;
}
private Map<String, Object> getDefaultValues(ResourceInfo resource, GetMapRequest getMap,
DefaultValueConfigurations configurations) throws IOException {
Map<String, Map<String, Object>> defaults = DYNAMIC_DEFAULTS.get();
String resourceKey = resource.getId();
Map<String, Object> result = defaults.get(resourceKey);
if (result == null) {
result = buildDynamicDefaults(resource, getMap, configurations);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Computed the following dynamic dimension default values for layer "
+ resource.prefixedName() + ":\n" + result + "\nbased on configuration:\n"
+ configurations);
}
defaults.put(resourceKey, result);
}
return result;
}
@Override
public void finished(Request request) {
DYNAMIC_DEFAULTS.remove();
}
private Map<String, Object> buildDynamicDefaults(ResourceInfo resource, GetMapRequest getMap,
DefaultValueConfigurations configsBean) throws IOException {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Building dynamic defaults for resource " + resource.prefixedName());
}
List<DefaultValueConfiguration> configurations = filterConfigurations(configsBean, resource);
Map<String, Object> result;
result = new HashMap<String, Object>();
Map<String, List<? extends Object>> incompleteSpecs = new HashMap<String, List<? extends Object>>();
// lookup for user specified restrictions and dimensions that have a static default
for (Map.Entry<String, Serializable> entry : resource.getMetadata().entrySet()) {
String key = entry.getKey();
Serializable metadata = entry.getValue();
if (metadata instanceof DimensionInfo) {
// skip disabled dimensions
DimensionInfo di = (DimensionInfo) metadata;
if (!di.isEnabled()) {
continue;
}
// get the dimension name
String dimensionName;
if (key.startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)) {
dimensionName = key.substring(ResourceInfo.CUSTOM_DIMENSION_PREFIX.length());
} else {
dimensionName = key;
}
// check if the values were provided already in the request, we are
// going to use them for domain restriction
boolean foundExplicitDefault = false;
if (ResourceInfo.TIME.equals(dimensionName)
&& getMap.getRawKvp().get("time") != null) {
List<Object> times = getMap.getTime();
addExplicitValues(dimensionName, times, result, incompleteSpecs);
foundExplicitDefault = true;
} else if (ResourceInfo.ELEVATION.equals(dimensionName)
&& getMap.getRawKvp().get("elevation") != null) {
List<Object> elevations = getMap.getElevation();
addExplicitValues(dimensionName, elevations, result, incompleteSpecs);
foundExplicitDefault = true;
} else if (getMap.getRawKvp().get("dim_" + dimensionName) != null) {
List<String> customDimensions = getMap.getCustomDimension(dimensionName);
addExplicitValues(dimensionName, customDimensions, result, incompleteSpecs);
foundExplicitDefault = true;
}
if (!foundExplicitDefault) {
if (!hasDynamicConfiguration(configurations, dimensionName)) {
DimensionDefaultValueSelectionStrategy strategy = delegate.getStrategy(
resource, key, di);
if (strategy == null) {
LOGGER.warning("Skipping static setting of default dimension value for dimension "
+ dimensionName
+ " in layer "
+ resource.prefixedName()
+ " as the default value strategy could not be found");
} else {
Object staticDefault = strategy.getDefaultValue(resource,
dimensionName, di, getDimensionClass(dimensionName));
if (staticDefault != null) {
result.put(dimensionName, wrapIntoList(staticDefault));
} else {
LOGGER.warning("Skipping static setting of default dimension value for dimension "
+ dimensionName
+ " in layer "
+ resource.prefixedName()
+ " as the default value strategy "
+ strategy
+ "returned a null value");
}
}
}
}
}
}
// if there are incomplete user value specifications, resolve them in the same order as the
// dynamic defaults
if (!incompleteSpecs.isEmpty()) {
for (DefaultValueConfiguration config : configurations) {
String dimensionName = config.getDimension();
if (!incompleteSpecs.containsKey(dimensionName)) {
continue;
}
List<Object> values = (List<Object>) incompleteSpecs.remove(dimensionName);
List<Object> defaulted = new ArrayList<Object>(values.size());
for (int i = 0; i < values.size(); i++) {
Object v = values.get(i);
if (v != null) {
defaulted.add(v);
} else {
Object dynamicValue = getDynamicDefault(resource, result, config,
dimensionName);
if (dynamicValue != null) {
defaulted.add(dynamicValue);
}
}
result.put(dimensionName, defaulted);
}
}
// the ones that have no dynamc behavior get copied straight
result.putAll(incompleteSpecs);
}
// now go for the ones having dynamic defaults
for (DefaultValueConfiguration config : configurations) {
String dimensionName = config.getDimension();
// do we have explicit defaults already?
if (result.containsKey(dimensionName)) {
continue;
} else {
// compute the dynamic default
Object value = getDynamicDefault(resource, result, config, dimensionName);
if (value != null) {
result.put(dimensionName, value);
}
}
}
return result;
}
private boolean hasDynamicConfiguration(List<DefaultValueConfiguration> configurations,
String dimensionName) {
for (DefaultValueConfiguration config : configurations) {
if (dimensionName.equals(config.getDimension())) {
return true;
}
}
return false;
}
private List<DefaultValueConfiguration> filterConfigurations(
DefaultValueConfigurations configsBean, ResourceInfo resource) {
List<DefaultValueConfiguration> configs = configsBean.getConfigurations();
List<DefaultValueConfiguration> result = new ArrayList<DefaultValueConfiguration>(
configs.size());
MetadataMap metadata = resource.getMetadata();
for (DefaultValueConfiguration config : configs) {
String key = getDimensionMetadataKey(config.getDimension());
DimensionInfo di = metadata.get(key, DimensionInfo.class);
if (di == null) {
LOGGER.warning("Skipping dynamic default configuration for dimension "
+ config.getDimension() + " as the base dimension configuration is missing");
} else if (!di.isEnabled()) {
LOGGER.warning("Skipping dynamic default configuration for dimension "
+ config.getDimension() + " as the dimension is not enabled");
} else {
result.add(config);
}
}
return result;
}
/**
* Separates values provided by the user that do not need defaulting, from those where the user
* asked explicitly for a default value
*
* @param dimensionName
* @param values
* @param completeSpecs
* @param incompleteSpecs
*/
private void addExplicitValues(String dimensionName, List<? extends Object> values,
Map<String, Object> completeSpecs, Map<String, List<? extends Object>> incompleteSpecs) {
if (values.contains(null)) {
incompleteSpecs.put(dimensionName, values);
} else {
completeSpecs.put(dimensionName, values);
}
}
private Object getDynamicDefault(ResourceInfo resource, Map<String, Object> result,
DefaultValueConfiguration config, String dimensionName) throws IOException {
// get the dimension info
DimensionInfo di = resource.getMetadata().get(
getDimensionMetadataKey(config.getDimension()), DimensionInfo.class);
// ok, let's see what policy we have here
Class<?> dimensionClass = getDimensionClass(config.getDimension());
if (config.policy == DefaultValuePolicy.STANDARD) {
if (di == null) {
return null;
} else {
// we fetch the value anyways so that we can run domain restrictions later
DimensionDefaultValueSelectionStrategy strategy = delegate.getStrategy(resource,
config.dimension, di);
Object value = strategy
.getDefaultValue(resource, dimensionName, di, dimensionClass);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Computed default value for dimension " + dimensionName
+ " using standard policy " + strategy + " which resulted in: " + value);
}
return value;
}
} else if (config.policy == DefaultValuePolicy.EXPRESSION) {
return applyExpression(result, config, dimensionName, dimensionClass);
} else if (config.policy == DefaultValuePolicy.LIMIT_DOMAIN) {
if (resource instanceof FeatureTypeInfo) {
Object value = getVectorDefaultValue((FeatureTypeInfo) resource, config, result);
return value;
} else if (resource instanceof CoverageInfo) {
DimensionDefaultValueSelectionStrategy delegateStrategy = delegate.getStrategy(
resource, getDimensionMetadataKey(config.getDimension()), di);
Object value = getRasterDefaultValue((CoverageInfo) resource, config, result,
delegateStrategy);
return value;
} else {
throw new IllegalArgumentException(
"Don't know how to handle domain restriction for layers of type "
+ resource.getClass());
}
}
return null;
}
private Object applyExpression(Map<String, Object> result, DefaultValueConfiguration config,
String dimensionName, Class<?> dimensionClass) {
Expression expression;
String expressionStr = config.getDefaultValueExpression();
try {
expression = ECQL.toExpression(expressionStr);
} catch (CQLException e) {
throw new ServiceException("Failed to parse default value expression " + expressionStr,
e);
}
SimpleFeature values = buildSampleFeature(result);
String strValue = expression.evaluate(values, String.class);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Computed default value for dimension " + dimensionName
+ " using expression " + expression + " returned value " + strValue);
}
try {
Object value;
if (strValue == null) {
value = null;
} else if (Date.class.isAssignableFrom(dimensionClass)) {
value = new TimeKvpParser("whatever").parse(strValue);
} else if (Double.class.isAssignableFrom(dimensionClass)) {
value = new ElevationKvpParser("whatever").parse(strValue);
} else {
value = strValue;
}
value = getSimpleValue(value);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Computed value " + strValue + " got parsed to " + value);
}
return value;
} catch (ParseException e) {
throw new ServiceException(
"Failed to parse value returned from dynamic default value expression: "
+ strValue, e);
}
}
/**
* Dimension values are often wrapped in list and/or using ranges, unwrap them to get a single
* value
*
* @param value
* @return
*/
private Object getSimpleValue(Object value) {
if (value instanceof List) {
List list = (List) value;
if (list.isEmpty()) {
value = null;
} else {
value = list.get(0);
}
}
if (value instanceof DateRange) {
value = ((DateRange) value).getMinValue();
} else if (value instanceof NumberRange) {
value = ((NumberRange) value).getMinimum();
}
return value;
}
private Object getRasterDefaultValue(CoverageInfo resource, DefaultValueConfiguration config,
Map<String, Object> restrictions,
DimensionDefaultValueSelectionStrategy delegateStrategy) throws IOException {
String dimensionName = config.getDimension();
DimensionInfo di = resource.getMetadata().get(
getDimensionMetadataKey(config.getDimension()), DimensionInfo.class);
// check if it's a fixed strategy, simple case, no domain restriction needed
if (delegateStrategy instanceof FixedValueStrategyImpl) {
Class<?> dimensionClass = getDimensionClass(config.getDimension());
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Dynamic domain restriction on dimension " + dimensionName
+ " in layer " + resource.prefixedName()
+ " not possible, the default value strategy is a fixed value one");
}
return delegateStrategy.getDefaultValue(resource, dimensionName, di, dimensionClass);
}
// ok, grab the reader, and check it's a structured one
GridCoverageReader genericReader = resource.getGridCoverageReader(null, null);
if (!(genericReader instanceof StructuredGridCoverage2DReader)) {
throw new IllegalStateException(
"Cannot perform dynaminc domain restriction unless the reader is a structured one");
}
// we have a descriptor, now we need to find the association between the exposed
// dimension names and the granule source attributes
StructuredGridCoverage2DReader reader = (StructuredGridCoverage2DReader) genericReader;
String coverageName = resource.getNativeCoverageName();
if (coverageName == null) {
coverageName = reader.getGridCoverageNames()[0];
}
Map<String, DimensionDescriptor> descriptors = getDimensionDescriptors(reader, coverageName);
DimensionFilterBuilder builder = new DimensionFilterBuilder(ff);
for (Map.Entry<String, Object> entry : restrictions.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
List<Object> values = wrapIntoList(value);
DimensionDescriptor descriptor = descriptors.get(name);
if (descriptor == null) {
throw new ServiceException("Could not find dimension " + name
+ " in coverage reader backing " + resource.prefixedName());
}
builder.appendFilters(descriptor.getStartAttribute(), descriptor.getEndAttribute(),
values);
}
Filter domainRestriction = builder.getFilter();
// get the restricted domain
GranuleSource granules = reader.getGranules(coverageName, true);
Query q = new Query(granules.getSchema().getTypeName(), domainRestriction);
SimpleFeatureCollection fc = granules.getGranules(q);
DimensionDescriptor dd = descriptors.get(dimensionName);
if (dd == null) {
throw new ServiceException("Could not find dimension " + dimensionName
+ " in coverage reader backing " + resource.prefixedName());
}
FeatureCalc calc = getFeatureCalcForStrategy(delegateStrategy, dd);
fc.accepts(calc, null);
Object result = calc.getResult().getValue();
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Computed default value for " + dimensionName + " in layer "
+ resource.prefixedName() + " using " + domainRestriction
+ " to limit the domain resulted in value: " + result);
}
return result;
}
private FeatureCalc getFeatureCalcForStrategy(
DimensionDefaultValueSelectionStrategy delegateStrategy, DimensionDescriptor dd) {
String sa = dd.getStartAttribute();
String ea = dd.getEndAttribute();
if (delegateStrategy instanceof CoverageMaximumValueSelectionStrategyImpl) {
if (ea != null) {
return new MaxVisitor(ea);
} else {
return new MaxVisitor(sa);
}
} else if (delegateStrategy instanceof CoverageMinimumValueSelectionStrategyImpl) {
return new MinVisitor(sa);
} else if (delegateStrategy instanceof CoverageNearestValueSelectionStrategyImpl) {
CoverageNearestValueSelectionStrategyImpl impl = (CoverageNearestValueSelectionStrategyImpl) delegateStrategy;
Object targetValue = impl.getTargetValue();
return new NearestVisitor(ff.property(sa), targetValue);
} else {
throw new ServiceException("Don't konw how to restrict the domain for strategy "
+ delegateStrategy);
}
}
private Map<String, DimensionDescriptor> getDimensionDescriptors(
StructuredGridCoverage2DReader reader, String coverageName) throws IOException {
Map<String, DimensionDescriptor> result = new HashMap<String, DimensionDescriptor>();
List<DimensionDescriptor> dimensionDescriptors = reader
.getDimensionDescriptors(coverageName);
for (DimensionDescriptor dd : dimensionDescriptors) {
result.put(dd.getName(), dd);
}
return new CaseInsensitiveMap(result);
}
/**
* Applies the normal policy, but restricted to the
*
* @param resource
* @param config
* @param restrictions
* @return
* @throws IOException
*/
private Object getVectorDefaultValue(FeatureTypeInfo resource,
DefaultValueConfiguration config, Map<String, Object> restrictions) throws IOException {
DimensionInfo di = resource.getMetadata().get(
getDimensionMetadataKey(config.getDimension()), DimensionInfo.class);
DimensionDefaultValueSelectionStrategy strategy = delegate.getStrategy(resource,
config.getDimension(), di);
// vector data only support time and elevation now
Object time = restrictions.get(ResourceInfo.TIME);
List<Object> times = wrapIntoList(time);
Object elevation = restrictions.get(ResourceInfo.ELEVATION);
List<Object> elevations = wrapIntoList(elevation);
// reuse the same logic as dimension selection to restrict the domain of possible values
final Filter filter = wms.getTimeElevationToFilter(times, elevations, resource);
ResourceInfo restrictedResource;
if (filter != null && !Filter.INCLUDE.equals(filter)) {
restrictedResource = new DecoratingFeatureTypeInfo(resource) {
@Override
public FeatureSource getFeatureSource(ProgressListener listener, Hints hints)
throws IOException {
FeatureSource fs = super.getFeatureSource(listener, hints);
if (!(fs instanceof SimpleFeatureSource)) {
throw new IllegalStateException(
"Cannot apply dynamic dimension restrictions to complex features");
}
SimpleFeatureSource simpleSource = (SimpleFeatureSource) fs;
try {
return new DefaultView(simpleSource, new Query(simpleSource.getSchema()
.getTypeName(), filter));
} catch (SchemaException e) {
throw new IOException("Failed to restrict the domain");
}
}
};
} else {
restrictedResource = resource;
}
Class<?> dimensionClass = getDimensionClass(config.getDimension());
Object result = strategy.getDefaultValue(restrictedResource, config.getDimension(), di,
dimensionClass);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Computing default value for" + config.getDimension() + " in layer "
+ resource.prefixedName()
+ " using the following filter to restrict the domain: " + filter);
}
return result;
}
/**
* If the object is not a List, it wraps it into one
*
* @param value
* @return
*/
private List<Object> wrapIntoList(Object value) {
List<Object> values;
if (value == null) {
return null;
} else if (value instanceof List) {
values = (List<Object>) value;
} else {
values = new ArrayList<Object>();
values.add(value);
}
return values;
}
private SimpleFeature buildSampleFeature(Map<String, Object> values) {
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
for (String dimension : values.keySet()) {
tb.add(dimension, Object.class);
}
tb.setName("DimensionValues");
SimpleFeatureType schema = tb.buildFeatureType();
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(schema);
for (Map.Entry<String, Object> entry : values.entrySet()) {
Object value = entry.getValue();
value = getSimpleValue(value);
fb.set(entry.getKey(), value);
}
return fb.buildFeature(null);
}
public String getDimensionMetadataKey(String dimension) {
if (ResourceInfo.TIME.equals(dimension) || ResourceInfo.ELEVATION.equals(dimension)) {
return dimension;
} else {
return ResourceInfo.CUSTOM_DIMENSION_PREFIX + dimension;
}
}
public Class<?> getDimensionClass(String dimension) {
if (ResourceInfo.TIME.equals(dimension)) {
return Date.class;
} else if (ResourceInfo.ELEVATION.equals(dimension)) {
return Double.class;
} else {
return String.class;
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// get the normal dimension evaluator
delegate = applicationContext
.getBean(DimensionDefaultValueSelectionStrategyFactoryImpl.class);
// avoid dependency loop
wms = applicationContext.getBean(WMS.class);
}
@Override
public int getPriority() {
// pick an average value, we allow for further overrides if needs be
return (ExtensionPriority.HIGHEST + ExtensionPriority.LOWEST) / 2;
}
}