transition enricher rabbit

This commit is contained in:
2026-06-19 09:22:27 +02:00
parent 0a23daae05
commit b480dc83ef
9 changed files with 247 additions and 1 deletions

View File

@@ -400,6 +400,15 @@
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 50
}, {
"event" : "customMessage",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendCustomMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 69
} ],
"entryPoints" : [ {
"type" : "REST",
@@ -434,6 +443,21 @@
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener",
"methodName" : "handleCustomTransition",
"sourceFile" : "web/src/main/java/click/kamil/web/RabbitOrderListener.java",
"metadata" : {
"protocol" : "RABBIT",
"destination" : "order.custom.transition.queue"
},
"parameters" : [ {
"name" : "payload",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"type" : "JMS",
"name" : "JMS: order.process.queue",
@@ -559,6 +583,40 @@
"targetState" : "OrderState.CANCELLED",
"event" : "OrderEvent.CANCEL"
} ]
}, {
"entryPoint" : {
"type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener",
"methodName" : "handleCustomTransition",
"sourceFile" : "web/src/main/java/click/kamil/web/RabbitOrderListener.java",
"metadata" : {
"protocol" : "RABBIT",
"destination" : "order.custom.transition.queue"
},
"parameters" : [ {
"name" : "payload",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.web.RabbitOrderListener.handleCustomTransition", "click.kamil.service.StateMachineServiceImpl.sendCustomMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.PROCESS",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendCustomMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 69
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PROCESSING",
"event" : "OrderEvent.PROCESS"
} ]
}, {
"entryPoint" : {
"type" : "JMS",

View File

@@ -18,6 +18,15 @@
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 50
}, {
"event" : "customMessage",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendCustomMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 69
} ],
"entryPoints" : [ {
"type" : "REST",
@@ -52,6 +61,21 @@
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener",
"methodName" : "handleCustomTransition",
"sourceFile" : "web/src/main/java/click/kamil/web/RabbitOrderListener.java",
"metadata" : {
"protocol" : "RABBIT",
"destination" : "order.custom.transition.queue"
},
"parameters" : [ {
"name" : "payload",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"type" : "JMS",
"name" : "JMS: order.process.queue",
@@ -177,6 +201,40 @@
"targetState" : "OrderState.CANCELLED",
"event" : "OrderEvent.CANCEL"
} ]
}, {
"entryPoint" : {
"type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener",
"methodName" : "handleCustomTransition",
"sourceFile" : "web/src/main/java/click/kamil/web/RabbitOrderListener.java",
"metadata" : {
"protocol" : "RABBIT",
"destination" : "order.custom.transition.queue"
},
"parameters" : [ {
"name" : "payload",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.web.RabbitOrderListener.handleCustomTransition", "click.kamil.service.StateMachineServiceImpl.sendCustomMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.PROCESS",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendCustomMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 69
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PROCESSING",
"event" : "OrderEvent.PROCESS"
} ]
}, {
"entryPoint" : {
"type" : "JMS",

View File

@@ -216,6 +216,14 @@ public class CallGraphBuilder {
}
}
// Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...))
expr = traceVariable(expr);
if (expr instanceof ClassInstanceCreation cic) {
if (!cic.arguments().isEmpty()) {
expr = (Expression) cic.arguments().get(0);
}
}
String val = constantResolver.resolve(expr, context);
if (val == null) {
val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc)
@@ -317,4 +325,41 @@ public class CallGraphBuilder {
}
return (TypeDeclaration) parent;
}
private Expression traceVariable(Expression expr) {
if (expr instanceof SimpleName sn) {
String varName = sn.getIdentifier();
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
if (enclosingMethod != null) {
final Expression[] initializer = new Expression[1];
enclosingMethod.accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
initializer[0] = node.getInitializer();
}
return super.visit(node);
}
@Override
public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
initializer[0] = node.getRightHandSide();
}
return super.visit(node);
}
});
if (initializer[0] != null) {
return traceVariable(initializer[0]);
}
}
}
return expr;
}
private MethodDeclaration findEnclosingMethod(ASTNode node) {
ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof MethodDeclaration)) {
parent = parent.getParent();
}
return (MethodDeclaration) parent;
}
}

