Require endpoint-narrow polymorphic evidence before linking transitions.

Fail closed on broad enum widens so dispatchers link one event per endpoint instead of every transition on the machine.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 19:55:18 +02:00
parent 33ccc4a6c5
commit 1957266a98
11 changed files with 337 additions and 94 deletions

View File

@@ -37,21 +37,50 @@ public final class CallChainLinkPolicy {
if (trigger == null || trigger.isExternal() || !trigger.isAmbiguous()) { if (trigger == null || trigger.isExternal() || !trigger.isAmbiguous()) {
return false; return false;
} }
List<String> poly = trigger.getPolymorphicEvents(); String eventTypeFqn = trigger.getEventTypeFqn();
if (poly == null || poly.size() <= 1) { if (eventTypeFqn == null || eventTypeFqn.isBlank()) {
eventTypeFqn = machineEventTypeFqn;
}
List<String> concrete = concretePolymorphicCandidates(
trigger.getPolymorphicEvents(), eventTypeFqn, context);
if (concrete.size() <= 1) {
return false; return false;
} }
String event = trigger.getEvent(); String event = trigger.getEvent();
if (event != null && event.startsWith("ENUM_SET:")) { if (event != null && event.startsWith("ENUM_SET:")) {
return true; return true;
} }
if (event != null && event.contains(".valueOf(")) { return MachineEnumCanonicalizer.isDynamicTriggerExpression(event);
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event) }
&& !isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn, context);
/**
* True when call-graph evidence resolves to a single concrete machine enum event for this chain.
* Dispatcher/rich-event paths should narrow to one constant per endpoint before linking.
*/
public static boolean isEndpointNarrowPolyEvidence(
TriggerPoint trigger,
String machineEventTypeFqn,
CodebaseContext context) {
if (trigger == null) {
return false;
} }
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event) String eventTypeFqn = trigger.getEventTypeFqn();
&& event != null if (eventTypeFqn == null || eventTypeFqn.isBlank()) {
&& !isTrustedEnumPolymorphicWiden(trigger, machineEventTypeFqn, context); eventTypeFqn = machineEventTypeFqn;
}
List<String> concrete = concretePolymorphicCandidates(
trigger.getPolymorphicEvents(), eventTypeFqn, context);
return concrete.size() == 1;
}
public static boolean isEndpointNarrowPolyEvidence(TriggerPoint trigger) {
return isEndpointNarrowPolyEvidence(trigger, null, null);
}
public static boolean isEndpointNarrowPolyEvidence(
TriggerPoint trigger,
String machineEventTypeFqn) {
return isEndpointNarrowPolyEvidence(trigger, machineEventTypeFqn, null);
} }
/** /**
@@ -83,18 +112,14 @@ public final class CallChainLinkPolicy {
} }
List<String> poly = trigger.getPolymorphicEvents(); List<String> poly = trigger.getPolymorphicEvents();
List<String> concrete = concretePolymorphicCandidates(poly, eventTypeFqn, context); List<String> concrete = concretePolymorphicCandidates(poly, eventTypeFqn, context);
if (concrete.size() <= 1) { if (concrete.size() != 1) {
return false; return false;
} }
for (String pe : concrete) { return MachineEnumCanonicalizer.polymorphicEventMatchesMachineEventType(
if (!MachineEnumCanonicalizer.polymorphicEventMatchesMachineEventType(pe, eventTypeFqn, context)) { concrete.get(0), eventTypeFqn, context);
return false;
}
}
return true;
} }
private static List<String> concretePolymorphicCandidates( static List<String> concretePolymorphicCandidates(
List<String> poly, List<String> poly,
String eventTypeFqn, String eventTypeFqn,
CodebaseContext context) { CodebaseContext context) {
@@ -149,12 +174,12 @@ public final class CallChainLinkPolicy {
if (shouldFailClosedOnAmbiguousCallGraphWiden(trigger, machineEventTypeFqn, context)) { if (shouldFailClosedOnAmbiguousCallGraphWiden(trigger, machineEventTypeFqn, context)) {
return LinkResolution.AMBIGUOUS_WIDEN; return LinkResolution.AMBIGUOUS_WIDEN;
} }
if (matched != null && !matched.isEmpty()) {
return LinkResolution.RESOLVED;
}
if (ambiguousSource) { if (ambiguousSource) {
return LinkResolution.AMBIGUOUS_WIDEN; return LinkResolution.AMBIGUOUS_WIDEN;
} }
if (matched != null && !matched.isEmpty()) {
return LinkResolution.RESOLVED;
}
return LinkResolution.NO_MATCH; return LinkResolution.NO_MATCH;
} }

View File

@@ -121,8 +121,10 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
if (crossStateTypeConflict) { if (crossStateTypeConflict) {
ambiguousSource = true; ambiguousSource = true;
} else if (sourcesByEvent.values().stream().anyMatch(sources -> sources.size() > 1)) { } else if (sourcesByEvent.values().stream().anyMatch(sources -> sources.size() > 1)) {
// Same event from multiple sources within one state enum is normal — link all. ambiguousSource = !CallChainLinkPolicy.isEndpointNarrowPolyEvidence(
ambiguousSource = false; tp,
machineTypes != null ? machineTypes.eventTypeFqn() : null,
context);
} else { } else {
Set<String> allDistinctSources = new LinkedHashSet<>(); Set<String> allDistinctSources = new LinkedHashSet<>();
sourcesByEvent.values().forEach(allDistinctSources::addAll); sourcesByEvent.values().forEach(allDistinctSources::addAll);

View File

@@ -48,81 +48,41 @@ class CallChainLinkPolicyTest {
} }
@Test @Test
void shouldAllowTrustedMachineScopedValueOfWiden() { void shouldAllowEndpointNarrowSingleConcretePoly() {
TriggerPoint trigger = TriggerPoint.builder() TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true) .ambiguous(true)
.eventTypeFqn("com.example.OrderEvent") .eventTypeFqn("com.example.OrderEvent")
.event("OrderEvent.valueOf(eventStr)") .event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP")) .polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
.build(); .build();
assertThat(CallChainLinkPolicy.isEndpointNarrowPolyEvidence(trigger)).isTrue();
assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(trigger)).isTrue(); assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(trigger)).isTrue();
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isFalse(); assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isFalse();
} }
@Test @Test
void shouldAllowTrustedWidenUsingMachineEventTypeWhenTriggerTypeIsNull() { void shouldFailClosedWhenMultipleConcretePolyCandidatesRemain() {
TriggerPoint trigger = TriggerPoint.builder() TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true) .ambiguous(true)
.event("OrderEvent.valueOf(eventStr)") .event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP")) .polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP"))
.build(); .build();
assertThat(CallChainLinkPolicy.isEndpointNarrowPolyEvidence(
trigger, "com.example.OrderEvent")).isFalse();
assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden( assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(
trigger, "com.example.OrderEvent")).isFalse();
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
trigger, "com.example.OrderEvent")).isTrue(); trigger, "com.example.OrderEvent")).isTrue();
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
trigger, "com.example.OrderEvent")).isFalse();
} }
@Test @Test
void shouldResolveLinkWhenTrustedWidenPassesUsingMachineEventType() { void shouldResolveLinkWhenEndpointNarrowPolyHasMatches() {
TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(false)
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP"))
.build();
assertThat(CallChainLinkPolicy.resolveLinkResolution(
trigger,
List.of(),
false,
"com.example.OrderEvent")).isEqualTo(LinkResolution.NO_MATCH);
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
trigger, "com.example.OrderEvent")).isFalse();
}
@Test
void shouldNotTrustImportStylePolymorphicWidenWithoutPackage() {
TriggerPoint trigger = TriggerPoint.builder() TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true) .ambiguous(true)
.event("OrderEvent.valueOf(eventStr)") .event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP")) .polymorphicEvents(List.of("OrderEvent.PAY"))
.build();
assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(trigger)).isFalse();
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isTrue();
}
@Test
void shouldTrustImportStylePolymorphicWidenWhenMachineEventTypeIsKnown() {
TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true)
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP"))
.build();
assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(
trigger, "com.example.order.OrderEvent")).isTrue();
assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
trigger, "com.example.order.OrderEvent")).isFalse();
}
@Test
void shouldResolveWhenTrustedWidenHasMatchesDespiteAmbiguousCallGraphFlag() {
TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true)
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP"))
.build(); .build();
var matched = List.of( var matched = List.of(
@@ -138,11 +98,31 @@ class CallChainLinkPolicyTest {
} }
@Test @Test
void shouldTrustBareConstantPolymorphicCandidatesWhenMachineEventTypeIsKnown() { void shouldNotResolveWhenBroadPolyWidenHasMatches() {
TriggerPoint trigger = TriggerPoint.builder() TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true) .ambiguous(true)
.event("OrderEvent.valueOf(eventStr)") .event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("PAY", "SHIP")) .polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP"))
.build();
var matched = List.of(
click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder()
.event("com.example.order.OrderEvent.PAY")
.build());
assertThat(CallChainLinkPolicy.resolveLinkResolution(
trigger,
matched,
false,
"com.example.order.OrderEvent")).isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
}
@Test
void shouldTrustImportStyleSingleConcretePolyWhenMachineEventTypeIsKnown() {
TriggerPoint trigger = TriggerPoint.builder()
.ambiguous(true)
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of("OrderEvent.PAY"))
.build(); .build();
assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden( assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(

View File

@@ -237,9 +237,7 @@ class TransitionLinkerEnricherTest {
.triggerPoint(TriggerPoint.builder() .triggerPoint(TriggerPoint.builder()
.ambiguous(true) .ambiguous(true)
.event("OrderEvent.valueOf(eventStr)") .event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of( .polymorphicEvents(List.of("com.example.order.OrderEvent.PAY"))
"com.example.order.OrderEvent.PAY",
"com.example.order.OrderEvent.SHIP"))
.build()) .build())
.build(); .build();
@@ -258,6 +256,49 @@ class TransitionLinkerEnricherTest {
assertThat(updatedChain.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED); assertThat(updatedChain.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
} }
@Test
void shouldNotLinkAllTransitionsWhenPolyContainsManyEnumConstants() {
Transition pay = new Transition();
pay.setSourceStates(List.of(State.of("NEW", "com.example.order.OrderState.NEW")));
pay.setTargetStates(List.of(State.of("PAID", "com.example.order.OrderState.PAID")));
pay.setEvent(Event.of("PAY", "com.example.order.OrderEvent.PAY"));
Transition ship = new Transition();
ship.setSourceStates(List.of(State.of("PAID", "com.example.order.OrderState.PAID")));
ship.setTargetStates(List.of(State.of("SHIPPED", "com.example.order.OrderState.SHIPPED")));
ship.setEvent(Event.of("SHIP", "com.example.order.OrderEvent.SHIP"));
Transition cancel = new Transition();
cancel.setSourceStates(List.of(State.of("NEW", "com.example.order.OrderState.NEW")));
cancel.setTargetStates(List.of(State.of("CANCELLED", "com.example.order.OrderState.CANCELLED")));
cancel.setEvent(Event.of("CANCEL", "com.example.order.OrderEvent.CANCEL"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder()
.ambiguous(true)
.event("OrderEvent.valueOf(eventStr)")
.polymorphicEvents(List.of(
"com.example.order.OrderEvent.PAY",
"com.example.order.OrderEvent.SHIP",
"com.example.order.OrderEvent.CANCEL"))
.build())
.build();
AnalysisResult result = AnalysisResult.builder()
.name("com.example.config.OrderStateMachineConfiguration")
.eventTypeFqn("com.example.order.OrderEvent")
.stateTypeFqn("com.example.order.OrderState")
.transitions(List.of(pay, ship, cancel))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
assertThat(updatedChain.getLinkResolution()).isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
}
@Test @Test
void shouldNotInferSourceStateWhenDifferentEnumsShareSameConstantName() { void shouldNotInferSourceStateWhenDifferentEnumsShareSameConstantName() {
Transition t1 = new Transition(); Transition t1 = new Transition();

View File

@@ -53,7 +53,7 @@ class TrustedEnumWidenLinkingRegressionTest {
.event("OrderEvent.valueOf(eventStr)") .event("OrderEvent.valueOf(eventStr)")
.ambiguous(true) .ambiguous(true)
.external(false) .external(false)
.polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP")) .polymorphicEvents(List.of("OrderEvent.PAY"))
.build()) .build())
.build(); .build();
@@ -71,7 +71,7 @@ class TrustedEnumWidenLinkingRegressionTest {
assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED); assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
assertThat(linked.getMatchedTransitions()).hasSizeGreaterThanOrEqualTo(1); assertThat(linked.getMatchedTransitions()).hasSizeGreaterThanOrEqualTo(1);
assertThat(linked.getTriggerPoint().getPolymorphicEvents()) assertThat(linked.getTriggerPoint().getPolymorphicEvents())
.containsExactly("com.example.order.OrderEvent.PAY", "com.example.order.OrderEvent.SHIP"); .containsExactly("com.example.order.OrderEvent.PAY");
} }
@Test @Test
@@ -102,7 +102,7 @@ class TrustedEnumWidenLinkingRegressionTest {
.event("OrderEvent.valueOf(eventStr)") .event("OrderEvent.valueOf(eventStr)")
.ambiguous(true) .ambiguous(true)
.external(false) .external(false)
.polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP")) .polymorphicEvents(List.of("OrderEvent.PAY"))
.build()) .build())
.build(); .build();

View File

@@ -149,6 +149,8 @@ class DispatcherEndpointTest {
TriggerPoint linkedTrigger = result.getMetadata().getCallChains().get(0).getTriggerPoint(); TriggerPoint linkedTrigger = result.getMetadata().getCallChains().get(0).getTriggerPoint();
assertThat(linkedTrigger.getPolymorphicEvents()) assertThat(linkedTrigger.getPolymorphicEvents())
.contains("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP"); .contains("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP");
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(2); assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty();
assertThat(result.getMetadata().getCallChains().get(0).getLinkResolution())
.isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
} }
} }

View File

@@ -125,6 +125,21 @@
"metadata" : { "metadata" : {
"triggers" : [ ], "triggers" : [ ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/reactive",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "reactiveOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/reactive",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"type" : "REST", "type" : "REST",
"name" : "GET /api/base/{id}", "name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController", "className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
@@ -155,7 +170,46 @@
"annotations" : [ "PathVariable" ] "annotations" : [ "PathVariable" ]
} ] } ]
} ], } ],
"callChains" : [ ], "callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/reactive",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "reactiveOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/reactive",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.reactiveOrder", "click.kamil.examples.statemachine.extended.service.ReactiveOrderService.processReactive" ],
"triggerPoint" : {
"event" : "java.lang.String.REACTIVE_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
"methodName" : "processReactive",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "java.lang.String.START",
"lineNumber" : 18,
"polymorphicEvents" : [ "java.lang.String.REACTIVE_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.START",
"targetState" : "String.PROCESSING",
"event" : "String.REACTIVE_EVENT"
} ],
"linkResolution" : "RESOLVED"
} ],
"properties" : { "properties" : {
"default" : { "default" : {
"app.states.initial" : "INIT_STATE" "app.states.initial" : "INIT_STATE"

View File

@@ -125,6 +125,21 @@
"metadata" : { "metadata" : {
"triggers" : [ ], "triggers" : [ ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/reactive",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "reactiveOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/reactive",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"type" : "REST", "type" : "REST",
"name" : "GET /api/base/{id}", "name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController", "className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
@@ -155,7 +170,46 @@
"annotations" : [ "PathVariable" ] "annotations" : [ "PathVariable" ]
} ] } ]
} ], } ],
"callChains" : [ ], "callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/reactive",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "reactiveOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/reactive",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.reactiveOrder", "click.kamil.examples.statemachine.extended.service.ReactiveOrderService.processReactive" ],
"triggerPoint" : {
"event" : "java.lang.String.REACTIVE_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
"methodName" : "processReactive",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "java.lang.String.START",
"lineNumber" : 18,
"polymorphicEvents" : [ "java.lang.String.REACTIVE_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "String.START",
"targetState" : "String.PROCESSING",
"event" : "String.REACTIVE_EVENT"
} ],
"linkResolution" : "RESOLVED"
} ],
"properties" : { "properties" : {
"default" : { "default" : {
"app.states.initial" : "INIT_STATE" "app.states.initial" : "INIT_STATE"

View File

@@ -60,8 +60,62 @@
"eventTypeFqn" : "String", "eventTypeFqn" : "String",
"metadata" : { "metadata" : {
"triggers" : [ ], "triggers" : [ ],
"entryPoints" : [ ], "entryPoints" : [ {
"callChains" : [ ], "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" : {
"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"
} ],
"linkResolution" : "RESOLVED"
} ],
"properties" : { "properties" : {
"default" : { } "default" : { }
} }

View File

@@ -636,6 +636,45 @@
"event" : "String.AUTHORIZE" "event" : "String.AUTHORIZE"
} ], } ],
"linkResolution" : "RESOLVED" "linkResolution" : "RESOLVED"
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/payment/{id}/capture",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
"methodName" : "capturePaymentEndpoint",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java",
"metadata" : {
"path" : "/api/payment/{id}/capture",
"verb" : "POST"
},
"parameters" : [ {
"name" : "id",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
},
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.capturePaymentEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.capturePayment" ],
"triggerPoint" : {
"event" : "java.lang.String.CAPTURE",
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
"methodName" : "capturePayment",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : "paymentStateMachine",
"sourceState" : "java.lang.String.AUTHORIZED",
"lineNumber" : 35,
"polymorphicEvents" : [ "java.lang.String.CAPTURE" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : "paymentId",
"matchedTransitions" : [ {
"sourceState" : "String.AUTHORIZED",
"targetState" : "String.CAPTURED",
"event" : "String.CAPTURE"
} ],
"linkResolution" : "RESOLVED"
} ], } ],
"properties" : { "properties" : {
"default" : { "default" : {

View File

@@ -738,16 +738,8 @@
"ambiguous" : true "ambiguous" : true
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : null,
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "linkResolution" : "AMBIGUOUS_WIDEN"
"targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID",
"event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY"
}, {
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID",
"targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.CANCELED",
"event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.ABCD"
} ],
"linkResolution" : "RESOLVED"
} ], } ],
"properties" : { "properties" : {
"default" : { "default" : {