/**
* Copyright (C) 2012-2017 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.sos.decode.kvp;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
import org.joda.time.DateTime;
import org.n52.sos.config.annotation.Configurable;
import org.n52.sos.config.annotation.Setting;
import org.n52.sos.decode.Decoder;
import org.n52.sos.ds.FeatureQuerySettingsProvider;
import org.n52.sos.exception.CodedException;
import org.n52.sos.exception.ConfigurationException;
import org.n52.sos.exception.ows.InvalidParameterValueException;
import org.n52.sos.exception.ows.MissingParameterValueException;
import org.n52.sos.exception.ows.concrete.DateTimeParseException;
import org.n52.sos.exception.ows.concrete.MissingServiceParameterException;
import org.n52.sos.exception.ows.concrete.MissingVersionParameterException;
import org.n52.sos.ogc.filter.FilterConstants.SpatialOperator;
import org.n52.sos.ogc.filter.FilterConstants.TimeOperator;
import org.n52.sos.ogc.filter.FilterConstants.TimeOperator2;
import org.n52.sos.ogc.filter.SpatialFilter;
import org.n52.sos.ogc.filter.TemporalFilter;
import org.n52.sos.ogc.gml.time.Time;
import org.n52.sos.ogc.gml.time.TimeInstant;
import org.n52.sos.ogc.gml.time.TimePeriod;
import org.n52.sos.ogc.ows.CompositeOwsException;
import org.n52.sos.ogc.ows.OWSConstants;
import org.n52.sos.ogc.ows.OwsExceptionReport;
import org.n52.sos.ogc.sos.SosConstants.SosIndeterminateTime;
import org.n52.sos.ogc.swe.simpleType.SweBoolean;
import org.n52.sos.ogc.swe.simpleType.SweText;
import org.n52.sos.ogc.swes.SwesExtension;
import org.n52.sos.ogc.swes.SwesExtensionImpl;
import org.n52.sos.request.AbstractServiceRequest;
import org.n52.sos.service.ServiceConfiguration;
import org.n52.sos.service.ServiceConstants;
import org.n52.sos.util.Constants;
import org.n52.sos.util.DateTimeHelper;
import org.n52.sos.util.JTSHelper;
import org.n52.sos.util.KvpHelper;
import org.n52.sos.util.SosHelper;
import org.n52.sos.util.Validation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @since 4.0.0
*
*/
@Configurable
public abstract class AbstractKvpDecoder implements Decoder<AbstractServiceRequest<?>, Map<String, String>> {
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractKvpDecoder.class);
protected static final int VALID_COORDINATE_SIZE = 4;
private int storageEPSG;
private int storage3DEPSG;
private int defaultResponseEPSG;
private int defaultResponse3DEPSG;
@Override
public Set<String> getConformanceClasses() {
return Collections.emptySet();
}
@Override
public Map<ServiceConstants.SupportedTypeKey, Set<String>> getSupportedTypes() {
return Collections.emptyMap();
}
public int getStorageEPSG() {
return storageEPSG;
}
public int getStorage3DEPSG() {
return storage3DEPSG;
}
public int getDefaultResponseEPSG() {
return defaultResponseEPSG;
}
public int getDefaultResponse3DEPSG() {
return defaultResponse3DEPSG;
}
/**
* Set storage EPSG code from settings
*
* @param epsgCode
* EPSG code from settings
* @throws ConfigurationException
* If an error occurs
*/
@Setting(FeatureQuerySettingsProvider.STORAGE_EPSG)
public void setStorageEpsg(final int epsgCode) throws ConfigurationException {
Validation.greaterZero("Storage EPSG Code", epsgCode);
storageEPSG = epsgCode;
}
/**
* Set storage 3D EPSG code from settings
*
* @param epsgCode3D
* 3D EPSG code from settings
* @throws ConfigurationException
* If an error occurs
*/
@Setting(FeatureQuerySettingsProvider.STORAGE_3D_EPSG)
public void setStorage3DEpsg(final int epsgCode3D) throws ConfigurationException {
Validation.greaterZero("Storage 3D EPSG Code", epsgCode3D);
storage3DEPSG = epsgCode3D;
}
/**
* Set default response EPSG code from settings
*
* @param epsgCode
* EPSG code from settings
* @throws ConfigurationException
* If an error occurs
*/
@Setting(FeatureQuerySettingsProvider.DEFAULT_RESPONSE_EPSG)
public void setDefaultResponseEpsg(final int epsgCode) throws ConfigurationException {
Validation.greaterZero("Storage EPSG Code", epsgCode);
defaultResponseEPSG = epsgCode;
}
/**
* Set default response 3D EPSG code from settings
*
* @param epsgCode3D
* 3D EPSG code from settings
* @throws ConfigurationException
* If an error occurs
*/
@Setting(FeatureQuerySettingsProvider.DEFAULT_RESPONSE_3D_EPSG)
public void setDefaultResponse3DEpsg(final int epsgCode3D) throws ConfigurationException {
Validation.greaterZero("Storage 3D EPSG Code", epsgCode3D);
defaultResponse3DEPSG = epsgCode3D;
}
protected boolean parseExtensionParameter(AbstractServiceRequest<?> request, String parameterValues,
String parameterName) throws OwsExceptionReport {
return false;
}
protected boolean parseDefaultParameter(AbstractServiceRequest<?> request, String parameterValues,
String parameterName) throws OwsExceptionReport {
// service (mandatory)
if (parameterName.equalsIgnoreCase(OWSConstants.RequestParams.service.name())) {
request.setService(KvpHelper.checkParameterSingleValue(parameterValues, parameterName));
return true;
}
// version (mandatory)
else if (parameterName.equalsIgnoreCase(OWSConstants.RequestParams.version.name())) {
request.setVersion(KvpHelper.checkParameterSingleValue(parameterValues, parameterName));
return true;
}
// request (mandatory)
else if (parameterName.equalsIgnoreCase(OWSConstants.RequestParams.request.name())) {
KvpHelper.checkParameterSingleValue(parameterValues, parameterName);
return true;
}
// language (optional)
else if (parameterName.equalsIgnoreCase(OWSConstants.AdditionalRequestParams.language.name())) {
request.addExtension(getLanguageExtension(KvpHelper.checkParameterSingleValue(parameterValues,
parameterName)));
return true;
}
// CRS (optional)
else if (parameterName.equalsIgnoreCase(OWSConstants.AdditionalRequestParams.crs.name())) {
request.addExtension(getCrsExtension(KvpHelper.checkParameterSingleValue(parameterValues, parameterName)));
return true;
}
else if (parameterName.equalsIgnoreCase(OWSConstants.AdditionalRequestParams.returnHumanReadableIdentifier
.name())) {
request.addExtension(getReturnHumanReadableIdentifierExtension(KvpHelper.checkParameterSingleValue(
parameterValues, parameterName)));
return true;
} else {
return parseExtensionParameter(request, parameterValues, parameterName);
}
}
/**
* Check if service and version are contained in the request
*
* @param request
* Parsed request
* @param exceptions
* {@link CompositeOwsException} to add
* {@link MissingParameterValueException}s
*/
protected void checkIfServiceVersionIsMissing(AbstractServiceRequest<?> request, CompositeOwsException exceptions) {
if (!request.isSetService()) {
exceptions.add(new MissingServiceParameterException());
}
if (!request.isSetVersion()) {
exceptions.add(new MissingVersionParameterException());
}
}
protected SpatialFilter parseSpatialFilter(List<String> parameterValues, String parameterName)
throws OwsExceptionReport {
if (!parameterValues.isEmpty()) {
if (!(parameterValues instanceof RandomAccess)) {
parameterValues = new ArrayList<String>(parameterValues);
}
SpatialFilter spatialFilter = new SpatialFilter();
boolean hasSrid = false;
spatialFilter.setValueReference(parameterValues.get(0));
int srid = getStorageEPSG();
if (parameterValues.get(parameterValues.size() - 1).startsWith(getSrsNamePrefixSosV2())
|| parameterValues.get(parameterValues.size() - 1).startsWith(getSrsNamePrefix())) {
hasSrid = true;
srid = SosHelper.parseSrsName(parameterValues.get(parameterValues.size() - 1));
}
List<String> coordinates;
if (hasSrid) {
coordinates = parameterValues.subList(1, parameterValues.size() - 1);
} else {
coordinates = parameterValues.subList(1, parameterValues.size());
}
if (coordinates.size() != VALID_COORDINATE_SIZE) {
throw new InvalidParameterValueException().at(parameterName).withMessage(
"The parameter value is not valid!");
}
String lowerCorner =
String.format(Locale.US, "%s %s", new BigDecimal(coordinates.get(Constants.INT_0)).toString(),
new BigDecimal(coordinates.get(Constants.INT_1)).toString());
String upperCorner =
String.format(Locale.US, "%s %s", new BigDecimal(coordinates.get(Constants.INT_2)).toString(),
new BigDecimal(coordinates.get(Constants.INT_3)).toString());
spatialFilter.setGeometry(JTSHelper.createGeometryFromWKT(
JTSHelper.createWKTPolygonFromEnvelope(lowerCorner, upperCorner), srid));
spatialFilter.setOperator(SpatialOperator.BBOX);
return spatialFilter;
}
return null;
}
/**
* @param parameterValue
* @param parameterName
* @return SOS time object
* @throws OwsExceptionReport
* @throws DateTimeParseException
*/
protected Time parseValidTime(String parameterValue, String parameterName) throws OwsExceptionReport,
DateTimeParseException {
return parseTime(parameterValue, parameterName);
}
/**
* @param parameterValue
* @return SOS time object
* @throws CodedException
*/
protected Time parseTime(String parameterValue, String parameterName) throws CodedException {
String[] times = parameterValue.split("/");
if (times.length == 1) {
TimeInstant ti = new TimeInstant();
if (SosIndeterminateTime.contains(times[0])) {
ti.setSosIndeterminateTime(SosIndeterminateTime.getEnumForString(times[0]));
} else {
DateTime instant = DateTimeHelper.parseIsoString2DateTime(times[0]);
ti.setValue(instant);
ti.setRequestedTimeLength(DateTimeHelper.getTimeLengthBeforeTimeZone(times[0]));
}
return ti;
} else if (times.length == 2) {
DateTime start = DateTimeHelper.parseIsoString2DateTime(times[0]);
// check if end time is a full ISO 8106 string
int timeLength = DateTimeHelper.getTimeLengthBeforeTimeZone(times[1]);
DateTime origEnd = DateTimeHelper.parseIsoString2DateTime(times[1]);
DateTime end = DateTimeHelper.setDateTime2EndOfMostPreciseUnit4RequestedEndPosition(origEnd, timeLength);
TimePeriod timePeriod = new TimePeriod(start, end);
return timePeriod;
} else {
throw new InvalidParameterValueException().at(parameterName).withMessage(
"The parameter value is not valid!");
}
}
protected List<TemporalFilter> parseTemporalFilter(List<String> parameterValues, String parameterName)
throws OwsExceptionReport, DateTimeParseException {
List<TemporalFilter> filterList = new ArrayList<TemporalFilter>(1);
// order: valueReference, time
if (parameterValues.size() == 2) {
filterList.add(createTemporalFilterFromValue(parameterValues.get(1), parameterValues.get(0)));
}
// order: valueReference, temporal operator, time
else if (parameterValues.size() == 3) {
filterList.add(createTemporalFilterFromValue(parameterValues.get(2), parameterValues.get(1), parameterValues.get(0)));
} else {
throw new InvalidParameterValueException().withMessage("The parameter value is not valid!");
}
return filterList;
}
protected Map<String, String> parseNamespaces(String parameterValues) {
List<String> array =
Arrays.asList(parameterValues.replaceAll("\\),", "").replaceAll("\\)", "").split("xmlns\\("));
Map<String, String> namespaces = new HashMap<String, String>(array.size());
for (String string : array) {
if (string != null && !string.isEmpty()) {
String[] s = string.split(",");
namespaces.put(s[0], s[1]);
}
}
return namespaces;
}
private TemporalFilter createTemporalFilterFromValue(String value, String valueReference)
throws OwsExceptionReport, DateTimeParseException {
String[] times = value.split("/");
if (times.length == 1) {
return createTemporalFilterFromValue(value, TimeOperator.TM_Equals.name(), valueReference);
} else if (times.length == 2) {
return createTemporalFilterFromValue(value, TimeOperator.TM_During.name(), valueReference);
} else {
throw new InvalidParameterValueException().withMessage("The paramter value '%s' is invalid!", value);
}
}
private TemporalFilter createTemporalFilterFromValue(String value, String operator, String valueReference)
throws OwsExceptionReport, DateTimeParseException {
TemporalFilter temporalFilter = new TemporalFilter();
temporalFilter.setValueReference(valueReference);
temporalFilter.setOperator(getTimeOperator(operator));
String[] times = value.split("/");
if (times.length == 1 && !temporalFilter.getOperator().equals(TimeOperator.TM_During)) {
TimeInstant ti = new TimeInstant();
if (SosIndeterminateTime.contains(times[0])) {
ti.setSosIndeterminateTime(SosIndeterminateTime.getEnumForString(times[0]));
} else {
DateTime instant = DateTimeHelper.parseIsoString2DateTime(times[0]);
ti.setValue(instant);
ti.setRequestedTimeLength(DateTimeHelper.getTimeLengthBeforeTimeZone(times[0]));
}
temporalFilter.setTime(ti);
} else if (times.length == 2 & temporalFilter.getOperator().equals(TimeOperator.TM_During)) {
DateTime start = DateTimeHelper.parseIsoString2DateTime(times[0]);
// check if end time is a full ISO 8106 string
int timeLength = DateTimeHelper.getTimeLengthBeforeTimeZone(times[1]);
DateTime origEnd = DateTimeHelper.parseIsoString2DateTime(times[1]);
DateTime end = DateTimeHelper.setDateTime2EndOfMostPreciseUnit4RequestedEndPosition(origEnd, timeLength);
TimePeriod tp = new TimePeriod();
tp.setStart(start);
tp.setEnd(end);
temporalFilter.setTime(tp);
} else {
throw new InvalidParameterValueException().withMessage("The paramter value '%s' is invalid!", value);
}
return temporalFilter;
}
private TimeOperator getTimeOperator(String operator) {
try {
return TimeOperator.from(operator);
} catch (IllegalArgumentException iae) {
LOGGER.debug("Not a FES 1.0.0 temporal operator!", iae);
}
return TimeOperator.from(TimeOperator2.from(operator));
}
protected String getSrsNamePrefix() {
return ServiceConfiguration.getInstance().getSrsNamePrefix();
}
protected String getSrsNamePrefixSosV2() {
return ServiceConfiguration.getInstance().getSrsNamePrefixSosV2();
}
protected SwesExtension<SweText> getLanguageExtension(String language) {
return getSweTextFor(OWSConstants.AdditionalRequestParams.language.name(), language);
}
protected SwesExtension<SweText> getCrsExtension(String crs) {
return getSweTextFor(OWSConstants.AdditionalRequestParams.crs.name(), crs);
}
protected SwesExtension<SweBoolean> getReturnHumanReadableIdentifierExtension(String returnHumanReadableIdentifier) {
SweBoolean bool =
(SweBoolean) new SweBoolean().setValue(Boolean.parseBoolean(returnHumanReadableIdentifier))
.setIdentifier(OWSConstants.AdditionalRequestParams.returnHumanReadableIdentifier.name());
return new SwesExtensionImpl<SweBoolean>().setValue(bool);
}
protected SwesExtension<SweText> getSweTextFor(String identifier, String value) {
SweText text = (SweText) new SweText().setValue(value).setIdentifier(identifier);
return new SwesExtensionImpl<SweText>().setValue(text);
}
}