/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import java.lang.reflect.InvocationTargetException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
import org.springframework.data.rest.webmvc.support.ETagDoesntMatchException;
import org.springframework.data.rest.webmvc.support.ExceptionMessage;
import org.springframework.data.rest.webmvc.support.RepositoryConstraintViolationExceptionMessage;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* Exception handler for Spring Data REST controllers.
*
* @author Thibaud Lepretre
* @author Oliver Gierke
*/
@ControllerAdvice(basePackageClasses = RepositoryRestExceptionHandler.class)
public class RepositoryRestExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestExceptionHandler.class);
private final MessageSourceAccessor messageSourceAccessor;
/**
* Creates a new {@link RepositoryRestExceptionHandler} using the given {@link MessageSource}.
*
* @param messageSource must not be {@literal null}.
*/
public RepositoryRestExceptionHandler(MessageSource messageSource) {
Assert.notNull(messageSource, "MessageSource must not be null!");
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
}
/**
* Handles {@link ResourceNotFoundException} by returning {@code 404 Not Found}.
*
* @param o_O the exception to handle.
* @return
*/
@ExceptionHandler
ResponseEntity<?> handleNotFound(ResourceNotFoundException o_O) {
return notFound(new HttpHeaders());
}
/**
* Handles {@link HttpMessageNotReadableException} by returning {@code 400 Bad Request}.
*
* @param o_O the exception to handle.
* @return
*/
@ExceptionHandler
ResponseEntity<ExceptionMessage> handleNotReadable(HttpMessageNotReadableException o_O) {
return badRequest(new HttpHeaders(), o_O);
}
/**
* Handle failures commonly thrown from code tries to read incoming data and convert or cast it to the right type by
* returning {@code 500 Internal Server Error} and the thrown exception marshalled into JSON.
*
* @param o_O the exception to handle.
* @return
*/
@ExceptionHandler({ InvocationTargetException.class, IllegalArgumentException.class, ClassCastException.class,
ConversionFailedException.class, NullPointerException.class })
ResponseEntity<ExceptionMessage> handleMiscFailures(Exception o_O) {
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, new HttpHeaders(), o_O);
}
/**
* Handles {@link RepositoryConstraintViolationException}s by returning {@code 400 Bad Request}.
*
* @param o_O the exception to handle.
* @return
*/
@ExceptionHandler
ResponseEntity<RepositoryConstraintViolationExceptionMessage> handleRepositoryConstraintViolationException(
RepositoryConstraintViolationException o_O) {
return response(HttpStatus.BAD_REQUEST, new HttpHeaders(), new RepositoryConstraintViolationExceptionMessage(o_O,
messageSourceAccessor));
}
/**
* Send a {@code 409 Conflict} in case of concurrent modification.
*
* @param o_O the exception to handle.
* @return
*/
@ExceptionHandler({ OptimisticLockingFailureException.class, DataIntegrityViolationException.class })
ResponseEntity<ExceptionMessage> handleConflict(Exception o_O) {
return errorResponse(HttpStatus.CONFLICT, new HttpHeaders(), o_O);
}
/**
* Send {@code 405 Method Not Allowed} and include the supported {@link org.springframework.http.HttpMethod}s in the
* {@code Allow} header.
*
* @param o_O the exception to handle.
* @return
*/
@ExceptionHandler
ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {
HttpHeaders headers = new HttpHeaders();
headers.setAllow(o_O.getSupportedHttpMethods());
return response(HttpStatus.METHOD_NOT_ALLOWED, headers);
}
/**
* Handles {@link ETagDoesntMatchException} by returning {@code 412 Precondition Failed}.
*
* @param o_O the exception to handle.
* @return
*/
@ExceptionHandler
ResponseEntity<Void> handle(ETagDoesntMatchException o_O) {
HttpHeaders headers = o_O.getExpectedETag().addTo(new HttpHeaders());
return response(HttpStatus.PRECONDITION_FAILED, headers);
}
private static ResponseEntity<?> notFound(HttpHeaders headers) {
return response(HttpStatus.NOT_FOUND, headers, null);
}
private static ResponseEntity<ExceptionMessage> badRequest(HttpHeaders headers, Exception throwable) {
return errorResponse(HttpStatus.BAD_REQUEST, headers, throwable);
}
private static ResponseEntity<ExceptionMessage> errorResponse(HttpStatus status, HttpHeaders headers,
Exception exception) {
if (exception != null) {
String message = exception.getMessage();
LOG.error(message, exception);
if (StringUtils.hasText(message)) {
return response(status, headers, new ExceptionMessage(exception));
}
}
return response(status, headers, null);
}
private static <T> ResponseEntity<T> response(HttpStatus status, HttpHeaders headers) {
return response(status, headers, null);
}
private static <T> ResponseEntity<T> response(HttpStatus status, HttpHeaders headers, T body) {
Assert.notNull(headers, "Headers must not be null!");
Assert.notNull(status, "HttpStatus must not be null!");
return new ResponseEntity<T>(body, headers, status);
}
}