Improve multi-machine scoping, path binding expansion, and analysis precision.

Expand generic REST path variables into concrete command keys, filter entry points and call chains per machine, and tighten ambiguous routing and switch constraints so layered dispatchers link fewer false positives.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-11 21:26:13 +02:00
parent 8a97bceca4
commit ea9b8d6cff
30 changed files with 820 additions and 2304 deletions

View File

@@ -8,16 +8,45 @@ Static analysis tool to extract and visualize Spring State Machines with deep co
## Usage ## Usage
Analysis is **source-first**: the exporter reads project source and build metadata (`pom.xml`, `settings.gradle`) statically. It does **not** run Maven or Gradle against the project being analyzed.
### 1. Extract Metadata (JSON) ### 1. Extract Metadata (JSON)
```bash ```bash
./gradlew :state_machine_exporter:run --args="-i ./my-project -o ./out -f json" ./gradlew :state_machine_exporter:run --args="-i ./my-project -o ./out -f json"
``` ```
Common options:
| Flag | Description |
|------|-------------|
| `-i, --input` | Project directory to scan (defaults to `.`) |
| `-j, --json` | Re-render from an existing JSON export instead of scanning source |
| `-o, --output` | Output directory (default `./out`) |
| `-f, --format` | Comma-separated formats: `json`, `dot`, `puml`, `scxml` |
| `-p, --profiles` | Spring profiles for property placeholder resolution |
| `--event`, `--state` | Enum formatting: `fn` (default), `fqn`, `sn` |
| `--debug` | Verbose analysis logging and unresolved chain diagnostics |
| `--no-diamonds` | Render choice/junction states as regular nodes (default: diamonds) |
| `--include-generated-sources` | Also scan `target/` / `build/` generated `.java` trees (default: on) |
| `--no-inline-accessors` | Disable accessor indexing/inlining (enabled by default) |
### 2. Generate Interactive Portal (HTML) ### 2. Generate Interactive Portal (HTML)
```bash ```bash
./gradlew :state_machine_exporter_html:run --args="-i ./my-project -o ./out_html" ./gradlew :state_machine_exporter_html:run --args="-i ./my-project -o ./out_html"
``` ```
*Supports `--profiles prod` to resolve Spring property placeholders.*
HTML-specific options:
| Flag | Description |
|------|-------------|
| `-f, --flows` | Custom `flows.json` path |
| `-m, --machine` | Export only machines whose name contains this filter |
| `--open` | Open the generated portal in the default browser |
| `--no-metadata-pane` | Hide the left metadata pane (entry points, flows) |
| `--no-diamonds` | Render choice pseudostates as normal nodes |
| `--debug` | Same diagnostic mode as the core exporter |
| `--include-generated-sources` | Same generated-source scanning as the core exporter |
| `-p, --profiles` | Spring profiles for property resolution |
## Business Flows ## Business Flows
Define sequence of events in `src/main/resources/flows.json`: Define sequence of events in `src/main/resources/flows.json`:

View File

