DATAREST-421 - Extracted @ControllerAdvice class in order to allow easy overriding.

Original pull request: #155.
This commit is contained in:
kakawait
2014-12-04 23:13:46 +01:00
committed by Oliver Gierke
parent 7f990b006b
commit e13be6badc
5 changed files with 235 additions and 151 deletions

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}