Link REST endpoints via source-proven call-graph scoping and branch expansion.

Scope machines from call-chain type evidence, expand path bindings from switch/if literals, unify external trigger policy, add linkResolution export metadata, and built-in trigger detection without hints.json.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 17:46:14 +02:00
parent 1bf75c8a34
commit f8f64487d1
57 changed files with 3202 additions and 297 deletions

View File

@@ -1,9 +1,14 @@
package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
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 java.util.Map;
@@ -12,77 +17,121 @@ import static org.assertj.core.api.Assertions.assertThat;
class MachineScopeFilterEntryPointTest {
@Test
void shouldKeepOrderEndpointsOnOrderMachineOnly() {
void shouldDeriveScopedEntryPointsFromCallChainsAndKeepGenericPathVariables() {
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"))
.name("POST /api/machine/order/pay")
.className("com.example.StateMachineController")
.methodName("payOrder")
.build();
EntryPoint documentSubmit = EntryPoint.builder()
EntryPoint genericTransition = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/documents/submit")
.className("click.kamil.examples.statemachine.layered.web.DocumentController")
.methodName("submit")
.metadata(Map.of("path", "/api/documents/submit"))
.build();
EntryPoint genericCommand = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/commands/{commandKey}")
.className("click.kamil.examples.statemachine.layered.web.GenericCommandController")
.methodName("execute")
.metadata(Map.of("path", "/api/commands/{commandKey}"))
.name("POST /api/machine/{machineType}/transition/{event}")
.className("com.example.StateMachineController")
.methodName("transition")
.metadata(Map.of("path", "/api/machine/{machineType}/transition/{event}"))
.build();
CallChain payChain = CallChain.builder()
.entryPoint(orderPay)
.triggerPoint(TriggerPoint.builder()
.event("com.example.OrderEvent.PAY")
.eventTypeFqn("com.example.OrderEvent")
.build())
.build();
List<EntryPoint> scoped = EntryPointScopeResolver.scopeFromCallChains(
List.of(payChain),
List.of(orderPay, genericTransition));
assertThat(scoped).extracting(EntryPoint::getName)
.containsExactlyInAnyOrder(
"POST /api/machine/order/pay",
"POST /api/machine/{machineType}/transition/{event}");
}
@Test
void shouldScopeEnterpriseDedicatedPayEndpointToOrderMachine(@TempDir Path tempDir) throws Exception {
writeEnterpriseWebLayer(tempDir);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
EntryPoint payOrder = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/machine/order/pay")
.className("com.example.StateMachineController")
.methodName("payOrder")
.metadata(Map.of("path", "/api/machine/order/pay"))
.build();
EntryPoint submitDocument = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/machine/document/submit")
.className("com.example.StateMachineController")
.methodName("submitDocument")
.metadata(Map.of("path", "/api/machine/document/submit"))
.build();
List<EntryPoint> orderScoped = MachineScopeFilter.filterEntryPointsForMachine(
List.of(orderPay, documentSubmit, genericCommand),
"click.kamil.examples.statemachine.layered.order.config.StandardOrderStateMachineConfiguration",
List.of(payOrder, submitDocument),
"com.example.OrderStateMachineConfiguration",
context);
assertThat(orderScoped).extracting(EntryPoint::getName)
.contains("POST /api/orders/pay", "POST /api/commands/{commandKey}")
.doesNotContain("POST /api/documents/submit");
.containsExactly("POST /api/machine/order/pay");
}
@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();
void shouldExcludeDocumentDedicatedEndpointFromOrderMachine(@TempDir Path tempDir) throws Exception {
writeEnterpriseWebLayer(tempDir);
CodebaseContext context = new CodebaseContext();
List<EntryPoint> scoped = MachineScopeFilter.filterEntryPointsForMachine(
List.of(orderPay),
"click.kamil.examples.statemachine.layered.order.config.StandardOrderStateMachineConfiguration",
context.setResolveBindings(true);
context.scan(tempDir);
EntryPoint submitDocument = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/machine/document/submit")
.className("com.example.StateMachineController")
.methodName("submitDocument")
.metadata(Map.of("path", "/api/machine/document/submit"))
.build();
List<EntryPoint> orderScoped = MachineScopeFilter.filterEntryPointsForMachine(
List.of(submitDocument),
"com.example.OrderStateMachineConfiguration",
context);
assertThat(scoped).extracting(EntryPoint::getName)
.containsExactly("POST /api/orders/pay");
assertThat(orderScoped).isEmpty();
}
@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");
private static void writeEnterpriseWebLayer(Path tempDir) throws Exception {
Files.writeString(tempDir.resolve("App.java"), """
package com.example;
public class StateMachineController {
private final StateMachineDispatcher dispatcher = new StateMachineDispatcher();
public void payOrder() { dispatcher.payOrder(); }
public void submitDocument() { dispatcher.submitDocument(); }
}
class StateMachineDispatcher {
void payOrder() {
org.springframework.statemachine.StateMachine<OrderState, OrderEvent> machine = null;
machine.sendEvent(OrderEvent.PAY);
}
void submitDocument() {
org.springframework.statemachine.StateMachine<DocumentState, DocumentEvent> machine = null;
machine.sendEvent(DocumentEvent.SUBMIT);
}
}
enum OrderEvent { PAY }
enum OrderState { NEW, PAID }
enum DocumentEvent { SUBMIT }
enum DocumentState { DRAFT }
class OrderStateMachineConfiguration
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<OrderState, OrderEvent> {}
class DocumentStateMachineConfiguration
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<DocumentState, DocumentEvent> {}
""");
}
}

View File

@@ -3,6 +3,8 @@ package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import click.kamil.springstatemachineexporter.model.State;
@@ -42,6 +44,74 @@ class TransitionLinkerEnricherTest {
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
}
@Test
void shouldExportResolvedLinkResolutionForDedicatedLiteralEndpoint() {
Transition payT = new Transition();
payT.setSourceStates(List.of(State.of("NEW", "OrderState.NEW")));
payT.setTargetStates(List.of(State.of("PAID", "OrderState.PAID")));
payT.setEvent(Event.of("OrderEvent.PAY", "com.example.OrderEvent.PAY"));
CallChain chain = CallChain.builder()
.entryPoint(EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/order/pay")
.className("com.example.Api")
.methodName("pay")
.build())
.triggerPoint(TriggerPoint.builder()
.event("OrderEvent.PAY")
.polymorphicEvents(List.of("OrderEvent.PAY"))
.external(false)
.build())
.build();
AnalysisResult result = AnalysisResult.builder()
.name("OrderStateMachineConfig")
.transitions(List.of(payT))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updated = result.getMetadata().getCallChains().get(0);
assertThat(updated.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
assertThat(updated.getMatchedTransitions()).hasSize(1);
}
@Test
void shouldExportUnresolvedExternalForGenericPathVariableEndpoint() {
CallChain chain = CallChain.builder()
.entryPoint(EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/machine/{machineType}/transition/{event}")
.className("com.example.Api")
.methodName("transition")
.build())
.triggerPoint(TriggerPoint.builder()
.event("eventString.toUpperCase()")
.external(true)
.ambiguous(true)
.build())
.build();
Transition payT = new Transition();
payT.setSourceStates(List.of(State.of("NEW", "OrderState.NEW")));
payT.setTargetStates(List.of(State.of("PAID", "OrderState.PAID")));
payT.setEvent(Event.of("OrderEvent.PAY", "com.example.OrderEvent.PAY"));
AnalysisResult result = AnalysisResult.builder()
.name("OrderStateMachineConfig")
.transitions(List.of(payT))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updated = result.getMetadata().getCallChains().get(0);
assertThat(updated.getLinkResolution()).isEqualTo(LinkResolution.UNRESOLVED_EXTERNAL);
assertThat(updated.getMatchedTransitions()).isNullOrEmpty();
}
@Test
void shouldLinkWhenSymbolicPolymorphicEventsExpandToMachineEnumConstants() {
Transition payT = new Transition();

View File

@@ -0,0 +1,72 @@
package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class TransitionLinkerMultiSourceStateTest {
@Test
void shouldLinkEachSourceStatePairWithoutCrossMultiplication() {
TriggerPoint trigger = TriggerPoint.builder()
.event("com.example.OrderEvent.CANCEL")
.sourceState("com.example.OrderState.PENDING")
.build();
CallChain chain = CallChain.builder()
.entryPoint(click.kamil.springstatemachineexporter.analysis.model.EntryPoint.builder()
.type(click.kamil.springstatemachineexporter.analysis.model.EntryPoint.Type.REST)
.name("POST /cancel")
.className("com.example.Api")
.methodName("cancel")
.build())
.triggerPoint(trigger)
.methodChain(List.of("com.example.Api.cancel", "com.example.Service.fire"))
.build();
Transition fromPending = transition(
"com.example.OrderEvent.CANCEL",
"com.example.OrderState.PENDING",
"com.example.OrderState.CANCELLED");
Transition fromPaid = transition(
"com.example.OrderEvent.CANCEL",
"com.example.OrderState.PAID",
"com.example.OrderState.CANCELLED");
AnalysisResult result = AnalysisResult.builder()
.name("com.example.config.OrderStateMachineConfiguration")
.stateTypeFqn("com.example.OrderState")
.eventTypeFqn("com.example.OrderEvent")
.transitions(List.of(fromPending, fromPaid))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
new TransitionLinkerEnricher().enrich(result, null, null);
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions())
.hasSize(1)
.first()
.satisfies(mt -> {
assertThat(mt.getSourceState()).isEqualTo("com.example.OrderState.PENDING");
assertThat(mt.getEvent()).isEqualTo("com.example.OrderEvent.CANCEL");
});
}
private static Transition transition(String event, String source, String target) {
Transition transition = new Transition();
transition.setEvent(Event.of(event, event));
transition.setSourceStates(List.of(
State.of(source, source),
State.of("com.example.OrderState.OTHER", "com.example.OrderState.OTHER")));
transition.setTargetStates(List.of(State.of(target, target)));
return transition;
}
}

View File

@@ -35,6 +35,65 @@ class HeuristicBeanResolutionEngineRoutingTest {
context)).isFalse();
}
@Test
void sharedServiceWithProvenOrderTypesShouldOnlyRouteToOrderMachine(@TempDir Path tempDir) throws Exception {
Files.writeString(tempDir.resolve("SharedDispatcher.java"), """
package com.example;
public class SharedDispatcher {
void payOrder() {
org.springframework.statemachine.StateMachine<OrderState, OrderEvent> machine = null;
machine.sendEvent(OrderEvent.PAY);
}
void submitDocument() {
org.springframework.statemachine.StateMachine<DocumentState, DocumentEvent> machine = null;
machine.sendEvent(DocumentEvent.SUBMIT);
}
}
enum OrderEvent { PAY }
enum OrderState { NEW, PAID }
enum DocumentEvent { SUBMIT }
enum DocumentState { DRAFT }
class OrderStateMachineConfiguration
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<OrderState, OrderEvent> {}
class DocumentStateMachineConfiguration
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<DocumentState, DocumentEvent> {}
""");
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
CallChain orderChain = CallChain.builder()
.triggerPoint(TriggerPoint.builder()
.event("com.example.OrderEvent.PAY")
.eventTypeFqn("com.example.OrderEvent")
.stateTypeFqn("com.example.OrderState")
.className("com.example.SharedDispatcher")
.methodName("payOrder")
.build())
.methodChain(List.of("com.example.SharedDispatcher.payOrder()"))
.build();
CallChain documentChain = CallChain.builder()
.triggerPoint(TriggerPoint.builder()
.event("com.example.DocumentEvent.SUBMIT")
.eventTypeFqn("com.example.DocumentEvent")
.stateTypeFqn("com.example.DocumentState")
.className("com.example.SharedDispatcher")
.methodName("submitDocument")
.build())
.methodChain(List.of("com.example.SharedDispatcher.submitDocument()"))
.build();
assertThat(engine.isRoutedToCorrectMachine(
orderChain, "com.example.OrderStateMachineConfiguration", context)).isTrue();
assertThat(engine.isRoutedToCorrectMachine(
orderChain, "com.example.DocumentStateMachineConfiguration", context)).isFalse();
assertThat(engine.isRoutedToCorrectMachine(
documentChain, "com.example.DocumentStateMachineConfiguration", context)).isTrue();
assertThat(engine.isRoutedToCorrectMachine(
documentChain, "com.example.OrderStateMachineConfiguration", context)).isFalse();
}
private static void writeTwoMachineSample(Path tempDir) throws Exception {
Path orderPkg = tempDir.resolve("com/example/order");
Path invoicePkg = tempDir.resolve("com/example/invoice");

View File

@@ -0,0 +1,28 @@
package click.kamil.springstatemachineexporter.analysis.resolver;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class BooleanConstraintEvaluatorBindingTest {
@Test
void shouldEvaluateEqualsIgnoreCaseAgainstPathVariableBinding() {
String constraint = "\"ORDER\".equalsIgnoreCase(machineType)";
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(constraint, Map.of("machineType", "ORDER")))
.isTrue();
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(constraint, Map.of("machineType", "DOCUMENT")))
.isFalse();
}
@Test
void shouldEvaluateDocumentBranchAgainstBinding() {
String constraint = "\"DOCUMENT\".equalsIgnoreCase(machineType)";
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(constraint, Map.of("machineType", "DOCUMENT")))
.isTrue();
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(constraint, Map.of("machineType", "ORDER")))
.isFalse();
}
}

