Clean project

This commit is contained in:
Rachel Walker
2012-12-18 17:34:49 -08:00
parent dbd18858eb
commit 60ff377162
6 changed files with 730 additions and 0 deletions

48
README.md Normal file
View File

@@ -0,0 +1,48 @@
# 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:
```xml
<bean id="exceptionResolver" class="com.github.raychatter.AnnotationHandler" />
```
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.
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:
```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);
}
}
```

287
pom.xml Normal file
View File

@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<name>spring-restful-exception-handler</name>
<description>Custom exception handler annotation for Spring to unify the error handling.</description>
<url>https://github.com/raychatter/spring-restful-exception-handler</url>
<organization>
<name>ThoughtWorks</name>
<url>http://www.thoughtworks.com/</url>
</organization>
<inceptionYear>2012</inceptionYear>
<groupId>com.github.raychatter</groupId>
<artifactId>spring-restful-exception-handler</artifactId>
<version>1.0.2-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>${project.build.sourceEncoding}</project.reporting.outputEncoding>
<siteProjectVersion>${project.version}</siteProjectVersion>
<manifest.builtBy>Rachel Walker</manifest.builtBy>
<manifest.addDefaultImplementationEntries>true</manifest.addDefaultImplementationEntries>
<manifest.addClasspath>true</manifest.addClasspath>
<manifest.packageName>${project.groupId}</manifest.packageName>
<manifest.mainClass>${project.groupId}.Main</manifest.mainClass>
<manifest.compress>true</manifest.compress>
<compileSource>1.6</compileSource>
</properties>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>${compileSource}</source>
<target>${compileSource}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>no-dependencies</classifier>
</configuration>
</execution>
</executions>
<configuration>
<archive>
<compress>${manifest.compress}</compress>
<manifest>
<addDefaultImplementationEntries>
${manifest.addDefaultImplementationEntries}
</addDefaultImplementationEntries>
<addClasspath>${manifest.addClasspath}</addClasspath>
<mainClass>${manifest.mainClass}</mainClass>
<packageName>${manifest.packageName}</packageName>
</manifest>
<manifestEntries>
<Built-By>${manifest.builtBy}</Built-By>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>generate-sources-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<useDefaultManifestFile>false</useDefaultManifestFile>
<archive>
<compress>${manifest.compress}</compress>
<manifest>
<addDefaultImplementationEntries>
${manifest.addDefaultImplementationEntries}
</addDefaultImplementationEntries>
<addClasspath>${manifest.addClasspath}</addClasspath>
<mainClass>${manifest.mainClass}</mainClass>
<packageName>${manifest.packageName}</packageName>
</manifest>
<manifestEntries>
<Built-By>${manifest.builtBy}</Built-By>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>generate-javadocs-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<quiet>true</quiet>
<javadocVersion>${compileSource}</javadocVersion>
<archive>
<compress>${manifest.compress}</compress>
<manifest>
<addDefaultImplementationEntries>
${manifest.addDefaultImplementationEntries}
</addDefaultImplementationEntries>
<addClasspath>${manifest.addClasspath}</addClasspath>
<mainClass>${manifest.mainClass}</mainClass>
<packageName>${manifest.packageName}</packageName>
</manifest>
<manifestEntries>
<Built-By>${manifest.builtBy}</Built-By>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</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>
<configuration>
<filters>
<filter>
<artifact>org.eclipse.jetty.orbit:javax.servlet</artifact>
<excludes>
<exclude>META-INF/ECLIPSE.SF</exclude>
<exclude>META-INF/ECLIPSE.RSA</exclude>
<exclude>META-INF/ECLIPSEF.SF</exclude>
<exclude>META-INF/ECLIPSEF.RSA</exclude>
<exclude>META-INF/ECLIPSE.INF</exclude>
<exclude>META-INF/eclipse.inf</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<arguments>-Dgpg.passphrase=${gpg.passphrase}</arguments>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>sign</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<serverAuthId>oss-sonatype-org</serverAuthId>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<scm>
<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-0.1</tag>
</scm>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/raychatter/spring-restful-exception-handler/issues/</url>
</issueManagement>
<developers>
<developer>
<id>raychatter</id>
<name>Rachel Walker</name>
<email>rachel.e.walker@gmail.com</email>
<roles>
<role>author</role>
</roles>
<organization>ThoughtWorks</organization>
<organizationUrl>http://www.ThoughtWorks.com</organizationUrl>
</developer>
</developers>
</project>

