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 0000000..9e8a2dd Binary files /dev/null and b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.png differ diff --git a/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.puml b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.puml new file mode 100644 index 0000000..3698fb8 --- /dev/null +++ b/state_machine_exporter/src/test/resources/golden/PaymentStateMachineConfig/PaymentStateMachineConfig.puml @@ -0,0 +1,42 @@ +@startuml +!pragma layout smetana +set separator none +hide empty description +hide stereotype +skinparam state { + BackgroundColor white + BorderColor #94a3b8 + BorderThickness 1 + FontName Inter + FontSize 9 + FontStyle bold + RoundCorner 20 + Padding 1 +} +skinparam shadowing false +skinparam ArrowFontName JetBrains Mono +skinparam ArrowFontSize 8 +skinparam ArrowColor #cbd5e1 +skinparam ArrowThickness 1 +skinparam dpi 110 +skinparam svgLinkTarget _self + +[*] --> 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",