Skip to content

Commit ac1e287

Browse files
committed
Consistent throwing of HttpMessageNotReadableException (5.0.x revision)
Includes specific fine-tuning of ProtobufHttpMessageConverter and JAXB2 based message converters, as well as revised javadoc for abstract base classes. Issue: SPR-16995
1 parent ce0323f commit ac1e287

10 files changed

+61
-53
lines changed

spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ protected boolean canWrite(@Nullable MediaType mediaType) {
192192
* Future implementations might add some default behavior, however.
193193
*/
194194
@Override
195-
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {
195+
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
196+
throws IOException, HttpMessageNotReadableException {
197+
196198
return readInternal(clazz, inputMessage);
197199
}
198200

@@ -233,7 +235,7 @@ public HttpHeaders getHeaders() {
233235
* {@link #getContentLength}, and sets the corresponding headers.
234236
* @since 4.2
235237
*/
236-
protected void addDefaultHeaders(HttpHeaders headers, T t, @Nullable MediaType contentType) throws IOException{
238+
protected void addDefaultHeaders(HttpHeaders headers, T t, @Nullable MediaType contentType) throws IOException {
237239
if (headers.getContentType() == null) {
238240
MediaType contentTypeToUse = contentType;
239241
if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {

spring-web/src/main/java/org/springframework/http/converter/ObjectToStringHttpMessageConverter.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -106,11 +106,13 @@ protected boolean supports(Class<?> clazz) {
106106
}
107107

108108
@Override
109-
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException {
109+
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
110+
throws IOException, HttpMessageNotReadableException {
111+
110112
String value = this.stringHttpMessageConverter.readInternal(String.class, inputMessage);
111113
Object result = this.conversionService.convert(value, clazz);
112114
if (result == null) {
113-
throw new HttpMessageConversionException(
115+
throw new HttpMessageNotReadableException(
114116
"Unexpected null conversion result for '" + value + "' to " + clazz);
115117
}
116118
return result;

spring-web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public String getFilename() {
9797
};
9898
}
9999
else {
100-
throw new IllegalStateException("Unsupported resource class: " + clazz);
100+
throw new HttpMessageNotReadableException("Unsupported resource class: " + clazz);
101101
}
102102
}
103103

spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
2525
import java.nio.charset.Charset;
2626
import java.nio.charset.StandardCharsets;
2727
import java.util.Arrays;
28-
import java.util.concurrent.ConcurrentHashMap;
28+
import java.util.Map;
2929

3030
import com.google.protobuf.CodedOutputStream;
3131
import com.google.protobuf.ExtensionRegistry;
@@ -44,6 +44,7 @@
4444
import org.springframework.lang.Nullable;
4545
import org.springframework.util.Assert;
4646
import org.springframework.util.ClassUtils;
47+
import org.springframework.util.ConcurrentReferenceHashMap;
4748

4849
import static org.springframework.http.MediaType.*;
4950

@@ -87,7 +88,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
8788
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message";
8889

8990

90-
private static final ConcurrentHashMap<Class<?>, Method> methodCache = new ConcurrentHashMap<>();
91+
private static final Map<Class<?>, Method> methodCache = new ConcurrentReferenceHashMap<>();
9192

9293
private final ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
9394

@@ -127,8 +128,8 @@ else if (ClassUtils.isPresent("com.google.protobuf.util.JsonFormat", getClass().
127128
this.protobufFormatSupport = null;
128129
}
129130

130-
setSupportedMediaTypes(Arrays.asList((this.protobufFormatSupport != null ?
131-
this.protobufFormatSupport.supportedMediaTypes() : new MediaType[] {PROTOBUF, TEXT_PLAIN})));
131+
setSupportedMediaTypes(Arrays.asList(this.protobufFormatSupport != null ?
132+
this.protobufFormatSupport.supportedMediaTypes() : new MediaType[] {PROTOBUF, TEXT_PLAIN}));
132133

133134
if (registryInitializer != null) {
134135
registryInitializer.initializeExtensionRegistry(this.extensionRegistry);
@@ -179,6 +180,20 @@ else if (this.protobufFormatSupport != null) {
179180
}
180181
}
181182

183+
/**
184+
* Create a new {@code Message.Builder} instance for the given class.
185+
* <p>This method uses a ConcurrentReferenceHashMap for caching method lookups.
186+
*/
187+
private Message.Builder getMessageBuilder(Class<? extends Message> clazz) throws Exception {
188+
Method method = methodCache.get(clazz);
189+
if (method == null) {
190+
method = clazz.getMethod("newBuilder");
191+
methodCache.put(clazz, method);
192+
}
193+
return (Message.Builder) method.invoke(clazz);
194+
}
195+
196+
182197
@Override
183198
protected boolean canWrite(@Nullable MediaType mediaType) {
184199
return (super.canWrite(mediaType) ||
@@ -229,20 +244,6 @@ private void setProtoHeader(HttpOutputMessage response, Message message) {
229244
}
230245

231246

232-
/**
233-
* Create a new {@code Message.Builder} instance for the given class.
234-
* <p>This method uses a ConcurrentHashMap for caching method lookups.
235-
*/
236-
private static Message.Builder getMessageBuilder(Class<? extends Message> clazz) throws Exception {
237-
Method method = methodCache.get(clazz);
238-
if (method == null) {
239-
method = clazz.getMethod("newBuilder");
240-
methodCache.put(clazz, method);
241-
}
242-
return (Message.Builder) method.invoke(clazz);
243-
}
244-
245-
246247
interface ProtobufFormatSupport {
247248

248249
MediaType[] supportedMediaTypes();
@@ -293,7 +294,7 @@ else if (contentType.isCompatibleWith(APPLICATION_XML)) {
293294
this.xmlFormatter.merge(input, charset, extensionRegistry, builder);
294295
}
295296
else {
296-
throw new IOException("com.google.protobuf.util does not support " + contentType + " format");
297+
throw new IOException("protobuf-java-format does not support " + contentType + " format");
297298
}
298299
}
299300

spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -74,7 +74,7 @@ protected void customizeMarshaller(Marshaller marshaller) {
7474
* @return the {@code Unmarshaller}
7575
* @throws HttpMessageConversionException in case of JAXB errors
7676
*/
77-
protected final Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException {
77+
protected final Unmarshaller createUnmarshaller(Class<?> clazz) {
7878
try {
7979
JAXBContext jaxbContext = getJaxbContext(clazz);
8080
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
@@ -104,7 +104,7 @@ protected void customizeUnmarshaller(Unmarshaller unmarshaller) {
104104
* @throws HttpMessageConversionException in case of JAXB errors
105105
*/
106106
protected final JAXBContext getJaxbContext(Class<?> clazz) {
107-
Assert.notNull(clazz, "'clazz' must not be null");
107+
Assert.notNull(clazz, "Class must not be null");
108108
JAXBContext jaxbContext = this.jaxbContexts.get(clazz);
109109
if (jaxbContext == null) {
110110
try {

spring-web/src/main/java/org/springframework/http/converter/xml/AbstractXmlHttpMessageConverter.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,8 @@
2929
import org.springframework.http.HttpOutputMessage;
3030
import org.springframework.http.MediaType;
3131
import org.springframework.http.converter.AbstractHttpMessageConverter;
32-
import org.springframework.http.converter.HttpMessageConversionException;
32+
import org.springframework.http.converter.HttpMessageNotReadableException;
33+
import org.springframework.http.converter.HttpMessageNotWritableException;
3334

3435
/**
3536
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters}
@@ -57,12 +58,16 @@ protected AbstractXmlHttpMessageConverter() {
5758

5859

5960
@Override
60-
public final T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {
61+
public final T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
62+
throws IOException, HttpMessageNotReadableException {
63+
6164
return readFromSource(clazz, inputMessage.getHeaders(), new StreamSource(inputMessage.getBody()));
6265
}
6366

6467
@Override
65-
protected final void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException {
68+
protected final void writeInternal(T t, HttpOutputMessage outputMessage)
69+
throws IOException, HttpMessageNotWritableException {
70+
6671
writeToResult(t, outputMessage.getHeaders(), new StreamResult(outputMessage.getBody()));
6772
}
6873

@@ -84,20 +89,20 @@ protected void transform(Source source, Result result) throws TransformerExcepti
8489
* @param source the HTTP input body
8590
* @return the converted object
8691
* @throws IOException in case of I/O errors
87-
* @throws org.springframework.http.converter.HttpMessageConversionException in case of conversion errors
92+
* @throws HttpMessageNotReadableException in case of conversion errors
8893
*/
8994
protected abstract T readFromSource(Class<? extends T> clazz, HttpHeaders headers, Source source)
90-
throws IOException;
95+
throws IOException, HttpMessageNotReadableException;
9196

9297
/**
9398
* Abstract template method called from {@link #writeInternal(Object, HttpOutputMessage)}.
9499
* @param t the object to write to the output message
95100
* @param headers the HTTP output headers
96101
* @param result the HTTP output body
97102
* @throws IOException in case of I/O errors
98-
* @throws HttpMessageConversionException in case of conversion errors
103+
* @throws HttpMessageNotWritableException in case of conversion errors
99104
*/
100105
protected abstract void writeToResult(T t, HttpHeaders headers, Result result)
101-
throws IOException;
106+
throws IOException, HttpMessageNotWritableException;
102107

103108
}

spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverter.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -159,21 +159,21 @@ else if (elementClass.isAnnotationPresent(XmlType.class)) {
159159
}
160160
else {
161161
// should not happen, since we check in canRead(Type)
162-
throw new HttpMessageConversionException("Could not unmarshal to [" + elementClass + "]");
162+
throw new HttpMessageNotReadableException("Cannot unmarshal to [" + elementClass + "]");
163163
}
164164
event = moveToNextElement(streamReader);
165165
}
166166
return result;
167167
}
168+
catch (XMLStreamException ex) {
169+
throw new HttpMessageNotReadableException("Failed to read XML stream: " + ex.getMessage(), ex);
170+
}
168171
catch (UnmarshalException ex) {
169172
throw new HttpMessageNotReadableException(
170173
"Could not unmarshal to [" + elementClass + "]: " + ex.getMessage(), ex);
171174
}
172175
catch (JAXBException ex) {
173-
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
174-
}
175-
catch (XMLStreamException ex) {
176-
throw new HttpMessageConversionException(ex.getMessage(), ex);
176+
throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex);
177177
}
178178
}
179179

spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -145,10 +145,9 @@ protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source sour
145145
}
146146
catch (UnmarshalException ex) {
147147
throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex);
148-
149148
}
150149
catch (JAXBException ex) {
151-
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
150+
throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex);
152151
}
153152
}
154153

@@ -189,7 +188,7 @@ protected void writeToResult(Object o, HttpHeaders headers, Result result) throw
189188
throw new HttpMessageNotWritableException("Could not marshal [" + o + "]: " + ex.getMessage(), ex);
190189
}
191190
catch (JAXBException ex) {
192-
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
191+
throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex);
193192
}
194193
}
195194

spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,7 +50,6 @@
5050
import org.springframework.http.HttpOutputMessage;
5151
import org.springframework.http.MediaType;
5252
import org.springframework.http.converter.AbstractHttpMessageConverter;
53-
import org.springframework.http.converter.HttpMessageConversionException;
5453
import org.springframework.http.converter.HttpMessageNotReadableException;
5554
import org.springframework.http.converter.HttpMessageNotWritableException;
5655
import org.springframework.lang.Nullable;
@@ -159,7 +158,7 @@ else if (StreamSource.class == clazz || Source.class == clazz) {
159158
return (T) readStreamSource(body);
160159
}
161160
else {
162-
throw new HttpMessageConversionException("Could not read class [" + clazz +
161+
throw new HttpMessageNotReadableException("Could not read class [" + clazz +
163162
"]. Only DOMSource, SAXSource, StAXSource, and StreamSource are supported.");
164163
}
165164
}

spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,7 +95,7 @@ public void shouldReadInputStreamResource() throws IOException {
9595
public void shouldNotReadInputStreamResource() throws IOException {
9696
ResourceHttpMessageConverter noStreamConverter = new ResourceHttpMessageConverter(false);
9797
try (InputStream body = getClass().getResourceAsStream("logo.jpg") ) {
98-
this.thrown.expect(IllegalStateException.class);
98+
this.thrown.expect(HttpMessageNotReadableException.class);
9999
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
100100
inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG);
101101
noStreamConverter.read(InputStreamResource.class, inputMessage);

0 commit comments

Comments
 (0)