View File

@@ -0,0 +1,94 @@
package com.github.raychatter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class AnnotationHandler implements HandlerExceptionResolver {
protected static final String DEFAULT_ERROR_STRING = "<error>%s</error>";
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";
//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);
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
e.printStackTrace();
}
return new ModelAndView();
}
protected ModelAndView handleException(final ExceptionHandler annotation, final Exception thrownException, final HttpServletResponse response) throws IOException {
response.setContentType(annotation.contentType());
response.setStatus(annotation.httpStatus().value());
try {
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();
}
protected ExceptionHandler getAnnotationFrom(Exception thrownException) {
return thrownException.getClass().getAnnotation(ExceptionHandler.class);
}
protected String formatMessage(final Exception thrownException) throws IOException {
return String.format(readTemplate(), thrownException.getMessage());
}
protected String formatDefaultMessage(final Exception thrownException) throws IOException {
return String.format(readDefaultTemplate(), thrownException.getMessage());
}
protected String readTemplate() throws IOException {
final InputStream templateFile = getResource(USER_TEMPLATE);
return new Scanner(templateFile, UTF_8).useDelimiter("\\A").next().trim();
}
protected String readDefaultTemplate() {
try {
final InputStream templateFile = getResource(DEFAULT_TEMPLATE);
return new Scanner(templateFile, UTF_8).useDelimiter("\\A").next().trim();
} catch (IOException ex) {
return DEFAULT_ERROR_STRING;
}
}
protected InputStream getResource(final String resource) throws IOException {
return new ClassPathResource(resource).getInputStream();
}
}

View File

