This commit is contained in:
Rossen Stoyanchev
2015-11-13 15:35:19 -05:00
parent bdc5b38cb1
commit 2de127ad4a
15 changed files with 111 additions and 134 deletions

View File

@@ -30,10 +30,17 @@ public abstract class AbstractDecoder<T> implements Decoder<T> {
private List<MimeType> supportedMimeTypes = Collections.emptyList();
public AbstractDecoder(MimeType... supportedMimeTypes) {
this.supportedMimeTypes = Arrays.asList(supportedMimeTypes);
}
@Override
public List<MimeType> 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<T> implements Decoder<T> {
return false;
}
@Override
public List<MimeType> getSupportedMimeTypes() {
return this.supportedMimeTypes;
}
}

View File

@@ -29,14 +29,16 @@ import org.springframework.util.MimeTypeUtils;
*/
public class ByteBufferDecoder extends AbstractDecoder<ByteBuffer> {
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<ByteBuffer> {
return inputStream;
}
}

View File

@@ -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<T> {
/**
* 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<T> {
* @param hints Additional information about how to do decode, optional.
* @return the output stream
*/
Publisher<T> decode(Publisher<ByteBuffer> inputStream, ResolvableType type, MimeType mimeType, Object... hints);
Publisher<T> decode(Publisher<ByteBuffer> 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<MimeType> getSupportedMimeTypes();

View File

@@ -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<Object> {
this.preProcessor = preProcessor;
}
@Override
public Publisher<Object> decode(Publisher<ByteBuffer> inputStream, ResolvableType type,
MimeType mimeType, Object... hints) {
ObjectReader reader = this.mapper.readerFor(type.getRawClass());
Publisher<ByteBuffer> 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);

View File

@@ -54,10 +54,12 @@ public class Jaxb2Decoder extends AbstractDecoder<Object> {
private final ConcurrentMap<Class<?>, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64);
public Jaxb2Decoder() {
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML);
}
@Override
public Publisher<Object> decode(Publisher<ByteBuffer> inputStream, ResolvableType type,
MimeType mimeType, Object... hints) {

View File

@@ -59,7 +59,7 @@ public class StringDecoder extends AbstractDecoder<String> {
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));
}
}

View File

@@ -30,10 +30,17 @@ public abstract class AbstractEncoder<T> implements Encoder<T> {
private List<MimeType> supportedMimeTypes = Collections.emptyList();
public AbstractEncoder(MimeType... supportedMimeTypes) {
this.supportedMimeTypes = Arrays.asList(supportedMimeTypes);
}
@Override
public List<MimeType> 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<T> implements Encoder<T> {
return false;
}
@Override
public List<MimeType> getSupportedMimeTypes() {
return this.supportedMimeTypes;
}
}

View File

@@ -29,20 +29,24 @@ import org.springframework.util.MimeTypeUtils;
*/
public class ByteBufferEncoder extends AbstractEncoder<ByteBuffer> {
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<ByteBuffer> encode(Publisher<? extends ByteBuffer> inputStream, ResolvableType type,
MimeType mimeType, Object... hints) {
return (Publisher<ByteBuffer>)inputStream;
//noinspection unchecked
return (Publisher<ByteBuffer>) inputStream;
}
}

View File

@@ -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<T> {
* @param hints Additional information about how to do decode, optional.
* @return the output stream
*/
Publisher<ByteBuffer> encode(Publisher<? extends T> inputStream, ResolvableType type, MimeType mimeType, Object... hints);
Publisher<ByteBuffer> encode(Publisher<? extends T> 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<MimeType> getSupportedMimeTypes();

View File

@@ -43,6 +43,7 @@ public class JacksonJsonEncoder extends AbstractEncoder<Object> {
private Encoder<ByteBuffer> postProcessor;
public JacksonJsonEncoder() {
this(new ObjectMapper(), null);
}
@@ -51,6 +52,7 @@ public class JacksonJsonEncoder extends AbstractEncoder<Object> {
this(new ObjectMapper(), postProcessor);
}
public JacksonJsonEncoder(ObjectMapper mapper, Encoder<ByteBuffer> 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<Object> {
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;
}
}

View File

@@ -53,6 +53,7 @@ public class Jaxb2Encoder extends AbstractEncoder<Object> {
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML);
}
@Override
public Publisher<ByteBuffer> encode(Publisher<? extends Object> messageStream, ResolvableType type,
MimeType mimeType, Object... hints) {

View File

@@ -42,10 +42,11 @@ public class StringEncoder extends AbstractEncoder<String> {
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

View File

@@ -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> T getHintByClass(Class<T> clazz, Object[] hints) {
return getHintByClass(clazz, hints, null);
}
public static <T> T getHintByClass(Class<T> 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;
}
}

View File

@@ -39,15 +39,15 @@ import org.springframework.web.bind.annotation.RequestBody;
*/
public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolver {
private final List<Decoder<?>> deserializers;
private final List<Decoder<?>> decoders;
private final ConversionService conversionService;
public RequestBodyArgumentResolver(List<Decoder<?>> deserializers, ConversionService service) {
Assert.notEmpty(deserializers, "At least one deserializer is required.");
public RequestBodyArgumentResolver(List<Decoder<?>> 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<Object> resolveArgument(MethodParameter parameter, ReactiveServerHttpRequest request) {
MediaType mediaType = resolveMediaType(request);
ResolvableType type = ResolvableType.forMethodParameter(parameter);
Publisher<ByteBuffer> inputStream = request.getBody();
Publisher<?> elementStream = inputStream;
Publisher<ByteBuffer> 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;

View File

@@ -48,17 +48,17 @@ import org.springframework.web.method.HandlerMethod;
*/
public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered {
private final List<Encoder<?>> serializers;
private final List<Encoder<?>> encoders;
private final ConversionService conversionService;
private int order = 0;
public ResponseBodyResultHandler(List<Encoder<?>> serializers, ConversionService service) {
Assert.notEmpty(serializers, "At least one serializers is required.");
public ResponseBodyResultHandler(List<Encoder<?>> 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<ByteBuffer> outputStream = serializer.encode((Publisher)elementStream, type, mediaType);
if (mediaType == null || mediaType.isWildcardType() || mediaType.isWildcardSubtype()) {
List<MimeType> 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<ByteBuffer> outputStream = encoder.encode((Publisher) elementStream, returnType, mediaType);
if (mediaType == null || mediaType.isWildcardType() || mediaType.isWildcardSubtype()) {
List<MimeType> 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;