Changed the package names and modified the README

This commit is contained in:
Rachel Walker
2012-12-11 16:29:30 -08:00
4 changed files with 75 additions and 13 deletions

View File

@@ -1,16 +1,48 @@
# spring-restful-exception-handler
### An annotation for the Spring framework to handle HTTP responses for custom exceptions.
## How to use spring-restful-exception-handler
Under PROJECT/resources create a file called *error.template*. Inside this file, place the formatted error template you want to return when a custom exception is thrown. If no custom template is given, the following default template will be used:
An annotation for the Spring framework to handle HTTP responses for custom exceptions. The way it works is you have to change the default exception resolver for Spring to our custom exception resolver by modifying your servlet xml file for Spring:
```<?xml version="1.0" encoding="UTF-8"?> <error> %s </error>```
```xml
<bean id="exceptionResolver" class="com.github.raychatter.AnnotationHandler" />
```
Annotate the custom exception class with `@Exception(*httpStatus*, *contentType*)`.
The defaults are `httpStatus = HttpStatus.INTERNAL_SERVER_ERROR` and `contentType = MediaType.APPLICATION_XML_VALUE`.
After overriding the exception resolving mechanism, just annotate the custom exception classes with `@ExceptionHandler(*httpStatus*, *contentType*)`. The defaults are `httpStatus = HttpStatus.INTERNAL_SERVER_ERROR` and `contentType = MediaType.APPLICATION_XML_VALUE`. So an example exception class would be:
```java
@ExceptionHandler(httpStatus = HttpStatus.NOT_FOUND, contentType = MediaType.APPLICATION_XML_VALUE)
public class MyCustomException extends Exception {
public MyCustomException(final String message) {
super(message);
}
}
```
The custom message is taken from the custom annotation class itself, so any parameters you'd like to insert need to be handled there.
Make sure to add `<bean id="exceptionResolver" class="com.github.raychatter.AnnotationHandler" />` to your XML.
And that's it! Just keep in mind that the exception handler will take care of all the exceptions and by default it will return `Internal Server Error` with an `XML` body described above. If you don't specify any custom template for your error responses, following error template will be used by default:
And that's it!
```xml
<?xml version="1.0" encoding="UTF-8"?>
<error>
%s
</error>
```
If you want to override the default template, make sure you have `error.template` in your classpath with `%s` for the message placeholder. An example for a `error.template` file in your classpath for a json response:
```json
{
"message": "%s"
}
```
And now you need to make sure that your exceptions are returning `json` content type.
```java
@ExceptionHandler(httpStatus = HttpStatus.CONFLICT, contentType = MediaType.APPLICATION_JSON_VALUE)
public class MyCustomException extends Exception {
public MyCustomException(final String message) {
super(message);
}
}
```

View File

@@ -23,9 +23,12 @@ public class AnnotationHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception thrownException) {
final ExceptionHandler annotation = getAnnotationFrom(thrownException);
if (annotation == null) return new ModelAndView();
try {
if (annotation == null) {
thrownException.printStackTrace();
return respondWithDefault(thrownException, response);
}
return handleException(annotation, thrownException, response);
} catch (IOException e) {
// potentially something went wrong in response itself
@@ -43,10 +46,16 @@ public class AnnotationHandler implements HandlerExceptionResolver {
final String message = formatMessage(thrownException);
response.getWriter().write(message);
} catch (IOException e) {
return respondWithDefault(thrownException, response);
}
return new ModelAndView();
}
protected ModelAndView respondWithDefault(final Exception thrownException, final HttpServletResponse response) throws IOException {
response.setContentType(MediaType.APPLICATION_XML_VALUE);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write(formatDefaultMessage(thrownException));
}
return new ModelAndView();
}

View File

@@ -240,6 +240,21 @@ public class AnnotationHandlerTest {
verify(sut).handleException(mockAnnotation, expectedException, mockResponse);
}
@Test public void resolveException_ShouldReturnDefaultErrorMessage_WhenUncheckedExceptionIsGiven() throws Exception {
final NullPointerException expectedException = mock(NullPointerException.class);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(null).when(sut).getAnnotationFrom(expectedException);
final ModelAndView view = sut.resolveException(null, mockResponse, null, expectedException);
verify(sut).respondWithDefault(expectedException, mockResponse);
}
}
@ExceptionHandler()

View File

@@ -29,4 +29,10 @@ public class HelloController {
throw new MyNegativeArraySizeException("oops");
}
@RequestMapping(value = "/unchecked", method = RequestMethod.GET)
@ResponseBody
public Object unchecked() throws Exception {
throw new NullPointerException("an npe");
}
}