DATAREST-421 - Extracted @ControllerAdvice class in order to allow easy overriding.
Original pull request: #155.
This commit is contained in:
@@ -15,53 +15,33 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
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.domain.Page;
|
||||
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
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.data.web.PagedResourcesAssembler;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.springframework.data.rest.webmvc.ControllerUtils.EMPTY_RESOURCE_LIST;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Thibaud Lepretre
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
class AbstractRepositoryRestController implements MessageSourceAware {
|
||||
class AbstractRepositoryRestController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractRepositoryRestController.class);
|
||||
|
||||
private final PagedResourcesAssembler<Object> pagedResourcesAssembler;
|
||||
private MessageSourceAccessor messageSourceAccessor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractRepositoryRestController} for the given {@link PagedResourcesAssembler}.
|
||||
@@ -74,129 +54,6 @@ class AbstractRepositoryRestController implements MessageSourceAware {
|
||||
this.pagedResourcesAssembler = pagedResourcesAssembler;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.MessageSourceAware#setMessageSource(org.springframework.context.MessageSource)
|
||||
*/
|
||||
@Override
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ NullPointerException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> handleNPE(NullPointerException npe) {
|
||||
return errorResponse(npe, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ ResourceNotFoundException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> handleNotFound() {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
@ExceptionHandler({ HttpMessageNotReadableException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<ExceptionMessage> handleNotReadable(HttpMessageNotReadableException e) {
|
||||
return badRequest(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle failures commonly thrown from code tries to read incoming data and convert or cast it to the right type.
|
||||
*
|
||||
* @param t
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler({ InvocationTargetException.class, IllegalArgumentException.class, ClassCastException.class,
|
||||
ConversionFailedException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity handleMiscFailures(Throwable t) {
|
||||
if (null != t.getCause() && t.getCause() instanceof ResourceNotFoundException) {
|
||||
return notFound();
|
||||
}
|
||||
return badRequest(t);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ RepositoryConstraintViolationException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity handleRepositoryConstraintViolationException(Locale locale,
|
||||
RepositoryConstraintViolationException rcve) {
|
||||
|
||||
return response(null, new RepositoryConstraintViolationExceptionMessage(rcve, messageSourceAccessor),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a 409 Conflict in case of concurrent modification.
|
||||
*
|
||||
* @param ex
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler({ OptimisticLockingFailureException.class, DataIntegrityViolationException.class })
|
||||
public ResponseEntity handleConflict(Exception ex) {
|
||||
return errorResponse(null, ex, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send {@code 405 Method Not Allowed} and include the supported {@link HttpMethod}s in the {@code Allow} header.
|
||||
*
|
||||
* @param o_O
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAllow(o_O.getSupportedHttpMethods());
|
||||
|
||||
return new ResponseEntity<Void>(headers, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Void> handle(ETagDoesntMatchException o_O) {
|
||||
|
||||
HttpHeaders headers = o_O.getExpectedETag().addTo(new HttpHeaders());
|
||||
return new ResponseEntity<Void>(headers, HttpStatus.PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
protected <T> ResponseEntity<T> notFound() {
|
||||
return notFound(null, null);
|
||||
}
|
||||
|
||||
protected <T> ResponseEntity<T> notFound(HttpHeaders headers, T body) {
|
||||
return response(headers, body, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
protected <T extends Throwable> ResponseEntity<ExceptionMessage> badRequest(T throwable) {
|
||||
return badRequest(null, throwable);
|
||||
}
|
||||
|
||||
protected <T extends Throwable> ResponseEntity<ExceptionMessage> badRequest(HttpHeaders headers, T throwable) {
|
||||
return errorResponse(headers, throwable, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(T throwable, HttpStatus status) {
|
||||
return errorResponse(null, throwable, status);
|
||||
}
|
||||
|
||||
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(HttpHeaders headers, T throwable,
|
||||
HttpStatus status) {
|
||||
if (null != throwable && null != throwable.getMessage()) {
|
||||
LOG.error(throwable.getMessage(), throwable);
|
||||
return response(headers, new ExceptionMessage(throwable), status);
|
||||
} else {
|
||||
return response(headers, null, status);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> response(HttpHeaders headers, T body, HttpStatus status) {
|
||||
HttpHeaders hdrs = new HttpHeaders();
|
||||
if (null != headers) {
|
||||
hdrs.putAll(headers);
|
||||
}
|
||||
return new ResponseEntity<T>(body, hdrs, status);
|
||||
}
|
||||
|
||||
protected Link resourceLink(RootResourceInformation resourceLink, Resource resource) {
|
||||
|
||||
ResourceMetadata repoMapping = resourceLink.getResourceMetadata();
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
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.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author Thibaud Lepretre
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler implements MessageSourceAware {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
|
||||
private MessageSourceAccessor messageSourceAccessor;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.MessageSourceAware#setMessageSource(org.springframework.context.MessageSource)
|
||||
*/
|
||||
@Override
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ NullPointerException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> handleNPE(NullPointerException npe) {
|
||||
return errorResponse(npe, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ ResourceNotFoundException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> handleNotFound() {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
@ExceptionHandler({ HttpMessageNotReadableException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<ExceptionMessage> handleNotReadable(HttpMessageNotReadableException e) {
|
||||
return badRequest(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle failures commonly thrown from code tries to read incoming data and convert or cast it to the right type.
|
||||
*
|
||||
* @param t
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler({ InvocationTargetException.class, IllegalArgumentException.class, ClassCastException.class,
|
||||
ConversionFailedException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity handleMiscFailures(Throwable t) {
|
||||
if (null != t.getCause() && t.getCause() instanceof ResourceNotFoundException) {
|
||||
return notFound();
|
||||
}
|
||||
return badRequest(t);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ RepositoryConstraintViolationException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity handleRepositoryConstraintViolationException(Locale locale,
|
||||
RepositoryConstraintViolationException rcve) {
|
||||
|
||||
return response(null, new RepositoryConstraintViolationExceptionMessage(rcve, messageSourceAccessor),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a 409 Conflict in case of concurrent modification.
|
||||
*
|
||||
* @param ex
|
||||
* @return HTTP Status 409 ResponseEntity
|
||||
*/
|
||||
@ExceptionHandler({ OptimisticLockingFailureException.class, DataIntegrityViolationException.class })
|
||||
public ResponseEntity handleConflict(Exception ex) {
|
||||
return errorResponse(null, ex, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send {@code 405 Method Not Allowed} and include the supported {@link org.springframework.http.HttpMethod}s in the {@code Allow} header.
|
||||
*
|
||||
* @param o_O
|
||||
* @return HTTP Status 405 ResponseEntity
|
||||
*/
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAllow(o_O.getSupportedHttpMethods());
|
||||
|
||||
return new ResponseEntity<Void>(headers, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Void> handle(ETagDoesntMatchException o_O) {
|
||||
|
||||
HttpHeaders headers = o_O.getExpectedETag().addTo(new HttpHeaders());
|
||||
return new ResponseEntity<Void>(headers, HttpStatus.PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
protected <T> ResponseEntity<T> notFound() {
|
||||
return notFound(null, null);
|
||||
}
|
||||
|
||||
protected <T> ResponseEntity<T> notFound(HttpHeaders headers, T body) {
|
||||
return response(headers, body, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
protected <T extends Throwable> ResponseEntity<ExceptionMessage> badRequest(T throwable) {
|
||||
return badRequest(null, throwable);
|
||||
}
|
||||
|
||||
protected <T extends Throwable> ResponseEntity<ExceptionMessage> badRequest(HttpHeaders headers, T throwable) {
|
||||
return errorResponse(headers, throwable, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(T throwable, HttpStatus status) {
|
||||
return errorResponse(null, throwable, status);
|
||||
}
|
||||
|
||||
public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(HttpHeaders headers, T throwable,
|
||||
HttpStatus status) {
|
||||
if (null != throwable && null != throwable.getMessage()) {
|
||||
LOG.error(throwable.getMessage(), throwable);
|
||||
return response(headers, new ExceptionMessage(throwable), status);
|
||||
} else {
|
||||
return response(headers, null, status);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> response(HttpHeaders headers, T body, HttpStatus status) {
|
||||
HttpHeaders hdrs = new HttpHeaders();
|
||||
if (null != headers) {
|
||||
hdrs.putAll(headers);
|
||||
}
|
||||
return new ResponseEntity<T>(body, hdrs, status);
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ import org.springframework.data.rest.core.support.RepositoryRelProvider;
|
||||
import org.springframework.data.rest.webmvc.BaseUri;
|
||||
import org.springframework.data.rest.webmvc.BaseUriAwareController;
|
||||
import org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping;
|
||||
import org.springframework.data.rest.webmvc.GlobalExceptionHandler;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
|
||||
@@ -547,6 +548,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
return er;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GlobalExceptionHandler globalExceptionHandler() {
|
||||
return new GlobalExceptionHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RepositoryInvokerFactory repositoryInvokerFactory() {
|
||||
return new DefaultRepositoryInvokerFactory(repositories(), defaultConversionService());
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
/**
|
||||
* @author Thibaud Lepretre
|
||||
*/
|
||||
@Configuration
|
||||
@Import(JpaRepositoryConfig.class)
|
||||
public class ControllerAdviceConfig {
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
@ControllerAdvice
|
||||
public static class CustomGlobalConfiguration {
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAllow(o_O.getSupportedHttpMethods());
|
||||
|
||||
return new ResponseEntity<Void>(headers, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.rest.webmvc.AbstractWebIntegrationTests;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* @author Thibaud Lepretre
|
||||
*/
|
||||
@ContextConfiguration(classes = ControllerAdviceConfig.class)
|
||||
public class ControllerAdviceWebTests extends AbstractWebIntegrationTests {
|
||||
@Test
|
||||
public void httpRequestMethodNotSupportedExceptionShouldNowReturnHttpStatus500Over405() throws Exception {
|
||||
|
||||
Link link = client.discoverUnique("addresses");
|
||||
|
||||
mvc.perform(get(link.getHref())).//
|
||||
andExpect(status().isInternalServerError());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<String> expectedRootLinkRels() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user