Skip to content

Commit dc8de51

Browse files
committed
Json view support for JMS
Support of @JSONVIEW on @JmsListener annotated method that uses the jackson converter. Also update MappingJackson2MessageConverter to offer a public API to set the JSON view to use to serialize a payload. Issue: SPR-13237
1 parent cdc9bf7 commit dc8de51

File tree

7 files changed

+439
-19
lines changed

7 files changed

+439
-19
lines changed

spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import org.springframework.jms.support.converter.MessageConverter;
3737
import org.springframework.jms.support.converter.MessagingMessageConverter;
3838
import org.springframework.jms.support.converter.SimpleMessageConverter;
39+
import org.springframework.jms.support.converter.SmartMessageConverter;
3940
import org.springframework.jms.support.destination.DestinationResolver;
4041
import org.springframework.jms.support.destination.DynamicDestinationResolver;
4142
import org.springframework.util.Assert;
@@ -269,14 +270,17 @@ protected void handleResult(Object result, Message request, Session session) {
269270
* @see #setMessageConverter
270271
*/
271272
protected Message buildMessage(Session session, Object result) throws JMSException {
272-
Object content = (result instanceof JmsResponse ? ((JmsResponse<?>) result).getResponse() : result);
273-
if (content instanceof org.springframework.messaging.Message) {
274-
return this.messagingMessageConverter.toMessage(content, session);
275-
}
273+
Object content = preProcessResponse(result instanceof JmsResponse
274+
? ((JmsResponse<?>) result).getResponse() : result);
276275

277276
MessageConverter converter = getMessageConverter();
278277
if (converter != null) {
279-
return converter.toMessage(content, session);
278+
if (content instanceof org.springframework.messaging.Message) {
279+
return this.messagingMessageConverter.toMessage(content, session);
280+
}
281+
else {
282+
return converter.toMessage(content, session);
283+
}
280284
}
281285

282286
if (!(content instanceof Message)) {
@@ -286,6 +290,17 @@ protected Message buildMessage(Session session, Object result) throws JMSExcepti
286290
return (Message) content;
287291
}
288292

293+
/**
294+
* Pre-process the given result before it is converted to a {@link Message}.
295+
* @param result the result of the invocation
296+
* @return the payload response to handle, either the {@code result} argument or any other
297+
* object (for instance wrapping the result).
298+
* @since 4.3
299+
*/
300+
protected Object preProcessResponse(Object result) {
301+
return result;
302+
}
303+
289304
/**
290305
* Post-process the given response message before it will be sent.
291306
* <p>The default implementation sets the response's correlation id
@@ -425,12 +440,17 @@ protected Object extractPayload(Message message) throws JMSException {
425440
}
426441

427442
@Override
428-
protected Message createMessageForPayload(Object payload, Session session) throws JMSException {
443+
protected Message createMessageForPayload(Object payload, Session session, Object conversionHint)
444+
throws JMSException {
429445
MessageConverter converter = getMessageConverter();
430-
if (converter != null) {
431-
return converter.toMessage(payload, session);
446+
if (converter == null) {
447+
throw new IllegalStateException("No message converter, cannot handle '" + payload + "'");
448+
}
449+
if (converter instanceof SmartMessageConverter) {
450+
return ((SmartMessageConverter) converter).toMessage(payload, session, conversionHint);
451+
432452
}
433-
throw new IllegalStateException("No message converter - cannot handle [" + payload + "]");
453+
return converter.toMessage(payload, session);
434454
}
435455
}
436456

spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,11 +19,14 @@
1919
import javax.jms.JMSException;
2020
import javax.jms.Session;
2121

22+
import org.springframework.core.MethodParameter;
2223
import org.springframework.jms.support.JmsHeaderMapper;
2324
import org.springframework.jms.support.converter.MessageConversionException;
2425
import org.springframework.messaging.Message;
2526
import org.springframework.messaging.MessagingException;
27+
import org.springframework.messaging.core.AbstractMessageSendingTemplate;
2628
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
29+
import org.springframework.messaging.support.MessageBuilder;
2730

2831
/**
2932
* A {@link javax.jms.MessageListener} adapter that invokes a configurable
@@ -72,6 +75,17 @@ public void onMessage(javax.jms.Message jmsMessage, Session session) throws JMSE
7275
}
7376
}
7477

78+
@Override
79+
protected Object preProcessResponse(Object result) {
80+
MethodParameter returnType = this.handlerMethod.getReturnType();
81+
if (result instanceof Message) {
82+
return MessageBuilder.fromMessage((Message<?>) result)
83+
.setHeader(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
84+
}
85+
return MessageBuilder.withPayload(result).setHeader(
86+
AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
87+
}
88+
7589
protected Message<?> toMessagingMessage(javax.jms.Message jmsMessage) {
7690
try {
7791
return (Message<?>) getMessagingMessageConverter().fromMessage(jmsMessage);

spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,12 +29,15 @@
2929
import javax.jms.Session;
3030
import javax.jms.TextMessage;
3131

32+
import com.fasterxml.jackson.annotation.JsonView;
3233
import com.fasterxml.jackson.databind.DeserializationFeature;
3334
import com.fasterxml.jackson.databind.JavaType;
3435
import com.fasterxml.jackson.databind.MapperFeature;
3536
import com.fasterxml.jackson.databind.ObjectMapper;
37+
import com.fasterxml.jackson.databind.ObjectWriter;
3638

3739
import org.springframework.beans.factory.BeanClassLoaderAware;
40+
import org.springframework.core.MethodParameter;
3841
import org.springframework.util.Assert;
3942
import org.springframework.util.ClassUtils;
4043

@@ -55,9 +58,10 @@
5558
* @author Mark Pollack
5659
* @author Dave Syer
5760
* @author Juergen Hoeller
61+
* @author Stephane Nicoll
5862
* @since 3.1.4
5963
*/
60-
public class MappingJackson2MessageConverter implements MessageConverter, BeanClassLoaderAware {
64+
public class MappingJackson2MessageConverter implements SmartMessageConverter, BeanClassLoaderAware {
6165

6266
/**
6367
* The default encoding used for writing to text messages: UTF-8.
@@ -189,6 +193,33 @@ public Message toMessage(Object object, Session session) throws JMSException, Me
189193
return message;
190194
}
191195

196+
@Override
197+
public Message toMessage(Object object, Session session, Object conversionHint)
198+
throws JMSException, MessageConversionException {
199+
return toMessage(object, session, getSerializationView(conversionHint));
200+
}
201+
202+
/**
203+
* Convert a Java object to a JMS Message using the specified json view
204+
* and the supplied session to create the message object.
205+
* @param object the object to convert
206+
* @param session the Session to use for creating a JMS Message
207+
* @param jsonView the view to use to filter the content
208+
* @return the JMS Message
209+
* @throws javax.jms.JMSException if thrown by JMS API methods
210+
* @throws MessageConversionException in case of conversion failure
211+
* @since 4.3
212+
*/
213+
public Message toMessage(Object object, Session session, Class<?> jsonView)
214+
throws JMSException, MessageConversionException {
215+
if (jsonView != null) {
216+
return toMessage(object, session, this.objectMapper.writerWithView(jsonView));
217+
}
218+
else {
219+
return toMessage(object, session, this.objectMapper.writer());
220+
}
221+
}
222+
192223
@Override
193224
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
194225
try {
@@ -200,6 +231,28 @@ public Object fromMessage(Message message) throws JMSException, MessageConversio
200231
}
201232
}
202233

234+
protected Message toMessage(Object object, Session session, ObjectWriter objectWriter)
235+
throws JMSException, MessageConversionException {
236+
Message message;
237+
try {
238+
switch (this.targetType) {
239+
case TEXT:
240+
message = mapToTextMessage(object, session, objectWriter);
241+
break;
242+
case BYTES:
243+
message = mapToBytesMessage(object, session, objectWriter);
244+
break;
245+
default:
246+
message = mapToMessage(object, session, objectWriter, this.targetType);
247+
}
248+
}
249+
catch (IOException ex) {
250+
throw new MessageConversionException("Could not map JSON object [" + object + "]", ex);
251+
}
252+
setTypeIdOnMessage(object, message);
253+
return message;
254+
}
255+
203256

204257
/**
205258
* Map the given object to a {@link TextMessage}.
@@ -210,12 +263,31 @@ public Object fromMessage(Message message) throws JMSException, MessageConversio
210263
* @throws JMSException if thrown by JMS methods
211264
* @throws IOException in case of I/O errors
212265
* @see Session#createBytesMessage
266+
* @deprecated as of 4.3, use {@link #mapToTextMessage(Object, Session, ObjectWriter)}
213267
*/
268+
@Deprecated
214269
protected TextMessage mapToTextMessage(Object object, Session session, ObjectMapper objectMapper)
215270
throws JMSException, IOException {
216271

272+
return mapToTextMessage(object, session, objectMapper.writer());
273+
}
274+
275+
/**
276+
* Map the given object to a {@link TextMessage}.
277+
* @param object the object to be mapped
278+
* @param session current JMS session
279+
* @param objectWriter the writer to use
280+
* @return the resulting message
281+
* @throws JMSException if thrown by JMS methods
282+
* @throws IOException in case of I/O errors
283+
* @see Session#createBytesMessage
284+
* @since 4.3
285+
*/
286+
protected TextMessage mapToTextMessage(Object object, Session session, ObjectWriter objectWriter)
287+
throws JMSException, IOException {
288+
217289
StringWriter writer = new StringWriter();
218-
objectMapper.writeValue(writer, object);
290+
objectWriter.writeValue(writer, object);
219291
return session.createTextMessage(writer.toString());
220292
}
221293

@@ -228,13 +300,33 @@ protected TextMessage mapToTextMessage(Object object, Session session, ObjectMap
228300
* @throws JMSException if thrown by JMS methods
229301
* @throws IOException in case of I/O errors
230302
* @see Session#createBytesMessage
303+
* @deprecated as of 4.3, use {@link #mapToBytesMessage(Object, Session, ObjectWriter)}
231304
*/
305+
@Deprecated
232306
protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectMapper objectMapper)
233307
throws JMSException, IOException {
234308

309+
return mapToBytesMessage(object, session, objectMapper.writer());
310+
}
311+
312+
313+
/**
314+
* Map the given object to a {@link BytesMessage}.
315+
* @param object the object to be mapped
316+
* @param session current JMS session
317+
* @param objectWriter the writer to use
318+
* @return the resulting message
319+
* @throws JMSException if thrown by JMS methods
320+
* @throws IOException in case of I/O errors
321+
* @see Session#createBytesMessage
322+
* @since 4.3
323+
*/
324+
protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectWriter objectWriter)
325+
throws JMSException, IOException {
326+
235327
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
236328
OutputStreamWriter writer = new OutputStreamWriter(bos, this.encoding);
237-
objectMapper.writeValue(writer, object);
329+
objectWriter.writeValue(writer, object);
238330

239331
BytesMessage message = session.createBytesMessage();
240332
message.writeBytes(bos.toByteArray());
@@ -256,10 +348,31 @@ protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectM
256348
* @return the resulting message
257349
* @throws JMSException if thrown by JMS methods
258350
* @throws IOException in case of I/O errors
351+
* @deprecated as of 4.3, use {@link #mapToMessage(Object, Session, ObjectWriter, MessageType)}
259352
*/
353+
@Deprecated
260354
protected Message mapToMessage(Object object, Session session, ObjectMapper objectMapper, MessageType targetType)
261355
throws JMSException, IOException {
262356

357+
return mapToMessage(object, session, objectMapper.writer(), targetType);
358+
}
359+
360+
/**
361+
* Template method that allows for custom message mapping.
362+
* Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
363+
* {@link MessageType#BYTES}.
364+
* <p>The default implementation throws an {@link IllegalArgumentException}.
365+
* @param object the object to marshal
366+
* @param session the JMS Session
367+
* @param objectWriter the writer to use
368+
* @param targetType the target message type (other than TEXT or BYTES)
369+
* @return the resulting message
370+
* @throws JMSException if thrown by JMS methods
371+
* @throws IOException in case of I/O errors
372+
*/
373+
protected Message mapToMessage(Object object, Session session, ObjectWriter objectWriter, MessageType targetType)
374+
throws JMSException, IOException {
375+
263376
throw new IllegalArgumentException("Unsupported message type [" + targetType +
264377
"]. MappingJackson2MessageConverter by default only supports TextMessages and BytesMessages.");
265378
}
@@ -391,4 +504,42 @@ protected JavaType getJavaTypeForMessage(Message message) throws JMSException {
391504
}
392505
}
393506

507+
/**
508+
* Determine a Jackson serialization view based on the given conversion hint.
509+
* @param conversionHint the conversion hint Object as passed into the
510+
* converter for the current conversion attempt
511+
* @return the serialization view class, or {@code null} if none
512+
*/
513+
protected Class<?> getSerializationView(Object conversionHint) {
514+
if (conversionHint instanceof MethodParameter) {
515+
MethodParameter methodParam = (MethodParameter) conversionHint;
516+
JsonView annotation = methodParam.getParameterAnnotation(JsonView.class);
517+
if (annotation == null) {
518+
annotation = methodParam.getMethodAnnotation(JsonView.class);
519+
if (annotation == null) {
520+
return null;
521+
}
522+
}
523+
return extractViewClass(annotation, conversionHint);
524+
}
525+
else if (conversionHint instanceof JsonView) {
526+
return extractViewClass((JsonView) conversionHint, conversionHint);
527+
}
528+
else if (conversionHint instanceof Class) {
529+
return (Class) conversionHint;
530+
}
531+
else {
532+
return null;
533+
}
534+
}
535+
536+
private Class<?> extractViewClass(JsonView annotation, Object conversionHint) {
537+
Class<?>[] classes = annotation.value();
538+
if (classes.length != 1) {
539+
throw new IllegalArgumentException(
540+
"@JsonView only supported for handler methods with exactly 1 class argument: " + conversionHint);
541+
}
542+
return classes[0];
543+
}
544+
394545
}

0 commit comments

Comments
 (0)