diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/AbstractDecoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/AbstractDecoder.java index af899e079d..aa17856078 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/AbstractDecoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/AbstractDecoder.java @@ -30,10 +30,17 @@ public abstract class AbstractDecoder implements Decoder { private List supportedMimeTypes = Collections.emptyList(); + public AbstractDecoder(MimeType... supportedMimeTypes) { this.supportedMimeTypes = Arrays.asList(supportedMimeTypes); } + + @Override + public List getSupportedMimeTypes() { + return this.supportedMimeTypes; + } + @Override public boolean canDecode(ResolvableType type, MimeType mimeType, Object... hints) { for (MimeType supportedMimeType : this.supportedMimeTypes) { @@ -44,9 +51,4 @@ public abstract class AbstractDecoder implements Decoder { return false; } - @Override - public List getSupportedMimeTypes() { - return this.supportedMimeTypes; - } - } diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/ByteBufferDecoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/ByteBufferDecoder.java index a06063ae48..fd1ddaad9f 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/ByteBufferDecoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/ByteBufferDecoder.java @@ -29,14 +29,16 @@ import org.springframework.util.MimeTypeUtils; */ public class ByteBufferDecoder extends AbstractDecoder { + public ByteBufferDecoder() { super(MimeTypeUtils.ALL); } + @Override public boolean canDecode(ResolvableType type, MimeType mimeType, Object... hints) { - return super.canDecode(type, mimeType, hints) - && ByteBuffer.class.isAssignableFrom(type.getRawClass()); + Class clazz = type.getRawClass(); + return (super.canDecode(type, mimeType, hints) && ByteBuffer.class.isAssignableFrom(clazz)); } @Override @@ -45,4 +47,5 @@ public class ByteBufferDecoder extends AbstractDecoder { return inputStream; } + } \ No newline at end of file diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Decoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Decoder.java index 2a7416501e..dbb43b5227 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Decoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Decoder.java @@ -26,7 +26,7 @@ import org.springframework.reactive.codec.encoder.Encoder; import org.springframework.util.MimeType; /** - * Decode from a bytes stream to a stream of {@code T}. + * Decode a stream of bytes to a stream of type {@code T}. * * @author Sebastien Deleuze * @see Encoder @@ -34,7 +34,7 @@ import org.springframework.util.MimeType; public interface Decoder { /** - * Indicate whether the given type and mime type can be processed by this decoder. + * Whether the decoder supports the given Java and mime type. * @param type the stream element type to process. * @param mimeType the mime type to process. * @param hints Additional information about how to do decode, optional. @@ -50,11 +50,11 @@ public interface Decoder { * @param hints Additional information about how to do decode, optional. * @return the output stream */ - Publisher decode(Publisher inputStream, ResolvableType type, MimeType mimeType, Object... hints); + Publisher decode(Publisher inputStream, ResolvableType type, + MimeType mimeType, Object... hints); /** - * Return the list of {@link MimeType} objects supported by this codec. - * @return the list of supported mime types + * Return the list of mime types this decoder supports. */ List getSupportedMimeTypes(); diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/JacksonJsonDecoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/JacksonJsonDecoder.java index 3775b6520f..39a4133e8c 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/JacksonJsonDecoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/JacksonJsonDecoder.java @@ -31,6 +31,7 @@ import org.springframework.reactive.codec.encoder.JacksonJsonEncoder; import org.springframework.reactive.io.ByteBufferInputStream; import org.springframework.util.MimeType; + /** * Decode from a bytes stream of JSON objects to a stream of {@code Object} (POJO). * @@ -59,16 +60,20 @@ public class JacksonJsonDecoder extends AbstractDecoder { this.preProcessor = preProcessor; } + @Override public Publisher decode(Publisher inputStream, ResolvableType type, MimeType mimeType, Object... hints) { ObjectReader reader = this.mapper.readerFor(type.getRawClass()); - Publisher decodedStream = this.preProcessor == null ? inputStream : - this.preProcessor.decode(inputStream, type, mimeType, hints); - return Publishers.map(decodedStream, chunk -> { + + if (this.preProcessor != null) { + inputStream = this.preProcessor.decode(inputStream, type, mimeType, hints); + } + + return Publishers.map(inputStream, content -> { try { - return reader.readValue(new ByteBufferInputStream(chunk)); + return reader.readValue(new ByteBufferInputStream(content)); } catch (IOException e) { throw new CodecException("Error while reading the data", e); diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Jaxb2Decoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Jaxb2Decoder.java index 917400ddb5..63f0cbc323 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Jaxb2Decoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/Jaxb2Decoder.java @@ -54,10 +54,12 @@ public class Jaxb2Decoder extends AbstractDecoder { private final ConcurrentMap, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64); + public Jaxb2Decoder() { super(MediaType.APPLICATION_XML, MediaType.TEXT_XML); } + @Override public Publisher decode(Publisher inputStream, ResolvableType type, MimeType mimeType, Object... hints) { diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/StringDecoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/StringDecoder.java index fa86f751c6..de47a06fda 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/StringDecoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/decoder/StringDecoder.java @@ -59,7 +59,7 @@ public class StringDecoder extends AbstractDecoder { else { charset = DEFAULT_CHARSET; } - return Publishers.map(inputStream, chunk -> new String(new Buffer(chunk).asBytes(), charset)); + return Publishers.map(inputStream, content -> new String(new Buffer(content).asBytes(), charset)); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/AbstractEncoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/AbstractEncoder.java index c88926e822..08f504423b 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/AbstractEncoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/AbstractEncoder.java @@ -30,10 +30,17 @@ public abstract class AbstractEncoder implements Encoder { private List supportedMimeTypes = Collections.emptyList(); + public AbstractEncoder(MimeType... supportedMimeTypes) { this.supportedMimeTypes = Arrays.asList(supportedMimeTypes); } + + @Override + public List getSupportedMimeTypes() { + return this.supportedMimeTypes; + } + @Override public boolean canEncode(ResolvableType type, MimeType mimeType, Object... hints) { for (MimeType supportedMimeType : this.supportedMimeTypes) { @@ -44,9 +51,4 @@ public abstract class AbstractEncoder implements Encoder { return false; } - @Override - public List getSupportedMimeTypes() { - return this.supportedMimeTypes; - } - } diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/ByteBufferEncoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/ByteBufferEncoder.java index 1157779714..11ca5ed4ab 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/ByteBufferEncoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/ByteBufferEncoder.java @@ -29,20 +29,24 @@ import org.springframework.util.MimeTypeUtils; */ public class ByteBufferEncoder extends AbstractEncoder { + public ByteBufferEncoder() { super(MimeTypeUtils.ALL); } + @Override public boolean canEncode(ResolvableType type, MimeType mimeType, Object... hints) { - return super.canEncode(type, mimeType, hints) - && ByteBuffer.class.isAssignableFrom(type.getRawClass()); + Class clazz = type.getRawClass(); + return (super.canEncode(type, mimeType, hints) && ByteBuffer.class.isAssignableFrom(clazz)); } @Override public Publisher encode(Publisher inputStream, ResolvableType type, MimeType mimeType, Object... hints) { - return (Publisher)inputStream; + //noinspection unchecked + return (Publisher) inputStream; } + } \ No newline at end of file diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Encoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Encoder.java index 55e5455422..22240f93e5 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Encoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Encoder.java @@ -26,7 +26,7 @@ import org.springframework.reactive.codec.decoder.Decoder; import org.springframework.util.MimeType; /** - * Encode from a stream of {@code T} to a bytes stream. + * Encode a stream of Objects of type {@code T} into a stream of bytes. * * @author Sebastien Deleuze * @see Decoder @@ -50,11 +50,11 @@ public interface Encoder { * @param hints Additional information about how to do decode, optional. * @return the output stream */ - Publisher encode(Publisher inputStream, ResolvableType type, MimeType mimeType, Object... hints); + Publisher encode(Publisher inputStream, ResolvableType type, + MimeType mimeType, Object... hints); /** - * Return the list of {@link MimeType} objects supported by this codec. - * @return the list of supported mime types + * Return the list of mime types this encoder supports. */ List getSupportedMimeTypes(); diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/JacksonJsonEncoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/JacksonJsonEncoder.java index a2d8e02cc8..d12df127b7 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/JacksonJsonEncoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/JacksonJsonEncoder.java @@ -43,6 +43,7 @@ public class JacksonJsonEncoder extends AbstractEncoder { private Encoder postProcessor; + public JacksonJsonEncoder() { this(new ObjectMapper(), null); } @@ -51,6 +52,7 @@ public class JacksonJsonEncoder extends AbstractEncoder { this(new ObjectMapper(), postProcessor); } + public JacksonJsonEncoder(ObjectMapper mapper, Encoder postProcessor) { super(new MimeType("application", "json", StandardCharsets.UTF_8), new MimeType("application", "*+json", StandardCharsets.UTF_8)); @@ -67,13 +69,17 @@ public class JacksonJsonEncoder extends AbstractEncoder { BufferOutputStream outputStream = new BufferOutputStream(buffer); try { this.mapper.writeValue(outputStream, value); - } catch (IOException e) { + } + catch (IOException e) { throw new CodecException("Error while writing the data", e); } buffer.flip(); return buffer.byteBuffer(); }); - return this.postProcessor == null ? stream : this.postProcessor.encode(stream, type, mimeType, hints); + if (this.postProcessor != null) { + stream = this.postProcessor.encode(stream, type, mimeType, hints); + }; + return stream; } } diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Jaxb2Encoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Jaxb2Encoder.java index cc9a7509ee..1e8c6ff375 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Jaxb2Encoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/Jaxb2Encoder.java @@ -53,6 +53,7 @@ public class Jaxb2Encoder extends AbstractEncoder { super(MediaType.APPLICATION_XML, MediaType.TEXT_XML); } + @Override public Publisher encode(Publisher messageStream, ResolvableType type, MimeType mimeType, Object... hints) { diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/StringEncoder.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/StringEncoder.java index 2f5e99ca5b..b87387773f 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/StringEncoder.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/encoder/StringEncoder.java @@ -42,10 +42,11 @@ public class StringEncoder extends AbstractEncoder { super(new MimeType("text", "plain", DEFAULT_CHARSET)); } + @Override public boolean canEncode(ResolvableType type, MimeType mimeType, Object... hints) { - return super.canEncode(type, mimeType, hints) - && String.class.isAssignableFrom(type.getRawClass()); + Class clazz = type.getRawClass(); + return (super.canEncode(type, mimeType, hints) && String.class.isAssignableFrom(clazz)); } @Override diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/support/HintUtils.java b/spring-web-reactive/src/main/java/org/springframework/reactive/codec/support/HintUtils.java deleted file mode 100644 index 892d64b451..0000000000 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/codec/support/HintUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2015 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.reactive.codec.support; - -import org.springframework.reactive.codec.decoder.Decoder; -import org.springframework.reactive.codec.encoder.Encoder; - -/** - * Utility methods for dealing with codec hints. - * - * @author Sebastien Deleuze - * @see Encoder - * @see Decoder - */ -public abstract class HintUtils { - - public static T getHintByClass(Class clazz, Object[] hints) { - return getHintByClass(clazz, hints, null); - } - - public static T getHintByClass(Class clazz, Object[] hints, T defaultValue) { - for (Object hint : hints) { - if (hint.getClass().isAssignableFrom(clazz)) { - return (T) hint; - } - } - return defaultValue; - } - - public static boolean containsHint(Object hint, Object[] hints) { - for (Object h : hints) { - if (h.equals(hint)) { - return true; - } - } - return false; - } - -} diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestBodyArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestBodyArgumentResolver.java index 72cfbe9735..25e92c6899 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestBodyArgumentResolver.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestBodyArgumentResolver.java @@ -39,15 +39,15 @@ import org.springframework.web.bind.annotation.RequestBody; */ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolver { - private final List> deserializers; + private final List> decoders; private final ConversionService conversionService; - public RequestBodyArgumentResolver(List> deserializers, ConversionService service) { - Assert.notEmpty(deserializers, "At least one deserializer is required."); + public RequestBodyArgumentResolver(List> decoders, ConversionService service) { + Assert.notEmpty(decoders, "At least one decoder is required."); Assert.notNull(service, "'conversionService' is required."); - this.deserializers = deserializers; + this.decoders = decoders; this.conversionService = service; } @@ -61,16 +61,19 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve public Publisher resolveArgument(MethodParameter parameter, ReactiveServerHttpRequest request) { MediaType mediaType = resolveMediaType(request); ResolvableType type = ResolvableType.forMethodParameter(parameter); - Publisher inputStream = request.getBody(); - Publisher elementStream = inputStream; + Publisher body = request.getBody(); + Publisher elementStream = body; ResolvableType elementType = type.hasGenerics() ? type.getGeneric(0) : type; - Decoder deserializer = resolveDeserializer(elementType, mediaType); - if (deserializer != null) { - elementStream = deserializer.decode(inputStream, elementType, mediaType); + + Decoder decoder = resolveDecoder(elementType, mediaType); + if (decoder != null) { + elementStream = decoder.decode(body, elementType, mediaType); } + if (this.conversionService.canConvert(Publisher.class, type.getRawClass())) { return Publishers.just(this.conversionService.convert(elementStream, type.getRawClass())); } + return Publishers.map(elementStream, element -> element); } @@ -81,10 +84,10 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve return ( mediaTypes.size() > 0 ? mediaTypes.get(0) : MediaType.TEXT_PLAIN); } - private Decoder resolveDeserializer(ResolvableType type, MediaType mediaType, Object... hints) { - for (Decoder deserializer : this.deserializers) { - if (deserializer.canDecode(type, mediaType, hints)) { - return deserializer; + private Decoder resolveDecoder(ResolvableType type, MediaType mediaType, Object... hints) { + for (Decoder decoder : this.decoders) { + if (decoder.canDecode(type, mediaType, hints)) { + return decoder; } } return null; diff --git a/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/ResponseBodyResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/ResponseBodyResultHandler.java index d1818f3ebe..dd4fd9aa49 100644 --- a/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/ResponseBodyResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/ResponseBodyResultHandler.java @@ -48,17 +48,17 @@ import org.springframework.web.method.HandlerMethod; */ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered { - private final List> serializers; + private final List> encoders; private final ConversionService conversionService; private int order = 0; - public ResponseBodyResultHandler(List> serializers, ConversionService service) { - Assert.notEmpty(serializers, "At least one serializers is required."); + public ResponseBodyResultHandler(List> encoders, ConversionService service) { + Assert.notEmpty(encoders, "At least one encoders is required."); Assert.notNull(service, "'conversionService' is required."); - this.serializers = serializers; + this.encoders = encoders; this.conversionService = service; } @@ -89,45 +89,46 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered ReactiveServerHttpResponse response, HandlerResult result) { Object value = result.getValue(); - HandlerMethod handlerMethod = (HandlerMethod) result.getHandler(); - MethodParameter returnType = handlerMethod.getReturnValueType(value); - if (value == null) { return Publishers.empty(); } - ResolvableType type = ResolvableType.forMethodParameter(returnType); - MediaType mediaType = resolveMediaType(request); + HandlerMethod hm = (HandlerMethod) result.getHandler(); + ResolvableType returnType = ResolvableType.forMethodParameter(hm.getReturnValueType(value)); + Publisher elementStream; ResolvableType elementType; - if (conversionService.canConvert(type.getRawClass(), Publisher.class)) { - elementStream = conversionService.convert(value, Publisher.class); - elementType = type.getGeneric(0); + if (this.conversionService.canConvert(returnType.getRawClass(), Publisher.class)) { + elementStream = this.conversionService.convert(value, Publisher.class); + elementType = returnType.getGeneric(0); } else { elementStream = Publishers.just(value); - elementType = type; + elementType = returnType; } - Encoder serializer = resolveSerializer(elementType, mediaType); - - if (serializer != null) { - Publisher outputStream = serializer.encode((Publisher)elementStream, type, mediaType); - if (mediaType == null || mediaType.isWildcardType() || mediaType.isWildcardSubtype()) { - List mimeTypes = serializer.getSupportedMimeTypes(); - if (!mimeTypes.isEmpty()) { - MimeType mimeType = mimeTypes.get(0); - mediaType = new MediaType(mimeType.getType(), mimeType.getSubtype(), mimeType.getParameters()); - } - } - if (mediaType != null && !mediaType.equals(MediaType.ALL)) { - response.getHeaders().setContentType(mediaType); - } - return response.setBody(outputStream); + MediaType mediaType = resolveMediaType(request); + Encoder encoder = resolveEncoder(elementType, mediaType); + if (encoder == null) { + return Publishers.error(new IllegalStateException( + "Return value type '" + returnType + + "' with media type '" + mediaType + "' not supported")); } - String returnTypeName = returnType.getParameterType().getName(); - return Publishers.error(new IllegalStateException("Return value type '" + returnTypeName + - "' with media type '" + mediaType + "' not supported")); + + Publisher outputStream = encoder.encode((Publisher) elementStream, returnType, mediaType); + if (mediaType == null || mediaType.isWildcardType() || mediaType.isWildcardSubtype()) { + List mimeTypes = encoder.getSupportedMimeTypes(); + if (!mimeTypes.isEmpty()) { + MimeType mimeType = mimeTypes.get(0); + mediaType = new MediaType(mimeType.getType(), mimeType.getSubtype(), mimeType.getParameters()); + } + } + + if (mediaType != null && !mediaType.equals(MediaType.ALL)) { + response.getHeaders().setContentType(mediaType); + } + + return response.setBody(outputStream); } private MediaType resolveMediaType(ReactiveServerHttpRequest request) { @@ -137,10 +138,10 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered return ( mediaTypes.size() > 0 ? mediaTypes.get(0) : MediaType.TEXT_PLAIN); } - private Encoder resolveSerializer(ResolvableType type, MediaType mediaType, Object... hints) { - for (Encoder serializer : this.serializers) { - if (serializer.canEncode(type, mediaType, hints)) { - return serializer; + private Encoder resolveEncoder(ResolvableType type, MediaType mediaType, Object... hints) { + for (Encoder encoder : this.encoders) { + if (encoder.canEncode(type, mediaType, hints)) { + return encoder; } } return null;