@@ -17,6 +17,7 @@ public class EntryPointEnricher implements AnalysisEnricher {
log.info("Enriching {} with entry points", result.getName()); log.info("Enriching {} with entry points", result.getName());
List<EntryPoint> entryPoints = intelligence.findEntryPoints(); List<EntryPoint> entryPoints = intelligence.findEntryPoints();
entryPoints = MachineScopeFilter.filterEntryPointsForMachine(entryPoints, result.getName(), context);
result.addMetadata(CodebaseMetadata.builder() result.addMetadata(CodebaseMetadata.builder()
.entryPoints(entryPoints) .entryPoints(entryPoints)

View File

@@ -3,11 +3,13 @@ package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine; import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine; import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.model.CallChain; 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.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
/** /**
* Filters codebase-wide analysis artifacts to those relevant for a single state machine config. * Filters codebase-wide analysis artifacts to those relevant for a single state machine config.
@@ -47,6 +49,89 @@ public final class MachineScopeFilter {
return filtered; return filtered;
} }
public static List<EntryPoint> filterEntryPointsForMachine(
List<EntryPoint> entryPoints, String machineName, CodebaseContext context) {
if (entryPoints == null || machineName == null) {
return entryPoints == null ? List.of() : entryPoints;
}
String machineDomain = extractMachineDomainKey(machineName);
List<EntryPoint> filtered = new ArrayList<>();
for (EntryPoint entryPoint : entryPoints) {
if (isEntryPointForMachine(entryPoint, machineDomain, machineName, context)) {
filtered.add(entryPoint);
}
}
return filtered;
}
private static boolean isEntryPointForMachine(
EntryPoint entryPoint, String machineDomain, String machineName, CodebaseContext context) {
if (entryPoint == null) {
return false;
}
String path = entryPoint.getMetadata() != null ? entryPoint.getMetadata().get("path") : null;
if (path != null) {
String pathDomain = inferDomainFromPath(path);
if (pathDomain != null && machineDomain != null && !domainsMatch(pathDomain, machineDomain)) {
return false;
}
if (pathDomain == null && path.contains("{")) {
return true;
}
}
CallChain probe = CallChain.builder()
.entryPoint(entryPoint)
.methodChain(List.of(entryPoint.getClassName() + "." + entryPoint.getMethodName()))
.build();
return ROUTING.isRoutedToCorrectMachine(probe, machineName, context);
}
private static String inferDomainFromPath(String path) {
String lower = path.toLowerCase(Locale.ROOT);
if (lower.contains("/orders") || lower.contains("order.")) {
return "ORDER";
}
if (lower.contains("/documents") || lower.contains("document.")) {
return "DOCUMENT";
}
return null;
}
private static boolean domainsMatch(String pathDomain, String machineDomain) {
String normalizedPath = normalizeDomainKey(pathDomain);
String normalizedMachine = normalizeDomainKey(machineDomain);
return normalizedPath.equalsIgnoreCase(normalizedMachine)
|| normalizedMachine.startsWith(normalizedPath)
|| normalizedPath.startsWith(normalizedMachine);
}
private static String extractMachineDomainKey(String machineName) {
String simple = machineName.substring(machineName.lastIndexOf('.') + 1);
String upper = simple.toUpperCase(Locale.ROOT);
for (String marker : List.of("STATEMACHINECONFIGURATION", "STATEMACHINE", "CONFIGURATION", "CONFIG")) {
int idx = upper.indexOf(marker);
if (idx > 0) {
return normalizeDomainKey(upper.substring(0, idx));
}
}
int stateIdx = upper.indexOf("STATE");
if (stateIdx > 0) {
return normalizeDomainKey(upper.substring(0, stateIdx));
}
return normalizeDomainKey(upper);
}
private static String normalizeDomainKey(String domain) {
if (domain == null || domain.isEmpty()) {
return domain;
}
String upper = domain.toUpperCase(Locale.ROOT);
if (upper.startsWith("STANDARD") && upper.length() > "STANDARD".length()) {
return upper.substring("STANDARD".length());
}
return upper;
}
private static boolean isTriggerForMachine(TriggerPoint trigger, String machineName, CodebaseContext context) { private static boolean isTriggerForMachine(TriggerPoint trigger, String machineName, CodebaseContext context) {
if (trigger == null) { if (trigger == null) {
return false; return false;

View File

@@ -3,6 +3,7 @@ package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult; import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain; import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata; import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition; import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.service.CodebaseIntelligenceProvider; import click.kamil.springstatemachineexporter.analysis.service.CodebaseIntelligenceProvider;
@@ -95,10 +96,12 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
List<TriggerPoint> scopedTriggers = MachineScopeFilter.filterTriggersForMachine( List<TriggerPoint> scopedTriggers = MachineScopeFilter.filterTriggersForMachine(
result.getMetadata().getTriggers(), result.getName(), context); result.getMetadata().getTriggers(), result.getName(), context);
List<EntryPoint> scopedEntryPoints = MachineScopeFilter.filterEntryPointsForMachine(
result.getMetadata().getEntryPoints(), result.getName(), context);
CodebaseMetadata updatedMetadata = CodebaseMetadata.builder() CodebaseMetadata updatedMetadata = CodebaseMetadata.builder()
.triggers(scopedTriggers) .triggers(scopedTriggers)
.entryPoints(result.getMetadata().getEntryPoints()) .entryPoints(scopedEntryPoints)
.callChains(updatedChains) .callChains(updatedChains)
.properties(result.getMetadata().getProperties()) .properties(result.getMetadata().getProperties())
.build(); .build();

View File

@@ -2,6 +2,7 @@ package click.kamil.springstatemachineexporter.analysis.enricher.routing;
import click.kamil.springstatemachineexporter.analysis.model.CallChain; import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -110,16 +111,52 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
return false; return false;
} }
} }
return true; return context == null || isSingleStateMachine(context);
}
private int countStateMachines(CodebaseContext context) {
if (context == null) {
return 0;
}
Set<String> machines = new HashSet<>();
for (TypeDeclaration td : context.findEntryPointClasses(
java.util.List.of("EnableStateMachineFactory", "EnableStateMachine"))) {
String fqn = context.getFqn(td);
if (fqn != null) {
machines.add(fqn);
}
}
for (TypeDeclaration td : context.getTypeDeclarations()) {
if (td == null || td.isInterface()) {
continue;
}
if (context.extendsStateMachineConfigurerAdapter(td)) {
String fqn = context.getFqn(td);
if (fqn != null) {
machines.add(fqn);
}
}
}
if (!machines.isEmpty()) {
return machines.size();
}
return context.findBeanMethodsReturning(
java.util.Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory")).size();
} }
private boolean isDomainMatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix) { private boolean isDomainMatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix) {
if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) return false; if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) {
return false;
}
if (smPackage.startsWith(chainPackage) || chainPackage.startsWith(smPackage)) { if (smPackage.equals(chainPackage)) {
return true; return true;
} }
if (!chainPackage.startsWith(smPackage + ".") && !smPackage.startsWith(chainPackage + ".")) {
return false;
}
// Find deepest common package root // Find deepest common package root
String[] p1 = smPackage.split("\\."); String[] p1 = smPackage.split("\\.");
String[] p2 = chainPackage.split("\\."); String[] p2 = chainPackage.split("\\.");
@@ -371,10 +408,6 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
} }
private boolean isSingleStateMachine(CodebaseContext context) { private boolean isSingleStateMachine(CodebaseContext context) {
if (context == null) return false; return countStateMachines(context) <= 1;
int count = 0;
count += context.findEntryPointClasses(java.util.List.of("EnableStateMachineFactory", "EnableStateMachine")).size();
count += context.findBeanMethodsReturning(java.util.Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory")).size();
return count <= 1;
} }
} }

View File

@@ -74,9 +74,10 @@ public final class BooleanConstraintEvaluator {
if (bindings == null || bindings.isEmpty()) { if (bindings == null || bindings.isEmpty()) {
return true; return true;
} }
for (String varName : extractEqualityVariables(constraint)) { Set<String> equalityVars = extractEqualityVariables(constraint);
if (!bindings.containsKey(varName) || varName.equals(bindings.get(varName))) { for (String varName : equalityVars) {
return true; if (!bindings.containsKey(varName)) {
return false;
} }
} }
String expr = constraint; String expr = constraint;

View File

@@ -104,6 +104,17 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
List<String> startMethods = new ArrayList<>(); List<String> startMethods = new ArrayList<>();
startMethods.add(startMethod); startMethods.add(startMethod);
List<Map<String, String>> bindingVariants =
EntryPointBindingExpander.expandPathVariableBindings(ep, context, callGraph);
if (bindingVariants.isEmpty()) {
bindingVariants = List.of(Map.of());
}
for (Map<String, String> initialBindings : bindingVariants) {
EntryPoint chainEntryPoint = initialBindings.isEmpty()
? ep
: EntryPointBindingExpander.withResolvedPath(ep, initialBindings);
boolean foundAny = false; boolean foundAny = false;
for (TriggerPoint tp : triggers) { for (TriggerPoint tp : triggers) {
String targetMethod = tp.getClassName() + "." + tp.getMethodName(); String targetMethod = tp.getClassName() + "." + tp.getMethodName();
@@ -113,7 +124,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
} }
Set<List<String>> uniquePaths = new LinkedHashSet<>(allPaths); Set<List<String>> uniquePaths = new LinkedHashSet<>(allPaths);
for (List<String> path : uniquePaths) { for (List<String> path : uniquePaths) {
if (!pathBindingEvaluator.isPathCompatible(path, callGraph, pathFinder)) { if (!pathBindingEvaluator.isPathCompatible(path, callGraph, pathFinder, initialBindings)) {
continue; continue;
} }
foundAny = true; foundAny = true;
@@ -121,7 +132,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
if (resolvedTp != null) { if (resolvedTp != null) {
String contextMachineId = pathFinder.extractContextMachineId(path, callGraph); String contextMachineId = pathFinder.extractContextMachineId(path, callGraph);
chains.add(CallChain.builder() chains.add(CallChain.builder()
.entryPoint(ep) .entryPoint(chainEntryPoint)
.triggerPoint(resolvedTp) .triggerPoint(resolvedTp)
.methodChain(path) .methodChain(path)
.contextMachineId(contextMachineId) .contextMachineId(contextMachineId)
@@ -130,7 +141,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
} }
} }
if (!foundAny && log.isDebugEnabled()) { if (!foundAny && log.isDebugEnabled()) {
log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet()); log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}",
chainEntryPoint.getName(), startMethod, callGraph.keySet());
}
} }
} }
return chains; return chains;

View File

@@ -299,7 +299,8 @@ public class ConstantExtractor {
if (budget == null) { if (budget == null) {
budget = ResolutionBudget.defaults(); budget = ResolutionBudget.defaults();
} }
if (budget.isMethodReturnExhausted(depth)) { final ResolutionBudget activeBudget = budget;
if (activeBudget.isMethodReturnExhausted(depth)) {
log.debug("Exiting resolveMethodReturnConstant early (depth limit) for {}.{}", className, methodName); log.debug("Exiting resolveMethodReturnConstant early (depth limit) for {}.{}", className, methodName);
return Collections.emptyList(); return Collections.emptyList();
} }
@@ -309,7 +310,7 @@ public class ConstantExtractor {
return Collections.emptyList(); return Collections.emptyList();
} }
Optional<AccessorSummary> indexedAccessor = lookupIndexedGetter(className, methodName, budget); Optional<AccessorSummary> indexedAccessor = lookupIndexedGetter(className, methodName, activeBudget);
TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null; TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
if (tempTd == null) { if (tempTd == null) {
tempTd = context.getTypeDeclaration(className); tempTd = context.getTypeDeclaration(className);
@@ -359,7 +360,8 @@ public class ConstantExtractor {
if (targetTd == null) targetTd = context.getTypeDeclaration(cName); if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
List<String> delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); List<String> delegationResult = resolveMethodReturnConstant(
cName, mName, depth + 1, visited, contextCu, activeBudget);
constants.addAll(delegationResult); constants.addAll(delegationResult);
handled = true; handled = true;
} }
@@ -378,7 +380,8 @@ public class ConstantExtractor {
if (targetTd == null) targetTd = context.getTypeDeclaration(cName); if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
List<String> delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); List<String> delegationResult = resolveMethodReturnConstant(
cName, mName, depth + 1, visited, contextCu, activeBudget);
constants.addAll(delegationResult); constants.addAll(delegationResult);
handled = true; handled = true;
} }
@@ -425,7 +428,7 @@ public class ConstantExtractor {
if (impls != null) { if (impls != null) {
for (String implName : impls) { for (String implName : impls) {
List<String> delegationResult = resolveMethodReturnConstant( List<String> delegationResult = resolveMethodReturnConstant(
implName, methodName, depth + 1, visited, contextCu, budget); implName, methodName, depth + 1, visited, contextCu, activeBudget);
constants.addAll(delegationResult); constants.addAll(delegationResult);
} }
} }

View File

@@ -72,7 +72,8 @@ public class ConstructorAnalyzer {
targetClass = context.getFqn(targetTd); targetClass = context.getFqn(targetTd);
} }
} }
results.addAll(constantExtractor.resolveMethodReturnConstant(targetClass, calledName, 0, visited, cu)); results.addAll(constantExtractor.resolveMethodReturnConstant(
targetClass, calledName, 0, visited, cu, budget));
} else { } else {
String val = constantResolver.resolve(tracedRight, context); String val = constantResolver.resolve(tracedRight, context);
if (val != null) results.add(val); if (val != null) results.add(val);
@@ -116,7 +117,8 @@ public class ConstructorAnalyzer {
} }
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
results.addAll(constantExtractor.resolveMethodReturnConstant(targetClass, calledName, 0, visited, cu)); results.addAll(constantExtractor.resolveMethodReturnConstant(
targetClass, calledName, 0, visited, cu, budget));
} else { } else {
String val = constantResolver.resolve(tracedRight, context); String val = constantResolver.resolve(tracedRight, context);
if (val != null) results.add(val); if (val != null) results.add(val);
@@ -164,7 +166,7 @@ public class ConstructorAnalyzer {
if (superFqn != null && !superFqn.equals("java.lang.Object")) { if (superFqn != null && !superFqn.equals("java.lang.Object")) {
TypeDeclaration superTd = context.getTypeDeclaration(superFqn); TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
if (superTd != null) { if (superTd != null) {
results.addAll(traceFieldInConstructors(superTd, fieldName, context, visited, budget)); results.addAll(traceFieldInConstructors(superTd, fieldName, context, visited, budget.child()));
} }
} }
} }
@@ -177,7 +179,7 @@ public class ConstructorAnalyzer {
for (String implFqn : impls) { for (String implFqn : impls) {
TypeDeclaration implTd = context.getTypeDeclaration(implFqn); TypeDeclaration implTd = context.getTypeDeclaration(implFqn);
if (implTd != null) { if (implTd != null) {
results.addAll(traceFieldInConstructors(implTd, fieldName, context, visited, budget)); results.addAll(traceFieldInConstructors(implTd, fieldName, context, visited, budget.child()));
} }
} }
} }
@@ -189,11 +191,10 @@ public class ConstructorAnalyzer {
Collection<TypeDeclaration> allTypes = context.getTypeDeclarations(); Collection<TypeDeclaration> allTypes = context.getTypeDeclarations();
if (allTypes != null) { if (allTypes != null) {
int matchedCount = 0; int matchedCount = 0;
ResolutionBudget localBudget = ResolutionBudget.defaults();
for (TypeDeclaration typeDecl : allTypes) { for (TypeDeclaration typeDecl : allTypes) {
if (typeDecl == null) continue; if (typeDecl == null) continue;
if (context.hasField(typeDecl, fieldName)) { if (context.hasField(typeDecl, fieldName)) {
results.addAll(traceFieldInConstructors(typeDecl, fieldName, context, visited, localBudget)); results.addAll(traceFieldInConstructors(typeDecl, fieldName, context, visited, budget.child()));
matchedCount++; matchedCount++;
if (matchedCount >= budget.globalFieldScanLimit()) { if (matchedCount >= budget.globalFieldScanLimit()) {
break; break;

View File

@@ -0,0 +1,183 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
/**
* Expands REST entry points whose {@code @PathVariable} parameters flow into string-keyed
* switch mappers (e.g. {@code StringCommandMapper.fromString}) into concrete binding variants.
*/
public final class EntryPointBindingExpander {
private EntryPointBindingExpander() {
}
public static List<Map<String, String>> expandPathVariableBindings(
EntryPoint entryPoint,
CodebaseContext context,
Map<String, List<CallEdge>> callGraph) {
if (entryPoint == null || entryPoint.getParameters() == null || entryPoint.getParameters().isEmpty()) {
return List.of();
}
for (EntryPoint.Parameter parameter : entryPoint.getParameters()) {
if (!isPathOrQueryVariable(parameter)) {
continue;
}
List<String> cases = resolveStringCasesForParameter(
entryPoint.getClassName() + "." + entryPoint.getMethodName(),
parameter.getName(),
context,
callGraph);
if (cases.isEmpty()) {
continue;
}
List<Map<String, String>> variants = new ArrayList<>();
for (String caseValue : cases) {
variants.add(Map.of(parameter.getName(), caseValue));
}
return variants;
}
return List.of();
}
public static EntryPoint withResolvedPath(EntryPoint entryPoint, Map<String, String> bindings) {
if (entryPoint == null || bindings == null || bindings.isEmpty()) {
return entryPoint;
}
String name = entryPoint.getName();
Map<String, String> metadata = entryPoint.getMetadata() == null
? new LinkedHashMap<>()
: new LinkedHashMap<>(entryPoint.getMetadata());
for (Map.Entry<String, String> binding : bindings.entrySet()) {
String placeholder = "{" + binding.getKey() + "}";
if (name != null) {
name = name.replace(placeholder, binding.getValue());
}
String path = metadata.get("path");
if (path != null) {
metadata.put("path", path.replace(placeholder, binding.getValue()));
}
}
return EntryPoint.builder()
.type(entryPoint.getType())
.name(name)
.className(entryPoint.getClassName())
.methodName(entryPoint.getMethodName())
.sourceFile(entryPoint.getSourceFile())
.metadata(metadata)
.parameters(entryPoint.getParameters())
.build();
}
private static boolean isPathOrQueryVariable(EntryPoint.Parameter parameter) {
if (parameter.getAnnotations() == null) {
return false;
}
return parameter.getAnnotations().stream()
.anyMatch(a -> "PathVariable".equals(a) || "RequestParam".equals(a));
}
private static List<String> resolveStringCasesForParameter(
String startMethod,
String paramName,
CodebaseContext context,
Map<String, List<CallEdge>> callGraph) {
ArrayDeque<String> queue = new ArrayDeque<>();
Set<String> visited = new HashSet<>();
queue.add(startMethod);
while (!queue.isEmpty()) {
String methodFqn = queue.poll();
if (!visited.add(methodFqn)) {
continue;
}
if (visited.size() > 12) {
break;
}
List<String> localCases = extractStringSwitchCases(methodFqn, paramName, context);
if (!localCases.isEmpty()) {
return localCases;
}
List<CallEdge> edges = callGraph.get(methodFqn);
if (edges == null) {
continue;
}
for (CallEdge edge : edges) {
if (edge.getTargetMethod() != null) {
queue.add(edge.getTargetMethod());
}
}
}
return List.of();
}
static List<String> extractStringSwitchCases(String methodFqn, String paramName, CodebaseContext context) {
MethodDeclaration method = findMethodDeclaration(methodFqn, context);
if (method == null || method.getBody() == null) {
return List.of();
}
if (!hasParameter(method, paramName)) {
return List.of();
}
LinkedHashSet<String> cases = new LinkedHashSet<>();
method.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(SwitchStatement node) {
collectCasesFromSwitch(node.getExpression(), node.statements(), paramName, cases);
return super.visit(node);
}
@Override
public boolean visit(SwitchExpression node) {
collectCasesFromSwitch(node.getExpression(), node.statements(), paramName, cases);
return super.visit(node);
}
});
return List.copyOf(cases);
}
private static void collectCasesFromSwitch(
Expression selector,
List<?> statements,
String paramName,
Set<String> cases) {
if (!(selector instanceof SimpleName simpleName) || !paramName.equals(simpleName.getIdentifier())) {
return;
}
for (Object statementObj : statements) {
if (statementObj instanceof SwitchCase switchCase && !switchCase.isDefault()) {
for (Object exprObj : switchCase.expressions()) {
if (exprObj instanceof StringLiteral stringLiteral) {
cases.add(stringLiteral.getLiteralValue());
}
}
}
}
}
private static boolean hasParameter(MethodDeclaration method, String paramName) {
for (Object paramObj : method.parameters()) {
if (paramObj instanceof SingleVariableDeclaration param
&& paramName.equals(param.getName().getIdentifier())) {
return true;
}
}
return false;
}
private static MethodDeclaration findMethodDeclaration(String methodFqn, CodebaseContext context) {
if (methodFqn == null || !methodFqn.contains(".")) {
return null;
}
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
TypeDeclaration typeDeclaration = context.getTypeDeclaration(className);
if (typeDeclaration == null) {
return null;
}
return context.findMethodDeclaration(typeDeclaration, methodName, true);
}
}

View File

@@ -64,7 +64,7 @@ public class GenericEventDetector {
} }
MethodDeclaration method = findEnclosingMethod(node); MethodDeclaration method = findEnclosingMethod(node);
TypeDeclaration type = findEnclosingType(node); AbstractTypeDeclaration type = findEnclosingAbstractType(node);
if (type == null) return; if (type == null) return;
String sourceState = extractSourceState(node); String sourceState = extractSourceState(node);
@@ -191,18 +191,11 @@ public class GenericEventDetector {
if (receiver instanceof SimpleName sn) { if (receiver instanceof SimpleName sn) {
String name = sn.getIdentifier(); String name = sn.getIdentifier();
// Check if it's a field in the enclosing class AbstractTypeDeclaration enclosing = findEnclosingAbstractType(node);
TypeDeclaration td = findEnclosingType(node); if (enclosing != null) {
if (td != null) { Type memberType = lookupMemberType(enclosing, name);
for (FieldDeclaration fd : td.getFields()) { if (memberType != null) {
for (Object fragObj : fd.fragments()) { return memberType + "." + methodName;
if (fragObj instanceof VariableDeclarationFragment fragment) {
if (fragment.getName().getIdentifier().equals(name)) {
String typeName = fd.getType().toString();
return typeName + "." + methodName;
}
}
}
} }
} }
return name + "." + methodName; return name + "." + methodName;
@@ -230,7 +223,7 @@ public class GenericEventDetector {
} }
MethodDeclaration method = findEnclosingMethod(node); MethodDeclaration method = findEnclosingMethod(node);
TypeDeclaration type = findEnclosingType(node); AbstractTypeDeclaration type = findEnclosingAbstractType(node);
if (type == null) return Collections.emptyList(); if (type == null) return Collections.emptyList();
@@ -516,11 +509,49 @@ public class GenericEventDetector {
} }
private TypeDeclaration findEnclosingType(ASTNode node) { private TypeDeclaration findEnclosingType(ASTNode node) {
AbstractTypeDeclaration type = findEnclosingAbstractType(node);
return type instanceof TypeDeclaration td ? td : null;
}
private AbstractTypeDeclaration findEnclosingAbstractType(ASTNode node) {
ASTNode parent = node.getParent(); ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof TypeDeclaration)) { while (parent != null && !(parent instanceof AbstractTypeDeclaration)) {
parent = parent.getParent(); parent = parent.getParent();
} }
return (TypeDeclaration) parent; return (AbstractTypeDeclaration) parent;
}
private List<FieldDeclaration> getFieldDeclarations(AbstractTypeDeclaration type) {
if (type instanceof TypeDeclaration td) {
return List.of(td.getFields());
}
if (type instanceof RecordDeclaration rd) {
return List.of(rd.getFields());
}
return List.of();
}
private Type lookupMemberType(AbstractTypeDeclaration enclosingType, String memberName) {
if (enclosingType == null || memberName == null) {
return null;
}
for (FieldDeclaration fd : getFieldDeclarations(enclosingType)) {
for (Object fragObj : fd.fragments()) {
if (fragObj instanceof VariableDeclarationFragment fragment
&& fragment.getName().getIdentifier().equals(memberName)) {
return fd.getType();
}
}
}
if (enclosingType instanceof RecordDeclaration rd) {
for (Object compObj : rd.recordComponents()) {
if (compObj instanceof SingleVariableDeclaration svd
&& svd.getName().getIdentifier().equals(memberName)) {
return svd.getType();
}
}
}
return null;
} }
private String[] resolveStateMachineTypeArguments(MethodInvocation node) { private String[] resolveStateMachineTypeArguments(MethodInvocation node) {
@@ -576,7 +607,7 @@ public class GenericEventDetector {
} }
private String resolveGenericTypeVariable(String typeVarName, ASTNode node, CompilationUnit cu) { private String resolveGenericTypeVariable(String typeVarName, ASTNode node, CompilationUnit cu) {
TypeDeclaration enclosingClass = findEnclosingType(node); AbstractTypeDeclaration enclosingClass = findEnclosingAbstractType(node);
if (enclosingClass == null) return typeVarName; if (enclosingClass == null) return typeVarName;
String className = context.getFqn(enclosingClass); String className = context.getFqn(enclosingClass);
if (className == null) return typeVarName; if (className == null) return typeVarName;
@@ -593,7 +624,11 @@ public class GenericEventDetector {
String baseName = resolveTypeToFqn(baseType, (CompilationUnit) implNode.getRoot()); String baseName = resolveTypeToFqn(baseType, (CompilationUnit) implNode.getRoot());
if (className.equals(baseName) || className.endsWith("." + baseName)) { if (className.equals(baseName) || className.endsWith("." + baseName)) {
int typeIndex = -1; int typeIndex = -1;
List<?> typeParams = enclosingClass.typeParameters(); List<?> typeParams = enclosingClass instanceof TypeDeclaration typeDecl
? typeDecl.typeParameters()
: enclosingClass instanceof RecordDeclaration recordDecl
? recordDecl.typeParameters()
: List.of();
for (int i = 0; i < typeParams.size(); i++) { for (int i = 0; i < typeParams.size(); i++) {
TypeParameter tp = (TypeParameter) typeParams.get(i); TypeParameter tp = (TypeParameter) typeParams.get(i);
if (tp.getName().getIdentifier().equals(typeVarName)) { if (tp.getName().getIdentifier().equals(typeVarName)) {
@@ -665,17 +700,12 @@ public class GenericEventDetector {
} }
} }
// Check fields // Check fields and record components
TypeDeclaration enclosingType = findEnclosingType(node); AbstractTypeDeclaration enclosingType = findEnclosingAbstractType(node);
if (enclosingType != null) { if (enclosingType != null) {
for (FieldDeclaration fd : enclosingType.getFields()) { Type memberType = lookupMemberType(enclosingType, varName);
for (Object fragObj : fd.fragments()) { if (memberType != null) {
if (fragObj instanceof VariableDeclarationFragment fragment) { return memberType;
if (fragment.getName().getIdentifier().equals(varName)) {
return fd.getType();
}
}
}
} }
} }

View File

@@ -97,8 +97,13 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
if (cachedCallChains != null) { if (cachedCallChains != null) {
return cachedCallChains; return cachedCallChains;
} }
log.info("JdtIntelligenceProvider searching for call chains between {} entry points and {} triggers", entryPoints.size(), triggers.size()); List<EntryPoint> allEntryPoints = findEntryPoints();
cachedCallChains = callGraphEngine.findChains(entryPoints, triggers); List<TriggerPoint> allTriggers = triggers != null && !triggers.isEmpty()
? triggers
: findTriggerPoints();
log.info("JdtIntelligenceProvider searching for call chains between {} entry points and {} triggers",
allEntryPoints.size(), allTriggers.size());
cachedCallChains = callGraphEngine.findChains(allEntryPoints, allTriggers);
return cachedCallChains; return cachedCallChains;
} }

View File

@@ -50,11 +50,18 @@ public class PathBindingEvaluator {
return bindings; return bindings;
} }
public boolean isPathCompatible(List<String> path, Map<String, List<CallEdge>> callGraph, CallGraphPathFinder pathFinder) { public boolean isPathCompatible(
List<String> path,
Map<String, List<CallEdge>> callGraph,
CallGraphPathFinder pathFinder,
Map<String, String> initialBindings) {
if (path == null || path.size() < 2) { if (path == null || path.size() < 2) {
return true; return true;
} }
Map<String, String> bindings = new HashMap<>(); Map<String, String> bindings = new HashMap<>();
if (initialBindings != null) {
bindings.putAll(initialBindings);
}
for (int i = 0; i < path.size() - 1; i++) { for (int i = 0; i < path.size() - 1; i++) {
String caller = path.get(i); String caller = path.get(i);
String target = path.get(i + 1); String target = path.get(i + 1);
@@ -72,6 +79,10 @@ public class PathBindingEvaluator {
return true; return true;
} }
public boolean isPathCompatible(List<String> path, Map<String, List<CallEdge>> callGraph, CallGraphPathFinder pathFinder) {
return isPathCompatible(path, callGraph, pathFinder, Map.of());
}
private void enrichBindings( private void enrichBindings(
String caller, String caller,
String target, String target,

View File

@@ -845,8 +845,11 @@ public class CodebaseContext {
Type superclass = td.getSuperclassType(); Type superclass = td.getSuperclassType();
if (superclass != null) { if (superclass != null) {
String superclassName = getSimpleName(superclass); String superclassName = getSimpleName(superclass);
if (knownAdapters.contains(superclassName)) if (knownAdapters.contains(superclassName)
|| superclass.toString().contains("StateMachineConfigurerAdapter")
|| superclass.toString().contains("EnumStateMachineConfigurerAdapter")) {
return true; return true;
}
TypeDeclaration superTd = getTypeDeclaration(superclassName, cu); TypeDeclaration superTd = getTypeDeclaration(superclassName, cu);
if (superTd != null && extendsStateMachineConfigurerAdapterLegacy(superTd, visited)) if (superTd != null && extendsStateMachineConfigurerAdapterLegacy(superTd, visited))
return true; return true;

View File

@@ -745,39 +745,10 @@ public class JdtDataFlowModel implements DataFlowModel {
} }
private org.eclipse.jdt.core.dom.RecordDeclaration findRecordDeclaration(String fqn) { private org.eclipse.jdt.core.dom.RecordDeclaration findRecordDeclaration(String fqn) {
for (CompilationUnit cu : context.getCompilationUnits()) { org.eclipse.jdt.core.dom.AbstractTypeDeclaration abstractType = context.getAbstractTypeDeclaration(fqn);
for (Object typeObj : cu.types()) { if (abstractType instanceof org.eclipse.jdt.core.dom.RecordDeclaration recordDeclaration) {
org.eclipse.jdt.core.dom.RecordDeclaration found = findRecordInType(fqn, typeObj);
if (found != null) {
return found;
}
}
}
return null;
}
private org.eclipse.jdt.core.dom.RecordDeclaration findRecordInType(String fqn, Object typeObj) {
if (typeObj instanceof org.eclipse.jdt.core.dom.RecordDeclaration recordDeclaration) {
if (context.getFqn(recordDeclaration).equals(fqn)) {
return recordDeclaration; return recordDeclaration;
} }
for (Object decl : recordDeclaration.bodyDeclarations()) {
if (decl instanceof org.eclipse.jdt.core.dom.RecordDeclaration nested) {
org.eclipse.jdt.core.dom.RecordDeclaration found = findRecordInType(fqn, nested);
if (found != null) {
return found;
}
}
}
}
if (typeObj instanceof TypeDeclaration typeDeclaration) {
for (TypeDeclaration nested : typeDeclaration.getTypes()) {
org.eclipse.jdt.core.dom.RecordDeclaration found = findRecordInType(fqn, nested);
if (found != null) {
return found;
}
}
}
return null; return null;
} }

View File

@@ -0,0 +1,48 @@
package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class MachineScopeFilterEntryPointTest {
@Test
void shouldKeepOrderEndpointsOnOrderMachineOnly() {
EntryPoint orderPay = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/orders/pay")
.className("click.kamil.examples.statemachine.layered.web.OrderController")
.methodName("pay")
.metadata(Map.of("path", "/api/orders/pay"))
.build();
EntryPoint documentSubmit = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/documents/submit")
.className("click.kamil.examples.statemachine.layered.web.DocumentController")
.methodName("submit")
.metadata(Map.of("path", "/api/documents/submit"))
.build();
EntryPoint genericCommand = EntryPoint.builder()
.type(EntryPoint.Type.REST)
.name("POST /api/commands/{commandKey}")
.className("click.kamil.examples.statemachine.layered.web.GenericCommandController")
.methodName("execute")
.metadata(Map.of("path", "/api/commands/{commandKey}"))
.build();
CodebaseContext context = new CodebaseContext();
List<EntryPoint> orderScoped = MachineScopeFilter.filterEntryPointsForMachine(
List.of(orderPay, documentSubmit, genericCommand),
"click.kamil.examples.statemachine.layered.order.config.StandardOrderStateMachineConfiguration",
context);
assertThat(orderScoped).extracting(EntryPoint::getName)
.contains("POST /api/orders/pay", "POST /api/commands/{commandKey}")
.doesNotContain("POST /api/documents/submit");
}
}

View File

@@ -1,9 +1,13 @@
package click.kamil.springstatemachineexporter.analysis.enricher.routing; package click.kamil.springstatemachineexporter.analysis.enricher.routing;
import click.kamil.springstatemachineexporter.analysis.model.CallChain; import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; 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.Arrays; import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@@ -127,4 +131,38 @@ public class HeuristicBeanResolutionEngineTest {
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null); boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
assertTrue(result, "MyStateMachineConfig should not trigger mismatch because it contains StateMachineConfig"); assertTrue(result, "MyStateMachineConfig should not trigger mismatch because it contains StateMachineConfig");
} }
@Test
void shouldRejectAmbiguousChainWhenMultipleStateMachinesExist(@TempDir Path tempDir) throws Exception {
Files.writeString(tempDir.resolve("OrderConfig.java"), """
package com.example;
@org.springframework.statemachine.config.EnableStateMachine
public class OrderStateMachineConfig
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<OrderStates, OrderEvents> {}
enum OrderStates { S1 }
enum OrderEvents { E1 }
""");
Files.writeString(tempDir.resolve("DocumentConfig.java"), """
package com.example;
@org.springframework.statemachine.config.EnableStateMachine
public class DocumentStateMachineConfig
extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<DocumentStates, DocumentEvents> {}
enum DocumentStates { D1 }
enum DocumentEvents { D1E }
""");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
CallChain chain = CallChain.builder()
.methodChain(Arrays.asList("com.example.shared.GlobalGateway.fire()"))
.build();
assertFalse(
engine.isRoutedToCorrectMachine(chain, "com.example.OrderStateMachineConfig", context),
"Ambiguous shared gateway should not attach to every machine");
assertFalse(
engine.isRoutedToCorrectMachine(chain, "com.example.DocumentStateMachineConfig", context),
"Ambiguous shared gateway should not attach to every machine");
}
} }

View File

@@ -75,8 +75,12 @@ class PipelineRefactorTest {
} }
@Test @Test
void shouldEnableGlobalFieldScanExplicitly() { void shouldDecrementDepthLimitsViaChild() {
assertThat(ResolutionBudget.withGlobalFieldScan().allowGlobalFieldScan()).isTrue(); ResolutionBudget child = ResolutionBudget.defaults().child();
assertThat(child.isMethodReturnExhausted(19)).isFalse();
assertThat(child.isMethodReturnExhausted(20)).isTrue();
assertThat(child.isDataflowExhausted(49)).isFalse();
assertThat(child.isDataflowExhausted(50)).isTrue();
} }
} }

View File

@@ -28,4 +28,11 @@ class BooleanConstraintEvaluatorBindingsTest {
"commandKey == \"order.pay\"", "commandKey == \"order.pay\"",
Map.of("commandKey", "order.pay"))).isTrue(); Map.of("commandKey", "order.pay"))).isTrue();
} }
@Test
void shouldRejectConstraintWhenBindingIsMissing() {
assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
"command == ORDER_PAY",
Map.of("commandKey", "order.pay"))).isFalse();
}
} }

