1
1
/*
2
- * Copyright 2002-2014 the original author or authors.
2
+ * Copyright 2002-2016 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
30
30
import com .googlecode .protobuf .format .JsonFormat ;
31
31
import com .googlecode .protobuf .format .XmlFormat ;
32
32
33
- import org .springframework .http .HttpHeaders ;
34
33
import org .springframework .http .HttpInputMessage ;
35
34
import org .springframework .http .HttpOutputMessage ;
36
35
import org .springframework .http .MediaType ;
41
40
42
41
43
42
/**
44
- * An {@code HttpMessageConverter} that can read and write Protobuf
45
- * {@link com.google.protobuf.Message} using
46
- * <a href="https://developers.google.com/protocol-buffers/">Google Protocol buffers</a>.
43
+ * An {@code HttpMessageConverter} that reads and writes {@link com.google.protobuf.Message}s
44
+ * using <a href="https://developers.google.com/protocol-buffers/">Google Protocol Buffers</a>.
47
45
*
48
- * <p>By default it supports {@code "application/json"}, {@code "application/xml"},
49
- * {@code "text/plain"} and {@code "application/x-protobuf"} while writing also
50
- * supports {@code "text/html"}
46
+ * <p>By default, it supports {@code "application/x-protobuf"}, {@code "text/plain"},
47
+ * {@code "application/json"}, {@code "application/xml"}, while also writing {@code "text/html"}.
51
48
*
52
- * <p>To generate Message Java classes you need to install the protoc binary.
49
+ * <p>To generate {@code Message} Java classes, you need to install the {@code protoc} binary.
53
50
*
54
- * <p>Tested against Protobuf version 2.5.0.
51
+ * <p>Requires Protobuf 2.5/2.6 and Protobuf Java Format 1.2.
52
+ * (Note: Does not work with later Protobuf Java Format versions in Spring 4.2 yet.)
55
53
*
56
54
* @author Alex Antonov
57
55
* @author Brian Clozel
56
+ * @author Juergen Hoeller
58
57
* @since 4.1
59
58
*/
60
59
public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter <Message > {
@@ -67,10 +66,10 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
67
66
68
67
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message" ;
69
68
70
- private static final ConcurrentHashMap <Class <?>, Method > methodCache = new ConcurrentHashMap <Class <?>, Method >();
71
69
70
+ private static final ConcurrentHashMap <Class <?>, Method > methodCache = new ConcurrentHashMap <Class <?>, Method >();
72
71
73
- private ExtensionRegistry extensionRegistry = ExtensionRegistry .newInstance ();
72
+ private final ExtensionRegistry extensionRegistry = ExtensionRegistry .newInstance ();
74
73
75
74
76
75
/**
@@ -85,7 +84,7 @@ public ProtobufHttpMessageConverter() {
85
84
* that allows the registration of message extensions.
86
85
*/
87
86
public ProtobufHttpMessageConverter (ExtensionRegistryInitializer registryInitializer ) {
88
- super (PROTOBUF , MediaType .TEXT_PLAIN , MediaType .APPLICATION_XML , MediaType .APPLICATION_JSON );
87
+ super (PROTOBUF , MediaType .TEXT_PLAIN , MediaType .APPLICATION_JSON , MediaType .APPLICATION_XML );
89
88
if (registryInitializer != null ) {
90
89
registryInitializer .initializeExtensionRegistry (this .extensionRegistry );
91
90
}
@@ -97,56 +96,46 @@ protected boolean supports(Class<?> clazz) {
97
96
return Message .class .isAssignableFrom (clazz );
98
97
}
99
98
99
+ @ Override
100
+ protected MediaType getDefaultContentType (Message message ) {
101
+ return PROTOBUF ;
102
+ }
103
+
100
104
@ Override
101
105
protected Message readInternal (Class <? extends Message > clazz , HttpInputMessage inputMessage )
102
106
throws IOException , HttpMessageNotReadableException {
103
107
104
108
MediaType contentType = inputMessage .getHeaders ().getContentType ();
105
- contentType = (contentType != null ? contentType : PROTOBUF );
106
-
107
- Charset charset = getCharset (inputMessage .getHeaders ());
108
- InputStreamReader reader = new InputStreamReader (inputMessage .getBody (), charset );
109
+ if (contentType == null ) {
110
+ contentType = PROTOBUF ;
111
+ }
112
+ Charset charset = contentType .getCharSet ();
113
+ if (charset == null ) {
114
+ charset = DEFAULT_CHARSET ;
115
+ }
109
116
110
117
try {
111
118
Message .Builder builder = getMessageBuilder (clazz );
112
-
113
- if (MediaType .APPLICATION_JSON .isCompatibleWith (contentType )) {
114
- JsonFormat .merge (reader , this .extensionRegistry , builder );
115
- }
116
- else if (MediaType .TEXT_PLAIN .isCompatibleWith (contentType )) {
119
+ if (MediaType .TEXT_PLAIN .isCompatibleWith (contentType )) {
120
+ InputStreamReader reader = new InputStreamReader (inputMessage .getBody (), charset );
117
121
TextFormat .merge (reader , this .extensionRegistry , builder );
118
122
}
123
+ else if (MediaType .APPLICATION_JSON .isCompatibleWith (contentType )) {
124
+ InputStreamReader reader = new InputStreamReader (inputMessage .getBody (), charset );
125
+ JsonFormat .merge (reader , this .extensionRegistry , builder );
126
+ }
119
127
else if (MediaType .APPLICATION_XML .isCompatibleWith (contentType )) {
128
+ InputStreamReader reader = new InputStreamReader (inputMessage .getBody (), charset );
120
129
XmlFormat .merge (reader , this .extensionRegistry , builder );
121
130
}
122
131
else {
123
132
builder .mergeFrom (inputMessage .getBody (), this .extensionRegistry );
124
133
}
125
134
return builder .build ();
126
135
}
127
- catch (Exception e ) {
128
- throw new HttpMessageNotReadableException ("Could not read Protobuf message: " + e .getMessage (), e );
129
- }
130
- }
131
-
132
- private Charset getCharset (HttpHeaders headers ) {
133
- if (headers == null || headers .getContentType () == null || headers .getContentType ().getCharSet () == null ) {
134
- return DEFAULT_CHARSET ;
135
- }
136
- return headers .getContentType ().getCharSet ();
137
- }
138
-
139
- /**
140
- * Create a new {@code Message.Builder} instance for the given class.
141
- * <p>This method uses a ConcurrentHashMap for caching method lookups.
142
- */
143
- private Message .Builder getMessageBuilder (Class <? extends Message > clazz ) throws Exception {
144
- Method method = methodCache .get (clazz );
145
- if (method == null ) {
146
- method = clazz .getMethod ("newBuilder" );
147
- methodCache .put (clazz , method );
136
+ catch (Exception ex ) {
137
+ throw new HttpMessageNotReadableException ("Could not read Protobuf message: " + ex .getMessage (), ex );
148
138
}
149
- return (Message .Builder ) method .invoke (clazz );
150
139
}
151
140
152
141
/**
@@ -155,46 +144,48 @@ private Message.Builder getMessageBuilder(Class<? extends Message> clazz) throws
155
144
*/
156
145
@ Override
157
146
protected boolean canWrite (MediaType mediaType ) {
158
- return super .canWrite (mediaType ) || MediaType .TEXT_HTML .isCompatibleWith (mediaType );
147
+ return ( super .canWrite (mediaType ) || MediaType .TEXT_HTML .isCompatibleWith (mediaType ) );
159
148
}
160
149
161
150
@ Override
162
151
protected void writeInternal (Message message , HttpOutputMessage outputMessage )
163
152
throws IOException , HttpMessageNotWritableException {
164
153
165
154
MediaType contentType = outputMessage .getHeaders ().getContentType ();
166
- Charset charset = getCharset (contentType );
155
+ if (contentType == null ) {
156
+ contentType = getDefaultContentType (message );
157
+ }
158
+ Charset charset = contentType .getCharSet ();
159
+ if (charset == null ) {
160
+ charset = DEFAULT_CHARSET ;
161
+ }
167
162
168
- if (MediaType .TEXT_HTML .isCompatibleWith (contentType )) {
169
- final OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
170
- HtmlFormat .print (message , outputStreamWriter );
163
+ if (MediaType .TEXT_PLAIN .isCompatibleWith (contentType )) {
164
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
165
+ TextFormat .print (message , outputStreamWriter );
171
166
outputStreamWriter .flush ();
172
167
}
173
168
else if (MediaType .APPLICATION_JSON .isCompatibleWith (contentType )) {
174
- final OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
169
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
175
170
JsonFormat .print (message , outputStreamWriter );
176
171
outputStreamWriter .flush ();
177
172
}
178
- else if (MediaType .TEXT_PLAIN .isCompatibleWith (contentType )) {
179
- final OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
180
- TextFormat .print (message , outputStreamWriter );
181
- outputStreamWriter .flush ();
182
- }
183
173
else if (MediaType .APPLICATION_XML .isCompatibleWith (contentType )) {
184
- final OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
174
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
185
175
XmlFormat .print (message , outputStreamWriter );
186
176
outputStreamWriter .flush ();
187
177
}
178
+ else if (MediaType .TEXT_HTML .isCompatibleWith (contentType )) {
179
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
180
+ HtmlFormat .print (message , outputStreamWriter );
181
+ outputStreamWriter .flush ();
182
+ }
188
183
else if (PROTOBUF .isCompatibleWith (contentType )) {
189
184
setProtoHeader (outputMessage , message );
190
185
FileCopyUtils .copy (message .toByteArray (), outputMessage .getBody ());
191
186
}
192
187
}
193
188
194
- private Charset getCharset (MediaType contentType ) {
195
- return contentType .getCharSet () != null ? contentType .getCharSet () : DEFAULT_CHARSET ;
196
- }
197
-
198
189
/**
199
190
* Set the "X-Protobuf-*" HTTP headers when responding with a message of
200
191
* content type "application/x-protobuf"
@@ -206,9 +197,18 @@ private void setProtoHeader(HttpOutputMessage response, Message message) {
206
197
response .getHeaders ().set (X_PROTOBUF_MESSAGE_HEADER , message .getDescriptorForType ().getFullName ());
207
198
}
208
199
209
- @ Override
210
- protected MediaType getDefaultContentType (Message message ) {
211
- return PROTOBUF ;
200
+
201
+ /**
202
+ * Create a new {@code Message.Builder} instance for the given class.
203
+ * <p>This method uses a ConcurrentHashMap for caching method lookups.
204
+ */
205
+ private static Message .Builder getMessageBuilder (Class <? extends Message > clazz ) throws Exception {
206
+ Method method = methodCache .get (clazz );
207
+ if (method == null ) {
208
+ method = clazz .getMethod ("newBuilder" );
209
+ methodCache .put (clazz , method );
210
+ }
211
+ return (Message .Builder ) method .invoke (clazz );
212
212
}
213
213
214
214
}
0 commit comments