Skip to content

Commit 212a97a

Browse files
committed
GH-3151: Pull projection logic down to the JSON-specific converter impl
Related to: #3151 The `JacksonProjectingMessageConverter` is based only on JSON format. Therefore, it is wrong to have its logic involved in the `AbstractJacksonMessageConverter`. * Pull the `JacksonProjectingMessageConverter` down to the `JacksonJsonMessageConverter` with respective `convertContent()` override * Remove unnecessary now `setUseProjectionForInterfaces()` from the `JacksonXmlMessageConverter`
1 parent b682cda commit 212a97a

File tree

3 files changed

+51
-38
lines changed

3 files changed

+51
-38
lines changed

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

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import tools.jackson.core.JacksonException;
3030
import tools.jackson.databind.JavaType;
3131
import tools.jackson.databind.ObjectMapper;
32-
import tools.jackson.databind.json.JsonMapper;
3332

3433
import org.springframework.amqp.core.Message;
3534
import org.springframework.amqp.core.MessageProperties;
@@ -80,10 +79,6 @@ public abstract class AbstractJacksonMessageConverter extends AbstractMessageCon
8079

8180
private JacksonJavaTypeMapper javaTypeMapper = new DefaultJacksonJavaTypeMapper();
8281

83-
private boolean useProjectionForInterfaces;
84-
85-
private @Nullable JacksonProjectingMessageConverter projectingConverter;
86-
8782
private boolean charsetIsUtf8 = true;
8883

8984
private boolean assumeSupportedContentType = true;
@@ -254,25 +249,6 @@ public void setAlwaysConvertToInferredType(boolean alwaysAttemptConversion) {
254249
this.alwaysConvertToInferredType = alwaysAttemptConversion;
255250
}
256251

