From e3f3350398b75679079612492443696fa666ad68 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Tue, 14 Jul 2026 07:15:16 +0200 Subject: [PATCH] Link expanded generic REST paths via parameter alias tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow renamed path variables through call-graph aliases, expand machineType×event bindings from branch-scoped valueOf, treat fully resolved URLs as internal, and synthesize concrete polymorphic events from path constraints for transition linking. Co-authored-by: Cursor --- .../enricher/TransitionLinkerEnricher.java | 16 +- .../resolver/MachineEnumCanonicalizer.java | 83 ++++ .../service/EntryPointBindingExpander.java | 196 ++++++--- .../service/ExternalTriggerPolicy.java | 24 ++ .../AnalysisCanonicalFormValidator.java | 5 + ...erpriseBooleanEnumPredicateExportTest.java | 33 +- ...hineEnumCanonicalizerBoundValueOfTest.java | 33 ++ ...tcherMatchedTransitionsRegressionTest.java | 47 ++- ...iseExpandedGenericEndpointLinkingTest.java | 68 +++ .../EntryPointBindingExpanderTest.java | 126 ++++++ .../service/ExternalTriggerPolicyTest.java | 27 ++ .../DocumentStateMachineConfiguration.json | 390 ++++++++++++++++-- .../OrderStateMachineConfiguration.json | 386 +++++++++++++++-- .../UserStateMachineConfiguration.json | 380 ++++++++++++++++- 14 files changed, 1660 insertions(+), 154 deletions(-) create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerBoundValueOfTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseExpandedGenericEndpointLinkingTest.java diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java index b7c0895..0f08e09 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java @@ -57,6 +57,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { continue; } + tp = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints( + tp, machineTypes, context, chain.getEntryPoint()); tp = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents( tp, machineTypes, context, stateMachineTransitions, true); tp = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(tp, machineTypes, context); @@ -207,8 +209,20 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { if (constraint == null || machineName == null) { return true; } + String machineConstraint = stripEventBindingClauses(constraint); return BooleanConstraintEvaluator.isCompatibleWithMachineDomain( - constraint, MachineDomainKeys.extractMachineDomainKey(machineName)); + machineConstraint, MachineDomainKeys.extractMachineDomainKey(machineName)); + } + + private static String stripEventBindingClauses(String constraint) { + if (constraint == null || constraint.isBlank()) { + return constraint; + } + String stripped = constraint.replaceAll("\"[^\"]+\"\\.equalsIgnoreCase\\(event\\)", "true"); + stripped = stripped.replaceAll("&&\\s*&&+", "&&"); + stripped = stripped.replaceAll("^\\s*&&\\s*", ""); + stripped = stripped.replaceAll("\\s*&&\\s*$", ""); + return stripped.trim(); } private final java.util.Map simplifySourceCache = new java.util.HashMap<>(); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java index 3ae31d1..9ad5424 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java @@ -1,5 +1,6 @@ package click.kamil.springstatemachineexporter.analysis.resolver; +import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.model.Event; @@ -999,4 +1000,86 @@ public final class MachineEnumCanonicalizer { } return simpleName(enumTypeFqn) + "." + canonicalFqn.substring(lastDot + 1); } + + /** + * When path bindings prove a single {@code event} literal (e.g. {@code "PAY".equalsIgnoreCase(event)}) + * and the trigger is {@code Enum.valueOf(...)}, synthesize one concrete polymorphic event for linking. + */ + public static TriggerPoint expandBoundValueOfFromConstraints( + TriggerPoint trigger, + StateMachineTypeResolver.MachineTypes machineTypes, + CodebaseContext context) { + return expandBoundValueOfFromConstraints(trigger, machineTypes, context, null); + } + + public static TriggerPoint expandBoundValueOfFromConstraints( + TriggerPoint trigger, + StateMachineTypeResolver.MachineTypes machineTypes, + CodebaseContext context, + EntryPoint entryPoint) { + if (trigger == null || trigger.getEvent() == null || machineTypes == null) { + return trigger; + } + if (!isDynamicTriggerExpression(trigger.getEvent()) || !trigger.getEvent().contains(".valueOf(")) { + return trigger; + } + String constraint = trigger.getConstraint(); + if (constraint == null || constraint.isBlank()) { + return trigger; + } + List boundEventLiterals = extractBoundEventLiteralsFromConstraint(constraint); + if (boundEventLiterals.size() != 1) { + return trigger; + } + String eventTypeFqn = machineTypes.eventTypeFqn(); + if (eventTypeFqn == null || eventTypeFqn.isBlank()) { + return trigger; + } + String literal = boundEventLiterals.get(0).toUpperCase(); + if (entryPoint != null && entryPoint.getName() != null + && !entryPoint.getName().toUpperCase().contains("/" + literal + "/") + && !entryPoint.getName().toUpperCase().endsWith("/" + literal)) { + return trigger; + } + String machineTypeLiteral = extractBoundParamLiteralFromConstraint(constraint, "machineType"); + if (machineTypeLiteral != null && entryPoint != null && entryPoint.getName() != null + && !entryPoint.getName().toUpperCase().contains("/" + machineTypeLiteral.toUpperCase() + "/")) { + return trigger; + } + String constantFqn = eventTypeFqn + "." + literal; + if (context != null) { + List machineEnumValues = context.getEnumValues(eventTypeFqn); + if (machineEnumValues == null || machineEnumValues.stream().noneMatch(constantFqn::equals)) { + return trigger; + } + } + return trigger.toBuilder() + .polymorphicEvents(List.of(constantFqn)) + .ambiguous(false) + .external(false) + .build(); + } + + private static String extractBoundParamLiteralFromConstraint(String constraint, String paramName) { + java.util.regex.Matcher matcher = java.util.regex.Pattern + .compile("\"([^\"]+)\"\\.equalsIgnoreCase\\(" + paramName + "\\)") + .matcher(constraint); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + private static List extractBoundEventLiteralsFromConstraint(String constraint) { + List literals = new ArrayList<>(); + java.util.regex.Matcher matcher = java.util.regex.Pattern + .compile("\"([^\"]+)\"\\.equalsIgnoreCase\\((\\w+)\\)") + .matcher(constraint); + while (matcher.find()) { + if ("event".equals(matcher.group(2))) { + literals.add(matcher.group(1)); + } + } + return literals; + } } 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 3d7104a..d3021f8 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 @@ -2,6 +2,8 @@ package click.kamil.springstatemachineexporter.analysis.service; import click.kamil.springstatemachineexporter.analysis.model.CallEdge; import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; +import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator; +import click.kamil.springstatemachineexporter.ast.common.AstUtils; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import org.eclipse.jdt.core.dom.*; @@ -59,22 +61,26 @@ public final class EntryPointBindingExpander { if (isPlaceholderResolvedInPath(entryPoint, parameter.getName())) { continue; } - List cases = resolveBindingCasesForParameter( - entryPoint.getClassName() + "." + entryPoint.getMethodName(), - parameter.getName(), - context, - callGraph); - if (cases.isEmpty()) { - continue; - } List> expanded = new ArrayList<>(); for (Map base : variants) { + List cases = resolveBindingCasesForParameter( + entryPoint.getClassName() + "." + entryPoint.getMethodName(), + parameter.getName(), + context, + callGraph, + base); + if (cases.isEmpty()) { + continue; + } for (String caseValue : cases) { Map merged = new LinkedHashMap<>(base); merged.put(parameter.getName(), caseValue); expanded.add(merged); } } + if (expanded.isEmpty()) { + continue; + } variants = expanded; } return variants.stream().filter(map -> !map.isEmpty()).toList(); @@ -270,81 +276,149 @@ public final class EntryPointBindingExpander { String startMethod, String paramName, CodebaseContext context, - Map> callGraph) { - List stringCases = resolveStringCasesForParameter(startMethod, paramName, context, callGraph); + Map> callGraph, + Map partialBindings) { + List stringCases = resolveCasesWithParameterAliases( + startMethod, paramName, context, callGraph, partialBindings, + EntryPointBindingExpander::extractStringSwitchCasesForBindings); if (!stringCases.isEmpty()) { return stringCases; } - return resolveEnumValueOfCasesForParameter(startMethod, paramName, context, callGraph); + return resolveCasesWithParameterAliases( + startMethod, paramName, context, callGraph, partialBindings, + EntryPointBindingExpander::extractEnumValueOfCasesForBindings); } - private static List resolveStringCasesForParameter( - String startMethod, + @FunctionalInterface + private interface ParameterCaseExtractor { + List extract( + String methodFqn, + String paramName, + CodebaseContext context, + Map partialBindings); + } + + private static List extractStringSwitchCasesForBindings( + String methodFqn, String paramName, CodebaseContext context, - Map> callGraph) { - ArrayDeque queue = new ArrayDeque<>(); + Map partialBindings) { + return extractStringSwitchCases(methodFqn, paramName, context); + } + + private static List extractEnumValueOfCasesForBindings( + String methodFqn, + String paramName, + CodebaseContext context, + Map partialBindings) { + return extractEnumValueOfCases(methodFqn, paramName, context, partialBindings); + } + + private record ParameterAliasState(String methodFqn, String paramName) { + } + + private static List resolveCasesWithParameterAliases( + String startMethod, + String entryParamName, + CodebaseContext context, + Map> callGraph, + Map partialBindings, + ParameterCaseExtractor extractor) { + ArrayDeque queue = new ArrayDeque<>(); Set visited = new HashSet<>(); - queue.add(startMethod); + queue.add(new ParameterAliasState(startMethod, entryParamName)); while (!queue.isEmpty()) { - String methodFqn = queue.poll(); - if (!visited.add(methodFqn)) { + ParameterAliasState state = queue.poll(); + String visitKey = state.methodFqn() + "#" + state.paramName(); + if (!visited.add(visitKey)) { continue; } - if (visited.size() > 12) { + if (visited.size() > 24) { break; } - List localCases = extractStringSwitchCases(methodFqn, paramName, context); + List localCases = extractor.extract( + state.methodFqn(), state.paramName(), context, partialBindings); if (!localCases.isEmpty()) { return localCases; } - List edges = callGraph.get(methodFqn); - if (edges == null) { - continue; - } - for (CallEdge edge : edges) { - if (edge.getTargetMethod() != null) { - queue.add(edge.getTargetMethod()); - } - } + enqueueParameterAliasSuccessors(state, context, callGraph, partialBindings, queue); } return List.of(); } - private static List resolveEnumValueOfCasesForParameter( - String startMethod, - String paramName, + private static void enqueueParameterAliasSuccessors( + ParameterAliasState state, 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)) { + Map> callGraph, + Map partialBindings, + ArrayDeque queue) { + List edges = callGraph.get(state.methodFqn()); + if (edges == null) { + return; + } + for (CallEdge edge : edges) { + if (edge.getTargetMethod() == null || !isEdgeCompatibleWithBindings(edge, partialBindings)) { continue; } - if (visited.size() > 12) { - break; - } - List localCases = extractEnumValueOfCases(methodFqn, paramName, context); - if (!localCases.isEmpty()) { - return localCases; - } - List edges = callGraph.get(methodFqn); - if (edges == null) { + List args = edge.getArguments(); + if (args == null || args.isEmpty()) { continue; } - for (CallEdge edge : edges) { - if (edge.getTargetMethod() != null) { - queue.add(edge.getTargetMethod()); + List targetParamNames = getParameterNames(edge.getTargetMethod(), context); + if (targetParamNames == null) { + continue; + } + int limit = Math.min(args.size(), targetParamNames.size()); + for (int i = 0; i < limit; i++) { + if (!argumentForwardsParameter(args.get(i), state.paramName())) { + continue; } + queue.add(new ParameterAliasState(edge.getTargetMethod(), targetParamNames.get(i))); } } - return List.of(); } - static List extractEnumValueOfCases(String methodFqn, String paramName, CodebaseContext context) { + private static boolean isEdgeCompatibleWithBindings(CallEdge edge, Map partialBindings) { + if (partialBindings == null || partialBindings.isEmpty()) { + return true; + } + String constraint = edge.getConstraint(); + if (constraint == null || constraint.isBlank()) { + return true; + } + return BooleanConstraintEvaluator.isCompatibleWithBindings(constraint, partialBindings); + } + + private static boolean argumentForwardsParameter(String argument, String paramName) { + if (argument == null || paramName == null) { + return false; + } + String trimmed = argument.trim(); + if (paramName.equals(trimmed)) { + return true; + } + return ("this." + paramName).equals(trimmed); + } + + private static List getParameterNames(String methodFqn, CodebaseContext context) { + MethodDeclaration method = findMethodDeclaration(methodFqn, context); + if (method == null) { + return null; + } + List names = new ArrayList<>(); + for (Object paramObj : method.parameters()) { + if (paramObj instanceof SingleVariableDeclaration param) { + names.add(param.getName().getIdentifier()); + } + } + return names; + } + + static List extractEnumValueOfCases( + String methodFqn, + String paramName, + CodebaseContext context, + Map partialBindings) { MethodDeclaration method = findMethodDeclaration(methodFqn, context); if (method == null || method.getBody() == null || context == null) { return List.of(); @@ -363,6 +437,9 @@ public final class EntryPointBindingExpander { if (!argumentReferencesParameter(argument, paramName)) { return super.visit(node); } + if (!isValueOfSiteCompatibleWithBindings(node, partialBindings)) { + return super.visit(node); + } String enumType = resolveValueOfEnumType(node, context); if (enumType != null) { enumTypes.add(enumType); @@ -388,6 +465,19 @@ public final class EntryPointBindingExpander { return List.copyOf(pathValues); } + private static boolean isValueOfSiteCompatibleWithBindings( + MethodInvocation valueOfInvocation, + Map partialBindings) { + if (partialBindings == null || partialBindings.isEmpty()) { + return true; + } + String branchConstraint = AstUtils.findConditionConstraint(valueOfInvocation); + if (branchConstraint == null || branchConstraint.isBlank()) { + return true; + } + return BooleanConstraintEvaluator.isCompatibleWithBindings(branchConstraint, partialBindings); + } + private static boolean argumentReferencesParameter(Expression argument, String paramName) { if (argument instanceof SimpleName simpleName) { return paramName.equals(simpleName.getIdentifier()); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicy.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicy.java index 0210423..2e6aaa4 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicy.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicy.java @@ -40,6 +40,9 @@ public final class ExternalTriggerPolicy { RestParamBinding binding = findRestParameterBinding(entryPoint, paramName, context); if (binding != null) { if (binding.pathOrQueryVariable() && !binding.enumType()) { + if (binding.pathVariable() && !entryPointHasUnboundPlaceholders(entryPoint)) { + return false; + } return true; } if (binding.requestBodyEnum()) { @@ -81,9 +84,30 @@ public final class ExternalTriggerPolicy { return false; } } + if (entryPoint != null && entryPoint.getType() == EntryPoint.Type.REST + && !entryPointHasUnboundPlaceholders(entryPoint) + && MachineEnumCanonicalizer.isDynamicTriggerExpression(trigger.getEvent())) { + return false; + } return trigger.isExternal(); } + private static boolean entryPointHasUnboundPlaceholders(EntryPoint entryPoint) { + if (entryPoint == null) { + return false; + } + if (entryPoint.getName() != null && entryPoint.getName().contains("{")) { + return true; + } + if (entryPoint.getMetadata() != null) { + String path = entryPoint.getMetadata().get("path"); + if (path != null && path.contains("{")) { + return true; + } + } + return false; + } + private static String resolveRestEventParameterName(EntryPoint entryPoint, String resolvedEventParamName) { if (resolvedEventParamName != null && !resolvedEventParamName.isBlank()) { return resolvedEventParamName; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java index 6ed1125..8562728 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java @@ -437,6 +437,11 @@ public final class AnalysisCanonicalFormValidator { if (chain.getMatchedTransitions() != null && !chain.getMatchedTransitions().isEmpty()) { return; } + if (chain.getLinkResolution() == click.kamil.springstatemachineexporter.analysis.model.LinkResolution.NO_MATCH + || chain.getLinkResolution() + == click.kamil.springstatemachineexporter.analysis.model.LinkResolution.UNRESOLVED_EXTERNAL) { + return; + } MachineEnumCanonicalizer.TriggerEventKind eventKind = MachineEnumCanonicalizer.classifyTriggerEvent(trigger.getEvent()); diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java index 8d6f145..e94276f 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnterpriseBooleanEnumPredicateExportTest.java @@ -51,14 +51,16 @@ class EnterpriseBooleanEnumPredicateExportTest { .toList(); assertThat(callChains).isNotEmpty(); - JsonNode dynamicChain = callChains.stream() + List dispatchChains = callChains.stream() .filter(chain -> chain.get("methodChain").toString().contains("OrderDispatcher.dispatch")) - .findFirst() - .orElseThrow(() -> new AssertionError("missing OrderDispatcher.dispatch chain: " + callChains)); + .toList(); + assertThat(dispatchChains).isNotEmpty(); - JsonNode trigger = dynamicChain.get("triggerPoint"); - List polyEvents = StreamSupport.stream(trigger.get("polymorphicEvents").spliterator(), false) + List polyEvents = dispatchChains.stream() + .flatMap(chain -> StreamSupport.stream( + chain.get("triggerPoint").get("polymorphicEvents").spliterator(), false)) .map(JsonNode::asText) + .distinct() .toList(); assertThat(polyEvents) @@ -71,12 +73,23 @@ class EnterpriseBooleanEnumPredicateExportTest { "com.example.order.OrderEvent.SHIP"); assertThat(polyEvents.size()).isLessThan(4); - List matched = StreamSupport.stream(dynamicChain.get("matchedTransitions").spliterator(), false) - .toList(); - assertThat(matched.size()) + for (JsonNode dispatchChain : dispatchChains) { + long chainMatched = StreamSupport.stream(dispatchChain.get("matchedTransitions").spliterator(), false) + .count(); + assertThat(chainMatched) + .as("each dispatch chain must not cover all transitions") + .isLessThanOrEqualTo(transitionCount); + } + int matchedCount = dispatchChains.stream() + .mapToInt(chain -> StreamSupport.stream(chain.get("matchedTransitions").spliterator(), false) + .mapToInt(node -> 1) + .sum()) + .sum(); + assertThat(matchedCount) .as("matchedTransitions must not cover every transition × every enum constant") - .isLessThanOrEqualTo(transitionCount); - Set matchedEvents = matched.stream() + .isLessThan(transitionCount * dispatchChains.size()); + Set matchedEvents = dispatchChains.stream() + .flatMap(chain -> StreamSupport.stream(chain.get("matchedTransitions").spliterator(), false)) .map(node -> node.get("event").asText()) .collect(Collectors.toSet()); assertThat(matchedEvents) diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerBoundValueOfTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerBoundValueOfTest.java new file mode 100644 index 0000000..eac2826 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerBoundValueOfTest.java @@ -0,0 +1,33 @@ +package click.kamil.springstatemachineexporter.analysis.resolver; + +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class MachineEnumCanonicalizerBoundValueOfTest { + + @Test + void shouldExpandSingleBoundEventLiteralForValueOfTrigger() { + TriggerPoint trigger = TriggerPoint.builder() + .event("OrderEvent.valueOf(eventString.toUpperCase())") + .constraint("\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event)") + .external(true) + .build(); + StateMachineTypeResolver.MachineTypes machineTypes = + new StateMachineTypeResolver.MachineTypes( + "com.example.order.OrderState", + "com.example.order.OrderEvent"); + + TriggerPoint expanded = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints( + trigger, machineTypes, null); + + assertThat(expanded.getPolymorphicEvents()) + .containsExactly("com.example.order.OrderEvent.PAY"); + assertThat(expanded.isExternal()).isFalse(); + assertThat(expanded.isAmbiguous()).isFalse(); + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java index 4e7ab8c..109511f 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java @@ -10,19 +10,18 @@ import org.junit.jupiter.api.io.TempDir; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.stream.StreamSupport; import static org.assertj.core.api.Assertions.assertThat; /** - * Regression for REST dispatcher chains: JSON round-trip must not re-infer all transition events - * for generic {@code {event}} endpoints. + * Regression for REST dispatcher chains: expanded concrete endpoints must not re-infer all + * transition events; unresolved {@code {event}} templates stay external. */ class EnterpriseDispatcherMatchedTransitionsRegressionTest { - private static final String TRANSITION_ENDPOINT = "POST /api/machine/{machineType}/transition/{event}"; - @Test - void astExportGenericTransitionEndpointShouldRemainUnlinked(@TempDir Path tempDir) throws Exception { + void astExportExpandedTransitionEndpointsShouldNotOverLink(@TempDir Path tempDir) throws Exception { Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise"); ExportService exportService = new ExportService(List.of(new JsonExporter())); exportService.runExporter(enterprise, tempDir, List.of("json"), true, List.of(), null, null, @@ -30,25 +29,31 @@ class EnterpriseDispatcherMatchedTransitionsRegressionTest { click.kamil.springstatemachineexporter.exporter.EnumFormat.fn); JsonNode json = readMachineJson(tempDir, "OrderStateMachineConfiguration", new ObjectMapper()); - JsonNode chain = findTransitionEndpointChain(json); + int transitionCount = json.path("transitions").size(); - assertThat(chain.path("triggerPoint").path("external").asBoolean()).isTrue(); - assertThat(chain.path("triggerPoint").path("polymorphicEvents").isArray()).isTrue(); - assertThat(chain.path("triggerPoint").path("polymorphicEvents")).isEmpty(); - JsonNode matched = chain.path("matchedTransitions"); - assertThat(matched.isNull() || (matched.isArray() && matched.isEmpty())).isTrue(); - assertThat(chain.path("linkResolution").asText()).isEqualTo("UNRESOLVED_EXTERNAL"); + StreamSupport.stream(json.path("metadata").path("callChains").spliterator(), false) + .filter(chain -> chain.path("entryPoint").path("name").asText().contains("/transition/{event}")) + .forEach(chain -> { + JsonNode matched = chain.path("matchedTransitions"); + assertThat(matched.isNull() || (matched.isArray() && matched.isEmpty())).isTrue(); + assertThat(chain.path("linkResolution").asText()).isEqualTo("UNRESOLVED_EXTERNAL"); + }); + + JsonNode orderPay = findChainByEndpoint(json, "POST /api/machine/ORDER/transition/PAY"); + assertThat(orderPay.path("linkResolution").asText()).isEqualTo("RESOLVED"); + JsonNode matched = orderPay.path("matchedTransitions"); + assertThat(matched.isArray()).isTrue(); + assertThat(matched.size()).isLessThanOrEqualTo(transitionCount); + assertThat(matched.size()).isGreaterThan(0); + assertThat(matched.get(0).path("event").asText()) + .contains("OrderEvent.PAY"); } - private static JsonNode findTransitionEndpointChain(JsonNode machineJson) { - for (JsonNode chain : machineJson.path("metadata").path("callChains")) { - String name = chain.path("entryPoint").path("name").asText(); - if (TRANSITION_ENDPOINT.equals(name) - || (name.contains("/transition/{event}") && name.contains("POST /api/machine/"))) { - return chain; - } - } - throw new IllegalStateException("transition endpoint chain not found"); + private static JsonNode findChainByEndpoint(JsonNode machineJson, String endpointName) { + return StreamSupport.stream(machineJson.path("metadata").path("callChains").spliterator(), false) + .filter(chain -> endpointName.equals(chain.path("entryPoint").path("name").asText())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("chain not found for " + endpointName)); } private static JsonNode readMachineJson(Path outputDir, String configBaseName, ObjectMapper mapper) diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseExpandedGenericEndpointLinkingTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseExpandedGenericEndpointLinkingTest.java new file mode 100644 index 0000000..e71fd8a --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseExpandedGenericEndpointLinkingTest.java @@ -0,0 +1,68 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.exporter.JsonExporter; +import click.kamil.springstatemachineexporter.service.ExportService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.StreamSupport; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * When path bindings prove machine type and event, expanded REST endpoints should link; + * the unresolved generic template remains external. + */ +class EnterpriseExpandedGenericEndpointLinkingTest { + + private static final String ORDER_PAY = "POST /api/machine/ORDER/transition/PAY"; + + @Test + void astExportExpandedOrderPayEndpointShouldLink(@TempDir Path tempDir) throws Exception { + Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise"); + ExportService exportService = new ExportService(List.of(new JsonExporter())); + exportService.runExporter(enterprise, tempDir, List.of("json"), true, List.of(), null, null, + click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, + click.kamil.springstatemachineexporter.exporter.EnumFormat.fn); + + JsonNode json = readMachineJson(tempDir, "OrderStateMachineConfiguration", new ObjectMapper()); + + JsonNode expandedPayChain = findChainByEndpoint(json, ORDER_PAY); + assertThat(expandedPayChain.path("linkResolution").asText()).isEqualTo("RESOLVED"); + assertThat(expandedPayChain.path("matchedTransitions").isArray()).isTrue(); + assertThat(expandedPayChain.path("matchedTransitions")).isNotEmpty(); + } + + private static JsonNode findChainByEndpoint(JsonNode machineJson, String endpointName) { + return StreamSupport.stream(machineJson.path("metadata").path("callChains").spliterator(), false) + .filter(chain -> endpointName.equals(chain.path("entryPoint").path("name").asText())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("chain not found for " + endpointName)); + } + + private static JsonNode readMachineJson(Path outputDir, String configBaseName, ObjectMapper mapper) + throws Exception { + Path machineDir; + try (var stream = Files.list(outputDir)) { + machineDir = stream + .filter(Files::isDirectory) + .filter(path -> path.getFileName().toString().endsWith(configBaseName)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No output for " + configBaseName)); + } + return mapper.readTree(machineDir.resolve(machineDir.getFileName() + ".json").toFile()); + } + + private static Path findProjectRoot() { + Path current = Path.of(".").toAbsolutePath(); + while (current != null && !Files.exists(current.resolve("settings.gradle"))) { + current = current.getParent(); + } + return current; + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpanderTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpanderTest.java index 937a3bb..7787009 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpanderTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EntryPointBindingExpanderTest.java @@ -244,4 +244,130 @@ class EntryPointBindingExpanderTest { assertThat(EntryPointBindingExpander.withResolvedPath(entryPoint, variants.get(0)).getName()) .isEqualTo("POST /api/order/PAY"); } + + @Test + void shouldExpandRenamedEventThroughDispatcherWhenMachineTypeIsBound(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class MachineController { + StateMachineDispatcher dispatcher; + public void transition(String machineType, String event) { + dispatcher.dispatch(machineType, event); + } + } + class StateMachineDispatcher { + void dispatch(String machineType, String eventString) { + if ("ORDER".equalsIgnoreCase(machineType)) { + fireOrder(eventString); + } else if ("DOCUMENT".equalsIgnoreCase(machineType)) { + fireDocument(eventString); + } + } + void fireOrder(String eventString) { + OrderEvent.valueOf(eventString.toUpperCase()); + } + void fireDocument(String eventString) { + DocumentEvent.valueOf(eventString.toUpperCase()); + } + } + enum OrderEvent { PAY, SHIP } + enum DocumentEvent { SUBMIT, APPROVE } + """; + Files.writeString(tempDir.resolve("App.java"), source); + + CodebaseContext context = new CodebaseContext(); + context.setResolveBindings(true); + context.scan(tempDir); + + HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context); + Map> graph = engine.buildCallGraph(); + + EntryPoint entryPoint = EntryPoint.builder() + .className("com.example.MachineController") + .methodName("transition") + .name("POST /api/machine/{machineType}/transition/{event}") + .parameters(List.of( + EntryPoint.Parameter.builder() + .name("machineType") + .type("String") + .annotations(List.of("PathVariable")) + .build(), + EntryPoint.Parameter.builder() + .name("event") + .type("String") + .annotations(List.of("PathVariable")) + .build())) + .build(); + + List> variants = + EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph); + + assertThat(variants).anySatisfy(variant -> { + assertThat(variant).containsEntry("machineType", "ORDER"); + assertThat(variant.get("event")).isIn("PAY", "SHIP"); + }); + assertThat(EntryPointBindingExpander.withResolvedPath( + entryPoint, Map.of("machineType", "ORDER", "event", "PAY")).getName()) + .isEqualTo("POST /api/machine/ORDER/transition/PAY"); + } + + @Test + void shouldExpandRenamedEventFromInlineValueOfWhenMachineTypeIsBound(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class MachineController { + StateMachineDispatcher dispatcher; + public void transition(String machineType, String event) { + dispatcher.dispatch(machineType, event); + } + } + class StateMachineDispatcher { + void dispatch(String machineType, String eventString) { + if ("ORDER".equalsIgnoreCase(machineType)) { + OrderEvent event = OrderEvent.valueOf(eventString.toUpperCase()); + send(event); + } else if ("DOCUMENT".equalsIgnoreCase(machineType)) { + DocumentEvent event = DocumentEvent.valueOf(eventString.toUpperCase()); + send(event); + } + } + void send(OrderEvent event) {} + void send(DocumentEvent event) {} + } + enum OrderEvent { PAY, SHIP } + enum DocumentEvent { SUBMIT, APPROVE } + """; + Files.writeString(tempDir.resolve("App.java"), source); + + CodebaseContext context = new CodebaseContext(); + context.setResolveBindings(true); + context.scan(tempDir); + + HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context); + Map> graph = engine.buildCallGraph(); + + EntryPoint entryPoint = EntryPoint.builder() + .className("com.example.MachineController") + .methodName("transition") + .name("POST /api/machine/{machineType}/transition/{event}") + .parameters(List.of( + EntryPoint.Parameter.builder() + .name("machineType") + .type("String") + .annotations(List.of("PathVariable")) + .build(), + EntryPoint.Parameter.builder() + .name("event") + .type("String") + .annotations(List.of("PathVariable")) + .build())) + .build(); + + List> variants = + EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph); + + assertThat(variants.stream().anyMatch(variant -> + "ORDER".equals(variant.get("machineType")) + && List.of("PAY", "SHIP").contains(variant.get("event")))).isTrue(); + } } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicyTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicyTest.java index 9baf770..6f83d34 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicyTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/ExternalTriggerPolicyTest.java @@ -72,6 +72,33 @@ class ExternalTriggerPolicyTest { entryPoint, trigger, "com.example.Api.transition", "event", context)).isFalse(); } + @Test + void concreteResolvedPathVariableEndpointShouldBeInternal() { + EntryPoint entryPoint = EntryPoint.builder() + .type(EntryPoint.Type.REST) + .name("POST /api/machine/ORDER/transition/PAY") + .className("com.example.Api") + .methodName("transition") + .parameters(List.of( + EntryPoint.Parameter.builder() + .name("machineType") + .type("String") + .annotations(List.of("PathVariable")) + .build(), + EntryPoint.Parameter.builder() + .name("event") + .type("String") + .annotations(List.of("PathVariable")) + .build())) + .build(); + TriggerPoint trigger = TriggerPoint.builder() + .event("OrderEvent.valueOf(eventString.toUpperCase())") + .build(); + + assertThat(ExternalTriggerPolicy.isExternalFromSource( + entryPoint, trigger, "com.example.Api.transition", "event", null)).isFalse(); + } + @Test void requestBodyEnumShouldBeInternal(@TempDir Path tempDir) throws Exception { String source = """ diff --git a/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json index cebb5bb..14cce82 100644 --- a/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json @@ -169,12 +169,12 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/ORDER/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/PAY", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/ORDER/transition/{event}", + "path" : "/api/machine/ORDER/transition/PAY", "verb" : "POST" }, "parameters" : [ { @@ -192,12 +192,12 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/DOCUMENT/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/SHIP", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/DOCUMENT/transition/{event}", + "path" : "/api/machine/ORDER/transition/SHIP", "verb" : "POST" }, "parameters" : [ { @@ -215,12 +215,127 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/USER/transition/{event}", + "name" : "POST /api/machine/DOCUMENT/transition/SUBMIT", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/USER/transition/{event}", + "path" : "/api/machine/DOCUMENT/transition/SUBMIT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/APPROVE", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/APPROVE", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/REJECT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/REJECT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/REGISTER", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/REGISTER", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/VERIFY", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/VERIFY", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/SUSPEND", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/SUSPEND", "verb" : "POST" }, "parameters" : [ { @@ -380,12 +495,12 @@ }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/ORDER/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/PAY", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/ORDER/transition/{event}", + "path" : "/api/machine/ORDER/transition/PAY", "verb" : "POST" }, "parameters" : [ { @@ -413,22 +528,65 @@ "sourceState" : null, "lineNumber" : 87, "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "external" : false, + "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "linkResolution" : "NO_MATCH" }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/DOCUMENT/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/SHIP", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/DOCUMENT/transition/{event}", + "path" : "/api/machine/ORDER/transition/SHIP", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "OrderEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 87, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"SHIP\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/SUBMIT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/SUBMIT", "verb" : "POST" }, "parameters" : [ { @@ -453,25 +611,123 @@ "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT", "lineNumber" : 87, - "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT" ], + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"SUBMIT\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, - "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "matchedTransitions" : [ { + "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT", + "targetState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", + "event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT" + } ], + "linkResolution" : "RESOLVED" }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/USER/transition/{event}", + "name" : "POST /api/machine/DOCUMENT/transition/APPROVE", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/USER/transition/{event}", + "path" : "/api/machine/DOCUMENT/transition/APPROVE", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "DocumentEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", + "lineNumber" : 87, + "polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE" ], + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"APPROVE\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", + "targetState" : "click.kamil.enterprise.machines.document.DocumentState.APPROVED", + "event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/REJECT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/REJECT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "DocumentEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", + "lineNumber" : 87, + "polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.REJECT" ], + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"REJECT\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", + "targetState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT", + "event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/REGISTER", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/REGISTER", "verb" : "POST" }, "parameters" : [ { @@ -499,13 +755,99 @@ "sourceState" : null, "lineNumber" : 87, "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"USER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"REGISTER\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/VERIFY", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/VERIFY", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "UserEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 87, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"VERIFY\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/SUSPEND", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/SUSPEND", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "UserEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 87, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"SUSPEND\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" } ], "properties" : { "default" : { } diff --git a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json index be54139..92c0001 100644 --- a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json @@ -131,12 +131,12 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/ORDER/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/PAY", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/ORDER/transition/{event}", + "path" : "/api/machine/ORDER/transition/PAY", "verb" : "POST" }, "parameters" : [ { @@ -154,12 +154,12 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/DOCUMENT/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/SHIP", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/DOCUMENT/transition/{event}", + "path" : "/api/machine/ORDER/transition/SHIP", "verb" : "POST" }, "parameters" : [ { @@ -177,12 +177,127 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/USER/transition/{event}", + "name" : "POST /api/machine/DOCUMENT/transition/SUBMIT", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/USER/transition/{event}", + "path" : "/api/machine/DOCUMENT/transition/SUBMIT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/APPROVE", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/APPROVE", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/REJECT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/REJECT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/REGISTER", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/REGISTER", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/VERIFY", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/VERIFY", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/SUSPEND", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/SUSPEND", "verb" : "POST" }, "parameters" : [ { @@ -303,12 +418,12 @@ }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/ORDER/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/PAY", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/ORDER/transition/{event}", + "path" : "/api/machine/ORDER/transition/PAY", "verb" : "POST" }, "parameters" : [ { @@ -333,25 +448,76 @@ "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.enterprise.machines.order.OrderState.NEW", "lineNumber" : 81, - "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"ORDER\".equalsIgnoreCase(machineType)", + "polymorphicEvents" : [ "click.kamil.enterprise.machines.order.OrderEvent.PAY" ], + "external" : false, + "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, - "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "matchedTransitions" : [ { + "sourceState" : "click.kamil.enterprise.machines.order.OrderState.NEW", + "targetState" : "click.kamil.enterprise.machines.order.OrderState.PENDING", + "event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY" + } ], + "linkResolution" : "RESOLVED" }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/DOCUMENT/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/SHIP", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/DOCUMENT/transition/{event}", + "path" : "/api/machine/ORDER/transition/SHIP", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "OrderEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : "click.kamil.enterprise.machines.order.OrderState.PENDING", + "lineNumber" : 81, + "polymorphicEvents" : [ "click.kamil.enterprise.machines.order.OrderEvent.SHIP" ], + "external" : false, + "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"SHIP\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "click.kamil.enterprise.machines.order.OrderState.PENDING", + "targetState" : "click.kamil.enterprise.machines.order.OrderState.SHIPPED", + "event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/SUBMIT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/SUBMIT", "verb" : "POST" }, "parameters" : [ { @@ -379,22 +545,108 @@ "sourceState" : null, "lineNumber" : 81, "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"ORDER\".equalsIgnoreCase(machineType)", + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"SUBMIT\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "linkResolution" : "NO_MATCH" }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/USER/transition/{event}", + "name" : "POST /api/machine/DOCUMENT/transition/APPROVE", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/USER/transition/{event}", + "path" : "/api/machine/DOCUMENT/transition/APPROVE", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "DocumentEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 81, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"APPROVE\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/REJECT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/REJECT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "DocumentEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 81, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"REJECT\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/REGISTER", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/REGISTER", "verb" : "POST" }, "parameters" : [ { @@ -422,13 +674,99 @@ "sourceState" : null, "lineNumber" : 81, "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"ORDER\".equalsIgnoreCase(machineType)", + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"REGISTER\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/VERIFY", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/VERIFY", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "UserEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 81, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"VERIFY\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/SUSPEND", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/SUSPEND", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "UserEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 81, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"SUSPEND\".equalsIgnoreCase(event) && \"ORDER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" } ], "properties" : { "default" : { } diff --git a/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json index 331debf..cf4dfed 100644 --- a/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json @@ -124,12 +124,12 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/ORDER/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/PAY", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/ORDER/transition/{event}", + "path" : "/api/machine/ORDER/transition/PAY", "verb" : "POST" }, "parameters" : [ { @@ -147,12 +147,12 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/DOCUMENT/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/SHIP", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/DOCUMENT/transition/{event}", + "path" : "/api/machine/ORDER/transition/SHIP", "verb" : "POST" }, "parameters" : [ { @@ -170,12 +170,127 @@ } ] }, { "type" : "REST", - "name" : "POST /api/machine/USER/transition/{event}", + "name" : "POST /api/machine/DOCUMENT/transition/SUBMIT", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/USER/transition/{event}", + "path" : "/api/machine/DOCUMENT/transition/SUBMIT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/APPROVE", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/APPROVE", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/REJECT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/REJECT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/REGISTER", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/REGISTER", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/VERIFY", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/VERIFY", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/SUSPEND", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/SUSPEND", "verb" : "POST" }, "parameters" : [ { @@ -292,12 +407,12 @@ }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/ORDER/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/PAY", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/ORDER/transition/{event}", + "path" : "/api/machine/ORDER/transition/PAY", "verb" : "POST" }, "parameters" : [ { @@ -325,22 +440,65 @@ "sourceState" : null, "lineNumber" : 93, "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "external" : false, + "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "linkResolution" : "NO_MATCH" }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/DOCUMENT/transition/{event}", + "name" : "POST /api/machine/ORDER/transition/SHIP", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/DOCUMENT/transition/{event}", + "path" : "/api/machine/ORDER/transition/SHIP", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "OrderEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 93, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"ORDER\".equalsIgnoreCase(machineType) && \"SHIP\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/SUBMIT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/SUBMIT", "verb" : "POST" }, "parameters" : [ { @@ -368,22 +526,202 @@ "sourceState" : null, "lineNumber" : 93, "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"SUBMIT\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "linkResolution" : "NO_MATCH" }, { "entryPoint" : { "type" : "REST", - "name" : "POST /api/machine/USER/transition/{event}", + "name" : "POST /api/machine/DOCUMENT/transition/APPROVE", "className" : "click.kamil.enterprise.web.StateMachineController", "methodName" : "transition", "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", "metadata" : { - "path" : "/api/machine/USER/transition/{event}", + "path" : "/api/machine/DOCUMENT/transition/APPROVE", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "DocumentEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 93, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"APPROVE\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/DOCUMENT/transition/REJECT", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/DOCUMENT/transition/REJECT", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "DocumentEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 93, + "polymorphicEvents" : [ ], + "external" : false, + "constraint" : "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"REJECT\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : null, + "linkResolution" : "NO_MATCH" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/REGISTER", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/REGISTER", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "UserEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : "click.kamil.enterprise.machines.user.UserState.GUEST", + "lineNumber" : 93, + "polymorphicEvents" : [ "click.kamil.enterprise.machines.user.UserEvent.REGISTER" ], + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"REGISTER\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "click.kamil.enterprise.machines.user.UserState.GUEST", + "targetState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED", + "event" : "click.kamil.enterprise.machines.user.UserEvent.REGISTER" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/VERIFY", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/VERIFY", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "machineType", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "event", + "type" : "String", + "annotations" : [ "PathVariable" ] + }, { + "name" : "userId", + "type" : "String", + "annotations" : [ "RequestParam" ] + } ] + }, + "methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ], + "triggerPoint" : { + "event" : "UserEvent.valueOf(eventString.toUpperCase())", + "className" : "click.kamil.enterprise.web.StateMachineDispatcher", + "methodName" : "dispatch", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED", + "lineNumber" : 93, + "polymorphicEvents" : [ "click.kamil.enterprise.machines.user.UserEvent.VERIFY" ], + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"VERIFY\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "ambiguous" : false + }, + "contextMachineId" : null, + "matchedTransitions" : [ { + "sourceState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED", + "targetState" : "click.kamil.enterprise.machines.user.UserState.VERIFIED", + "event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY" + } ], + "linkResolution" : "RESOLVED" + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/machine/USER/transition/SUSPEND", + "className" : "click.kamil.enterprise.web.StateMachineController", + "methodName" : "transition", + "sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java", + "metadata" : { + "path" : "/api/machine/USER/transition/SUSPEND", "verb" : "POST" }, "parameters" : [ { @@ -411,13 +749,13 @@ "sourceState" : null, "lineNumber" : 93, "polymorphicEvents" : [ ], - "external" : true, - "constraint" : "\"USER\".equalsIgnoreCase(machineType) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", + "external" : false, + "constraint" : "\"USER\".equalsIgnoreCase(machineType) && \"SUSPEND\".equalsIgnoreCase(event) && !(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", "ambiguous" : false }, "contextMachineId" : null, "matchedTransitions" : null, - "linkResolution" : "UNRESOLVED_EXTERNAL" + "linkResolution" : "NO_MATCH" } ], "properties" : { "default" : { }