Files
spring-state-machine-renderer/plan-extended-anaylis/implementation_details.md

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: Extract topics, groupId.
  • @RabbitListener: Extract queues, bindings (Exchange/RoutingKey).

From TypeDeclaration, we can check for:

  • @RestController, @Controller
  • @RequestMapping at 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:

  1. Find methods returning RouterFunction.
  2. Analyze the MethodInvocation chain (route, andRoute, nest).
  3. Extract the URI pattern and the HandlerFunction reference.
  4. If the handler is a method reference (handler::submitOrder), link it to the corresponding MethodDeclaration.

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:

  1. Find @RabbitListener.
  2. If it has bindings, drill down into @QueueBinding, @Queue, @Exchange to extract the topology.
  3. 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).

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.