Browse Source

Allow ProtobufHttpMessageConverter extensions

Make ProtobufFormatDelegate protected and visible to subclasses.
Expose constructor that allows passing the delegate in.

See gh-35403
pull/35684/head
rstoyanchev 2 months ago
parent
commit
754372a1f6
  1. 81
      spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java
  2. 2
      spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverter.java
  3. 2
      spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java

81
spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java

@ -17,9 +17,7 @@
package org.springframework.http.converter.protobuf; package org.springframework.http.converter.protobuf;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -105,7 +103,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
final ExtensionRegistry extensionRegistry; final ExtensionRegistry extensionRegistry;
private final @Nullable ProtobufFormatSupport protobufFormatSupport; private final ProtobufHttpMessageConverter.@Nullable ProtobufFormatDelegate protobufFormatDelegate;
/** /**
@ -124,21 +122,27 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
this(null, extensionRegistry); this(null, extensionRegistry);
} }
ProtobufHttpMessageConverter(@Nullable ProtobufFormatSupport formatSupport, /**
* Constructor for a subclass that supports additional formats.
* @param formatDelegate delegate to read and write additional formats
* @param extensionRegistry the registry to populate
*/
protected ProtobufHttpMessageConverter(
ProtobufHttpMessageConverter.@Nullable ProtobufFormatDelegate formatDelegate,
@Nullable ExtensionRegistry extensionRegistry) { @Nullable ExtensionRegistry extensionRegistry) {
if (formatSupport != null) { if (formatDelegate != null) {
this.protobufFormatSupport = formatSupport; this.protobufFormatDelegate = formatDelegate;
} }
else if (PROTOBUF_JSON_FORMAT_PRESENT) { else if (PROTOBUF_JSON_FORMAT_PRESENT) {
this.protobufFormatSupport = new ProtobufJavaUtilSupport(null, null); this.protobufFormatDelegate = new ProtobufJavaUtilDelegate(null, null);
} }
else { else {
this.protobufFormatSupport = null; this.protobufFormatDelegate = null;
} }
setSupportedMediaTypes(Arrays.asList(this.protobufFormatSupport != null ? setSupportedMediaTypes(Arrays.asList(this.protobufFormatDelegate != null ?
this.protobufFormatSupport.supportedMediaTypes() : new MediaType[] {PROTOBUF, PLUS_PROTOBUF, TEXT_PLAIN})); this.protobufFormatDelegate.supportedMediaTypes() : new MediaType[] {PROTOBUF, PLUS_PROTOBUF, TEXT_PLAIN}));
this.extensionRegistry = (extensionRegistry == null ? ExtensionRegistry.newInstance() : extensionRegistry); this.extensionRegistry = (extensionRegistry == null ? ExtensionRegistry.newInstance() : extensionRegistry);
} }
@ -172,13 +176,13 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
PLUS_PROTOBUF.isCompatibleWith(contentType)) { PLUS_PROTOBUF.isCompatibleWith(contentType)) {
builder.mergeFrom(inputMessage.getBody(), this.extensionRegistry); builder.mergeFrom(inputMessage.getBody(), this.extensionRegistry);
} }
else if (TEXT_PLAIN.isCompatibleWith(contentType)) { else if (MediaType.TEXT_PLAIN.isCompatibleWith(contentType)) {
InputStreamReader reader = new InputStreamReader(inputMessage.getBody(), charset); InputStreamReader reader = new InputStreamReader(inputMessage.getBody(), charset);
TextFormat.merge(reader, this.extensionRegistry, builder); TextFormat.merge(reader, this.extensionRegistry, builder);
} }
else if (this.protobufFormatSupport != null) { else if (this.protobufFormatDelegate != null) {
this.protobufFormatSupport.merge( this.protobufFormatDelegate.merge(
inputMessage.getBody(), charset, contentType, this.extensionRegistry, builder); inputMessage, contentType, charset, builder, this.extensionRegistry);
} }
return builder.build(); return builder.build();
} }
@ -206,7 +210,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
@Override @Override
protected boolean canWrite(@Nullable MediaType mediaType) { protected boolean canWrite(@Nullable MediaType mediaType) {
return (super.canWrite(mediaType) || return (super.canWrite(mediaType) ||
(this.protobufFormatSupport != null && this.protobufFormatSupport.supportsWriteOnly(mediaType))); (this.protobufFormatDelegate != null && this.protobufFormatDelegate.supportsWriteOnly(mediaType)));
} }
@Override @Override
@ -235,8 +239,8 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
outputStreamWriter.flush(); outputStreamWriter.flush();
outputMessage.getBody().flush(); outputMessage.getBody().flush();
} }
else if (this.protobufFormatSupport != null) { else if (this.protobufFormatDelegate != null) {
this.protobufFormatSupport.print(message, outputMessage.getBody(), contentType, charset); this.protobufFormatDelegate.print(message, outputMessage, contentType, charset);
outputMessage.getBody().flush(); outputMessage.getBody().flush();
} }
} }
@ -259,34 +263,51 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
/** /**
* Protobuf format support. * Contract to enable subclasses to plug in support for additional formats.
*/ */
interface ProtobufFormatSupport { protected interface ProtobufFormatDelegate {
/**
* Return the supported media types for the converter.
* <p>Note that {@link #PROTOBUF}, {@link #PLUS_PROTOBUF}, and {@link MediaType#TEXT_PLAIN}
* have built-in support and can be listed in addition to formats
* specific to this delegate.
*/
MediaType[] supportedMediaTypes(); MediaType[] supportedMediaTypes();
/**
* Whether the media type is supported for writing.
*/
boolean supportsWriteOnly(@Nullable MediaType mediaType); boolean supportsWriteOnly(@Nullable MediaType mediaType);
void merge(InputStream input, Charset charset, MediaType contentType, /**
ExtensionRegistry extensionRegistry, Message.Builder builder) * Use merge methods on {@link Message.Builder} to read a message from
* the given {@code HttpInputMessage}.
*/
void merge(HttpInputMessage inputMessage, MediaType contentType, Charset charset,
Message.Builder builder, ExtensionRegistry extensionRegistry)
throws IOException, HttpMessageConversionException; throws IOException, HttpMessageConversionException;
void print(Message message, OutputStream output, MediaType contentType, Charset charset) /**
* Use print methods on {@link Message.Builder} to write the message to
* the given {@code HttpOutputMessage}.
*/
void print(Message message, HttpOutputMessage outputMessage, MediaType contentType, Charset charset)
throws IOException, HttpMessageConversionException; throws IOException, HttpMessageConversionException;
} }
/** /**
* {@link ProtobufFormatSupport} implementation used when * {@link ProtobufFormatDelegate} implementation used when
* {@code com.google.protobuf.util.JsonFormat} is available. * {@code com.google.protobuf.util.JsonFormat} is available.
*/ */
static class ProtobufJavaUtilSupport implements ProtobufFormatSupport { static class ProtobufJavaUtilDelegate implements ProtobufFormatDelegate {
private final JsonFormat.Parser parser; private final JsonFormat.Parser parser;
private final JsonFormat.Printer printer; private final JsonFormat.Printer printer;
public ProtobufJavaUtilSupport(JsonFormat.@Nullable Parser parser, JsonFormat.@Nullable Printer printer) { public ProtobufJavaUtilDelegate(JsonFormat.@Nullable Parser parser, JsonFormat.@Nullable Printer printer) {
this.parser = (parser != null ? parser : JsonFormat.parser()); this.parser = (parser != null ? parser : JsonFormat.parser());
this.printer = (printer != null ? printer : JsonFormat.printer()); this.printer = (printer != null ? printer : JsonFormat.printer());
} }
@ -302,12 +323,12 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
} }
@Override @Override
public void merge(InputStream input, Charset charset, MediaType contentType, public void merge(HttpInputMessage inputMessage, MediaType contentType, Charset charset,
ExtensionRegistry extensionRegistry, Message.Builder builder) Message.Builder builder, ExtensionRegistry extensionRegistry)
throws IOException, HttpMessageConversionException { throws IOException, HttpMessageConversionException {
if (contentType.isCompatibleWith(APPLICATION_JSON)) { if (contentType.isCompatibleWith(APPLICATION_JSON)) {
InputStreamReader reader = new InputStreamReader(input, charset); InputStreamReader reader = new InputStreamReader(inputMessage.getBody(), charset);
this.parser.merge(reader, builder); this.parser.merge(reader, builder);
} }
else { else {
@ -317,11 +338,11 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
} }
@Override @Override
public void print(Message message, OutputStream output, MediaType contentType, Charset charset) public void print(Message message, HttpOutputMessage outputMessage, MediaType contentType, Charset charset)
throws IOException, HttpMessageConversionException { throws IOException, HttpMessageConversionException {
if (contentType.isCompatibleWith(APPLICATION_JSON)) { if (contentType.isCompatibleWith(APPLICATION_JSON)) {
OutputStreamWriter writer = new OutputStreamWriter(output, charset); OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), charset);
this.printer.appendTo(message, writer); this.printer.appendTo(message, writer);
writer.flush(); writer.flush();
} }

2
spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverter.java

@ -71,7 +71,7 @@ public class ProtobufJsonFormatHttpMessageConverter extends ProtobufHttpMessageC
public ProtobufJsonFormatHttpMessageConverter(JsonFormat.@Nullable Parser parser, public ProtobufJsonFormatHttpMessageConverter(JsonFormat.@Nullable Parser parser,
JsonFormat.@Nullable Printer printer, @Nullable ExtensionRegistry extensionRegistry) { JsonFormat.@Nullable Printer printer, @Nullable ExtensionRegistry extensionRegistry) {
super(new ProtobufJavaUtilSupport(parser, printer), extensionRegistry); super(new ProtobufJavaUtilDelegate(parser, printer), extensionRegistry);
} }
} }

2
spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java

@ -107,7 +107,7 @@ class ProtobufHttpMessageConverterTests {
@Test @Test
void writeJsonWithGoogleProtobuf() throws IOException { void writeJsonWithGoogleProtobuf() throws IOException {
this.converter = new ProtobufHttpMessageConverter( this.converter = new ProtobufHttpMessageConverter(
new ProtobufHttpMessageConverter.ProtobufJavaUtilSupport(null, null), new ProtobufHttpMessageConverter.ProtobufJavaUtilDelegate(null, null),
this.extensionRegistry); this.extensionRegistry);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
MediaType contentType = MediaType.APPLICATION_JSON; MediaType contentType = MediaType.APPLICATION_JSON;

Loading…
Cancel
Save