Fix export metadata scoping and call-chain resolution gaps.

Correct machine-scope filtering for domain paths and Spring type erasure, skip phantom REST interfaces, resolve custom message wrapper events, scope JDT call-chain lookup per machine, and wire scan-time analysis indexes with JDT/heuristic parity tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 09:43:50 +02:00
parent 7a5fc60ace
commit 5d3333d494
54 changed files with 3479 additions and 908 deletions

View File

@@ -45,4 +45,44 @@ class MachineScopeFilterEntryPointTest {
.contains("POST /api/orders/pay", "POST /api/commands/{commandKey}")
.doesNotContain("POST /api/documents/submit");
}
@Test
void shouldKeepDedicatedOrderEndpointOnStandardOrderMachineInMultiModuleCodebase() {
EntryPoint orderPay = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/orders/pay")
.className("click.kamil.examples.statemachine.layered.web.OrderController")
.methodName("pay")
.metadata(Map.of("path", "/api/orders/pay"))
.build();
CodebaseContext context = new CodebaseContext();
List<EntryPoint> scoped = MachineScopeFilter.filterEntryPointsForMachine(
List.of(orderPay),
"click.kamil.examples.statemachine.layered.order.config.StandardOrderStateMachineConfiguration",
context);
assertThat(scoped).extracting(EntryPoint::getName)
.containsExactly("POST /api/orders/pay");
}
@Test
void shouldKeepOrdersPathOnNonDomainNamedMachineConfig() {
EntryPoint ordersSubmit = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/v2/orders/submit")
.className("click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl")
.methodName("submitOrder")
.metadata(Map.of("path", "/api/v2/orders/submit"))
.build();
CodebaseContext context = new CodebaseContext();
List<EntryPoint> scoped = MachineScopeFilter.filterEntryPointsForMachine(
List.of(ordersSubmit),
"click.kamil.examples.statemachine.inheritance.config.InheritanceStateMachineConfig",
context);
assertThat(scoped).extracting(EntryPoint::getName)
.containsExactly("POST /api/v2/orders/submit");
}
}

View File

@@ -1,6 +1,7 @@
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -9,6 +10,7 @@ import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@@ -165,4 +167,37 @@ public class HeuristicBeanResolutionEngineTest {
engine.isRoutedToCorrectMachine(chain, "com.example.DocumentStateMachineConfig", context),
"Ambiguous shared gateway should not attach to every machine");
}
@Test
void shouldAcceptTriggerWhenSpringErasureDiffersFromStringMachineTypes(@TempDir Path tempDir) throws Exception {
Files.writeString(tempDir.resolve("InheritanceStateMachineConfig.java"), """
package com.example;
@org.springframework.statemachine.config.EnableStateMachine
public class InheritanceStateMachineConfig
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
""");
Files.writeString(tempDir.resolve("ProcessingServiceImpl.java"), """
package com.example;
public class ProcessingServiceImpl {
public void doProcess() {}
}
""");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
CallChain triggerProbe = CallChain.builder()
.triggerPoint(TriggerPoint.builder()
.className("com.example.ProcessingServiceImpl")
.methodName("doProcess")
.event("INHERITED_SUBMIT")
.stateTypeFqn("java.lang.Object")
.eventTypeFqn("java.lang.Object")
.build())
.methodChain(List.of())
.build();
assertTrue(engine.isRoutedToCorrectMachine(
triggerProbe, "com.example.InheritanceStateMachineConfig", context));
}
}

View File

@@ -0,0 +1,108 @@
package click.kamil.springstatemachineexporter.analysis.index;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.analysis.service.ConstructorAnalyzer;
import click.kamil.springstatemachineexporter.analysis.service.ConstantExtractor;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class AnalysisPerformanceCacheTest {
@TempDir
Path tempDir;
CodebaseContext context;
ConstantExtractor constantExtractor;
@BeforeEach
void setUp() throws IOException {
context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
ConstantResolver constantResolver = context.getConstantResolver();
constantExtractor = new ConstantExtractor(context, constantResolver, mi -> null);
constantExtractor.setConstructorAnalyzer(new ConstructorAnalyzer(context, constantResolver));
}
@Test
void shouldPrecomputeIndexedFieldConstants() throws IOException {
writeJava("com/example/Payload.java", """
package com.example;
public class Payload {
private String event = "PAY";
public String getEvent() { return event; }
}
""");
scan();
List<String> precomputed = context.getIndexedFieldConstantsIndex()
.lookup("com.example.Payload", "getEvent")
.orElseThrow();
assertThat(precomputed).containsExactly("PAY");
List<String> first = constantExtractor.resolveMethodReturnConstant(
"com.example.Payload", "getEvent", 0, new HashSet<>(), null);
List<String> second = constantExtractor.resolveMethodReturnConstant(
"com.example.Payload", "getEvent", 0, new HashSet<>(), null);
assertThat(first).containsExactly("PAY");
assertThat(second).isEqualTo(first);
}
@Test
void findMethodDeclarationShouldBeMemoized() throws IOException {
writeJava("com/example/Demo.java", """
package com.example;
public class Demo {
public void run() {}
}
""");
scan();
TypeDeclaration type = context.getTypeDeclaration("com.example.Demo");
MethodDeclaration first = context.findMethodDeclaration(type, "run", true);
MethodDeclaration second = context.findMethodDeclaration(type, "run", true);
assertThat(first).isNotNull();
assertThat(second).isSameAs(first);
}
@Test
void shouldMemoizePolymorphicClassCompatibility() throws IOException {
writeJava("com/example/EventSource.java", """
package com.example;
public interface EventSource {}
""");
writeJava("com/example/PayEvent.java", """
package com.example;
public class PayEvent implements EventSource {}
""");
scan();
assertThat(context.areClassesPolymorphicallyCompatible(
"com.example.PayEvent", "com.example.EventSource")).isTrue();
assertThat(context.areClassesPolymorphicallyCompatible(
"com.example.PayEvent", "com.example.EventSource")).isTrue();
}
private void scan() throws IOException {
context.scan(tempDir);
}
private void writeJava(String relativePath, String source) throws IOException {
Path file = tempDir.resolve(relativePath);
Files.createDirectories(file.getParent());
Files.writeString(file, source);
}
}

