24
24
import java .lang .reflect .Method ;
25
25
import java .nio .charset .Charset ;
26
26
import java .nio .charset .StandardCharsets ;
27
+ import java .util .Arrays ;
27
28
import java .util .concurrent .ConcurrentHashMap ;
28
29
29
30
import com .google .protobuf .CodedOutputStream ;
30
31
import com .google .protobuf .ExtensionRegistry ;
31
32
import com .google .protobuf .Message ;
32
33
import com .google .protobuf .TextFormat ;
34
+ import com .google .protobuf .util .JsonFormat ;
33
35
import com .googlecode .protobuf .format .FormatFactory ;
34
36
import com .googlecode .protobuf .format .ProtobufFormatter ;
35
37
41
43
import org .springframework .http .converter .HttpMessageNotWritableException ;
42
44
import org .springframework .util .ClassUtils ;
43
45
46
+ import static org .springframework .http .MediaType .*;
47
+
44
48
/**
45
49
* An {@code HttpMessageConverter} that reads and writes {@link com.google.protobuf.Message}s
46
50
* using <a href="https://developers.google.com/protocol-buffers/">Google Protocol Buffers</a>.
47
51
*
48
- * <p>This converter supports by default {@code "application/x-protobuf"} and {@code "text/plain"}
49
- * with the official {@code "com.google.protobuf:protobuf-java"} library.
52
+ * <p>To generate {@code Message} Java classes, you need to install the {@code protoc} binary.
50
53
*
51
- * <p>Other formats can be supported with additional libraries:
54
+ * <p>This converter supports by default {@code "application/x-protobuf"} and {@code "text/plain"}
55
+ * with the official {@code "com.google.protobuf:protobuf-java"} library. Other formats can be
56
+ * supported with one of the following additional libraries on the classpath:
52
57
* <ul>
53
- * <li>{@code "application/json"} with the official library
54
- * {@code "com.google.protobuf:protobuf-java-util"}
55
- * <li>{@code "application/json"}, {@code "application/xml"} and {@code "text/html"} (write only)
56
- * can be supported with the 3rd party library
57
- * {@code "com.googlecode.protobuf-java-format:protobuf-java-format"}
58
+ * <li>{@code "application/json"}, {@code "application/xml"}, and {@code "text/html"} (write-only)
59
+ * with the {@code "com.googlecode.protobuf-java-format:protobuf-java-format"} third-party library
60
+ * <li>{@code "application/json"} with the official {@code "com.google.protobuf:protobuf-java-util"}
61
+ * for Protobuf 3 (see {@link ProtobufJsonFormatHttpMessageConverter} for a configurable variant)
58
62
* </ul>
59
63
*
60
- * <p>To generate {@code Message} Java classes, you need to install the {@code protoc} binary.
61
- *
62
- * <p>Requires Protobuf 2.6 or 3.x and Protobuf Java Format 1.4 or higher, as of Spring 5.0.
64
+ * <p>Requires Protobuf 2.6 or higher (and Protobuf Java Format 1.4 or higher for formatting).
65
+ * This converter will auto-adapt to Protobuf 3 and its default {@code protobuf-java-util} JSON
66
+ * format if the Protobuf 2 based {@code protobuf-java-format} isn't present; however, for more
67
+ * explicit JSON setup on Protobuf 3, consider {@link ProtobufJsonFormatHttpMessageConverter}.
63
68
*
64
69
* @author Alex Antonov
65
70
* @author Brian Clozel
66
71
* @author Juergen Hoeller
67
72
* @since 4.1
73
+ * @see FormatFactory
74
+ * @see JsonFormat
75
+ * @see ProtobufJsonFormatHttpMessageConverter
68
76
*/
69
77
public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter <Message > {
70
78
@@ -76,33 +84,12 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
76
84
77
85
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message" ;
78
86
79
- private static final boolean isProtobufJavaUtilPresent =
80
- ClassUtils .isPresent ("com.google.protobuf.util.JsonFormat" , ProtobufHttpMessageConverter .class .getClassLoader ());
81
-
82
- private static final boolean isProtobufJavaFormatPresent =
83
- ClassUtils .isPresent ("com.googlecode.protobuf.format.JsonFormat" , ProtobufHttpMessageConverter .class .getClassLoader ());
84
87
85
88
private static final ConcurrentHashMap <Class <?>, Method > methodCache = new ConcurrentHashMap <>();
86
89
87
- private final ProtobufFormatSupport protobufFormatSupport ;
88
-
89
90
private final ExtensionRegistry extensionRegistry = ExtensionRegistry .newInstance ();
90
91
91
-
92
- private static final MediaType [] SUPPORTED_MEDIATYPES ;
93
-
94
- static {
95
- if (isProtobufJavaFormatPresent ) {
96
- SUPPORTED_MEDIATYPES = new MediaType [] {PROTOBUF , MediaType .TEXT_PLAIN , MediaType .APPLICATION_XML ,
97
- MediaType .APPLICATION_JSON };
98
- }
99
- else if (isProtobufJavaUtilPresent ) {
100
- SUPPORTED_MEDIATYPES = new MediaType [] {PROTOBUF , MediaType .TEXT_PLAIN , MediaType .APPLICATION_JSON };
101
- }
102
- else {
103
- SUPPORTED_MEDIATYPES = new MediaType [] {PROTOBUF , MediaType .TEXT_PLAIN };
104
- }
105
- }
92
+ private final ProtobufFormatSupport protobufFormatSupport ;
106
93
107
94
108
95
/**
@@ -115,18 +102,29 @@ public ProtobufHttpMessageConverter() {
115
102
/**
116
103
* Construct a new {@code ProtobufHttpMessageConverter} with an
117
104
* initializer that allows the registration of message extensions.
105
+ * @param registryInitializer an initializer for message extensions
118
106
*/
119
107
public ProtobufHttpMessageConverter (ExtensionRegistryInitializer registryInitializer ) {
120
- super (SUPPORTED_MEDIATYPES );
121
- if (isProtobufJavaFormatPresent ) {
108
+ this (null , registryInitializer );
109
+ }
110
+
111
+ ProtobufHttpMessageConverter (ProtobufFormatSupport formatSupport , ExtensionRegistryInitializer registryInitializer ) {
112
+ if (formatSupport != null ) {
113
+ this .protobufFormatSupport = formatSupport ;
114
+ }
115
+ else if (ClassUtils .isPresent ("com.googlecode.protobuf.format.FormatFactory" , getClass ().getClassLoader ())) {
122
116
this .protobufFormatSupport = new ProtobufJavaFormatSupport ();
123
117
}
124
- else if (isProtobufJavaUtilPresent ) {
125
- this .protobufFormatSupport = new ProtobufJavaUtilSupport ();
118
+ else if (ClassUtils . isPresent ( "com.google.protobuf.util.JsonFormat" , getClass (). getClassLoader ()) ) {
119
+ this .protobufFormatSupport = new ProtobufJavaUtilSupport (null , null );
126
120
}
127
121
else {
128
122
this .protobufFormatSupport = null ;
129
123
}
124
+
125
+ setSupportedMediaTypes (Arrays .asList ((this .protobufFormatSupport != null ?
126
+ this .protobufFormatSupport .supportedMediaTypes () : new MediaType [] {PROTOBUF , TEXT_PLAIN })));
127
+
130
128
if (registryInitializer != null ) {
131
129
registryInitializer .initializeExtensionRegistry (this .extensionRegistry );
132
130
}
@@ -161,11 +159,11 @@ protected Message readInternal(Class<? extends Message> clazz, HttpInputMessage
161
159
if (PROTOBUF .isCompatibleWith (contentType )) {
162
160
builder .mergeFrom (inputMessage .getBody (), this .extensionRegistry );
163
161
}
164
- else if (MediaType . TEXT_PLAIN .isCompatibleWith (contentType )) {
162
+ else if (TEXT_PLAIN .isCompatibleWith (contentType )) {
165
163
InputStreamReader reader = new InputStreamReader (inputMessage .getBody (), charset );
166
164
TextFormat .merge (reader , this .extensionRegistry , builder );
167
165
}
168
- else if (isProtobufJavaUtilPresent || isProtobufJavaFormatPresent ) {
166
+ else if (this . protobufFormatSupport != null ) {
169
167
this .protobufFormatSupport .merge (inputMessage .getBody (), charset , contentType ,
170
168
this .extensionRegistry , builder );
171
169
}
@@ -176,14 +174,10 @@ else if (isProtobufJavaUtilPresent || isProtobufJavaFormatPresent) {
176
174
}
177
175
}
178
176
179
- /**
180
- * This method overrides the parent implementation, since this HttpMessageConverter
181
- * can also produce {@code MediaType.HTML "text/html"} ContentType.
182
- */
183
177
@ Override
184
178
protected boolean canWrite (MediaType mediaType ) {
185
179
return (super .canWrite (mediaType ) ||
186
- (isProtobufJavaFormatPresent && MediaType . TEXT_HTML . isCompatibleWith (mediaType )));
180
+ (this . protobufFormatSupport != null && this . protobufFormatSupport . supportsWriteOnly (mediaType )));
187
181
}
188
182
189
183
@ Override
@@ -205,13 +199,13 @@ protected void writeInternal(Message message, HttpOutputMessage outputMessage)
205
199
message .writeTo (codedOutputStream );
206
200
codedOutputStream .flush ();
207
201
}
208
- else if (MediaType . TEXT_PLAIN .isCompatibleWith (contentType )) {
209
- final OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
202
+ else if (TEXT_PLAIN .isCompatibleWith (contentType )) {
203
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputMessage .getBody (), charset );
210
204
TextFormat .print (message , outputStreamWriter );
211
205
outputStreamWriter .flush ();
212
206
outputMessage .getBody ().flush ();
213
207
}
214
- else if (isProtobufJavaUtilPresent || isProtobufJavaFormatPresent ) {
208
+ else if (this . protobufFormatSupport != null ) {
215
209
this .protobufFormatSupport .print (message , outputMessage .getBody (), contentType , charset );
216
210
outputMessage .getBody ().flush ();
217
211
}
@@ -243,98 +237,122 @@ private static Message.Builder getMessageBuilder(Class<? extends Message> clazz)
243
237
}
244
238
245
239
246
- private interface ProtobufFormatSupport {
240
+ interface ProtobufFormatSupport {
241
+
242
+ MediaType [] supportedMediaTypes ();
243
+
244
+ boolean supportsWriteOnly (MediaType mediaType );
247
245
248
246
void merge (InputStream input , Charset charset , MediaType contentType , ExtensionRegistry extensionRegistry ,
249
247
Message .Builder builder ) throws IOException ;
250
248
251
- void print (Message message , OutputStream output , MediaType contentType , Charset cs ) throws IOException ;
249
+ void print (Message message , OutputStream output , MediaType contentType , Charset charset ) throws IOException ;
252
250
}
253
251
254
252
255
- private class ProtobufJavaUtilSupport implements ProtobufFormatSupport {
253
+ static class ProtobufJavaFormatSupport implements ProtobufFormatSupport {
254
+
255
+ private final ProtobufFormatter jsonFormatter ;
256
256
257
- private final com . google . protobuf . util . JsonFormat . Parser parser ;
257
+ private final ProtobufFormatter xmlFormatter ;
258
258
259
- private final com . google . protobuf . util . JsonFormat . Printer printer ;
259
+ private final ProtobufFormatter htmlFormatter ;
260
260
261
- public ProtobufJavaUtilSupport () {
262
- this .parser = com .google .protobuf .util .JsonFormat .parser ();
263
- this .printer = com .google .protobuf .util .JsonFormat .printer ();
261
+ public ProtobufJavaFormatSupport () {
262
+ FormatFactory formatFactory = new FormatFactory ();
263
+ this .jsonFormatter = formatFactory .createFormatter (FormatFactory .Formatter .JSON );
264
+ this .xmlFormatter = formatFactory .createFormatter (FormatFactory .Formatter .XML );
265
+ this .htmlFormatter = formatFactory .createFormatter (FormatFactory .Formatter .HTML );
266
+ }
267
+
268
+ @ Override
269
+ public MediaType [] supportedMediaTypes () {
270
+ return new MediaType [] {PROTOBUF , TEXT_PLAIN , APPLICATION_XML , APPLICATION_JSON };
271
+ }
272
+
273
+ @ Override
274
+ public boolean supportsWriteOnly (MediaType mediaType ) {
275
+ return TEXT_HTML .isCompatibleWith (mediaType );
264
276
}
265
277
266
278
@ Override
267
279
public void merge (InputStream input , Charset charset , MediaType contentType ,
268
280
ExtensionRegistry extensionRegistry , Message .Builder builder ) throws IOException {
269
281
270
- if (contentType .isCompatibleWith (MediaType .APPLICATION_JSON )) {
271
- InputStreamReader reader = new InputStreamReader (input , charset );
272
- this .parser .merge (reader , builder );
282
+ if (contentType .isCompatibleWith (APPLICATION_JSON )) {
283
+ this .jsonFormatter .merge (input , charset , extensionRegistry , builder );
284
+ }
285
+ else if (contentType .isCompatibleWith (APPLICATION_XML )) {
286
+ this .xmlFormatter .merge (input , charset , extensionRegistry , builder );
273
287
}
274
288
else {
275
- throw new IOException (
276
- "com.googlecode.protobuf:protobuf-java-util does not support " + contentType + " format" );
289
+ throw new IOException ("com.google.protobuf.util does not support " + contentType + " format" );
277
290
}
278
291
}
279
292
280
293
@ Override
281
- public void print (Message message , OutputStream output , MediaType contentType , Charset cs ) throws IOException {
282
- if (contentType .isCompatibleWith (MediaType .APPLICATION_JSON )) {
283
- this .printer .appendTo (message , new OutputStreamWriter (output , cs ));
294
+ public void print (Message message , OutputStream output , MediaType contentType , Charset charset )
295
+ throws IOException {
296
+
297
+ if (contentType .isCompatibleWith (APPLICATION_JSON )) {
298
+ this .jsonFormatter .print (message , output , charset );
299
+ }
300
+ else if (contentType .isCompatibleWith (APPLICATION_XML )) {
301
+ this .xmlFormatter .print (message , output , charset );
302
+ }
303
+ else if (contentType .isCompatibleWith (TEXT_HTML )) {
304
+ this .htmlFormatter .print (message , output , charset );
284
305
}
285
306
else {
286
- throw new IOException (
287
- "com.googlecode.protobuf:protobuf-java-util does not support " + contentType + " format" );
307
+ throw new IOException ("protobuf-java-format does not support " + contentType + " format" );
288
308
}
289
309
}
290
310
}
291
311
292
312
293
- private class ProtobufJavaFormatSupport implements ProtobufFormatSupport {
313
+ static class ProtobufJavaUtilSupport implements ProtobufFormatSupport {
294
314
295
- private final FormatFactory FORMAT_FACTORY ;
315
+ private final JsonFormat . Parser parser ;
296
316
297
- private final ProtobufFormatter JSON_FORMATTER ;
317
+ private final JsonFormat . Printer printer ;
298
318
299
- private final ProtobufFormatter XML_FORMATTER ;
319
+ public ProtobufJavaUtilSupport (JsonFormat .Parser parser , JsonFormat .Printer printer ) {
320
+ this .parser = (parser != null ? parser : JsonFormat .parser ());
321
+ this .printer = (printer != null ? printer : JsonFormat .printer ());
322
+ }
300
323
301
- private final ProtobufFormatter HTML_FORMATTER ;
324
+ @ Override
325
+ public MediaType [] supportedMediaTypes () {
326
+ return new MediaType [] {PROTOBUF , TEXT_PLAIN , APPLICATION_JSON };
327
+ }
302
328
303
- public ProtobufJavaFormatSupport () {
304
- FORMAT_FACTORY = new FormatFactory ();
305
- JSON_FORMATTER = FORMAT_FACTORY .createFormatter (FormatFactory .Formatter .JSON );
306
- XML_FORMATTER = FORMAT_FACTORY .createFormatter (FormatFactory .Formatter .XML );
307
- HTML_FORMATTER = FORMAT_FACTORY .createFormatter (FormatFactory .Formatter .HTML );
329
+ @ Override
330
+ public boolean supportsWriteOnly (MediaType mediaType ) {
331
+ return false ;
308
332
}
309
333
310
334
@ Override
311
335
public void merge (InputStream input , Charset charset , MediaType contentType ,
312
336
ExtensionRegistry extensionRegistry , Message .Builder builder ) throws IOException {
313
337
314
- if (contentType .isCompatibleWith (MediaType .APPLICATION_JSON )) {
315
- JSON_FORMATTER .merge (input , charset , extensionRegistry , builder );
316
- }
317
- else if (contentType .isCompatibleWith (MediaType .APPLICATION_XML )) {
318
- XML_FORMATTER .merge (input , charset , extensionRegistry , builder );
338
+ if (contentType .isCompatibleWith (APPLICATION_JSON )) {
339
+ InputStreamReader reader = new InputStreamReader (input , charset );
340
+ this .parser .merge (reader , builder );
319
341
}
320
342
else {
321
- throw new IOException ("com.google. protobuf. util does not support " + contentType + " format" );
343
+ throw new IOException ("protobuf-java- util does not support " + contentType + " format" );
322
344
}
323
345
}
324
346
325
347
@ Override
326
- public void print (Message message , OutputStream output , MediaType contentType , Charset cs ) throws IOException {
327
- if (contentType .isCompatibleWith (MediaType .APPLICATION_JSON )) {
328
- JSON_FORMATTER .print (message , output , cs );
329
- }
330
- else if (contentType .isCompatibleWith (MediaType .APPLICATION_XML )) {
331
- XML_FORMATTER .print (message , output , cs );
332
- }
333
- else if (contentType .isCompatibleWith (MediaType .TEXT_HTML )) {
334
- HTML_FORMATTER .print (message , output , cs );
348
+ public void print (Message message , OutputStream output , MediaType contentType , Charset charset )
349
+ throws IOException {
350
+
351
+ if (contentType .isCompatibleWith (APPLICATION_JSON )) {
352
+ this .printer .appendTo (message , new OutputStreamWriter (output , charset ));
335
353
}
336
354
else {
337
- throw new IOException ("com.google. protobuf. util does not support " + contentType + " format" );
355
+ throw new IOException ("protobuf-java- util does not support " + contentType + " format" );
338
356
}
339
357
}
340
358
}
0 commit comments