View File

@@ -193,6 +193,10 @@ public class GenericEventDetector {
}
private String extractEventFromMessageBuilder(Expression expr) {
if (expr instanceof CastExpression ce) {
return extractEventFromMessageBuilder(ce.getExpression());
}
if (expr instanceof SimpleName sn) {
String varName = sn.getIdentifier();
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
@@ -218,6 +222,15 @@ public class GenericEventDetector {
if (initializer[0] != null) {
return extractEventFromMessageBuilder(initializer[0]); // recursive
}
// If it has NO initializer, it might be a method parameter!
for (Object paramObj : enclosingMethod.parameters()) {
if (paramObj instanceof SingleVariableDeclaration svd) {
if (svd.getName().getIdentifier().equals(varName)) {
return varName;
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
package click.kamil.domain;
import org.springframework.messaging.support.GenericMessage;
import java.util.Map;
public class CustomStateMachineMessage<T, P> extends GenericMessage<T> {
private final P customPayload;
public CustomStateMachineMessage(T payload, P customPayload) {
super(payload);
this.customPayload = customPayload;
}
public CustomStateMachineMessage(T payload, Map<String, Object> headers, P customPayload) {
super(payload, headers);
this.customPayload = customPayload;
}
public P getCustomPayload() {
return customPayload;
}
}

View File

@@ -14,6 +14,9 @@ public interface StateMachineService {
// Quirky pattern 2: Generic Varargs
<T extends IEvent> void sendEventsVarargs(T... events);
// Child class of Spring's GenericMessage with a generic payload
<T extends click.kamil.domain.OrderEvent, P> void sendCustomMessage(click.kamil.domain.CustomStateMachineMessage<T, P> customMessage);
// Quirky pattern 3: Multiple generic bounds (Intersection Types)
<T extends IEvent & java.io.Serializable & Cloneable> T processQuirkyEvent(T event);
}

View File

@@ -54,12 +54,21 @@ public class StateMachineServiceImpl implements StateMachineService {
@Override
@SafeVarargs
public final <T extends IEvent> void sendEventsVarargs(T... events) {
// Quirky pattern 2: Iterating over generic varargs
for (T event : events) {
sendMessage(event);
}
}
@Override
public <T extends OrderEvent, P> void sendCustomMessage(click.kamil.domain.CustomStateMachineMessage<T, P> customMessage) {
// Because CustomStateMachineMessage extends GenericMessage<T> and implements Message<T>,
// we can pass it directly to the state machine as long as T resolves to OrderEvent!
// We cast it to Message<OrderEvent> to satisfy Mono's strict type requirements
org.springframework.messaging.Message<OrderEvent> msg = (org.springframework.messaging.Message<OrderEvent>) customMessage;
stateMachine.sendEvent(Mono.just(msg)).subscribe();
}
@Override
public <T extends IEvent & java.io.Serializable & Cloneable> T processQuirkyEvent(T event) {
// Quirky pattern 3: Multiple bounds, returning the same generic type

View File

@@ -24,5 +24,9 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,32 @@
package click.kamil.web;
import click.kamil.domain.CustomStateMachineMessage;
import click.kamil.domain.OrderEvent;
import click.kamil.service.StateMachineService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class RabbitOrderListener {
private final StateMachineService stateMachineService;
// Constructor Injection
public RabbitOrderListener(StateMachineService stateMachineService) {
this.stateMachineService = stateMachineService;
}
// Listens to RabbitMQ, constructs the custom class, triggers a state transition
@RabbitListener(queues = "order.custom.transition.queue")
public void handleCustomTransition(String payload) {
System.out.println("Received RabbitMQ message: " + payload);
// Construct our custom event containing the generic payload and the enum state
CustomStateMachineMessage<OrderEvent, String> customMessage =
new CustomStateMachineMessage<>(OrderEvent.PROCESS, "My Rabbit Custom Payload: " + payload);
// Push the custom event into the state machine to perform the transition
stateMachineService.sendCustomMessage(customMessage);
System.out.println("Triggered PROCESS transition via custom message over RabbitMQ");
}
}