2525import java .nio .charset .Charset ;
2626import java .nio .charset .StandardCharsets ;
2727import java .util .Arrays ;
28- import java .util .concurrent . ConcurrentHashMap ;
28+ import java .util .Map ;
2929
3030import com .google .protobuf .CodedOutputStream ;
3131import com .google .protobuf .ExtensionRegistry ;
3939import org .springframework .http .HttpOutputMessage ;
4040import org .springframework .http .MediaType ;
4141import org .springframework .http .converter .AbstractHttpMessageConverter ;
42+ import org .springframework .http .converter .HttpMessageConversionException ;
4243import org .springframework .http .converter .HttpMessageNotReadableException ;
4344import org .springframework .http .converter .HttpMessageNotWritableException ;
4445import org .springframework .lang .Nullable ;
4546import org .springframework .util .Assert ;
4647import org .springframework .util .ClassUtils ;
48+ import org .springframework .util .ConcurrentReferenceHashMap ;
4749
4850import static org .springframework .http .MediaType .APPLICATION_JSON ;
4951import static org .springframework .http .MediaType .APPLICATION_XML ;
5052import static org .springframework .http .MediaType .TEXT_HTML ;
5153import static org .springframework .http .MediaType .TEXT_PLAIN ;
5254
5355/**
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>.
5659 *
5760 * <p>To generate {@code Message} Java classes, you need to install the {@code protoc} binary.
5861 *
@@ -102,7 +105,7 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
102105 public static final String X_PROTOBUF_MESSAGE_HEADER = "X-Protobuf-Message" ;
103106
104107
105- private static final ConcurrentHashMap <Class <?>, Method > methodCache = new ConcurrentHashMap <>();
108+ private static final Map <Class <?>, Method > methodCache = new ConcurrentReferenceHashMap <>();
106109
107110 private final ExtensionRegistry extensionRegistry = ExtensionRegistry .newInstance ();
108111
@@ -142,8 +145,8 @@ else if (ClassUtils.isPresent("com.google.protobuf.util.JsonFormat", getClass().
142145 this .protobufFormatSupport = null ;
143146 }
144147
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 }));
147150
148151 if (registryInitializer != null ) {
149152 registryInitializer .initializeExtensionRegistry (this .extensionRegistry );
@@ -174,26 +177,41 @@ protected Message readInternal(Class<? extends Message> clazz, HttpInputMessage
174177 charset = DEFAULT_CHARSET ;
175178 }
176179
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 ) {
177200 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 );
181205 }
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 );
191207 }
192208 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 );
194211 }
195212 }
196213
214+
197215 @ Override
198216 protected boolean canWrite (@ Nullable MediaType mediaType ) {
199217 return (super .canWrite (mediaType ) ||
@@ -244,20 +262,6 @@ private void setProtoHeader(HttpOutputMessage response, Message message) {
244262 }
245263
246264
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-
261265 /**
262266 * Protobuf format support.
263267 */
@@ -268,10 +272,11 @@ interface ProtobufFormatSupport {
268272 boolean supportsWriteOnly (@ Nullable MediaType mediaType );
269273
270274 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 ;
272277
273278 void print (Message message , OutputStream output , MediaType contentType , Charset charset )
274- throws IOException ;
279+ throws IOException , HttpMessageNotWritableException ;
275280 }
276281
277282 /**
@@ -305,7 +310,8 @@ public boolean supportsWriteOnly(@Nullable MediaType mediaType) {
305310
306311 @ Override
307312 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 {
309315
310316 if (contentType .isCompatibleWith (APPLICATION_JSON )) {
311317 this .jsonFormatter .merge (input , charset , extensionRegistry , builder );
@@ -314,13 +320,14 @@ else if (contentType.isCompatibleWith(APPLICATION_XML)) {
314320 this .xmlFormatter .merge (input , charset , extensionRegistry , builder );
315321 }
316322 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 );
318325 }
319326 }
320327
321328 @ Override
322329 public void print (Message message , OutputStream output , MediaType contentType , Charset charset )
323- throws IOException {
330+ throws IOException , HttpMessageNotWritableException {
324331
325332 if (contentType .isCompatibleWith (APPLICATION_JSON )) {
326333 this .jsonFormatter .print (message , output , charset );
@@ -332,7 +339,8 @@ else if (contentType.isCompatibleWith(TEXT_HTML)) {
332339 this .htmlFormatter .print (message , output , charset );
333340 }
334341 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 );
336344 }
337345 }
338346 }
@@ -365,28 +373,31 @@ public boolean supportsWriteOnly(@Nullable MediaType mediaType) {
365373
366374 @ Override
367375 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 {
369378
370379 if (contentType .isCompatibleWith (APPLICATION_JSON )) {
371380 InputStreamReader reader = new InputStreamReader (input , charset );
372381 this .parser .merge (reader , builder );
373382 }
374383 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 );
376386 }
377387 }
378388
379389 @ Override
380390 public void print (Message message , OutputStream output , MediaType contentType , Charset charset )
381- throws IOException {
391+ throws IOException , HttpMessageNotWritableException {
382392
383393 if (contentType .isCompatibleWith (APPLICATION_JSON )) {
384394 OutputStreamWriter writer = new OutputStreamWriter (output , charset );
385395 this .printer .appendTo (message , writer );
386396 writer .flush ();
387397 }
388398 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 );
390401 }
391402 }
392403 }
0 commit comments