diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java index aa346306c3..f5f2690256 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java @@ -38,6 +38,7 @@ import org.springframework.core.codec.CodecException; import org.springframework.core.codec.DecodingException; import org.springframework.core.codec.Hints; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.log.LogFormatUtils; import org.springframework.http.codec.HttpMessageDecoder; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -88,56 +89,73 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple Flux tokens = Jackson2Tokenizer.tokenize( Flux.from(input), this.jsonFactory, getObjectMapper(), true); - return decodeInternal(tokens, elementType, mimeType, hints); + + ObjectReader reader = getObjectReader(elementType, hints); + + return tokens.handle((tokenBuffer, sink) -> { + try { + Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper())); + logValue(value, hints); + if (value != null) { + sink.next(value); + } + } + catch (IOException ex) { + sink.error(processException(ex)); + } + }); } @Override public Mono decodeToMono(Publisher input, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map hints) { - Flux tokens = Jackson2Tokenizer.tokenize( - Flux.from(input), this.jsonFactory, getObjectMapper(), false); - return decodeInternal(tokens, elementType, mimeType, hints).singleOrEmpty(); + return DataBufferUtils.join(input).map(dataBuffer -> { + try { + ObjectReader objectReader = getObjectReader(elementType, hints); + Object value = objectReader.readValue(dataBuffer.asInputStream()); + logValue(value, hints); + return value; + } + catch (IOException ex) { + throw processException(ex); + } + finally { + DataBufferUtils.release(dataBuffer); + } + }); } - private Flux decodeInternal(Flux tokens, ResolvableType elementType, - @Nullable MimeType mimeType, @Nullable Map hints) { - - Assert.notNull(tokens, "'tokens' must not be null"); + private ObjectReader getObjectReader(ResolvableType elementType, @Nullable Map hints) { Assert.notNull(elementType, "'elementType' must not be null"); - MethodParameter param = getParameter(elementType); Class contextClass = (param != null ? param.getContainingClass() : null); JavaType javaType = getJavaType(elementType.getType(), contextClass); Class jsonView = (hints != null ? (Class) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null); - - ObjectReader reader = (jsonView != null ? + return jsonView != null ? getObjectMapper().readerWithView(jsonView).forType(javaType) : - getObjectMapper().readerFor(javaType)); + getObjectMapper().readerFor(javaType); + } - return tokens.handle((tokenBuffer, sink) -> { - try { - Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper())); - if (!Hints.isLoggingSuppressed(hints)) { - LogFormatUtils.traceDebug(logger, traceOn -> { - String formatted = LogFormatUtils.formatValue(value, !traceOn); - return Hints.getLogPrefix(hints) + "Decoded [" + formatted + "]"; - }); - } - if (value != null) { - sink.next(value); - } - } - catch (InvalidDefinitionException ex) { - sink.error(new CodecException("Type definition error: " + ex.getType(), ex)); - } - catch (JsonProcessingException ex) { - sink.error(new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex)); - } - catch (IOException ex) { - sink.error(new DecodingException("I/O error while parsing input stream", ex)); - } - }); + private void logValue(@Nullable Object value, @Nullable Map hints) { + if (!Hints.isLoggingSuppressed(hints)) { + LogFormatUtils.traceDebug(logger, traceOn -> { + String formatted = LogFormatUtils.formatValue(value, !traceOn); + return Hints.getLogPrefix(hints) + "Decoded [" + formatted + "]"; + }); + } + } + + private CodecException processException(IOException ex) { + if (ex instanceof InvalidDefinitionException) { + JavaType type = ((InvalidDefinitionException) ex).getType(); + return new CodecException("Type definition error: " + type, ex); + } + if (ex instanceof JsonProcessingException) { + String originalMessage = ((JsonProcessingException) ex).getOriginalMessage(); + return new DecodingException("JSON decoding error: " + originalMessage, ex); + } + return new DecodingException("I/O error while parsing input stream", ex); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java index ab63d30741..04227f73e2 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java @@ -17,6 +17,7 @@ package org.springframework.http.codec.xml; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -31,9 +32,12 @@ import javax.xml.bind.annotation.XmlSchema; import javax.xml.bind.annotation.XmlType; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; import org.reactivestreams.Publisher; +import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.SynchronousSink; @@ -44,6 +48,7 @@ import org.springframework.core.codec.CodecException; import org.springframework.core.codec.DecodingException; import org.springframework.core.codec.Hints; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.log.LogFormatUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -72,6 +77,8 @@ public class Jaxb2XmlDecoder extends AbstractDecoder { */ private static final String JAXB_DEFAULT_ANNOTATION_VALUE = "##default"; + private static final XMLInputFactory inputFactory = StaxUtils.createDefensiveInputFactory(); + private final XmlEventDecoder xmlEventDecoder = new XmlEventDecoder(); @@ -132,10 +139,24 @@ public class Jaxb2XmlDecoder extends AbstractDecoder { } @Override - public Mono decodeToMono(Publisher inputStream, ResolvableType elementType, + @SuppressWarnings({"rawtypes", "unchecked", "cast"}) // XMLEventReader is Iterator on JDK 9 + public Mono decodeToMono(Publisher input, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map hints) { - return decode(inputStream, elementType, mimeType, hints).singleOrEmpty(); + return DataBufferUtils.join(input).map(dataBuffer -> { + try { + Iterator eventReader = inputFactory.createXMLEventReader(dataBuffer.asInputStream()); + List events = new ArrayList<>(); + eventReader.forEachRemaining(event -> events.add((XMLEvent) event)); + return unmarshal(events, elementType.toClass()); + } + catch (XMLStreamException ex) { + throw Exceptions.propagate(ex); + } + finally { + DataBufferUtils.release(dataBuffer); + } + }); } private Object unmarshal(List events, Class outputClass) { diff --git a/spring-web/src/main/java/org/springframework/http/codec/xml/XmlEventDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/xml/XmlEventDecoder.java index d44ff23a11..b417de7610 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/xml/XmlEventDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/xml/XmlEventDecoder.java @@ -95,7 +95,7 @@ public class XmlEventDecoder extends AbstractDecoder { @Override - @SuppressWarnings({"rawtypes", "unchecked", "cast"}) // on JDK 9 where XMLEventReader is Iterator instead of simply Iterator + @SuppressWarnings({"rawtypes", "unchecked", "cast"}) // XMLEventReader is Iterator on JDK 9 public Flux decode(Publisher input, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map hints) {