Added missing exception resolver configuration to turn exceptions into meaningful HTTP codes as well as provide sane serialization of exception stacktraces.

This commit is contained in:
Jon Brisbin
2012-07-30 15:17:37 -05:00
parent 3a8969e55f
commit b681844258
4 changed files with 40 additions and 95 deletions

View File

@@ -22,7 +22,7 @@ public class RepositoryRestConfiguration {
private String jsonpOnErrParamName = null;
private List<HttpMessageConverter<?>> customConverters = Collections.emptyList();
private MediaType defaultMediaType = MediaType.APPLICATION_JSON;
private boolean dumpErrors = false;
private boolean dumpErrors = true;
public int getDefaultPageSize() {
return defaultPageSize;

View File

@@ -31,7 +31,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -1367,38 +1366,8 @@ public class RepositoryRestController
@ResponseBody
public ResponseEntity handleNPE(NullPointerException e,
ServletServerHttpRequest request) throws IOException {
if(LOG.isErrorEnabled()) {
LOG.error(e.getMessage(), e);
}
return negotiateResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, new HttpHeaders(), null);
}
/**
* Handle {@link InvocationTargetException}s as a 400 Bad Request because these are likely to occur if, e.g. the user
* does not provide a value for a query parameter.
*
* @param e
* @param request
*
* @return
*
* @throws IOException
*/
@ExceptionHandler(InvocationTargetException.class)
@ResponseBody
public ResponseEntity handleInvocationTargetException(InvocationTargetException e,
ServletServerHttpRequest request) throws IOException {
if(LOG.isErrorEnabled()) {
LOG.error(e.getMessage(), e);
}
for(Throwable cause = e.getCause(); (null != cause && cause != e.getCause()); cause = cause.getCause()) {
if(cause instanceof InvalidDataAccessApiUsageException || cause instanceof IllegalArgumentException) {
return negotiateResponse(request, HttpStatus.BAD_REQUEST, new HttpHeaders(), null);
}
}
return negotiateResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, new HttpHeaders(), e);
LOG.error(e.getMessage(), e);
return errorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, e);
}
/**
@@ -1413,6 +1382,7 @@ public class RepositoryRestController
*/
@ExceptionHandler(
{
InvocationTargetException.class,
IllegalArgumentException.class,
ClassCastException.class
}
@@ -1420,10 +1390,8 @@ public class RepositoryRestController
@ResponseBody
public ResponseEntity handleMiscFailures(Throwable t,
ServletServerHttpRequest request) throws IOException {
if(LOG.isErrorEnabled()) {
LOG.error(t.getMessage(), t);
}
return negotiateResponse(request, HttpStatus.BAD_REQUEST, new HttpHeaders(), t);
LOG.error(t.getMessage(), t);
return errorResponse(request, HttpStatus.BAD_REQUEST, t);
}
/**
@@ -1442,11 +1410,7 @@ public class RepositoryRestController
public ResponseEntity handleLockingFailure(OptimisticLockingFailureException ex,
ServletServerHttpRequest request) throws IOException {
LOG.error(ex.getMessage(), ex);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map m = new HashMap();
m.put("message", ex.getMessage());
return negotiateResponse(request, HttpStatus.CONFLICT, headers, objectMapper.writeValueAsBytes(m));
return errorResponse(request, HttpStatus.CONFLICT, ex);
}
/**
@@ -1493,13 +1457,11 @@ public class RepositoryRestController
ServletServerHttpRequest request) throws IOException {
LOG.error(ex.getMessage(), ex);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map m = new HashMap();
m.put("message", ex.getMessage());
m.put("acceptableTypes", availableMediaTypes);
return negotiateResponse(request, HttpStatus.BAD_REQUEST, headers, objectMapper.writeValueAsBytes(m));
return negotiateResponse(request, HttpStatus.BAD_REQUEST, new HttpHeaders(), m);
}
/*
@@ -1657,10 +1619,30 @@ public class RepositoryRestController
&& !config.getSortParamName().equals(name));
}
@SuppressWarnings({"unchecked"})
private Map throwableToMap(Throwable t) {
Map m = new HashMap();
m.put("message", t.getMessage());
if(null != t.getCause()) {
m.put("cause", throwableToMap(t.getCause()));
}
return m;
}
private ResponseEntity<byte[]> notFoundResponse(ServletServerHttpRequest request) throws IOException {
return negotiateResponse(request, HttpStatus.NOT_FOUND, new HttpHeaders(), null);
}
@SuppressWarnings({"unchecked"})
private ResponseEntity<byte[]> errorResponse(ServletServerHttpRequest request, HttpStatus status, Throwable t)
throws IOException {
Object body = null;
if(config.isDumpErrors()) {
body = throwableToMap(t);
}
return negotiateResponse(request, status, new HttpHeaders(), body);
}
@SuppressWarnings({"unchecked"})
private ResponseEntity<byte[]> negotiateResponse(final ServletServerHttpRequest request,
final HttpStatus status,

View File

@@ -1,5 +1,7 @@
package org.springframework.data.rest.webmvc;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -7,7 +9,8 @@ import org.springframework.context.annotation.ImportResource;
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
/**
* @author Jon Brisbin <jbrisbin@vmware.com>
@@ -58,4 +61,12 @@ public class RepositoryRestMvcConfiguration {
return new RepositoryRestHandlerMapping();
}
@Bean public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
ExceptionHandlerExceptionResolver er = new ExceptionHandlerExceptionResolver();
er.setCustomArgumentResolvers(
Arrays.<HandlerMethodArgumentResolver>asList(new ServerHttpRequestMethodArgumentResolver())
);
return er;
}
}

View File

@@ -1,48 +0,0 @@
package org.springframework.data.rest.webmvc;
import java.io.IOException;
import java.io.PrintWriter;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
/**
* @author Jon Brisbin
*/
public class ThrowableHttpMessageConverter extends AbstractHttpMessageConverter<Throwable> {
private final ObjectMapper mapper = new ObjectMapper();
@Override protected boolean supports(Class<?> clazz) {
throw new IllegalStateException("supports(Class<?> clazz) not used in " + getClass().getName());
}
@Override public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return (Throwable.class.isAssignableFrom(clazz)
&& (mediaType.getSubtype().contains("json") || mediaType.getSubtype().contains("text")));
}
@Override protected Throwable readInternal(Class<? extends Throwable> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new HttpMessageNotReadableException("Cannot read Throwables from input.");
}
@Override protected void writeInternal(Throwable throwable, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if(outputMessage.getHeaders().getContentType().getSubtype().contains("json")) {
outputMessage.getBody().write(mapper.writeValueAsBytes(throwable));
} else {
throwable.printStackTrace(new PrintWriter(outputMessage.getBody()));
}
}
}