Skip to content

Commit 4fdd853

Browse files
committed
Aligned exception handling in Jackson and JAXB codecs
Issue: SPR-15516
1 parent 1c4babd commit 4fdd853

File tree

6 files changed

+96
-85
lines changed

6 files changed

+96
-85
lines changed

spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,13 @@ private Flux<Object> decodeInternal(JsonObjectDecoder objectDecoder, Publisher<D
117117
return value;
118118
}
119119
catch (InvalidDefinitionException ex) {
120-
throw new CodecException("Type definition error: " + ex.getMessage(), ex);
120+
throw new CodecException("Type definition error: " + ex.getType(), ex);
121121
}
122122
catch (JsonProcessingException ex) {
123-
throw new DecodingException("JSON parse error: " + ex.getMessage(), ex);
123+
throw new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex);
124124
}
125125
catch (IOException ex) {
126-
throw new CodecException("I/O error while reading: " + ex.getMessage(), ex);
126+
throw new DecodingException("I/O error while parsing input stream", ex);
127127
}
128128
});
129129
}

spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.fasterxml.jackson.databind.ObjectMapper;
3333
import com.fasterxml.jackson.databind.ObjectWriter;
3434
import com.fasterxml.jackson.databind.SerializationFeature;
35+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
3536
import com.fasterxml.jackson.databind.type.TypeFactory;
3637
import org.reactivestreams.Publisher;
3738
import reactor.core.publisher.Flux;
@@ -51,7 +52,6 @@
5152
import org.springframework.util.Assert;
5253
import org.springframework.util.MimeType;
5354

