
org.n52.web.ctrl.BaseController Maven / Gradle / Ivy
The newest version!
/*
* 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.web.ctrl;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.n52.io.Constants;
import org.n52.io.HrefHelper;
import org.n52.io.IoParseException;
import org.n52.io.request.IoParameters;
import org.n52.io.request.Parameters;
import org.n52.io.request.RequestSimpleParameterSet;
import org.n52.io.request.RequestStyledParameterSet;
import org.n52.web.common.RequestUtils;
import org.n52.web.exception.BadQueryParameterException;
import org.n52.web.exception.BadRequestException;
import org.n52.web.exception.ExceptionResponse;
import org.n52.web.exception.InternalServerException;
import org.n52.web.exception.ResourceNotFoundException;
import org.n52.web.exception.WebException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
/**
*
* Serves as central {@link ExceptionHandler} for all Web bindings inheriting from this class.
* {@link WebException}s indicate an expected workflows while unexpected exceptions are automatically wrapped
* to {@link InternalServerException}s as fallback.
*
*
* Developers should consider to add hints via {@link WebException#addHint(String)} so that as much
* information is communicated to the caller as possible.
*
*/
@RestController
public abstract class BaseController {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourcesController.class);
private static final String REFER_TO_API_SYNTAX = "Refer to the API documentation and check parameter "
+ "value against required syntax!";
private static final String INVALID_REQUEST_BODY = "Check the request body which has been sent to the "
+ "server. Probably it is not valid.";
private static final String HEADER_ACCEPT = "Accept";
@Value("${external.url:http://localhost:8080/api}")
private String externalUrl;
public String getExternalUrl() {
return externalUrl;
}
public void setExternalUrl(String externalUrl) {
this.externalUrl = RequestUtils.resolveQueryLessRequestUrl(externalUrl);
}
public String createCollectionUrl(String collectionName) {
return HrefHelper.constructHref(getExternalUrl(), collectionName);
}
protected BiConsumer getExceptionHandle() {
return (parameter, e) -> {
BadRequestException ex = new BadRequestException("Invalid '" + parameter + "' parameter.", e);
throw ex.addHint(REFER_TO_API_SYNTAX)
.addHint(e.getMessage())
.addHint(e.getHints());
};
}
protected IoParameters createParameters(RequestSimpleParameterSet query, String locale,
HttpServletResponse response) {
return createParameters(query.toParameters(), locale, response);
}
protected IoParameters createParameters(RequestStyledParameterSet query, String locale,
HttpServletResponse response) {
return createParameters(query.toParameters(), locale, response);
}
protected IoParameters createParameters(MultiValueMap query, String locale,
HttpServletResponse response) {
return createParameters(IoParameters.createFromMultiValueMap(query), locale, response);
}
protected IoParameters createParameters(String datasetId, MultiValueMap query, String locale,
HttpServletResponse response) {
IoParameters parameters =
IoParameters.createFromMultiValueMap(query).replaceWith(Parameters.DATASETS, datasetId);
return createParameters(parameters, locale, response);
}
protected IoParameters createParameters(Map query, String locale, HttpServletResponse response) {
return createParameters(IoParameters.createFromSingleValueMap(query), locale, response);
}
protected IoParameters createParameters(String datasetId, Map query, String locale,
HttpServletResponse response) {
IoParameters parameters =
IoParameters.createFromSingleValueMap(query).replaceWith(Parameters.DATASETS, datasetId);
return createParameters(parameters, locale, response);
}
private IoParameters createParameters(IoParameters parameters, String locale, HttpServletResponse response) {
if (parameters != null && response != null) {
addCacheHeader(parameters, response);
}
return RequestUtils.overrideQueryLocaleWhenSet(locale, parameters)
.setParseExceptionHandle(getExceptionHandle());
}
protected boolean isRequestingJsonData(HttpServletRequest request) {
return Constants.MimeType.APPLICATION_JSON.getMimeType()
.equals(getAcceptHeader(request));
}
protected boolean isRequestingPdfData(HttpServletRequest request) {
return Constants.MimeType.APPLICATION_PDF.getMimeType()
.equals(getAcceptHeader(request));
}
protected boolean isRequestingPngData(HttpServletRequest request) {
return Constants.MimeType.IMAGE_PNG.getMimeType()
.equals(getAcceptHeader(request));
}
private static String getAcceptHeader(HttpServletRequest request) {
return request.getHeader(HEADER_ACCEPT);
}
@ExceptionHandler(value = {
BadRequestException.class,
BadQueryParameterException.class
})
public void handle400(Exception e, HttpServletRequest request, HttpServletResponse response) {
writeExceptionResponse((WebException) e, response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(value = ResourceNotFoundException.class)
public void handle404(Exception e, HttpServletRequest request, HttpServletResponse response) {
writeExceptionResponse((WebException) e, response, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(value = InternalServerException.class)
public void handle500(Exception e, HttpServletRequest request, HttpServletResponse response) {
writeExceptionResponse((WebException) e, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(value = {
RuntimeException.class,
Exception.class,
Throwable.class
})
public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) {
if (e instanceof HttpMessageNotReadableException) {
WebException ex = new BadRequestException("Invalid Request", e).addHint(INVALID_REQUEST_BODY)
.addHint(REFER_TO_API_SYNTAX);
writeExceptionResponse(ex, response, HttpStatus.BAD_REQUEST);
} else {
WebException wrappedException = new InternalServerException("Unexpected Exception occured.", e);
writeExceptionResponse(wrappedException, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private void writeExceptionResponse(WebException e, HttpServletResponse response, HttpStatus status) {
final String logMessage = "An exception occured.";
if (status == HttpStatus.INTERNAL_SERVER_ERROR) {
LOGGER.error(logMessage, e);
} else {
LOGGER.debug(logMessage, e);
}
// TODO consider using a 'suppress_response_codes=true' parameter and always return 200 OK
response.setStatus(status.value());
response.setContentType(Constants.MimeType.APPLICATION_JSON.getMimeType());
ObjectMapper objectMapper = createObjectMapper();
ObjectWriter writer = objectMapper.writerFor(ExceptionResponse.class);
ExceptionResponse exceptionResponse = ExceptionResponse.createExceptionResponse(e, status);
try (OutputStream outputStream = response.getOutputStream()) {
writer.writeValue(outputStream, exceptionResponse);
} catch (IOException ioe) {
LOGGER.error("Could not process error message.", ioe);
}
}
protected ObjectMapper createObjectMapper() {
return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
protected abstract void addCacheHeader(IoParameters parameter, HttpServletResponse response);
protected void addCacheHeader(HttpServletResponse response, long maxAge) {
String maxAgeHeader = maxAge > 0
? CacheControl.maxAge(maxAge, TimeUnit.MINUTES).getHeaderValue()
: CacheControl.noStore().getHeaderValue();
response.setHeader(HttpHeaders.CACHE_CONTROL, maxAgeHeader);
}
protected String getResourcePathFrom(String path) {
return path.substring(path.lastIndexOf("/") + 1);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy