Skip to content

Commit 4f49038

Browse files
artembilangaryrussell
authored andcommitted
Use bean CL for JdbcMessageStore.deserializer
Related to https://stackoverflow.com/questions/72305387/spring-integration-delayer-starts-sending-null-message-payloads-when-switched In some async use-cases (e.g. `DelayHandler`), the context classloader might be different for the data to be deserialized from message store. * Fix `JdbcMessageStore` to populate a bean `ClassLoader` into default `AllowListDeserializingConverter` from the application context. The provided `Deserializer` must ensure such a `ClassLoader` itself * Add warning message to the `LambdaMessageProcessor` when converter returns `null` for the payload it cannot convert to expected type. Cannot be raised as error since some applications may already rely on the `null` conversion result in their method arguments **Cherry-pick to `5.5.x`**
1 parent cdcc986 commit 4f49038

File tree

5 files changed

+61
-8
lines changed

5 files changed

+61
-8
lines changed

spring-integration-core/src/main/java/org/springframework/integration/handler/LambdaMessageProcessor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.beans.factory.BeanFactory;
3030
import org.springframework.beans.factory.BeanFactoryAware;
3131
import org.springframework.core.MethodIntrospector;
32+
import org.springframework.core.log.LogMessage;
3233
import org.springframework.integration.context.IntegrationContextUtils;
3334
import org.springframework.lang.Nullable;
3435
import org.springframework.messaging.Message;
@@ -151,7 +152,14 @@ else if (Map.class.isAssignableFrom(parameterType)) {
151152
args[i] = message;
152153
}
153154
else {
154-
args[i] = this.messageConverter.fromMessage(message, this.expectedType);
155+
Object payload = this.messageConverter.fromMessage(message, this.expectedType);
156+
if (payload == null && LOGGER.isWarnEnabled()) {
157+
LOGGER.warn(LogMessage.format(
158+
"The '%s' returned 'null' for the payload conversion from the " +
159+
"'%s' and expected type '%s'.",
160+
this.messageConverter, message, this.expectedType));
161+
}
162+
args[i] = payload;
155163
}
156164
}
157165
else {

spring-integration-core/src/main/java/org/springframework/integration/support/converter/AllowListDeserializingConverter.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
* classes/packages are deserialized. If you receive data from untrusted sources, consider
4141
* adding trusted classes/packages using {@link #setAllowedPatterns(String...)} or
4242
* {@link #addAllowedPatterns(String...)}.
43+
* <p>
44+
* If a delegate deserializer is a {@link DefaultDeserializer}, only its {@link ClassLoader}
45+
* is used for a {@link ConfigurableObjectInputStream} logic.
4346
*
4447
* @author Gary Russell
4548
* @author Mark Fisher
@@ -132,7 +135,13 @@ public Object convert(byte[] source) {
132135
return deserialize(byteStream);
133136
}
134137
else {
135-
return this.deserializer.deserialize(byteStream);
138+
Object result = this.deserializer.deserialize(byteStream);
139+
/* Even if there is no knowledge what is the target deserialization algorithm
140+
and malicious code may be executed already, it is still better to fail
141+
with untrusted data rather than just let it pass downstream.
142+
*/
143+
checkAllowList(result.getClass());
144+
return result;
136145
}
137146
}
138147
catch (Exception ex) {

spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 the original author or authors.
2+
* Copyright 2016-2022 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,6 +19,7 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2121

22+
import java.util.Date;
2223
import java.util.Objects;
2324
import java.util.function.Function;
2425

@@ -32,6 +33,7 @@
3233
import org.springframework.core.convert.converter.Converter;
3334
import org.springframework.integration.config.EnableIntegration;
3435
import org.springframework.integration.config.IntegrationConverter;
36+
import org.springframework.integration.core.GenericSelector;
3537
import org.springframework.integration.handler.GenericHandler;
3638
import org.springframework.integration.handler.LambdaMessageProcessor;
3739
import org.springframework.integration.transformer.GenericTransformer;
@@ -86,6 +88,24 @@ public void testMessageAsArgumentLambda() {
8688
.isThrownBy(() -> lmp.processMessage(testMessage));
8789
}
8890

91+
@Test
92+
public void testConversionToNull() {
93+
LambdaMessageProcessor lmp = new LambdaMessageProcessor(
94+
new GenericSelector<Date>() { // Must not be lambda
95+
96+
@Override
97+
public boolean accept(Date payload) {
98+
return payload == null;
99+
}
100+
101+
}, Date.class);
102+
lmp.setBeanFactory(this.beanFactory);
103+
GenericMessage<String> testMessage = new GenericMessage<>("foo");
104+
Object result = lmp.processMessage(testMessage);
105+
assertThat(result).isEqualTo(Boolean.TRUE);
106+
}
107+
108+
89109
@Test
90110
@Disabled("Until https://github.com/spring-projects/spring-integration/issues/3660")
91111
public void testCustomConverter() {

spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcMessageStore.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import javax.sql.DataSource;
3333

34+
import org.springframework.beans.factory.BeanClassLoaderAware;
3435
import org.springframework.core.serializer.Deserializer;
3536
import org.springframework.core.serializer.Serializer;
3637
import org.springframework.core.serializer.support.SerializingConverter;
@@ -79,7 +80,7 @@
7980
*
8081
* @since 2.0
8182
*/
82-
public class JdbcMessageStore extends AbstractMessageGroupStore implements MessageStore {
83+
public class JdbcMessageStore extends AbstractMessageGroupStore implements MessageStore, BeanClassLoaderAware {
8384

8485
/**
8586
* Default value for the table prefix property.
@@ -177,7 +178,10 @@ public String getSql() {
177178

178179
private String tablePrefix = DEFAULT_TABLE_PREFIX;
179180

180-
private AllowListDeserializingConverter deserializer;
181+
private AllowListDeserializingConverter deserializer =
182+
new AllowListDeserializingConverter(JdbcMessageStore.class.getClassLoader());
183+
184+
private boolean deserializerExplicitlySet;
181185

182186
private SerializingConverter serializer;
183187

@@ -199,7 +203,6 @@ public JdbcMessageStore(DataSource dataSource) {
199203
public JdbcMessageStore(JdbcOperations jdbcOperations) {
200204
Assert.notNull(jdbcOperations, "'dataSource' must not be null");
201205
this.jdbcTemplate = jdbcOperations;
202-
this.deserializer = new AllowListDeserializingConverter();
203206
this.serializer = new SerializingConverter();
204207
try {
205208
this.vendorName =
@@ -211,6 +214,13 @@ public JdbcMessageStore(JdbcOperations jdbcOperations) {
211214
}
212215
}
213216

217+
@Override
218+
public void setBeanClassLoader(ClassLoader classLoader) {
219+
if (!this.deserializerExplicitlySet) {
220+
this.deserializer = new AllowListDeserializingConverter(classLoader);
221+
}
222+
}
223+
214224
/**
215225
* Public setter for the table prefix property. This will be prefixed to all the table names before queries are
216226
* executed. Defaults to {@link #DEFAULT_TABLE_PREFIX}.
@@ -249,12 +259,13 @@ public void setSerializer(Serializer<? super Message<?>> serializer) {
249259
}
250260

251261
/**
252-
* A converter for deserializing byte arrays to messages.
262+
* A converter for deserializing byte arrays to message.
253263
* @param deserializer the deserializer to set
254264
*/
255265
@SuppressWarnings({ "unchecked", "rawtypes" })
256266
public void setDeserializer(Deserializer<? extends Message<?>> deserializer) {
257267
this.deserializer = new AllowListDeserializingConverter((Deserializer) deserializer);
268+
this.deserializerExplicitlySet = true;
258269
}
259270

260271
/**

spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/DelayerHandlerRescheduleIntegrationTests.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -35,6 +35,7 @@
3535
import org.springframework.integration.store.SimpleMessageGroup;
3636
import org.springframework.integration.support.MessageBuilder;
3737
import org.springframework.integration.test.condition.LongRunningTest;
38+
import org.springframework.integration.test.util.TestUtils;
3839
import org.springframework.integration.util.UUIDConverter;
3940
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
4041
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
@@ -80,6 +81,10 @@ public void testDelayerHandlerRescheduleWithJdbcMessageStore() throws Exception
8081
MessageChannel input = context.getBean("input", MessageChannel.class);
8182
MessageGroupStore messageStore = context.getBean("messageStore", MessageGroupStore.class);
8283

84+
ClassLoader messageStoreDeserializerClassLoader =
85+
TestUtils.getPropertyValue(messageStore, "deserializer.defaultDeserializerClassLoader",
86+
ClassLoader.class);
87+
assertThat(messageStoreDeserializerClassLoader).isSameAs(context.getClassLoader());
8388
assertThat(messageStore.getMessageGroupCount()).isEqualTo(0);
8489
Message<String> message1 = MessageBuilder.withPayload("test1").build();
8590
input.send(message1);

0 commit comments

Comments
 (0)