54-
5555
/**
5656
* Encode from an {@code Object} stream to a byte stream of JSON objects,
5757
* using Jackson 2.9.
@@ -107,7 +107,7 @@ public List<MimeType> getEncodableMimeTypes() {
107107
@Override
108108
public boolean canEncode(ResolvableType elementType, MimeType mimeType) {
109109
Class<?> clazz = elementType.resolve(Object.class);
110-
return Object.class.equals(clazz) ||
110+
return (Object.class == clazz) ||
111111
!String.class.isAssignableFrom(elementType.resolve(clazz)) &&
112112
this.objectMapper.canSerialize(clazz) && supportsMimeType(mimeType);
113113
}
@@ -166,11 +166,14 @@ private DataBuffer encodeValue(Object value, MimeType mimeType, DataBufferFactor
166166
try {
167167
writer.writeValue(outputStream, value);
168168
}
169+
catch (InvalidDefinitionException ex) {
170+
throw new CodecException("Type definition error: " + ex.getType(), ex);
171+
}
169172
catch (JsonProcessingException ex) {
170-
throw new EncodingException("JSON encoding error: " + ex.getMessage(), ex);
173+
throw new EncodingException("JSON encoding error: " + ex.getOriginalMessage(), ex);
171174
}
172175
catch (IOException ex) {
173-
throw new CodecException("I/O error while writing: " + ex.getMessage(), ex);
176+
throw new IllegalStateException("Unexpected I/O error while writing to data buffer", ex);
174177
}
175178

176179
return buffer;

spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java

Lines changed: 72 additions & 63 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-2017 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.
@@ -23,6 +23,7 @@
2323
import javax.xml.XMLConstants;
2424
import javax.xml.bind.JAXBElement;
2525
import javax.xml.bind.JAXBException;
26+
import javax.xml.bind.UnmarshalException;
2627
import javax.xml.bind.Unmarshaller;
2728
import javax.xml.bind.annotation.XmlRootElement;
2829
import javax.xml.bind.annotation.XmlSchema;
@@ -37,6 +38,7 @@
3738

3839
import org.springframework.core.ResolvableType;
3940
import org.springframework.core.codec.AbstractDecoder;
41+
import org.springframework.core.codec.CodecException;
4042
import org.springframework.core.codec.DecodingException;
4143
import org.springframework.core.io.buffer.DataBuffer;
4244
import org.springframework.util.ClassUtils;
@@ -92,15 +94,34 @@ public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType ele
9294
MimeType mimeType, Map<String, Object> hints) {
9395

9496
Class<?> outputClass = elementType.getRawClass();
95-
Flux<XMLEvent> xmlEventFlux =
96-
this.xmlEventDecoder.decode(inputStream, null, mimeType, hints);
97+
Flux<XMLEvent> xmlEventFlux = this.xmlEventDecoder.decode(inputStream, null, mimeType, hints);
9798

9899
QName typeName = toQName(outputClass);
99100
Flux<List<XMLEvent>> splitEvents = split(xmlEventFlux, typeName);
100101

101102
return splitEvents.map(events -> unmarshal(events, outputClass));
102103
}
103104

105+
private Object unmarshal(List<XMLEvent> events, Class<?> outputClass) {
106+
try {
107+
Unmarshaller unmarshaller = this.jaxbContexts.createUnmarshaller(outputClass);
108+
XMLEventReader eventReader = StaxUtils.createXMLEventReader(events);
109+
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
110+
return unmarshaller.unmarshal(eventReader);
111+
}
112+
else {
113+
JAXBElement<?> jaxbElement = unmarshaller.unmarshal(eventReader, outputClass);
114+
return jaxbElement.getValue();
115+
}
116+
}
117+
catch (UnmarshalException ex) {
118+
throw new DecodingException("Could not unmarshal XML to " + outputClass, ex);
119+
}
120+
catch (JAXBException ex) {
121+
throw new CodecException("Invalid JAXB configuration", ex);
122+
}
123+
}
124+
104125
/**
105126
* Returns the qualified name for the given class, according to the mapping rules
106127
* in the JAXB specification.
@@ -120,17 +141,16 @@ else if (outputClass.isAnnotationPresent(XmlType.class)) {
120141
namespaceUri = annotation.namespace();
121142
}
122143
else {
123-
throw new IllegalArgumentException("Outputclass [" + outputClass + "] is " +
124-
"neither annotated with @XmlRootElement nor @XmlType");
144+
throw new IllegalArgumentException("Output class [" + outputClass.getName() +
145+
"] is neither annotated with @XmlRootElement nor @XmlType");
125146
}
126147

127148
if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(localPart)) {
128149
localPart = ClassUtils.getShortNameAsProperty(outputClass);
129150
}
130151
if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(namespaceUri)) {
131152
Package outputClassPackage = outputClass.getPackage();
132-
if (outputClassPackage != null &&
133-
outputClassPackage.isAnnotationPresent(XmlSchema.class)) {
153+
if (outputClassPackage != null && outputClassPackage.isAnnotationPresent(XmlSchema.class)) {
134154
XmlSchema annotation = outputClassPackage.getAnnotation(XmlSchema.class);
135155
namespaceUri = annotation.namespace();
136156
}
@@ -142,21 +162,20 @@ else if (outputClass.isAnnotationPresent(XmlType.class)) {
142162
}
143163

144164
/**
145-
* Split a flux of {@link XMLEvent}s into a flux of XMLEvent lists, one list for each
146-
* branch of the tree that starts with the given qualified name.
147-
* That is, given the XMLEvents shown
148-
* {@linkplain XmlEventDecoder here},
149-
* and the {@code desiredName} "{@code child}", this method
150-
* returns a flux of two lists, each of which containing the events of a particular
151-
* branch of the tree that starts with "{@code child}".
165+
* Split a flux of {@link XMLEvent}s into a flux of XMLEvent lists, one list
166+
* for each branch of the tree that starts with the given qualified name.
167+
* That is, given the XMLEvents shown {@linkplain XmlEventDecoder here},
168+
* and the {@code desiredName} "{@code child}", this method returns a flux
169+
* of two lists, each of which containing the events of a particular branch
170+
* of the tree that starts with "{@code child}".
152171
* <ol>
153-
* <li>The first list, dealing with the first branch of the tree
172+
* <li>The first list, dealing with the first branch of the tree:
154173
* <ol>
155174
* <li>{@link javax.xml.stream.events.StartElement} {@code child}</li>
156175
* <li>{@link javax.xml.stream.events.Characters} {@code foo}</li>
157176
* <li>{@link javax.xml.stream.events.EndElement} {@code child}</li>
158177
* </ol>
159-
* <li>The second list, dealing with the second branch of the tree
178+
* <li>The second list, dealing with the second branch of the tree:
160179
* <ol>
161180
* <li>{@link javax.xml.stream.events.StartElement} {@code child}</li>
162181
* <li>{@link javax.xml.stream.events.Characters} {@code bar}</li>
@@ -166,57 +185,47 @@ else if (outputClass.isAnnotationPresent(XmlType.class)) {
166185
* </ol>
167186
*/
168187
Flux<List<XMLEvent>> split(Flux<XMLEvent> xmlEventFlux, QName desiredName) {
169-
return xmlEventFlux
170-
.flatMap(new Function<XMLEvent, Publisher<? extends List<XMLEvent>>>() {
171-
172-
private List<XMLEvent> events = null;
173-
174-
private int elementDepth = 0;
175-
176-
private int barrier = Integer.MAX_VALUE;
177-
178-
@Override
179-
public Publisher<? extends List<XMLEvent>> apply(XMLEvent event) {
180-
if (event.isStartElement()) {
181-
if (this.barrier == Integer.MAX_VALUE) {
182-
QName startElementName = event.asStartElement().getName();
183-
if (desiredName.equals(startElementName)) {
184-
this.events = new ArrayList<XMLEvent>();
185-
this.barrier = this.elementDepth;
186-
}
187-
}
188-
this.elementDepth++;
189-
}
190-
if (this.elementDepth > this.barrier) {
191-
this.events.add(event);
192-
}
193-
if (event.isEndElement()) {
194-
this.elementDepth--;
195-
if (this.elementDepth == this.barrier) {
196-
this.barrier = Integer.MAX_VALUE;
197-
return Mono.just(this.events);
198-
}
199-
}
200-
return Mono.empty();
201-
}
202-
});
188+
return xmlEventFlux.flatMap(new SplitFunction(desiredName));
203189
}
204190

