Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) 2013-2019 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* - Apache License, version 2.0
* - Apache Software License, version 1.0
* - GNU Lesser General Public License, version 3
* - Mozilla Public License, versions 1.0, 1.1 and 2.0
* - Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public License
* version 2 and the aforementioned licenses.
*
* This program 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 General Public License
* for more details.
*/
package org.n52.io.request;
import static java.util.stream.Collectors.toSet;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormatter;
import org.n52.io.IntervalWithTimeZone;
import org.n52.io.IoParseException;
import org.n52.io.crs.BoundingBox;
import org.n52.io.crs.CRSUtils;
import org.n52.io.geojson.old.GeojsonPoint;
import org.n52.io.response.PlatformType;
import org.n52.io.response.dataset.ValueType;
import org.n52.io.style.LineStyle;
import org.n52.io.style.Style;
import org.n52.shetland.ogc.filter.Filter;
import org.n52.svalbard.decode.exception.DecodingException;
import org.n52.svalbard.odata.ODataFesParser;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.vividsolutions.jts.geom.Point;
public final class IoParameters implements Parameters {
private static final Logger LOGGER = LoggerFactory.getLogger(IoParameters.class);
private static final String DEFAULT_CONFIG_FILE = "config-general.json";
// TODO use global object mapper
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final ODataFesParser ODATA_PARSER = new ODataFesParser();
private final MultiValueMap query;
private final FilterResolver filterResolver;
private boolean behaveBackwardsCompatible;
private BiConsumer parseExceptionHandle;
protected IoParameters() {
this(Collections. emptyMap());
}
protected IoParameters(IoParameters other) {
this(other.query);
}
protected IoParameters(Map queryParameters) {
this(queryParameters, (File) null);
}
protected IoParameters(Map queryParameters, File defaults) {
this(defaults);
query.setAll(mergeToLowerCasedKeys(queryParameters));
}
protected IoParameters(MultiValueMap queryParameters) {
this(queryParameters, (File) null);
}
protected IoParameters(MultiValueMap queryParameters, File defaults) {
this(defaults);
if (queryParameters != null) {
query.putAll(mergeToLowerCasedKeys(queryParameters));
}
}
private IoParameters(File defaultConfig) {
MultiValueMap config = new LinkedMultiValueMap<>();
config.setAll(readDefaultConfig(defaultConfig));
query = mergeToLowerCasedKeys(config);
filterResolver = new FilterResolver(this);
}
private Map readDefaultConfig(File config) {
try (InputStream stream = config == null
? getDefaultConfigFile()
: new FileInputStream(config)) {
return OBJECT_MAPPER.readValue(stream, TypeFactory.defaultInstance()
.constructMapLikeType(HashMap.class,
String.class,
JsonNode.class));
} catch (IOException e) {
LOGGER.info("Could not load '{}'. Using empty config.", DEFAULT_CONFIG_FILE, e);
return new HashMap<>();
}
}
private static InputStream getDefaultConfigFile() {
try {
Path path = Paths.get(IoParameters.class.getResource("/")
.toURI());
File config = path.resolve(DEFAULT_CONFIG_FILE)
.toFile();
final String fallbackPath = "/" + DEFAULT_CONFIG_FILE;
return config.exists()
? new FileInputStream(config)
: IoParameters.class.getClassLoader()
.getResourceAsStream(fallbackPath);
} catch (URISyntaxException | IOException e) {
LOGGER.debug("Could not find default config under '{}'", DEFAULT_CONFIG_FILE, e);
return null;
}
}
public boolean shallBehaveBackwardsCompatible() {
return behaveBackwardsCompatible;
}
private IoParameters setBehaveBackwardsCompatible(boolean behaveBackwardsCompatible) {
this.behaveBackwardsCompatible = behaveBackwardsCompatible;
return this;
}
public IoParameters setParseExceptionHandle(BiConsumer handle) {
this.parseExceptionHandle = handle;
return this;
}
private BiConsumer getParseExceptionHandle() {
return parseExceptionHandle;
}
/**
* @return the value of {@value #OFFSET} parameter. If not present, the default {@value #DEFAULT_OFFSET}
* is returned.
* @throws IoParseException
* if parameter could not be parsed.
*/
public int getOffset() {
return getAsInteger(OFFSET, DEFAULT_OFFSET);
}
/**
* @return the value of {@value #LIMIT} parameter. If not present, the default {@value #DEFAULT_LIMIT} is
* returned.
* @throws IoParseException
* if parameter could not be parsed.
*/
public int getLimit() {
return getAsInteger(LIMIT, DEFAULT_LIMIT);
}
/**
* @return the requested chart width in pixels or the default {@value #DEFAULT_WIDTH}.
* @throws IoParseException
* if parsing parameter fails.
*/
public int getWidth() {
return getAsInteger(WIDTH, DEFAULT_WIDTH);
}
/**
* Returns the requested chart height in pixels.
*
* @return the requested chart height in pixels or the default {@value #DEFAULT_HEIGHT}.
* @throws IoParseException
* if parsing parameter fails.
*/
public int getHeight() {
return getAsInteger(HEIGHT, DEFAULT_HEIGHT);
}
/**
* Indicates if rendered chart shall be returned as Base64 encoded string.
*
* @return the value of parameter {@value #BASE_64} or the default {@value #DEFAULT_BASE_64}.
* @throws IoParseException
* if parsing parameter fails.
*/
public boolean isBase64() {
return getAsBoolean(BASE_64, DEFAULT_BASE_64);
}
/**
* @return true if timeseries chart shall include a background grid.
* @throws IoParseException
* if parsing parameter fails.
*/
public boolean isGrid() {
return getAsBoolean(GRID, DEFAULT_GRID);
}
/**
* @return true if timeseries data shall be generalized.
* @throws IoParseException
* if parsing parameter fails.
*/
public boolean isGeneralize() {
return getAsBoolean(GENERALIZE, DEFAULT_GENERALIZE);
}
/**
* @return true if a legend shall be included when rendering a chart, false
* otherwise.
* @throws IoParseException
* if parsing parameter fails.
*/
public boolean isLegend() {
return getAsBoolean(LEGEND, DEFAULT_LEGEND);
}
/**
* @return the value of {@value #LOCALE} parameter. If not present, the default {@value #DEFAULT_LOCALE}
* is returned.
* @throws IoParseException
* if parsing parameter fails.
*/
public String getLocale() {
return getAsString(LOCALE, DEFAULT_LOCALE);
}
/**
* @return the value of {@value #STYLE} parameter. If not present, the default styles are returned.
* @throws IoParseException
* if parsing parameter fails.
*/
public StyleProperties getSingleStyle() {
return containsParameter(STYLE)
? parseStyleProperties()
: StyleProperties.createDefaults();
}
/**
* @return the value of {@value #STYLES} parameter.
* @throws IoParseException
* if parsing parameter fails.
*/
public Map getReferencedStyles() {
return containsParameter(STYLES)
? parseMultipleStyleProperties()
: Collections.emptyMap();
}
/**
* @return in case of either style for single or styles for multiple datasets are
* available.
*/
public boolean hasStyles() {
return getSingleStyle() != null || !getReferencedStyles().isEmpty();
}
/**
* Creates a generic {@link StyleProperties} instance which can be used to create more concrete
* {@link Style}s. For example use {@link LineStyle#createLineStyle(StyleProperties)} which gives you a
* style view which can be used for lines.
*
* @return a parsed {@link StyleProperties} instance.
* @throws IoParseException
* if parsing parameter fails.
*/
private StyleProperties parseStyleProperties() {
return handleJsonValueParseException(STYLE, StyleProperties.class, this::parseJson);
}
private Map parseMultipleStyleProperties() {
return handleJsonValueParseException(STYLES,
new TypeReference>() {},
this::parseJson);
}
public String getFormat() {
return getAsString(FORMAT, DEFAULT_FORMAT);
}
public boolean isSetRawFormat() {
return containsParameter(RAW_FORMAT);
}
public String getRawFormat() {
if (isSetRawFormat()) {
final JsonNode value = query.getFirst(RAW_FORMAT);
return value != null
? value.asText()
: null;
}
return null;
}
public String getTimeFormat() {
return getAsString(TIME_FORMAT, DEFAULT_TIME_FORMAT);
}
/**
* @return the value of {@value #TIMESPAN} parameter. If not present, the default timespan is returned.
* @throws IoParseException
* if timespan could not be parsed.
*/
public IntervalWithTimeZone getTimespan() {
return containsParameter(TIMESPAN)
? validateTimespan(getNormalizedTimespan())
: createDefaultTimespan();
}
private String getNormalizedTimespan() {
return getNormalizedTimespan(null);
}
protected String getNormalizedTimespan(DateTimeFormatter dateFormat) {
String parameterValue = getAsString(TIMESPAN);
String now = dateFormat == null
? new DateTime().toString()
: dateFormat.print(new DateTime());
return parameterValue.replaceAll("(?i)now", now);
}
public static IntervalWithTimeZone createDefaultTimespan() {
DateTime now = new DateTime();
// TODO make this configurable
DateTime lastWeek = now.minusWeeks(1);
String interval = lastWeek.toString()
.concat("/")
.concat(now.toString());
return new IntervalWithTimeZone(interval);
}
private IntervalWithTimeZone validateTimespan(String timespan) {
try {
return new IntervalWithTimeZone(timespan);
} catch (IllegalArgumentException e) {
IoParseException ex = new IoParseException(e.getMessage(), e);
throw ex.addHint("Valid timespans have to be in ISO8601 period format.")
.addHint("Valid examples: 'PT6H/2013-08-13TZ' or '2013-07-13TZ/2013-08-13TZ'.");
}
}
public String getOutputTimezone() {
if (!containsParameter(OUTPUT_TIMEZONE)) {
return DEFAULT_OUTPUT_TIMEZONE;
}
String timezone = getAsString(OUTPUT_TIMEZONE);
Set availableIDs = DateTimeZone.getAvailableIDs();
DateTimeZone zone = availableIDs.contains(timezone)
? DateTimeZone.forID(timezone)
: DateTimeZone.UTC;
return zone.toString();
}
public Instant getResultTime() {
if (!containsParameter(RESULTTIME)) {
return null;
}
return validateTimestamp(getAsString(RESULTTIME));
}
public boolean shallClassifyByResultTimes() {
return isAllResultTimes() || !getResultTimes().isEmpty();
}
public boolean isAllResultTimes() {
Set resultTimes = csvToLowerCasedSet(getAsString(RESULTTIMES));
return resultTimes.contains(RESULT_TIMES_VALUE_ALL);
}
/**
* parses csv parameter {@link Parameters#RESULTTIMES} to a set of values. Validates each result time
* value after removing special value all. Use {@link #isAllResultTimes()} to check if client
* requests all result times available.
*
* @return a (probably empty) set of result times.
*/
public Set getResultTimes() {
Set resultTimes = csvToSet(getAsString(RESULTTIMES));
if (resultTimes.contains(RESULT_TIMES_VALUE_ALL)) {
resultTimes.remove(RESULT_TIMES_VALUE_ALL);
}
resultTimes.stream().forEach(this::validateTimestamp);
Instant fromOldParameter = getResultTime();
if (fromOldParameter != null) {
resultTimes.add(fromOldParameter.toString());
}
return resultTimes;
}
private Instant validateTimestamp(String timestamp) {
return handleSimpleValueParseException(timestamp, Instant::parse);
}
public Optional> getODataFilter() {
if (!containsParameter(ODATA_FILTER)) {
return Optional.empty();
}
String parameter = getAsString(ODATA_FILTER);
if (parameter.trim().isEmpty()) {
return Optional.empty();
}
try {
return Optional.ofNullable(ODATA_PARSER.decode(parameter));
} catch (DecodingException ex) {
handleIoParseException(ODATA_FILTER, createIoParseException(ODATA_FILTER, ex));
return Optional.empty();
}
}
/**
* @return the category filter
* @deprecated use {@link #getCategories()}
*/
@Deprecated
public String getCategory() {
return getAsString(CATEGORY);
}
/**
* @return the service filter
* @deprecated use {@link #getServices()}
*/
@Deprecated
public String getService() {
return getAsString(SERVICE);
}
/**
* @return the offering filter
* @deprecated use {@link #getOfferings()}
*/
@Deprecated
public String getOffering() {
return getAsString(OFFERING);
}
/**
* @return the feature filter
* @deprecated use {@link #getFeatures()}
*/
@Deprecated
public String getFeature() {
return getAsString(FEATURE);
}
/**
* @return the procedure filter
* @deprecated use {@link #getProcedures()}
*/
@Deprecated
public String getProcedure() {
return getAsString(PROCEDURE);
}
/**
* @return the phenomenon filter
* @deprecated use {@link #getPhenomena()}
*/
@Deprecated
public String getPhenomenon() {
return getAsString(PHENOMENON);
}
@Deprecated
public String getStation() {
return getAsString(STATION);
}
public Set getCategories() {
Set values = getValuesOf(CATEGORIES);
values.addAll(getValuesOf(CATEGORY));
return values;
}
public Set getServices() {
Set values = getValuesOf(SERVICES);
values.addAll(getValuesOf(SERVICE));
return values;
}
public Set getOfferings() {
Set values = getValuesOf(OFFERINGS);
values.addAll(getValuesOf(OFFERING));
return values;
}
public Set getFeatures() {
Set values = getValuesOf(FEATURES);
values.addAll(getValuesOf(FEATURE));
return values;
}
public Set getProcedures() {
Set values = getValuesOf(PROCEDURES);
values.addAll(getValuesOf(PROCEDURE));
return values;
}
public Set getPhenomena() {
Set values = getValuesOf(PHENOMENA);
values.addAll(getValuesOf(PHENOMENON));
return values;
}
public Set getStations() {
Set values = getValuesOf(STATIONS);
values.addAll(getValuesOf(STATION));
return values;
}
public Set getPlatforms() {
return getValuesOf(PLATFORMS);
}
public Set getTimeseries() {
return getSeries();
}
public Set getSeries() {
Set values = getValuesOf(SERIES);
values.addAll(getValuesOf(TIMESERIES));
return values;
}
public Set getDatasets() {
Set values = getSeries();
values.addAll(getValuesOf(DATASETS));
return values;
}
public Set getFields() {
return getValuesOf(FILTER_FIELDS);
}
public Set getPlatformTypes() {
return getValuesOf(FILTER_PLATFORM_TYPES);
}
public Set getPlatformGeometryTypes() {
return getValuesOf(FILTER_PLATFORM_GEOMETRIES);
}
public Set getObservedGeometryTypes() {
return getValuesOf(FILTER_OBSERVED_GEOMETRIES);
}
public Set getValueTypes() {
return getValuesOf(FILTER_VALUE_TYPES);
}
public Set getSearchTerms() {
return getValuesOf(SEARCH_TERM);
}
public Set getGeometryTypes() {
return getValuesOf(GEOMETRY_TYPES);
}
Set getValuesOf(String parameterName) {
return containsParameter(parameterName)
? new HashSet<>(csvToLowerCasedSet(getAsString(parameterName)))
: new HashSet<>(0);
}
private Set csvToLowerCasedSet(String csv) {
return csvToSet(csv, String::toLowerCase);
}
private Set csvToSet(String csv) {
return csvToSet(csv, Function.identity());
}
private Set csvToSet(String csv, Function c) {
return Optional.ofNullable(csv)
.map(str -> str.split(","))
.flatMap(values -> Optional.ofNullable(c).map(f -> Arrays.stream(values).map(f).collect(toSet())))
.orElseGet(HashSet::new);
}
public FilterResolver getFilterResolver() {
return filterResolver;
}
/**
* Creates a {@link BoundingBox} instance from given spatial request parameters. The resulting bounding
* box is the merged extent of all spatial filters given. For example if {@value #NEAR} and {@value #BBOX}
* exist, the returned bounding box includes both extents.
*
* @return a spatial filter created from given spatial parameters.
* @throws IoParseException
* if parsing parameters fails, or if a requested {@value #CRS} object could not be created.
*/
public BoundingBox getSpatialFilter() {
if (!containsParameter(NEAR) && !containsParameter(BBOX)) {
return null;
}
BoundingBox bboxBounds = createBbox();
BoundingBox bounds = parseBoundsFromVicinity();
return mergeBounds(bounds, bboxBounds);
}
private BoundingBox mergeBounds(BoundingBox bounds, BoundingBox bboxBounds) {
if (bboxBounds == null) {
// nothing to merge
return bounds;
}
Point lowerLeft = bboxBounds.getLowerLeft();
Point upperRight = bboxBounds.getUpperRight();
if (bounds == null) {
BoundingBox parsed = new BoundingBox(lowerLeft, upperRight, CRSUtils.DEFAULT_CRS);
LOGGER.debug("Parsed bbox bounds: {}", parsed.toString());
return parsed;
} else {
extendBy(lowerLeft, bounds);
extendBy(upperRight, bounds);
LOGGER.debug("Merged bounds: {}", bounds.toString());
return bounds;
}
}
/**
* Extends the bounding box with the given point. If point is contained by this instance nothing is
* changed.
*
* @param point
* the point in CRS:84 which shall extend the bounding box.
*/
private void extendBy(Point point, BoundingBox bbox) {
bbox.extendBy(point);
}
/**
* @return a {@link BBox} instance or null if no {@link #BBOX} parameter is present.
* @throws IoParseException
* if parsing parameter fails.
* @throws IoParseException
* if a requested {@value #CRS} object could not be created
*/
private BoundingBox createBbox() {
if (!containsParameter(BBOX)) {
return null;
}
String bboxValue = getAsString(BBOX);
CRSUtils crsUtils = CRSUtils.createEpsgForcedXYAxisOrder();
// Check if supplied in minx,miny,maxx,maxy format - else assume json
if (bboxValue.matches("^(-?\\d*\\.?\\d*\\,\\s*){3}(-?\\d*\\.?\\d*)\\s*$")) {
String[] coordArray = bboxValue.split("\\,");
Point lowerLeft = crsUtils.createPoint(Double.valueOf(coordArray[0].trim()),
Double.valueOf(coordArray[1].trim()),
CRSUtils.DEFAULT_CRS);
Point upperRight = crsUtils.createPoint(Double.valueOf(coordArray[2].trim()),
Double.valueOf(coordArray[3].trim()),
CRSUtils.DEFAULT_CRS);
return new BoundingBox(lowerLeft, upperRight, CRSUtils.DEFAULT_CRS);
}
try {
BBox bbox = handleJsonValueParseException(BBOX, BBox.class, this::parseJson);
return new BoundingBox(crsUtils.convertToPointFrom(bbox.getLl(), CRSUtils.DEFAULT_CRS),
crsUtils.convertToPointFrom(bbox.getUr(), CRSUtils.DEFAULT_CRS),
CRSUtils.DEFAULT_CRS);
} catch (IoParseException e) {
throw e.addHint(createInvalidParameterMessage(Parameters.BBOX))
.addHint("Check http://epsg-registry.org for EPSG CRS definitions and codes.")
.addHint("(alternate format of 'llLon,llLat,urLon,urLat' couldn't be detected)");
}
}
private BoundingBox parseBoundsFromVicinity() {
if (!containsParameter(NEAR)) {
return null;
}
Vicinity vicinity = handleJsonValueParseException(NEAR, Vicinity.class, this::parseJson);
if (containsParameter(CRS)) {
vicinity.setCenter(convertToCrs84(vicinity.getCenter()));
}
BoundingBox bounds = vicinity.calculateBounds();
LOGGER.debug("Parsed vicinity bounds: {}", bounds.toString());
return bounds;
}
/**
* @param parameter
* the parameter name.
* @param clazz
* the type to serialize given parameter to.
* @return a mapped instance parsed from given paramter's value.
* @throws IoParseException
* if parameter is invalid JSON or does not map to given type.
*/
private T parseJson(String parameter, Class clazz) {
try {
String value = getAsString(parameter);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(value, clazz);
} catch (JsonParseException | JsonMappingException e) {
throw createInvalidJsonValueException(parameter, e);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* @param parameter
* the parameter name.
* @param typeReference
* the type reference to serialize given parameter to.
* @return a mapped instance parsed from given paramter's value.
* @throws IoParseException
* if parameter is invalid JSON or does not map to given type.
*/
private T parseJson(String parameter, TypeReference typeReference) {
try {
Optional value = getAsNode(parameter);
return value.isPresent()
? new ObjectMapper().readerFor(typeReference)
.readValue(value.get())
: null;
} catch (JsonParseException | JsonMappingException e) {
throw createInvalidJsonValueException(parameter, e);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private IoParseException createInvalidJsonValueException(String nodeValue, Exception e) {
return new IoParseException("The given JSON value is invalid: " + nodeValue, e);
}
private GeojsonPoint convertToCrs84(GeojsonPoint point) {
// is strict XY axis order?!
return isForceXY()
? transformToInnerCrs(point, CRSUtils.createEpsgForcedXYAxisOrder())
: transformToInnerCrs(point, CRSUtils.createEpsgStrictAxisOrder());
}
/**
* @param point
* a GeoJSON point to be transformed to internally used CRS:84.
* @param crsUtils
* a reference helper.
* @return a transformed GeoJSON instance.
* @throws IoParseException
* if point could not be transformed, or if requested CRS object could not be created.
*/
private GeojsonPoint transformToInnerCrs(GeojsonPoint point,
CRSUtils crsUtils) {
try {
Point toTransformed = crsUtils.convertToPointFrom(point, getCrs());
Point crs84Point = (Point) crsUtils.transformOuterToInner(toTransformed, getCrs());
return crsUtils.convertToGeojsonFrom(crs84Point);
} catch (TransformException e) {
throw new IoParseException("Could not transform to internally used CRS:84.", e);
} catch (FactoryException e) {
throw new IoParseException("Check if 'crs' parameter is a valid EPSG CRS. Was: '"
+ getCrs()
+ "'.", e);
}
}
/**
* @return the requested reference context, or the default ({@value CRSUtils#DEFAULT_CRS}) which will be
* interpreted as lon/lat ordered axes).
*/
public String getCrs() {
return getAsString(CRS, CRSUtils.DEFAULT_CRS);
}
public boolean isForceXY() {
return getAsBoolean(FORCE_XY, DEFAULT_FORCE_XY);
}
public boolean isMatchDomainIds() {
return getAsBoolean(MATCH_DOMAIN_IDS, DEFAULT_MATCH_DOMAIN_IDS);
}
/**
* @return the value of {@value #EXPANDED} parameter.
* @throws IoParseException
* if parameter could not be parsed.
*/
public boolean isExpanded() {
return getAsBoolean(EXPANDED, DEFAULT_EXPANDED);
}
public boolean isForceLatestValueRequests() {
return getAsBoolean(FORCE_LATEST_VALUE, DEFAULT_FORCE_LATEST_VALUE);
}
/**
* @return if status intervals shall be serialized with (timeseries) output
* @throws IoParseException
* if parameter could not be parsed.
* @deprecated since v2.0 covered by extras endpoint
*/
@Deprecated
public boolean isStatusIntervalsRequests() {
return getAsBoolean(STATUS_INTERVALS, DEFAULT_STATUS_INTERVALS);
}
/**
* @return if rendering hints shall be serialized with (timeseries) output
* @throws IoParseException
* if parameter could not be parsed.
* @deprecated since v2.0 covered by extras endpoint
*/
@Deprecated
public boolean isRenderingHintsRequests() {
return getAsBoolean(RENDERING_HINTS, DEFAULT_RENDERING_HINTS);
}
public String getHrefBase() {
return getAsString(Parameters.HREF_BASE);
}
public boolean isShowTimeIntervals() {
return getAsBoolean(SHOW_TIME_INTERVALS, DEFAULT_SHOW_TIME_INTERVALS);
}
public boolean isShowVerticalIntervals() {
return getAsBoolean(SHOW_VERTICAL_INTERVALS, DEFAULT_SHOW_VERTICAL_INTERVALS);
}
public boolean containsParameter(String parameter) {
return query.containsKey(parameter.toLowerCase())
|| query.containsKey(parameter);
}
public String getOther(String parameter) {
return getAsString(parameter);
}
public String getAsString(String parameter, String defaultValue) {
return containsParameter(parameter)
? getAsString(parameter)
: defaultValue;
}
public String getAsString(String parameter) {
if (!containsParameter(parameter)) {
return null;
}
return asCsv(getAsNodes(parameter));
}
private Optional getAsNode(String parameter) {
return getAsNodes(parameter).stream()
.findFirst();
}
private List getAsNodes(String parameter) {
return query.get(parameter) == null
? query.get(parameter.toLowerCase())
: query.get(parameter);
}
private String asCsv(List list) {
StringBuilder sb = new StringBuilder();
for (JsonNode jsonNode : list) {
if (sb.length() != 0) {
sb.append(",");
}
sb.append(jsonNode.asText());
}
return sb.toString();
}
public int getAsInteger(String parameter, int defaultValue) {
return containsParameter(parameter)
? handleSimpleValueParseException(parameter, this::getAsInteger)
: defaultValue;
}
/**
* @param parameter
* the parameter to parse to an int value.
* @return an integer value.
* @throws IoParseException
* if parsing to int fails.
*/
public int getAsInteger(String parameter) {
try {
String value = getAsString(parameter);
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw createIoParseException(parameter).addHint("Value must be an integer!");
}
}
public boolean getAsBoolean(String parameter, boolean defaultValue) {
return containsParameter(parameter)
? handleSimpleValueParseException(parameter, this::getAsBoolean)
: defaultValue;
}
/**
* @param parameter
* the parameter to parse to boolean.
* @return true or false as boolean.
* @throws IoParseException
* if parsing to boolean fails.
*/
public boolean getAsBoolean(String parameter) {
String value = getAsString(parameter);
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
return Boolean.parseBoolean(value);
} else {
IoParseException ex = createIoParseException(parameter).addHint("Value must be either 'false' or 'true'!");
return handleIoParseException(parameter, ex);
}
}
private IoParseException createIoParseException(String parameter) {
return createIoParseException(parameter, null);
}
private IoParseException createIoParseException(String parameter, Exception e) {
return e != null
? new IoParseException(createInvalidParameterMessage(parameter), e)
: new IoParseException(createInvalidParameterMessage(parameter));
}
private String createInvalidParameterMessage(String parameter) {
return "The parameter '" + parameter + "' is invalid.";
}
private R handleSimpleValueParseException(String parameter, Function supplier) {
try {
return supplier.apply(parameter);
} catch (IoParseException e) {
return handleIoParseException(parameter, e);
}
}
private R handleJsonValueParseException(String parameter,
Class clazz,
BiFunction, R> supplier) {
try {
return supplier.apply(parameter, clazz);
} catch (IoParseException e) {
return handleIoParseException(parameter, e);
}
}
private R handleJsonValueParseException(String parameter,
TypeReference reference,
BiFunction, R> supplier) {
try {
return supplier.apply(parameter, reference);
} catch (IoParseException e) {
return handleIoParseException(parameter, e);
}
}
/**
* @param parameter
* the parameter causing the parse exception.
* @param e
* the parse exception to handle
* @return nothing, as {@link #parseExceptionHandle} is expected to handle the exception.
*/
private R handleIoParseException(String parameter, IoParseException e) {
if (parseExceptionHandle != null) {
parseExceptionHandle.accept(parameter, e);
return null;
} else {
throw e;
}
}
public static JsonNode getJsonNodeFrom(Object object) {
if (object == null) {
return null;
}
try {
return OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsString(object));
} catch (IOException e) {
LOGGER.error("Could not parse parameter", e);
return null;
}
}
/**
* Creates a new instance based on the current one and removes given parameter with the all its values. As
* a new instance is created the current one stays unchanged (unmutable instance) and can be reused as is.
*
* @param key
* the name of the parameter to remove all its values
* @return a new instance with extended key/values
*/
public IoParameters removeAllOf(String key) {
MultiValueMap newValues = new LinkedMultiValueMap<>(query);
newValues.remove(key.toLowerCase());
return new IoParameters(newValues).setParseExceptionHandle(parseExceptionHandle);
}
/**
* Creates a new instance based on the current one and adds parameter with the given values. As a new
* instance is created the current one stays unchanged (unmutable instance) and can be reused as is.
*
* @param key
* the parameter name
* @param values
* the parameter values
* @return a new instance with extended key/values
*/
public IoParameters extendWith(String key, String... values) {
MultiValueMap newValues = new LinkedMultiValueMap<>();
newValues.put(key.toLowerCase(), Arrays.asList(values));
MultiValueMap mergedValues = new LinkedMultiValueMap<>(query);
mergedValues.putAll(convertToJsonNodes(newValues));
return new IoParameters(mergedValues).setParseExceptionHandle(parseExceptionHandle);
}
public IoParameters replaceWith(String key, String... values) {
return removeAllOf(key).extendWith(key, values);
}
protected static Map convertValuesToJsonNodes(Map queryParameters) {
Map parameters = new HashMap<>();
for (Entry entry : queryParameters.entrySet()) {
String key = entry.getKey();
parameters.put(key.toLowerCase(), getJsonNodeFrom(entry.getValue()));
}
return parameters;
}
protected static MultiValueMap convertToJsonNodes(MultiValueMap queryParameters) {
MultiValueMap parameters = new LinkedMultiValueMap<>();
final Set>> entrySet = queryParameters.entrySet();
for (Entry> entry : entrySet) {
for (String value : entry.getValue()) {
final String key = entry.getKey().toLowerCase();
parameters.add(key, getJsonNodeFrom(value));
}
}
return parameters;
}
@Override
public String toString() {
return "IoParameters{ behaveBackwardsCompatible: " + behaveBackwardsCompatible + ", query=" + query + '}';
}
protected Map mergeToLowerCasedKeys(Map parameters) {
Map queryParameters = new HashMap<>();
for (Entry entry : parameters.entrySet()) {
String parameter = entry.getKey();
String lowerCasedKey = parameter.toLowerCase();
queryParameters.put(lowerCasedKey, parameters.get(parameter));
}
return queryParameters;
}
protected MultiValueMap mergeToLowerCasedKeys(MultiValueMap parameters) {
MultiValueMap queryParameters = new LinkedMultiValueMap<>();
for (Entry> entry : parameters.entrySet()) {
String parameter = entry.getKey();
String lowerCasedKey = parameter.toLowerCase();
List values = parameters.get(parameter);
if (!queryParameters.containsKey(lowerCasedKey)) {
queryParameters.put(lowerCasedKey, values);
} else {
queryParameters.get(lowerCasedKey)
.addAll(values);
}
}
return queryParameters;
}
/*
* ********************* FACTORY METHODS ***************************
*/
public static IoParameters createDefaults() {
return createDefaults(null);
}
static IoParameters createDefaults(File defaultConfig) {
return new IoParameters(Collections. emptyMap(), defaultConfig);
}
public static IoParameters createFromMultiValueMap(MultiValueMap query) {
return createFromMultiValueMap(query, null);
}
private static IoParameters createFromMultiValueMap(MultiValueMap query, File defaultConfig) {
return createFromMultiJsonValueMap(convertToJsonNodes(query), defaultConfig);
}
private static IoParameters createFromMultiJsonValueMap(MultiValueMap query, File defaultConfig) {
return new IoParameters(query, defaultConfig);
}
public static IoParameters createFromSingleValueMap(Map query) {
return createFromSingleValueMap(query, null);
}
private static IoParameters createFromSingleValueMap(Map query, File defaultConfig) {
return createFromSingleJsonValueMap(convertValuesToJsonNodes(query), defaultConfig);
}
static IoParameters createFromSingleJsonValueMap(Map query) {
return new IoParameters(query, null);
}
private static IoParameters createFromSingleJsonValueMap(Map query, File defaultConfig) {
return new IoParameters(query, defaultConfig);
}
public IoParameters respectBackwardsCompatibility() {
String[] platformTypes = {
PlatformType.PLATFORM_TYPE_STATIONARY,
PlatformType.PLATFORM_TYPE_INSITU
};
return filterResolver.shallBehaveBackwardsCompatible()
? removeAllOf(Parameters.HREF_BASE).extendWith(Parameters.FILTER_PLATFORM_TYPES, platformTypes)
.extendWith(Parameters.FILTER_VALUE_TYPES,
ValueType.DEFAULT_VALUE_TYPE)
// set backwards compatibility at the end
.setBehaveBackwardsCompatible(true)
: this;
}
public boolean isPureStationaryInsituQuery() {
Set platformTypes = getPlatformTypes();
Set datasetTypes = getValueTypes();
return isStationaryInsituOnly(platformTypes)
&& isQuantityOnly(datasetTypes);
}
private boolean isStationaryInsituOnly(Set platformTypes) {
return platformTypes.size() == 2
&& platformTypes.contains(PlatformType.PLATFORM_TYPE_STATIONARY)
&& platformTypes.contains(PlatformType.PLATFORM_TYPE_INSITU);
}
private boolean isQuantityOnly(Set valueTypes) {
return valueTypes.size() == 1
&& valueTypes.contains(ValueType.DEFAULT_VALUE_TYPE);
}
}