View File

@@ -0,0 +1,97 @@
package click.kamil.springstatemachineexporter.analysis.index;
import click.kamil.springstatemachineexporter.analysis.pipeline.AccessorResolver;
import click.kamil.springstatemachineexporter.analysis.pipeline.ResolutionBudget;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.analysis.service.ConstructorAnalyzer;
import click.kamil.springstatemachineexporter.analysis.service.ConstantExtractor;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class GetterChainIndexTest {
@TempDir
Path tempDir;
CodebaseContext context;
AccessorResolver accessorResolver;
@BeforeEach
void setUp() throws IOException {
context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
ConstantResolver constantResolver = context.getConstantResolver();
ConstantExtractor constantExtractor = new ConstantExtractor(context, constantResolver, mi -> null);
ConstructorAnalyzer constructorAnalyzer = new ConstructorAnalyzer(context, constantResolver);
accessorResolver = new AccessorResolver(context, constantExtractor, constructorAnalyzer, constantResolver);
}
@Test
void shouldPrecomputeGetterChainEdgeWithFieldInitializerCic() throws IOException {
writeJava("com/example/Payload.java", """
package com.example;
public class Payload {
public String getType() { return "PAY"; }
}
""");
writeJava("com/example/Inner.java", """
package com.example;
public class Inner {
private final Payload payload = new Payload();
public Payload getPayload() { return payload; }
}
""");
scan();
GetterChainIndex index = context.getGetterChainIndex();
assertThat(index.edgeCount()).isGreaterThan(0);
GetterChainEdge edge = index.lookup("com.example.Inner", "getPayload").orElseThrow();
assertThat(edge.nextTypeFqn()).contains("Payload");
assertThat(edge.fieldInitializerCic()).isNotNull();
AccessorResolver.GetterChainStep step = accessorResolver.resolveGetterChainStep(
"com.example.Inner", "getPayload", ResolutionBudget.defaults());
assertThat(step).isNotNull();
assertThat(step.typeFqn()).isEqualTo(edge.nextTypeFqn());
assertThat(step.cic()).isSameAs(edge.fieldInitializerCic());
}
@Test
void getTypeDeclarationShouldUseFlatIndex() throws IOException {
writeJava("com/example/Demo.java", """
package com.example;
public class Demo {
public String getValue() { return "X"; }
}
""");
scan();
TypeDeclaration first = context.getTypeDeclaration("com.example.Demo");
TypeDeclaration second = context.getTypeDeclaration("com.example.Demo");
assertThat(first).isNotNull();
assertThat(second).isSameAs(first);
}
private void scan() throws IOException {
context.scan(tempDir);
}
private void writeJava(String relativePath, String source) throws IOException {
Path file = tempDir.resolve(relativePath);
Files.createDirectories(file.getParent());
Files.writeString(file, source);
}
}

View File

