/**
* 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;
import static org.n52.sos.util.http.HTTPStatus.INTERNAL_SERVER_ERROR;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.n52.sos.binding.Binding;
import org.n52.sos.binding.rest.decode.RestDecoder;
import org.n52.sos.binding.rest.encode.RestEncoder;
import org.n52.sos.binding.rest.requests.BadRequestException;
import org.n52.sos.binding.rest.requests.RequestHandler;
import org.n52.sos.binding.rest.requests.RestRequest;
import org.n52.sos.binding.rest.requests.RestResponse;
import org.n52.sos.binding.rest.resources.OptionsRequestHandler;
import org.n52.sos.binding.rest.resources.OptionsRestRequest;
import org.n52.sos.binding.rest.resources.ServiceEndpointRequest;
import org.n52.sos.binding.rest.resources.ServiceEndpointRequestHandler;
import org.n52.sos.binding.rest.resources.capabilities.CapabilitiesRequest;
import org.n52.sos.binding.rest.resources.capabilities.CapabilitiesRequestHandler;
import org.n52.sos.binding.rest.resources.features.FeaturesRequest;
import org.n52.sos.binding.rest.resources.features.FeaturesRequestHandler;
import org.n52.sos.binding.rest.resources.observations.IObservationsRequest;
import org.n52.sos.binding.rest.resources.observations.ObservationsRequestHandler;
import org.n52.sos.binding.rest.resources.offerings.OfferingsRequest;
import org.n52.sos.binding.rest.resources.offerings.OfferingsRequestHandler;
import org.n52.sos.binding.rest.resources.sensors.ISensorsRequest;
import org.n52.sos.binding.rest.resources.sensors.SensorsRequestHandler;
import org.n52.sos.decode.Decoder;
import org.n52.sos.encode.Encoder;
import org.n52.sos.encode.EncoderKey;
import org.n52.sos.encode.ExceptionEncoderKey;
import org.n52.sos.encode.XmlEncoderKey;
import org.n52.sos.event.SosEventBus;
import org.n52.sos.event.events.ExceptionEvent;
import org.n52.sos.exception.CodedException;
import org.n52.sos.exception.HTTPException;
import org.n52.sos.exception.ows.MissingParameterValueException;
import org.n52.sos.exception.ows.NoApplicableCodeException;
import org.n52.sos.exception.ows.OwsExceptionCode;
import org.n52.sos.exception.ows.concrete.NoEncoderForKeyException;
import org.n52.sos.ogc.ows.OwsExceptionReport;
import org.n52.sos.request.RequestContext;
import org.n52.sos.response.ServiceResponse;
import org.n52.sos.coding.CodingRepository;
import org.n52.sos.util.XmlOptionsHelper;
import org.n52.sos.util.http.HTTPStatus;
import org.n52.sos.util.http.HTTPUtils;
import org.n52.sos.util.http.MediaTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author <a href="mailto:e.h.juerrens@52north.org">Eike Hinderk
* Jürrens</a>
*/
public class RestBinding extends Binding {
private static final Logger LOGGER = LoggerFactory.getLogger(RestBinding.class);
private final Set<String> conformanceClasses;
private final Constants bindingConstants;
public RestBinding() {
bindingConstants = Constants.getInstance();
conformanceClasses = new HashSet<String>(0);
conformanceClasses.add(bindingConstants.getConformanceClass());
}
@Override
public Set<String> getConformanceClasses() {
return Collections.unmodifiableSet(conformanceClasses);
}
@Override
public String getUrlPattern() {
return bindingConstants.getUrlPattern();
}
@Override
public void doOptionsOperation(HttpServletRequest request,
HttpServletResponse response)
throws HTTPException, IOException {
if (request.getPathInfo() == null ||
request.getPathInfo().isEmpty()) {
super.doOptionsOperation(request, response);
} else {
doOperation(request, response);
}
}
@Override
public void doDeleteOperation(HttpServletRequest request,
HttpServletResponse response)
throws HTTPException, IOException {
doOperation(request, response);
}
@Override
public void doPutOperation(HttpServletRequest request,
HttpServletResponse response)
throws HTTPException, IOException {
doOperation(request, response);
}
@Override
public void doPostOperation(HttpServletRequest request,
HttpServletResponse response)
throws HTTPException, IOException {
doOperation(request, response);
}
@Override
public void doGetOperation(HttpServletRequest request,
HttpServletResponse response)
throws HTTPException, IOException {
doOperation(request, response);
}
/*
* (non-Javadoc)
*
* @see org.n52.sos.binding.rest.Binding#doGetOperation(javax.servlet.http.
* HttpServletRequest)
*
* INPUT: /rest/RESOURCE{/id|?kvps}
*
* EXAMPLE: /rest/sensors/0815 => OGC::DescribeSensor in format SensorML
* 1.0.1 /rest/features?bbox=52.0,7.0,50.0,8.0 for all features in the given
* BBox
*/
protected void doOperation(HttpServletRequest request,
HttpServletResponse response)
throws HTTPException, IOException{
ServiceResponse serviceResponse;
try {
serviceResponse = handleRequest(request, response);
} catch (final OwsExceptionReport oer) {
LOGGER.error("Error while processing rest request. Exception thrown: {}",
oer.getClass().getSimpleName());
SosEventBus.fire(new ExceptionEvent(oer));
serviceResponse = encodeOwsExceptionReport(oer);
}
HTTPUtils.writeObject(request, response, serviceResponse);
}
private ServiceResponse encodeOwsExceptionReport(OwsExceptionReport oer) throws HTTPException, IOException {
try {
ExceptionEncoderKey key = new ExceptionEncoderKey(MediaTypes.TEXT_XML);
Encoder<XmlObject, OwsExceptionReport> encoder =
CodingRepository.getInstance().getEncoder(key);
if (encoder == null) {
throw new HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR,
new NoEncoderForKeyException(key));
}
XmlObject encoded = encoder.encode(oer);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
encoded.save(baos, XmlOptionsHelper.getInstance().getXmlOptions());
baos.flush();
return new ServiceResponse(null, MediaTypes.TEXT_XML, getResponseCode(oer));
} catch (OwsExceptionReport ex) {
throw new HTTPException(INTERNAL_SERVER_ERROR, ex);
}
}
private boolean isOperationNotSupportedException(final CodedException owsE) {
return owsE.hasMessage() &&
owsE.getMessage().contains(bindingConstants.getSosErrorMessageOperationNotSupportedStart()) &&
owsE.getMessage().contains(bindingConstants.getSosErrorMessageOperationNotSupportedEnd());
}
private boolean isMethodeNotAllowedExceptionForResourceType(final CodedException owsE) {
return owsE.hasMessage() &&
owsE.getMessage().contains(bindingConstants.getHttpOperationNotAllowedForResourceTypeMessagePart());
}
private RestRequest decodeHttpRequest(final HttpServletRequest request) throws OwsExceptionReport
{
final Decoder<RestRequest, HttpServletRequest> decoder = getDecoder();
if (decoder == null) {
final String exceptionText = String.format("No decoder implementation is available for RestBinding and namespace \"%s\"!",
bindingConstants.getEncodingNamespace());
LOGGER.debug(exceptionText);
throw new NoApplicableCodeException().withMessage(exceptionText);
}
// 2 decode request
final RestRequest restRequest = decoder.decode(request);
if (restRequest == null) {
final String exceptionText = "Decoding of request failed but no exception is thrown.";
LOGGER.debug(exceptionText);
throw new NoApplicableCodeException().withMessage(exceptionText).setStatus(INTERNAL_SERVER_ERROR);
}
if (restRequest.hasAbstractServiceRequest()) {
restRequest.getAbstractServiceRequest().setRequestContext(getRequestContext(request));
}
return restRequest;
}
private ServiceResponse encodeRestResponse(final RestResponse restResponse) throws OwsExceptionReport
{
ServiceResponse response;
final RestEncoder encoder = getEncoder();
if (encoder == null) {
final String exceptionText = String.format("No encoder implementation is available for RestBinding and namespace \"%s\"!",
bindingConstants.getEncodingNamespace());
LOGGER.debug(exceptionText);
throw new NoApplicableCodeException().withMessage(exceptionText);
}
response = encoder.encode(restResponse);
if (response == null) {
final String exceptionText = String.format("Encoding of response \"%s\" failed with encoder \"%s\" but no exception is thrown.",
restResponse.getClass().getName(),
encoder.getClass().getName());
LOGGER.debug(exceptionText);
throw new NoApplicableCodeException().withMessage(exceptionText);
}
return response;
}
private RestResponse handleRestRequest(final RestRequest restRequest) throws OwsExceptionReport
{
// 3 get request handler
final RequestHandler requestHandler = getRequestHandler(restRequest);
LOGGER.debug("RequestHandler of type {} found for RestRequest of type {}",
requestHandler.getClass().getName(),
restRequest != null ? restRequest.getClass().getName() : restRequest);
try {
RestResponse sdcResponse = requestHandler.handleRequest(restRequest);
if (sdcResponse == null) {
final String exceptionText = "Processing of rest request failed but no exception is thrown.";
LOGGER.debug(exceptionText);
throw new NoApplicableCodeException().withMessage(exceptionText);
}
return sdcResponse;
}catch(final XmlException xe) {
final String exceptionText = String.format("Processing of rest request response failed. Exception thrown: %s",
xe.getMessage());
LOGGER.debug(exceptionText,xe);
throw new NoApplicableCodeException().withMessage(exceptionText).causedBy(xe);
} catch (final IOException e) {
final String exceptionText = String.format("Processing of rest request response failed. Exception thrown: %s",
e.getMessage());
LOGGER.debug(exceptionText,e);
throw new NoApplicableCodeException().withMessage(exceptionText).causedBy(e);
}
}
private RestEncoder getEncoder() throws OwsExceptionReport
{
final EncoderKey key = new XmlEncoderKey(bindingConstants.getEncodingNamespace(), RestResponse.class);
final Encoder<?,?> encoder = CodingRepository.getInstance().getEncoder(key);
if (encoder instanceof RestEncoder) {
return (RestEncoder) encoder;
}
return null;
}
private RequestHandler getRequestHandler(final RestRequest restRequest)
throws OwsExceptionReport {
if ((restRequest != null)) {
if (restRequest instanceof ISensorsRequest) {
return new SensorsRequestHandler();
} else if (restRequest instanceof IObservationsRequest) {
return new ObservationsRequestHandler();
} else if (restRequest instanceof CapabilitiesRequest) {
return new CapabilitiesRequestHandler();
} else if (restRequest instanceof OfferingsRequest) {
return new OfferingsRequestHandler();
} else if (restRequest instanceof FeaturesRequest) {
return new FeaturesRequestHandler();
} else if (restRequest instanceof OptionsRestRequest) {
return new OptionsRequestHandler();
} else if (restRequest instanceof ServiceEndpointRequest) {
return new ServiceEndpointRequestHandler();
}
}
throw new MissingParameterValueException(bindingConstants.getResourceType());
}
private RestDecoder getDecoder() throws OwsExceptionReport {
final Set<Decoder<?, ?>> decoders = CodingRepository.getInstance().getDecoders();
for (final Decoder<?,?> decoder : decoders) {
if (decoder instanceof RestDecoder) {
return (RestDecoder) decoder;
}
}
return null;
}
private HTTPStatus getResponseCode(final OwsExceptionReport oer) {
for (final CodedException e : oer.getExceptions()) {
if (e.getCode().equals(OwsExceptionCode.OperationNotSupported)) {
if (isOperationNotSupportedException(e)) {
return HTTPStatus.BAD_REQUEST;
}
} else if (e.getCode().equals(OwsExceptionCode.NoApplicableCode)) {
if (isMethodeNotAllowedExceptionForResourceType(e)) {
return HTTPStatus.METHOD_NOT_ALLOWED;
} else if (e.getCause() instanceof BadRequestException) {
return HTTPStatus.BAD_REQUEST;
} else if (e.hasMessage() &&
e.getMessage().contains(bindingConstants.getErrorMessageWrongContentType())) {
return HTTPStatus.UNSUPPORTED_MEDIA_TYPE;
} else if (e.hasMessage() &&
e.getMessage().contains(bindingConstants.getErrorMessageWrongContentTypeInAcceptHeader())) {
return HTTPStatus.NOT_ACCEPTABLE;
} else if (e.hasMessage() &&
e.getMessage().contains("HTTP method") &&
e.getMessage().contains("not allowed")) {
return HTTPStatus.METHOD_NOT_ALLOWED;
}
} else if (e.getCode().equals(OwsExceptionCode.InvalidParameterValue)) {
return HTTPStatus.BAD_REQUEST;
}
}
return HTTPStatus.INTERNAL_SERVER_ERROR;
}
private ServiceResponse handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws OwsExceptionReport {
ServiceResponse serviceResponse;
LOGGER.debug("Start handling of REST request. URI:{}",
request.getRequestURI());
// Decode the request
final RestRequest restRequest = decodeHttpRequest(request);
LOGGER.debug("Rest request decoded to {}",
restRequest != null ? restRequest.getClass().getName() : null);
// Handle the request
final RestResponse restResponse = handleRestRequest(restRequest);
LOGGER.debug("Rest request handled. DeleteObservationResponse received: {}",
restResponse.getClass().getName());
// Encode the response
serviceResponse = encodeRestResponse(restResponse);
LOGGER.debug("Rest response encoded. DeleteObservationResponse received: {}",
response != null ? response.getClass().getName() : null);
LOGGER.debug("Handling of REST request finished. Returning response to web tier");
return serviceResponse;
}
protected RequestContext getRequestContext(HttpServletRequest req) {
return RequestContext.fromRequest(req);
}
}