@@ -0,0 +1,16 @@
package com.github.raychatter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ExceptionHandler {
HttpStatus httpStatus() default HttpStatus.INTERNAL_SERVER_ERROR;
String contentType() default MediaType.APPLICATION_XML_VALUE;
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<error>
%s
</error>

View File

@@ -0,0 +1,281 @@
package com.github.raychatter;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
public class AnnotationHandlerTest {
@Test
public void formatMessage_ShouldRenderUserTemplate_WhenUserTemplateGiven() throws Exception {
final String givenUserTemplate = "A TEMPLATE: %s";
final String exceptionMessage = "AN ERROR MESSAGE";
final String expectedResult = "A TEMPLATE: AN ERROR MESSAGE";
AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(givenUserTemplate).when(sut).readTemplate();
final String actual = sut.formatMessage(new Exception(exceptionMessage));
Assert.assertEquals(expectedResult, actual);
}
@Test
public void formatDefaultMessage_ShouldRenderDefaultUserTemplate_WhenNoUserTemplateGiven() throws Exception {
final String defaultTemplate = "DEFAULT TEMPLATE: %s";
final String exceptionMessage = "AN ERROR MESSAGE";
final String expectedMessage = "DEFAULT TEMPLATE: AN ERROR MESSAGE";
AnnotationHandler spyAnnotationHandler = spy(new AnnotationHandler());
doReturn(defaultTemplate).when(spyAnnotationHandler).readDefaultTemplate();
final String actual = spyAnnotationHandler.formatDefaultMessage(new Exception(exceptionMessage));
Assert.assertEquals(expectedMessage, actual);
}
@Test
public void readDefaultTemplate_ShouldReturnDefaultErrorMessage_WhenIOError() throws Exception {
final String defaultTemplatePath = "defaults/default.template";
AnnotationHandler spyAnnotationHandler = spy(new AnnotationHandler());
doThrow(new IOException()).when(spyAnnotationHandler).getResource(defaultTemplatePath);
final String actualTemplate = spyAnnotationHandler.readDefaultTemplate();
Assert.assertEquals(AnnotationHandler.DEFAULT_ERROR_STRING, actualTemplate);
}
@Test
public void readTemplate_ShouldReturnUserTemplateString_WhenUserTemplateIsGiven() throws Exception {
// arrange
final String expectedUserTemplateString = "USER TEMPLATE";
final InputStream expectedInputStream = new ByteArrayInputStream(expectedUserTemplateString.getBytes());
// act
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(expectedInputStream).when(sut).getResource(anyString());
final String actual = sut.readTemplate();
// assert
Assert.assertEquals(expectedUserTemplateString, actual);
}
@Test
public void readDefaultTemplate_ShouldReturnDefaultTemplateString_WhenNoUserTemplateIsGiven() throws Exception {
// arrange
final String expectedDefaultTemplateString = "DEFAULT TEMPLATE";
final InputStream expectedInputStream = new ByteArrayInputStream(expectedDefaultTemplateString.getBytes());
// act
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(expectedInputStream).when(sut).getResource(anyString());
final String actual = sut.readDefaultTemplate();
// assert
Assert.assertEquals(expectedDefaultTemplateString, actual);
}
@Test
public void handleException_ShouldRenderDefaultContentType_WhenNoAnnotationAttributesGiven() throws Exception {
final String emptyString = "";
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(emptyString).when(sut).formatMessage(null);
doReturn(emptyString).when(sut).formatDefaultMessage(null);
sut.handleException(TestExceptionWithNoAnnotationAttributes.class.getAnnotation(ExceptionHandler.class), null, mockResponse);
verify(mockResponse).setContentType(MediaType.APPLICATION_XML_VALUE);
}
@Test
public void handleException_ShouldRenderDefaultHttpStatusCode_WhenNoAnnotationAttributesGiven() throws Exception {
final String emptyString = "";
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(emptyString).when(sut).formatMessage(null);
doReturn(emptyString).when(sut).formatDefaultMessage(null);
sut.handleException(TestExceptionWithNoAnnotationAttributes.class.getAnnotation(ExceptionHandler.class), null, mockResponse);
verify(mockResponse).setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
@Test
public void handleException_ShouldRenderNotFoundHttpStatusCode_WhenNotFoundAnnotationAttributeIsGiven() throws Exception {
final String emptyString = "";
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(emptyString).when(sut).formatMessage(null);
doReturn(emptyString).when(sut).formatDefaultMessage(null);
sut.handleException(TestExceptionWithNotFoundStatusCode.class.getAnnotation(ExceptionHandler.class), null, mockResponse);
verify(mockResponse).setStatus(HttpStatus.NOT_FOUND.value());
}
@Test
public void handleException_ShouldRenderXmlContentType_WhenXmlContentTypeAnnotationAttributeIsGiven() throws Exception {
final String emptyString = "";
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(emptyString).when(sut).formatMessage(null);
doReturn(emptyString).when(sut).formatDefaultMessage(null);
sut.handleException(TestExceptionWithXmlContentType.class.getAnnotation(ExceptionHandler.class), null, mockResponse);
verify(mockResponse).setContentType(MediaType.APPLICATION_XML_VALUE);
}
@Test
public void handleException_ShouldRenderUserMessage_WhenUserTemplateIsGiven() throws Exception {
final String expectedUserTemplate = "USER TEMPLATE: %s";
final String expectedErrorMessage = "ERROR MESSAGE";
final String expectedErrorBody = "USER TEMPLATE: ERROR MESSAGE";
final TestExceptionWithNoAnnotationAttributes expectedException = new TestExceptionWithNoAnnotationAttributes(expectedErrorMessage);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(new ByteArrayInputStream(expectedUserTemplate.getBytes())).when(sut).getResource(AnnotationHandler.USER_TEMPLATE);
sut.handleException(expectedException.getClass().getAnnotation(ExceptionHandler.class), expectedException, mockResponse);
verify(mockPrinter).write(expectedErrorBody);
}
@Test
public void handleException_ShouldReturnXmlContentType_WhenNoUserTemplateGiven() throws Exception {
final String emptyString = "";
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doThrow(IOException.class).when(sut).getResource(AnnotationHandler.USER_TEMPLATE);
doReturn(emptyString).when(sut).formatDefaultMessage(null);
sut.handleException(TestExceptionWithNoContentStatusCodeAndTextContentType.class.getAnnotation(ExceptionHandler.class), null, mockResponse);
verify(mockResponse).setContentType(MediaType.APPLICATION_XML_VALUE);
}
@Test
public void handleException_ShouldReturnInternalServerErrorStatusCode_WhenNoUserTemplateGiven() throws Exception {
final String emptyString = "";
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doThrow(IOException.class).when(sut).getResource(AnnotationHandler.USER_TEMPLATE);
doReturn(emptyString).when(sut).formatDefaultMessage(null);
sut.handleException(TestExceptionWithNoContentStatusCodeAndTextContentType.class.getAnnotation(ExceptionHandler.class), null, mockResponse);
verify(mockResponse).setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
@Test
public void handleException_ShouldRenderDefaultTemplate_WhenNoUserTemplateGiven() throws Exception {
final String expectedDefaultTemplate = "DEFAULT TEMPLATE: %s";
final String expectedErrorMessage = "ERROR MESSAGE";
final String expectedErrorBody = "DEFAULT TEMPLATE: ERROR MESSAGE";
final TestExceptionWithNoContentStatusCodeAndTextContentType expectedException = new TestExceptionWithNoContentStatusCodeAndTextContentType(expectedErrorMessage);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final PrintWriter mockPrinter = mock(PrintWriter.class);
when(mockResponse.getWriter()).thenReturn(mockPrinter);
final AnnotationHandler sut = spy(new AnnotationHandler());
doThrow(IOException.class).when(sut).getResource(AnnotationHandler.USER_TEMPLATE);
doReturn(new ByteArrayInputStream(expectedDefaultTemplate.getBytes())).when(sut).getResource(AnnotationHandler.DEFAULT_TEMPLATE);
sut.handleException(expectedException.getClass().getAnnotation(ExceptionHandler.class), expectedException, mockResponse);
verify(mockPrinter).write(expectedErrorBody);
}
@Test public void resolveException_ShouldReturnCustomErrorMessage_WhenValidExceptionWithAnnotationIsGiven() throws Exception {
final ExceptionHandler mockAnnotation = mock(ExceptionHandler.class);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final TestExceptionWithNoAnnotationAttributes expectedException = new TestExceptionWithNoAnnotationAttributes("");
final AnnotationHandler sut = spy(new AnnotationHandler());
doReturn(new ModelAndView()).when(sut).handleException(mockAnnotation, expectedException, mockResponse);
doReturn(mockAnnotation).when(sut).getAnnotationFrom(expectedException);
final ModelAndView view = sut.resolveException(null, mockResponse, null, expectedException);
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()
class TestExceptionWithNoAnnotationAttributes extends Exception {
public TestExceptionWithNoAnnotationAttributes(final String s) {
super(s);
}
}
@ExceptionHandler(contentType = MediaType.APPLICATION_XML_VALUE)
class TestExceptionWithXmlContentType extends Exception {
}
@ExceptionHandler(httpStatus = HttpStatus.NOT_FOUND)
class TestExceptionWithNotFoundStatusCode extends Exception {
}
@ExceptionHandler(contentType = MediaType.TEXT_PLAIN_VALUE, httpStatus = HttpStatus.NO_CONTENT)
class TestExceptionWithNoContentStatusCodeAndTextContentType extends Exception {
public TestExceptionWithNoContentStatusCodeAndTextContentType(final String s) {
super(s);
}
}