From 3899b7a909b15f521f99b2d267cb74264ac9682e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 25 Jul 2018 14:15:50 +0200 Subject: [PATCH] Refactor DefaultCodecs.protobufWriter into protobufEncoder Includes nullability declarations for the protobuf package. Issue: SPR-15776 --- .../http/codec/CodecConfigurer.java | 7 +++-- .../codec/protobuf/ProtobufCodecSupport.java | 1 + .../http/codec/protobuf/ProtobufDecoder.java | 29 ++++++++++------- .../http/codec/protobuf/ProtobufEncoder.java | 15 ++++++--- .../protobuf/ProtobufHttpMessageWriter.java | 19 +++++++++--- .../http/codec/protobuf/package-info.java | 10 ++++++ .../http/codec/support/BaseDefaultCodecs.java | 31 ++++++++++--------- 7 files changed, 74 insertions(+), 38 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/http/codec/protobuf/package-info.java diff --git a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java index 29d5e7cecd..4b9487aefe 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java @@ -119,12 +119,13 @@ public interface CodecConfigurer { void protobufDecoder(Decoder decoder); /** - * Override the default Protobuf {@code HttpMessageReader}. - * @param decoder the decoder instance to use + * Override the default Protobuf {@code Encoder}. + * @param encoder the encoder instance to use * @since 5.1 + * @see org.springframework.http.codec.protobuf.ProtobufEncoder * @see org.springframework.http.codec.protobuf.ProtobufHttpMessageWriter */ - void protobufWriter(HttpMessageWriter decoder); + void protobufEncoder(Encoder encoder); /** * Whether to log form data at DEBUG level, and headers at TRACE level. diff --git a/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufCodecSupport.java b/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufCodecSupport.java index d1e08eddc9..611dcd9630 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufCodecSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufCodecSupport.java @@ -48,4 +48,5 @@ public abstract class ProtobufCodecSupport { protected List getMimeTypes() { return MIME_TYPES; } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufDecoder.java index 4e783c396c..0b6218cb6b 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufDecoder.java @@ -20,7 +20,7 @@ import java.io.IOException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import com.google.protobuf.CodedInputStream; @@ -35,7 +35,9 @@ import org.springframework.core.codec.Decoder; import org.springframework.core.codec.DecodingException; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.MimeType; /** @@ -66,7 +68,7 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder, Method> methodCache = new ConcurrentHashMap<>(); + private static final ConcurrentMap, Method> methodCache = new ConcurrentReferenceHashMap<>(); private final ExtensionRegistry extensionRegistry; @@ -90,18 +92,20 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder decode(Publisher inputStream, ResolvableType elementType, - MimeType mimeType, Map hints) { + @Nullable MimeType mimeType, @Nullable Map hints) { return Flux.from(inputStream) .concatMap(new MessageDecoderFunction(elementType, this.maxMessageSize)); @@ -109,10 +113,11 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder decodeToMono(Publisher inputStream, ResolvableType elementType, - MimeType mimeType, Map hints) { + @Nullable MimeType mimeType, @Nullable Map hints) { + return DataBufferUtils.join(inputStream).map(dataBuffer -> { try { - Message.Builder builder = getMessageBuilder(elementType.getRawClass()); + Message.Builder builder = getMessageBuilder(elementType.toClass()); builder.mergeFrom(CodedInputStream.newInstance(dataBuffer.asByteBuffer()), this.extensionRegistry); Message message = builder.build(); DataBufferUtils.release(dataBuffer); @@ -153,6 +158,7 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder apply(DataBuffer input) { - try { if (this.output == null) { int firstByte = input.read(); @@ -176,7 +182,7 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder this.maxMessageSize) { return Flux.error(new DecodingException( "The number of bytes to read parsed in the incoming stream (" + - this.messageBytesToRead + ") exceeds the configured limit (" + this.maxMessageSize + ")")); + this.messageBytesToRead + ") exceeds the configured limit (" + this.maxMessageSize + ")")); } this.output = input.factory().allocateBuffer(this.messageBytesToRead); } @@ -187,7 +193,7 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder streamingMediaTypes = MIME_TYPES .stream() - .map(mimeType -> new MediaType(mimeType.getType(), mimeType.getSubtype(), Collections.singletonMap(DELIMITED_KEY, DELIMITED_VALUE))) + .map(mimeType -> new MediaType(mimeType.getType(), mimeType.getSubtype(), + Collections.singletonMap(DELIMITED_KEY, DELIMITED_VALUE))) .collect(Collectors.toList()); + @Override - public boolean canEncode(ResolvableType elementType, MimeType mimeType) { - return Message.class.isAssignableFrom(elementType.getRawClass()) && supportsMimeType(mimeType); + public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType) { + return Message.class.isAssignableFrom(elementType.toClass()) && supportsMimeType(mimeType); } @Override - public Flux encode(Publisher inputStream, - DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map hints) { + public Flux encode(Publisher inputStream, DataBufferFactory bufferFactory, + ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map hints) { + return Flux .from(inputStream) .map(message -> encodeMessage(message, bufferFactory, !(inputStream instanceof Mono))); @@ -100,4 +104,5 @@ public class ProtobufEncoder extends ProtobufCodecSupport implements HttpMessage public List getEncodableMimeTypes() { return getMimeTypes(); } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufHttpMessageWriter.java index dd55a557fd..ae118ee0e0 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/protobuf/ProtobufHttpMessageWriter.java @@ -19,7 +19,7 @@ package org.springframework.http.codec.protobuf; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; @@ -29,11 +29,13 @@ import reactor.core.publisher.Mono; import org.springframework.core.ResolvableType; import org.springframework.core.codec.DecodingException; +import org.springframework.core.codec.Encoder; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageEncoder; import org.springframework.lang.Nullable; +import org.springframework.util.ConcurrentReferenceHashMap; /** * {@code HttpMessageWriter} that can write a protobuf {@link Message} and adds @@ -49,18 +51,25 @@ import org.springframework.lang.Nullable; */ public class ProtobufHttpMessageWriter extends EncoderHttpMessageWriter { - private static final ConcurrentHashMap, Method> methodCache = new ConcurrentHashMap<>(); - private static final String X_PROTOBUF_SCHEMA_HEADER = "X-Protobuf-Schema"; private static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message"; + private static final ConcurrentMap, Method> methodCache = new ConcurrentReferenceHashMap<>(); + + /** + * Create a new {@code ProtobufHttpMessageWriter} with a default {@link ProtobufEncoder}. + */ public ProtobufHttpMessageWriter() { super(new ProtobufEncoder()); } - public ProtobufHttpMessageWriter(ProtobufEncoder encoder) { + /** + * Create a new {@code ProtobufHttpMessageWriter} with the given encoder. + * @param encoder the Protobuf message encoder to use + */ + public ProtobufHttpMessageWriter(Encoder encoder) { super(encoder); } @@ -71,7 +80,7 @@ public class ProtobufHttpMessageWriter extends EncoderHttpMessageWriter @Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map hints) { try { - Message.Builder builder = getMessageBuilder(elementType.getRawClass()); + Message.Builder builder = getMessageBuilder(elementType.toClass()); Descriptors.Descriptor descriptor = builder.getDescriptorForType(); message.getHeaders().add(X_PROTOBUF_SCHEMA_HEADER, descriptor.getFile().getName()); message.getHeaders().add(X_PROTOBUF_MESSAGE_HEADER, descriptor.getFullName()); diff --git a/spring-web/src/main/java/org/springframework/http/codec/protobuf/package-info.java b/spring-web/src/main/java/org/springframework/http/codec/protobuf/package-info.java new file mode 100644 index 0000000000..c9dfcb9196 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/codec/protobuf/package-info.java @@ -0,0 +1,10 @@ +/** + * Provides an encoder and a decoder for + * Google Protocol Buffers. + */ +@NonNullApi +@NonNullFields +package org.springframework.http.codec.protobuf; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java index db03d284eb..2634f862ca 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.http.codec.support; import java.util.ArrayList; @@ -42,6 +43,7 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.Jackson2SmileDecoder; import org.springframework.http.codec.json.Jackson2SmileEncoder; import org.springframework.http.codec.protobuf.ProtobufDecoder; +import org.springframework.http.codec.protobuf.ProtobufEncoder; import org.springframework.http.codec.protobuf.ProtobufHttpMessageWriter; import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; @@ -53,6 +55,7 @@ import org.springframework.util.ClassUtils; * as a base for client and server specific variants. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze */ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { @@ -78,13 +81,13 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { private Decoder jackson2JsonDecoder; @Nullable - private Decoder protobufDecoder; + private Encoder jackson2JsonEncoder; @Nullable - private Encoder jackson2JsonEncoder; + private Decoder protobufDecoder; @Nullable - private HttpMessageWriter protobufWriter; + private Encoder protobufEncoder; private boolean enableLoggingRequestDetails = false; @@ -107,8 +110,8 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { } @Override - public void protobufWriter(HttpMessageWriter writer) { - this.protobufWriter = writer; + public void protobufEncoder(Encoder encoder) { + this.protobufEncoder = encoder; } @Override @@ -205,6 +208,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { * or for multipart requests only ("true"). Generally the two sets are the * same except for the multipart writer itself. */ + @SuppressWarnings("unchecked") final List> getTypedWriters(boolean forMultipart) { if (!this.registerDefaults) { return Collections.emptyList(); @@ -220,7 +224,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { extendTypedWriters(writers); } if (protobufPresent) { - writers.add(getProtobufWriter()); + writers.add(new ProtobufHttpMessageWriter((Encoder) getProtobufEncoder())); } return writers; } @@ -277,23 +281,22 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { } - // Accessors for use in sub-classes... + // Accessors for use in subclasses... protected Decoder getJackson2JsonDecoder() { return (this.jackson2JsonDecoder != null ? this.jackson2JsonDecoder : new Jackson2JsonDecoder()); } - protected Decoder getProtobufDecoder() { - return (this.protobufDecoder != null ? this.protobufDecoder : new ProtobufDecoder()); - } - protected Encoder getJackson2JsonEncoder() { return (this.jackson2JsonEncoder != null ? this.jackson2JsonEncoder : new Jackson2JsonEncoder()); } - protected HttpMessageWriter getProtobufWriter() { - return (this.protobufWriter != null ? this.protobufWriter : new ProtobufHttpMessageWriter()); + protected Decoder getProtobufDecoder() { + return (this.protobufDecoder != null ? this.protobufDecoder : new ProtobufDecoder()); } -} + protected Encoder getProtobufEncoder() { + return (this.protobufEncoder != null ? this.protobufEncoder : new ProtobufEncoder()); + } +}