Fail closed on ambiguous polymorphic widening to prevent over-linking transitions.
Tighten linker, matcher, and canonicalizer guards for null sourceState inference, dynamic getter wildcards, predicate ceiling, and interface accessor ENUM_SET widen; update goldens for ambiguous internal triggers. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,7 +14,11 @@ import click.kamil.springstatemachineexporter.model.State;
|
||||
import click.kamil.springstatemachineexporter.model.Transition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine;
|
||||
@@ -61,9 +65,50 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
}
|
||||
|
||||
String triggerSource = tp.getSourceState() != null ? simplify(tp.getSourceState()) : null;
|
||||
if (shouldFailClosedOnAmbiguousCallGraphWiden(tp)) {
|
||||
updatedChains.add(chain.toBuilder()
|
||||
.triggerPoint(tp)
|
||||
.matchedTransitions(null)
|
||||
.build());
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean ambiguousSource = false;
|
||||
|
||||
if (triggerSource == null) {
|
||||
Map<String, Set<String>> sourcesByEvent = new LinkedHashMap<>();
|
||||
Map<String, String> canonicalSourceBySimplified = new LinkedHashMap<>();
|
||||
for (Transition t : stateMachineTransitions) {
|
||||
if (t.getEvent() != null && matchingEngine.matches(t.getEvent(), tp)
|
||||
&& isRoutedToCorrectMachine(chain, result.getName(), context)
|
||||
&& isConstraintCompatible(tp.getConstraint(), result.getName())) {
|
||||
String smEventForLink = canonicalEvent(t.getEvent());
|
||||
for (State smSourceState : t.getSourceStates()) {
|
||||
String smSourceForLink = canonicalState(smSourceState);
|
||||
String smSource = simplify(smSourceForLink);
|
||||
sourcesByEvent.computeIfAbsent(smEventForLink, ignored -> new LinkedHashSet<>())
|
||||
.add(smSource);
|
||||
canonicalSourceBySimplified.putIfAbsent(smSource, smSourceForLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sourcesByEvent.values().stream().anyMatch(sources -> sources.size() > 1)) {
|
||||
ambiguousSource = true;
|
||||
} else {
|
||||
Set<String> allDistinctSources = new LinkedHashSet<>();
|
||||
sourcesByEvent.values().forEach(allDistinctSources::addAll);
|
||||
if (allDistinctSources.size() == 1) {
|
||||
String inferredSimplifiedSource = allDistinctSources.iterator().next();
|
||||
String inferredCanonicalSource = canonicalSourceBySimplified.get(inferredSimplifiedSource);
|
||||
tp = tp.toBuilder().sourceState(inferredCanonicalSource).build();
|
||||
triggerSource = inferredSimplifiedSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<MatchedTransition> matched = new ArrayList<>();
|
||||
|
||||
if (!ambiguousSource) {
|
||||
for (Transition t : stateMachineTransitions) {
|
||||
if (t.getEvent() != null && matchingEngine.matches(t.getEvent(), tp)) {
|
||||
String smEventForLink = canonicalEvent(t.getEvent());
|
||||
@@ -99,6 +144,11 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ambiguousSource) {
|
||||
tp = tp.toBuilder().ambiguous(true).build();
|
||||
}
|
||||
|
||||
if (!matched.isEmpty()) {
|
||||
CallChain newChain = chain.toBuilder()
|
||||
@@ -176,6 +226,23 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
return event.fullIdentifier() != null ? event.fullIdentifier() : event.rawName();
|
||||
}
|
||||
|
||||
private boolean shouldFailClosedOnAmbiguousCallGraphWiden(TriggerPoint tp) {
|
||||
if (tp == null || tp.isExternal() || !tp.isAmbiguous()) {
|
||||
return false;
|
||||
}
|
||||
List<String> poly = tp.getPolymorphicEvents();
|
||||
if (poly == null || poly.size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
String event = tp.getEvent();
|
||||
if (event != null && event.startsWith("ENUM_SET:")) {
|
||||
return true;
|
||||
}
|
||||
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event)
|
||||
&& event != null
|
||||
&& !event.contains("valueOf");
|
||||
}
|
||||
|
||||
private TriggerPoint markRestEndpointExternal(TriggerPoint trigger, CallChain chain) {
|
||||
if (trigger == null || trigger.isExternal() || chain.getEntryPoint() == null) {
|
||||
return trigger;
|
||||
|
||||
@@ -65,6 +65,11 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine {
|
||||
if (triggerPoint.getEventTypeFqn() != null && !isStringTypeOrPrimitive(triggerPoint.getEventTypeFqn())) {
|
||||
return false;
|
||||
}
|
||||
// Getter/method-call expressions (e.g. richEvent.getId(), getType()) without resolved
|
||||
// polymorphic events must not wildcard-match bare or enum transition events.
|
||||
if (rawTriggerEvent.contains(".") || rawTriggerEvent.endsWith("()")) {
|
||||
return false;
|
||||
}
|
||||
if (triggerPoint.getEventTypeFqn() != null && isStringTypeOrPrimitive(triggerPoint.getEventTypeFqn())) {
|
||||
return !smEventRaw.contains(".");
|
||||
}
|
||||
|
||||
@@ -128,10 +128,14 @@ public final class MachineEnumCanonicalizer {
|
||||
if (!enumTypesMatch(machineEventTypeFqn, symbolicType)) {
|
||||
continue;
|
||||
}
|
||||
if (!transitionEvents.isEmpty()) {
|
||||
if (transitionEvents.size() == 1) {
|
||||
expanded.addAll(transitionEvents);
|
||||
continue;
|
||||
}
|
||||
if (!transitionEvents.isEmpty()) {
|
||||
// Multiple configured transitions: do not widen symbolic placeholders to all events.
|
||||
continue;
|
||||
}
|
||||
if (context != null) {
|
||||
List<String> enumValues = context.getEnumValues(stripGenerics(machineEventTypeFqn));
|
||||
if (enumValues != null) {
|
||||
@@ -246,7 +250,7 @@ public final class MachineEnumCanonicalizer {
|
||||
if (!narrowed.equals(expanded.getPolymorphicEvents())) {
|
||||
return expanded.toBuilder()
|
||||
.polymorphicEvents(narrowed)
|
||||
.ambiguous(narrowed.size() > 1)
|
||||
.ambiguous(narrowed.size() > 1 && expanded.isAmbiguous())
|
||||
.build();
|
||||
}
|
||||
return expanded;
|
||||
@@ -334,18 +338,15 @@ public final class MachineEnumCanonicalizer {
|
||||
if (transitionEvents.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<String> filtered = EnumMemberPredicateEvaluator.filterEnumConstants(
|
||||
transitionEvents, constraint, eventTypeFqn, context);
|
||||
if (!filtered.isEmpty()) {
|
||||
return filtered;
|
||||
}
|
||||
if (EnumMemberPredicateEvaluator.hasEnumMemberPredicates(constraint)) {
|
||||
if (EnumMemberPredicateEvaluator.hasResolvablePredicateMethods(constraint, eventTypeFqn, context)) {
|
||||
return List.of();
|
||||
List<String> filtered = EnumMemberPredicateEvaluator.filterEnumConstants(
|
||||
transitionEvents, constraint, eventTypeFqn, context);
|
||||
if (!filtered.isEmpty()) {
|
||||
return filtered;
|
||||
}
|
||||
return transitionEvents;
|
||||
return List.of();
|
||||
}
|
||||
return transitionEvents;
|
||||
return transitionEvents.size() == 1 ? transitionEvents : List.of();
|
||||
}
|
||||
|
||||
private static List<String> narrowPolymorphicCandidates(
|
||||
@@ -367,11 +368,8 @@ public final class MachineEnumCanonicalizer {
|
||||
transitionEvents, constraint, eventTypeFqn, context);
|
||||
if (!filtered.isEmpty()) {
|
||||
result = filtered;
|
||||
} else if (EnumMemberPredicateEvaluator.hasResolvablePredicateMethods(
|
||||
constraint, eventTypeFqn, context)) {
|
||||
result = List.of();
|
||||
} else {
|
||||
result = transitionEvents;
|
||||
result = List.of();
|
||||
}
|
||||
} else {
|
||||
result = List.of();
|
||||
|
||||
@@ -390,21 +390,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (debug) {
|
||||
log.debug("Early return 2: getterEvents = {}", getterEvents);
|
||||
}
|
||||
return TriggerPoint.builder()
|
||||
.event(resolvedValue)
|
||||
.className(tp.getClassName())
|
||||
.methodName(tp.getMethodName())
|
||||
.sourceFile(tp.getSourceFile())
|
||||
.sourceModule(tp.getSourceModule())
|
||||
.stateMachineId(tp.getStateMachineId())
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(getterEvents)
|
||||
.constraint(tp.getConstraint())
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.build();
|
||||
return buildTriggerPointWithPolymorphicEvents(tp, resolvedValue, getterEvents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +429,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.ambiguous(polymorphicEvents.size() > 1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -924,6 +911,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (polymorphicEvents.size() > 1) {
|
||||
isAmbiguous = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1079,6 +1069,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.ambiguous(isAmbiguous || polymorphicEvents.size() > 1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -1633,6 +1624,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
* and, when possible, a {@code new ...()} instance from the getter body.
|
||||
*/
|
||||
private TriggerPoint buildTriggerPointWithPolymorphicEvents(TriggerPoint tp, String resolvedValue, List<String> polymorphicEvents) {
|
||||
boolean ambiguous = tp.isAmbiguous()
|
||||
|| (polymorphicEvents != null && polymorphicEvents.size() > 1);
|
||||
return TriggerPoint.builder()
|
||||
.event(resolvedValue)
|
||||
.className(tp.getClassName())
|
||||
@@ -1647,6 +1640,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.ambiguous(ambiguous)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -419,6 +419,9 @@ public final class AnalysisCanonicalFormValidator {
|
||||
if (LifecycleTriggerMarkers.isLifecycle(trigger.getEvent())) {
|
||||
return;
|
||||
}
|
||||
if (trigger.isAmbiguous() && !trigger.isExternal()) {
|
||||
return;
|
||||
}
|
||||
if (!MachineEnumCanonicalizer.hasOnlyConcretePolymorphicEvents(trigger.getPolymorphicEvents())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,10 +121,34 @@ class TransitionLinkerEnricherTest {
|
||||
enricher.enrich(result, null, null);
|
||||
|
||||
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
|
||||
assertThat(updatedChain.getMatchedTransitions()).hasSize(2);
|
||||
assertThat(updatedChain.getMatchedTransitions())
|
||||
.extracting("sourceState")
|
||||
.containsExactlyInAnyOrder("NEW", "FAILED");
|
||||
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
|
||||
assertThat(updatedChain.getTriggerPoint().isAmbiguous()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLinkTransitionByEventWhenSourceStateIsUniquelyDeterminedByTransitionTable() {
|
||||
Transition t1 = new Transition();
|
||||
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
|
||||
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
|
||||
t1.setEvent(Event.of("PAY", "PAY"));
|
||||
|
||||
CallChain chain = CallChain.builder()
|
||||
.triggerPoint(TriggerPoint.builder().event("PAY").build())
|
||||
.build();
|
||||
|
||||
AnalysisResult result = AnalysisResult.builder()
|
||||
.transitions(List.of(t1))
|
||||
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
|
||||
.build();
|
||||
|
||||
enricher.enrich(result, null, null);
|
||||
|
||||
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
|
||||
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
|
||||
assertThat(updatedChain.getMatchedTransitions().get(0).getSourceState()).isEqualTo("NEW");
|
||||
assertThat(updatedChain.getMatchedTransitions().get(0).getTargetState()).isEqualTo("PAID");
|
||||
assertThat(updatedChain.getTriggerPoint().getSourceState()).isEqualTo("NEW");
|
||||
assertThat(updatedChain.getTriggerPoint().isAmbiguous()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -307,7 +331,7 @@ class TransitionLinkerEnricherTest {
|
||||
enricher.enrich(result, null, null);
|
||||
|
||||
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
|
||||
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
|
||||
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -284,4 +284,35 @@ class StrictFqnMatchingEngineTest {
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchGetterChainWithoutPolymorphicEventsAgainstBareStringEvent() {
|
||||
Event smEvent = Event.of("PAY", "PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("richEvent.getId()")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchGetterChainWithoutPolymorphicEventsAgainstEnumEvent() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("payload.getType()")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchMethodCallWithoutPolymorphicEventsAgainstBareStringEvent() {
|
||||
Event smEvent = Event.of("PAY", "PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("getType()")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ class EnumMemberPredicatePolymorphicInferenceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallBackToTransitionEventsWhenPredicateEvaluationIsInconclusive(@TempDir Path tempDir) throws Exception {
|
||||
void shouldFailClosedWhenPredicateMethodCannotBeResolved(@TempDir Path tempDir) throws Exception {
|
||||
CodebaseContext context = scanEnumProject(tempDir, """
|
||||
package com.example.order;
|
||||
public enum OrderCommand {
|
||||
@@ -304,10 +304,7 @@ class EnumMemberPredicatePolymorphicInferenceTest {
|
||||
TriggerPoint enriched = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
||||
trigger, types, context, transitions, true);
|
||||
|
||||
assertThat(enriched.getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder(
|
||||
"com.example.order.OrderCommand.PAY",
|
||||
"com.example.order.OrderCommand.SHIP");
|
||||
assertThat(enriched.getPolymorphicEvents()).isNullOrEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -293,7 +293,7 @@ class MachineEnumCanonicalizerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInferPolymorphicEventsFromTransitionsWhenContextIsUnavailable() {
|
||||
void shouldNotInferMultipleTransitionEventsForUnresolvedValueOf() {
|
||||
Transition pay = new Transition();
|
||||
pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY"));
|
||||
Transition ship = new Transition();
|
||||
@@ -309,11 +309,44 @@ class MachineEnumCanonicalizerTest {
|
||||
TriggerPoint linked = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
||||
trigger, types, null, List.of(pay, ship));
|
||||
|
||||
assertThat(linked.getPolymorphicEvents()).isNullOrEmpty();
|
||||
assertThat(linked.isAmbiguous()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInferSingleTransitionEventForValueOfWhenOnlyOneConfigured() {
|
||||
Transition pay = new Transition();
|
||||
pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY"));
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.event("OrderEvent.valueOf(eventString)")
|
||||
.build();
|
||||
|
||||
StateMachineTypeResolver.MachineTypes types = new StateMachineTypeResolver.MachineTypes(
|
||||
"com.example.order.OrderState", "com.example.order.OrderEvent");
|
||||
|
||||
TriggerPoint linked = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
||||
trigger, types, null, List.of(pay));
|
||||
|
||||
assertThat(linked.getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder(
|
||||
"com.example.order.OrderEvent.PAY",
|
||||
"com.example.order.OrderEvent.SHIP");
|
||||
assertThat(linked.isAmbiguous()).isTrue();
|
||||
.containsExactly("com.example.order.OrderEvent.PAY");
|
||||
assertThat(linked.isAmbiguous()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotExpandSymbolicToMultipleConfiguredTransitionEvents() {
|
||||
Transition pay = new Transition();
|
||||
pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY"));
|
||||
Transition ship = new Transition();
|
||||
ship.setEvent(Event.of("OrderEvent.SHIP", "com.example.order.OrderEvent.SHIP"));
|
||||
|
||||
List<String> expanded = MachineEnumCanonicalizer.expandSymbolicPolymorphicEvents(
|
||||
List.of("<SYMBOLIC: com.example.order.OrderEvent.*>"),
|
||||
"com.example.order.OrderEvent",
|
||||
null,
|
||||
List.of(pay, ship));
|
||||
|
||||
assertThat(expanded).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -77,6 +77,47 @@ class DispatcherPolyCeilingPipelineTest {
|
||||
.containsExactlyInAnyOrder(EVENT_TYPE + ".PAY", EVENT_TYPE + ".SHIP");
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkerShouldNotInferAllTransitionEventsForUnresolvedValueOf(@TempDir Path tempDir) throws Exception {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class ApiController {
|
||||
CentralDispatcher dispatcher;
|
||||
public void handle(String action) { dispatcher.route(action); }
|
||||
}
|
||||
class CentralDispatcher {
|
||||
StateMachine machine;
|
||||
public void route(String action) { machine.sendEvent(OrderEvent.valueOf(action)); }
|
||||
}
|
||||
class StateMachine { public void sendEvent(OrderEvent e) {} }
|
||||
enum OrderEvent { PAY, SHIP, LOG, META }
|
||||
class OrderStateMachineConfig {}
|
||||
""";
|
||||
CodebaseContext context = scanSource(source, tempDir);
|
||||
|
||||
CallChain raw = resolveChain(
|
||||
context,
|
||||
"com.example.ApiController",
|
||||
"handle",
|
||||
"com.example.StateMachine",
|
||||
"sendEvent",
|
||||
EngineKind.JDT);
|
||||
|
||||
TriggerPoint stripped = raw.getTriggerPoint().toBuilder()
|
||||
.polymorphicEvents(null)
|
||||
.build();
|
||||
CallChain chain = raw.toBuilder().triggerPoint(stripped).build();
|
||||
|
||||
Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY");
|
||||
Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP");
|
||||
|
||||
CallChain linked = linkChain(
|
||||
context, chain, MACHINE_CONFIG, EVENT_TYPE, STATE_TYPE, pay, ship);
|
||||
|
||||
assertThat(linked.getTriggerPoint().getPolymorphicEvents()).isNullOrEmpty();
|
||||
assertThat(linked.getMatchedTransitions()).isNullOrEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkerShouldCapCallGraphPolyForDeepDispatcherToTransitionCeiling(@TempDir Path tempDir) throws Exception {
|
||||
String source = """
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
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.exporter.ExportOptions;
|
||||
import click.kamil.springstatemachineexporter.exporter.JsonExporter;
|
||||
import click.kamil.springstatemachineexporter.service.ExportService;
|
||||
import click.kamil.springstatemachineexporter.service.JsonImportService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -19,92 +14,15 @@ import java.util.List;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Regression for REST dispatcher chains that lost {@code polymorphicEvents} during JSON round-trip.
|
||||
* Regression for REST dispatcher chains: JSON round-trip must not re-infer all transition events
|
||||
* for generic {@code {event}} endpoints.
|
||||
*/
|
||||
class EnterpriseDispatcherMatchedTransitionsRegressionTest {
|
||||
|
||||
@Test
|
||||
void jsonReExportWithSourceShouldRestoreDispatcherMatchedTransitions(@TempDir Path tempDir) throws Exception {
|
||||
Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise");
|
||||
|
||||
Path astOut = tempDir.resolve("ast");
|
||||
ExportService exportService = new ExportService(List.of(new JsonExporter()));
|
||||
exportService.runExporter(enterprise, astOut, List.of("json"), true, List.of(), null, null,
|
||||
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
|
||||
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
|
||||
|
||||
Path astJson = astOut.resolve(
|
||||
"click.kamil.enterprise.machines.order.OrderStateMachineConfiguration/click.kamil.enterprise.machines.order.OrderStateMachineConfiguration.json");
|
||||
|
||||
AnalysisResult imported = new JsonImportService().importAnalysisResult(astJson);
|
||||
List<CallChain> corruptedChains = imported.getMetadata().getCallChains().stream()
|
||||
.map(chain -> {
|
||||
if (chain.getTriggerPoint() == null
|
||||
|| chain.getMethodChain() == null
|
||||
|| !chain.getMethodChain().stream()
|
||||
.anyMatch(m -> m.contains("StateMachineDispatcher.dispatch"))) {
|
||||
return chain;
|
||||
}
|
||||
return chain.toBuilder()
|
||||
.triggerPoint(chain.getTriggerPoint().toBuilder()
|
||||
.event("event")
|
||||
.external(false)
|
||||
.polymorphicEvents(null)
|
||||
.build())
|
||||
.matchedTransitions(null)
|
||||
.build();
|
||||
})
|
||||
.toList();
|
||||
imported.setMetadata(CodebaseMetadata.builder()
|
||||
.triggers(imported.getMetadata().getTriggers())
|
||||
.entryPoints(imported.getMetadata().getEntryPoints())
|
||||
.callChains(corruptedChains)
|
||||
.properties(imported.getMetadata().getProperties())
|
||||
.build());
|
||||
|
||||
Path brokenJson = tempDir.resolve("broken-order-machine.json");
|
||||
Files.writeString(brokenJson, new JsonExporter().export(imported, ExportOptions.builder().build()));
|
||||
|
||||
Path outputDir = tempDir.resolve("out");
|
||||
exportService.runJsonExporter(
|
||||
brokenJson, outputDir, List.of("json"), List.of(),
|
||||
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
|
||||
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
|
||||
enterprise);
|
||||
|
||||
Path finalized = outputDir.resolve(
|
||||
"click.kamil.enterprise.machines.order.OrderStateMachineConfiguration/click.kamil.enterprise.machines.order.OrderStateMachineConfiguration.json");
|
||||
AnalysisResult roundTripped = new JsonImportService().importAnalysisResult(finalized);
|
||||
|
||||
assertThat(roundTripped.getMetadata().getCallChains())
|
||||
.as("call chains after re-export")
|
||||
.isNotEmpty();
|
||||
|
||||
CallChain chain = roundTripped.getMetadata().getCallChains().stream()
|
||||
.filter(c -> c.getMethodChain() != null && c.getMethodChain().stream()
|
||||
.anyMatch(m -> m.contains("StateMachineDispatcher.dispatch")))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"no dispatcher chain among: "
|
||||
+ roundTripped.getMetadata().getCallChains().stream()
|
||||
.map(c -> c.getEntryPoint() != null ? c.getEntryPoint().getName() : "null")
|
||||
.toList()));
|
||||
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.as("polymorphicEvents on %s", chain.getEntryPoint().getName())
|
||||
.isNotNull()
|
||||
.isNotEmpty();
|
||||
assertThat(chain.getTriggerPoint().isExternal())
|
||||
.as("external flag on %s", chain.getEntryPoint().getName())
|
||||
.isTrue();
|
||||
assertThat(chain.getMatchedTransitions())
|
||||
.as("matchedTransitions on %s", chain.getEntryPoint().getName())
|
||||
.isNotNull()
|
||||
.isNotEmpty();
|
||||
}
|
||||
private static final String TRANSITION_ENDPOINT = "POST /api/machine/{machineType}/transition/{event}";
|
||||
|
||||
@Test
|
||||
void astExportTransitionEndpointShouldLinkMatchedTransitions(@TempDir Path tempDir) throws Exception {
|
||||
void astExportGenericTransitionEndpointShouldRemainUnlinked(@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,
|
||||
@@ -116,15 +34,14 @@ class EnterpriseDispatcherMatchedTransitionsRegressionTest {
|
||||
|
||||
assertThat(chain.path("triggerPoint").path("external").asBoolean()).isTrue();
|
||||
assertThat(chain.path("triggerPoint").path("polymorphicEvents").isArray()).isTrue();
|
||||
assertThat(chain.path("triggerPoint").path("polymorphicEvents")).isNotEmpty();
|
||||
assertThat(chain.path("matchedTransitions").isArray()).isTrue();
|
||||
assertThat(chain.path("matchedTransitions")).isNotEmpty();
|
||||
assertThat(chain.path("triggerPoint").path("polymorphicEvents")).isEmpty();
|
||||
JsonNode matched = chain.path("matchedTransitions");
|
||||
assertThat(matched.isNull() || (matched.isArray() && matched.isEmpty())).isTrue();
|
||||
}
|
||||
|
||||
private static JsonNode findTransitionEndpointChain(JsonNode machineJson) {
|
||||
for (JsonNode chain : machineJson.path("metadata").path("callChains")) {
|
||||
if ("POST /api/machine/{machineType}/transition/{event}".equals(
|
||||
chain.path("entryPoint").path("name").asText())) {
|
||||
if (TRANSITION_ENDPOINT.equals(chain.path("entryPoint").path("name").asText())) {
|
||||
return chain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||
import click.kamil.springstatemachineexporter.model.Transition;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Interface accessor widen must not union all implementation return values at ambiguous call sites.
|
||||
*/
|
||||
class InterfaceAccessorPolyPipelineTest {
|
||||
|
||||
private static final String MACHINE_CONFIG = "com.example.OrderStateMachineConfig";
|
||||
private static final String EVENT_TYPE = "com.example.OrderEvent";
|
||||
private static final String STATE_TYPE = "com.example.OrderState";
|
||||
|
||||
@Test
|
||||
void linkerShouldNotMatchAllTransitionsForInterfaceTypedEventSource(@TempDir Path tempDir) throws Exception {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class ApiController {
|
||||
EventHandler handler;
|
||||
public void dispatch() { handler.fire(); }
|
||||
}
|
||||
class EventHandler {
|
||||
StateMachine machine;
|
||||
EventSource source;
|
||||
void fire() { machine.sendEvent(source.getEvent()); }
|
||||
}
|
||||
interface EventSource { OrderEvent getEvent(); }
|
||||
class PaySource implements EventSource {
|
||||
public OrderEvent getEvent() { return OrderEvent.PAY; }
|
||||
}
|
||||
class ShipSource implements EventSource {
|
||||
public OrderEvent getEvent() { return OrderEvent.SHIP; }
|
||||
}
|
||||
class StateMachine { public void sendEvent(OrderEvent e) {} }
|
||||
enum OrderEvent { PAY, SHIP, LOG, META }
|
||||
class OrderStateMachineConfig {}
|
||||
""";
|
||||
CodebaseContext context = scanSource(source, tempDir);
|
||||
|
||||
Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY");
|
||||
Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP");
|
||||
|
||||
CallChain raw = resolveChain(context, "com.example.ApiController", "dispatch",
|
||||
"com.example.StateMachine", "sendEvent", EngineKind.JDT);
|
||||
|
||||
CallChain linked = linkChain(
|
||||
context,
|
||||
raw,
|
||||
MACHINE_CONFIG,
|
||||
EVENT_TYPE,
|
||||
STATE_TYPE,
|
||||
pay,
|
||||
ship);
|
||||
|
||||
assertThat(linked.getTriggerPoint().isAmbiguous()).isTrue();
|
||||
assertThat(linked.getMatchedTransitions()).isNullOrEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkerShouldResolveConcretePaySourceImplementation(@TempDir Path tempDir) throws Exception {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class ApiController {
|
||||
EventHandler handler;
|
||||
public void pay() { handler.fire(new PaySource()); }
|
||||
}
|
||||
class EventHandler {
|
||||
StateMachine machine;
|
||||
void fire(EventSource source) { machine.sendEvent(source.getEvent()); }
|
||||
}
|
||||
interface EventSource { OrderEvent getEvent(); }
|
||||
class PaySource implements EventSource {
|
||||
public OrderEvent getEvent() { return OrderEvent.PAY; }
|
||||
}
|
||||
class ShipSource implements EventSource {
|
||||
public OrderEvent getEvent() { return OrderEvent.SHIP; }
|
||||
}
|
||||
class StateMachine { public void sendEvent(OrderEvent e) {} }
|
||||
enum OrderEvent { PAY, SHIP, LOG, META }
|
||||
class OrderStateMachineConfig {}
|
||||
""";
|
||||
CodebaseContext context = scanSource(source, tempDir);
|
||||
|
||||
Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY");
|
||||
Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP");
|
||||
|
||||
CallChain linked = resolveLinkAndAssertSingleMatch(
|
||||
context,
|
||||
"com.example.ApiController",
|
||||
"pay",
|
||||
"com.example.StateMachine",
|
||||
"sendEvent",
|
||||
MACHINE_CONFIG,
|
||||
EVENT_TYPE,
|
||||
STATE_TYPE,
|
||||
EVENT_TYPE + ".PAY",
|
||||
EngineKind.JDT,
|
||||
pay,
|
||||
ship);
|
||||
|
||||
assertPolyCappedToTransitionEvents(linked, EVENT_TYPE, pay, ship);
|
||||
}
|
||||
}
|
||||
@@ -181,25 +181,13 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 87,
|
||||
"polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT", "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE", "click.kamil.enterprise.machines.document.DocumentEvent.REJECT" ],
|
||||
"polymorphicEvents" : [ ],
|
||||
"external" : true,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
"ambiguous" : true
|
||||
},
|
||||
"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"
|
||||
}, {
|
||||
"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"
|
||||
}, {
|
||||
"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"
|
||||
} ]
|
||||
"matchedTransitions" : null
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : { }
|
||||
|
||||
@@ -364,7 +364,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.NEW",
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : [ "java.lang.String.PLACE_ORDER" ],
|
||||
"external" : false,
|
||||
@@ -402,7 +402,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.PAID",
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : [ "java.lang.String.CANCEL_ORDER" ],
|
||||
"external" : false,
|
||||
@@ -469,7 +469,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.PENDING_PAYMENT",
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : [ "java.lang.String.PAY_ORDER" ],
|
||||
"external" : false,
|
||||
@@ -507,7 +507,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.PAID",
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
@@ -545,7 +545,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.DELIVERED",
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.START",
|
||||
"lineNumber" : 15,
|
||||
"polymorphicEvents" : [ "java.lang.String.INHERITED_SUBMIT" ],
|
||||
"external" : false,
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.INIT",
|
||||
"lineNumber" : 40,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "java.lang.String.NEW",
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
|
||||
@@ -158,21 +158,13 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "click.kamil.enterprise.machines.order.OrderEvent.PAY", "click.kamil.enterprise.machines.order.OrderEvent.SHIP" ],
|
||||
"polymorphicEvents" : [ ],
|
||||
"external" : true,
|
||||
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
"ambiguous" : true
|
||||
},
|
||||
"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"
|
||||
}, {
|
||||
"sourceState" : "click.kamil.enterprise.machines.order.OrderState.PENDING",
|
||||
"targetState" : "click.kamil.enterprise.machines.order.OrderState.SHIPPED",
|
||||
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP"
|
||||
} ]
|
||||
"matchedTransitions" : null
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : { }
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ],
|
||||
"external" : false,
|
||||
@@ -321,7 +321,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.FULFILL" ],
|
||||
"external" : false,
|
||||
@@ -355,7 +355,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ],
|
||||
"external" : false,
|
||||
@@ -389,7 +389,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ],
|
||||
"external" : false,
|
||||
@@ -423,7 +423,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ],
|
||||
"external" : false,
|
||||
@@ -457,7 +457,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ],
|
||||
"external" : false,
|
||||
@@ -500,18 +500,10 @@
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY", "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
"ambiguous" : true
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID",
|
||||
"event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY"
|
||||
}, {
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.CANCELED",
|
||||
"event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL"
|
||||
} ]
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
@@ -533,7 +525,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ],
|
||||
"external" : false,
|
||||
@@ -567,7 +559,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.FULFILL" ],
|
||||
"external" : false,
|
||||
@@ -601,7 +593,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ],
|
||||
"external" : false,
|
||||
@@ -635,7 +627,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID",
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.ABCD" ],
|
||||
"external" : false,
|
||||
@@ -674,18 +666,10 @@
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.ABCD", "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
"ambiguous" : true
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED",
|
||||
"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"
|
||||
} ]
|
||||
"matchedTransitions" : null
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : {
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.DRAFT",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.SUBMIT" ],
|
||||
"external" : false,
|
||||
@@ -190,7 +190,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.APPROVE" ],
|
||||
"external" : false,
|
||||
@@ -224,7 +224,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.REJECT" ],
|
||||
"external" : false,
|
||||
@@ -262,7 +262,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.DRAFT",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.SUBMIT" ],
|
||||
"external" : false,
|
||||
@@ -300,7 +300,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.APPROVE" ],
|
||||
"external" : false,
|
||||
@@ -338,7 +338,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.REJECT" ],
|
||||
"external" : false,
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ],
|
||||
"external" : false,
|
||||
@@ -234,7 +234,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ],
|
||||
"external" : false,
|
||||
@@ -268,7 +268,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.CANCEL" ],
|
||||
"external" : false,
|
||||
@@ -302,7 +302,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ],
|
||||
"external" : false,
|
||||
@@ -336,7 +336,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ],
|
||||
"external" : false,
|
||||
@@ -370,7 +370,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ],
|
||||
"external" : false,
|
||||
@@ -404,7 +404,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ],
|
||||
"external" : false,
|
||||
@@ -442,7 +442,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ],
|
||||
"external" : false,
|
||||
@@ -480,7 +480,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ],
|
||||
"external" : false,
|
||||
@@ -518,7 +518,7 @@
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID",
|
||||
"lineNumber" : 75,
|
||||
"polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.CANCEL" ],
|
||||
"external" : false,
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.domain.OrderState.NEW",
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.PROCESS" ],
|
||||
"external" : false,
|
||||
@@ -274,7 +274,7 @@
|
||||
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.domain.OrderState.PROCESSING",
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.COMPLETE" ],
|
||||
"external" : false,
|
||||
@@ -313,18 +313,10 @@
|
||||
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.CANCEL" ],
|
||||
"external" : false,
|
||||
"constraint" : "event instanceof OrderEvent",
|
||||
"ambiguous" : false
|
||||
"ambiguous" : true
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "click.kamil.domain.OrderState.NEW",
|
||||
"targetState" : "click.kamil.domain.OrderState.CANCELLED",
|
||||
"event" : "click.kamil.domain.OrderEvent.CANCEL"
|
||||
}, {
|
||||
"sourceState" : "click.kamil.domain.OrderState.PROCESSING",
|
||||
"targetState" : "click.kamil.domain.OrderState.CANCELLED",
|
||||
"event" : "click.kamil.domain.OrderEvent.CANCEL"
|
||||
} ]
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
@@ -346,7 +338,7 @@
|
||||
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.domain.OrderState.PROCESSING",
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.COMPLETE" ],
|
||||
"external" : false,
|
||||
@@ -384,7 +376,7 @@
|
||||
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.domain.OrderState.NEW",
|
||||
"lineNumber" : 78,
|
||||
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.PROCESS" ],
|
||||
"external" : false,
|
||||
@@ -422,7 +414,7 @@
|
||||
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"sourceState" : "click.kamil.domain.OrderState.NEW",
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.PROCESS" ],
|
||||
"external" : false,
|
||||
@@ -465,18 +457,10 @@
|
||||
"polymorphicEvents" : [ "click.kamil.domain.OrderEvent.CANCEL" ],
|
||||
"external" : false,
|
||||
"constraint" : "event != null",
|
||||
"ambiguous" : false
|
||||
"ambiguous" : true
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "click.kamil.domain.OrderState.NEW",
|
||||
"targetState" : "click.kamil.domain.OrderState.CANCELLED",
|
||||
"event" : "click.kamil.domain.OrderEvent.CANCEL"
|
||||
}, {
|
||||
"sourceState" : "click.kamil.domain.OrderState.PROCESSING",
|
||||
"targetState" : "click.kamil.domain.OrderState.CANCELLED",
|
||||
"event" : "click.kamil.domain.OrderEvent.CANCEL"
|
||||
} ]
|
||||
"matchedTransitions" : null
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : { }
|
||||
|
||||
@@ -151,21 +151,13 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 93,
|
||||
"polymorphicEvents" : [ "click.kamil.enterprise.machines.user.UserEvent.REGISTER", "click.kamil.enterprise.machines.user.UserEvent.VERIFY" ],
|
||||
"polymorphicEvents" : [ ],
|
||||
"external" : true,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
"ambiguous" : true
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "click.kamil.enterprise.machines.user.UserState.GUEST",
|
||||
"targetState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED",
|
||||
"event" : "click.kamil.enterprise.machines.user.UserEvent.REGISTER"
|
||||
}, {
|
||||
"sourceState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED",
|
||||
"targetState" : "click.kamil.enterprise.machines.user.UserState.VERIFIED",
|
||||
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY"
|
||||
} ]
|
||||
"matchedTransitions" : null
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : { }
|
||||
|
||||
Reference in New Issue
Block a user