Skip to content

Commit bac9aa2

Browse files
mhewedyartembilan
authored andcommitted
GH-768: Add Jackson2XmlMessageConverter
Fixes #768 documentations cleanup and fixes. * Polishing code style a bit * Remove unused and replaced `AbstractJsonMessageConverter` class * Fix typos in docs and reflect an `AbstractJsonMessageConverter` situation
1 parent 46151a7 commit bac9aa2

File tree

10 files changed

+1024
-316
lines changed

10 files changed

+1024
-316
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,9 @@ project('spring-amqp') {
234234
compile ("org.springframework:spring-context:$springVersion", optional)
235235
compile ("com.fasterxml.jackson.core:jackson-core:$jackson2Version", optional)
236236
compile ("com.fasterxml.jackson.core:jackson-databind:$jackson2Version", optional)
237+
compile ("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jackson2Version", optional)
237238

239+
testCompile "org.assertj:assertj-core:$assertjVersion"
238240
testRuntime "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
239241
}
240242

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.amqp.support.converter;
18+
19+
import java.io.IOException;
20+
import java.lang.reflect.Type;
21+
22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
24+
25+
import org.springframework.amqp.core.Message;
26+
import org.springframework.amqp.core.MessageProperties;
27+
import org.springframework.beans.factory.BeanClassLoaderAware;
28+
import org.springframework.core.ParameterizedTypeReference;
29+
import org.springframework.util.Assert;
30+
import org.springframework.util.ClassUtils;
31+
import org.springframework.util.MimeType;
32+
33+
import com.fasterxml.jackson.databind.JavaType;
34+
import com.fasterxml.jackson.databind.ObjectMapper;
35+
36+
/**
37+
* Abstract Jackson2 message converter.
38+
*
39+
* @author Mark Pollack
40+
* @author James Carr
41+
* @author Dave Syer
42+
* @author Sam Nelson
43+
* @author Andreas Asplund
44+
* @author Artem Bilan
45+
* @author Mohammad Hewedy
46+
*
47+
* @since 2.1
48+
*/
49+
public abstract class AbstractJackson2MessageConverter extends AbstractMessageConverter
50+
implements BeanClassLoaderAware, SmartMessageConverter {
51+
52+
protected final Log log = LogFactory.getLog(getClass());
53+
54+
public static final String DEFAULT_CHARSET = "UTF-8";
55+
56+
private volatile String defaultCharset = DEFAULT_CHARSET;
57+
58+
private ClassMapper classMapper = null;
59+
60+
protected boolean typeMapperSet;
61+
62+
private final MimeType contentType;
63+
64+
protected final ObjectMapper objectMapper;
65+
66+
private ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
67+
68+
private Jackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
69+
70+
/**
71+
* Construct with the provided {@link ObjectMapper} instance.
72+
* @param objectMapper the {@link ObjectMapper} to use.
73+
* @param contentType content type of the message
74+
* @param trustedPackages the trusted Java packages for deserialization
75+
* @see DefaultJackson2JavaTypeMapper#setTrustedPackages(String...)
76+
*/
77+
protected AbstractJackson2MessageConverter(ObjectMapper objectMapper, MimeType contentType,
78+
String... trustedPackages) {
79+
80+
Assert.notNull(objectMapper, "'objectMapper' must not be null");
81+
Assert.notNull(contentType, "'contentType' must not be null");
82+
this.objectMapper = objectMapper;
83+
this.contentType = contentType;
84+
((DefaultJackson2JavaTypeMapper) this.javaTypeMapper).setTrustedPackages(trustedPackages);
85+
}
86+
87+
public ClassMapper getClassMapper() {
88+
return this.classMapper;
89+
90+
}
91+
92+
public void setClassMapper(ClassMapper classMapper) {
93+
this.classMapper = classMapper;
94+
}
95+
96+
/**
97+
* Specify the default charset to use when converting to or from text-based
98+
* Message body content. If not specified, the charset will be "UTF-8".
99+
* @param defaultCharset The default charset.
100+
*/
101+
public void setDefaultCharset(String defaultCharset) {
102+
this.defaultCharset = (defaultCharset != null) ? defaultCharset
103+
: DEFAULT_CHARSET;
104+
}
105+
106+
public String getDefaultCharset() {
107+
return this.defaultCharset;
108+
}
109+
110+
@Override
111+
public void setBeanClassLoader(ClassLoader classLoader) {
112+
this.classLoader = classLoader;
113+
if (!this.typeMapperSet) {
114+
((DefaultJackson2JavaTypeMapper) this.javaTypeMapper).setBeanClassLoader(classLoader);
115+
}
116+
}
117+
118+
protected ClassLoader getClassLoader() {
119+
return this.classLoader;
120+
}
121+
122+
public Jackson2JavaTypeMapper getJavaTypeMapper() {
123+
return this.javaTypeMapper;
124+
}
125+
126+
public void setJavaTypeMapper(Jackson2JavaTypeMapper javaTypeMapper) {
127+
this.javaTypeMapper = javaTypeMapper;
128+
this.typeMapperSet = true;
129+
}
130+
131+
/**
132+
* Return the type precedence.
133+
* @return the precedence.
134+
* @see #setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence)
135+
*/
136+
public Jackson2JavaTypeMapper.TypePrecedence getTypePrecedence() {
137+
return this.javaTypeMapper.getTypePrecedence();
138+
}
139+
140+
/**
141+
* Set the precedence for evaluating type information in message properties.
142+
* When using {@code @RabbitListener} at the method level, the framework attempts
143+
* to determine the target type for payload conversion from the method signature.
144+
* If so, this type is provided in the
145+
* {@link MessageProperties#getInferredArgumentType() inferredArgumentType}
146+
* message property.
147+
* <p> By default, if the type is concrete (not abstract, not an interface), this will
148+
* be used ahead of type information provided in the {@code __TypeId__} and
149+
* associated headers provided by the sender.
150+
* <p> If you wish to force the use of the {@code __TypeId__} and associated headers
151+
* (such as when the actual type is a subclass of the method argument type),
152+
* set the precedence to {@link Jackson2JavaTypeMapper.TypePrecedence#TYPE_ID}.
153+
* @param typePrecedence the precedence.
154+
* @see DefaultJackson2JavaTypeMapper#setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence)
155+
*/
156+
public void setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence typePrecedence) {
157+
if (this.typeMapperSet) {
158+
throw new IllegalStateException("When providing your own type mapper, you should set the precedence on it");
159+
}
160+
if (this.javaTypeMapper instanceof DefaultJackson2JavaTypeMapper) {
161+
((DefaultJackson2JavaTypeMapper) this.javaTypeMapper).setTypePrecedence(typePrecedence);
162+
}
163+
else {
164+
throw new IllegalStateException("Type precedence is available with the DefaultJackson2JavaTypeMapper");
165+
}
166+
}
167+
168+
@Override
169+
public Object fromMessage(Message message) throws MessageConversionException {
170+
return fromMessage(message, null);
171+
}
172+
173+
/**
174+
* {@inheritDoc}
175+
* @param conversionHint The conversionHint must be a {@link ParameterizedTypeReference}.
176+
*/
177+
@Override
178+
public Object fromMessage(Message message, Object conversionHint) throws MessageConversionException {
179+
Object content = null;
180+
MessageProperties properties = message.getMessageProperties();
181+
if (properties != null) {
182+
String contentType = properties.getContentType();
183+
if (contentType != null && contentType.contains(this.contentType.getSubtype())) {
184+
String encoding = properties.getContentEncoding();
185+
if (encoding == null) {
186+
encoding = getDefaultCharset();
187+
}
188+
try {
189+
if (conversionHint instanceof ParameterizedTypeReference) {
190+
content = convertBytesToObject(message.getBody(), encoding,
191+
this.objectMapper.getTypeFactory().constructType(
192+
((ParameterizedTypeReference<?>) conversionHint).getType()));
193+
}
194+
else if (getClassMapper() == null) {
195+
JavaType targetJavaType = getJavaTypeMapper()
196+
.toJavaType(message.getMessageProperties());
197+
content = convertBytesToObject(message.getBody(),
198+
encoding, targetJavaType);
199+
}
200+
else {
201+
Class<?> targetClass = getClassMapper().toClass(
202+
message.getMessageProperties());
203+
content = convertBytesToObject(message.getBody(),
204+
encoding, targetClass);
205+
}
206+
}
207+
catch (IOException e) {
208+
throw new MessageConversionException(
209+
"Failed to convert Message content", e);
210+
}
211+
}
212+
else {
213+
if (this.log.isWarnEnabled()) {
214+
this.log.warn("Could not convert incoming message with content-type ["
215+
+ contentType + "], '" + this.contentType.getSubtype() + "' keyword missing.");
216+
}
217+
}
218+
}
219+
if (content == null) {
220+
content = message.getBody();
221+
}
222+
return content;
223+
}
224+
225+
private Object convertBytesToObject(byte[] body, String encoding, JavaType targetJavaType) throws IOException {
226+
String contentAsString = new String(body, encoding);
227+
return this.objectMapper.readValue(contentAsString, targetJavaType);
228+
}
229+
230+
private Object convertBytesToObject(byte[] body, String encoding, Class<?> targetClass) throws IOException {
231+
String contentAsString = new String(body, encoding);
232+
return this.objectMapper.readValue(contentAsString, this.objectMapper.constructType(targetClass));
233+
}
234+
235+
@Override
236+
protected Message createMessage(Object objectToConvert, MessageProperties messageProperties)
237+
throws MessageConversionException {
238+
239+
return createMessage(objectToConvert, messageProperties, null);
240+
}
241+
242+
@Override
243+
protected Message createMessage(Object objectToConvert, MessageProperties messageProperties, Type genericType)
244+
throws MessageConversionException {
245+
246+
byte[] bytes;
247+
try {
248+
String jsonString = this.objectMapper
249+
.writeValueAsString(objectToConvert);
250+
bytes = jsonString.getBytes(getDefaultCharset());
251+
}
252+
catch (IOException e) {
253+
throw new MessageConversionException("Failed to convert Message content", e);
254+
}
255+
messageProperties.setContentType(this.contentType.toString());
256+
messageProperties.setContentEncoding(getDefaultCharset());
257+
messageProperties.setContentLength(bytes.length);
258+
259+
if (getClassMapper() == null) {
260+
getJavaTypeMapper().fromJavaType(this.objectMapper.constructType(
261+
genericType == null ? objectToConvert.getClass() : genericType), messageProperties);
262+
}
263+
else {
264+
getClassMapper().fromClass(objectToConvert.getClass(),
265+
messageProperties);
266+
}
267+
268+
return new Message(bytes, messageProperties);
269+
}
270+
271+
}

spring-amqp/src/main/java/org/springframework/amqp/support/converter/AbstractJsonMessageConverter.java

Lines changed: 0 additions & 75 deletions
This file was deleted.

0 commit comments

Comments
 (0)