View File

@@ -0,0 +1,146 @@
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 EntryPointBindingExpanderTest {
@Test
void shouldExpandPathVariableFromStringSwitchMapper(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class GenericCommandController {
CommandGateway gateway;
public void execute(String commandKey) {
gateway.executeViaMapper(commandKey);
}
}
class CommandGateway {
StringCommandMapper mapper;
void executeViaMapper(String commandKey) {
mapper.fromString(commandKey);
}
}
class StringCommandMapper {
DomainCommand fromString(String commandKey) {
return switch (commandKey) {
case "order.pay" -> DomainCommand.ORDER_PAY;
case "order.ship" -> DomainCommand.ORDER_SHIP;
default -> throw new IllegalArgumentException(commandKey);
};
}
}
enum DomainCommand { ORDER_PAY, ORDER_SHIP }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> graph = engine.buildCallGraph();
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.GenericCommandController")
.methodName("execute")
.name("POST /api/commands/{commandKey}")
.parameters(List.of(EntryPoint.Parameter.builder()
.name("commandKey")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
List<Map<String, String>> variants =
EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);
assertThat(variants).extracting(map -> map.get("commandKey"))
.containsExactlyInAnyOrder("order.pay", "order.ship");
}
@Test
void shouldProduceSinglePayChainForGenericCommandEndpoint(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class GenericCommandController {
private final CommandGateway gateway = new CommandGateway();
public void execute(String commandKey) {
gateway.executeViaMapper(commandKey);
}
}
enum DomainCommand { ORDER_PAY, ORDER_SHIP }
enum OrderEvent { PAY, SHIP }
class CommandGateway {
private final StringCommandMapper mapper = new StringCommandMapper();
private final CentralDispatcher central = new CentralDispatcher();
void executeViaMapper(String commandKey) {
DomainCommand command = mapper.fromString(commandKey);
central.dispatch(command);
}
}
class StringCommandMapper {
DomainCommand fromString(String commandKey) {
return switch (commandKey) {
case "order.pay" -> DomainCommand.ORDER_PAY;
case "order.ship" -> DomainCommand.ORDER_SHIP;
default -> throw new IllegalArgumentException(commandKey);
};
}
}
class CentralDispatcher {
void dispatch(DomainCommand command) {
switch (command) {
case ORDER_PAY -> orderPay();
case ORDER_SHIP -> orderShip();
}
}
void orderPay() { fire(OrderEvent.PAY); }
void orderShip() { fire(OrderEvent.SHIP); }
void fire(OrderEvent event) {}
}
""";
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.GenericCommandController")
.methodName("execute")
.name("POST /api/commands/{commandKey}")
.parameters(List.of(EntryPoint.Parameter.builder()
.name("commandKey")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.CentralDispatcher")
.methodName("fire")
.event("event")
.build();
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(2);
assertThat(chains.stream().map(chain -> chain.getEntryPoint().getName()))
.containsExactlyInAnyOrder("POST /api/commands/order.pay", "POST /api/commands/order.ship");
assertThat(chains.stream().filter(chain -> chain.getEntryPoint().getName().endsWith("order.pay"))).hasSize(1);
assertThat(chains.stream().filter(chain -> chain.getEntryPoint().getName().endsWith("order.pay")).findFirst().orElseThrow()
.getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
}
}

View File

@@ -0,0 +1,43 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.CompilationUnit;
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 static org.assertj.core.api.Assertions.assertThat;
class GenericEventDetectorRecordTest {
@Test
void shouldDetectSendEventInsideTopLevelRecord(@TempDir Path tempDir) throws Exception {
Files.writeString(tempDir.resolve("EventBridge.java"), """
package com.example;
import org.springframework.statemachine.StateMachine;
public record EventBridge(StateMachine<String, String> sm, String event) {
public void fire() {
sm.sendEvent(event);
}
}
""");
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), List.of());
CompilationUnit cu = context.getCompilationUnits().iterator().next();
List<TriggerPoint> triggers = detector.detect(cu);
assertThat(triggers).hasSize(1);
assertThat(triggers.get(0).getClassName()).isEqualTo("com.example.EventBridge");
assertThat(triggers.get(0).getMethodName()).isEqualTo("fire");
assertThat(triggers.get(0).getEvent()).isEqualTo("event");
}
}

View File

@@ -79,6 +79,31 @@ class LayeredDispatcherSampleTest {
assertSingleMatchedChain(orderMachine, "POST /api/string-dispatch/orders/ship", "OrderTransitionEvent.SHIP"); assertSingleMatchedChain(orderMachine, "POST /api/string-dispatch/orders/ship", "OrderTransitionEvent.SHIP");
} }
@Test
void shouldExpandGenericCommandEndpointIntoConcreteKeys(@TempDir Path tempDir) throws Exception {
Path sampleRoot = findProjectRoot().resolve("state_machines/layered_dispatcher_sample");
ExportService exportService = new ExportService(List.of(new JsonExporter()));
exportService.runExporter(sampleRoot, tempDir, List.of("json"), true, List.of(), null, null,
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
JsonNode orderMachine = readMachineJson(tempDir, "StandardOrderStateMachineConfiguration", new ObjectMapper());
assertEndpointLinksSingleTransition(orderMachine, "POST /api/commands/order.pay",
"OrderTransitionEvent.PAY", "OrderState.NEW", "OrderState.PAID");
assertEndpointLinksSingleTransition(orderMachine, "POST /api/commands/order.ship",
"OrderTransitionEvent.SHIP", "OrderState.PAID", "OrderState.SHIPPED");
assertSingleMatchedChain(orderMachine, "POST /api/commands/order.pay", "OrderTransitionEvent.PAY");
JsonNode documentMachine = readMachineJson(tempDir, "StandardDocumentStateMachineConfiguration", new ObjectMapper());
assertEndpointLinksSingleTransition(documentMachine, "POST /api/commands/document.submit",
"DocumentTransitionEvent.SUBMIT", "DocumentState.DRAFT", "DocumentState.SUBMITTED");
JsonNode entryPoints = documentMachine.path("metadata").path("entryPoints");
assertThat(entryPoints.isArray()).isTrue();
assertThat(entryPoints.findValuesAsText("name"))
.noneMatch(name -> name.contains("/api/orders/"));
}
private static void assertSingleMatchedChain(JsonNode machineJson, String endpointName, String expectedEvent) { private static void assertSingleMatchedChain(JsonNode machineJson, String endpointName, String expectedEvent) {
List<JsonNode> matchedChains = findMatchedCallChains(machineJson, endpointName); List<JsonNode> matchedChains = findMatchedCallChains(machineJson, endpointName);
assertThat(matchedChains).as("matched chains for %s", endpointName).hasSize(1); assertThat(matchedChains).as("matched chains for %s", endpointName).hasSize(1);

View File

@@ -80,58 +80,6 @@
"ambiguous" : false "ambiguous" : false
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/enterprise/orders/place",
"className" : "click.kamil.examples.enterprise.api.OrderApi",
"methodName" : "placeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderApi.java",
"metadata" : {
"path" : "/api/enterprise/orders/place",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/enterprise/orders/{id}/cancel",
"className" : "click.kamil.examples.enterprise.api.OrderApi",
"methodName" : "cancelOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderApi.java",
"metadata" : {
"path" : "/api/enterprise/orders/{id}/cancel",
"verb" : "POST"
},
"parameters" : [ {
"name" : "id",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/enterprise/orders/place",
"className" : "click.kamil.examples.enterprise.api.OrderController",
"methodName" : "placeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderController.java",
"metadata" : {
"path" : "/api/enterprise/orders/place",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/enterprise/orders/{id}/cancel",
"className" : "click.kamil.examples.enterprise.api.OrderController",
"methodName" : "cancelOrder",
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderController.java",
"metadata" : {
"path" : "/api/enterprise/orders/{id}/cancel",
"verb" : "POST"
},
"parameters" : [ {
"name" : "id",
"type" : "String",
"annotations" : [ "PathVariable" ]
} ]
}, {
"type" : "CUSTOM", "type" : "CUSTOM",
"name" : "INTERCEPTOR: SecurityInterceptor.preHandle", "name" : "INTERCEPTOR: SecurityInterceptor.preHandle",
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor", "className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",

View File

@@ -1,361 +1,7 @@
{ {
"metadata" : { "metadata" : {
"triggers" : [ { "triggers" : [ ],
"event" : "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" : "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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : "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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 22,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"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" : "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" : [ { "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/resume",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "resumeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"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-order",
"className" : "click.kamil.examples.statemachine.extended.web.OrderedTestController",
"methodName" : "testOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderedTestController.java",
"metadata" : {
"path" : "/test-order",
"verb" : "GET"
},
"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 /test-list",
"className" : "click.kamil.examples.statemachine.extended.web.ListInjectionController",
"methodName" : "testList",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ListInjectionController.java",
"metadata" : {
"path" : "/test-list",
"verb" : "GET"
},
"parameters" : [ ]
}, {
"type" : "REST", "type" : "REST",
"name" : "GET /api/base/{id}", "name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController", "className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
@@ -386,526 +32,7 @@
"annotations" : [ "PathVariable" ] "annotations" : [ "PathVariable" ]
} ] } ]
} ], } ],
"callChains" : [ { "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" : "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" : [ "SUBMIT_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "SUBMIT_EVENT"
} ]
}, {
"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" : "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" : [ "CANCEL_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PROCESSING",
"targetState" : "CANCELLED",
"event" : "CANCEL_EVENT"
} ]
}, {
"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" : "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" : [ {
"sourceState" : "PROCESSING",
"targetState" : "COMPLETED",
"event" : "FINISH"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/resume",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "resumeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.resumeOrder", "click.kamil.examples.statemachine.extended.service.OrderService.resumeOrder" ],
"triggerPoint" : {
"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
},
"contextMachineId" : "orderId",
"matchedTransitions" : null
}, {
"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" : "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" : [ "REACTIVE_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "REACTIVE_EVENT"
} ]
}, {
"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" : "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" : [ {
"sourceState" : "START",
"targetState" : "START",
"event" : "AUDIT_EVENT"
} ]
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 22,
"polymorphicEvents" : [ "AUTHORIZE" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 35,
"polymorphicEvents" : [ "CAPTURE" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : "paymentId",
"matchedTransitions" : null
}, {
"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
} ],
"properties" : { "properties" : {
"default" : { "default" : {
"app.states.initial" : "INIT_STATE" "app.states.initial" : "INIT_STATE"

View File

@@ -1,361 +1,7 @@
{ {
"metadata" : { "metadata" : {
"triggers" : [ { "triggers" : [ ],
"event" : "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" : "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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : "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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"sourceState" : null,
"lineNumber" : 22,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "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" : null,
"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" : "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" : [ { "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/resume",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "resumeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"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-order",
"className" : "click.kamil.examples.statemachine.extended.web.OrderedTestController",
"methodName" : "testOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderedTestController.java",
"metadata" : {
"path" : "/test-order",
"verb" : "GET"
},
"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 /test-list",
"className" : "click.kamil.examples.statemachine.extended.web.ListInjectionController",
"methodName" : "testList",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ListInjectionController.java",
"metadata" : {
"path" : "/test-list",
"verb" : "GET"
},
"parameters" : [ ]
}, {
"type" : "REST", "type" : "REST",
"name" : "GET /api/base/{id}", "name" : "GET /api/base/{id}",
"className" : "click.kamil.examples.statemachine.extended.web.PaymentController", "className" : "click.kamil.examples.statemachine.extended.web.PaymentController",
@@ -386,526 +32,7 @@
"annotations" : [ "PathVariable" ] "annotations" : [ "PathVariable" ]
} ] } ]
} ], } ],
"callChains" : [ { "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" : "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" : [ "SUBMIT_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "SUBMIT_EVENT"
} ]
}, {
"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" : "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" : [ "CANCEL_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PROCESSING",
"targetState" : "CANCELLED",
"event" : "CANCEL_EVENT"
} ]
}, {
"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" : "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" : [ {
"sourceState" : "PROCESSING",
"targetState" : "COMPLETED",
"event" : "FINISH"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/resume",
"className" : "click.kamil.examples.statemachine.extended.web.OrderController",
"methodName" : "resumeOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/resume",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.resumeOrder", "click.kamil.examples.statemachine.extended.service.OrderService.resumeOrder" ],
"triggerPoint" : {
"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
},
"contextMachineId" : "orderId",
"matchedTransitions" : null
}, {
"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" : "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" : [ "REACTIVE_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "REACTIVE_EVENT"
} ]
}, {
"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" : "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" : [ {
"sourceState" : "START",
"targetState" : "START",
"event" : "AUDIT_EVENT"
} ]
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 22,
"polymorphicEvents" : [ "AUTHORIZE" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"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" : "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" : null,
"sourceState" : null,
"lineNumber" : 35,
"polymorphicEvents" : [ "CAPTURE" ],
"external" : false,
"constraint" : null,
"ambiguous" : false
},
"contextMachineId" : "paymentId",
"matchedTransitions" : null
}, {
"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
} ],
"properties" : { "properties" : {
"default" : { "default" : {
"app.states.initial" : "INIT_STATE" "app.states.initial" : "INIT_STATE"

View File

@@ -14,29 +14,7 @@
"constraint" : null, "constraint" : null,
"ambiguous" : false "ambiguous" : false
} ], } ],
"entryPoints" : [ { "entryPoints" : [ ],
"type" : "REST",
"name" : "POST /api/v2/orders/submit",
"className" : "click.kamil.examples.statemachine.inheritance.api.OrderApi",
"methodName" : "submitOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/api/OrderApi.java",
"metadata" : {
"path" : "/api/v2/orders/submit",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/v2/orders/submit",
"className" : "click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl",
"methodName" : "submitOrder",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/api/OrderControllerImpl.java",
"metadata" : {
"path" : "/api/v2/orders/submit",
"verb" : "POST"
},
"parameters" : [ ]
} ],
"callChains" : [ { "callChains" : [ {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",

View File

@@ -1,90 +1,7 @@
{ {
"metadata" : { "metadata" : {
"triggers" : [ { "triggers" : [ ],
"event" : "SUBMIT", "entryPoints" : [ ],
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 40,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
}, {
"event" : "ORDER_EVENT",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
"methodName" : "onMessage",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 52,
"polymorphicEvents" : null,
"external" : false,
"constraint" : null,
"ambiguous" : false
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /maven/orders/submit",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
"methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"metadata" : {
"path" : "/maven/orders/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
}, {
"type" : "REST",
"name" : "POST /reactive/order",
"className" : "click.kamil.maven.api.ReactiveOrderApi",
"methodName" : "orderRoutes",
"sourceFile" : "api-module/src/main/java/click/kamil/maven/api/ReactiveOrderApi.java",
"metadata" : {
"path" : "/reactive/order",
"verb" : "POST",
"style" : "WebFlux Functional"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /maven/orders/submit",
"className" : "click.kamil.maven.api.MavenOrderApi",
"methodName" : "submitOrder",
"sourceFile" : "api-module/src/main/java/click/kamil/maven/api/MavenOrderApi.java",
"metadata" : {
"path" : "/maven/orders/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "orderId",
"type" : "String",
"annotations" : [ "RequestBody" ]
} ]
}, {
"type" : "JMS",
"name" : "JMS: order.queue",
"className" : "click.kamil.maven.api.JmsOrderListener",
"methodName" : "onMessage",
"sourceFile" : "api-module/src/main/java/click/kamil/maven/api/JmsOrderListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "order.queue"
},
"parameters" : [ {
"name" : "message",
"type" : "String",
"annotations" : [ ]
} ]
} ],
"callChains" : [ { "callChains" : [ {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",

View File

@@ -15,116 +15,6 @@
"ambiguous" : false "ambiguous" : false
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/pay",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "pay",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/ship",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "ship",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/cancel",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "cancel",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/cancel",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/submit",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "submit",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/submit",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/approve",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "approve",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/approve",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/reject",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "reject",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/reject",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/string-dispatch/orders/pay",
"className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController",
"methodName" : "payWithStringKey",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java",
"metadata" : {
"path" : "/api/string-dispatch/orders/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/string-dispatch/orders/ship",
"className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController",
"methodName" : "shipWithStringKey",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java",
"metadata" : {
"path" : "/api/string-dispatch/orders/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/rich/pay",
"className" : "click.kamil.examples.statemachine.layered.web.RichOrderController",
"methodName" : "payViaRichEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java",
"metadata" : {
"path" : "/api/orders/rich/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/rich/ship",
"className" : "click.kamil.examples.statemachine.layered.web.RichOrderController",
"methodName" : "shipViaRichEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java",
"metadata" : {
"path" : "/api/orders/rich/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/{commandKey}",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
@@ -245,12 +135,12 @@
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/document.submit",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute", "methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : { "metadata" : {
"path" : "/api/commands/{commandKey}", "path" : "/api/commands/document.submit",
"verb" : "POST" "verb" : "POST"
}, },
"parameters" : [ { "parameters" : [ {
@@ -283,12 +173,12 @@
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/document.approve",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute", "methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : { "metadata" : {
"path" : "/api/commands/{commandKey}", "path" : "/api/commands/document.approve",
"verb" : "POST" "verb" : "POST"
}, },
"parameters" : [ { "parameters" : [ {
@@ -321,12 +211,12 @@
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/document.reject",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute", "methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : { "metadata" : {
"path" : "/api/commands/{commandKey}", "path" : "/api/commands/document.reject",
"verb" : "POST" "verb" : "POST"
}, },
"parameters" : [ { "parameters" : [ {

View File

@@ -15,116 +15,6 @@
"ambiguous" : false "ambiguous" : false
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/pay",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "pay",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/ship",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "ship",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/cancel",
"className" : "click.kamil.examples.statemachine.layered.web.OrderController",
"methodName" : "cancel",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/cancel",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/submit",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "submit",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/submit",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/approve",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "approve",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/approve",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/documents/reject",
"className" : "click.kamil.examples.statemachine.layered.web.DocumentController",
"methodName" : "reject",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java",
"metadata" : {
"path" : "/api/documents/reject",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/string-dispatch/orders/pay",
"className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController",
"methodName" : "payWithStringKey",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java",
"metadata" : {
"path" : "/api/string-dispatch/orders/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/string-dispatch/orders/ship",
"className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController",
"methodName" : "shipWithStringKey",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java",
"metadata" : {
"path" : "/api/string-dispatch/orders/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/rich/pay",
"className" : "click.kamil.examples.statemachine.layered.web.RichOrderController",
"methodName" : "payViaRichEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java",
"metadata" : {
"path" : "/api/orders/rich/pay",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/rich/ship",
"className" : "click.kamil.examples.statemachine.layered.web.RichOrderController",
"methodName" : "shipViaRichEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java",
"metadata" : {
"path" : "/api/orders/rich/ship",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/{commandKey}",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
@@ -381,12 +271,12 @@
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/order.pay",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute", "methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : { "metadata" : {
"path" : "/api/commands/{commandKey}", "path" : "/api/commands/order.pay",
"verb" : "POST" "verb" : "POST"
}, },
"parameters" : [ { "parameters" : [ {
@@ -419,12 +309,12 @@
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/order.ship",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute", "methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : { "metadata" : {
"path" : "/api/commands/{commandKey}", "path" : "/api/commands/order.ship",
"verb" : "POST" "verb" : "POST"
}, },
"parameters" : [ { "parameters" : [ {
@@ -457,12 +347,12 @@
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
"name" : "POST /api/commands/{commandKey}", "name" : "POST /api/commands/order.cancel",
"className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController",
"methodName" : "execute", "methodName" : "execute",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java",
"metadata" : { "metadata" : {
"path" : "/api/commands/{commandKey}", "path" : "/api/commands/order.cancel",
"verb" : "POST" "verb" : "POST"
}, },
"parameters" : [ { "parameters" : [ {

View File

@@ -41,50 +41,6 @@
"ambiguous" : false "ambiguous" : false
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/process",
"className" : "click.kamil.web.OrderController",
"methodName" : "processOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/process",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "completeOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/complete",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/cancel",
"className" : "click.kamil.web.OrderController",
"methodName" : "cancelOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/cancel",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/functional-complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "functionalCompleteOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/functional-complete",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "RABBIT", "type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue", "name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener", "className" : "click.kamil.web.RabbitOrderListener",