Skip to content

Commit aaf18cd

Browse files
committed
add support for genric object
1 parent 5aff890 commit aaf18cd

3 files changed

Lines changed: 125 additions & 11 deletions

File tree

rqueue-core/src/main/java/com/github/sonus21/rqueue/converter/GenericMessageConverter.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.fasterxml.jackson.databind.JavaType;
2323
import com.fasterxml.jackson.databind.ObjectMapper;
2424
import com.github.sonus21.rqueue.utils.SerializationUtils;
25+
import java.lang.reflect.Field;
2526
import java.lang.reflect.TypeVariable;
2627
import java.util.Collection;
2728
import java.util.List;
@@ -37,8 +38,8 @@
3738

3839
/**
3940
* A converter to turn the payload of a {@link Message} from serialized form to a typed String and
40-
* vice versa. This class does not support generic class except {@link List},even for list the
41-
* entries should be non generic.
41+
* vice versa. Supports {@link List} and single-level generic envelope types (e.g. {@code Event<T>})
42+
* where type parameters are non-generic and can be resolved from non-null field values.
4243
*/
4344
@Slf4j
4445
public class GenericMessageConverter implements SmartMessageConverter {
@@ -137,7 +138,12 @@ private String getClassNameForCollection(String name, Collection<?> payload) {
137138
if (payload.isEmpty()) {
138139
return null;
139140
}
140-
String itemClassName = getClassName(((List<?>) payload).get(0));
141+
Object firstItem = ((List<?>) payload).get(0);
142+
// Only support non-generic item classes in lists to avoid ambiguous encoding
143+
if (firstItem.getClass().getTypeParameters().length > 0) {
144+
return null;
145+
}
146+
String itemClassName = getClassName(firstItem);
141147
if (itemClassName == null) {
142148
return null;
143149
}
@@ -146,12 +152,40 @@ private String getClassNameForCollection(String name, Collection<?> payload) {
146152
return null;
147153
}
148154

149-
private String getGenericFieldBasedClassName(Class<?> clazz) {
155+
private Class<?> resolveTypeVariable(Class<?> clazz, TypeVariable<?> tv, Object payload) {
156+
// TypeVariable instances are scoped to the class that declares them, so
157+
// field.getGenericType().equals(tv) can only match fields declared on clazz itself.
158+
// Superclass fields reference their own TypeVariable instances, which are distinct objects.
159+
for (Field field : clazz.getDeclaredFields()) {
160+
if (field.getGenericType().equals(tv)) {
161+
field.setAccessible(true);
162+
try {
163+
Object value = field.get(payload);
164+
if (value != null) {
165+
return value.getClass();
166+
}
167+
} catch (IllegalAccessException e) {
168+
log.debug("Cannot access field {}", field.getName(), e);
169+
}
170+
}
171+
}
172+
return null;
173+
}
174+
175+
private String getGenericFieldBasedClassName(Class<?> clazz, Object payload) {
150176
TypeVariable<?>[] typeVariables = clazz.getTypeParameters();
151177
if (typeVariables.length == 0) {
152178
return clazz.getName();
153179
}
154-
return null;
180+
StringBuilder sb = new StringBuilder(clazz.getName());
181+
for (TypeVariable<?> tv : typeVariables) {
182+
Class<?> resolved = resolveTypeVariable(clazz, tv, payload);
183+
if (resolved == null || resolved.getTypeParameters().length > 0) {
184+
return null;
185+
}
186+
sb.append('#').append(resolved.getName());
187+
}
188+
return sb.toString();
155189
}
156190

157191
private String getClassName(Object payload) {
@@ -160,7 +194,7 @@ private String getClassName(Object payload) {
160194
if (payload instanceof Collection) {
161195
return getClassNameForCollection(name, (Collection<?>) payload);
162196
}
163-
return getGenericFieldBasedClassName(payloadClass);
197+
return getGenericFieldBasedClassName(payloadClass, payload);
164198
}
165199

166200
private JavaType getTargetType(Msg msg) throws ClassNotFoundException {

rqueue-core/src/test/java/com/github/sonus21/rqueue/converter/GenericMessageConverterTest.java

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,45 @@ void toAndFromMessageList() {
116116
}
117117

118118
@Test
119-
void genericMessageToReturnNull() {
119+
void genericEnvelopeToAndFromMessage() {
120120
GenericTestData<Comment> data = new GenericTestData<>(10, comment);
121121
Message message =
122122
genericMessageConverter.toMessage(data, RqueueMessageHeaders.emptyMessageHeaders());
123+
GenericTestData<Comment> fromMessage =
124+
(GenericTestData<Comment>) genericMessageConverter.fromMessage(message, null);
125+
assertEquals(data, fromMessage);
126+
}
127+
128+
@Test
129+
void envelopeEventToAndFromMessage() {
130+
Event<Comment> event = new Event<>("evt-1", comment);
131+
Message message =
132+
genericMessageConverter.toMessage(event, RqueueMessageHeaders.emptyMessageHeaders());
133+
Event<Comment> fromMessage =
134+
(Event<Comment>) genericMessageConverter.fromMessage(message, null);
135+
assertEquals(event, fromMessage);
136+
}
137+
138+
@Test
139+
void envelopeEventWithNullPayloadToReturnNull() {
140+
Event<Comment> event = new Event<>("evt-1", null);
141+
Message message =
142+
genericMessageConverter.toMessage(event, RqueueMessageHeaders.emptyMessageHeaders());
123143
assertNull(message);
124144
}
125145

146+
@Test
147+
void envelopeEventWithInheritedTypeToAndFromMessage() {
148+
// T=Notification extends Alert extends BaseAlert — runtime class is Notification
149+
Notification notification = new Notification("n-1", "hello", 42);
150+
Event<Notification> event = new Event<>("evt-2", notification);
151+
Message message =
152+
genericMessageConverter.toMessage(event, RqueueMessageHeaders.emptyMessageHeaders());
153+
Event<Notification> fromMessage =
154+
(Event<Notification>) genericMessageConverter.fromMessage(message, null);
155+
assertEquals(event, fromMessage);
156+
}
157+
126158
@Test
127159
@Disabled
128160
void multipleGenericFieldMessageToAndFrom() {
@@ -379,4 +411,50 @@ public static class GenericTestDataWithPredefinedType {
379411
private Integer index;
380412
private MultiGenericTestData<Comment, Email> data;
381413
}
414+
415+
@Data
416+
@NoArgsConstructor
417+
@AllArgsConstructor
418+
public static class Event<T> {
419+
420+
private String id;
421+
private T payload;
422+
}
423+
424+
// Three-level hierarchy: Notification extends Alert extends BaseAlert
425+
@Data
426+
@NoArgsConstructor
427+
@AllArgsConstructor
428+
public static class BaseAlert {
429+
430+
private String id;
431+
}
432+
433+
@Data
434+
@EqualsAndHashCode(callSuper = true)
435+
@NoArgsConstructor
436+
@AllArgsConstructor
437+
public static class Alert extends BaseAlert {
438+
439+
private String message;
440+
441+
public Alert(String id, String message) {
442+
super(id);
443+
this.message = message;
444+
}
445+
}
446+
447+
@Data
448+
@EqualsAndHashCode(callSuper = true)
449+
@NoArgsConstructor
450+
@AllArgsConstructor
451+
public static class Notification extends Alert {
452+
453+
private int priority;
454+
455+
public Notification(String id, String message, int priority) {
456+
super(id, message);
457+
this.priority = priority;
458+
}
459+
}
382460
}

rqueue-core/src/test/java/com/github/sonus21/rqueue/core/support/RqueueMessageUtilsTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ void buildMessageWithDelay() {
148148

149149
@Test
150150
void buildMessageNull() {
151+
// id is null so the type parameter T cannot be resolved, making conversion fail
151152
GenericClass<String> genericClass = new GenericClass<>();
152-
genericClass.id = UUID.randomUUID().toString();
153153
try {
154154
RqueueMessageUtils.buildMessage(
155155
messageConverter,
@@ -167,8 +167,8 @@ void buildMessageNull() {
167167

168168
@Test
169169
void buildPeriodicMessageNull() {
170+
// id is null so the type parameter T cannot be resolved, making conversion fail
170171
GenericClass<String> genericClass = new GenericClass<>();
171-
genericClass.id = UUID.randomUUID().toString();
172172
try {
173173
RqueueMessageUtils.buildPeriodicMessage(
174174
messageConverter,
@@ -186,8 +186,9 @@ void buildPeriodicMessageNull() {
186186

187187
@Test
188188
void buildMessageReturnInvalidType() {
189+
// id is null so GenericMessageConverter returns null; falls through to NoMessageConverter
190+
// which wraps the object in a GenericMessage with a non-String/non-byte[] payload
189191
GenericClass<String> genericClass = new GenericClass<>();
190-
genericClass.id = UUID.randomUUID().toString();
191192
try {
192193
RqueueMessageUtils.buildMessage(
193194
messageConverter2,
@@ -205,8 +206,9 @@ void buildMessageReturnInvalidType() {
205206

206207
@Test
207208
void buildPeriodicMessageReturnInvalidType() {
209+
// id is null so GenericMessageConverter returns null; falls through to NoMessageConverter
210+
// which wraps the object in a GenericMessage with a non-String/non-byte[] payload
208211
GenericClass<String> genericClass = new GenericClass<>();
209-
genericClass.id = UUID.randomUUID().toString();
210212
try {
211213
RqueueMessageUtils.buildPeriodicMessage(
212214
messageConverter2,

0 commit comments

Comments
 (0)