View File

@@ -0,0 +1,72 @@
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;
/**
* Enterprise dedicated REST endpoints must link to transitions when call-graph resolution
* proves a concrete machine enum literal on the dispatcher branch.
*/
class EnterpriseDedicatedEndpointLinkingTest {
private static final String PAY_ENDPOINT = "POST /api/machine/order/pay";
@Test
void astExportDedicatedPayEndpointShouldLinkToOrderPayTransition(@TempDir Path tempDir) throws Exception {
Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise");
ExportService exportService = new ExportService(List.of(new JsonExporter()));
exportService.runExporter(enterprise, tempDir, List.of("json"), true, List.of(), null, null,
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
JsonNode json = readMachineJson(tempDir, "OrderStateMachineConfiguration", new ObjectMapper());
JsonNode chain = findPayEndpointChain(json);
assertThat(chain.path("triggerPoint").path("event").asText())
.isEqualTo("click.kamil.enterprise.machines.order.OrderEvent.PAY");
assertThat(chain.path("matchedTransitions").isArray()).isTrue();
assertThat(chain.path("matchedTransitions")).isNotEmpty();
assertThat(chain.path("matchedTransitions").get(0).path("event").asText())
.isEqualTo("click.kamil.enterprise.machines.order.OrderEvent.PAY");
}
private static JsonNode findPayEndpointChain(JsonNode machineJson) {
for (JsonNode chain : machineJson.path("metadata").path("callChains")) {
if (PAY_ENDPOINT.equals(chain.path("entryPoint").path("name").asText())) {
return chain;
}
}
throw new IllegalStateException("pay endpoint chain not found");
}
private static JsonNode readMachineJson(Path outputDir, String configBaseName, ObjectMapper mapper)
throws Exception {
Path machineDir;
try (var stream = Files.list(outputDir)) {
machineDir = stream
.filter(Files::isDirectory)
.filter(path -> path.getFileName().toString().endsWith(configBaseName))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No output for " + configBaseName));
}
return mapper.readTree(machineDir.resolve(machineDir.getFileName() + ".json").toFile());
}
private static Path findProjectRoot() {
Path current = Path.of(".").toAbsolutePath();
while (current != null && !Files.exists(current.resolve("settings.gradle"))) {
current = current.getParent();
}
return current;
}
}

View File

@@ -0,0 +1,175 @@
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 click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
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 java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Per-branch dispatcher splitting: if-else machineType arms must resolve to branch-specific
* valueOf triggers and constraints, not a collapsed ternary across machines.
*/
class EnterpriseDispatcherBranchSplitTest {
@Test
void shouldResolveBranchSpecificValueOfForEachMachineTypeBinding(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class MachineController {
StateMachineDispatcher dispatcher;
public void transition(String machineType, String event) {
dispatcher.dispatch(machineType, event);
}
}
class StateMachineDispatcher {
void dispatch(String machineType, String eventString) {
if ("ORDER".equalsIgnoreCase(machineType)) {
fireOrder(eventString);
} else if ("DOCUMENT".equalsIgnoreCase(machineType)) {
fireDocument(eventString);
} else if ("USER".equalsIgnoreCase(machineType)) {
fireUser(eventString);
}
}
void fireOrder(String eventString) {
OrderEvent event = OrderEvent.valueOf(eventString.toUpperCase());
send(event);
}
void fireDocument(String eventString) {
DocumentEvent event = DocumentEvent.valueOf(eventString.toUpperCase());
send(event);
}
void fireUser(String eventString) {
UserEvent event = UserEvent.valueOf(eventString.toUpperCase());
send(event);
}
void send(OrderEvent event) {}
void send(DocumentEvent event) {}
void send(UserEvent event) {}
}
enum OrderEvent { PAY }
enum DocumentEvent { SUBMIT }
enum UserEvent { VERIFY }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.MachineController")
.methodName("transition")
.name("POST /api/machine/{machineType}/transition/{event}")
.parameters(List.of(
EntryPoint.Parameter.builder()
.name("machineType")
.type("String")
.annotations(List.of("PathVariable"))
.build(),
EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
TriggerPoint orderTrigger = TriggerPoint.builder()
.className("com.example.StateMachineDispatcher")
.methodName("send")
.event("event")
.build();
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(orderTrigger));
CallChain orderChain = chains.stream()
.filter(c -> c.getEntryPoint().getName().contains("/ORDER/"))
.findFirst()
.orElseThrow();
List<CallChain> documentChains = chains.stream()
.filter(c -> c.getEntryPoint().getName().contains("/DOCUMENT/"))
.toList();
assertThat(documentChains).isNotEmpty();
CallChain documentChain = documentChains.stream()
.filter(c -> c.getMethodChain().contains("com.example.StateMachineDispatcher.fireDocument"))
.findFirst()
.orElseThrow();
assertThat(orderChain.getTriggerPoint().getEvent())
.contains("OrderEvent.valueOf")
.doesNotContain("DocumentEvent")
.doesNotContain("UserEvent")
.doesNotContain("true ?");
assertThat(orderChain.getTriggerPoint().getConstraint())
.contains("ORDER")
.doesNotContain("DOCUMENT");
assertThat(documentChain.getMethodChain())
.contains("com.example.StateMachineDispatcher.fireDocument")
.doesNotContain("com.example.StateMachineDispatcher.fireOrder");
assertThat(documentChain.getTriggerPoint().getEvent())
.satisfiesAnyOf(
e -> assertThat(e).contains("DocumentEvent.valueOf"),
e -> assertThat(e).isEqualTo("event"));
assertThat(documentChain.getTriggerPoint().getConstraint())
.contains("DOCUMENT")
.doesNotContain("USER");
}
@Test
void bindingExpanderShouldProduceMachineTypeVariants(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class MachineController {
StateMachineDispatcher dispatcher;
public void transition(String machineType, String event) {
dispatcher.dispatch(machineType, event);
}
}
class StateMachineDispatcher {
void dispatch(String machineType, String eventString) {
if ("ORDER".equalsIgnoreCase(machineType)) {
order(eventString);
} else if ("DOCUMENT".equalsIgnoreCase(machineType)) {
document(eventString);
}
}
void order(String eventString) {}
void document(String eventString) {}
}
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.MachineController")
.methodName("transition")
.parameters(List.of(
EntryPoint.Parameter.builder()
.name("machineType")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
List<Map<String, String>> variants = EntryPointBindingExpander.expandPathVariableBindings(
entryPoint, context, engine.buildCallGraph());
assertThat(variants).extracting(map -> map.get("machineType"))
.containsExactlyInAnyOrder("ORDER", "DOCUMENT");
}
}

View File

@@ -41,7 +41,9 @@ class EnterpriseDispatcherMatchedTransitionsRegressionTest {
private static JsonNode findTransitionEndpointChain(JsonNode machineJson) {
for (JsonNode chain : machineJson.path("metadata").path("callChains")) {
if (TRANSITION_ENDPOINT.equals(chain.path("entryPoint").path("name").asText())) {
String name = chain.path("entryPoint").path("name").asText();
if (TRANSITION_ENDPOINT.equals(name)
|| (name.contains("/transition/{event}") && name.contains("POST /api/machine/"))) {
return chain;
}
}

View File

@@ -143,4 +143,59 @@ class EntryPointBindingExpanderTest {
assertThat(chains.stream().filter(chain -> chain.getEntryPoint().getName().endsWith("order.pay")).findFirst().orElseThrow()
.getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
}
@Test
void shouldExpandPathVariableFromIfElseEqualsChain(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class MachineController {
StateMachineDispatcher dispatcher;
public void transition(String machineType, String event) {
dispatcher.dispatch(machineType, event);
}
}
class StateMachineDispatcher {
void dispatch(String machineType, String eventString) {
if ("ORDER".equalsIgnoreCase(machineType)) {
fireOrder(eventString);
} else if ("DOCUMENT".equalsIgnoreCase(machineType)) {
fireDocument(eventString);
}
}
void fireOrder(String eventString) {}
void fireDocument(String eventString) {}
}
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> graph = engine.buildCallGraph();
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.MachineController")
.methodName("transition")
.name("POST /api/machine/{machineType}/transition/{event}")
.parameters(List.of(
EntryPoint.Parameter.builder()
.name("machineType")
.type("String")
.annotations(List.of("PathVariable"))
.build(),
EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
List<Map<String, String>> variants =
EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);
assertThat(variants).extracting(map -> map.get("machineType"))
.containsExactlyInAnyOrder("ORDER", "DOCUMENT");
}
}

View File

@@ -0,0 +1,50 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.CompilationUnit;
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;
/**
* Export must work on standard Spring State Machine patterns without hints.json.
*/
class ExportWithoutHintsTest {
@Test
void shouldDetectSendEventTriggersWithoutHintsJson(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
import org.springframework.statemachine.StateMachine;
public class OrderService {
public void pay(StateMachine<OrderState, OrderEvent> sm) {
sm.sendEvent(OrderEvent.PAY);
}
}
enum OrderState { NEW, PAID }
enum OrderEvent { PAY }
""";
Files.writeString(tempDir.resolve("OrderService.java"), source);
assertThat(tempDir.resolve("hints.json")).doesNotExist();
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
assertThat(context.getLibraryHints()).isEmpty();
GenericEventDetector detector = new GenericEventDetector(
context, context.getConstantResolver(), context.getLibraryHints());
List<TriggerPoint> triggers = detector.detect(
context.getCompilationUnits().iterator().next());
assertThat(triggers).isNotEmpty();
assertThat(triggers)
.extracting(TriggerPoint::getEvent)
.anyMatch(e -> e != null && e.contains("PAY"));
}
}

View File

@@ -0,0 +1,122 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
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;
class ExternalTriggerPolicyTest {
@Test
void pathVariableEventParameterShouldBeExternal() {
EntryPoint entryPoint = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/{event}")
.className("com.example.Api")
.methodName("trigger")
.parameters(List.of(EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
TriggerPoint trigger = TriggerPoint.builder()
.event("eventString.toUpperCase()")
.build();
assertThat(ExternalTriggerPolicy.isExternalFromSource(
entryPoint, trigger, "com.example.Api.trigger", "event", null)).isTrue();
}
@Test
void requestBodyEnumShouldBeInternal(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
import org.springframework.web.bind.annotation.*;
public class Api {
@PostMapping("/pay")
public void pay(@RequestBody OrderEvent event) {
fire(event);
}
void fire(OrderEvent event) {}
}
enum OrderEvent { PAY }
""";
Files.writeString(tempDir.resolve("Api.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
EntryPoint entryPoint = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /pay")
.className("com.example.Api")
.methodName("pay")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.event("com.example.OrderEvent.PAY")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
.build();
assertThat(ExternalTriggerPolicy.isExternalFromSource(
entryPoint, trigger, "com.example.Api.pay", "event", context)).isFalse();
}
@Test
void concreteEnumLiteralFromDedicatedEndpointShouldBeInternal() {
EntryPoint entryPoint = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/order/pay")
.className("com.example.Api")
.methodName("pay")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.event("com.example.OrderEvent.PAY")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
.build();
assertThat(ExternalTriggerPolicy.isExternalFromSource(
entryPoint, trigger, "com.example.Api.pay", null, null)).isFalse();
}
@Test
void requestBodyRecordEnumComponentShouldBeInternal(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
import org.springframework.web.bind.annotation.*;
public class Api {
@PostMapping("/pay")
public void pay(@RequestBody OrderRequest request) {
fire(request.event());
}
void fire(OrderEvent event) {}
}
record OrderRequest(OrderEvent event) {}
enum OrderEvent { PAY }
""";
Files.writeString(tempDir.resolve("Api.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
EntryPoint entryPoint = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /pay")
.className("com.example.Api")
.methodName("pay")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.event("com.example.OrderEvent.PAY")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
.build();
assertThat(ExternalTriggerPolicy.isExternalFromSource(
entryPoint, trigger, "com.example.Api.pay", "event", context)).isFalse();
}
}

View File

@@ -158,4 +158,66 @@ class GenericEventDetectorControlFlowTest {
assertThat(triggers).hasSize(1);
assertThat(triggers.get(0).getSourceState()).isNull();
}
@Test
void shouldDetectSourceStateFromLiteralSendEventArgument(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderService {
private StateMachine sm;
public void pay() {
sm.sendEvent(OrderEvent.PAY, OrderState.PENDING);
}
}
class StateMachine {
public void sendEvent(OrderEvent e, OrderState s) {}
}
enum OrderState { PENDING }
enum OrderEvent { PAY }
""";
Files.writeString(tempDir.resolve("OrderService.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
List<TriggerPoint> triggers = detector.detect(
(org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
assertThat(triggers).hasSize(1);
assertThat(triggers.get(0).getSourceState()).isEqualTo("PENDING");
}
@Test
void shouldNotGuessSourceStateFromVariableSendEventArgument(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderService {
private StateMachine sm;
public void pay(OrderState current) {
sm.sendEvent(OrderEvent.PAY, current);
}
}
class StateMachine {
public void sendEvent(OrderEvent e, OrderState s) {}
}
enum OrderState { PENDING }
enum OrderEvent { PAY }
""";
Files.writeString(tempDir.resolve("OrderService.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
List<TriggerPoint> triggers = detector.detect(
(org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
assertThat(triggers).hasSize(1);
assertThat(triggers.get(0).getSourceState()).isNull();
}
}

View File

@@ -525,6 +525,18 @@ class HeuristicCallGraphEngineTypeTest {
EntryPoint entry = EntryPoint.builder()
.className("click.kamil.enterprise.web.StateMachineController")
.methodName("transition")
.name("POST /api/machine/{machineType}/transition/{event}")
.parameters(java.util.List.of(
EntryPoint.Parameter.builder()
.name("machineType")
.type("String")
.annotations(java.util.List.of("PathVariable"))
.build(),
EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(java.util.List.of("PathVariable"))
.build()))
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("click.kamil.enterprise.web.StateMachineDispatcher")
@@ -534,7 +546,15 @@ class HeuristicCallGraphEngineTypeTest {
List<CallChain> chains = builder.findChains(List.of(entry), List.of(trigger));
assertThat(chains).isNotEmpty();
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>");
java.util.Set<String> polymorphicEvents = new java.util.LinkedHashSet<>();
for (CallChain chain : chains) {
if (chain.getTriggerPoint().getPolymorphicEvents() != null) {
polymorphicEvents.addAll(chain.getTriggerPoint().getPolymorphicEvents());
}
assertThat(chain.getTriggerPoint().getEvent())
.doesNotContain("true ?");
}
assertThat(polymorphicEvents)
.contains("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>");
}
}

View File

@@ -304,6 +304,18 @@ class JdtCallGraphEngineIntegrationTest {
EntryPoint entry = EntryPoint.builder()
.className("click.kamil.enterprise.web.StateMachineController")
.methodName("transition")
.name("POST /api/machine/{machineType}/transition/{event}")
.parameters(java.util.List.of(
EntryPoint.Parameter.builder()
.name("machineType")
.type("String")
.annotations(java.util.List.of("PathVariable"))
.build(),
EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(java.util.List.of("PathVariable"))
.build()))
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("click.kamil.enterprise.web.StateMachineDispatcher")
@@ -313,7 +325,15 @@ class JdtCallGraphEngineIntegrationTest {
List<CallChain> chains = jdtEngine.findChains(List.of(entry), List.of(trigger));
assertThat(chains).isNotEmpty();
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>");
java.util.Set<String> polymorphicEvents = new java.util.LinkedHashSet<>();
for (CallChain chain : chains) {
if (chain.getTriggerPoint().getPolymorphicEvents() != null) {
polymorphicEvents.addAll(chain.getTriggerPoint().getPolymorphicEvents());
}
assertThat(chain.getTriggerPoint().getEvent())
.doesNotContain("true ?");
}
assertThat(polymorphicEvents)
.contains("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>");
}
}

View File

@@ -0,0 +1,42 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
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.Map;
import static org.assertj.core.api.Assertions.assertThat;
class VariableTracerBranchFilterTest {
@Test
void shouldReturnBranchLocalInitializerWithoutCrossBranchTernary(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
class StateMachineDispatcher {
void fireDocument(String eventString) {
DocumentEvent event = DocumentEvent.valueOf(eventString.toUpperCase());
send(event);
}
void send(DocumentEvent event) {}
}
enum DocumentEvent { SUBMIT }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
String traced = engine.variableTracer.traceLocalVariable(
"com.example.StateMachineDispatcher.fireDocument",
"event",
Map.of("machineType", "DOCUMENT"));
assertThat(traced).contains("DocumentEvent.valueOf");
}
}

View File

@@ -322,6 +322,46 @@ class AnalysisCanonicalFormValidatorTest {
.contains("metadata.callChains[0].matchedTransitions");
}
@Test
void shouldFailWhenDedicatedEndpointHasConcreteLiteralEventButNoMatchedTransitions() {
AnalysisResult result = AnalysisResult.builder()
.name("com.example.config.OrderStateMachineConfiguration")
.stateTypeFqn("com.example.order.OrderState")
.eventTypeFqn("com.example.order.OrderEvent")
.transitions(List.of(transition(
"com.example.order.OrderEvent.PAY",
"com.example.order.OrderState.NEW",
"com.example.order.OrderState.PAID")))
.metadata(CodebaseMetadata.builder()
.callChains(List.of(CallChain.builder()
.entryPoint(click.kamil.springstatemachineexporter.analysis.model.EntryPoint.builder()
.type(click.kamil.springstatemachineexporter.analysis.model.EntryPoint.Type.REST)
.name("POST /api/machine/order/pay")
.className("com.example.web.StateMachineController")
.methodName("payOrder")
.build())
.triggerPoint(TriggerPoint.builder()
.event("com.example.order.OrderEvent.PAY")
.className("com.example.web.StateMachineDispatcher")
.methodName("payOrder")
.sourceFile("StateMachineDispatcher.java")
.eventTypeFqn("com.example.order.OrderEvent")
.build())
.build()))
.build())
.build();
List<AnalysisCanonicalFormValidator.Violation> violations =
AnalysisCanonicalFormValidator.validateWithMachineTypes(
result,
new click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes(
"com.example.order.OrderState", "com.example.order.OrderEvent"));
assertThat(violations)
.extracting(AnalysisCanonicalFormValidator.Violation::path)
.contains("metadata.callChains[0].matchedTransitions");
}
@Test
void shouldAllowSymbolicPolymorphicEventsWithoutMatchedTransitions() {
AnalysisResult result = AnalysisResult.builder()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View File

@@ -123,6 +123,120 @@
"ambiguous" : false
} ],
"entryPoints" : [ {
"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" ]
} ]
}, {
"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" ]
} ]
}, {
"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" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/ORDER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/ORDER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/DOCUMENT/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/DOCUMENT/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/USER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/USER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
@@ -149,12 +263,126 @@
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"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" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT",
"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" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
"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" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
"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/ORDER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"path" : "/api/machine/ORDER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
@@ -173,7 +401,7 @@
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"event" : "OrderEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
@@ -183,8 +411,92 @@
"lineNumber" : 87,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
"ambiguous" : true
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/DOCUMENT/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/DOCUMENT/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "DocumentEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 87,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/USER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/USER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 87,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "\"USER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null

View File

@@ -152,18 +152,18 @@
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
"methodName" : "processBaseEndpoint",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java",
"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/base/{id}",
"verb" : "GET"
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "id",
"name" : "orderId",
"type" : "String",
"annotations" : [ "PathVariable" ]
"annotations" : [ ]
} ]
}, {
"type" : "REST",
@@ -180,8 +180,57 @@
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
"methodName" : "processBaseEndpoint",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java",
"metadata" : {
"path" : "/api/base/{id}",
"verb" : "GET"
},
"parameters" : [ {
"name" : "id",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
} ],
"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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -152,18 +152,18 @@
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
"methodName" : "processBaseEndpoint",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java",
"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/base/{id}",
"verb" : "GET"
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "id",
"name" : "orderId",
"type" : "String",
"annotations" : [ "PathVariable" ]
"annotations" : [ ]
} ]
}, {
"type" : "REST",
@@ -180,8 +180,57 @@
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
"methodName" : "processBaseEndpoint",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java",
"metadata" : {
"path" : "/api/base/{id}",
"verb" : "GET"
},
"parameters" : [ {
"name" : "id",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
} ],
"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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -75,6 +75,21 @@
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
}, {
"type" : "JMS",
"name" : "JMS: order.queue",
"className" : "click.kamil.maven.api.JmsOrderListener",
"methodName" : "onMessage",
"sourceFile" : "api-module/src/main/java/click/kamil/maven/api/JmsOrderListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "order.queue"
},
"parameters" : [ {
"name" : "message",
"type" : "String",
"annotations" : [ ]
} ]
} ],
"callChains" : [ {
"entryPoint" : {
@@ -114,6 +129,44 @@
"targetState" : "String.BUSY",
"event" : "String.SUBMIT"
} ]
}, {
"entryPoint" : {
"type" : "JMS",
"name" : "JMS: order.queue",
"className" : "click.kamil.maven.api.JmsOrderListener",
"methodName" : "onMessage",
"sourceFile" : "api-module/src/main/java/click/kamil/maven/api/JmsOrderListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "order.queue"
},
"parameters" : [ {
"name" : "message",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.maven.api.JmsOrderListener.onMessage", "click.kamil.maven.core.MavenOrderStateMachine.OrderService.onMessage" ],
"triggerPoint" : {
"event" : "java.lang.String.ORDER_EVENT",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
"methodName" : "onMessage",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "java.lang.String.BUSY",
"lineNumber" : 52,
"polymorphicEvents" : [ "java.lang.String.ORDER_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.BUSY",
"targetState" : "String.DONE",
"event" : "String.ORDER_EVENT"
} ]
} ],
"properties" : {
"default" : { }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -100,6 +100,105 @@
"ambiguous" : false
} ],
"entryPoints" : [ {
"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" ]
} ]
}, {
"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" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/ORDER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/ORDER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/DOCUMENT/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/DOCUMENT/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/USER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/USER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
@@ -126,12 +225,88 @@
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"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" : "click.kamil.enterprise.machines.order.OrderState.NEW",
"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" : "click.kamil.enterprise.machines.order.OrderState.PENDING",
"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/ORDER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"path" : "/api/machine/ORDER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
@@ -150,7 +325,7 @@
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"event" : "OrderEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
@@ -160,8 +335,92 @@
"lineNumber" : 81,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
"ambiguous" : true
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"ORDER\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/DOCUMENT/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/DOCUMENT/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "DocumentEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 81,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"ORDER\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/USER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/USER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 81,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"ORDER\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -119,6 +119,51 @@
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/commands/document.submit",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : {
"path" : "/api/commands/document.submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "commandKey",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/commands/document.approve",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : {
"path" : "/api/commands/document.approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "commandKey",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/commands/document.reject",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : {
"path" : "/api/commands/document.reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "commandKey",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/commands/{commandKey}",
@@ -266,7 +311,7 @@
"lineNumber" : 81,
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.SUBMIT" ],
"external" : false,
"constraint" : "command == DOCUMENT_SUBMIT",
"constraint" : "\"document.submit\".equalsIgnoreCase(commandKey) && command == DOCUMENT_SUBMIT",
"ambiguous" : false
},
"contextMachineId" : null,
@@ -304,7 +349,7 @@
"lineNumber" : 81,
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.APPROVE" ],
"external" : false,
"constraint" : "command == DOCUMENT_APPROVE",
"constraint" : "\"document.approve\".equalsIgnoreCase(commandKey) && command == DOCUMENT_APPROVE",
"ambiguous" : false
},
"contextMachineId" : null,
@@ -342,7 +387,7 @@
"lineNumber" : 81,
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.REJECT" ],
"external" : false,
"constraint" : "command == DOCUMENT_REJECT",
"constraint" : "\"document.reject\".equalsIgnoreCase(commandKey) && command == DOCUMENT_REJECT",
"ambiguous" : false
},
"contextMachineId" : null,

View File

@@ -163,6 +163,51 @@
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/commands/order.pay",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : {
"path" : "/api/commands/order.pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "commandKey",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/commands/order.ship",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : {
"path" : "/api/commands/order.ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "commandKey",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/commands/order.cancel",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : {
"path" : "/api/commands/order.cancel",
"verb" : "POST"
},
"parameters" : [ {
"name" : "commandKey",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/commands/{commandKey}",
@@ -446,7 +491,7 @@
"lineNumber" : 75,
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ],
"external" : false,
"constraint" : "command == ORDER_PAY",
"constraint" : "\"order.pay\".equalsIgnoreCase(commandKey) && command == ORDER_PAY",
"ambiguous" : false
},
"contextMachineId" : null,
@@ -484,7 +529,7 @@
"lineNumber" : 75,
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ],
"external" : false,
"constraint" : "command == ORDER_SHIP",
"constraint" : "\"order.ship\".equalsIgnoreCase(commandKey) && command == ORDER_SHIP",
"ambiguous" : false
},
"contextMachineId" : null,
@@ -522,7 +567,7 @@
"lineNumber" : 75,
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.CANCEL" ],
"external" : false,
"constraint" : "command == ORDER_CANCEL",
"constraint" : "\"order.cancel\".equalsIgnoreCase(commandKey) && command == ORDER_CANCEL",
"ambiguous" : false
},
"contextMachineId" : null,

View File

@@ -446,7 +446,7 @@
},
"methodChain" : [ "click.kamil.web.JmsOrderListener.receiveCancelCommand", "click.kamil.service.StateMachineServiceImpl.sendMessageWithProvider" ],
"triggerPoint" : {
"event" : "click.kamil.domain.OrderEvent.CANCEL",
"event" : "eventProvider",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessageWithProvider",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
@@ -454,10 +454,10 @@
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 50,
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.CANCEL" ],
"polymorphicEvents" : null,
"external" : false,
"constraint" : "event != null",
"ambiguous" : true
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

@@ -93,6 +93,105 @@
"ambiguous" : false
} ],
"entryPoints" : [ {
"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" ]
} ]
}, {
"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" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/ORDER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/ORDER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/DOCUMENT/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/DOCUMENT/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/USER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/USER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
@@ -119,12 +218,84 @@
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"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" : "click.kamil.enterprise.machines.user.UserState.REGISTERED",
"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" : [ ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/ORDER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"path" : "/api/machine/ORDER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
@@ -143,7 +314,7 @@
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"event" : "OrderEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
@@ -153,8 +324,92 @@
"lineNumber" : 93,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
"ambiguous" : true
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/DOCUMENT/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/DOCUMENT/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "DocumentEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 93,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/USER/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/USER/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 93,
"polymorphicEvents" : [ ],
"external" : true,
"constraint" : "\"USER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null