@@ -0,0 +1,238 @@
package click.kamil.springstatemachineexporter.analysis.index;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.analysis.service.ConstructorAnalyzer;
import click.kamil.springstatemachineexporter.analysis.service.ConstantExtractor;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class PolymorphicAccessorIndexTest {
@TempDir
Path tempDir;
CodebaseContext context;
ConstantExtractor constantExtractor;
@BeforeEach
void setUp() {
context = new CodebaseContext();
constantExtractor = new ConstantExtractor(
context,
new ConstantResolver(),
mi -> null);
constantExtractor.setConstructorAnalyzer(new ConstructorAnalyzer(context, context.getConstantResolver()));
}
@Test
void shouldPrecomputeWidenedConstantsForThreeEventImplementations() throws IOException {
writeJava("com/example/EventSource.java", """
package com.example;
public interface EventSource {
String getEvent();
}
""");
writeJava("com/example/PayEvent.java", """
package com.example;
public class PayEvent implements EventSource {
public String getEvent() { return "PAY"; }
}
""");
writeJava("com/example/ShipEvent.java", """
package com.example;
public class ShipEvent implements EventSource {
public String getEvent() { return "SHIP"; }
}
""");
writeJava("com/example/CancelEvent.java", """
package com.example;
public class CancelEvent implements EventSource {
public String getEvent() { return "CANCEL"; }
}
""");
scan();
PolymorphicAccessorIndex index = context.getPolymorphicAccessorIndex();
assertThat(index.entryCount()).isGreaterThan(0);
List<String> precomputed = index.widenedReturnConstants("com.example.EventSource", "getEvent")
.orElseThrow();
assertThat(precomputed).containsExactlyInAnyOrder("PAY", "SHIP", "CANCEL");
List<String> resolved = constantExtractor.resolveMethodReturnConstant(
"com.example.EventSource", "getEvent", 0, new HashSet<>(), null);
assertThat(resolved).containsExactlyInAnyOrder("PAY", "SHIP", "CANCEL");
}
@Test
void shouldIncludeDefaultMethodAndConcreteOverrides() throws IOException {
writeJava("com/example/EventSource.java", """
package com.example;
public interface EventSource {
default String getEvent() { return "DEFAULT"; }
}
""");
writeJava("com/example/PayEvent.java", """
package com.example;
public class PayEvent implements EventSource {
public String getEvent() { return "PAY"; }
}
""");
writeJava("com/example/ShipEvent.java", """
package com.example;
public class ShipEvent implements EventSource {
public String getEvent() { return "SHIP"; }
}
""");
writeJava("com/example/CancelEvent.java", """
package com.example;
public class CancelEvent implements EventSource {
public String getEvent() { return "CANCEL"; }
}
""");
scan();
List<String> precomputed = context.getPolymorphicAccessorIndex()
.widenedReturnConstants("com.example.EventSource", "getEvent")
.orElseThrow();
assertThat(precomputed).containsExactlyInAnyOrder("DEFAULT", "PAY", "SHIP", "CANCEL");
}
@Nested
class AbstractStateMachineInheritance {
@Test
void shouldResolveEventsFromThreeConcreteConfigsExtendingAbstractBase() throws IOException {
writeJava("com/example/OrderState.java", """
package com.example;
public enum OrderState { CREATED, PAID, SHIPPED, CANCELLED }
""");
writeJava("com/example/OrderEvent.java", """
package com.example;
public enum OrderEvent { PAY, SHIP, CANCEL }
""");
writeJava("com/example/EventSource.java", """
package com.example;
public interface EventSource {
OrderEvent getEvent();
}
""");
writeJava("com/example/PayEvent.java", """
package com.example;
public class PayEvent implements EventSource {
public OrderEvent getEvent() { return OrderEvent.PAY; }
}
""");
writeJava("com/example/ShipEvent.java", """
package com.example;
public class ShipEvent implements EventSource {
public OrderEvent getEvent() { return OrderEvent.SHIP; }
}
""");
writeJava("com/example/CancelEvent.java", """
package com.example;
public class CancelEvent implements EventSource {
public OrderEvent getEvent() { return OrderEvent.CANCEL; }
}
""");
writeJava("com/example/BaseOrderStateMachineConfig.java", """
package com.example;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public abstract class BaseOrderStateMachineConfig
extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {
protected void registerTransition(
StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions,
EventSource source) {
transitions.withExternal()
.source(OrderState.CREATED)
.target(OrderState.PAID)
.event(source.getEvent());
}
}
""");
writeJava("com/example/PayOrderStateMachineConfig.java", """
package com.example;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public class PayOrderStateMachineConfig extends BaseOrderStateMachineConfig {
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
registerTransition(transitions, new PayEvent());
}
}
""");
writeJava("com/example/ShipOrderStateMachineConfig.java", """
package com.example;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public class ShipOrderStateMachineConfig extends BaseOrderStateMachineConfig {
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
registerTransition(transitions, new ShipEvent());
}
}
""");
writeJava("com/example/CancelOrderStateMachineConfig.java", """
package com.example;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public class CancelOrderStateMachineConfig extends BaseOrderStateMachineConfig {
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
registerTransition(transitions, new CancelEvent());
}
}
""");
scan();
List<String> widened = context.getPolymorphicAccessorIndex()
.widenedReturnConstants("com.example.EventSource", "getEvent")
.orElseThrow();
assertThat(widened).containsExactlyInAnyOrder("OrderEvent.PAY", "OrderEvent.SHIP", "OrderEvent.CANCEL");
List<String> resolved = constantExtractor.resolveMethodReturnConstant(
"com.example.EventSource", "getEvent", 0, new HashSet<>(), null);
assertThat(resolved).containsExactlyInAnyOrder("OrderEvent.PAY", "OrderEvent.SHIP", "OrderEvent.CANCEL");
}
}
@Test
void getImplementationsShouldBeMemoized() throws IOException {
writeJava("com/example/EventSource.java", """
package com.example;
public interface EventSource {}
""");
writeJava("com/example/PayEvent.java", """
package com.example;
public class PayEvent implements EventSource {}
""");
scan();
List<String> first = context.getImplementations("com.example.EventSource");
List<String> second = context.getImplementations("com.example.EventSource");
assertThat(second).isSameAs(first);
}
private void scan() throws IOException {
context.scan(tempDir);
}
private void writeJava(String relativePath, String source) throws IOException {
Path file = tempDir.resolve(relativePath);
Files.createDirectories(file.getParent());
Files.writeString(file, source);
}
}

View File

