diff --git a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java index 93b8db7fa3..b6295f6b0e 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.java @@ -24,12 +24,14 @@ import java.io.OutputStreamWriter; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import com.google.protobuf.CodedOutputStream; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import com.google.protobuf.TextFormat; +import com.google.protobuf.util.JsonFormat; import com.googlecode.protobuf.format.FormatFactory; import com.googlecode.protobuf.format.ProtobufFormatter; @@ -41,30 +43,36 @@ import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.util.ClassUtils; +import static org.springframework.http.MediaType.*; + /** * An {@code HttpMessageConverter} that reads and writes {@link com.google.protobuf.Message}s * using Google Protocol Buffers. * - *
This converter supports by default {@code "application/x-protobuf"} and {@code "text/plain"} - * with the official {@code "com.google.protobuf:protobuf-java"} library. - * - *
Other formats can be supported with additional libraries: - *
To generate {@code Message} Java classes, you need to install the {@code protoc} binary. * - *
Requires Protobuf 2.6 or 3.x and Protobuf Java Format 1.4 or higher, as of Spring 5.0. + *
This converter supports by default {@code "application/x-protobuf"} and {@code "text/plain"} + * with the official {@code "com.google.protobuf:protobuf-java"} library. Other formats can be + * supported with one of the following additional libraries on the classpath: + *
Requires Protobuf 2.6 or higher (and Protobuf Java Format 1.4 or higher for formatting).
+ * This converter will auto-adapt to Protobuf 3 and its default {@code protobuf-java-util} JSON
+ * format if the Protobuf 2 based {@code protobuf-java-format} isn't present; however, for more
+ * explicit JSON setup on Protobuf 3, consider {@link ProtobufJsonFormatHttpMessageConverter}.
*
* @author Alex Antonov
* @author Brian Clozel
* @author Juergen Hoeller
* @since 4.1
+ * @see FormatFactory
+ * @see JsonFormat
+ * @see ProtobufJsonFormatHttpMessageConverter
*/
public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter Requires Protobuf 3.x and {@code "com.google.protobuf:protobuf-java-util"} 3.x,
+ * with 3.3 or higher recommended.
+ *
+ * @author Juergen Hoeller
+ * @since 5.0
+ * @see JsonFormat#parser()
+ * @see JsonFormat#printer()
+ * @see #ProtobufJsonFormatHttpMessageConverter(JsonFormat.Parser, JsonFormat.Printer)
+ */
+public class ProtobufJsonFormatHttpMessageConverter extends ProtobufHttpMessageConverter {
+
+ /**
+ * Construct a new {@code ProtobufJsonFormatHttpMessageConverter} with default
+ * {@link JsonFormat.Parser} and {@link JsonFormat.Printer} configuration.
+ */
+ public ProtobufJsonFormatHttpMessageConverter() {
+ this(null, null, null);
+ }
+
+ /**
+ * Construct a new {@code ProtobufJsonFormatHttpMessageConverter} with the given
+ * {@link JsonFormat.Parser} and {@link JsonFormat.Printer} configuration.
+ * @param parser the JSON parser configuration
+ * @param printer the JSON printer configuration
+ */
+ public ProtobufJsonFormatHttpMessageConverter(JsonFormat.Parser parser, JsonFormat.Printer printer) {
+ this(parser, printer, null);
+ }
+
+ /**
+ * Construct a new {@code ProtobufJsonFormatHttpMessageConverter} with the given
+ * {@link JsonFormat.Parser} and {@link JsonFormat.Printer} configuration, also
+ * accepting an initializer that allows the registration of message extensions
+ * @param parser the JSON parser configuration
+ * @param printer the JSON printer configuration
+ * @param registryInitializer an initializer for message extensions
+ */
+ public ProtobufJsonFormatHttpMessageConverter(JsonFormat.Parser parser, JsonFormat.Printer printer,
+ ExtensionRegistryInitializer registryInitializer) {
+
+ super(new ProtobufJavaUtilSupport(parser, printer), registryInitializer);
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java
index 1ef5b55752..72171e0767 100644
--- a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java
+++ b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2017 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.
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.*;
* Test suite for {@link ProtobufHttpMessageConverter}.
*
* @author Alex Antonov
+ * @author Juergen Hoeller
*/
public class ProtobufHttpMessageConverterTests {
@@ -46,7 +47,7 @@ public class ProtobufHttpMessageConverterTests {
@Before
- public void setUp() {
+ public void setup() {
this.registryInitializer = mock(ExtensionRegistryInitializer.class);
this.converter = new ProtobufHttpMessageConverter(this.registryInitializer);
this.testMsg = Msg.newBuilder().setFoo("Foo").setBlah(SecondMsg.newBuilder().setBlah(123).build()).build();
@@ -60,12 +61,7 @@ public class ProtobufHttpMessageConverterTests {
@Test
public void extensionRegistryNull() {
- try {
- new ProtobufHttpMessageConverter(null);
- }
- catch (Exception ex) {
- fail("Unable to create ProtobufHttpMessageConverter with null extensionRegistry");
- }
+ new ProtobufHttpMessageConverter(null);
}
@Test
diff --git a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java
new file mode 100644
index 0000000000..2d2d38703a
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2017 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.http.converter.protobuf;
+
+import java.io.IOException;
+
+import com.google.protobuf.Message;
+import com.google.protobuf.util.JsonFormat;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.http.MediaType;
+import org.springframework.http.MockHttpInputMessage;
+import org.springframework.http.MockHttpOutputMessage;
+import org.springframework.protobuf.Msg;
+import org.springframework.protobuf.SecondMsg;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Test suite for {@link ProtobufJsonFormatHttpMessageConverter}.
+ *
+ * @author Juergen Hoeller
+ */
+public class ProtobufJsonFormatHttpMessageConverterTests {
+
+ private ProtobufHttpMessageConverter converter;
+
+ private ExtensionRegistryInitializer registryInitializer;
+
+ private Msg testMsg;
+
+
+ @Before
+ public void setup() {
+ this.registryInitializer = mock(ExtensionRegistryInitializer.class);
+ this.converter = new ProtobufJsonFormatHttpMessageConverter(
+ JsonFormat.parser(), JsonFormat.printer(), this.registryInitializer);
+ this.testMsg = Msg.newBuilder().setFoo("Foo").setBlah(SecondMsg.newBuilder().setBlah(123).build()).build();
+ }
+
+
+ @Test
+ public void extensionRegistryInitialized() {
+ verify(this.registryInitializer, times(1)).initializeExtensionRegistry(any());
+ }
+
+ @Test
+ public void extensionRegistryNull() {
+ new ProtobufHttpMessageConverter(null);
+ }
+
+ @Test
+ public void canRead() {
+ assertTrue(this.converter.canRead(Msg.class, null));
+ assertTrue(this.converter.canRead(Msg.class, ProtobufHttpMessageConverter.PROTOBUF));
+ assertTrue(this.converter.canRead(Msg.class, MediaType.APPLICATION_JSON));
+ assertTrue(this.converter.canRead(Msg.class, MediaType.TEXT_PLAIN));
+ }
+
+ @Test
+ public void canWrite() {
+ assertTrue(this.converter.canWrite(Msg.class, null));
+ assertTrue(this.converter.canWrite(Msg.class, ProtobufHttpMessageConverter.PROTOBUF));
+ assertTrue(this.converter.canWrite(Msg.class, MediaType.APPLICATION_JSON));
+ assertTrue(this.converter.canWrite(Msg.class, MediaType.TEXT_PLAIN));
+ }
+
+ @Test
+ public void read() throws IOException {
+ byte[] body = this.testMsg.toByteArray();
+ MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
+ inputMessage.getHeaders().setContentType(ProtobufHttpMessageConverter.PROTOBUF);
+ Message result = this.converter.read(Msg.class, inputMessage);
+ assertEquals(this.testMsg, result);
+ }
+
+ @Test
+ public void readNoContentType() throws IOException {
+ byte[] body = this.testMsg.toByteArray();
+ MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
+ Message result = this.converter.read(Msg.class, inputMessage);
+ assertEquals(this.testMsg, result);
+ }
+
+ @Test
+ public void write() throws IOException {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ MediaType contentType = ProtobufHttpMessageConverter.PROTOBUF;
+ this.converter.write(this.testMsg, contentType, outputMessage);
+ assertEquals(contentType, outputMessage.getHeaders().getContentType());
+ assertTrue(outputMessage.getBodyAsBytes().length > 0);
+ Message result = Msg.parseFrom(outputMessage.getBodyAsBytes());
+ assertEquals(this.testMsg, result);
+
+ String messageHeader =
+ outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_MESSAGE_HEADER);
+ assertEquals("Msg", messageHeader);
+ String schemaHeader =
+ outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_SCHEMA_HEADER);
+ assertEquals("sample.proto", schemaHeader);
+ }
+
+ @Test
+ public void defaultContentType() throws Exception {
+ assertEquals(ProtobufHttpMessageConverter.PROTOBUF, this.converter.getDefaultContentType(this.testMsg));
+ }
+
+ @Test
+ public void getContentLength() throws Exception {
+ MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+ MediaType contentType = ProtobufHttpMessageConverter.PROTOBUF;
+ this.converter.write(this.testMsg, contentType, outputMessage);
+ assertEquals(-1, outputMessage.getHeaders().getContentLength());
+ }
+
+}