Skip to content

Commit 73bb813

Browse files
NaccOllartembilan
authored andcommitted
GH-9561: Make DelayedMessageWrapper JSON-serializable
Fixes: #9561 PR: #9561 The `DelayHandler.DelayedMessageWrapper` cannot be deserialized when using `RedisMessageStore` and JSON serialization: ``` org.springframework.data.redis.serializer.SerializationException: Could not read JSON:Cannot construct instance of `org.springframework.integration.handler.DelayHandler$DelayedMessageWrapper` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: UNKNOWN; byte offset: #UNKNOWN] at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:311) ``` * More code clean up and refactoring in the test **Auto-cherry-pick to `6.3.x` & `6.2.x`**
1 parent 1f11ac4 commit 73bb813

File tree

3 files changed

+39
-59
lines changed

3 files changed

+39
-59
lines changed

Diff for: spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import java.util.concurrent.locks.ReentrantLock;
3232
import java.util.stream.Stream;
3333

34+
import com.fasterxml.jackson.annotation.JsonCreator;
35+
import com.fasterxml.jackson.annotation.JsonProperty;
3436
import org.aopalliance.aop.Advice;
3537

3638
import org.springframework.aop.framework.ProxyFactory;
@@ -97,6 +99,7 @@
9799
* @author Artem Bilan
98100
* @author Gary Russell
99101
* @author Christian Tzolov
102+
* @author Youbin Wu
100103
*
101104
* @since 1.0.3
102105
*/
@@ -689,7 +692,10 @@ public static final class DelayedMessageWrapper implements Serializable {
689692
@SuppressWarnings("serial")
690693
private final Message<?> original;
691694

692-
DelayedMessageWrapper(Message<?> original, long requestDate) {
695+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
696+
DelayedMessageWrapper(@JsonProperty("original") Message<?> original,
697+
@JsonProperty("requestDate") long requestDate) {
698+
693699
this.original = original;
694700
this.requestDate = requestDate;
695701
}

Diff for: spring-integration-core/src/main/java/org/springframework/integration/support/json/JacksonJsonUtils.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -46,6 +46,7 @@
4646
*
4747
* @author Artem Bilan
4848
* @author Gary Russell
49+
* @author Youbin Wu
4950
*
5051
* @since 3.0
5152
*
@@ -63,7 +64,8 @@ public final class JacksonJsonUtils {
6364
"org.springframework.integration.support",
6465
"org.springframework.integration.message",
6566
"org.springframework.integration.store",
66-
"org.springframework.integration.history"
67+
"org.springframework.integration.history",
68+
"org.springframework.integration.handler"
6769
);
6870

6971
private JacksonJsonUtils() {

Diff for: spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageGroupStoreTests.java

+28-56
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.Date;
2121
import java.util.Iterator;
2222
import java.util.List;
23-
import java.util.Objects;
2423
import java.util.Properties;
2524
import java.util.UUID;
2625
import java.util.concurrent.ExecutorService;
@@ -35,13 +34,16 @@
3534
import org.junit.jupiter.api.Disabled;
3635
import org.junit.jupiter.api.Test;
3736

37+
import org.springframework.beans.BeanUtils;
3838
import org.springframework.context.support.ClassPathXmlApplicationContext;
3939
import org.springframework.data.redis.connection.RedisConnectionFactory;
4040
import org.springframework.data.redis.core.StringRedisTemplate;
4141
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
42+
import org.springframework.data.redis.serializer.SerializationException;
4243
import org.springframework.integration.channel.DirectChannel;
4344
import org.springframework.integration.channel.NullChannel;
4445
import org.springframework.integration.channel.QueueChannel;
46+
import org.springframework.integration.handler.DelayHandler;
4547
import org.springframework.integration.history.MessageHistory;
4648
import org.springframework.integration.message.AdviceMessage;
4749
import org.springframework.integration.redis.RedisContainerTest;
@@ -56,14 +58,15 @@
5658
import org.springframework.messaging.support.GenericMessage;
5759

5860
import static org.assertj.core.api.Assertions.assertThat;
61+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5962
import static org.assertj.core.api.Assertions.assertThatNoException;
60-
import static org.assertj.core.api.Assertions.fail;
6163

6264
/**
6365
* @author Oleg Zhurakousky
6466
* @author Artem Bilan
6567
* @author Gary Russell
6668
* @author Artem Vozhdayenko
69+
* @author Youbin Wu
6770
*/
6871
class RedisMessageGroupStoreTests implements RedisContainerTest {
6972

@@ -316,7 +319,7 @@ void testConcurrentModifications() throws Exception {
316319
executor.execute(() -> {
317320
store2.removeMessagesFromGroup(this.groupId, message);
318321
MessageGroup group = store2.getMessageGroup(this.groupId);
319-
if (group.getMessages().size() != 0) {
322+
if (!group.getMessages().isEmpty()) {
320323
failures.add("REMOVE");
321324
throw new AssertionFailedError("Failed on Remove");
322325
}
@@ -400,11 +403,17 @@ void testJsonSerialization() {
400403
Message<?> mutableMessage = new MutableMessage<>(UUID.randomUUID());
401404
Message<?> adviceMessage = new AdviceMessage<>("foo", genericMessage);
402405
ErrorMessage errorMessage = new ErrorMessage(new RuntimeException("test exception"), mutableMessage);
406+
var delayedMessageWrapperConstructor =
407+
BeanUtils.getResolvableConstructor(DelayHandler.DelayedMessageWrapper.class);
408+
Message<?> delayMessage = new GenericMessage<>(
409+
BeanUtils.instantiateClass(delayedMessageWrapperConstructor, genericMessage,
410+
System.currentTimeMillis()));
403411

404-
store.addMessagesToGroup(this.groupId, genericMessage, mutableMessage, adviceMessage, errorMessage);
412+
store.addMessagesToGroup(this.groupId,
413+
genericMessage, mutableMessage, adviceMessage, errorMessage, delayMessage);
405414

406415
MessageGroup messageGroup = store.getMessageGroup(this.groupId);
407-
assertThat(messageGroup.size()).isEqualTo(4);
416+
assertThat(messageGroup.size()).isEqualTo(5);
408417
List<Message<?>> messages = new ArrayList<>(messageGroup.getMessages());
409418
assertThat(messages.get(0)).isEqualTo(genericMessage);
410419
assertThat(messages.get(0).getHeaders()).containsKeys(MessageHistory.HEADER_NAME);
@@ -417,22 +426,21 @@ void testJsonSerialization() {
417426
.isEqualTo(errorMessage.getOriginalMessage());
418427
assertThat(((ErrorMessage) errorMessageResult).getPayload().getMessage())
419428
.isEqualTo(errorMessage.getPayload().getMessage());
429+
assertThat(messages.get(4)).isEqualTo(delayMessage);
420430

421431
Message<Foo> fooMessage = new GenericMessage<>(new Foo("foo"));
422-
try {
423-
store.addMessageToGroup(this.groupId, fooMessage)
424-
.getMessages()
425-
.iterator()
426-
.next();
427-
fail("SerializationException expected");
428-
}
429-
catch (Exception e) {
430-
assertThat(e.getCause().getCause()).isInstanceOf(IllegalArgumentException.class);
431-
assertThat(e.getMessage()).contains("The class with " +
432-
"org.springframework.integration.redis.store.RedisMessageGroupStoreTests$Foo and name of " +
433-
"org.springframework.integration.redis.store.RedisMessageGroupStoreTests$Foo " +
434-
"is not in the trusted packages:");
435-
}
432+
433+
assertThatExceptionOfType(SerializationException.class)
434+
.isThrownBy(() ->
435+
store.addMessageToGroup(this.groupId, fooMessage)
436+
.getMessages()
437+
.iterator()
438+
.next())
439+
.withRootCauseInstanceOf(IllegalArgumentException.class)
440+
.withMessageContaining("The class with " +
441+
"org.springframework.integration.redis.store.RedisMessageGroupStoreTests$Foo and name of " +
442+
"org.springframework.integration.redis.store.RedisMessageGroupStoreTests$Foo " +
443+
"is not in the trusted packages:");
436444

437445
mapper = JacksonJsonUtils.messagingAwareMapper(getClass().getPackage().getName());
438446

@@ -485,43 +493,7 @@ public void removeMessagesFromGroupDontRemoveSameMessageInOtherGroup() {
485493
assertThat(store.messageGroupSize("2")).isEqualTo(1);
486494
}
487495

488-
private static class Foo {
489-
490-
private String foo;
491-
492-
Foo() {
493-
}
494-
495-
Foo(String foo) {
496-
this.foo = foo;
497-
}
498-
499-
public String getFoo() {
500-
return this.foo;
501-
}
502-
503-
public void setFoo(String foo) {
504-
this.foo = foo;
505-
}
506-
507-
@Override
508-
public boolean equals(Object o) {
509-
if (this == o) {
510-
return true;
511-
}
512-
if (o == null || getClass() != o.getClass()) {
513-
return false;
514-
}
515-
516-
Foo foo1 = (Foo) o;
517-
518-
return this.foo != null ? this.foo.equals(foo1.foo) : foo1.foo == null;
519-
}
520-
521-
@Override
522-
public int hashCode() {
523-
return Objects.hashCode(this.foo);
524-
}
496+
private record Foo(String foo) {
525497

526498
}
527499

0 commit comments

Comments
 (0)