@@ -97,7 +97,7 @@ class AnalysisCacheCorrectnessTest {
List<CallChain> first = intelligence.findCallChains(entryPoints, triggers);
List<CallChain> second = intelligence.findCallChains(entryPoints, triggers);
assertThat(second).isSameAs(first);
assertThat(second).isEqualTo(first);
}
/**

View File

@@ -20,8 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* <p>Each test embeds a miniature codebase as a string so humans and agents can see the full
* REST → dispatcher → sendEvent pipeline without opening another module.
*
* <p>DTO setter/getter propagation is covered by {@code HeuristicCallGraphEngineTypeTest}.
* Cross-hop routing helpers that return computed strings are covered by literal-forwarding tests above.
* <p>Cross-hop routing helpers and DTO constructor→getter propagation are covered by Tier 1 tests below.
*/
class CentralDispatcherResolutionTest {
@@ -418,6 +417,105 @@ class CentralDispatcherResolutionTest {
"OrderEvent.PAY");
}
/**
* Tier 1 — field-injected routing helper returns string literals consumed by central dispatcher.
*
* <pre>
* pay() → gateway.routeAndPay() → dispatch("ORDER", routingHelper.payAction()) → OrderEvent.PAY
* ship() → gateway.routeAndShip() → dispatch("ORDER", routingHelper.shipAction()) → OrderEvent.SHIP
* </pre>
*/
@Test
void fieldWiredRoutingHelperShouldForwardLiteralsToCentralDispatcher(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class ApiController {
CommandGateway gateway;
public void pay() { gateway.routeAndPay(); }
public void ship() { gateway.routeAndShip(); }
}
class CommandGateway {
OrderRoutingHelper routingHelper;
CentralDispatcher dispatcher;
void routeAndPay() { dispatcher.dispatch("ORDER", routingHelper.payAction()); }
void routeAndShip() { dispatcher.dispatch("ORDER", routingHelper.shipAction()); }
}
class OrderRoutingHelper {
String payAction() { return "PAY"; }
String shipAction() { return "SHIP"; }
}
class CentralDispatcher {
void dispatch(String domain, String action) {
StateMachine sm = new StateMachine();
sm.sendEvent(OrderEvent.valueOf(action));
}
}
enum OrderEvent { PAY, SHIP }
class StateMachine { public void sendEvent(OrderEvent e) {} }
class OrderStateMachineConfig {}
""";
CodebaseContext context = scanSource(source, tempDir);
CallChain payChain = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE);
CallChain shipChain = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE);
assertPolyEvents(payChain, "OrderEvent.PAY");
assertPolyEvents(shipChain, "OrderEvent.SHIP");
assertLinkedEvent(
linkEndpointToTransition(payChain, MACHINE_CONFIG,
transition("NEW", "PAID", "OrderEvent.PAY"),
transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
"OrderEvent.PAY");
assertLinkedEvent(
linkEndpointToTransition(shipChain, MACHINE_CONFIG,
transition("NEW", "PAID", "OrderEvent.PAY"),
transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
"OrderEvent.SHIP");
}
/**
* Tier 1 — DTO field set via constructor in controller, read via getter in gateway.
*
* <pre>
* submit() → gateway.submit(new OrderRequest(OrderEvent.SUBMIT)) → req.getType() → OrderEvent.SUBMIT
* </pre>
*/
@Test
void dtoCrossHopFieldFlowShouldReachCentralDispatcher(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class ApiController {
CommandGateway gateway;
public void submit() { gateway.submit(new OrderRequest(OrderEvent.SUBMIT)); }
}
class CommandGateway {
void submit(OrderRequest req) {
StateMachine sm = new StateMachine();
sm.sendEvent(req.getType());
}
}
class OrderRequest {
private final OrderEvent type;
OrderRequest(OrderEvent type) { this.type = type; }
OrderEvent getType() { return type; }
}
enum OrderEvent { SUBMIT, PAY }
class StateMachine { public void sendEvent(OrderEvent e) {} }
class OrderStateMachineConfig {}
""";
CodebaseContext context = scanSource(source, tempDir);
CallChain submitChain = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE);
assertPolyEvents(submitChain, "OrderEvent.SUBMIT");
assertLinkedEvent(
linkEndpointToTransition(submitChain, MACHINE_CONFIG,
transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
transition("SUBMITTED", "PAID", "OrderEvent.PAY")),
"OrderEvent.SUBMIT");
}
/**
* Chained field-backed trivial getters ({@code return this.field}) through a central dispatcher.
*/

View File

