25
25
import java .nio .charset .Charset ;
26
26
import java .nio .charset .StandardCharsets ;
27
27
import java .util .Arrays ;
28
- import java .util .concurrent . ConcurrentHashMap ;
28
+ import java .util .Map ;
29
29
30
30
import com .google .protobuf .CodedOutputStream ;
31
31
import com .google .protobuf .ExtensionRegistry ;
39
39
import org .springframework .http .HttpOutputMessage ;
40
40
import org .springframework .http .MediaType ;
41
41
import org .springframework .http .converter .AbstractHttpMessageConverter ;
42
+ import org .springframework .http .converter .HttpMessageConversionException ;
42
43
import org .springframework .http .converter .HttpMessageNotReadableException ;
43
44
import org .springframework .http .converter .HttpMessageNotWritableException ;
44
45
import org .springframework .lang .Nullable ;
45
46
import org .springframework .util .Assert ;
46
47
import org .springframework .util .ClassUtils ;
48
+ import org .springframework .util .ConcurrentReferenceHashMap ;
47
49
48
50
import static org .springframework .http .MediaType .APPLICATION_JSON ;
49
51
import static org .springframework .http .MediaType .APPLICATION_XML ;
50
52
import static org .springframework .http .MediaType .TEXT_HTML ;
51
53
import static org .springframework .http .MediaType .TEXT_PLAIN ;
52
54
53
55
/**
54
- * An {@code HttpMessageConverter} that reads and writes {@link com.google.protobuf.Message com.google.protobuf.Messages}
55
- * using <a href="https://developers.google.com/protocol-buffers/">Google Protocol Buffers</a>.
56
+ * An {@code HttpMessageConverter} that reads and writes
57
+ * {@link com.google.protobuf.Message com.google.protobuf.Messages} using
58
+ * <a href="https://developers.google.com/protocol-buffers/">Google Protocol Buffers</a>.
56
59
*
57
60
* <p>To generate {@code Message} Java classes, you need to install the {@code protoc} binary.
58
61
*
@@ -102,7 +105,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
102
105
public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message" ;
103
106
104
107
105
- private static final ConcurrentHashMap <Class <?>, Method > methodCache = new ConcurrentHashMap <>();
108
+ private static final Map <Class <?>, Method > methodCache = new ConcurrentReferenceHashMap <>();
106
109
107
110
private final ExtensionRegistry extensionRegistry = ExtensionRegistry .newInstance ();
108
111
@@ -142,8 +145,8 @@ else if (ClassUtils.isPresent("com.google.protobuf.util.JsonFormat", getClass().
142
145
this .protobufFormatSupport = null ;
143
146
}
144
147
145
- setSupportedMediaTypes (Arrays .asList (( this .protobufFormatSupport != null ?
146
- this .protobufFormatSupport .supportedMediaTypes () : new MediaType [] {PROTOBUF , TEXT_PLAIN }))) ;
148
+ setSupportedMediaTypes (Arrays .asList (this .protobufFormatSupport != null ?
149
+ this .protobufFormatSupport .supportedMediaTypes () : new MediaType [] {PROTOBUF , TEXT_PLAIN }));
147
150
148
151
if (registryInitializer != null ) {
149
152
registryInitializer .initializeExtensionRegistry (this .extensionRegistry );
@@ -174,26 +177,41 @@ protected Message readInternal(Class<? extends Message> clazz, HttpInputMessage
174
177
charset = DEFAULT_CHARSET ;
175
178
}
176
179
180
+ Message .Builder builder = getMessageBuilder (clazz );
181
+ if (PROTOBUF .isCompatibleWith (contentType )) {
182
+ builder .mergeFrom (inputMessage .getBody (), this .extensionRegistry );
183
+ }
184
+ else if (TEXT_PLAIN .isCompatibleWith (contentType )) {
185
+ InputStreamReader reader = new InputStreamReader (inputMessage .getBody (), charset );
186
+ TextFormat .merge (reader , this .extensionRegistry , builder );
187
+ }
188
+ else if (this .protobufFormatSupport != null ) {
189
+ this .protobufFormatSupport .merge (
190
+ inputMessage .getBody (), charset , contentType , this .extensionRegistry , builder );
191
+ }
192
+ return builder .build ();
193
+ }
194
+
195
+ /**
196
+ * Create a new {@code Message.Builder} instance for the given class.
197
+ * <p>This method uses a ConcurrentReferenceHashMap for caching method lookups.
198
+ */
199
+ private Message .Builder getMessageBuilder (Class <? extends Message > clazz ) {
177
200
try {
178
- Message .Builder builder = getMessageBuilder (clazz );
179
- if (PROTOBUF .isCompatibleWith (contentType )) {
180
- builder .mergeFrom (inputMessage .getBody (), this .extensionRegistry );
201
+ Method method = methodCache .get (clazz );
202
+ if (method == null ) {
203
+ method = clazz .getMethod ("newBuilder" );
204
+ methodCache .put (clazz , method );
181
205
}
182
- else if (TEXT_PLAIN .isCompatibleWith (contentType )) {
183
- InputStreamReader reader = new InputStreamReader (inputMessage .getBody (), charset );
184
- TextFormat .merge (reader , this .extensionRegistry , builder );
185
- }
186
- else if (this .protobufFormatSupport != null ) {
187
- this .protobufFormatSupport .merge (inputMessage .getBody (), charset , contentType ,
188
- this .extensionRegistry , builder );
189
- }
190
- return builder .build ();
206
+ return (Message .Builder ) method .invoke (clazz );
191
207
}
192
208
catch (Exception ex ) {
193
- throw new HttpMessageNotReadableException ("Could not read Protobuf message: " + ex .getMessage (), ex );
209
+ throw new HttpMessageConversionException (
210
+ "Invalid Protobuf Message type: no invocable newBuilder() method on " + clazz , ex );
194
211
}
195
212
}
196
213
214
+
197
215
@ Override
198
216
protected boolean canWrite (@ Nullable MediaType mediaType ) {
199
217
return (super .canWrite (mediaType ) ||
@@ -244,20 +262,6 @@ private void setProtoHeader(HttpOutputMessage response, Message message) {
244
262
}
245
263
246
264
247
- /**
248
- * Create a new {@code Message.Builder} instance for the given class.
249
- * <p>This method uses a ConcurrentHashMap for caching method lookups.
250
- */
251
- private static Message .Builder getMessageBuilder (Class <? extends Message > clazz ) throws Exception {
252
- Method method = methodCache .get (clazz );
253
- if (method == null ) {
254
- method = clazz .getMethod ("newBuilder" );
255
- methodCache .put (clazz , method );
256
- }
257
- return (Message .Builder ) method .invoke (clazz );
258
- }
259
-
260
-
261
265
/**
262
266
* Protobuf format support.
263
267
*/
@@ -268,10 +272,11 @@ interface ProtobufFormatSupport {
268
272
boolean supportsWriteOnly (@ Nullable MediaType mediaType );
269
273
270
274
void merge (InputStream input , Charset charset , MediaType contentType ,
271
- ExtensionRegistry extensionRegistry , Message .Builder builder ) throws IOException ;
275
+ ExtensionRegistry extensionRegistry , Message .Builder builder )
276
+ throws IOException , HttpMessageNotReadableException ;
272
277
273
278
void print (Message message , OutputStream output , MediaType contentType , Charset charset )
274
- throws IOException ;
279
+ throws IOException , HttpMessageNotWritableException ;
275
280
}
276
281
277
282
/**
@@ -305,7 +310,8 @@ public boolean supportsWriteOnly(@Nullable MediaType mediaType) {
305
310
306
311
@ Override
307
312
public void merge (InputStream input , Charset charset , MediaType contentType ,
308
- ExtensionRegistry extensionRegistry , Message .Builder builder ) throws IOException {
313
+ ExtensionRegistry extensionRegistry , Message .Builder builder )
314
+ throws IOException , HttpMessageNotReadableException {
309
315
310
316
if (contentType .isCompatibleWith (APPLICATION_JSON )) {
311
317
this .jsonFormatter .merge (input , charset , extensionRegistry , builder );
@@ -314,13 +320,14 @@ else if (contentType.isCompatibleWith(APPLICATION_XML)) {
314
320
this .xmlFormatter .merge (input , charset , extensionRegistry , builder );
315
321
}
316
322
else {
317
- throw new IOException ("com.google.protobuf.util does not support " + contentType + " format" );
323
+ throw new HttpMessageNotReadableException (
324
+ "protobuf-java-format does not support parsing " + contentType );
318
325
}
319
326
}
320
327
321
328
@ Override
322
329
public void print (Message message , OutputStream output , MediaType contentType , Charset charset )
323
- throws IOException {
330
+ throws IOException , HttpMessageNotWritableException {
324
331
325
332
if (contentType .isCompatibleWith (APPLICATION_JSON )) {
326
333
this .jsonFormatter .print (message , output , charset );
@@ -332,7 +339,8 @@ else if (contentType.isCompatibleWith(TEXT_HTML)) {
332
339
this .htmlFormatter .print (message , output , charset );
333
340
}
334
341
else {
335
- throw new IOException ("protobuf-java-format does not support " + contentType + " format" );
342
+ throw new HttpMessageNotWritableException (
343
+ "protobuf-java-format does not support printing " + contentType );
336
344
}
337
345
}
338
346
}
@@ -365,28 +373,31 @@ public boolean supportsWriteOnly(@Nullable MediaType mediaType) {
365
373
366
374
@ Override
367
375
public void merge (InputStream input , Charset charset , MediaType contentType ,
368
- ExtensionRegistry extensionRegistry , Message .Builder builder ) throws IOException {
376
+ ExtensionRegistry extensionRegistry , Message .Builder builder )
377
+ throws IOException , HttpMessageNotReadableException {
369
378
370
379
if (contentType .isCompatibleWith (APPLICATION_JSON )) {
371
380
InputStreamReader reader = new InputStreamReader (input , charset );
372
381
this .parser .merge (reader , builder );
373
382
}
374
383
else {
375
- throw new IOException ("protobuf-java-util does not support " + contentType + " format" );
384
+ throw new HttpMessageNotReadableException (
385
+ "protobuf-java-util does not support parsing " + contentType );
376
386
}
377
387
}
378
388
379
389
@ Override
380
390
public void print (Message message , OutputStream output , MediaType contentType , Charset charset )
381
- throws IOException {
391
+ throws IOException , HttpMessageNotWritableException {
382
392
383
393
if (contentType .isCompatibleWith (APPLICATION_JSON )) {
384
394
OutputStreamWriter writer = new OutputStreamWriter (output , charset );
385
395
this .printer .appendTo (message , writer );
386
396
writer .flush ();
387
397
}
388
398
else {
389
- throw new IOException ("protobuf-java-util does not support " + contentType + " format" );
399
+ throw new HttpMessageNotWritableException (
400
+ "protobuf-java-util does not support printing " + contentType );
390
401
}
391
402
}
392
403
}
0 commit comments