diff --git a/build.gradle b/build.gradle index 15df06a997..44b544f7f9 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,6 @@ configure(allprojects) { project -> ext.okhttp3Version = "3.2.0" ext.openjpaVersion = "2.4.1" ext.poiVersion = "3.14" - ext.protobufVersion = "2.6.1" ext.reactorVersion = "2.0.7.RELEASE" ext.romeVersion = "1.6.0" ext.seleniumVersion = "2.53.0" @@ -721,8 +720,8 @@ project("spring-web") { exclude group: "javax.servlet", module: "javax.servlet-api" } optional("log4j:log4j:1.2.17") - optional("com.googlecode.protobuf-java-format:protobuf-java-format:1.2") - optional("com.google.protobuf:protobuf-java:${protobufVersion}") + optional("com.google.protobuf:protobuf-java:2.6.1") + optional("com.googlecode.protobuf-java-format:protobuf-java-format:1.4") optional("javax.mail:javax.mail-api:${javamailVersion}") testCompile(project(":spring-context-support")) // for JafMediaTypeFactory testCompile("xmlunit:xmlunit:${xmlunitVersion}") 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 281d00a761..9958d6ba53 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -28,9 +28,9 @@ import com.google.protobuf.Message; import com.google.protobuf.TextFormat; import com.googlecode.protobuf.format.HtmlFormat; import com.googlecode.protobuf.format.JsonFormat; +import com.googlecode.protobuf.format.ProtobufFormatter; import com.googlecode.protobuf.format.XmlFormat; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -41,20 +41,19 @@ import org.springframework.util.FileCopyUtils; /** - * An {@code HttpMessageConverter} that can read and write Protobuf - * {@link com.google.protobuf.Message} using - * Google Protocol buffers. + * An {@code HttpMessageConverter} that reads and writes {@link com.google.protobuf.Message}s + * using Google Protocol Buffers. * - *

By default it supports {@code "application/json"}, {@code "application/xml"}, - * {@code "text/plain"} and {@code "application/x-protobuf"} while writing also - * supports {@code "text/html"} + *

By default, it supports {@code "application/x-protobuf"}, {@code "text/plain"}, + * {@code "application/json"}, {@code "application/xml"}, while also writing {@code "text/html"}. * - *

To generate Message Java classes you need to install the protoc binary. + *

To generate {@code Message} Java classes, you need to install the {@code protoc} binary. * - *

Tested against Protobuf version 2.5.0. + *

Requires Protobuf 2.6 and Protobuf Java Format 1.4, as of Spring 4.3. * * @author Alex Antonov * @author Brian Clozel + * @author Juergen Hoeller * @since 4.1 */ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter { @@ -67,10 +66,17 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter, Method> methodCache = new ConcurrentHashMap, Method>(); - - private ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + private final ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); /** @@ -85,7 +91,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { MediaType contentType = inputMessage.getHeaders().getContentType(); - contentType = (contentType != null ? contentType : PROTOBUF); - - Charset charset = getCharset(inputMessage.getHeaders()); - InputStreamReader reader = new InputStreamReader(inputMessage.getBody(), charset); + if (contentType == null) { + contentType = PROTOBUF; + } + Charset charset = contentType.getCharSet(); + if (charset == null) { + charset = DEFAULT_CHARSET; + } try { Message.Builder builder = getMessageBuilder(clazz); - - if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) { - JsonFormat.merge(reader, this.extensionRegistry, builder); - } - else if (MediaType.TEXT_PLAIN.isCompatibleWith(contentType)) { + if (MediaType.TEXT_PLAIN.isCompatibleWith(contentType)) { + InputStreamReader reader = new InputStreamReader(inputMessage.getBody(), charset); TextFormat.merge(reader, this.extensionRegistry, builder); } + else if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) { + JSON_FORMAT.merge(inputMessage.getBody(), charset, this.extensionRegistry, builder); + } else if (MediaType.APPLICATION_XML.isCompatibleWith(contentType)) { - XmlFormat.merge(reader, this.extensionRegistry, builder); + XML_FORMAT.merge(inputMessage.getBody(), charset, this.extensionRegistry, builder); } else { builder.mergeFrom(inputMessage.getBody(), this.extensionRegistry); } return builder.build(); } - catch (Exception e) { - throw new HttpMessageNotReadableException("Could not read Protobuf message: " + e.getMessage(), e); + catch (Exception ex) { + throw new HttpMessageNotReadableException("Could not read Protobuf message: " + ex.getMessage(), ex); } } - private Charset getCharset(HttpHeaders headers) { - if (headers == null || headers.getContentType() == null || headers.getContentType().getCharSet() == null) { - return DEFAULT_CHARSET; - } - return headers.getContentType().getCharSet(); - } - - /** - * Create a new {@code Message.Builder} instance for the given class. - *

This method uses a ConcurrentHashMap for caching method lookups. - */ - private Message.Builder getMessageBuilder(Class clazz) throws Exception { - Method method = methodCache.get(clazz); - if (method == null) { - method = clazz.getMethod("newBuilder"); - methodCache.put(clazz, method); - } - return (Message.Builder) method.invoke(clazz); - } - /** * This method overrides the parent implementation, since this HttpMessageConverter * can also produce {@code MediaType.HTML "text/html"} ContentType. */ @Override protected boolean canWrite(MediaType mediaType) { - return super.canWrite(mediaType) || MediaType.TEXT_HTML.isCompatibleWith(mediaType); + return (super.canWrite(mediaType) || MediaType.TEXT_HTML.isCompatibleWith(mediaType)); } @Override @@ -163,27 +157,27 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverterThis method uses a ConcurrentHashMap for caching method lookups. + */ + private static Message.Builder getMessageBuilder(Class clazz) throws Exception { + Method method = methodCache.get(clazz); + if (method == null) { + method = clazz.getMethod("newBuilder"); + methodCache.put(clazz, method); + } + return (Message.Builder) method.invoke(clazz); } } 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 3334a7f372..0a82b7d469 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-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -31,9 +31,9 @@ import org.springframework.protobuf.SecondMsg; import static org.junit.Assert.*; import static org.mockito.Mockito.*; - /** - * Test suite for {@link ProtobufHttpMessageConverter} + * Test suite for {@link ProtobufHttpMessageConverter}. + * * @author Alex Antonov */ public class ProtobufHttpMessageConverterTests { @@ -52,6 +52,7 @@ public class ProtobufHttpMessageConverterTests { this.testMsg = Msg.newBuilder().setFoo("Foo").setBlah(SecondMsg.newBuilder().setBlah(123).build()).build(); } + @Test public void extensionRegistryInitialized() { verify(this.registryInitializer, times(1)).initializeExtensionRegistry(anyObject()); @@ -74,6 +75,7 @@ public class ProtobufHttpMessageConverterTests { assertTrue(this.converter.canRead(Msg.class, MediaType.APPLICATION_JSON)); assertTrue(this.converter.canRead(Msg.class, MediaType.APPLICATION_XML)); assertTrue(this.converter.canRead(Msg.class, MediaType.TEXT_PLAIN)); + // only supported as an output format assertFalse(this.converter.canRead(Msg.class, MediaType.TEXT_HTML)); } @@ -115,9 +117,11 @@ public class ProtobufHttpMessageConverterTests { Message result = Msg.parseFrom(outputMessage.getBodyAsBytes()); assertEquals(this.testMsg, result); - String messageHeader = outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_MESSAGE_HEADER); + String messageHeader = + outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_MESSAGE_HEADER); assertEquals("Msg", messageHeader); - String schemaHeader = outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_SCHEMA_HEADER); + String schemaHeader = + outputMessage.getHeaders().getFirst(ProtobufHttpMessageConverter.X_PROTOBUF_SCHEMA_HEADER); assertEquals("sample.proto", schemaHeader); } @@ -133,4 +137,5 @@ public class ProtobufHttpMessageConverterTests { this.converter.write(this.testMsg, contentType, outputMessage); assertEquals(-1, outputMessage.getHeaders().getContentLength()); } + }