@@ -26,6 +26,11 @@ final class CentralDispatcherTestSupport {
private CentralDispatcherTestSupport() {
}
enum EngineKind {
HEURISTIC,
JDT
}
static CodebaseContext scanSource(String source, Path tempDir) throws Exception {
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
@@ -38,7 +43,18 @@ final class CentralDispatcherTestSupport {
String controllerClass,
String controllerMethod,
String stateMachineClass) {
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
return resolveChain(context, controllerClass, controllerMethod, stateMachineClass, EngineKind.HEURISTIC);
}
static CallChain resolveChain(
CodebaseContext context,
String controllerClass,
String controllerMethod,
String stateMachineClass,
EngineKind engineKind) {
AbstractCallGraphEngine engine = engineKind == EngineKind.JDT
? new JdtCallGraphEngine(context, null)
: new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className(controllerClass)
.methodName(controllerMethod)
@@ -50,7 +66,8 @@ final class CentralDispatcherTestSupport {
.build();
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains)
.as("expected one call chain from %s.%s to %s.sendEvent", controllerClass, controllerMethod, stateMachineClass)
.as("expected one call chain from %s.%s to %s.sendEvent via %s",
controllerClass, controllerMethod, stateMachineClass, engineKind)
.hasSize(1);
return chains.get(0);
}

View File

@@ -0,0 +1,63 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import java.nio.file.Path;
import java.util.List;
import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.EngineKind;
import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.assertPolyEvents;
import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.scanSource;
import static org.assertj.core.api.Assertions.assertThat;
class CustomMessageTriggerResolutionTest {
private static final String SOURCE = """
package com.example;
enum OrderEvent { PROCESS, COMPLETE }
class CustomStateMachineMessage<T, P> {
CustomStateMachineMessage(T payload, P customPayload) {}
}
class StateMachineService {
void sendCustomMessage(CustomStateMachineMessage<OrderEvent, String> customMessage) {}
}
class RabbitOrderListener {
private StateMachineService stateMachineService;
void handleCustomTransition(String payload) {
CustomStateMachineMessage<OrderEvent, String> customMessage =
new CustomStateMachineMessage<>(OrderEvent.PROCESS, "payload: " + payload);
stateMachineService.sendCustomMessage(customMessage);
}
}
""";
@ParameterizedTest
@EnumSource(EngineKind.class)
void customStateMachineMessageShouldExtractEventFromConstructor(EngineKind engineKind, @TempDir Path tempDir)
throws Exception {
var context = scanSource(SOURCE, tempDir);
AbstractCallGraphEngine engine = engineKind == EngineKind.JDT
? new JdtCallGraphEngine(context, null)
: new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.RabbitOrderListener")
.methodName("handleCustomTransition")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.StateMachineService")
.methodName("sendCustomMessage")
.event("customMessage")
.build();
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
assertPolyEvents(chains.get(0), "OrderEvent.PROCESS");
}
}

View File

@@ -0,0 +1,60 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.exporter.JsonExporter;
import click.kamil.springstatemachineexporter.service.ExportService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* End-to-end verification for {@code state_machines/inheritance_sample}:
* interface-mapped REST → injected service impl → {@code sendEvent}.
*/
class InheritanceSampleChainTest {
private static Path findProjectRoot() {
Path current = Path.of(".").toAbsolutePath();
while (current != null && !Files.exists(current.resolve("settings.gradle"))) {
current = current.getParent();
}
return current;
}
@Test
void exportShouldIncludeTriggerAndCallChain(@TempDir Path tempDir) throws Exception {
Path sampleRoot = findProjectRoot().resolve("state_machines/inheritance_sample");
ExportService exportService = new ExportService(List.of(new JsonExporter()));
exportService.runExporter(sampleRoot, tempDir, List.of("json"), true, List.of(), null, null,
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
ObjectMapper mapper = new ObjectMapper();
Path jsonFile;
try (var stream = Files.walk(tempDir)) {
jsonFile = stream.filter(p -> p.getFileName().toString().contains("InheritanceStateMachineConfig")
&& p.getFileName().toString().endsWith(".json")).findFirst().orElseThrow();
}
JsonNode metadata = mapper.readTree(jsonFile.toFile()).path("metadata");
assertThat(metadata.path("triggers").size()).as("triggers").isGreaterThan(0);
assertThat(metadata.path("callChains").size()).as("callChains").isGreaterThan(0);
JsonNode chain = metadata.path("callChains").get(0);
assertThat(chain.path("entryPoint").path("name").asText())
.isEqualTo("POST /api/v2/orders/submit");
assertThat(chain.path("methodChain").toString())
.contains("OrderControllerImpl.submitOrder")
.contains("ProcessingServiceImpl.doProcess");
assertThat(chain.path("triggerPoint").path("event").asText())
.isIn("INHERITED_SUBMIT", "java.lang.String.INHERITED_SUBMIT", "String.INHERITED_SUBMIT");
assertThat(chain.path("matchedTransitions").size()).isEqualTo(1);
}
}

View File

@@ -0,0 +1,148 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import java.util.List;
import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.EngineKind;
import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.*;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Ensures the production {@link JdtCallGraphEngine} resolves single-dispatcher endpoints the same
* way as {@link HeuristicCallGraphEngine} for Tier-1 central-dispatcher patterns.
*/
class JdtCentralDispatcherParityTest {
private static final String CONTROLLER = "com.example.ApiController";
private static final String STATE_MACHINE = "com.example.StateMachine";
private static final String MACHINE_CONFIG = "com.example.OrderStateMachineConfig";
@Test
void jdtShouldMatchHeuristicForFieldWiredRoutingHelper(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class ApiController {
CommandGateway gateway;
public void pay() { gateway.routeAndPay(); }
public void ship() { gateway.routeAndShip(); }
}
class CommandGateway {
OrderRoutingHelper routingHelper;
CentralDispatcher dispatcher;
void routeAndPay() { dispatcher.dispatch("ORDER", routingHelper.payAction()); }
void routeAndShip() { dispatcher.dispatch("ORDER", routingHelper.shipAction()); }
}
class OrderRoutingHelper {
String payAction() { return "PAY"; }
String shipAction() { return "SHIP"; }
}
class CentralDispatcher {
void dispatch(String domain, String action) {
StateMachine sm = new StateMachine();
sm.sendEvent(OrderEvent.valueOf(action));
}
}
enum OrderEvent { PAY, SHIP }
class StateMachine { public void sendEvent(OrderEvent e) {} }
class OrderStateMachineConfig {}
""";
CodebaseContext context = scanSource(source, tempDir);
CallChain heuristicPay = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE, EngineKind.HEURISTIC);
CallChain jdtPay = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE, EngineKind.JDT);
CallChain heuristicShip = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE, EngineKind.HEURISTIC);
CallChain jdtShip = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE, EngineKind.JDT);
assertThat(jdtPay.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrderElementsOf(heuristicPay.getTriggerPoint().getPolymorphicEvents());
assertThat(jdtShip.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrderElementsOf(heuristicShip.getTriggerPoint().getPolymorphicEvents());
MatchedTransition heuristicLink = linkEndpointToTransition(heuristicPay, MACHINE_CONFIG,
transition("NEW", "PAID", "OrderEvent.PAY"),
transition("PAID", "SHIPPED", "OrderEvent.SHIP"));
MatchedTransition jdtLink = linkEndpointToTransition(jdtPay, MACHINE_CONFIG,
transition("NEW", "PAID", "OrderEvent.PAY"),
transition("PAID", "SHIPPED", "OrderEvent.SHIP"));
assertThat(jdtLink.getEvent()).isEqualTo(heuristicLink.getEvent());
}
@Test
void jdtShouldMatchHeuristicForDtoCrossHop(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class ApiController {
CommandGateway gateway;
public void submit() { gateway.submit(new OrderRequest(OrderEvent.SUBMIT)); }
}
class CommandGateway {
void submit(OrderRequest req) {
StateMachine sm = new StateMachine();
sm.sendEvent(req.getType());
}
}
class OrderRequest {
private final OrderEvent type;
OrderRequest(OrderEvent type) { this.type = type; }
OrderEvent getType() { return type; }
}
enum OrderEvent { SUBMIT, PAY }
class StateMachine { public void sendEvent(OrderEvent e) {} }
class OrderStateMachineConfig {}
""";
CodebaseContext context = scanSource(source, tempDir);
CallChain heuristic = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.HEURISTIC);
CallChain jdt = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.JDT);
assertThat(jdt.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrderElementsOf(heuristic.getTriggerPoint().getPolymorphicEvents());
MatchedTransition heuristicLink = linkEndpointToTransition(heuristic, MACHINE_CONFIG,
transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
transition("SUBMITTED", "PAID", "OrderEvent.PAY"));
MatchedTransition jdtLink = linkEndpointToTransition(jdt, MACHINE_CONFIG,
transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
transition("SUBMITTED", "PAID", "OrderEvent.PAY"));
assertThat(jdtLink.getEvent()).isEqualTo(heuristicLink.getEvent());
}
@Test
void jdtShouldMatchHeuristicForSingleDispatcherNamedMethods(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class ApiController {
CentralDispatcher dispatcher;
public void pay() { dispatcher.orderPay(); }
public void ship() { dispatcher.orderShip(); }
}
class CentralDispatcher {
public void orderPay() {
StateMachine sm = new StateMachine();
sm.sendEvent(OrderEvent.PAY);
}
public void orderShip() {
StateMachine sm = new StateMachine();
sm.sendEvent(OrderEvent.SHIP);
}
}
enum OrderEvent { PAY, SHIP }
class StateMachine { public void sendEvent(OrderEvent e) {} }
class OrderStateMachineConfig {}
""";
CodebaseContext context = scanSource(source, tempDir);
for (String method : List.of("pay", "ship")) {
CallChain heuristic = resolveChain(context, CONTROLLER, method, STATE_MACHINE, EngineKind.HEURISTIC);
CallChain jdt = resolveChain(context, CONTROLLER, method, STATE_MACHINE, EngineKind.JDT);
assertThat(jdt.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrderElementsOf(heuristic.getTriggerPoint().getPolymorphicEvents());
}
}
}

View File

@@ -71,4 +71,40 @@ class SpringMvcDetectorTest {
assertThat(entryPoints).anyMatch(ep ->
"POST /api/orders/pay".equals(ep.getName()) && "pay".equals(ep.getMethodName()));
}
@Test
void shouldDetectInterfaceMappedRestEndpointOnImplOnly(@TempDir Path tempDir) throws Exception {
String apiSource = """
package com.example;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/api/v2/orders")
public interface OrderApi {
@PostMapping("/submit")
void submitOrder();
}
""";
String implSource = """
package com.example;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderControllerImpl implements OrderApi {
@Override
public void submitOrder() {}
}
""";
Files.writeString(tempDir.resolve("OrderApi.java"), apiSource);
Files.writeString(tempDir.resolve("OrderControllerImpl.java"), implSource);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
SpringMvcDetector detector = new SpringMvcDetector(context, context.getConstantResolver());
org.eclipse.jdt.core.dom.TypeDeclaration impl = context.getTypeDeclaration("com.example.OrderControllerImpl");
List<EntryPoint> entryPoints = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) impl.getRoot());
assertThat(entryPoints).hasSize(1);
assertThat(entryPoints.get(0).getName()).isEqualTo("POST /api/v2/orders/submit");
assertThat(entryPoints.get(0).getClassName()).isEqualTo("com.example.OrderControllerImpl");
}
}

View File

@@ -147,120 +147,6 @@
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/submit",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.submitDocument", "click.kamil.enterprise.web.StateMachineDispatcher.submitDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 44,
"polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT",
"targetState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/approve",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.approveDocument", "click.kamil.enterprise.web.StateMachineDispatcher.approveDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 51,
"polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
"targetState" : "click.kamil.enterprise.machines.document.DocumentState.APPROVED",
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/reject",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.rejectDocument", "click.kamil.enterprise.web.StateMachineDispatcher.rejectDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 58,
"polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.REJECT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
"targetState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT",
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",

View File

@@ -182,8 +182,112 @@
"stateTypeFqn" : "String",
"eventTypeFqn" : "String",
"metadata" : {
"triggers" : [ ],
"triggers" : [ {
"event" : "java.lang.String.AUDIT_EVENT",
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
"methodName" : "preHandle",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "java.lang.String.PLACE_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
"methodName" : "place",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 16,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "java.lang.String.CANCEL_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
"methodName" : "cancel",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 21,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "java.lang.String.PAY_ORDER",
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
"methodName" : "processPayment",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "java.lang.String.SHIP_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
"methodName" : "onShippingReady",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "java.lang.String.RETURN_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
"methodName" : "onReturn",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/enterprise/orders/place",
"className" : "click.kamil.examples.enterprise.api.OrderController",
"methodName" : "placeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderController.java",
"metadata" : {
"path" : "/api/enterprise/orders/place",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/enterprise/orders/{id}/cancel",
"className" : "click.kamil.examples.enterprise.api.OrderController",
"methodName" : "cancelOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderController.java",
"metadata" : {
"path" : "/api/enterprise/orders/{id}/cancel",
"verb" : "POST"
},
"parameters" : [ {
"name" : "id",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "CUSTOM",
"name" : "INTERCEPTOR: SecurityInterceptor.preHandle",
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
@@ -239,7 +343,222 @@
"annotations" : [ ]
} ]
} ],
"callChains" : [ ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/enterprise/orders/place",
"className" : "click.kamil.examples.enterprise.api.OrderController",
"methodName" : "placeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderController.java",
"metadata" : {
"path" : "/api/enterprise/orders/place",
"verb" : "POST"
},
"parameters" : [ ]
},
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ],
"triggerPoint" : {
"event" : "java.lang.String.PLACE_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
"methodName" : "place",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 16,
"polymorphicEvents" : [ "java.lang.String.PLACE_ORDER" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.NEW",
"targetState" : "String.CHECK_AVAILABILITY",
"event" : "String.PLACE_ORDER"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/enterprise/orders/{id}/cancel",
"className" : "click.kamil.examples.enterprise.api.OrderController",
"methodName" : "cancelOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderController.java",
"metadata" : {
"path" : "/api/enterprise/orders/{id}/cancel",
"verb" : "POST"
},
"parameters" : [ {
"name" : "id",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
},
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ],
"triggerPoint" : {
"event" : "java.lang.String.CANCEL_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
"methodName" : "cancel",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 21,
"polymorphicEvents" : [ "java.lang.String.CANCEL_ORDER" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.PAID",
"targetState" : "String.CANCELLED",
"event" : "String.CANCEL_ORDER"
} ]
}, {
"entryPoint" : {
"type" : "CUSTOM",
"name" : "INTERCEPTOR: SecurityInterceptor.preHandle",
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
"methodName" : "preHandle",
"sourceFile" : null,
"metadata" : {
"interceptorType" : "Spring MVC Interceptor"
},
"parameters" : [ ]
},
"methodChain" : [ "click.kamil.examples.enterprise.api.SecurityInterceptor.preHandle" ],
"triggerPoint" : {
"event" : "java.lang.String.AUDIT_EVENT",
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
"methodName" : "preHandle",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/enterprise/payments",
"className" : "click.kamil.examples.enterprise.api.ReactivePaymentController",
"methodName" : "pay",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/ReactivePaymentController.java",
"metadata" : {
"path" : "/api/enterprise/payments",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
},
"methodChain" : [ "click.kamil.examples.enterprise.api.ReactivePaymentController.pay", "click.kamil.examples.enterprise.service.ReactivePaymentService.processPayment" ],
"triggerPoint" : {
"event" : "java.lang.String.PAY_ORDER",
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
"methodName" : "processPayment",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : [ "java.lang.String.PAY_ORDER" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.PENDING_PAYMENT",
"targetState" : "String.PAID",
"event" : "String.PAY_ORDER"
} ]
}, {
"entryPoint" : {
"type" : "JMS",
"name" : "JMS: shipping.queue",
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
"methodName" : "onShippingReady",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "shipping.queue"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ShippingJmsListener.onShippingReady" ],
"triggerPoint" : {
"event" : "java.lang.String.SHIP_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
"methodName" : "onShippingReady",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.PAID",
"targetState" : "String.SHIPPED",
"event" : "String.SHIP_ORDER"
} ]
}, {
"entryPoint" : {
"type" : "RABBIT",
"name" : "RABBIT: returns.queue",
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
"methodName" : "onReturn",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
"metadata" : {
"protocol" : "RABBIT",
"destination" : "returns.queue"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener.onReturn" ],
"triggerPoint" : {
"event" : "java.lang.String.RETURN_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
"methodName" : "onReturn",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.DELIVERED",
"targetState" : "String.RETURNED",
"event" : "String.RETURN_ORDER"
} ]
} ],
"properties" : {
"default" : { }
}

View File

@@ -182,40 +182,6 @@
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/resume",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "resumeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.resumeOrder", "click.kamil.examples.statemachine.extended.service.OrderService.resumeOrder" ],
"triggerPoint" : {
"event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
"methodName" : "resumeOrder",
"sourceFile" : null,
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 29,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : "orderId",
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/payment/{id}/capture",

View File

@@ -182,40 +182,6 @@
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/resume",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "resumeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.resumeOrder", "click.kamil.examples.statemachine.extended.service.OrderService.resumeOrder" ],
"triggerPoint" : {
"event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
"methodName" : "resumeOrder",
"sourceFile" : null,
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 29,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : "orderId",
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/payment/{id}/capture",

View File

@@ -32,9 +32,67 @@
"stateTypeFqn" : "String",
"eventTypeFqn" : "String",
"metadata" : {
"triggers" : [ ],
"entryPoints" : [ ],
"callChains" : [ ],
"triggers" : [ {
"event" : "java.lang.String.INHERITED_SUBMIT",
"className" : "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl",
"methodName" : "doProcess",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 15,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/v2/orders/submit",
"className" : "click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl",
"methodName" : "submitOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/api/OrderControllerImpl.java",
"metadata" : {
"path" : "/api/v2/orders/submit",
"verb" : "POST"
},
"parameters" : [ ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/v2/orders/submit",
"className" : "click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl",
"methodName" : "submitOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/api/OrderControllerImpl.java",
"metadata" : {
"path" : "/api/v2/orders/submit",
"verb" : "POST"
},
"parameters" : [ ]
},
"methodChain" : [ "click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl.submitOrder", "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl.doProcess" ],
"triggerPoint" : {
"event" : "java.lang.String.INHERITED_SUBMIT",
"className" : "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl",
"methodName" : "doProcess",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 15,
"polymorphicEvents" : [ "java.lang.String.INHERITED_SUBMIT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.START",
"targetState" : "String.WORKING",
"event" : "String.INHERITED_SUBMIT"
} ]
} ],
"properties" : {
"default" : { }
}

View File

@@ -60,8 +60,61 @@
"eventTypeFqn" : "String",
"metadata" : {
"triggers" : [ ],
"entryPoints" : [ ],
"callChains" : [ ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /maven/orders/submit",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"metadata" : {
"path" : "/maven/orders/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /maven/orders/submit",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"metadata" : {
"path" : "/maven/orders/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
},
"methodChain" : [ "click.kamil.maven.core.MavenOrderStateMachine.OrderController.submitOrder" ],
"triggerPoint" : {
"event" : "java.lang.String.SUBMIT",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 40,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.INIT",
"targetState" : "String.BUSY",
"event" : "String.SUBMIT"
} ]
} ],
"properties" : {
"default" : { }
}

View File

@@ -52,7 +52,7 @@
"rawName" : "\"BUSY\"",
"fullIdentifier" : "BUSY"
} ],
"event" : "ORDER_EVENT",
"event" : "SUBMIT",
"guard" : null,
"actions" : [ {
"expression" : "loggingAction()",
@@ -61,6 +61,20 @@
"lineNumber" : 23
} ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "\"BUSY\"",
"fullIdentifier" : "BUSY"
} ],
"targetStates" : [ {
"rawName" : "\"DONE\"",
"fullIdentifier" : "DONE"
} ],
"event" : "ORDER_EVENT",
"guard" : null,
"actions" : [ ],
"order" : null
} ],
"endStates" : [ "DONE" ]
}

View File

@@ -59,7 +59,20 @@
"stateTypeFqn" : "String",
"eventTypeFqn" : "String",
"metadata" : {
"triggers" : [ ],
"triggers" : [ {
"event" : "java.lang.String.SUBMIT",
"className" : "click.kamil.multi.core.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /orders/submit",
@@ -75,23 +88,46 @@
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
}, {
"type" : "REST",
"name" : "POST /orders/submit",
"className" : "click.kamil.multi.api.OrderApi",
"methodName" : "submitOrder",
"sourceFile" : "api-module/src/main/java/click/kamil/multi/api/OrderApi.java",
"metadata" : {
"path" : "/orders/submit",
"verb" : "POST"
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /orders/submit",
"className" : "click.kamil.multi.core.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
"metadata" : {
"path" : "/orders/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ "RequestBody" ]
"methodChain" : [ "click.kamil.multi.core.OrderController.submitOrder" ],
"triggerPoint" : {
"event" : "java.lang.String.SUBMIT",
"className" : "click.kamil.multi.core.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.NEW",
"targetState" : "String.PROCESSING",
"event" : "String.SUBMIT"
} ]
} ],
"callChains" : [ ],
"properties" : {
"default" : { }
}

View File

@@ -124,82 +124,6 @@
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/pay",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.payOrder", "click.kamil.enterprise.web.StateMachineDispatcher.payOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 30,
"polymorphicEvents" : [ "click.kamil.enterprise.machines.order.OrderEvent.PAY" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "click.kamil.enterprise.machines.order.OrderState.NEW",
"targetState" : "click.kamil.enterprise.machines.order.OrderState.PENDING",
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/ship",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.shipOrder", "click.kamil.enterprise.web.StateMachineDispatcher.shipOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 37,
"polymorphicEvents" : [ "click.kamil.enterprise.machines.order.OrderEvent.SHIP" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "click.kamil.enterprise.machines.order.OrderState.PENDING",
"targetState" : "click.kamil.enterprise.machines.order.OrderState.SHIPPED",
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",

View File

@@ -87,6 +87,39 @@
"ambiguous" : false
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/documents/submit",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "submit",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/submit",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/approve",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "approve",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/approve",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/reject",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "reject",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/reject",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/commands/{commandKey}",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",

View File

@@ -87,6 +87,83 @@
"ambiguous" : false
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/pay",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "pay",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/ship",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "ship",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/cancel",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "cancel",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/cancel",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/string-dispatch/orders/pay",
"className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController",
"methodName" : "payWithStringKey",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java",
"metadata" : {
"path" : "/api/string-dispatch/orders/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/string-dispatch/orders/ship",
"className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController",
"methodName" : "shipWithStringKey",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java",
"metadata" : {
"path" : "/api/string-dispatch/orders/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/rich/pay",
"className" : "click.kamil.examples.statemachine.layered.web.RichOrderController",
"methodName" : "payViaRichEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java",
"metadata" : {
"path" : "/api/orders/rich/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/rich/ship",
"className" : "click.kamil.examples.statemachine.layered.web.RichOrderController",
"methodName" : "shipViaRichEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java",
"metadata" : {
"path" : "/api/orders/rich/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/commands/{commandKey}",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",

View File

@@ -130,6 +130,50 @@
"ambiguous" : false
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/process",
"className" : "click.kamil.web.OrderController",
"methodName" : "processOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/process",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "completeOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/complete",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/cancel",
"className" : "click.kamil.web.OrderController",
"methodName" : "cancelOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/cancel",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/functional-complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "functionalCompleteOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/functional-complete",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener",
@@ -342,13 +386,17 @@
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 78,
"polymorphicEvents" : [ ],
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.PROCESS" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
"matchedTransitions" : [ {
"sourceState" : "click.kamil.domain.OrderState.NEW",
"targetState" : "click.kamil.domain.OrderState.PROCESSING",
"event" : "click.kamil.domain.OrderEvent.PROCESS"
} ]
}, {
"entryPoint" : {
"type" : "JMS",

View File

@@ -117,78 +117,6 @@
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/verify",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/verify",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.verifyUser", "click.kamil.enterprise.web.StateMachineDispatcher.verifyUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 65,
"polymorphicEvents" : [ "click.kamil.enterprise.machines.user.UserEvent.VERIFY" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED",
"targetState" : "click.kamil.enterprise.machines.user.UserState.VERIFIED",
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/suspend",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/suspend",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.suspendUser", "click.kamil.enterprise.web.StateMachineDispatcher.suspendUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.SUSPEND",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 72,
"polymorphicEvents" : [ "click.kamil.enterprise.machines.user.UserEvent.SUSPEND" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",