From c3c5d66031ee3acc66e0aca4de5934a5791e237f Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Mon, 13 Jul 2026 18:55:36 +0200 Subject: [PATCH] Split boolean ternary endpoints into provable branches and scope payment exports. Boolean request-param ternaries now expand into true/false binding variants so each call chain resolves a single event instead of AMBIGUOUS_WIDEN. Setter/constructor @Qualifier injection is traced for stateMachineId, and PaymentStateMachineConfig gets a dedicated golden with payment endpoints linked via paymentStateMachine. Co-authored-by: Cursor --- .../resolver/BooleanConstraintEvaluator.java | 7 + .../service/AbstractCallGraphEngine.java | 46 +- .../service/EntryPointBindingExpander.java | 164 ++- .../service/GenericEventDetector.java | 80 +- .../GoldenUpdater.java | 6 + .../PlantUmlE2ETest.java | 6 + .../RegressionTest.java | 6 + .../BooleanTernaryBranchSplitTest.java | 122 ++ .../service/GenericEventDetectorFireTest.java | 35 + .../PaymentStateMachineConfig.dot | 22 + .../PaymentStateMachineConfig.json | 1030 +++++++++++++++++ .../PaymentStateMachineConfig.png | Bin 0 -> 43726 bytes .../PaymentStateMachineConfig.puml | 42 + .../PaymentStateMachineConfig.scxml.xml | 27 + .../PolymorphicStateMachineConfiguration.json | 78 +- 15 files changed, 1649 insertions(+), 22 deletions(-) create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/BooleanTernaryBranchSplitTest.java create mode 100644 state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.dot create mode 100644 state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.json create mode 100644 state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.png create mode 100644 state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.puml create mode 100644 state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.scxml.xml diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java index d059ee4..cd306b1 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java @@ -102,6 +102,9 @@ public final class BooleanConstraintEvaluator { if (boundValue.startsWith("\"") && boundValue.endsWith("\"")) { return true; } + if ("true".equalsIgnoreCase(boundValue) || "false".equalsIgnoreCase(boundValue)) { + return true; + } if (boundValue.contains(".") && Character.isUpperCase(boundValue.charAt(boundValue.lastIndexOf('.') + 1))) { return true; } @@ -174,6 +177,10 @@ public final class BooleanConstraintEvaluator { matcher.appendReplacement(sb, matches ? "true" : "false"); } matcher.appendTail(sb); + if ("true".equalsIgnoreCase(cleanValue) || "false".equalsIgnoreCase(cleanValue)) { + Pattern bareVar = Pattern.compile("(?> bindingVariants = - EntryPointBindingExpander.expandPathVariableBindings(ep, context, callGraph); + EntryPointBindingExpander.expandEntryPointBindings(ep, context, callGraph); if (bindingVariants.isEmpty()) { bindingVariants = List.of(Map.of()); } @@ -213,6 +213,36 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { return triggerConstraint; } + private static Expression selectTernaryBranch(ConditionalExpression cond, Map bindings) { + if (cond == null || bindings == null || bindings.isEmpty()) { + return null; + } + Expression condition = cond.getExpression(); + if (condition instanceof PrefixExpression prefixExpression + && prefixExpression.getOperator() == PrefixExpression.Operator.NOT + && prefixExpression.getOperand() instanceof SimpleName negatedName) { + String bound = bindings.get(negatedName.getIdentifier()); + if ("true".equals(bound)) { + return cond.getElseExpression(); + } + if ("false".equals(bound)) { + return cond.getThenExpression(); + } + return null; + } + if (!(condition instanceof SimpleName simpleName)) { + return null; + } + String bound = bindings.get(simpleName.getIdentifier()); + if ("true".equals(bound)) { + return cond.getThenExpression(); + } + if ("false".equals(bound)) { + return cond.getElseExpression(); + } + return null; + } + protected TriggerPoint resolveTriggerPointParametersOriginal( TriggerPoint tp, List path, @@ -679,9 +709,16 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { polymorphicEvents.add(declaredType); } } else if (exprNode instanceof ConditionalExpression cond) { + Expression selectedBranch = selectTernaryBranch(cond, initialBindings); List branches = new ArrayList<>(); - branches.add(cond.getThenExpression()); - branches.add(cond.getElseExpression()); + if (selectedBranch != null) { + branches.add(selectedBranch); + resolvedValue = selectedBranch.toString(); + exprNode = selectedBranch; + } else { + branches.add(cond.getThenExpression()); + branches.add(cond.getElseExpression()); + } for (Expression branch : branches) { if (branch instanceof ConditionalExpression nestedCond) { // traceVariableAll collapses literal "true"/"false" conditions to a single branch; @@ -2192,6 +2229,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { if (value == null || value.isBlank() || value.contains("(")) { continue; } + if ("true".equals(value) || "false".equals(value)) { + continue; + } parts.add("\"" + value + "\".equalsIgnoreCase(" + entry.getKey() + ")"); } return parts.isEmpty() ? null : String.join(" && ", parts); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpander.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpander.java index c4e6436..ae3b33b 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpander.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpander.java @@ -16,6 +16,33 @@ public final class EntryPointBindingExpander { private EntryPointBindingExpander() { } + /** + * Expands an entry point into concrete binding variants from path/query parameters: + * string switch/if arms and boolean ternary conditions provable in source. + */ + public static List> expandEntryPointBindings( + EntryPoint entryPoint, + CodebaseContext context, + Map> callGraph) { + List> pathVariants = expandPathVariableBindings(entryPoint, context, callGraph); + List> booleanVariants = expandBooleanTernaryBindings(entryPoint, context, callGraph); + if (pathVariants.isEmpty()) { + return booleanVariants; + } + if (booleanVariants.isEmpty()) { + return pathVariants; + } + List> combined = new ArrayList<>(); + for (Map pathVariant : pathVariants) { + for (Map booleanVariant : booleanVariants) { + Map merged = new LinkedHashMap<>(pathVariant); + merged.putAll(booleanVariant); + combined.add(merged); + } + } + return combined; + } + public static List> expandPathVariableBindings( EntryPoint entryPoint, CodebaseContext context, @@ -55,14 +82,37 @@ public final class EntryPointBindingExpander { Map metadata = entryPoint.getMetadata() == null ? new LinkedHashMap<>() : new LinkedHashMap<>(entryPoint.getMetadata()); + StringBuilder querySuffix = new StringBuilder(); for (Map.Entry binding : bindings.entrySet()) { String placeholder = "{" + binding.getKey() + "}"; + boolean pathPlaceholder = name != null && name.contains(placeholder); + String path = metadata.get("path"); + if (!pathPlaceholder && path != null) { + pathPlaceholder = path.contains(placeholder); + } + if (pathPlaceholder) { + if (name != null) { + name = name.replace(placeholder, binding.getValue()); + } + if (path != null) { + metadata.put("path", path.replace(placeholder, binding.getValue())); + } + } else if (isBooleanBindingValue(binding.getValue())) { + if (querySuffix.isEmpty()) { + querySuffix.append('?'); + } else { + querySuffix.append('&'); + } + querySuffix.append(binding.getKey()).append('=').append(binding.getValue()); + } + } + if (!querySuffix.isEmpty()) { if (name != null) { - name = name.replace(placeholder, binding.getValue()); + name = name + querySuffix; } String path = metadata.get("path"); if (path != null) { - metadata.put("path", path.replace(placeholder, binding.getValue())); + metadata.put("path", path + querySuffix); } } return EntryPoint.builder() @@ -92,6 +142,116 @@ public final class EntryPointBindingExpander { return false; } + public static List> expandBooleanTernaryBindings( + EntryPoint entryPoint, + CodebaseContext context, + Map> callGraph) { + if (entryPoint == null || entryPoint.getParameters() == null || entryPoint.getParameters().isEmpty()) { + return List.of(); + } + for (EntryPoint.Parameter parameter : entryPoint.getParameters()) { + if (!isBooleanType(parameter.getType())) { + continue; + } + if (hasBooleanTernaryForParameter( + entryPoint.getClassName() + "." + entryPoint.getMethodName(), + parameter.getName(), + context, + callGraph)) { + return List.of( + Map.of(parameter.getName(), "true"), + Map.of(parameter.getName(), "false")); + } + } + return List.of(); + } + + private static boolean isBooleanType(String type) { + return "boolean".equals(type) || "Boolean".equals(type); + } + + private static boolean isBooleanBindingValue(String value) { + return "true".equals(value) || "false".equals(value); + } + + private static boolean hasBooleanTernaryForParameter( + String startMethod, + String paramName, + CodebaseContext context, + Map> callGraph) { + ArrayDeque queue = new ArrayDeque<>(); + Set visited = new HashSet<>(); + queue.add(startMethod); + while (!queue.isEmpty()) { + String methodFqn = queue.poll(); + if (!visited.add(methodFqn)) { + continue; + } + if (visited.size() > 12) { + break; + } + if (containsBooleanTernaryCondition(methodFqn, paramName, context)) { + return true; + } + List edges = callGraph.get(methodFqn); + if (edges == null) { + continue; + } + for (CallEdge edge : edges) { + if (edge.getTargetMethod() != null) { + queue.add(edge.getTargetMethod()); + } + } + } + return false; + } + + static boolean containsBooleanTernaryCondition(String methodFqn, String paramName, CodebaseContext context) { + MethodDeclaration method = findMethodDeclaration(methodFqn, context); + if (method == null || method.getBody() == null) { + return false; + } + if (!hasParameter(method, paramName)) { + return false; + } + final boolean[] found = {false}; + method.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(ConditionalExpression node) { + if (isBooleanConditionForParameter(node.getExpression(), paramName)) { + found[0] = true; + return false; + } + return super.visit(node); + } + + @Override + public boolean visit(MethodInvocation node) { + for (Object argObj : node.arguments()) { + if (argObj instanceof ConditionalExpression cond + && isBooleanConditionForParameter(cond.getExpression(), paramName)) { + found[0] = true; + return false; + } + } + return super.visit(node); + } + }); + return found[0]; + } + + private static boolean isBooleanConditionForParameter(Expression condition, String paramName) { + if (condition instanceof SimpleName simpleName) { + return paramName.equals(simpleName.getIdentifier()); + } + if (condition instanceof PrefixExpression prefixExpression + && prefixExpression.getOperator() == PrefixExpression.Operator.NOT + && prefixExpression.getOperand() instanceof SimpleName simpleName) { + return paramName.equals(simpleName.getIdentifier()); + } + return false; + } + private static boolean isPathOrQueryVariable(EntryPoint.Parameter parameter) { if (parameter.getAnnotations() == null) { return false; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java index eaf7ec3..e72905d 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java @@ -471,9 +471,48 @@ public class GenericEventDetector { } } } + if (enclosing instanceof TypeDeclaration typeDeclaration) { + for (MethodDeclaration method : typeDeclaration.getMethods()) { + if (!hasAutowiredAnnotation(method) || method.parameters().size() != 1) { + continue; + } + SingleVariableDeclaration param = (SingleVariableDeclaration) method.parameters().get(0); + if (!matchesInjectionTarget(method, fieldName, param)) { + continue; + } + String qualifier = extractQualifierAnnotation(param.modifiers()); + if (qualifier != null) { + return qualifier; + } + } + } return null; } + private static boolean hasAutowiredAnnotation(MethodDeclaration method) { + for (Object modifierObj : method.modifiers()) { + if (modifierObj instanceof Annotation annotation + && annotation.getTypeName().toString().endsWith("Autowired")) { + return true; + } + } + return false; + } + + private static boolean matchesInjectionTarget( + MethodDeclaration method, String fieldName, SingleVariableDeclaration param) { + if (fieldName.equals(param.getName().getIdentifier())) { + return true; + } + String methodName = method.getName().getIdentifier(); + if (methodName.startsWith("set") && methodName.length() > 3) { + String propertyName = methodName.substring(3); + return propertyName.equalsIgnoreCase(fieldName) + || (Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1)).equals(fieldName); + } + return false; + } + private String extractQualifierFromBinding(IVariableBinding binding) { if (binding == null) { return null; @@ -484,16 +523,31 @@ public class GenericEventDetector { } if (binding.isField() && binding.getDeclaringClass() != null) { for (IMethodBinding method : binding.getDeclaringClass().getDeclaredMethods()) { - if (!method.isConstructor()) { + boolean isAutowiredMethod = false; + for (IAnnotationBinding ann : method.getAnnotations()) { + if (ann.getAnnotationType() != null + && "org.springframework.beans.factory.annotation.Autowired" + .equals(ann.getAnnotationType().getQualifiedName())) { + isAutowiredMethod = true; + break; + } + } + if (!method.isConstructor() && !isAutowiredMethod) { continue; } for (int i = 0; i < method.getParameterTypes().length; i++) { - if (method.getParameterTypes()[i].isEqualTo(binding.getType().getErasure()) - && binding.getName().equals(method.getParameterNames()[i])) { - String paramQualifier = readQualifierFromAnnotations(method.getParameterAnnotations(i)); - if (paramQualifier != null) { - return paramQualifier; - } + if (!method.getParameterTypes()[i].isEqualTo(binding.getType().getErasure())) { + continue; + } + if (method.isConstructor() && !binding.getName().equals(method.getParameterNames()[i])) { + continue; + } + if (!method.isConstructor() && !matchesSetterParameterName(method.getName(), binding.getName())) { + continue; + } + String paramQualifier = readQualifierFromAnnotations(method.getParameterAnnotations(i)); + if (paramQualifier != null) { + return paramQualifier; } } } @@ -501,6 +555,18 @@ public class GenericEventDetector { return null; } + private static boolean matchesSetterParameterName(String methodName, String fieldName) { + if (methodName == null || fieldName == null) { + return false; + } + if (methodName.startsWith("set") && methodName.length() > 3) { + String propertyName = methodName.substring(3); + return propertyName.equalsIgnoreCase(fieldName) + || (Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1)).equals(fieldName); + } + return fieldName.equals(methodName); + } + private String readQualifierFromAnnotations(IAnnotationBinding[] annotations) { if (annotations == null) { return null; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/GoldenUpdater.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/GoldenUpdater.java index 19a30ea..43158ed 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/GoldenUpdater.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/GoldenUpdater.java @@ -172,6 +172,12 @@ public class GoldenUpdater { Path.of("src/test/resources/golden/ExtendedStateMachineConfig"), "ExtendedStateMachineConfig" ), + new TestScenario( + "Payment State Machine (Extended Sample)", + Path.of("state_machines/extended_analysis_sample"), + Path.of("src/test/resources/golden/PaymentStateMachineConfig"), + "PaymentStateMachineConfig" + ), new TestScenario( "Extended Analysis Sample (PROD)", Path.of("state_machines/extended_analysis_sample"), diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/PlantUmlE2ETest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/PlantUmlE2ETest.java index 97629fa..82dd956 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/PlantUmlE2ETest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/PlantUmlE2ETest.java @@ -113,6 +113,12 @@ public class PlantUmlE2ETest { Path.of("src/test/resources/golden/ExtendedStateMachineConfig"), "ExtendedStateMachineConfig" ), + new TestScenario( + "Payment State Machine (Extended Sample)", + root.resolve("state_machines/extended_analysis_sample"), + Path.of("src/test/resources/golden/PaymentStateMachineConfig"), + "PaymentStateMachineConfig" + ), new TestScenario( "Extended Analysis Sample (PROD)", root.resolve("state_machines/extended_analysis_sample"), diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java index e11641b..6299d9b 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java @@ -113,6 +113,12 @@ public class RegressionTest { Path.of("src/test/resources/golden/ExtendedStateMachineConfig"), "ExtendedStateMachineConfig" ), + new TestScenario( + "Payment State Machine (Extended Sample)", + root.resolve("state_machines/extended_analysis_sample"), + Path.of("src/test/resources/golden/PaymentStateMachineConfig"), + "PaymentStateMachineConfig" + ), new TestScenario( "Extended Analysis Sample (PROD)", root.resolve("state_machines/extended_analysis_sample"), diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/BooleanTernaryBranchSplitTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/BooleanTernaryBranchSplitTest.java new file mode 100644 index 0000000..51415a8 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/BooleanTernaryBranchSplitTest.java @@ -0,0 +1,122 @@ +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; + +class BooleanTernaryBranchSplitTest { + + @Test + void bindingExpanderShouldProduceBooleanVariantsForTernaryParameter(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class PolymorphicController { + OrderService orderService; + public void payTernary(boolean isPay) { + orderService.processEvent(isPay ? new PayEvent() : new CancelEvent()); + } + } + class OrderService { + void processEvent(BaseEvent event) {} + } + class BaseEvent {} + class PayEvent extends BaseEvent {} + class CancelEvent extends BaseEvent {} + """; + 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.PolymorphicController") + .methodName("payTernary") + .name("POST /pay-ternary") + .parameters(List.of(EntryPoint.Parameter.builder() + .name("isPay") + .type("boolean") + .build())) + .build(); + + List> variants = EntryPointBindingExpander.expandEntryPointBindings( + entryPoint, context, engine.buildCallGraph()); + + assertThat(variants).extracting(map -> map.get("isPay")) + .containsExactlyInAnyOrder("true", "false"); + } + + @Test + void shouldResolveSingleEventPerBooleanBinding(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class PolymorphicController { + OrderService orderService; + public void payTernary(boolean isPay) { + orderService.processEvent(isPay ? new PayEvent() : new CancelEvent()); + } + } + class OrderService { + void processEvent(BaseEvent event) {} + } + class BaseEvent {} + class PayEvent extends BaseEvent {} + class CancelEvent extends BaseEvent {} + """; + 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.PolymorphicController") + .methodName("payTernary") + .name("POST /pay-ternary") + .parameters(List.of(EntryPoint.Parameter.builder() + .name("isPay") + .type("boolean") + .build())) + .build(); + + TriggerPoint trigger = TriggerPoint.builder() + .className("com.example.OrderService") + .methodName("processEvent") + .event("event") + .build(); + + List chains = engine.findChains(List.of(entryPoint), List.of(trigger)); + + CallChain payChain = chains.stream() + .filter(c -> c.getEntryPoint().getName().contains("isPay=true")) + .findFirst() + .orElseThrow(); + CallChain cancelChain = chains.stream() + .filter(c -> c.getEntryPoint().getName().contains("isPay=false")) + .findFirst() + .orElseThrow(); + + assertThat(payChain.getTriggerPoint().isAmbiguous()).isFalse(); + assertThat(cancelChain.getTriggerPoint().isAmbiguous()).isFalse(); + assertThat(payChain.getTriggerPoint().getEvent()).contains("PayEvent").doesNotContain("CancelEvent"); + assertThat(cancelChain.getTriggerPoint().getEvent()).contains("CancelEvent").doesNotContain("PayEvent"); + if (payChain.getTriggerPoint().getPolymorphicEvents() != null) { + assertThat(payChain.getTriggerPoint().getPolymorphicEvents()).hasSizeLessThanOrEqualTo(1); + } + if (cancelChain.getTriggerPoint().getPolymorphicEvents() != null) { + assertThat(cancelChain.getTriggerPoint().getPolymorphicEvents()).hasSizeLessThanOrEqualTo(1); + } + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorFireTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorFireTest.java index 302f41a..4113ef4 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorFireTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorFireTest.java @@ -108,6 +108,41 @@ class GenericEventDetectorFireTest { assertThat(triggers.get(0).getStateMachineId()).isEqualTo("paymentStateMachine"); } + @Test + void shouldInferStateMachineIdFromQualifierOnAutowiredSetter(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + enum OrderEvent { PAY } + class OrderService { + private StateMachine stateMachine; + @org.springframework.beans.factory.annotation.Autowired + public void setStateMachine( + @org.springframework.beans.factory.annotation.Qualifier("paymentStateMachine") + StateMachine stateMachine) { + this.stateMachine = stateMachine; + } + public void pay() { + stateMachine.sendEvent(OrderEvent.PAY); + } + } + class StateMachine { + public void sendEvent(OrderEvent event) {} + } + """; + Files.writeString(tempDir.resolve("OrderService.java"), source); + + CodebaseContext context = new CodebaseContext(); + context.setResolveBindings(true); + context.scan(tempDir); + + GenericEventDetector detector = new GenericEventDetector(context, context.getConstantResolver(), List.of()); + org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration("com.example.OrderService"); + List triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot()); + + assertThat(triggers).hasSize(1); + assertThat(triggers.get(0).getStateMachineId()).isEqualTo("paymentStateMachine"); + } + @Test void shouldNotInferSourceStateFromVariableOnlyIfCondition(@TempDir Path tempDir) throws Exception { String source = """ diff --git a/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.dot b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.dot new file mode 100644 index 0000000..3ac846a --- /dev/null +++ b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.dot @@ -0,0 +1,22 @@ +digraph statemachine { + rankdir=LR; + node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"]; + edge [fontname="Arial", fontsize=10]; + + _start [shape=circle, label="", fillcolor=black, width=0.1]; + _start -> NEW; + DECLINED [fillcolor=lightgray]; + QUIRK2 [fillcolor=lightgray]; + QUIRK1 [fillcolor=lightgray]; + QUIRK4 [fillcolor=lightgray]; + QUIRK3 [fillcolor=lightgray]; + CAPTURED [fillcolor=lightgray]; + NEW -> AUTHORIZED [label="String.AUTHORIZE", style="solid", color="black"]; + AUTHORIZED -> CAPTURED [label="String.CAPTURE", style="solid", color="black"]; + NEW -> DECLINED [label="String.DECLINE", style="solid", color="black"]; + NEW -> QUIRK1 [label="String.PRIMARY_EVENT", style="solid", color="black"]; + NEW -> QUIRK2 [label="String.NAMED_EVENT", style="solid", color="black"]; + NEW -> QUIRK3 [label="String.QUALIFIER_EVENT", style="solid", color="black"]; + NEW -> QUIRK4 [label="String.FALLBACK_EVENT", style="solid", color="black"]; +} + diff --git a/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.json new file mode 100644 index 0000000..5e04ad6 --- /dev/null +++ b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.json @@ -0,0 +1,1030 @@ +{ + "name" : "click.kamil.examples.statemachine.extended.config.PaymentStateMachineConfig", + "states" : [ { + "rawName" : "\"QUIRK4\"", + "fullIdentifier" : "String.QUIRK4" + }, { + "rawName" : "\"NEW\"", + "fullIdentifier" : "String.NEW" + }, { + "rawName" : "\"AUTHORIZED\"", + "fullIdentifier" : "String.AUTHORIZED" + }, { + "rawName" : "\"DECLINED\"", + "fullIdentifier" : "String.DECLINED" + }, { + "rawName" : "\"QUIRK1\"", + "fullIdentifier" : "String.QUIRK1" + }, { + "rawName" : "\"QUIRK2\"", + "fullIdentifier" : "String.QUIRK2" + }, { + "rawName" : "\"CAPTURED\"", + "fullIdentifier" : "String.CAPTURED" + }, { + "rawName" : "\"QUIRK3\"", + "fullIdentifier" : "String.QUIRK3" + } ], + "transitions" : [ { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"NEW\"", + "fullIdentifier" : "String.NEW" + } ], + "targetStates" : [ { + "rawName" : "\"AUTHORIZED\"", + "fullIdentifier" : "String.AUTHORIZED" + } ], + "event" : { + "rawName" : "\"AUTHORIZE\"", + "fullIdentifier" : "String.AUTHORIZE" + }, + "guards" : [ ], + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"AUTHORIZED\"", + "fullIdentifier" : "String.AUTHORIZED" + } ], + "targetStates" : [ { + "rawName" : "\"CAPTURED\"", + "fullIdentifier" : "String.CAPTURED" + } ], + "event" : { + "rawName" : "\"CAPTURE\"", + "fullIdentifier" : "String.CAPTURE" + }, + "guards" : [ ], + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"NEW\"", + "fullIdentifier" : "String.NEW" + } ], + "targetStates" : [ { + "rawName" : "\"DECLINED\"", + "fullIdentifier" : "String.DECLINED" + } ], + "event" : { + "rawName" : "\"DECLINE\"", + "fullIdentifier" : "String.DECLINE" + }, + "guards" : [ ], + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"NEW\"", + "fullIdentifier" : "String.NEW" + } ], + "targetStates" : [ { + "rawName" : "\"QUIRK1\"", + "fullIdentifier" : "String.QUIRK1" + } ], + "event" : { + "rawName" : "\"PRIMARY_EVENT\"", + "fullIdentifier" : "String.PRIMARY_EVENT" + }, + "guards" : [ ], + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"NEW\"", + "fullIdentifier" : "String.NEW" + } ], + "targetStates" : [ { + "rawName" : "\"QUIRK2\"", + "fullIdentifier" : "String.QUIRK2" + } ], + "event" : { + "rawName" : "\"NAMED_EVENT\"", + "fullIdentifier" : "String.NAMED_EVENT" + }, + "guards" : [ ], + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"NEW\"", + "fullIdentifier" : "String.NEW" + } ], + "targetStates" : [ { + "rawName" : "\"QUIRK3\"", + "fullIdentifier" : "String.QUIRK3" + } ], + "event" : { + "rawName" : "\"QUALIFIER_EVENT\"", + "fullIdentifier" : "String.QUALIFIER_EVENT" + }, + "guards" : [ ], + "actions" : [ ], + "order" : null + }, { + "type" : "EXTERNAL", + "sourceStates" : [ { + "rawName" : "\"NEW\"", + "fullIdentifier" : "String.NEW" + } ], + "targetStates" : [ { + "rawName" : "\"QUIRK4\"", + "fullIdentifier" : "String.QUIRK4" + } ], + "event" : { + "rawName" : "\"FALLBACK_EVENT\"", + "fullIdentifier" : "String.FALLBACK_EVENT" + }, + "guards" : [ ], + "actions" : [ ], + "order" : null + } ], + "startStates" : [ "String.NEW" ], + "endStates" : [ "String.DECLINED", "String.QUIRK2", "String.QUIRK1", "String.QUIRK4", "String.QUIRK3", "String.CAPTURED" ], + "renderChoicesAsDiamonds" : true, + "flows" : [ ], + "stateTypeFqn" : "String", + "eventTypeFqn" : "String", + "metadata" : { + "triggers" : [ { + "event" : "java.lang.String.FINISH", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "finishOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 34, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.AUDIT_EVENT", + "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", + "methodName" : "preHandle", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 16, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.SUBMIT_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.OrderService", + "methodName" : "processSubmit", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 20, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.CANCEL_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.OrderService", + "methodName" : "processCancel", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 25, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "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 + }, { + "event" : "java.lang.String.QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "java.lang.String.AUTHORIZE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "processPayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : null, + "lineNumber" : 22, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "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" : null, + "lineNumber" : 35, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "event" : "[LIFECYCLE:RESTORE]", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : null, + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 28, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, { + "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" : null, + "lineNumber" : 18, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + } ], + "entryPoints" : [ { + "type" : "REST", + "name" : "POST /api/orders/submit", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "submitOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "metadata" : { + "path" : "/api/orders/submit", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/orders/cancel", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "cancelOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "metadata" : { + "path" : "/api/orders/cancel", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/orders/finish", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "finishOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "metadata" : { + "path" : "/api/orders/finish", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "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" : "CUSTOM", + "name" : "INTERCEPTOR: AuditInterceptor.preHandle", + "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", + "methodName" : "preHandle", + "sourceFile" : null, + "metadata" : { + "interceptorType" : "Spring MVC Interceptor" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, { + "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" ] + } ] + }, { + "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" ] + } ] + } ], + "callChains" : [ { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/orders/submit", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "submitOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "metadata" : { + "path" : "/api/orders/submit", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.submitOrder", "click.kamil.examples.statemachine.extended.service.OrderService.processSubmit" ], + "triggerPoint" : { + "event" : "java.lang.String.SUBMIT_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.OrderService", + "methodName" : "processSubmit", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 20, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/orders/cancel", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "cancelOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "metadata" : { + "path" : "/api/orders/cancel", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.cancelOrder", "click.kamil.examples.statemachine.extended.service.OrderService.processCancel" ], + "triggerPoint" : { + "event" : "java.lang.String.CANCEL_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.OrderService", + "methodName" : "processCancel", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 25, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/orders/finish", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "finishOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "metadata" : { + "path" : "/api/orders/finish", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.finishOrder" ], + "triggerPoint" : { + "event" : "java.lang.String.FINISH", + "className" : "click.kamil.examples.statemachine.extended.web.OrderController", + "methodName" : "finishOrder", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 34, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "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" : null, + "lineNumber" : 18, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "CUSTOM", + "name" : "INTERCEPTOR: AuditInterceptor.preHandle", + "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", + "methodName" : "preHandle", + "sourceFile" : null, + "metadata" : { + "interceptorType" : "Spring MVC Interceptor" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.AuditInterceptor.preHandle" ], + "triggerPoint" : { + "event" : "java.lang.String.AUDIT_EVENT", + "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", + "methodName" : "preHandle", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 16, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "java.lang.String.NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 17, + "polymorphicEvents" : [ "java.lang.String.NAMED_EVENT" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.QUIRK2", + "event" : "String.NAMED_EVENT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "java.lang.String.PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 19, + "polymorphicEvents" : [ "java.lang.String.PRIMARY_EVENT" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.QUIRK1", + "event" : "String.PRIMARY_EVENT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "java.lang.String.NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 17, + "polymorphicEvents" : [ "java.lang.String.NAMED_EVENT" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.QUIRK2", + "event" : "String.NAMED_EVENT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "java.lang.String.QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 17, + "polymorphicEvents" : [ "java.lang.String.QUALIFIER_EVENT" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.QUIRK3", + "event" : "String.QUALIFIER_EVENT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "java.lang.String.PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 19, + "polymorphicEvents" : [ "java.lang.String.PRIMARY_EVENT" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.QUIRK1", + "event" : "String.PRIMARY_EVENT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "java.lang.String.QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 17, + "polymorphicEvents" : [ "java.lang.String.QUALIFIER_EVENT" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.QUIRK3", + "event" : "String.QUALIFIER_EVENT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "java.lang.String.NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 17, + "polymorphicEvents" : [ "java.lang.String.NAMED_EVENT" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.QUIRK2", + "event" : "String.NAMED_EVENT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "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" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.processBaseEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.processPayment" ], + "triggerPoint" : { + "event" : "java.lang.String.AUTHORIZE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "processPayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : "paymentStateMachine", + "sourceState" : "java.lang.String.NEW", + "lineNumber" : 22, + "polymorphicEvents" : [ "java.lang.String.AUTHORIZE" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "String.NEW", + "targetState" : "String.AUTHORIZED", + "event" : "String.AUTHORIZE" + } ], + "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" + }, { + "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" : "[LIFECYCLE:RESTORE]", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : null, + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 28, + "polymorphicEvents" : null, + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : "paymentId", + "matchedTransitions" : null, + "linkResolution" : null + } ], + "properties" : { + "default" : { + "app.states.initial" : "INIT_STATE" + }, + "prod" : { + "app.states.initial" : "PROD_INITIAL" + } + } + } +} diff --git a/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.png b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.png new file mode 100644 index 0000000000000000000000000000000000000000..9e8a2dde50bec859350c55d85cdff10594b5dc7a GIT binary patch literal 43726 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 EROC1{t6Ib6Jr~YiFT?^ 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 String.NEW + + +String.NEW -[#1E90FF,bold]-> String.AUTHORIZED <> : String.AUTHORIZE +String.AUTHORIZED -[#1E90FF,bold]-> String.CAPTURED <> : String.CAPTURE +String.NEW -[#1E90FF,bold]-> String.DECLINED <> : String.DECLINE +String.NEW -[#1E90FF,bold]-> String.QUIRK1 <> : String.PRIMARY_EVENT +String.NEW -[#1E90FF,bold]-> String.QUIRK2 <> : String.NAMED_EVENT +String.NEW -[#1E90FF,bold]-> String.QUIRK3 <> : String.QUALIFIER_EVENT +String.NEW -[#1E90FF,bold]-> String.QUIRK4 <> : String.FALLBACK_EVENT + +String.DECLINED --> [*] +String.QUIRK2 --> [*] +String.QUIRK1 --> [*] +String.QUIRK4 --> [*] +String.QUIRK3 --> [*] +String.CAPTURED --> [*] +@enduml + diff --git a/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.scxml.xml b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.scxml.xml new file mode 100644 index 0000000..eb39a0a --- /dev/null +++ b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.scxml.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json index b17f969..36c871f 100644 --- a/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json @@ -197,12 +197,27 @@ "parameters" : [ ] }, { "type" : "REST", - "name" : "POST /pay-ternary", + "name" : "POST /pay-ternary?isPay=true", "className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController", "methodName" : "payTernary", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java", "metadata" : { - "path" : "/pay-ternary", + "path" : "/pay-ternary?isPay=true", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "isPay", + "type" : "boolean", + "annotations" : [ ] + } ] + }, { + "type" : "REST", + "name" : "POST /pay-ternary?isPay=false", + "className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController", + "methodName" : "payTernary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java", + "metadata" : { + "path" : "/pay-ternary?isPay=false", "verb" : "POST" }, "parameters" : [ { @@ -479,12 +494,12 @@ }, { "entryPoint" : { "type" : "REST", - "name" : "POST /pay-ternary", + "name" : "POST /pay-ternary?isPay=true", "className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController", "methodName" : "payTernary", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java", "metadata" : { - "path" : "/pay-ternary", + "path" : "/pay-ternary?isPay=true", "verb" : "POST" }, "parameters" : [ { @@ -495,22 +510,65 @@ }, "methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payTernary", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ], "triggerPoint" : { - "event" : "isPay ? new PayEvent() : new CancelEvent()", + "event" : "new PayEvent()", "className" : "click.kamil.examples.statemachine.polymorphic.OrderService", "methodName" : "processEvent", "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", "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ], + "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ], "external" : false, "constraint" : null, - "ambiguous" : true + "ambiguous" : false }, "contextMachineId" : null, - "matchedTransitions" : null, - "linkResolution" : "AMBIGUOUS_WIDEN" + "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" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /pay-ternary?isPay=false", + "className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController", + "methodName" : "payTernary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java", + "metadata" : { + "path" : "/pay-ternary?isPay=false", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "isPay", + "type" : "boolean", + "annotations" : [ ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payTernary", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ], + "triggerPoint" : { + "event" : "new CancelEvent()", + "className" : "click.kamil.examples.statemachine.polymorphic.OrderService", + "methodName" : "processEvent", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", + "lineNumber" : 13, + "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ], + "external" : false, + "constraint" : null, + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", + "targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.CANCELED", + "event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" + } ], + "linkResolution" : "RESOLVED" }, { "entryPoint" : { "type" : "REST",