58 Commits

Author SHA1 Message Date
raychatter
72a89f2b4e [maven-release-plugin] prepare for next development iteration 2013-01-16 23:07:02 -08:00
raychatter
8a4f4b59db [maven-release-plugin] prepare release spring-restful-exception-handler-2.0.1 2013-01-16 23:06:54 -08:00
raychatter
572053174e Refactored and updated pom to version 2.0.1 2013-01-16 18:07:05 -08:00
raychatter
bca69232cb Added ExceptionUtils for getThrowableList 2013-01-16 17:20:05 -08:00
raychatter
65640be661 [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.4 2013-01-15 15:28:34 -08:00
raychatter
61dae14384 Minor change to README 2013-01-15 14:49:51 -08:00
raychatter
c579b866c6 Reorganized README 2013-01-15 14:47:28 -08:00
raychatter
a099e1f8c9 Updated README to reflect new bean properties useGetCause and useHandledExceptionMessage 2013-01-15 14:35:29 -08:00
raychatter
95fef7d449 Made useGetCause and useHandledExceptionMessage bean properties 2013-01-15 14:15:11 -08:00
raychatter
7f00ffbbe9 [maven-release-plugin] prepare for next development iteration 2013-01-06 23:34:00 -08:00
raychatter
121af77b49 [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.3 2013-01-06 23:33:55 -08:00
raychatter
c9f5121992 Added snapshot 2013-01-06 23:33:30 -08:00
raychatter
c052294254 [maven-release-plugin] prepare for next development iteration 2013-01-06 16:59:58 -08:00
raychatter
f6a1e54f19 [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.2 2013-01-06 16:59:51 -08:00
raychatter
d482a60153 [maven-release-plugin] prepare for next development iteration 2013-01-06 16:03:23 -08:00
raychatter
33a95e1d40 [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.1 2013-01-06 16:03:17 -08:00
raychatter
d998bfe694 Added snapshot version 2013-01-06 16:02:15 -08:00
raychatter
67698be443 [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.0 2013-01-04 14:17:07 -08:00
raychatter
78b2c8a3b9 Created snapshot version 2013-01-04 14:16:47 -08:00
raychatter
528a58d881 [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.0 2013-01-04 12:19:58 -08:00
raychatter
78d9cb2ca8 Updated version to 1.1 2013-01-04 12:18:22 -08:00
raychatter
d4e9474f13 [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.7 2013-01-04 12:17:33 -08:00
raychatter
84d3407507 Moved to snapshot 2013-01-04 12:12:42 -08:00
raychatter
7f8ddd5157 [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.7 2013-01-04 11:55:20 -08:00
raychatter
af6d3d1596 Added a snapshot version 2013-01-04 11:52:18 -08:00
raychatter
90799b3a51 Fixed merge problems with pom.xml 2013-01-04 11:34:39 -08:00
Rachel Walker
4ab8856d8b [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.7 2013-01-04 11:19:23 -08:00
Rachel Walker
d27a26fa8c [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.0 2013-01-04 10:58:14 -08:00
Rachel Walker
9010542e85 reverted back to old versions 2013-01-04 10:45:27 -08:00
Rachel Walker
b74ccb0627 Reverted pom 2013-01-04 10:21:34 -08:00
Rachel Walker
4cd8bbdaa4 [maven-release-plugin] prepare release spring-restful-exception-handler-1.1.0 2013-01-04 10:01:57 -08:00
Rachel Walker
ef7d2b35a4 Updated the README to reflect the new changes 2013-01-03 17:04:54 -08:00
Rachel Walker
61ff23fac4 Updated the README to reflect the new changes 2013-01-03 15:34:59 -08:00
Rachel Walker
5c8d5a1289 Updated the README to reflect the new changes 2013-01-03 15:33:52 -08:00
Rachel Walker
62801050cd Updated the README to reflect the new changes 2013-01-03 15:32:31 -08:00
Rachel Walker
9f58c840cd Updated the README to reflect the new changes 2013-01-03 15:29:58 -08:00
Rachel Walker
a61e09f1b3 Modified the getCause changes to use first annotated exception 2013-01-03 15:19:30 -08:00
Rachel Walker
4205cc8805 Modified the annotationHandler to use getCause but it always finds the innermost annotation 2013-01-03 13:42:59 -08:00
Rachel Walker
138d0e78be Updated the TODO 2012-12-20 17:44:23 -08:00
Alexander Zagniotov
88396fcd75 [maven-release-plugin] prepare for next development iteration 2012-12-19 16:44:52 -08:00
Alexander Zagniotov
551b28c66d [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.6 2012-12-19 16:44:44 -08:00
Alexander Zagniotov
f49b17393c Merge branch 'master' of https://github.com/raychatter/spring-restful-exception-handler
Conflicts:
	pom.xml
2012-12-19 16:43:28 -08:00
Alexander Zagniotov
ebf8913f47 Not using Maven's shade plugin anymore to create a fat JAR 2012-12-19 16:40:13 -08:00
Rachel Walker
93b9c352e4 [maven-release-plugin] prepare for next development iteration 2012-12-19 11:24:27 -08:00
Rachel Walker
c93940b7fa [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.5 2012-12-19 11:24:17 -08:00
Rachel Walker
ff02eaf3de [maven-release-plugin] prepare for next development iteration 2012-12-19 10:49:19 -08:00
Rachel Walker
10559a8d25 [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.4 2012-12-19 10:49:06 -08:00
Rachel Walker
6b289c96d5 [maven-release-plugin] prepare for next development iteration 2012-12-19 10:42:44 -08:00
Rachel Walker
ac0a9d9a19 [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.3 2012-12-19 10:42:26 -08:00
Rachel Walker
05af97338a Merging changes.
Merge branch 'master' of https://github.com/raychatter/spring-restful-exception-handler
2012-12-19 10:38:32 -08:00
Alexander Zagniotov
b727a63bec [maven-release-plugin] prepare for next development iteration 2012-12-19 10:26:47 -08:00
Alexander Zagniotov
1743af38b8 [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.2 2012-12-19 10:26:28 -08:00
Alexander Zagniotov
2e7a8a2fc5 pom.xml 2012-12-19 10:22:34 -08:00
Alexander Zagniotov
4a9ae1803c [maven-release-plugin] prepare release spring-restful-exception-handler-1.0.2 2012-12-19 10:16:36 -08:00
Rachel Walker
39a26e4812 pom.xml 2012-12-19 10:00:32 -08:00
Rachel Walker
60ff377162 Clean project 2012-12-18 17:34:49 -08:00
Rachel Walker
dbd18858eb Removed all files so I can start clean 2012-12-18 15:18:17 -08:00
Rachel Walker
84a2615aef [maven-release-plugin] prepare for next development iteration 2012-12-17 06:18:00 -08:00
16 changed files with 474 additions and 652 deletions

View File

@@ -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!

View File

@@ -1,314 +0,0 @@
<?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>
<groupId>com.github.raychatter</groupId>
<artifactId>spring-restful-exception-handler</artifactId>
<packaging>jar</packaging>
<version>1.0.1</version>
<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>
<parent>
<groupId>com.github.raychatter</groupId>
<artifactId>spring-restful-exception-handler-parent</artifactId>
<version>1.0.1</version>
</parent>
<organization>
<name>ThoughtWorks</name>
<url>http://www.thoughtworks.com/</url>
</organization>
<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-jar-plugin</artifactId>
<version>2.4</version>
<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-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skip>false</skip>
<threadCount>50</threadCount>
<parallel>classes</parallel>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.0</version>
<configuration>
<reportPlugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.3</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<effort>Max</effort>
<threshold>Exp</threshold>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>2.7.1</version>
<configuration>
<targetJdk>${compileSource}</targetJdk>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<suppressionsLocation>
${project.basedir}/checkstyleSupressions.xml
</suppressionsLocation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.8</version>
<configuration>
<quiet>true</quiet>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>2.12.4</version>
</plugin>
</reportPlugins>
</configuration>
</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>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<phase>deploy</phase>
<id>default-cli</id>
<configuration>
<target>
<delete>
<fileset dir="${project.basedir}/artifacts" includes="*.${project.packaging}" />
</delete>
<copy todir="${project.basedir}/artifacts">
<fileset dir="${project.build.directory}">
<include name="${project.build.finalName}*.${project.packaging}" />
</fileset>
</copy>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</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

@@ -1,93 +0,0 @@
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";
@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();
}
}

255
pom.xml
View File

@@ -1,24 +1,197 @@
<?xml version="1.0"?>
<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/maven-v4_0_0.xsd">
<?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>
<groupId>com.github.raychatter</groupId>
<artifactId>spring-restful-exception-handler-parent</artifactId>
<packaging>pom</packaging>
<version>1.0.1</version>
<name>Spring Restful Exception Handler</name>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<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>
</scm>
<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>2.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>
</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-release-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<arguments>-Dgpg.passphrase=${gpg.passphrase}</arguments>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<id>sign</id>
<activation>
<property>
<name>performRelease</name>
@@ -30,7 +203,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.1</version>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
@@ -41,27 +214,43 @@
</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>
<modules>
<module>exception-handler</module>
<module>sample</module>
</modules>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
</plugins>
</build>
<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-1.1.4</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

@@ -1,86 +0,0 @@
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.raychatter</groupId>
<artifactId>sample</artifactId>
<packaging>war</packaging>
<version>1.0.1</version>
<name>A demo app that shows custom exception handling</name>
<url>https://github.com/raychatter/spring-restful-exception-handler</url>
<parent>
<groupId>com.github.raychatter</groupId>
<artifactId>spring-restful-exception-handler-parent</artifactId>
<version>1.0.1</version>
</parent>
<properties>
<spring.version>3.1.1.RELEASE</spring.version>
</properties>
<profiles>
<profile>
<id>release-sign-artifacts</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.1</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<!-- Spring 3 dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.raychatter</groupId>
<artifactId>spring-restful-exception-handler</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
<build>
<finalName>sample</finalName>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.2</version>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,38 +0,0 @@
package com.github.raychatter.common.controller;
import com.github.raychatter.common.exception.MyNegativeArraySizeException;
import com.github.raychatter.common.exception.CustomException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping(value = "/welcome", method = RequestMethod.GET)
@ResponseBody
public Object welcome() {
return "Hello World";
}
@RequestMapping(value = "/custom404", method = RequestMethod.GET)
@ResponseBody
public Object custom404() throws Exception {
throw new CustomException("It's broken!");
}
@RequestMapping(value = "/custom500", method = RequestMethod.GET)
@ResponseBody
public Object custom500() throws Exception {
throw new MyNegativeArraySizeException("oops");
}
@RequestMapping(value = "/unchecked", method = RequestMethod.GET)
@ResponseBody
public Object unchecked() throws Exception {
throw new NullPointerException("an npe");
}
}

View File

@@ -1,13 +0,0 @@
package com.github.raychatter.common.exception;
import com.github.raychatter.ExceptionHandler;
import org.springframework.http.HttpStatus;
@ExceptionHandler(httpStatus = HttpStatus.NOT_FOUND, contentType = "text/html")
public class CustomException extends Exception {
public CustomException(final String s) {
super(s);
}
}

View File

@@ -1,11 +0,0 @@
package com.github.raychatter.common.exception;
import com.github.raychatter.ExceptionHandler;
import org.springframework.http.HttpStatus;
@ExceptionHandler(httpStatus = HttpStatus.INTERNAL_SERVER_ERROR, contentType = "application/json")
public class MyNegativeArraySizeException extends NegativeArraySizeException {
public MyNegativeArraySizeException(String s) {
super(s);
}
}

View File

@@ -1 +0,0 @@
{ "errors": "%s" }

View File

@@ -1,25 +0,0 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config />
<context:component-scan base-package="com.github.raychatter.common.controller" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<bean id="exceptionResolver" class="com.github.raychatter.AnnotationHandler" />
</beans>

View File

@@ -1,5 +0,0 @@
<html>
<body>
<h1>Message : ${message}</h1>
</body>
</html>

View File

@@ -1,28 +0,0 @@
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Spring Web MVC Application</display-name>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

View File

@@ -0,0 +1,131 @@
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";
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;
}
@Override
public ModelAndView resolveException(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception thrownException) {
final Exception handledException = getHandledException(thrownException);
final ExceptionHandler annotation = getAnnotationFrom(handledException);
final Exception messageException = getMessageException(thrownException, handledException);
try {
if (annotation == null) {
handledException.printStackTrace();
return respondWithDefault(messageException, response);
}
return handleException(annotation, messageException, response);
} catch (IOException e) {
// potentially something went wrong in the response itself
e.printStackTrace();
}
return new ModelAndView();
}
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(handledException);
response.getWriter().write(message);
} catch (IOException e) {
return respondWithDefault(handledException, response);
}
return new ModelAndView();
}
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 Exception getAnnotatedException(Exception exception) {
Throwable causedException = exception.getCause();
if(getAnnotationFrom(exception) != null || causedException == null) {
return exception;
} else {
return getAnnotatedException((Exception) causedException);
}
}
protected ExceptionHandler getAnnotationFrom(Exception exception) {
return exception.getClass().getAnnotation(ExceptionHandler.class);
}
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 {
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

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