From 83faee67d5b9e4c247d8cec3f151f030a7d00d3d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 4 Jul 2018 22:46:09 +0200 Subject: [PATCH] HttpMessageNotReadableException provides access to HttpInputMessage Issue: SPR-15588 --- .../BufferedImageHttpMessageConverter.java | 5 +- .../HttpMessageNotReadableException.java | 48 ++++++++++++++++++- .../ObjectToStringHttpMessageConverter.java | 3 +- .../ResourceHttpMessageConverter.java | 2 +- .../AbstractWireFeedHttpMessageConverter.java | 2 +- .../AbstractJackson2HttpMessageConverter.java | 2 +- .../AbstractJsonHttpMessageConverter.java | 2 +- .../ProtobufHttpMessageConverter.java | 21 ++++---- .../xml/AbstractXmlHttpMessageConverter.java | 35 ++++++++++---- .../Jaxb2CollectionHttpMessageConverter.java | 12 +++-- .../Jaxb2RootElementHttpMessageConverter.java | 13 ++--- .../xml/MarshallingHttpMessageConverter.java | 32 ++++--------- .../xml/SourceHttpMessageConverter.java | 30 +++++++----- .../MarshallingHttpMessageConverterTests.java | 12 +++-- ...essageConverterMethodArgumentResolver.java | 2 +- .../RequestResponseBodyMethodProcessor.java | 2 +- 16 files changed, 141 insertions(+), 82 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java index 61197a5d29..e174888dfe 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java @@ -172,7 +172,7 @@ public class BufferedImageHttpMessageConverter implements HttpMessageConverter imageReaders = ImageIO.getImageReadersByMIMEType(contentType.toString()); if (imageReaders.hasNext()) { @@ -184,7 +184,8 @@ public class BufferedImageHttpMessageConverter implements HttpMessageConverter return (T) feedInput.build(reader); } catch (FeedException ex) { - throw new HttpMessageNotReadableException("Could not read WireFeed: " + ex.getMessage(), ex); + throw new HttpMessageNotReadableException("Could not read WireFeed: " + ex.getMessage(), ex, inputMessage); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index bdd7c076cf..476c5e1b26 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -242,7 +242,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex); } catch (JsonProcessingException ex) { - throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex); + throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java index 8af32bc21e..2db3b8141c 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java @@ -109,7 +109,7 @@ public abstract class AbstractJsonHttpMessageConverter extends AbstractGenericHt return readInternal(resolvedType, reader); } catch (Exception ex) { - throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); + throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex, inputMessage); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java index d61ce1cec1..585b0edfde 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java @@ -273,12 +273,13 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter the converted object type */ @@ -62,14 +64,31 @@ public abstract class AbstractXmlHttpMessageConverter extends AbstractHttpMes public final T readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { - return readFromSource(clazz, inputMessage.getHeaders(), new StreamSource(inputMessage.getBody())); + try { + return readFromSource(clazz, inputMessage.getHeaders(), new StreamSource(inputMessage.getBody())); + } + catch (IOException | HttpMessageConversionException ex) { + throw ex; + } + catch (Exception ex) { + throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), + ex, inputMessage); + } } @Override protected final void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { - writeToResult(t, outputMessage.getHeaders(), new StreamResult(outputMessage.getBody())); + try { + writeToResult(t, outputMessage.getHeaders(), new StreamResult(outputMessage.getBody())); + } + catch (IOException | HttpMessageConversionException ex) { + throw ex; + } + catch (Exception ex) { + throw new HttpMessageNotWritableException("Could not marshal [" + t + "]: " + ex.getMessage(), ex); + } } /** @@ -89,21 +108,17 @@ public abstract class AbstractXmlHttpMessageConverter extends AbstractHttpMes * @param headers the HTTP input headers * @param source the HTTP input body * @return the converted object - * @throws IOException in case of I/O errors - * @throws HttpMessageNotReadableException in case of conversion errors + * @throws Exception in case of I/O or conversion errors */ - protected abstract T readFromSource(Class clazz, HttpHeaders headers, Source source) - throws IOException, HttpMessageNotReadableException; + protected abstract T readFromSource(Class clazz, HttpHeaders headers, Source source) throws Exception; /** * Abstract template method called from {@link #writeInternal(Object, HttpOutputMessage)}. * @param t the object to write to the output message * @param headers the HTTP output headers * @param result the HTTP output body - * @throws IOException in case of I/O errors - * @throws HttpMessageNotWritableException in case of conversion errors + * @throws Exception in case of I/O or conversion errors */ - protected abstract void writeToResult(T t, HttpHeaders headers, Result result) - throws IOException, HttpMessageNotWritableException; + protected abstract void writeToResult(T t, HttpHeaders headers, Result result) throws Exception; } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverter.java index cdc341c8ea..559ffc0b90 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverter.java @@ -132,7 +132,7 @@ public class Jaxb2CollectionHttpMessageConverter } @Override - protected T readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException { + protected T readFromSource(Class clazz, HttpHeaders headers, Source source) throws Exception { // should not be called, since we return false for canRead(Class) throw new UnsupportedOperationException(); } @@ -160,18 +160,20 @@ public class Jaxb2CollectionHttpMessageConverter } else { // should not happen, since we check in canRead(Type) - throw new HttpMessageNotReadableException("Cannot unmarshal to [" + elementClass + "]"); + throw new HttpMessageNotReadableException( + "Cannot unmarshal to [" + elementClass + "]", inputMessage); } event = moveToNextElement(streamReader); } return result; } catch (XMLStreamException ex) { - throw new HttpMessageNotReadableException("Failed to read XML stream: " + ex.getMessage(), ex); + throw new HttpMessageNotReadableException( + "Failed to read XML stream: " + ex.getMessage(), ex, inputMessage); } catch (UnmarshalException ex) { throw new HttpMessageNotReadableException( - "Could not unmarshal to [" + elementClass + "]: " + ex.getMessage(), ex); + "Could not unmarshal to [" + elementClass + "]: " + ex.getMessage(), ex, inputMessage); } catch (JAXBException ex) { throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex); @@ -237,7 +239,7 @@ public class Jaxb2CollectionHttpMessageConverter } @Override - protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException { + protected void writeToResult(T t, HttpHeaders headers, Result result) throws Exception { throw new UnsupportedOperationException(); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java index a6e6708393..9ff39a76d3 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java @@ -16,7 +16,6 @@ package org.springframework.http.converter.xml; -import java.io.IOException; import java.io.StringReader; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; @@ -41,8 +40,6 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConversionException; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; @@ -124,7 +121,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa } @Override - protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException { + protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) throws Exception { try { source = processSource(source); Unmarshaller unmarshaller = createUnmarshaller(clazz); @@ -138,13 +135,13 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa } catch (NullPointerException ex) { if (!isSupportDtd()) { - throw new HttpMessageNotReadableException("NPE while unmarshalling. " + + throw new IllegalStateException("NPE while unmarshalling. " + "This can happen due to the presence of DTD declarations which are disabled.", ex); } throw ex; } catch (UnmarshalException ex) { - throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex); + throw ex; } catch (JAXBException ex) { throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex); @@ -177,7 +174,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa } @Override - protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException { + protected void writeToResult(Object o, HttpHeaders headers, Result result) throws Exception { try { Class clazz = ClassUtils.getUserClass(o); Marshaller marshaller = createMarshaller(clazz); @@ -185,7 +182,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa marshaller.marshal(o, result); } catch (MarshalException ex) { - throw new HttpMessageNotWritableException("Could not marshal [" + o + "]: " + ex.getMessage(), ex); + throw ex; } catch (JAXBException ex) { throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex); diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java index e833ec095e..b26b98a831 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,15 @@ package org.springframework.http.converter.xml; -import java.io.IOException; import javax.xml.transform.Result; import javax.xml.transform.Source; import org.springframework.beans.TypeMismatchException; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.lang.Nullable; import org.springframework.oxm.Marshaller; -import org.springframework.oxm.MarshallingFailureException; import org.springframework.oxm.Unmarshaller; -import org.springframework.oxm.UnmarshallingFailureException; import org.springframework.util.Assert; /** @@ -125,28 +120,19 @@ public class MarshallingHttpMessageConverter extends AbstractXmlHttpMessageConve } @Override - protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException { + protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) throws Exception { Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required"); - try { - Object result = this.unmarshaller.unmarshal(source); - if (!clazz.isInstance(result)) { - throw new TypeMismatchException(result, clazz); - } - return result; - } - catch (UnmarshallingFailureException ex) { - throw new HttpMessageNotReadableException("Could not read [" + clazz + "]", ex); + Object result = this.unmarshaller.unmarshal(source); + if (!clazz.isInstance(result)) { + throw new TypeMismatchException(result, clazz); } + return result; } @Override - protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException { + protected void writeToResult(Object o, HttpHeaders headers, Result result) throws Exception { Assert.notNull(this.marshaller, "Property 'marshaller' is required"); - try { - this.marshaller.marshal(o, result); - } - catch (MarshallingFailureException ex) { - throw new HttpMessageNotWritableException("Could not write [" + o + "]", ex); - } + this.marshaller.marshal(o, result); } + } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java index da358a8535..9dfdca4c26 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java @@ -147,24 +147,24 @@ public class SourceHttpMessageConverter extends AbstractHttpMe InputStream body = inputMessage.getBody(); if (DOMSource.class == clazz) { - return (T) readDOMSource(body); + return (T) readDOMSource(body, inputMessage); } else if (SAXSource.class == clazz) { - return (T) readSAXSource(body); + return (T) readSAXSource(body, inputMessage); } else if (StAXSource.class == clazz) { - return (T) readStAXSource(body); + return (T) readStAXSource(body, inputMessage); } else if (StreamSource.class == clazz || Source.class == clazz) { return (T) readStreamSource(body); } else { throw new HttpMessageNotReadableException("Could not read class [" + clazz + - "]. Only DOMSource, SAXSource, StAXSource, and StreamSource are supported."); + "]. Only DOMSource, SAXSource, StAXSource, and StreamSource are supported.", inputMessage); } } - private DOMSource readDOMSource(InputStream body) throws IOException { + private DOMSource readDOMSource(InputStream body, HttpInputMessage inputMessage) throws IOException { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); @@ -181,21 +181,23 @@ public class SourceHttpMessageConverter extends AbstractHttpMe } catch (NullPointerException ex) { if (!isSupportDtd()) { - throw new HttpMessageNotReadableException("NPE while unmarshalling: " + - "This can happen due to the presence of DTD declarations which are disabled.", ex); + throw new HttpMessageNotReadableException("NPE while unmarshalling: This can happen " + + "due to the presence of DTD declarations which are disabled.", ex, inputMessage); } throw ex; } catch (ParserConfigurationException ex) { - throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex); + throw new HttpMessageNotReadableException( + "Could not set feature: " + ex.getMessage(), ex, inputMessage); } catch (SAXException ex) { - throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + throw new HttpMessageNotReadableException( + "Could not parse document: " + ex.getMessage(), ex, inputMessage); } } @SuppressWarnings("deprecation") // on JDK 9 - private SAXSource readSAXSource(InputStream body) throws IOException { + private SAXSource readSAXSource(InputStream body, HttpInputMessage inputMessage) throws IOException { try { XMLReader xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); @@ -207,11 +209,12 @@ public class SourceHttpMessageConverter extends AbstractHttpMe return new SAXSource(xmlReader, new InputSource(new ByteArrayInputStream(bytes))); } catch (SAXException ex) { - throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + throw new HttpMessageNotReadableException( + "Could not parse document: " + ex.getMessage(), ex, inputMessage); } } - private Source readStAXSource(InputStream body) { + private Source readStAXSource(InputStream body, HttpInputMessage inputMessage) { try { XMLInputFactory inputFactory = XMLInputFactory.newInstance(); inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, isSupportDtd()); @@ -223,7 +226,8 @@ public class SourceHttpMessageConverter extends AbstractHttpMe return new StAXSource(streamReader); } catch (XMLStreamException ex) { - throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + throw new HttpMessageNotReadableException( + "Could not parse document: " + ex.getMessage(), ex, inputMessage); } } diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java index 074f883cb2..48e4c9ecf0 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ public class MarshallingHttpMessageConverterTests { assertEquals("Invalid result", body, result); } - @Test(expected = TypeMismatchException.class) + @Test public void readWithTypeMismatchException() throws Exception { MockHttpInputMessage inputMessage = new MockHttpInputMessage(new byte[0]); @@ -96,7 +96,13 @@ public class MarshallingHttpMessageConverterTests { given(unmarshaller.unmarshal(isA(StreamSource.class))).willReturn(Integer.valueOf(3)); MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter(marshaller, unmarshaller); - converter.read(String.class, inputMessage); + try { + converter.read(String.class, inputMessage); + fail("Should have thrown HttpMessageNotReadableException"); + } + catch (HttpMessageNotReadableException ex) { + assertTrue(ex.getCause() instanceof TypeMismatchException); + } } @Test diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java index dfd40ec424..434abb73ac 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java @@ -212,7 +212,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements } } catch (IOException ex) { - throw new HttpMessageNotReadableException("I/O error while reading input message", ex); + throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage); } if (body == NO_VALUE) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java index 478f8d628f..4ffba8c530 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java @@ -157,7 +157,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter Object arg = readWithMessageConverters(inputMessage, parameter, paramType); if (arg == null && checkRequired(parameter)) { throw new HttpMessageNotReadableException("Required request body is missing: " + - parameter.getExecutable().toGenericString()); + parameter.getExecutable().toGenericString(), inputMessage); } return arg; }