205-
private Object unmarshal(List<XMLEvent> events, Class<?> outputClass) {
206-
try {
207-
Unmarshaller unmarshaller = this.jaxbContexts.createUnmarshaller(outputClass);
208-
XMLEventReader eventReader = StaxUtils.createXMLEventReader(events);
209-
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
210-
return unmarshaller.unmarshal(eventReader);
191+
192+
private static class SplitFunction implements Function<XMLEvent, Publisher<? extends List<XMLEvent>>> {
193+
194+
private final QName desiredName;
195+
196+
private List<XMLEvent> events;
197+
198+
private int elementDepth = 0;
199+
200+
private int barrier = Integer.MAX_VALUE;
201+
202+
public SplitFunction(QName desiredName) {
203+
this.desiredName = desiredName;
204+
}
205+
206+
@Override
207+
public Publisher<? extends List<XMLEvent>> apply(XMLEvent event) {
208+
if (event.isStartElement()) {
209+
if (this.barrier == Integer.MAX_VALUE) {
210+
QName startElementName = event.asStartElement().getName();
211+
if (this.desiredName.equals(startElementName)) {
212+
this.events = new ArrayList<>();
213+
this.barrier = this.elementDepth;
214+
}
215+
}
216+
this.elementDepth++;
211217
}
212-
else {
213-
JAXBElement<?> jaxbElement =
214-
unmarshaller.unmarshal(eventReader, outputClass);
215-
return jaxbElement.getValue();
218+
if (this.elementDepth > this.barrier) {
219+
this.events.add(event);
216220
}
217-
}
218-
catch (JAXBException ex) {
219-
throw new DecodingException(ex.getMessage(), ex);
221+
if (event.isEndElement()) {
222+
this.elementDepth--;
223+
if (this.elementDepth == this.barrier) {
224+
this.barrier = Integer.MAX_VALUE;
225+
return Mono.just(this.events);
226+
}
227+
}
228+
return Mono.empty();
220229
}
221230
}
222231

spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* distributed under the License is distributed on an "AS IS" BASIS,
1212
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
* See the License for the specific language governing permissions and
14-
* limitations under the License.
14+
* limitations under the License.H
1515
*/
1616

1717
package org.springframework.http.codec.xml;
@@ -20,8 +20,8 @@
2020
import java.nio.charset.StandardCharsets;
2121
import java.util.Map;
2222
import javax.xml.bind.JAXBException;
23+
import javax.xml.bind.MarshalException;
2324
import javax.xml.bind.Marshaller;
24-
import javax.xml.bind.UnmarshalException;
2525
import javax.xml.bind.annotation.XmlRootElement;
2626
import javax.xml.bind.annotation.XmlType;
2727

@@ -75,17 +75,16 @@ protected Flux<DataBuffer> encode(Object value, DataBufferFactory dataBufferFact
7575
DataBuffer buffer = dataBufferFactory.allocateBuffer(1024);
7676
OutputStream outputStream = buffer.asOutputStream();
7777
Class<?> clazz = ClassUtils.getUserClass(value);
78-
Marshaller marshaller = jaxbContexts.createMarshaller(clazz);
78+
Marshaller marshaller = this.jaxbContexts.createMarshaller(clazz);
7979
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
8080
marshaller.marshal(value, outputStream);
8181
return Flux.just(buffer);
8282
}
83-
catch (UnmarshalException ex) {
84-
return Flux.error(new EncodingException(
85-
"Could not unmarshal to [" + value.getClass() + "]: " + ex.getMessage(), ex));
83+
catch (MarshalException ex) {
84+
return Flux.error(new EncodingException("Could not marshal " + value.getClass() + " to XML", ex));
8685
}
8786
catch (JAXBException ex) {
88-
return Flux.error(new CodecException("Could not instantiate JAXBContext: " + ex.getMessage(), ex));
87+
return Flux.error(new CodecException("Invalid JAXB configuration", ex));
8988
}
9089
}
9190

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessa
218218
return readJavaType(javaType, inputMessage);
219219
}
220220

221-
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
221+
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
222222
try {
223223
if (inputMessage instanceof MappingJacksonInputMessage) {
224224
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
@@ -230,13 +230,10 @@ private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
230230
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
231231
}
232232
catch (InvalidDefinitionException ex) {
233-
throw new HttpMessageConversionException("Type definition error: " + ex.getMessage(), ex);
233+
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
234234
}
235235
catch (JsonProcessingException ex) {
236-
throw new HttpMessageNotReadableException("JSON parse error: " + ex.getMessage(), ex);
237-
}
238-
catch (IOException ex) {
239-
throw new HttpMessageNotReadableException("I/O error while reading: " + ex.getMessage(), ex);
236+
throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex);
240237
}
241238
}
242239

@@ -287,8 +284,11 @@ else if (filters != null) {
287284
generator.flush();
288285

289286
}
287+
catch (InvalidDefinitionException ex) {
288+
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
289+
}
290290
catch (JsonProcessingException ex) {
291-
throw new HttpMessageNotWritableException("Could not write JSON document: " + ex.getMessage(), ex);
291+
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
292292
}
293293
}
294294

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ else if (targetClass != null) {
225225
}
226226
}
227227
catch (IOException ex) {
228-
throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex);
228+
throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
229229
}
230230

231231
if (body == NO_VALUE) {

0 commit comments

Comments
 (0)