1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.jms.support.converter;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.OutputStreamWriter;
22 import java.io.StringWriter;
23 import java.io.UnsupportedEncodingException;
24 import java.util.HashMap;
25 import java.util.Map;
26 import javax.jms.BytesMessage;
27 import javax.jms.JMSException;
28 import javax.jms.Message;
29 import javax.jms.Session;
30 import javax.jms.TextMessage;
31
32 import com.fasterxml.jackson.databind.DeserializationFeature;
33 import com.fasterxml.jackson.databind.JavaType;
34 import com.fasterxml.jackson.databind.MapperFeature;
35 import com.fasterxml.jackson.databind.ObjectMapper;
36
37 import org.springframework.beans.factory.BeanClassLoaderAware;
38 import org.springframework.util.Assert;
39 import org.springframework.util.ClassUtils;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public class MappingJackson2MessageConverter implements MessageConverter, BeanClassLoaderAware {
61
62
63
64
65 public static final String DEFAULT_ENCODING = "UTF-8";
66
67
68 private ObjectMapper objectMapper;
69
70 private MessageType targetType = MessageType.BYTES;
71
72 private String encoding = DEFAULT_ENCODING;
73
74 private String encodingPropertyName;
75
76 private String typeIdPropertyName;
77
78 private Map<String, Class<?>> idClassMappings = new HashMap<String, Class<?>>();
79
80 private Map<Class<?>, String> classIdMappings = new HashMap<Class<?>, String>();
81
82 private ClassLoader beanClassLoader;
83
84
85 public MappingJackson2MessageConverter() {
86 this.objectMapper = new ObjectMapper();
87 this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
88 this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
89 }
90
91
92
93
94 public void setObjectMapper(ObjectMapper objectMapper) {
95 Assert.notNull(objectMapper, "ObjectMapper must not be null");
96 this.objectMapper = objectMapper;
97 }
98
99
100
101
102
103
104
105
106
107
108 public void setTargetType(MessageType targetType) {
109 Assert.notNull(targetType, "MessageType must not be null");
110 this.targetType = targetType;
111 }
112
113
114
115
116
117
118
119
120
121 public void setEncoding(String encoding) {
122 this.encoding = encoding;
123 }
124
125
126
127
128
129
130
131
132 public void setEncodingPropertyName(String encodingPropertyName) {
133 this.encodingPropertyName = encodingPropertyName;
134 }
135
136
137
138
139
140
141
142
143 public void setTypeIdPropertyName(String typeIdPropertyName) {
144 this.typeIdPropertyName = typeIdPropertyName;
145 }
146
147
148
149
150
151
152
153
154 public void setTypeIdMappings(Map<String, Class<?>> typeIdMappings) {
155 this.idClassMappings = new HashMap<String, Class<?>>();
156 for (Map.Entry<String, Class<?>> entry : typeIdMappings.entrySet()) {
157 String id = entry.getKey();
158 Class<?> clazz = entry.getValue();
159 this.idClassMappings.put(id, clazz);
160 this.classIdMappings.put(clazz, id);
161 }
162 }
163
164 @Override
165 public void setBeanClassLoader(ClassLoader classLoader) {
166 this.beanClassLoader = classLoader;
167 }
168
169
170 @Override
171 public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
172 Message message;
173 try {
174 switch (this.targetType) {
175 case TEXT:
176 message = mapToTextMessage(object, session, this.objectMapper);
177 break;
178 case BYTES:
179 message = mapToBytesMessage(object, session, this.objectMapper);
180 break;
181 default:
182 message = mapToMessage(object, session, this.objectMapper, this.targetType);
183 }
184 }
185 catch (IOException ex) {
186 throw new MessageConversionException("Could not map JSON object [" + object + "]", ex);
187 }
188 setTypeIdOnMessage(object, message);
189 return message;
190 }
191
192 @Override
193 public Object fromMessage(Message message) throws JMSException, MessageConversionException {
194 try {
195 JavaType targetJavaType = getJavaTypeForMessage(message);
196 return convertToObject(message, targetJavaType);
197 }
198 catch (IOException ex) {
199 throw new MessageConversionException("Failed to convert JSON message content", ex);
200 }
201 }
202
203
204
205
206
207
208
209
210
211
212
213
214 protected TextMessage mapToTextMessage(Object object, Session session, ObjectMapper objectMapper)
215 throws JMSException, IOException {
216
217 StringWriter writer = new StringWriter();
218 objectMapper.writeValue(writer, object);
219 return session.createTextMessage(writer.toString());
220 }
221
222
223
224
225
226
227
228
229
230
231
232 protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectMapper objectMapper)
233 throws JMSException, IOException {
234
235 ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
236 OutputStreamWriter writer = new OutputStreamWriter(bos, this.encoding);
237 objectMapper.writeValue(writer, object);
238
239 BytesMessage message = session.createBytesMessage();
240 message.writeBytes(bos.toByteArray());
241 if (this.encodingPropertyName != null) {
242 message.setStringProperty(this.encodingPropertyName, this.encoding);
243 }
244 return message;
245 }
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260 protected Message mapToMessage(Object object, Session session, ObjectMapper objectMapper, MessageType targetType)
261 throws JMSException, IOException {
262
263 throw new IllegalArgumentException("Unsupported message type [" + targetType +
264 "]. MappingJackson2MessageConverter by default only supports TextMessages and BytesMessages.");
265 }
266
267
268
269
270
271
272
273
274
275
276
277
278
279 protected void setTypeIdOnMessage(Object object, Message message) throws JMSException {
280 if (this.typeIdPropertyName != null) {
281 String typeId = this.classIdMappings.get(object.getClass());
282 if (typeId == null) {
283 typeId = object.getClass().getName();
284 }
285 message.setStringProperty(this.typeIdPropertyName, typeId);
286 }
287 }
288
289
290
291
292
293 private Object convertToObject(Message message, JavaType targetJavaType) throws JMSException, IOException {
294 if (message instanceof TextMessage) {
295 return convertFromTextMessage((TextMessage) message, targetJavaType);
296 }
297 else if (message instanceof BytesMessage) {
298 return convertFromBytesMessage((BytesMessage) message, targetJavaType);
299 }
300 else {
301 return convertFromMessage(message, targetJavaType);
302 }
303 }
304
305
306
307
308
309
310
311
312
313 protected Object convertFromTextMessage(TextMessage message, JavaType targetJavaType)
314 throws JMSException, IOException {
315
316 String body = message.getText();
317 return this.objectMapper.readValue(body, targetJavaType);
318 }
319
320
321
322
323
324
325
326
327
328 protected Object convertFromBytesMessage(BytesMessage message, JavaType targetJavaType)
329 throws JMSException, IOException {
330
331 String encoding = this.encoding;
332 if (this.encodingPropertyName != null && message.propertyExists(this.encodingPropertyName)) {
333 encoding = message.getStringProperty(this.encodingPropertyName);
334 }
335 byte[] bytes = new byte[(int) message.getBodyLength()];
336 message.readBytes(bytes);
337 try {
338 String body = new String(bytes, encoding);
339 return this.objectMapper.readValue(body, targetJavaType);
340 }
341 catch (UnsupportedEncodingException ex) {
342 throw new MessageConversionException("Cannot convert bytes to String", ex);
343 }
344 }
345
346
347
348
349
350
351
352
353
354
355
356
357 protected Object convertFromMessage(Message message, JavaType targetJavaType)
358 throws JMSException, IOException {
359
360 throw new IllegalArgumentException("Unsupported message type [" + message.getClass() +
361 "]. MappingJacksonMessageConverter by default only supports TextMessages and BytesMessages.");
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375
376 protected JavaType getJavaTypeForMessage(Message message) throws JMSException {
377 String typeId = message.getStringProperty(this.typeIdPropertyName);
378 if (typeId == null) {
379 throw new MessageConversionException("Could not find type id property [" + this.typeIdPropertyName + "]");
380 }
381 Class<?> mappedClass = this.idClassMappings.get(typeId);
382 if (mappedClass != null) {
383 return this.objectMapper.getTypeFactory().constructType(mappedClass);
384 }
385 try {
386 Class<?> typeClass = ClassUtils.forName(typeId, this.beanClassLoader);
387 return this.objectMapper.getTypeFactory().constructType(typeClass);
388 }
389 catch (Throwable ex) {
390 throw new MessageConversionException("Failed to resolve type id [" + typeId + "]", ex);
391 }
392 }
393
394 }