3.8 KiB
Implementation Details: Trigger Detection with JDT
1. Finding sendEvent Calls
We can use an ASTVisitor to find all MethodInvocation nodes.
public class SendEventVisitor extends ASTVisitor {
@Override
public boolean visit(MethodInvocation node) {
if ("sendEvent".equals(node.getName().getIdentifier())) {
// Found a call!
// 1. Extract event (first argument)
// 2. Identify the enclosing method and class
}
return super.visit(node);
}
}
Challenges in Event Extraction
Events can be:
- Enum constants:
Events.SUBMIT - Strings:
"SUBMIT" - Variables:
sm.sendEvent(eventFromPayload)(Hard to resolve statically)
We should reuse CodebaseContext.resolveState logic (which is basically resolving an expression to a value/fqn).
2. Identifying Enclosing Context
Once a sendEvent is found, we can traverse up the AST to find the MethodDeclaration and TypeDeclaration.
ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof MethodDeclaration)) {
parent = parent.getParent();
}
// parent is now the MethodDeclaration
2. Extracting Annotations
From MethodDeclaration, we can check for mappings:
@PostMapping,@GetMapping, etc.@KafkaListener: Extracttopics,groupId.@RabbitListener: Extractqueues,bindings(Exchange/RoutingKey).
From TypeDeclaration, we can check for:
@RestController,@Controller@RequestMappingat class level (to get base path)
3. Specialized WebFlux Analysis
Annotation-based WebFlux
This is largely identical to Spring MVC. The challenge is if the sendEvent is wrapped in a reactive operator.
public Mono<Void> submit(Order order) {
return service.save(order)
.doOnNext(o -> stateMachine.sendEvent(Events.SUBMIT))
.then();
}
Static Strategy: We need to look inside LambdaExpression nodes passed to reactive operators (doOnNext, flatMap, map, subscribe). The ASTVisitor should traverse into these lambdas.
Functional WebFlux (RouterFunctions)
Functional endpoints are often defined as Beans returning RouterFunction.
@Bean
public RouterFunction<ServerResponse> route(OrderHandler handler) {
return RouterFunctions.route(POST("/orders"), handler::submitOrder);
}
Static Strategy:
- Find methods returning
RouterFunction. - Analyze the
MethodInvocationchain (route,andRoute,nest). - Extract the URI pattern and the
HandlerFunctionreference. - If the handler is a method reference (
handler::submitOrder), link it to the correspondingMethodDeclaration.
4. Specialized RabbitMQ Analysis
@RabbitListener can be complex:
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "orderQueue", durable = "true"),
exchange = @Exchange(value = "orderExchange"),
key = "order.created"
))
public void onOrder(Order order) { ... }
Static Strategy:
- Find
@RabbitListener. - If it has
bindings, drill down into@QueueBinding,@Queue,@Exchangeto extract the topology. - If it only has
queues, resolve the queue name (might be a SpEL expression or property placeholder, which we can try to resolve or just keep as-is).
5. Indirect Flow Detection (The "Service Link")
If the project has multiple state machines, we need to know which one is being targeted. Usually, this is done via:
- Autowiring by type:
StateMachine<States, Events> sm; - Autowiring by name:
@Qualifier("mySm") StateMachine sm;
We can look at the fields of the class where sendEvent is called.
6. Output Enhancement
The Exporter should be modified to:
- In DOT: Add nodes for Endpoints/Listeners and link them to the Events/Transitions.
- In SCXML: Add metadata to transitions.