/**
* 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.binding.rest.decode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.RandomAccess;
import javax.servlet.http.HttpServletRequest;
import org.joda.time.DateTime;
import org.n52.sos.binding.rest.Constants;
import org.n52.sos.binding.rest.requests.RestRequest;
import org.n52.sos.exception.ows.InvalidParameterValueException;
import org.n52.sos.exception.ows.MissingParameterValueException;
import org.n52.sos.exception.ows.OperationNotSupportedException;
import org.n52.sos.exception.ows.concrete.DateTimeException;
import org.n52.sos.ogc.filter.FilterConstants.SpatialOperator;
import org.n52.sos.ogc.filter.FilterConstants.TimeOperator;
import org.n52.sos.ogc.filter.SpatialFilter;
import org.n52.sos.ogc.filter.TemporalFilter;
import org.n52.sos.ogc.gml.time.TimeInstant;
import org.n52.sos.ogc.gml.time.TimePeriod;
import org.n52.sos.ogc.ows.OwsExceptionReport;
import org.n52.sos.ogc.sos.SosConstants.SosIndeterminateTime;
import org.n52.sos.request.GetCapabilitiesRequest;
import org.n52.sos.service.ServiceConfiguration;
import org.n52.sos.util.DateTimeHelper;
import org.n52.sos.util.JTSHelper;
import org.n52.sos.util.SosHelper;
import org.n52.sos.util.http.HTTPMethods;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author <a href="mailto:e.h.juerrens@52north.org">Eike Hinderk Jürrens</a>
* TODO Use KVP helper from 52n-sos-api module
*/
public abstract class ResourceDecoder extends RestDecoder {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceDecoder.class);
protected Constants bindingConstants = Constants.getInstance();
protected abstract RestRequest decodeGetRequest(HttpServletRequest httpRequest, String pathPayload) throws OwsExceptionReport, DateTimeException;
protected abstract RestRequest decodeDeleteRequest(HttpServletRequest httpRequest, String pathPayload) throws OwsExceptionReport;
protected abstract RestRequest decodePostRequest(HttpServletRequest httpRequest, String pathPayload) throws OwsExceptionReport;
protected abstract RestRequest decodePutRequest(HttpServletRequest httpRequest, String pathPayload) throws OwsExceptionReport;
protected abstract RestRequest decodeOptionsRequest(HttpServletRequest httpRequest, String pathPayload);
protected RestRequest decodeRestRequest(final HttpServletRequest httpRequest) throws OwsExceptionReport, DateTimeException
{
String resourceType = null;
String pathPayload = null;
if (httpRequest != null && httpRequest.getPathInfo() != null) {
final String resourceTypeWithOrWithoutId = getResourceTypeFromPathInfoWithWorkingUrl(httpRequest.getPathInfo());
final int indexOfPotentialSecondSlash = resourceTypeWithOrWithoutId.indexOf("/");
if (indexOfPotentialSecondSlash > 1) {
resourceType = resourceTypeWithOrWithoutId.substring(0,indexOfPotentialSecondSlash);
pathPayload = resourceTypeWithOrWithoutId.substring(indexOfPotentialSecondSlash + 1);
} else {
resourceType = resourceTypeWithOrWithoutId;
}
LOGGER.debug("resourceType: {}; pathPayload: {} ",resourceType,pathPayload);
// delegate to HTTP method specific decoders for parsing this resource's request
if (httpRequest.getMethod().equalsIgnoreCase(HTTPMethods.GET) ||
httpRequest.getMethod().equalsIgnoreCase(HTTPMethods.HEAD)) {
return decodeGetRequest(httpRequest, pathPayload);
} else if (httpRequest.getMethod().equalsIgnoreCase(HTTPMethods.DELETE)) {
return decodeDeleteRequest(httpRequest,pathPayload);
} else if (httpRequest.getMethod().equalsIgnoreCase(HTTPMethods.POST)) {
return decodePostRequest(httpRequest,pathPayload);
} else if (httpRequest.getMethod().equalsIgnoreCase(HTTPMethods.PUT)) {
return decodePutRequest(httpRequest,pathPayload);
} else if (httpRequest.getMethod().equalsIgnoreCase(HTTPMethods.OPTIONS)) {
return decodeOptionsRequest(httpRequest,pathPayload);
}
}
final String exceptionText = String.format("The resource type \"%s\" via HTTP method \"%s\" is not supported by this IDecoder implementiation.",
resourceType,
httpRequest.getMethod());
LOGGER.debug(exceptionText);
throw new OperationNotSupportedException(resourceType);
}
protected String getRelationIdentifierWithNamespace(final String resourceRelationIdentifier)
{
return bindingConstants.getEncodingNamespace()
.concat("/")
.concat(resourceRelationIdentifier);
}
protected GetCapabilitiesRequest createGetCapabilitiesRequest()
{
final GetCapabilitiesRequest getCapabilitiesRequest = new GetCapabilitiesRequest();
getCapabilitiesRequest.setVersion(bindingConstants.getSosVersion());
getCapabilitiesRequest.setService(bindingConstants.getSosService());
final String[] acceptedVersions = { bindingConstants.getSosVersion() };
getCapabilitiesRequest.setAcceptVersions(Arrays.asList(acceptedVersions));
return getCapabilitiesRequest;
}
protected String getResourceIdFromRestfulHref(final String restfulHref)
{
return restfulHref.substring(restfulHref.lastIndexOf("/")+1);
}
protected GetCapabilitiesRequest createGetCapabilitiesRequestWithContentSectionOnly()
{
final GetCapabilitiesRequest getCapabilitiesRequestOnlyContents = createGetCapabilitiesRequest();
final ArrayList<String> sections = new ArrayList<String>();
sections.add(bindingConstants.getSosCapabilitiesSectionNameContents());
getCapabilitiesRequestOnlyContents.setSections(sections);
return getCapabilitiesRequestOnlyContents;
}
// TODO use this to return operation not allowed response
protected OwsExceptionReport createHttpMethodForThisResourceNotSupportedException(final String httpMethod, final String resourceType)
{
final String exceptionText = String.format("The HTTP-%s %s \"%s\"!",
httpMethod,
bindingConstants.getHttpOperationNotAllowedForResourceTypeMessagePart(),
resourceType);
final OperationNotSupportedException onse = new OperationNotSupportedException(exceptionText);
return onse;
}
protected Map<String, String> getKvPEncodedParameters(final HttpServletRequest httpRequest)
{
final Map<String, String> kvp = new HashMap<String, String>();
final Enumeration<?> parameterNames = httpRequest.getParameterNames();
while (parameterNames.hasMoreElements()) {
// all key names to lower case
final String key = (String) parameterNames.nextElement();
kvp.put(key.toLowerCase(), httpRequest.getParameter(key));
}
return kvp;
}
protected String checkParameterSingleValue(final String parameterValue, final String parameterName)
throws OwsExceptionReport {
if (!parameterValue.isEmpty() && (parameterValue.split(",").length == 1)) {
return parameterValue;
} else {
final InvalidParameterValueException ipve = new InvalidParameterValueException(parameterName, parameterValue);
LOGGER.debug(ipve.getMessage());
throw ipve;
}
}
protected List<String> splitKvpParameterValueToList(final String value)
{
return Arrays.asList(value.split(bindingConstants.getKvPEncodingValueSplitter()));
}
/**
* {@link org.n52.sos.decode.SosKvpDecoderv20#parseNamespaces(String)}
*/
protected Map<String, String> parseNamespaces(final String values) {
final Map<String, String> namespaces = new HashMap<String, String>();
final List<String> array =
Arrays.asList(values.replaceAll("\\),", "").replaceAll("\\)", "").split("xmlns\\("));
for (final String string : array) {
if ((string != null) && !string.isEmpty()) {
final String[] s = string.split(",");
namespaces.put(s[0], s[1]);
}
}
return namespaces;
}
/*
* {@link org.n52.sos.decode.kvp.v2.AbstractKvpDecoder#parseTemporalFilter(List<String>, String)}
* TODO move to KVP map decoder to share code
*/
protected List<TemporalFilter> parseTemporalFilter(final List<String> parameterValues)
throws DateTimeException, InvalidParameterValueException {
final List<TemporalFilter> filterList = new ArrayList<TemporalFilter>(1);
if (parameterValues.size() != 2) {
throw new InvalidParameterValueException(
bindingConstants.getHttpGetParameterNameTemporalFilter(),
Arrays.toString(parameterValues.toArray()));
}
filterList.add(createTemporalFilterFromValue(parameterValues.get(1), parameterValues.get(0)));
return filterList;
}
/*
* {@link org.n52.sos.decode.kvp.v2.AbstractKvpDecoder#createTemporalFilterFromValue(String, String)}
* TODO move to KVP map decoder to share code
*/
private TemporalFilter createTemporalFilterFromValue(final String value, final String valueReference) throws DateTimeException, InvalidParameterValueException {
final TemporalFilter temporalFilter = new TemporalFilter();
temporalFilter.setValueReference(valueReference);
final String[] times = value.split("/");
if (times.length == 1) {
final TimeInstant ti = new TimeInstant();
if (SosIndeterminateTime.contains(times[0])) {
ti.setSosIndeterminateTime(SosIndeterminateTime.getEnumForString(times[0]));
} else {
final DateTime instant = DateTimeHelper.parseIsoString2DateTime(times[0]);
ti.setValue(instant);
ti.setRequestedTimeLength(DateTimeHelper.getTimeLengthBeforeTimeZone(times[0]));
}
temporalFilter.setOperator(TimeOperator.TM_Equals);
temporalFilter.setTime(ti);
} else if (times.length == 2) {
final 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);
final TimePeriod tp = new TimePeriod(start, end);
temporalFilter.setOperator(TimeOperator.TM_During);
temporalFilter.setTime(tp);
} else {
throw new InvalidParameterValueException(bindingConstants.getHttpGetParameterNameTemporalFilter(),value);
}
return temporalFilter;
}
/**
* {@link org.n52.sos.decode.kvp.v2.AbstractKvpDecoder#parseSpatialFilter(List<String>, String)}
* TODO move to KVP map decoder to share code
*/
protected SpatialFilter parseSpatialFilter(List<String> parameterValues, final String parameterName)
throws OwsExceptionReport {
if (!parameterValues.isEmpty()) {
if (!(parameterValues instanceof RandomAccess)) {
parameterValues = new ArrayList<String>(parameterValues);
}
final SpatialFilter spatialFilter = new SpatialFilter();
boolean hasSrid = false;
spatialFilter.setValueReference(parameterValues.get(0));
int srid = 4326;
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() != 4) {
throw new InvalidParameterValueException().
at(parameterName).
withMessage("The parameter value of '%s' is not valid!", parameterName);
}
final String lowerCorner = String.format(Locale.US, "%f %f", new Float(coordinates.get(0)), new Float(coordinates.get(1)));
final String upperCorner = String.format(Locale.US, "%f %f", new Float(coordinates.get(2)), new Float(coordinates.get(3)));
spatialFilter.setGeometry(JTSHelper.createGeometryFromWKT(JTSHelper.createWKTPolygonFromEnvelope(lowerCorner, upperCorner), srid));
spatialFilter.setOperator(SpatialOperator.BBOX);
return spatialFilter;
}
return null;
}
protected String getSrsNamePrefix() {
return ServiceConfiguration.getInstance().getSrsNamePrefix();
}
protected String getSrsNamePrefixSosV2() {
return ServiceConfiguration.getInstance().getSrsNamePrefixSosV2();
}
protected boolean isContentOfPostRequestValid(final HttpServletRequest httpRequest) throws OwsExceptionReport
{
if ((httpRequest == null) || (httpRequest.getContentType() == null))
{
final String errorMessage = "HTTP header 'Content-Type'";
LOGGER.debug("{} is missing", errorMessage);
throw new MissingParameterValueException(errorMessage);
}
if (!httpRequest.getContentType().contains(bindingConstants.getContentTypeDefault().toString()))
{
final String errorMessage = String.format("POST %s Type \"%s\" is not supported. Please use type \"%s\"",
bindingConstants.getErrorMessageWrongContentType(),
httpRequest.getContentType(),
bindingConstants.getContentTypeDefault());
LOGGER.debug(errorMessage);
throw new InvalidParameterValueException("Content-Type", httpRequest.getContentType()).
withMessage(errorMessage);
}
return true;
}
protected String createBadGetRequestMessage(final String resourceType,
final boolean globalResoureAllowed,
final boolean byIdAllowed,
final boolean searchAllowed)
{
final StringBuilder errorMsgBuilder = new StringBuilder();
errorMsgBuilder.append(String.format(bindingConstants.getErrorMessageBadGetRequest(), resourceType));
if (globalResoureAllowed)
{
errorMsgBuilder.append(String.format(bindingConstants.getErrorMessageBadGetRequestGlobalResource(), resourceType));
}
if (byIdAllowed)
{
if (globalResoureAllowed)
{
errorMsgBuilder.append(" or ");
}
errorMsgBuilder.append(String.format(bindingConstants.getErrorMessageBadGetRequestById(), resourceType));
}
if (searchAllowed)
{
if (globalResoureAllowed || byIdAllowed)
{
errorMsgBuilder.append(" or ");
}
errorMsgBuilder.append(String.format(bindingConstants.getErrorMessageBadGetRequestSearch(), resourceType));
}
errorMsgBuilder.append('.');
return errorMsgBuilder.toString();
}
}