diff --git a/exception-handler/src/main/java/com/raychatter/common/annotation/AnnotationHandler.java b/exception-handler/src/main/java/com/raychatter/common/annotation/AnnotationHandler.java index abd2bf9..f88f3fd 100644 --- a/exception-handler/src/main/java/com/raychatter/common/annotation/AnnotationHandler.java +++ b/exception-handler/src/main/java/com/raychatter/common/annotation/AnnotationHandler.java @@ -1,8 +1,6 @@ package com.raychatter.common.annotation; 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; @@ -14,35 +12,39 @@ import java.util.Scanner; public class AnnotationHandler implements HandlerExceptionResolver { + protected static final String DEFAULT_ERROR_STRING = "Error: %s"; + 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 = thrownException.getClass().getAnnotation(ExceptionHandler.class); -// This still returns an empty ModelAndView if (annotation == null) { return new ModelAndView(); } - return doStuffWithAnnotation(annotation, thrownException, response); + return handleException(annotation, thrownException, response); } - private ModelAndView doStuffWithAnnotation(final ExceptionHandler exceptionHandlerAnnotation, final Exception thrownException, final HttpServletResponse response) { - -// This is only outside of the try because the null annotation case is handled in resolveException - response.setContentType(exceptionHandlerAnnotation.contentType()); - response.setStatus(exceptionHandlerAnnotation.httpStatus().value()); + protected ModelAndView handleException(final ExceptionHandler annotation, final Exception thrownException, final HttpServletResponse response) { + response.setContentType(annotation.contentType()); + response.setStatus(annotation.httpStatus().value()); try { - response.getWriter().write(formatMessage(thrownException)); + final String message = formatMessage(thrownException); + response.getWriter().write(message); } catch (IOException e) { - response.setContentType(MediaType.APPLICATION_XML_VALUE); - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - - try { - response.getWriter().write(formatDefaultMessage(thrownException)); - } catch (IOException ex) { - ex.printStackTrace(); - } + //TODO: Potentially this can be handled differently than the template errors +// response.setContentType(MediaType.APPLICATION_XML_VALUE); +// response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); +// +// try { +// response.getWriter().write(formatDefaultMessage(thrownException)); +// } catch (IOException ex) { +// ex.printStackTrace(); +// } } return new ModelAndView(); @@ -57,22 +59,21 @@ public class AnnotationHandler implements HandlerExceptionResolver { } protected String readTemplate() throws IOException { - final InputStream templateFile = new ClassPathResource("error.template").getInputStream(); - return new Scanner(templateFile, "UTF-8").useDelimiter("\\A").next().trim(); + final InputStream templateFile = getResource(USER_TEMPLATE); + return new Scanner(templateFile, UTF_8).useDelimiter("\\A").next().trim(); } -// Extract into a new protected method to grab input stream; spy that new method, returning null? protected String readDefaultTemplate() { try { - final InputStream templateFile = getInputStream("defaults/default.template"); - return new Scanner(templateFile, "UTF-8").useDelimiter("\\A").next().trim(); + final InputStream templateFile = getResource(DEFAULT_TEMPLATE); + return new Scanner(templateFile, UTF_8).useDelimiter("\\A").next().trim(); } catch (IOException ex) { - return "Error: %s"; + return DEFAULT_ERROR_STRING; } } - protected InputStream getInputStream(String filepath) throws IOException { - return new ClassPathResource(filepath).getInputStream(); + protected InputStream getResource(final String resource) throws IOException { + return new ClassPathResource(resource).getInputStream(); } } diff --git a/exception-handler/src/main/java/com/raychatter/common/annotation/ExceptionHandler.java b/exception-handler/src/main/java/com/raychatter/common/annotation/ExceptionHandler.java index a802283..3461b94 100644 --- a/exception-handler/src/main/java/com/raychatter/common/annotation/ExceptionHandler.java +++ b/exception-handler/src/main/java/com/raychatter/common/annotation/ExceptionHandler.java @@ -1,6 +1,7 @@ package com.raychatter.common.annotation; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -17,6 +18,6 @@ import java.lang.annotation.Target; ElementType.PARAMETER, ElementType.TYPE}) public @interface ExceptionHandler { - HttpStatus httpStatus() default HttpStatus.OK; - String contentType(); + HttpStatus httpStatus() default HttpStatus.INTERNAL_SERVER_ERROR; + String contentType() default MediaType.TEXT_PLAIN_VALUE; } diff --git a/exception-handler/src/test/java/com/raychatter/common/annotation/AnnotationHandlerTest.java b/exception-handler/src/test/java/com/raychatter/common/annotation/AnnotationHandlerTest.java index ad0f0c9..7815180 100644 --- a/exception-handler/src/test/java/com/raychatter/common/annotation/AnnotationHandlerTest.java +++ b/exception-handler/src/test/java/com/raychatter/common/annotation/AnnotationHandlerTest.java @@ -2,12 +2,22 @@ package com.raychatter.common.annotation; import junit.framework.Assert; import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +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.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 { @@ -25,7 +35,8 @@ public class AnnotationHandlerTest { Assert.assertEquals(expectedResult, actual); } - @Test public void formatDefaultMessage_ShouldRenderDefaultUserTemplate_WhenNoUserTemplateGiven() throws Exception { + @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"; @@ -38,15 +49,144 @@ public class AnnotationHandlerTest { Assert.assertEquals(expectedMessage, actual); } - @Test public void readDefaultTemplate_ShouldCatchIOException_WhenDefaultTemplateNotFound() throws Exception { - final String expectedTemplate = "Error: %s"; - final String defaultFilepath = "defaults/default.template"; + @Test + public void readDefaultTemplate_ShouldReturnDefaultErrorMessage_WhenIOError() throws Exception { + final String defaultTemplatePath = "defaults/default.template"; AnnotationHandler spyAnnotationHandler = spy(new AnnotationHandler()); - doThrow(new IOException()).when(spyAnnotationHandler).getInputStream(defaultFilepath); + doThrow(new IOException()).when(spyAnnotationHandler).getResource(defaultTemplatePath); final String actualTemplate = spyAnnotationHandler.readDefaultTemplate(); - Assert.assertEquals(expectedTemplate, actualTemplate); + Assert.assertEquals(AnnotationHandler.DEFAULT_ERROR_STRING, actualTemplate); } -} \ No newline at end of file + + @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.TEXT_PLAIN_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); + } + +} + +@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 { } +