Compare commits
47 Commits
spring-res
...
spring-res
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a4f4b59db | ||
|
|
572053174e | ||
|
|
bca69232cb | ||
|
|
65640be661 | ||
|
|
61dae14384 | ||
|
|
c579b866c6 | ||
|
|
a099e1f8c9 | ||
|
|
95fef7d449 | ||
|
|
7f00ffbbe9 | ||
|
|
121af77b49 | ||
|
|
c9f5121992 | ||
|
|
c052294254 | ||
|
|
f6a1e54f19 | ||
|
|
d482a60153 | ||
|
|
33a95e1d40 | ||
|
|
d998bfe694 | ||
|
|
67698be443 | ||
|
|
78b2c8a3b9 | ||
|
|
528a58d881 | ||
|
|
78d9cb2ca8 | ||
|
|
d4e9474f13 | ||
|
|
84d3407507 | ||
|
|
7f8ddd5157 | ||
|
|
af6d3d1596 | ||
|
|
90799b3a51 | ||
|
|
4ab8856d8b | ||
|
|
d27a26fa8c | ||
|
|
9010542e85 | ||
|
|
b74ccb0627 | ||
|
|
4cd8bbdaa4 | ||
|
|
ef7d2b35a4 | ||
|
|
61ff23fac4 | ||
|
|
5c8d5a1289 | ||
|
|
62801050cd | ||
|
|
9f58c840cd | ||
|
|
a61e09f1b3 | ||
|
|
4205cc8805 | ||
|
|
138d0e78be | ||
|
|
88396fcd75 | ||
|
|
551b28c66d | ||
|
|
f49b17393c | ||
|
|
ebf8913f47 | ||
|
|
93b9c352e4 | ||
|
|
c93940b7fa | ||
|
|
ff02eaf3de | ||
|
|
10559a8d25 | ||
|
|
6b289c96d5 |
29
README.md
29
README.md
@@ -1,11 +1,28 @@
|
||||
# spring-restful-exception-handler
|
||||
|
||||
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:
|
||||
An annotation for the Spring framework to handle HTTP responses for custom exceptions.
|
||||
|
||||
### Bean Setup and Properties:
|
||||
|
||||
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
|
||||
<bean id="exceptionResolver" class="com.github.raychatter.AnnotationHandler" />
|
||||
<bean id="exceptionResolver" class="com.github.raychatter.AnnotationHandler">
|
||||
<property name="useHandledExceptionMessage" value="true"/>
|
||||
<property name="useGetCause" value="true"/>
|
||||
</bean>
|
||||
```
|
||||
|
||||
There are two boolean properties that can be set within the spring-restful-exception-handler bean:
|
||||
|
||||
`useGetCause` (true by default)
|
||||
If an unannotated exception is thrown and `useGetCause` is true, the spring-restful-exception-handler will call `getCause()` on the exception until the first annotated exception is found. Provided there is no annotated exception or `useGetcause` is set to false, the default httpStatus and contentType will be used with the thrown exception.
|
||||
|
||||
`useHandledExceptionMessage` (true by default)
|
||||
Sometimes it is useful to give the user a "higher level" exception response message than the one associated with the handled (annotated) exception. When `useHandledExceptionMessage` is true (which is the default setting), the message returned will be that from the annotated exception. If this property or `useGetCause` is set to false or there is no annotated exception, the message used will be from the thrown exception.
|
||||
|
||||
### Annotate the Custom Exception Class:
|
||||
|
||||
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
|
||||
@@ -19,7 +36,11 @@ public class MyCustomException extends Exception {
|
||||
|
||||
The custom message is taken from the custom annotation class itself, so any parameters you'd like to insert need to be handled there.
|
||||
|
||||
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! 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.
|
||||
|
||||
### Error Response Template:
|
||||
|
||||
If you don't specify any custom template for your error responses, following error template will be used by default:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -46,3 +67,5 @@ public class MyCustomException extends Exception {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And that's it!
|
||||
|
||||
22
pom.xml
22
pom.xml
@@ -18,7 +18,7 @@
|
||||
<inceptionYear>2012</inceptionYear>
|
||||
<groupId>com.github.raychatter</groupId>
|
||||
<artifactId>spring-restful-exception-handler</artifactId>
|
||||
<version>1.0.3</version>
|
||||
<version>2.0.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
@@ -66,7 +66,7 @@
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.9.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -94,9 +94,6 @@
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<classifier>no-dependencies</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
@@ -182,19 +179,6 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
@@ -248,7 +232,7 @@
|
||||
<connection>scm:git:git@github.com:raychatter/spring-restful-exception-handler.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:raychatter/spring-restful-exception-handler.git</developerConnection>
|
||||
<url>git@github.com:raychatter/spring-restful-exception-handler.git</url>
|
||||
<tag>spring-restful-exception-handler-1.0.3</tag>
|
||||
<tag>spring-restful-exception-handler-2.0.1</tag>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
|
||||
@@ -18,59 +18,96 @@ public class AnnotationHandler implements HandlerExceptionResolver {
|
||||
protected static final String USER_TEMPLATE = "error.template";
|
||||
protected static final String DEFAULT_TEMPLATE = "defaults/default.template";
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
private static final String DEFAULT_CONTENT_TYPE = MediaType.APPLICATION_XML_VALUE;
|
||||
private static final int DEFAULT_STATUS_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||
private boolean useHandledExceptionMessage = true; //use the message from the annotated exception
|
||||
private boolean useGetCause = true;
|
||||
|
||||
public void setUseHandledExceptionMessage(boolean flag) {
|
||||
useHandledExceptionMessage = flag;
|
||||
}
|
||||
|
||||
public void setUseGetCause(boolean flag) {
|
||||
useGetCause = flag;
|
||||
}
|
||||
|
||||
//TODO: When there's a wrapper exception leave it unannotated… call the e.getCause() method… until there is an annotated exception or if there are no more causes (e.geCause()==null)??.
|
||||
@Override
|
||||
public ModelAndView resolveException(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception thrownException) {
|
||||
final ExceptionHandler annotation = getAnnotationFrom(thrownException);
|
||||
final Exception handledException = getHandledException(thrownException);
|
||||
final ExceptionHandler annotation = getAnnotationFrom(handledException);
|
||||
final Exception messageException = getMessageException(thrownException, handledException);
|
||||
|
||||
try {
|
||||
if (annotation == null) {
|
||||
thrownException.printStackTrace();
|
||||
return respondWithDefault(thrownException, response);
|
||||
handledException.printStackTrace();
|
||||
return respondWithDefault(messageException, response);
|
||||
}
|
||||
|
||||
return handleException(annotation, thrownException, response);
|
||||
return handleException(annotation, messageException, response);
|
||||
} catch (IOException e) {
|
||||
// potentially something went wrong in response itself
|
||||
// potentially something went wrong in the response itself
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
protected ModelAndView handleException(final ExceptionHandler annotation, final Exception thrownException, final HttpServletResponse response) throws IOException {
|
||||
protected Exception getHandledException(final Exception thrownException) {
|
||||
if(useGetCause) {
|
||||
return getAnnotatedException(thrownException);
|
||||
}
|
||||
return thrownException;
|
||||
}
|
||||
|
||||
// This only matters if the user decides to use getCause
|
||||
protected Exception getMessageException(final Exception thrownException, final Exception annotatedException) {
|
||||
if(useHandledExceptionMessage) {
|
||||
return annotatedException;
|
||||
}
|
||||
return thrownException;
|
||||
}
|
||||
|
||||
protected ModelAndView handleException(final ExceptionHandler annotation, final Exception handledException, final HttpServletResponse response) throws IOException {
|
||||
response.setContentType(annotation.contentType());
|
||||
response.setStatus(annotation.httpStatus().value());
|
||||
|
||||
try {
|
||||
final String message = formatMessage(thrownException);
|
||||
final String message = formatMessage(handledException);
|
||||
response.getWriter().write(message);
|
||||
} catch (IOException e) {
|
||||
return respondWithDefault(thrownException, response);
|
||||
return respondWithDefault(handledException, 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));
|
||||
protected ModelAndView respondWithDefault(final Exception handledException, final HttpServletResponse response) throws IOException {
|
||||
response.setContentType(DEFAULT_CONTENT_TYPE);
|
||||
response.setStatus(DEFAULT_STATUS_CODE);
|
||||
response.getWriter().write(formatDefaultMessage(handledException));
|
||||
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
protected ExceptionHandler getAnnotationFrom(Exception thrownException) {
|
||||
return thrownException.getClass().getAnnotation(ExceptionHandler.class);
|
||||
protected Exception getAnnotatedException(Exception exception) {
|
||||
Throwable causedException = exception.getCause();
|
||||
if(getAnnotationFrom(exception) != null || causedException == null) {
|
||||
return exception;
|
||||
} else {
|
||||
return getAnnotatedException((Exception) causedException);
|
||||
}
|
||||
}
|
||||
|
||||
protected String formatMessage(final Exception thrownException) throws IOException {
|
||||
return String.format(readTemplate(), thrownException.getMessage());
|
||||
protected ExceptionHandler getAnnotationFrom(Exception exception) {
|
||||
return exception.getClass().getAnnotation(ExceptionHandler.class);
|
||||
}
|
||||
|
||||
protected String formatDefaultMessage(final Exception thrownException) throws IOException {
|
||||
return String.format(readDefaultTemplate(), thrownException.getMessage());
|
||||
protected String formatMessage(final Exception handledException) throws IOException {
|
||||
return String.format(readTemplate(), handledException.getMessage());
|
||||
}
|
||||
|
||||
protected String formatDefaultMessage(final Exception handledException) throws IOException {
|
||||
return String.format(readDefaultTemplate(), handledException.getMessage());
|
||||
}
|
||||
|
||||
protected String readTemplate() throws IOException {
|
||||
|
||||
@@ -12,8 +12,14 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class AnnotationHandlerTest {
|
||||
|
||||
@@ -241,6 +247,30 @@ public class AnnotationHandlerTest {
|
||||
verify(sut).handleException(mockAnnotation, expectedException, mockResponse);
|
||||
}
|
||||
|
||||
@Test public void getMessageException_ShouldReturnHandledException_WhenUseMessageExceptionIsTrue() throws Exception {
|
||||
final TestExceptionWithNoAnnotationAttributes expectedException = new TestExceptionWithNoAnnotationAttributes("");
|
||||
final TestExceptionWithNotFoundStatusCode thrownException = new TestExceptionWithNotFoundStatusCode();
|
||||
|
||||
final AnnotationHandler sut = spy(new AnnotationHandler());
|
||||
sut.setUseHandledExceptionMessage(true);
|
||||
|
||||
Exception messageException = sut.getMessageException(thrownException, expectedException);
|
||||
|
||||
assertEquals(expectedException, messageException);
|
||||
}
|
||||
|
||||
@Test public void getMessageException_ShouldReturnThrownException_WhenUseMessageExceptionIsFalse() throws Exception {
|
||||
final TestExceptionWithXmlContentType annotatedException = new TestExceptionWithXmlContentType();
|
||||
final TestExceptionWithNoAnnotation expectedException = new TestExceptionWithNoAnnotation("");
|
||||
|
||||
final AnnotationHandler sut = spy(new AnnotationHandler());
|
||||
sut.setUseHandledExceptionMessage(false);
|
||||
|
||||
Exception messageException = sut.getMessageException(expectedException, annotatedException);
|
||||
|
||||
assertEquals(expectedException, messageException);
|
||||
}
|
||||
|
||||
@Test public void resolveException_ShouldReturnDefaultErrorMessage_WhenUncheckedExceptionIsGiven() throws Exception {
|
||||
final NullPointerException expectedException = mock(NullPointerException.class);
|
||||
|
||||
@@ -249,12 +279,70 @@ public class AnnotationHandlerTest {
|
||||
when(mockResponse.getWriter()).thenReturn(mockPrinter);
|
||||
|
||||
final AnnotationHandler sut = spy(new AnnotationHandler());
|
||||
doReturn(null).when(sut).getAnnotationFrom(expectedException);
|
||||
when(sut.getAnnotatedException(expectedException)).thenReturn(expectedException);
|
||||
when(sut.getAnnotationFrom(expectedException)).thenReturn(null);
|
||||
|
||||
final ModelAndView view = sut.resolveException(null, mockResponse, null, expectedException);
|
||||
|
||||
verify(sut).respondWithDefault(expectedException, mockResponse);
|
||||
}
|
||||
|
||||
@Test public void getAnnotatedException_ShouldReturnOutermostAnnotatedException_WhenThereAreCheckedExceptionsChainedAndGetCauseIsTrue() throws Exception {
|
||||
TestExceptionWithNoAnnotationAttributes mockException = mock(TestExceptionWithNoAnnotationAttributes.class);
|
||||
doReturn(new TestExceptionWithXmlContentType()).when(mockException).getCause();
|
||||
|
||||
final ExceptionHandler mockAnnotation = mock(ExceptionHandler.class);
|
||||
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
|
||||
|
||||
final AnnotationHandler sut = spy(new AnnotationHandler());
|
||||
when(sut.getAnnotationFrom(mockException)).thenReturn(mockAnnotation);
|
||||
doReturn(new ModelAndView()).when(sut).handleException(mockAnnotation, mockException, mockResponse);
|
||||
|
||||
final ModelAndView view = sut.resolveException(null, mockResponse, null, mockException);
|
||||
|
||||
Assert.assertTrue(sut.getAnnotatedException(mockException) instanceof TestExceptionWithNoAnnotationAttributes);
|
||||
}
|
||||
|
||||
@Test public void getHandledException_ShouldReturnThrownException_WhenThereAreCheckedExceptionsChainedAndGetCauseIsFalse() throws Exception {
|
||||
TestExceptionWithNoAnnotationAttributes mockException = mock(TestExceptionWithNoAnnotationAttributes.class);
|
||||
doReturn(new TestExceptionWithXmlContentType()).when(mockException).getCause();
|
||||
|
||||
final ExceptionHandler mockAnnotation = mock(ExceptionHandler.class);
|
||||
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
|
||||
|
||||
final AnnotationHandler sut = spy(new AnnotationHandler());
|
||||
when(sut.getAnnotationFrom(mockException)).thenReturn(mockAnnotation);
|
||||
doReturn(new ModelAndView()).when(sut).handleException(mockAnnotation, mockException, mockResponse);
|
||||
|
||||
final ModelAndView view = sut.resolveException(null, mockResponse, null, mockException);
|
||||
|
||||
Assert.assertTrue(sut.getAnnotatedException(mockException) instanceof TestExceptionWithNoAnnotationAttributes);
|
||||
}
|
||||
|
||||
@Test public void getHandledException_ShouldReturnFirstChainedAnnotatedException_WhenThrownExceptionIsUnannotatedAndGetCauseIsTrue() throws Exception {
|
||||
TestExceptionWithNoAnnotation mockException = mock(TestExceptionWithNoAnnotation.class);
|
||||
TestExceptionWithNoAnnotationAttributes expectedException = new TestExceptionWithNoAnnotationAttributes("");
|
||||
doReturn(expectedException).when(mockException).getCause();
|
||||
|
||||
final ExceptionHandler mockAnnotation = mock(ExceptionHandler.class);
|
||||
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
|
||||
|
||||
final AnnotationHandler sut = spy(new AnnotationHandler());
|
||||
when(sut.getAnnotationFrom(expectedException)).thenReturn(mockAnnotation);
|
||||
doReturn(new ModelAndView()).when(sut).handleException(mockAnnotation, expectedException, mockResponse);
|
||||
|
||||
Assert.assertTrue(sut.getHandledException(mockException) instanceof TestExceptionWithNoAnnotationAttributes);
|
||||
}
|
||||
|
||||
@Test public void getHandledException_ShouldReturnThrownException_WhenThrownExceptionIsUnannotatedAndGetCauseIsFalse() throws Exception {
|
||||
TestExceptionWithNoAnnotation expectedException = new TestExceptionWithNoAnnotation("");
|
||||
|
||||
final AnnotationHandler sut = new AnnotationHandler();
|
||||
sut.setUseGetCause(false);
|
||||
Exception actualException = sut.getHandledException(expectedException);
|
||||
|
||||
Assert.assertTrue(actualException instanceof TestExceptionWithNoAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
@ExceptionHandler()
|
||||
@@ -279,3 +367,8 @@ class TestExceptionWithNoContentStatusCodeAndTextContentType extends Exception {
|
||||
}
|
||||
}
|
||||
|
||||
class TestExceptionWithNoAnnotation extends Exception {
|
||||
public TestExceptionWithNoAnnotation(final String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user