@ -17,20 +17,21 @@
@@ -17,20 +17,21 @@
package org.springframework.http.converter.protobuf ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.InputStreamReader ;
import java.io.OutputStream ;
import java.io.OutputStreamWriter ;
import java.lang.reflect.Method ;
import java.nio.charset.Charset ;
import java.nio.charset.StandardCharsets ;
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.googlecode.protobuf.format.HtmlFormat ;
import com.googlecode.protobuf.format.JsonFormat ;
import com.googlecode.protobuf.format.FormatFactory ;
import com.googlecode.protobuf.format.ProtobufFormatter ;
import com.googlecode.protobuf.format.XmlFormat ;
import org.springframework.http.HttpInputMessage ;
import org.springframework.http.HttpOutputMessage ;
@ -38,18 +39,28 @@ import org.springframework.http.MediaType;
@@ -38,18 +39,28 @@ import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter ;
import org.springframework.http.converter.HttpMessageNotReadableException ;
import org.springframework.http.converter.HttpMessageNotWritableException ;
import org.springframework.util.FileCopy Utils ;
import org.springframework.util.Class Utils ;
/ * *
* An { @code HttpMessageConverter } that reads and writes { @link com . google . protobuf . Message } s
* using < a href = "https://developers.google.com/protocol-buffers/" > Google Protocol Buffers < / a > .
*
* < p > By default , it supports { @code "application/x-protobuf" } , { @code "text/plain" } ,
* { @code "application/json" } , { @code "application/xml" } , while also writing { @code "text/html" } .
* < p > This converter supports by default { @code "application/x-protobuf" } and { @code "text/plain" }
* with the official { @code "com.google.protobuf:protobuf-java" } library .
*
* < p > Other formats can be supported with additional libraries :
* < ul >
* < li > { @code "application/json" } with the official library
* { @code "com.google.protobuf:protobuf-java-util" }
* < li > { @code "application/json" } , { @code "application/xml" } and { @code "text/html" } ( write only )
* can be supported with the 3rd party library
* { @code "com.googlecode.protobuf-java-format:protobuf-java-format" }
* < / ul >
*
* < p > To generate { @code Message } Java classes , you need to install the { @code protoc } binary .
*
* < p > Requires Protobuf 2 . 6 and Protobuf Java Format 1 . 4 , as of Spring 4 . 3 .
* Supports up to Protobuf 3 . 0 . 0 .
*
* @author Alex Antonov
* @author Brian Clozel
@ -66,18 +77,32 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
@@ -66,18 +77,32 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message" ;
private static final boolean isProtobufJavaUtilPresent =
ClassUtils . isPresent ( "com.google.protobuf.util.JsonFormat" , ProtobufHttpMessageConverter . class . getClassLoader ( ) ) ;
private static final ProtobufFormatter JSON_FORMAT = new JsonFormat ( ) ;
private static final ProtobufFormatter XML_FORMAT = new XmlFormat ( ) ;
private static final boolean isProtobufJavaFormatPresent =
ClassUtils . isPresent ( "com.googlecode.protobuf.format.JsonFormat" , ProtobufHttpMessageConverter . class . getClassLoader ( ) ) ;
private static final ProtobufFormatter HTML_FORMAT = new HtmlFormat ( ) ;
private static final MediaType [ ] SUPPORTED_MEDIATYPES ;
private final ProtobufFormatsSupport protobufFormatsSupport ;
private static final ConcurrentHashMap < Class < ? > , Method > methodCache = new ConcurrentHashMap < > ( ) ;
private final ExtensionRegistry extensionRegistry = ExtensionRegistry . newInstance ( ) ;
static {
if ( isProtobufJavaFormatPresent ) {
SUPPORTED_MEDIATYPES = new MediaType [ ] { PROTOBUF , MediaType . TEXT_PLAIN , MediaType . APPLICATION_XML ,
MediaType . APPLICATION_JSON } ;
}
else if ( isProtobufJavaUtilPresent ) {
SUPPORTED_MEDIATYPES = new MediaType [ ] { PROTOBUF , MediaType . TEXT_PLAIN , MediaType . APPLICATION_JSON } ;
}
else {
SUPPORTED_MEDIATYPES = new MediaType [ ] { PROTOBUF , MediaType . TEXT_PLAIN } ;
}
}
/ * *
* Construct a new instance .
@ -91,7 +116,16 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
@@ -91,7 +116,16 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
* that allows the registration of message extensions .
* /
public ProtobufHttpMessageConverter ( ExtensionRegistryInitializer registryInitializer ) {
super ( PROTOBUF , MediaType . TEXT_PLAIN , MediaType . APPLICATION_JSON , MediaType . APPLICATION_XML ) ;
super ( SUPPORTED_MEDIATYPES ) ;
if ( isProtobufJavaFormatPresent ) {
this . protobufFormatsSupport = new ProtobufJavaFormatSupport ( ) ;
}
else if ( isProtobufJavaUtilPresent ) {
this . protobufFormatsSupport = new ProtobufJavaUtilSupport ( ) ;
}
else {
this . protobufFormatsSupport = null ;
}
if ( registryInitializer ! = null ) {
registryInitializer . initializeExtensionRegistry ( this . extensionRegistry ) ;
}
@ -123,18 +157,16 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
@@ -123,18 +157,16 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
try {
Message . Builder builder = getMessageBuilder ( clazz ) ;
if ( MediaType . TEXT_PLAIN . isCompatibleWith ( contentType ) ) {
if ( PROTOBUF . isCompatibleWith ( contentType ) ) {
builder . mergeFrom ( inputMessage . getBody ( ) , this . extensionRegistry ) ;
}
else 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 ) ) {
XML_FORMAT . merge ( inputMessage . getBody ( ) , charset , this . extensionRegistry , builder ) ;
}
else {
builder . mergeFrom ( inputMessage . getBody ( ) , this . extensionRegistry ) ;
else if ( isProtobufJavaUtilPresent | | isProtobufJavaFormatPresent ) {
this . protobufFormatsSupport . merge ( inputMessage . getBody ( ) , charset , contentType ,
this . extensionRegistry , builder ) ;
}
return builder . build ( ) ;
}
@ -149,7 +181,8 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
@@ -149,7 +181,8 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
* /
@Override
protected boolean canWrite ( MediaType mediaType ) {
return ( super . canWrite ( mediaType ) | | MediaType . TEXT_HTML . isCompatibleWith ( mediaType ) ) ;
return ( super . canWrite ( mediaType ) | |
( isProtobufJavaFormatPresent & & MediaType . TEXT_HTML . isCompatibleWith ( mediaType ) ) ) ;
}
@Override
@ -165,23 +198,21 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
@@ -165,23 +198,21 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
charset = DEFAULT_CHARSET ;
}
if ( MediaType . TEXT_PLAIN . isCompatibleWith ( contentType ) ) {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter ( outputMessage . getBody ( ) , charset ) ;
if ( PROTOBUF . isCompatibleWith ( contentType ) ) {
setProtoHeader ( outputMessage , message ) ;
CodedOutputStream codedOutputStream = CodedOutputStream . newInstance ( outputMessage . getBody ( ) ) ;
message . writeTo ( codedOutputStream ) ;
codedOutputStream . flush ( ) ;
}
else if ( MediaType . TEXT_PLAIN . isCompatibleWith ( contentType ) ) {
final OutputStreamWriter outputStreamWriter = new OutputStreamWriter ( outputMessage . getBody ( ) , charset ) ;
TextFormat . print ( message , outputStreamWriter ) ;
outputStreamWriter . flush ( ) ;
outputMessage . getBody ( ) . flush ( ) ;
}
else if ( MediaType . APPLICATION_JSON . isCompatibleWith ( contentType ) ) {
JSON_FORMAT . print ( message , outputMessage . getBody ( ) , charset ) ;
}
else if ( MediaType . APPLICATION_XML . isCompatibleWith ( contentType ) ) {
XML_FORMAT . print ( message , outputMessage . getBody ( ) , charset ) ;
}
else if ( MediaType . TEXT_HTML . isCompatibleWith ( contentType ) ) {
HTML_FORMAT . print ( message , outputMessage . getBody ( ) , charset ) ;
}
else if ( PROTOBUF . isCompatibleWith ( contentType ) ) {
setProtoHeader ( outputMessage , message ) ;
FileCopyUtils . copy ( message . toByteArray ( ) , outputMessage . getBody ( ) ) ;
else if ( isProtobufJavaUtilPresent | | isProtobufJavaFormatPresent ) {
this . protobufFormatsSupport . print ( message , outputMessage . getBody ( ) , contentType , charset ) ;
outputMessage . getBody ( ) . flush ( ) ;
}
}
@ -210,4 +241,100 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
@@ -210,4 +241,100 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
return ( Message . Builder ) method . invoke ( clazz ) ;
}
private interface ProtobufFormatsSupport {
void merge ( InputStream input , Charset cs , MediaType contentType , ExtensionRegistry extensionRegistry ,
Message . Builder builder ) throws IOException ;
void print ( Message message , OutputStream output , MediaType contentType , Charset cs ) throws IOException ;
}
private class ProtobufJavaUtilSupport implements ProtobufFormatsSupport {
private final com . google . protobuf . util . JsonFormat . Parser parser ;
private final com . google . protobuf . util . JsonFormat . Printer printer ;
public ProtobufJavaUtilSupport ( ) {
this . parser = com . google . protobuf . util . JsonFormat . parser ( ) ;
this . printer = com . google . protobuf . util . JsonFormat . printer ( ) ;
}
@Override
public void merge ( InputStream input , Charset cs , MediaType contentType ,
ExtensionRegistry extensionRegistry , Message . Builder builder ) throws IOException {
if ( contentType . isCompatibleWith ( MediaType . APPLICATION_JSON ) ) {
InputStreamReader reader = new InputStreamReader ( input , cs ) ;
this . parser . merge ( reader , builder ) ;
}
else {
throw new UnsupportedOperationException ( "com.googlecode.protobuf:protobuf-java-util does not support "
+ contentType . toString ( ) + " format" ) ;
}
}
@Override
public void print ( Message message , OutputStream output , MediaType contentType , Charset cs ) throws IOException {
if ( contentType . isCompatibleWith ( MediaType . APPLICATION_JSON ) ) {
this . printer . appendTo ( message , new OutputStreamWriter ( output , cs ) ) ;
}
else {
throw new UnsupportedOperationException ( "com.googlecode.protobuf:protobuf-java-util does not support "
+ contentType . toString ( ) + " format" ) ;
}
}
}
private class ProtobufJavaFormatSupport implements ProtobufFormatsSupport {
private final FormatFactory FORMAT_FACTORY ;
private final ProtobufFormatter JSON_FORMATTER ;
private final ProtobufFormatter XML_FORMATTER ;
private final ProtobufFormatter HTML_FORMATTER ;
public ProtobufJavaFormatSupport ( ) {
FORMAT_FACTORY = new FormatFactory ( ) ;
JSON_FORMATTER = FORMAT_FACTORY . createFormatter ( FormatFactory . Formatter . JSON ) ;
XML_FORMATTER = FORMAT_FACTORY . createFormatter ( FormatFactory . Formatter . XML ) ;
HTML_FORMATTER = FORMAT_FACTORY . createFormatter ( FormatFactory . Formatter . HTML ) ;
}
@Override
public void merge ( InputStream input , Charset cs , MediaType contentType ,
ExtensionRegistry extensionRegistry , Message . Builder builder ) throws IOException {
if ( contentType . isCompatibleWith ( MediaType . APPLICATION_JSON ) ) {
JSON_FORMATTER . merge ( input , cs , extensionRegistry , builder ) ;
}
else if ( contentType . isCompatibleWith ( MediaType . APPLICATION_XML ) ) {
XML_FORMATTER . merge ( input , cs , extensionRegistry , builder ) ;
}
else {
throw new UnsupportedOperationException ( "com.google.protobuf.util does not support "
+ contentType . toString ( ) + " format" ) ;
}
}
@Override
public void print ( Message message , OutputStream output , MediaType contentType , Charset cs ) throws IOException {
if ( contentType . isCompatibleWith ( MediaType . APPLICATION_JSON ) ) {
JSON_FORMATTER . print ( message , output , cs ) ;
}
else if ( contentType . isCompatibleWith ( MediaType . APPLICATION_XML ) ) {
XML_FORMATTER . print ( message , output , cs ) ;
}
else if ( contentType . isCompatibleWith ( MediaType . TEXT_HTML ) ) {
HTML_FORMATTER . print ( message , output , cs ) ;
}
else {
throw new UnsupportedOperationException ( "com.google.protobuf.util does not support "
+ contentType . toString ( ) + " format" ) ;
}
}
}
}