257-
protected boolean isUseProjectionForInterfaces() {
258-
return this.useProjectionForInterfaces;
259-
}
260-
261-
/**
262-
* Set to true to use Spring Data projection to create the object if the inferred
263-
* parameter type is an interface.
264-
* @param useProjectionForInterfaces true to use projection.
265-
*/
266-
public void setUseProjectionForInterfaces(boolean useProjectionForInterfaces) {
267-
this.useProjectionForInterfaces = useProjectionForInterfaces;
268-
if (useProjectionForInterfaces) {
269-
if (!ClassUtils.isPresent("org.springframework.data.projection.ProjectionFactory", this.classLoader)) {
270-
throw new IllegalStateException("'spring-data-commons' is required to use Projection Interfaces");
271-
}
272-
this.projectingConverter = new JacksonProjectingMessageConverter((JsonMapper) this.objectMapper);
273-
}
274-
}
275-
276252
/**
277253
* By default, the supported content type is assumed when there is no contentType
278254
* property, or it is set to the default ('application/octet-stream'). Set to 'false'
@@ -336,7 +312,7 @@ public Object fromMessage(Message message, @Nullable Object conversionHint) thro
336312
return encoding;
337313
}
338314

339-
private Object doFromMessage(Message message, @Nullable Object conversionHint, MessageProperties properties,
315+
protected Object doFromMessage(Message message, @Nullable Object conversionHint, MessageProperties properties,
340316
@Nullable String encoding) {
341317

342318
try {
@@ -347,18 +323,12 @@ private Object doFromMessage(Message message, @Nullable Object conversionHint, M
347323
}
348324
}
349325

350-
@SuppressWarnings("NullAway") // Dataflow analysis limitation
351-
private Object convertContent(Message message, @Nullable Object conversionHint, MessageProperties properties,
326+
protected Object convertContent(Message message, @Nullable Object conversionHint, MessageProperties properties,
352327
@Nullable String encoding) throws IOException {
353328

354329
Object content = null;
355330
JavaType inferredType = this.javaTypeMapper.getInferredType(properties);
356-
if (inferredType != null && this.useProjectionForInterfaces && inferredType.isInterface()
357-
&& !inferredType.getRawClass().getPackage().getName().startsWith("java.util")) { // List etc
358-
content = this.projectingConverter.convert(message, inferredType.getRawClass());
359-
properties.setProjectionUsed(true);
360-
}
361-
else if (inferredType != null && this.alwaysConvertToInferredType) {
331+
if (inferredType != null && this.alwaysConvertToInferredType) {
362332
content = tryConvertType(message, encoding, inferredType);
363333
}
364334
if (content == null) {

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616

1717
package org.springframework.amqp.support.converter;
1818

19+
import java.io.IOException;
20+
21+
import org.jspecify.annotations.Nullable;
1922
import tools.jackson.databind.DeserializationFeature;
23+
import tools.jackson.databind.JavaType;
2024
import tools.jackson.databind.MapperFeature;
2125
import tools.jackson.databind.json.JsonMapper;
2226

27+
import org.springframework.amqp.core.Message;
2328
import org.springframework.amqp.core.MessageProperties;
29+
import org.springframework.util.ClassUtils;
2430
import org.springframework.util.MimeTypeUtils;
2531

2632
/**
@@ -32,6 +38,8 @@
3238
*/
3339
public class JacksonJsonMessageConverter extends AbstractJacksonMessageConverter {
3440

41+
private @Nullable JacksonProjectingMessageConverter projectingConverter;
42+
3543
/**
3644
* Construct with an internal {@link JsonMapper} instance and trusted packed to all ({@code *}).
3745
*/
@@ -73,4 +81,44 @@ public JacksonJsonMessageConverter(JsonMapper jsonMapper, String... trustedPacka
7381
super(jsonMapper, MimeTypeUtils.parseMimeType(MessageProperties.CONTENT_TYPE_JSON), trustedPackages);
7482
}
7583

84+
/**
85+
* Set to true to use Spring Data projection to create the object if the inferred
86+
* parameter type is an interface.
87+
* @param useProjectionForInterfaces true to use projection.
88+
*/
89+
public void setUseProjectionForInterfaces(boolean useProjectionForInterfaces) {
90+
if (useProjectionForInterfaces) {
91+
if (!ClassUtils.isPresent("org.springframework.data.projection.ProjectionFactory", getClassLoader())) {
92+
throw new IllegalStateException("'spring-data-commons' is required to use Projection Interfaces");
93+
}
94+
this.projectingConverter = new JacksonProjectingMessageConverter((JsonMapper) this.objectMapper);
95+
}
96+
}
97+
98+
protected boolean isUseProjectionForInterfaces() {
99+
return this.projectingConverter != null;
100+
}
101+
102+
@Override
103+
protected Object convertContent(Message message, @Nullable Object conversionHint, MessageProperties properties,
104+
@Nullable String encoding) throws IOException {
105+
106+
Object content = null;
107+
108+
JavaType inferredType = getJavaTypeMapper().getInferredType(properties);
109+
if (inferredType != null && this.projectingConverter != null && inferredType.isInterface()
110+
&& !inferredType.getRawClass().getPackage().getName().startsWith("java.util")) { // List etc
111+
112+
content = this.projectingConverter.convert(message, inferredType.getRawClass());
113+
properties.setProjectionUsed(true);
114+
}
115+
116+
if (content == null) {
117+
return super.convertContent(message, conversionHint, properties, encoding);
118+
}
119+
else {
120+
return content;
121+
}
122+
}
123+
76124
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,4 @@ public JacksonXmlMessageConverter(XmlMapper xmlMapper, String... trustedPackages
7272
super(xmlMapper, MimeTypeUtils.parseMimeType(MessageProperties.CONTENT_TYPE_XML), trustedPackages);
7373
}
7474

75-
@Override
76-
public void setUseProjectionForInterfaces(boolean useProjectionForInterfaces) {
77-
throw new UnsupportedOperationException("This converter does not support useProjectionForInterfaces");
78-
}
79-
8075
}

0 commit comments

Comments
 (0)