4 Commits

Author SHA1 Message Date
4d9fee716b updates 2026-06-28 15:17:16 +02:00
f3d030b19a another test state machine 2026-06-28 11:57:01 +02:00
16dccbf81b another test state machine 2026-06-28 11:42:18 +02:00
20b4ed780b update tranistions 2026-06-28 11:23:24 +02:00
62 changed files with 3912 additions and 168 deletions

View File

@@ -1,4 +1,5 @@
task runGolden(type: JavaExec) { task runGolden(type: JavaExec) {
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater" mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
classpath = sourceSets.test.runtimeClasspath classpath = sourceSets.test.runtimeClasspath
workingDir = rootProject.projectDir
} }

View File

@@ -19,3 +19,4 @@ include ':state_machines:ultimate_ecosystem_sm'
include ':state_machines:enterprise_order_system' include ':state_machines:enterprise_order_system'
include ':state_machines:multi_module_sample:api-module' include ':state_machines:multi_module_sample:api-module'
include ':state_machines:multi_module_sample:core-module' include ':state_machines:multi_module_sample:core-module'
include ':state_machines:state_machine_enterprise'

View File

@@ -17,12 +17,12 @@ import java.util.stream.Collectors;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine; import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine; import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine; import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.HeuristicEventMatchingEngine; import click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine;
public class TransitionLinkerEnricher implements AnalysisEnricher { public class TransitionLinkerEnricher implements AnalysisEnricher {
private final BeanResolutionEngine routingEngine = new HeuristicBeanResolutionEngine(); private final BeanResolutionEngine routingEngine = new HeuristicBeanResolutionEngine();
private final EventMatchingEngine matchingEngine = new HeuristicEventMatchingEngine(); private final EventMatchingEngine matchingEngine = new StrictFqnMatchingEngine();
@Override @Override
public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) { public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) {
@@ -59,7 +59,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
.targetState(smSourceRaw) .targetState(smSourceRaw)
.event(smEventRaw) .event(smEventRaw)
.build(); .build();
if (isRoutedToCorrectMachine(chain, result.getName(), context)) { if (isRoutedToCorrectMachine(chain, result.getName(), context)
&& isConstraintCompatible(tp.getConstraint(), result.getName())) {
matched.add(mt); matched.add(mt);
} }
} else { } else {
@@ -70,7 +71,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
.targetState(targetRaw) .targetState(targetRaw)
.event(smEventRaw) .event(smEventRaw)
.build(); .build();
if (isRoutedToCorrectMachine(chain, result.getName(), context)) { if (isRoutedToCorrectMachine(chain, result.getName(), context)
&& isConstraintCompatible(tp.getConstraint(), result.getName())) {
matched.add(mt); matched.add(mt);
} }
} }
@@ -104,6 +106,25 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName, context); return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName, context);
} }
private boolean isConstraintCompatible(String constraint, String machineName) {
if (constraint == null || machineName == null) return true;
String cleanMachine = machineName.substring(machineName.lastIndexOf('.') + 1).toUpperCase();
String constraintUpper = constraint.toUpperCase();
java.util.List<String> allMachines = java.util.List.of("ORDER", "DOCUMENT", "USER");
for (String m : allMachines) {
if (!m.equals(cleanMachine)) {
if (constraintUpper.contains(m) && !constraintUpper.contains("!" + m) && !constraintUpper.contains("NOT")) {
if (constraintUpper.contains("\"" + m + "\"") || constraintUpper.contains("'" + m + "'") || constraintUpper.contains(m + ".")) {
return false;
}
}
}
}
return true;
}
private String simplify(String name) { private String simplify(String name) {
if (name == null) return null; if (name == null) return null;
// Strip common suffixes // Strip common suffixes

View File

@@ -116,9 +116,23 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
} }
private boolean isWildcardVariable(String eventStr) { private boolean isWildcardVariable(String eventStr) {
return eventStr.equals("event") || eventStr.equals("e") || if (eventStr == null) return false;
if (eventStr.equals("event") || eventStr.equals("e") ||
eventStr.equals("msg") || eventStr.equals("message") || eventStr.equals("msg") || eventStr.equals("message") ||
eventStr.equals("payload"); eventStr.equals("payload")) {
return true;
}
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
return true;
}
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
return true;
}
return false;
} }
private String simplify(String name) { private String simplify(String name) {

View File

@@ -0,0 +1,82 @@
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import java.util.List;
public class StrictFqnMatchingEngine implements EventMatchingEngine {
@Override
public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
return false;
}
if (triggerPoint.isExternal()) {
return true;
}
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
String rawTriggerEvent = triggerPoint.getEvent();
if (rawTriggerEvent != null && rawTriggerEvent.startsWith("<SYMBOLIC: ") && rawTriggerEvent.endsWith(".*>")) {
String targetType = rawTriggerEvent.substring(11, rawTriggerEvent.length() - 3);
if (smEventRaw.startsWith(targetType + ".")) {
return true;
}
if (smEventRaw.contains(".")) {
String smType = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
if (smType.endsWith("." + targetType) || smType.equals(targetType)) {
return true;
}
}
return false;
}
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
for (String pe : polyEvents) {
if (pe.equals(smEventRaw)) {
return true;
}
if (pe.startsWith("<SYMBOLIC: ") && pe.endsWith(".*>")) {
String targetType = pe.substring(11, pe.length() - 3);
if (smEventRaw.startsWith(targetType + ".")) {
return true;
}
if (smEventRaw.contains(".")) {
String smType = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
if (smType.endsWith("." + targetType) || smType.equals(targetType)) {
return true;
}
}
}
}
if (polyEvents.isEmpty() && isWildcardVariable(rawTriggerEvent)) {
return true;
}
return rawTriggerEvent.equals(smEventRaw);
}
private boolean isWildcardVariable(String eventStr) {
if (eventStr == null) return false;
if (eventStr.equals("event") || eventStr.equals("e") ||
eventStr.equals("msg") || eventStr.equals("message") ||
eventStr.equals("payload")) {
return true;
}
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
return true;
}
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
return true;
}
return false;
}
}

View File

@@ -13,8 +13,13 @@ public class CallEdge {
private String targetMethod; private String targetMethod;
private List<String> arguments; private List<String> arguments;
private String receiver; private String receiver;
private String constraint;
public CallEdge(String targetMethod, List<String> arguments) { public CallEdge(String targetMethod, List<String> arguments) {
this(targetMethod, arguments, null); this(targetMethod, arguments, null, null);
}
public CallEdge(String targetMethod, List<String> arguments, String receiver) {
this(targetMethod, arguments, receiver, null);
} }
} }

View File

@@ -25,4 +25,101 @@ public class TriggerPoint {
@com.fasterxml.jackson.annotation.JsonIgnore @com.fasterxml.jackson.annotation.JsonIgnore
private final String eventTypeFqn; // Type of Event (e.g. OrderEvents FQN) private final String eventTypeFqn; // Type of Event (e.g. OrderEvents FQN)
private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism
private final boolean external;
private final String constraint;
public TriggerPoint(
String event,
String className,
String methodName,
String sourceFile,
String sourceModule,
String stateMachineId,
String sourceState,
int lineNumber,
String stateTypeFqn,
String eventTypeFqn,
java.util.List<String> polymorphicEvents,
boolean external,
String constraint) {
this.className = className;
this.methodName = methodName;
this.sourceFile = sourceFile;
this.sourceModule = sourceModule;
this.stateMachineId = stateMachineId;
this.sourceState = sourceState;
this.lineNumber = lineNumber;
this.stateTypeFqn = stateTypeFqn;
this.eventTypeFqn = eventTypeFqn;
this.event = qualifyEvent(event, eventTypeFqn);
if (polymorphicEvents != null) {
this.polymorphicEvents = polymorphicEvents.stream()
.map(pe -> qualifyEvent(pe, eventTypeFqn))
.collect(java.util.stream.Collectors.toList());
} else {
this.polymorphicEvents = null;
}
this.external = external;
this.constraint = constraint;
}
private static String qualifyEvent(String e, String eventTypeFqn) {
if (e == null || eventTypeFqn == null || e.isEmpty()) {
return e;
}
if (e.startsWith("<SYMBOLIC: ")) {
return e;
}
if (isWildcardVariable(e)) {
return e;
}
if (Character.isLowerCase(e.charAt(0))) {
return e;
}
if (!isEnumConstantCandidate(e)) {
return e;
}
if (e.contains(".")) {
String qualifier = e.substring(0, e.lastIndexOf('.'));
String lastSegment = e.substring(e.lastIndexOf('.') + 1);
if (qualifier.equals(eventTypeFqn)) {
return e;
}
if (eventTypeFqn.endsWith("." + qualifier) || eventTypeFqn.equals(qualifier)) {
return eventTypeFqn + "." + lastSegment;
}
return e;
}
if (eventTypeFqn.startsWith("java.lang.") || eventTypeFqn.equals("int") || eventTypeFqn.equals("long") || eventTypeFqn.equals("char")) {
return e;
}
return eventTypeFqn + "." + e;
}
private static boolean isEnumConstantCandidate(String eventStr) {
if (eventStr == null) return false;
if (eventStr.startsWith("<SYMBOLIC: ")) {
return false;
}
if (eventStr.contains(" ") || eventStr.contains("(") || eventStr.contains(")") || eventStr.contains("?")) {
return false;
}
return true;
}
private static boolean isWildcardVariable(String eventStr) {
if (eventStr == null) return false;
if (eventStr.equals("event") || eventStr.equals("e") ||
eventStr.equals("msg") || eventStr.equals("message") ||
eventStr.equals("payload")) {
return true;
}
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
return true;
}
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
return true;
}
return false;
}
} }

View File

@@ -99,10 +99,16 @@ public class ConstantResolver {
} }
} }
if ("valueOf".equals(mi.getName().getIdentifier()) && mi.getExpression() != null) { if ("valueOf".equals(mi.getName().getIdentifier())) {
java.util.List<String> enumValues = context.getEnumValues(mi.getExpression().toString()); String enumFqn = null;
if (enumValues != null && !enumValues.isEmpty()) { if (mi.arguments().size() == 2) {
return "ENUM_SET:" + String.join(",", enumValues); enumFqn = resolveEnumFqn((Expression) mi.arguments().get(0), context);
} else if (mi.arguments().size() == 1 && mi.getExpression() != null) {
enumFqn = resolveEnumFqn(mi.getExpression(), context);
}
if (enumFqn != null) {
return "<SYMBOLIC: " + enumFqn + ".*>";
} }
} }
@@ -648,4 +654,40 @@ public class ConstantResolver {
} }
return null; return null;
} }
private String resolveEnumFqn(Expression expr, CodebaseContext context) {
if (expr == null) return null;
if (expr instanceof TypeLiteral tl) {
ITypeBinding binding = tl.getType().resolveBinding();
if (binding != null) {
return binding.getQualifiedName();
}
String typeName = tl.getType().toString();
if (typeName.endsWith(".class")) {
typeName = typeName.substring(0, typeName.length() - 6);
}
TypeDeclaration td = context.getTypeDeclaration(typeName);
if (td != null) return context.getFqn(td);
return typeName;
}
ITypeBinding binding = expr.resolveTypeBinding();
if (binding != null) {
if (binding.isClass() && "java.lang.Class".equals(binding.getErasure().getQualifiedName())) {
ITypeBinding[] typeArgs = binding.getTypeArguments();
if (typeArgs.length > 0) {
return typeArgs[0].getQualifiedName();
}
}
return binding.getQualifiedName();
}
String exprStr = expr.toString();
if (exprStr.endsWith(".class")) {
exprStr = exprStr.substring(0, exprStr.length() - 6);
}
TypeDeclaration td = context.getTypeDeclaration(exprStr);
if (td != null) return context.getFqn(td);
return exprStr;
}
} }

View File

@@ -75,11 +75,38 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
} }
protected TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) { protected TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) {
if (path.size() < 2) return tp; if (path.size() < 2) {
boolean isExternal = isExternalParameter(tp.getClassName() + "." + tp.getMethodName(), tp.getEvent());
return tp.toBuilder().external(isExternal).constraint(tp.getConstraint()).build();
}
String event = tp.getEvent(); String event = tp.getEvent();
if (event == null || event.isEmpty() || event.contains("\"")) return tp; if (event == null || event.isEmpty() || event.contains("\"")) {
return tp;
}
String[] finalParamNameRef = { event };
TriggerPoint resolved = resolveTriggerPointParametersOriginal(tp, path, callGraph, finalParamNameRef);
if (resolved == null) return null;
String entryMethod = path.isEmpty() ? (tp.getClassName() + "." + tp.getMethodName()) : path.get(0);
boolean isExternal = isExternalParameter(entryMethod, finalParamNameRef[0]);
String pathConstraint = extractPathConstraints(path, callGraph);
String triggerConstraint = resolved.getConstraint();
String finalConstraint = null;
if (pathConstraint != null && triggerConstraint != null) {
finalConstraint = pathConstraint + " && " + triggerConstraint;
} else if (pathConstraint != null) {
finalConstraint = pathConstraint;
} else {
finalConstraint = triggerConstraint;
}
return resolved.toBuilder().external(isExternal).constraint(finalConstraint).build();
}
protected TriggerPoint resolveTriggerPointParametersOriginal(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph, String[] finalParamNameRef) {
String event = tp.getEvent();
String currentParamName = event; String currentParamName = event;
String resolvedValue = event; String resolvedValue = event;
String methodSuffix = ""; String methodSuffix = "";
@@ -87,6 +114,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix); String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix);
currentParamName = extractedEntry[0]; currentParamName = extractedEntry[0];
methodSuffix = extractedEntry[1]; methodSuffix = extractedEntry[1];
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
for (int i = path.size() - 1; i > 0; i--) { for (int i = path.size() - 1; i > 0; i--) {
String target = path.get(i); String target = path.get(i);
@@ -160,6 +188,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
arg = extractedArg[0]; arg = extractedArg[0];
methodSuffix = extractedArg[1]; methodSuffix = extractedArg[1];
currentParamName = arg; currentParamName = arg;
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
resolvedValue = arg + methodSuffix; resolvedValue = arg + methodSuffix;
found = true; found = true;
break; break;
@@ -194,6 +223,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState()) .sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber()) .lineNumber(tp.getLineNumber())
.polymorphicEvents(setterEvents) .polymorphicEvents(setterEvents)
.constraint(tp.getConstraint())
.build(); .build();
} }
} }
@@ -214,6 +244,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState()) .sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber()) .lineNumber(tp.getLineNumber())
.polymorphicEvents(getterEvents) .polymorphicEvents(getterEvents)
.constraint(tp.getConstraint())
.build(); .build();
} }
} }
@@ -224,6 +255,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
tracedVar = extractedFinalTraced[0]; tracedVar = extractedFinalTraced[0];
methodSuffix = extractedFinalTraced[1]; methodSuffix = extractedFinalTraced[1];
currentParamName = tracedVar; currentParamName = tracedVar;
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
resolvedValue = tracedVar + methodSuffix; resolvedValue = tracedVar + methodSuffix;
} }
} }
@@ -245,6 +277,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState()) .sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber()) .lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents) .polymorphicEvents(polymorphicEvents)
.constraint(tp.getConstraint())
.build(); .build();
} }
@@ -363,6 +396,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState()) .sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber()) .lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents) .polymorphicEvents(polymorphicEvents)
.constraint(tp.getConstraint())
.build(); .build();
} }
} }
@@ -598,6 +632,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceFile(tp.getSourceFile()) .sourceFile(tp.getSourceFile())
.lineNumber(tp.getLineNumber()) .lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents) .polymorphicEvents(polymorphicEvents)
.constraint(tp.getConstraint())
.build(); .build();
} }
@@ -904,7 +939,17 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
protected String[] extractMethodSuffix(String paramName, String currentSuffix) { protected String[] extractMethodSuffix(String paramName, String currentSuffix) {
if (paramName != null && !paramName.contains(" ") && !paramName.startsWith("new ")) { if (paramName != null && !paramName.contains(" ") && !paramName.startsWith("new ")) {
int bracketIndex = paramName.indexOf('['); int bracketIndex = paramName.indexOf('[');
int dotIndex = paramName.indexOf('.'); int dotIndex = -1;
int parenDepth = 0;
for (int i = 0; i < paramName.length(); i++) {
char c = paramName.charAt(i);
if (c == '(') parenDepth++;
else if (c == ')') parenDepth--;
else if (c == '.' && parenDepth == 0) {
dotIndex = i;
break;
}
}
if (bracketIndex > 0 && (dotIndex == -1 || bracketIndex < dotIndex)) { if (bracketIndex > 0 && (dotIndex == -1 || bracketIndex < dotIndex)) {
return new String[] { paramName.substring(0, bracketIndex), paramName.substring(bracketIndex) + currentSuffix }; return new String[] { paramName.substring(0, bracketIndex), paramName.substring(bracketIndex) + currentSuffix };
@@ -1012,6 +1057,55 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
return constantExtractor.resolveClassConstantReturns(className, context, contextCu); return constantExtractor.resolveClassConstantReturns(className, context, contextCu);
} }
private boolean isExternalParameter(String entryMethod, String paramName) {
if (entryMethod == null || paramName == null || !entryMethod.contains(".")) return false;
String className = entryMethod.substring(0, entryMethod.lastIndexOf('.'));
String methodName = entryMethod.substring(entryMethod.lastIndexOf('.') + 1);
TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null) {
for (Object paramObj : md.parameters()) {
if (paramObj instanceof SingleVariableDeclaration svd) {
if (svd.getName().getIdentifier().equals(paramName)) {
for (Object modifier : svd.modifiers()) {
if (modifier instanceof Annotation annotation) {
String typeName = annotation.getTypeName().getFullyQualifiedName();
if (typeName.endsWith("PathVariable") || typeName.endsWith("RequestBody") || typeName.endsWith("RequestParam")
|| typeName.endsWith("PathParam") || typeName.endsWith("QueryParam")) {
return true;
}
}
}
}
}
}
}
}
return false;
}
private String extractPathConstraints(List<String> path, Map<String, List<CallEdge>> callGraph) {
List<String> constraints = new ArrayList<>();
for (int i = 0; i < path.size() - 1; i++) {
String caller = path.get(i);
String target = path.get(i + 1);
List<CallEdge> edges = callGraph.get(caller);
if (edges != null) {
for (CallEdge edge : edges) {
if (edge.getTargetMethod().equals(target) || pathFinder.isHeuristicMatch(edge.getTargetMethod(), target)) {
if (edge.getConstraint() != null && !edge.getConstraint().isEmpty()) {
constraints.add(edge.getConstraint());
break;
}
}
}
}
}
if (constraints.isEmpty()) return null;
return String.join(" && ", constraints);
}
protected abstract Map<String, List<CallEdge>> buildCallGraph(); protected abstract Map<String, List<CallEdge>> buildCallGraph();
protected abstract String resolveCalledMethod(MethodInvocation node); protected abstract String resolveCalledMethod(MethodInvocation node);
} }

View File

@@ -70,7 +70,14 @@ public class GenericEventDetector {
String sourceState = extractSourceState(node); String sourceState = extractSourceState(node);
String[] smTypes = resolveStateMachineTypeArgumentsForExpression(emr.getExpression(), node); String[] smTypes = resolveStateMachineTypeArgumentsForExpression(emr.getExpression(), node);
boolean external = false;
if (receiver != null) {
AssignmentDag dag = new AssignmentDag(method);
external = dag.isExternal(receiver, new java.util.HashSet<>());
}
if (eventValue != null) { if (eventValue != null) {
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
triggers.add(TriggerPoint.builder() triggers.add(TriggerPoint.builder()
.event(eventValue) .event(eventValue)
.sourceState(sourceState) .sourceState(sourceState)
@@ -80,6 +87,8 @@ public class GenericEventDetector {
.lineNumber(cu.getLineNumber(node.getStartPosition())) .lineNumber(cu.getLineNumber(node.getStartPosition()))
.stateTypeFqn(smTypes[0]) .stateTypeFqn(smTypes[0])
.eventTypeFqn(smTypes[1]) .eventTypeFqn(smTypes[1])
.external(external)
.constraint(constraint)
.build()); .build());
} }
} }
@@ -228,6 +237,15 @@ public class GenericEventDetector {
String sourceState = extractSourceState(node); String sourceState = extractSourceState(node);
String[] smTypes = resolveStateMachineTypeArguments(node); String[] smTypes = resolveStateMachineTypeArguments(node);
boolean external = false;
if (!node.arguments().isEmpty()) {
Expression eventExpr = (Expression) node.arguments().get(0);
AssignmentDag dag = new AssignmentDag(method);
external = dag.isExternal(eventExpr, new java.util.HashSet<>());
}
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
List<TriggerPoint> results = new ArrayList<>(); List<TriggerPoint> results = new ArrayList<>();
if (eventValue != null && eventValue.startsWith("ENUM_SET:")) { if (eventValue != null && eventValue.startsWith("ENUM_SET:")) {
String[] parts = eventValue.substring(9).split(","); String[] parts = eventValue.substring(9).split(",");
@@ -242,6 +260,8 @@ public class GenericEventDetector {
.lineNumber(cu.getLineNumber(node.getStartPosition())) .lineNumber(cu.getLineNumber(node.getStartPosition()))
.stateTypeFqn(smTypes[0]) .stateTypeFqn(smTypes[0])
.eventTypeFqn(smTypes[1]) .eventTypeFqn(smTypes[1])
.external(external)
.constraint(constraint)
.build()); .build());
} }
} }
@@ -255,6 +275,8 @@ public class GenericEventDetector {
.lineNumber(cu.getLineNumber(node.getStartPosition())) .lineNumber(cu.getLineNumber(node.getStartPosition()))
.stateTypeFqn(smTypes[0]) .stateTypeFqn(smTypes[0])
.eventTypeFqn(smTypes[1]) .eventTypeFqn(smTypes[1])
.external(external)
.constraint(constraint)
.build()); .build());
} }
@@ -635,4 +657,93 @@ public class GenericEventDetector {
return simpleName; return simpleName;
} }
private static class AssignmentDag {
final java.util.Map<String, List<Expression>> assignments = new java.util.HashMap<>();
final java.util.Set<SingleVariableDeclaration> parameters = new java.util.HashSet<>();
AssignmentDag(MethodDeclaration method) {
if (method == null) return;
for (Object paramObj : method.parameters()) {
if (paramObj instanceof SingleVariableDeclaration svd) {
parameters.add(svd);
}
}
if (method.getBody() != null) {
method.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment fragment) {
String varName = fragment.getName().getIdentifier();
if (fragment.getInitializer() != null) {
assignments.computeIfAbsent(varName, k -> new ArrayList<>()).add(fragment.getInitializer());
}
return super.visit(fragment);
}
@Override
public boolean visit(Assignment assignment) {
if (assignment.getLeftHandSide() instanceof SimpleName sn) {
String varName = sn.getIdentifier();
assignments.computeIfAbsent(varName, k -> new ArrayList<>()).add(assignment.getRightHandSide());
}
return super.visit(assignment);
}
});
}
}
boolean isExternal(Expression expr, java.util.Set<String> visited) {
if (expr == null) return false;
Expression peeled = expr;
while (peeled instanceof ParenthesizedExpression pe) {
peeled = pe.getExpression();
}
while (peeled instanceof CastExpression ce) {
peeled = ce.getExpression();
}
if (peeled instanceof SimpleName sn) {
String varName = sn.getIdentifier();
for (SingleVariableDeclaration svd : parameters) {
if (svd.getName().getIdentifier().equals(varName)) {
return isAnnotatedAsExternal(svd);
}
}
if (assignments.containsKey(varName)) {
if (!visited.add(varName)) {
return false;
}
List<Expression> defs = assignments.get(varName);
for (Expression def : defs) {
if (isExternal(def, visited)) {
return true;
}
}
}
} else if (peeled instanceof MethodInvocation mi) {
if (mi.getExpression() != null && isExternal(mi.getExpression(), visited)) {
return true;
}
for (Object arg : mi.arguments()) {
if (isExternal((Expression) arg, visited)) {
return true;
}
}
}
return false;
}
private boolean isAnnotatedAsExternal(SingleVariableDeclaration svd) {
for (Object modifier : svd.modifiers()) {
if (modifier instanceof Annotation annotation) {
String typeName = annotation.getTypeName().getFullyQualifiedName();
if (typeName.endsWith("PathVariable") || typeName.endsWith("RequestBody") || typeName.endsWith("RequestParam")
|| typeName.endsWith("PathParam") || typeName.endsWith("QueryParam")) {
return true;
}
}
}
return false;
}
}
} }

View File

@@ -34,9 +34,12 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier(); String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
List<String> calledMethods = resolveCalledMethodsPolymorphic(node); List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
List<String> args = resolveArguments(node.arguments()); List<String> args = resolveArguments(node.arguments());
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
for (String calledMethod : calledMethods) { for (String calledMethod : calledMethods) {
String receiver = getReceiverString(node.getExpression()); String receiver = getReceiverString(node.getExpression());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args, receiver)); CallEdge edge = new CallEdge(calledMethod, args, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
} }
for (Object argObj : node.arguments()) { for (Object argObj : node.arguments()) {
if (argObj instanceof ExpressionMethodReference emr) { if (argObj instanceof ExpressionMethodReference emr) {
@@ -53,7 +56,9 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
implicitArgs.addAll(args); implicitArgs.addAll(args);
} }
String receiver = getReceiverString(node.getExpression()); String receiver = getReceiverString(node.getExpression());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, implicitArgs, receiver)); CallEdge edge = new CallEdge(refMethod, implicitArgs, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
} }
} }
} else { } else {
@@ -73,11 +78,15 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
implicitArgs.addAll(args); implicitArgs.addAll(args);
} }
String receiver = getReceiverString(node.getExpression()); String receiver = getReceiverString(node.getExpression());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, implicitArgs, receiver)); CallEdge edge = new CallEdge(calledMethod, implicitArgs, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
List<String> impls = context.getImplementations(fallbackTypeFqn); List<String> impls = context.getImplementations(fallbackTypeFqn);
for (String impl : impls) { for (String impl : impls) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args, receiver)); CallEdge implEdge = new CallEdge(impl + "." + emr.getName().getIdentifier(), args, receiver);
implEdge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(implEdge);
} }
} }
} }
@@ -110,7 +119,10 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
} }
List<String> args = resolveArguments(node.arguments()); List<String> args = resolveArguments(node.arguments());
String receiver = "super"; String receiver = "super";
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args, receiver)); String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
CallEdge edge = new CallEdge(calledMethod, args, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
} }
} }
} }

View File

@@ -38,8 +38,11 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier(); String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
List<String> calledMethods = resolveCalledMethodsPolymorphic(node); List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
List<String> args = resolveArguments(node.arguments()); List<String> args = resolveArguments(node.arguments());
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
for (String calledMethod : calledMethods) { for (String calledMethod : calledMethods) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args)); CallEdge edge = new CallEdge(calledMethod, args);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
} }
for (Object argObj : node.arguments()) { for (Object argObj : node.arguments()) {
if (argObj instanceof ExpressionMethodReference emr) { if (argObj instanceof ExpressionMethodReference emr) {
@@ -55,7 +58,9 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
} else { } else {
implicitArgs.addAll(args); implicitArgs.addAll(args);
} }
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, implicitArgs)); CallEdge edge = new CallEdge(refMethod, implicitArgs);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
} }
} }
} else { } else {
@@ -74,11 +79,15 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
} else { } else {
implicitArgs.addAll(args); implicitArgs.addAll(args);
} }
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, implicitArgs)); CallEdge edge = new CallEdge(calledMethod, implicitArgs);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
List<String> impls = context.getImplementations(fallbackTypeFqn); List<String> impls = context.getImplementations(fallbackTypeFqn);
for (String impl : impls) { for (String impl : impls) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args)); CallEdge implEdge = new CallEdge(impl + "." + emr.getName().getIdentifier(), args);
implEdge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(implEdge);
} }
} }
} }
@@ -110,7 +119,10 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
calledMethod = superFqn + "." + methodName; calledMethod = superFqn + "." + methodName;
} }
List<String> args = resolveArguments(node.arguments()); List<String> args = resolveArguments(node.arguments());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args)); String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
CallEdge edge = new CallEdge(calledMethod, args);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
} }
} }
} }

View File

@@ -28,6 +28,16 @@ public class VariableTracer {
return all.isEmpty() ? expr : all.get(all.size() - 1); return all.isEmpty() ? expr : all.get(all.size() - 1);
} }
private Expression peelExpression(Expression expr) {
while (expr instanceof ParenthesizedExpression pe) {
expr = pe.getExpression();
}
if (expr instanceof CastExpression ce) {
return peelExpression(ce.getExpression());
}
return expr;
}
public List<Expression> traceVariableAll(Expression expr) { public List<Expression> traceVariableAll(Expression expr) {
return dataFlowModel.getReachingDefinitions(expr); return dataFlowModel.getReachingDefinitions(expr);
} }
@@ -82,6 +92,30 @@ public class VariableTracer {
return null; return null;
} }
private String getFieldType(TypeDeclaration td, String fieldName, CodebaseContext context, java.util.Set<String> visited) {
if (td == null) return null;
String typeFqn = context.getFqn(td);
if (!visited.add(typeFqn)) return null;
for (FieldDeclaration fd : td.getFields()) {
for (Object fragObj : fd.fragments()) {
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
if (frag.getName().getIdentifier().equals(fieldName)) {
return fd.getType().toString();
}
}
}
String superFqn = context.getSuperclassFqn(td);
if (superFqn != null) {
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
if (superTd != null) {
return getFieldType(superTd, fieldName, context, visited);
}
}
return null;
}
public String getVariableDeclaredType(String methodFqn, String cleanFieldName) { public String getVariableDeclaredType(String methodFqn, String cleanFieldName) {
if (methodFqn == null) return null; if (methodFqn == null) return null;
int lastDot = methodFqn.lastIndexOf('.'); int lastDot = methodFqn.lastIndexOf('.');
@@ -200,15 +234,9 @@ public class VariableTracer {
if (foundType[0] != null) return foundType[0]; if (foundType[0] != null) return foundType[0];
} }
// Fallback to fields // Fallback to fields including superclasses
for (FieldDeclaration fd : td.getFields()) { String fieldType = getFieldType(td, cleanFieldName, context, new java.util.HashSet<>());
for (Object fragObj : fd.fragments()) { if (fieldType != null) return fieldType;
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
if (frag.getName().getIdentifier().equals(cleanFieldName)) {
return fd.getType().toString();
}
}
}
} }
} }
@@ -240,14 +268,14 @@ public class VariableTracer {
@Override @Override
public boolean visit(VariableDeclarationFragment node) { public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
initializers.add(node.getInitializer()); initializers.add(peelExpression(node.getInitializer()));
} }
return super.visit(node); return super.visit(node);
} }
@Override @Override
public boolean visit(Assignment node) { public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
initializers.add(node.getRightHandSide()); initializers.add(peelExpression(node.getRightHandSide()));
} }
return super.visit(node); return super.visit(node);
} }
@@ -258,7 +286,7 @@ public class VariableTracer {
for (Expression expr : initializers) { for (Expression expr : initializers) {
Expression traced = traceVariable(expr); Expression traced = traceVariable(expr);
if (traced instanceof MethodInvocation mi) { if (traced instanceof MethodInvocation mi) {
Expression innerMost = unwrapMethodInvocation(mi, 0); Expression innerMost = unwrapMethodInvocation(mi, 0, methodFqn);
stringified.add(innerMost.toString()); stringified.add(innerMost.toString());
} else if (traced instanceof ArrayInitializer) { } else if (traced instanceof ArrayInitializer) {
stringified.add("new Object[]" + traced.toString()); stringified.add("new Object[]" + traced.toString());
@@ -513,8 +541,33 @@ public class VariableTracer {
return (TypeDeclaration) parent; return (TypeDeclaration) parent;
} }
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) { private Expression unwrapMethodInvocation(MethodInvocation mi, int depth, String methodFqn) {
if (depth > 5) return mi; if (depth > 5) return mi;
String targetMethodFqn = resolveTargetMethodFqn(mi, methodFqn);
if (targetMethodFqn != null && targetMethodFqn.contains(".")) {
String className = targetMethodFqn.substring(0, targetMethodFqn.lastIndexOf('.'));
String methodName = targetMethodFqn.substring(targetMethodFqn.lastIndexOf('.') + 1);
TypeDeclaration currentTd = methodFqn != null && methodFqn.contains(".") ? context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.'))) : null;
CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
TypeDeclaration td = context.getTypeDeclaration(className, cu);
if (td != null) {
MethodDeclaration localMd = context.findMethodDeclaration(td, methodName, true);
if (localMd != null) {
int paramIdx = getReturnedParameterIndex(localMd);
if (paramIdx >= 0 && paramIdx < mi.arguments().size()) {
Expression arg = peelExpression((Expression) mi.arguments().get(paramIdx));
if (arg instanceof MethodInvocation innerMi) {
return unwrapMethodInvocation(innerMi, depth + 1, methodFqn);
}
return arg;
}
}
}
}
if (mi.getExpression() != null) { if (mi.getExpression() != null) {
String exprStr = mi.getExpression().toString(); String exprStr = mi.getExpression().toString();
if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays") && !exprStr.equals("Objects") && !exprStr.equals("Mono") && !exprStr.equals("Flux")) { if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays") && !exprStr.equals("Objects") && !exprStr.equals("Mono") && !exprStr.equals("Flux")) {
@@ -538,12 +591,61 @@ public class VariableTracer {
return mi; return mi;
} }
Expression arg = (Expression) mi.arguments().get(0); Expression arg = peelExpression((Expression) mi.arguments().get(0));
if (arg instanceof MethodInvocation innerMi) { if (arg instanceof MethodInvocation innerMi) {
return unwrapMethodInvocation(innerMi, depth + 1); return unwrapMethodInvocation(innerMi, depth + 1, methodFqn);
} }
return arg; return arg;
} }
return mi; return mi;
} }
private String resolveTargetMethodFqn(MethodInvocation mi, String currentMethodFqn) {
if (currentMethodFqn == null || !currentMethodFqn.contains(".")) return null;
String currentClass = currentMethodFqn.substring(0, currentMethodFqn.lastIndexOf('.'));
if (mi.getExpression() == null || mi.getExpression().toString().equals("this")) {
return currentClass + "." + mi.getName().getIdentifier();
}
if (mi.getExpression() instanceof SimpleName sn) {
String declaredType = getVariableDeclaredType(currentMethodFqn, sn.getIdentifier());
if (declaredType != null) {
return declaredType + "." + mi.getName().getIdentifier();
}
} else if (mi.getExpression() instanceof FieldAccess fa) {
String declaredType = getVariableDeclaredType(currentMethodFqn, fa.getName().getIdentifier());
if (declaredType != null) {
return declaredType + "." + mi.getName().getIdentifier();
}
}
return null;
}
private int getReturnedParameterIndex(MethodDeclaration md) {
if (md.getBody() == null) return -1;
List<?> parameters = md.parameters();
if (parameters.isEmpty()) return -1;
final List<String> returnedExprs = new ArrayList<>();
md.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(ReturnStatement node) {
if (node.getExpression() != null) {
returnedExprs.add(node.getExpression().toString());
}
return super.visit(node);
}
});
if (returnedExprs.size() == 1) {
String retStr = returnedExprs.get(0);
for (int i = 0; i < parameters.size(); i++) {
if (parameters.get(i) instanceof SingleVariableDeclaration svd) {
if (svd.getName().getIdentifier().equals(retStr)) {
return i;
}
}
}
}
return -1;
}
} }

View File

@@ -95,4 +95,26 @@ public final class AstUtils {
return false; return false;
} }
public static String findConditionConstraint(org.eclipse.jdt.core.dom.ASTNode node) {
java.util.List<String> conditions = new java.util.ArrayList<>();
org.eclipse.jdt.core.dom.ASTNode current = node;
while (current != null && !(current instanceof org.eclipse.jdt.core.dom.MethodDeclaration)) {
org.eclipse.jdt.core.dom.ASTNode parent = current.getParent();
if (parent instanceof org.eclipse.jdt.core.dom.IfStatement ifStmt) {
if (ifStmt.getExpression() != current) {
org.eclipse.jdt.core.dom.Expression cond = ifStmt.getExpression();
if (current == ifStmt.getElseStatement()) {
conditions.add("!(" + cond.toString() + ")");
} else {
conditions.add(cond.toString());
}
}
}
current = parent;
}
if (conditions.isEmpty()) return null;
java.util.Collections.reverse(conditions);
return String.join(" && ", conditions);
}
} }

View File

@@ -158,6 +158,24 @@ public class GoldenUpdater {
Path.of("state_machines/inheritance_extra_functions3_state_machine"), Path.of("state_machines/inheritance_extra_functions3_state_machine"),
Path.of("src/test/resources/golden/G2StateMachineConfiguration"), Path.of("src/test/resources/golden/G2StateMachineConfiguration"),
"G2StateMachineConfiguration" "G2StateMachineConfiguration"
),
new TestScenario(
"Enterprise Order State Machine",
Path.of("state_machines/state_machine_enterprise"),
Path.of("src/test/resources/golden/OrderStateMachineConfiguration"),
"OrderStateMachineConfiguration"
),
new TestScenario(
"Enterprise Document State Machine",
Path.of("state_machines/state_machine_enterprise"),
Path.of("src/test/resources/golden/DocumentStateMachineConfiguration"),
"DocumentStateMachineConfiguration"
),
new TestScenario(
"Enterprise User State Machine",
Path.of("state_machines/state_machine_enterprise"),
Path.of("src/test/resources/golden/UserStateMachineConfiguration"),
"UserStateMachineConfiguration"
) )
); );
} }

View File

@@ -0,0 +1,121 @@
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class StrictFqnMatchingEngineTest {
private final StrictFqnMatchingEngine engine = new StrictFqnMatchingEngine();
@Test
void shouldMatchExactFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("PAY")
.eventTypeFqn("com.example.OrderEvents")
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchEnumQualifierToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("OrderEvents.PAY")
.eventTypeFqn("com.example.OrderEvents")
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldPreventCrossEnumContamination() {
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("OrderEvents.PAY")
.eventTypeFqn("com.example.OrderEvents")
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
}
@Test
void shouldPreventCrossEnumContaminationWithPolymorphicEvents() {
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("getType()")
.eventTypeFqn("com.example.OrderEvents")
.polymorphicEvents(List.of("OrderEvents.PAY"))
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
}
@Test
void shouldMatchPolymorphicEnumQualifierToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("getType()")
.eventTypeFqn("com.example.OrderEvents")
.polymorphicEvents(List.of("OrderEvents.PAY"))
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchRawStringToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("PAY")
.eventTypeFqn("com.example.OrderEvents")
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchRawStringToRawString() {
Event smEvent = Event.of("PAY", "PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("PAY").build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchPolymorphicRawStringToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("getType()")
.eventTypeFqn("com.example.OrderEvents")
.polymorphicEvents(List.of("PAY"))
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchWildcards() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("event").build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldNotMatchMismatchedFqn() {
Event smEvent = Event.of("CREATE", "com.example.other.OrderEvent.CREATE");
TriggerPoint triggerPoint = TriggerPoint.builder()
.event("CREATE")
.eventTypeFqn("com.example.some.OrderEvent")
.build();
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
}
}

View File

@@ -466,7 +466,8 @@ class HeuristicCallGraphEngineTypeTest {
assertThat(chains).hasSize(1); assertThat(chains).hasSize(1);
CallChain chain = chains.get(0); CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("mapToEnum(str)");
assertThat(chain.getTriggerPoint().getPolymorphicEvents()) assertThat(chain.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("MyEvents.A", "MyEvents.B"); .containsExactly("<SYMBOLIC: MyEvents.*>");
} }
} }

View File

@@ -173,4 +173,111 @@ class LocalVariableTest {
assertThat(chain.getTriggerPoint().getPolymorphicEvents()) assertThat(chain.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("OrderEvents.CANCEL"); .containsExactlyInAnyOrder("OrderEvents.CANCEL");
} }
@Test
void shouldUnwrapExplicitPassthroughMethod(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
private EventValidator validator;
public void processOrderEvent(RichOrderEvent domainEvent) {
OrderEvents resolved = validator.passthrough(domainEvent.getType());
service.updateOrderState(resolved);
}
}
class EventValidator {
public OrderEvents passthrough(OrderEvents e) {
if (e == null) throw new IllegalArgumentException();
return e;
}
}
class OrderService {
public void updateOrderState(OrderEvents event) {}
}
class RichOrderEvent {
public OrderEvents getType() {
return OrderEvents.PAY;
}
}
enum OrderEvents { PAY, CANCEL }
""";
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.OrderController")
.methodName("processOrderEvent")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.OrderService")
.methodName("updateOrderState")
.event("event")
.build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("OrderEvents.PAY");
}
@Test
void shouldFallbackToPrefixMatchingForExternalMethods(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
public void processOrderEvent(RichOrderEvent domainEvent) {
OrderEvents resolved = UnknownExternalClass.validate(domainEvent.getType());
service.updateOrderState(resolved);
}
}
class OrderService {
public void updateOrderState(OrderEvents event) {}
}
class RichOrderEvent {
public OrderEvents getType() {
return OrderEvents.PAY;
}
}
enum OrderEvents { PAY, CANCEL }
""";
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.OrderController")
.methodName("processOrderEvent")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.OrderService")
.methodName("updateOrderState")
.event("event")
.build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
.doesNotContain("OrderEvents.PAY");
}
} }

View File

@@ -0,0 +1,13 @@
digraph statemachine {
rankdir=LR;
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
edge [fontname="Arial", fontsize=10];
_start [shape=circle, label="", fillcolor=black, width=0.1];
_start -> DRAFT;
APPROVED [fillcolor=lightgray];
DRAFT -> IN_REVIEW [label="DocumentEvent.SUBMIT", style="solid", color="black"];
IN_REVIEW -> APPROVED [label="DocumentEvent.APPROVE", style="solid", color="black"];
IN_REVIEW -> DRAFT [label="DocumentEvent.REJECT", style="solid", color="black"];
}

View File

@@ -0,0 +1,676 @@
{
"metadata" : {
"triggers" : [ {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 30,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 37,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 44,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 51,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 58,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 65,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.user.UserEvent.SUSPEND",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 72,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "ORDER",
"lineNumber" : 81,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "DOCUMENT",
"lineNumber" : 87,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "USER",
"lineNumber" : 93,
"polymorphicEvents" : null
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/machine/order/pay",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/order/ship",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/submit",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/approve",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/reject",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/user/verify",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/verify",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/user/suspend",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/suspend",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/pay",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.payOrder", "click.kamil.enterprise.web.StateMachineDispatcher.payOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 30,
"polymorphicEvents" : [ "OrderEvent.PAY" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/ship",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.shipOrder", "click.kamil.enterprise.web.StateMachineDispatcher.shipOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 37,
"polymorphicEvents" : [ "OrderEvent.SHIP" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/submit",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.submitDocument", "click.kamil.enterprise.web.StateMachineDispatcher.submitDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 44,
"polymorphicEvents" : [ "DocumentEvent.SUBMIT" ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "DocumentState.DRAFT",
"targetState" : "DocumentState.IN_REVIEW",
"event" : "DocumentEvent.SUBMIT"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/approve",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.approveDocument", "click.kamil.enterprise.web.StateMachineDispatcher.approveDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 51,
"polymorphicEvents" : [ "DocumentEvent.APPROVE" ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.APPROVED",
"event" : "DocumentEvent.APPROVE"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/reject",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.rejectDocument", "click.kamil.enterprise.web.StateMachineDispatcher.rejectDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 58,
"polymorphicEvents" : [ "DocumentEvent.REJECT" ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.DRAFT",
"event" : "DocumentEvent.REJECT"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/verify",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/verify",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.verifyUser", "click.kamil.enterprise.web.StateMachineDispatcher.verifyUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 65,
"polymorphicEvents" : [ "UserEvent.VERIFY" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/suspend",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/suspend",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.suspendUser", "click.kamil.enterprise.web.StateMachineDispatcher.suspendUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.SUSPEND",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 72,
"polymorphicEvents" : [ "UserEvent.SUSPEND" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 81,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "DocumentState.DRAFT",
"targetState" : "DocumentState.IN_REVIEW",
"event" : "DocumentEvent.SUBMIT"
}, {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.APPROVED",
"event" : "DocumentEvent.APPROVE"
}, {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.DRAFT",
"event" : "DocumentEvent.REJECT"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 87,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "DocumentState.DRAFT",
"targetState" : "DocumentState.IN_REVIEW",
"event" : "DocumentEvent.SUBMIT"
}, {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.APPROVED",
"event" : "DocumentEvent.APPROVE"
}, {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.DRAFT",
"event" : "DocumentEvent.REJECT"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 93,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "DocumentState.DRAFT",
"targetState" : "DocumentState.IN_REVIEW",
"event" : "DocumentEvent.SUBMIT"
}, {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.APPROVED",
"event" : "DocumentEvent.APPROVE"
}, {
"sourceState" : "DocumentState.IN_REVIEW",
"targetState" : "DocumentState.DRAFT",
"event" : "DocumentEvent.REJECT"
} ]
} ],
"properties" : {
"default" : { }
}
},
"name" : "click.kamil.enterprise.machines.document.DocumentStateMachineConfiguration",
"renderChoicesAsDiamonds" : true,
"startStates" : [ "DocumentState.DRAFT" ],
"transitions" : [ {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "DocumentState.DRAFT",
"fullIdentifier" : "DocumentState.DRAFT"
} ],
"targetStates" : [ {
"rawName" : "DocumentState.IN_REVIEW",
"fullIdentifier" : "DocumentState.IN_REVIEW"
} ],
"event" : {
"rawName" : "DocumentEvent.SUBMIT",
"fullIdentifier" : "DocumentEvent.SUBMIT"
},
"guard" : null,
"actions" : [ ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "DocumentState.IN_REVIEW",
"fullIdentifier" : "DocumentState.IN_REVIEW"
} ],
"targetStates" : [ {
"rawName" : "DocumentState.APPROVED",
"fullIdentifier" : "DocumentState.APPROVED"
} ],
"event" : {
"rawName" : "DocumentEvent.APPROVE",
"fullIdentifier" : "DocumentEvent.APPROVE"
},
"guard" : null,
"actions" : [ ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "DocumentState.IN_REVIEW",
"fullIdentifier" : "DocumentState.IN_REVIEW"
} ],
"targetStates" : [ {
"rawName" : "DocumentState.DRAFT",
"fullIdentifier" : "DocumentState.DRAFT"
} ],
"event" : {
"rawName" : "DocumentEvent.REJECT",
"fullIdentifier" : "DocumentEvent.REJECT"
},
"guard" : null,
"actions" : [ ],
"order" : null
} ],
"endStates" : [ "DocumentState.APPROVED" ]
}

View File

@@ -0,0 +1,33 @@
@startuml
!pragma layout smetana
set separator none
hide empty description
hide stereotype
skinparam state {
BackgroundColor white
BorderColor #94a3b8
BorderThickness 1
FontName Inter
FontSize 9
FontStyle bold
RoundCorner 20
Padding 1
}
skinparam shadowing false
skinparam ArrowFontName JetBrains Mono
skinparam ArrowFontSize 8
skinparam ArrowColor #cbd5e1
skinparam ArrowThickness 1
skinparam dpi 110
skinparam svgLinkTarget _self
[*] --> DocumentState.DRAFT
DocumentState.DRAFT -[#1E90FF,bold]-> DocumentState.IN_REVIEW <<external>> : DocumentEvent.SUBMIT
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.APPROVED <<external>> : DocumentEvent.APPROVE
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.DRAFT <<external>> : DocumentEvent.REJECT
DocumentState.APPROVED --> [*]
@enduml

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="DRAFT">
<state id="DRAFT">
<transition target="IN_REVIEW" event="DocumentEvent.SUBMIT"/>
</state>
<state id="IN_REVIEW">
<transition target="APPROVED" event="DocumentEvent.APPROVE"/>
<transition target="DRAFT" event="DocumentEvent.REJECT"/>
</state>
<state id="APPROVED">
</state>
</scxml>

View File

@@ -9,7 +9,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "PLACE_ORDER", "event" : "PLACE_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
@@ -19,7 +21,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 16, "lineNumber" : 16,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "CANCEL_ORDER", "event" : "CANCEL_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
@@ -29,7 +33,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 21, "lineNumber" : 21,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "PAY_ORDER", "event" : "PAY_ORDER",
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService", "className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
@@ -39,7 +45,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "SHIP_ORDER", "event" : "SHIP_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
@@ -49,7 +57,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "RETURN_ORDER", "event" : "RETURN_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
@@ -59,7 +69,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -192,7 +204,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 16, "lineNumber" : 16,
"polymorphicEvents" : [ "PLACE_ORDER" ] "polymorphicEvents" : [ "PLACE_ORDER" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -227,7 +241,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 21, "lineNumber" : 21,
"polymorphicEvents" : [ "CANCEL_ORDER" ] "polymorphicEvents" : [ "CANCEL_ORDER" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -257,7 +273,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -288,7 +306,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : [ "PAY_ORDER" ] "polymorphicEvents" : [ "PAY_ORDER" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -323,7 +343,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -358,7 +380,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {

View File

@@ -9,7 +9,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 34, "lineNumber" : 34,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "AUDIT_EVENT", "event" : "AUDIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor",
@@ -19,7 +21,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 16, "lineNumber" : 16,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "FALLBACK_EVENT", "event" : "FALLBACK_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
@@ -29,7 +33,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "PROFILED_EVENT", "event" : "PROFILED_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
@@ -39,7 +45,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "PRIMARY_EVENT", "event" : "PRIMARY_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
@@ -49,7 +57,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "SUBMIT_EVENT", "event" : "SUBMIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -59,7 +69,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 20, "lineNumber" : 20,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "CANCEL_EVENT", "event" : "CANCEL_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -69,7 +81,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "[LIFECYCLE:RESTORE]", "event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -79,7 +93,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 29, "lineNumber" : 29,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "QUALIFIER_EVENT", "event" : "QUALIFIER_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
@@ -89,7 +105,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "NAMED_EVENT", "event" : "NAMED_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
@@ -99,7 +117,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "AUTHORIZE", "event" : "AUTHORIZE",
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
@@ -109,7 +129,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 22, "lineNumber" : 22,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "CAPTURE", "event" : "CAPTURE",
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
@@ -119,7 +141,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 35, "lineNumber" : 35,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "[LIFECYCLE:RESTORE]", "event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
@@ -129,7 +153,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 28, "lineNumber" : 28,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "REACTIVE_EVENT", "event" : "REACTIVE_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
@@ -139,7 +165,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -367,7 +395,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 20, "lineNumber" : 20,
"polymorphicEvents" : [ "SUBMIT_EVENT" ] "polymorphicEvents" : [ "SUBMIT_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -398,7 +428,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : [ "CANCEL_EVENT" ] "polymorphicEvents" : [ "CANCEL_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -429,7 +461,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 34, "lineNumber" : 34,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -464,7 +498,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 29, "lineNumber" : 29,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : "orderId", "contextMachineId" : "orderId",
"matchedTransitions" : null "matchedTransitions" : null
@@ -495,7 +531,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : [ "REACTIVE_EVENT" ] "polymorphicEvents" : [ "REACTIVE_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -525,7 +563,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 16, "lineNumber" : 16,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -556,7 +596,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ] "polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -583,7 +625,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ] "polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -610,7 +654,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ] "polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -637,7 +683,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ] "polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -664,7 +712,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ] "polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -691,7 +741,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ] "polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -718,7 +770,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ] "polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -749,7 +803,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 22, "lineNumber" : 22,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -780,7 +836,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 35, "lineNumber" : 35,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : "paymentId", "contextMachineId" : "paymentId",
"matchedTransitions" : null "matchedTransitions" : null
@@ -811,7 +869,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 28, "lineNumber" : 28,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : "paymentId", "contextMachineId" : "paymentId",
"matchedTransitions" : null "matchedTransitions" : null

View File

@@ -9,7 +9,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 34, "lineNumber" : 34,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "AUDIT_EVENT", "event" : "AUDIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor",
@@ -19,7 +21,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 16, "lineNumber" : 16,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "FALLBACK_EVENT", "event" : "FALLBACK_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
@@ -29,7 +33,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "PROFILED_EVENT", "event" : "PROFILED_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
@@ -39,7 +45,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "PRIMARY_EVENT", "event" : "PRIMARY_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
@@ -49,7 +57,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "SUBMIT_EVENT", "event" : "SUBMIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -59,7 +69,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 20, "lineNumber" : 20,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "CANCEL_EVENT", "event" : "CANCEL_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -69,7 +81,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "[LIFECYCLE:RESTORE]", "event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -79,7 +93,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 29, "lineNumber" : 29,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "QUALIFIER_EVENT", "event" : "QUALIFIER_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
@@ -89,7 +105,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "NAMED_EVENT", "event" : "NAMED_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
@@ -99,7 +117,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "AUTHORIZE", "event" : "AUTHORIZE",
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
@@ -109,7 +129,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 22, "lineNumber" : 22,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "CAPTURE", "event" : "CAPTURE",
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
@@ -119,7 +141,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 35, "lineNumber" : 35,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "[LIFECYCLE:RESTORE]", "event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
@@ -129,7 +153,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 28, "lineNumber" : 28,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "REACTIVE_EVENT", "event" : "REACTIVE_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
@@ -139,7 +165,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -367,7 +395,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 20, "lineNumber" : 20,
"polymorphicEvents" : [ "SUBMIT_EVENT" ] "polymorphicEvents" : [ "SUBMIT_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -398,7 +428,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : [ "CANCEL_EVENT" ] "polymorphicEvents" : [ "CANCEL_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -429,7 +461,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 34, "lineNumber" : 34,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -464,7 +498,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 29, "lineNumber" : 29,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : "orderId", "contextMachineId" : "orderId",
"matchedTransitions" : null "matchedTransitions" : null
@@ -495,7 +531,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : [ "REACTIVE_EVENT" ] "polymorphicEvents" : [ "REACTIVE_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -525,7 +563,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 16, "lineNumber" : 16,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -556,7 +596,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ] "polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -583,7 +625,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ] "polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -610,7 +654,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ] "polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -637,7 +683,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ] "polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -664,7 +712,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 19, "lineNumber" : 19,
"polymorphicEvents" : [ "PRIMARY_EVENT" ] "polymorphicEvents" : [ "PRIMARY_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -691,7 +741,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "QUALIFIER_EVENT" ] "polymorphicEvents" : [ "QUALIFIER_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -718,7 +770,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "NAMED_EVENT" ] "polymorphicEvents" : [ "NAMED_EVENT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -749,7 +803,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 22, "lineNumber" : 22,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : null "matchedTransitions" : null
@@ -780,7 +836,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 35, "lineNumber" : 35,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : "paymentId", "contextMachineId" : "paymentId",
"matchedTransitions" : null "matchedTransitions" : null
@@ -811,7 +869,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 28, "lineNumber" : 28,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : "paymentId", "contextMachineId" : "paymentId",
"matchedTransitions" : null "matchedTransitions" : null

View File

@@ -9,7 +9,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 15, "lineNumber" : 15,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -57,7 +59,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 15, "lineNumber" : 15,
"polymorphicEvents" : [ "INHERITED_SUBMIT" ] "polymorphicEvents" : [ "INHERITED_SUBMIT" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {

View File

@@ -1,7 +1,7 @@
{ {
"metadata" : { "metadata" : {
"triggers" : [ { "triggers" : [ {
"event" : "SUBMIT", "event" : "String.SUBMIT",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController", "className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
"methodName" : "submitOrder", "methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java", "sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
@@ -9,9 +9,11 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 40, "lineNumber" : 40,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "ORDER_EVENT", "event" : "String.ORDER_EVENT",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService", "className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
"methodName" : "onMessage", "methodName" : "onMessage",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java", "sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
@@ -19,7 +21,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 52, "lineNumber" : 52,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -98,7 +102,7 @@
}, },
"methodChain" : [ "click.kamil.maven.core.MavenOrderStateMachine.OrderController.submitOrder" ], "methodChain" : [ "click.kamil.maven.core.MavenOrderStateMachine.OrderController.submitOrder" ],
"triggerPoint" : { "triggerPoint" : {
"event" : "SUBMIT", "event" : "String.SUBMIT",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController", "className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
"methodName" : "submitOrder", "methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java", "sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
@@ -106,14 +110,12 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 40, "lineNumber" : 40,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : null
"sourceState" : "INIT",
"targetState" : "BUSY",
"event" : "SUBMIT"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { } "default" : { }

View File

@@ -1,7 +1,7 @@
{ {
"metadata" : { "metadata" : {
"triggers" : [ { "triggers" : [ {
"event" : "SUBMIT", "event" : "String.SUBMIT",
"className" : "click.kamil.multi.core.OrderController", "className" : "click.kamil.multi.core.OrderController",
"methodName" : "submitOrder", "methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java", "sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
@@ -9,7 +9,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -61,7 +63,7 @@
}, },
"methodChain" : [ "click.kamil.multi.core.OrderController.submitOrder" ], "methodChain" : [ "click.kamil.multi.core.OrderController.submitOrder" ],
"triggerPoint" : { "triggerPoint" : {
"event" : "SUBMIT", "event" : "String.SUBMIT",
"className" : "click.kamil.multi.core.OrderController", "className" : "click.kamil.multi.core.OrderController",
"methodName" : "submitOrder", "methodName" : "submitOrder",
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java", "sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
@@ -69,14 +71,12 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 18, "lineNumber" : 18,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : null
"sourceState" : "NEW",
"targetState" : "PROCESSING",
"event" : "SUBMIT"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { } "default" : { }

View File

@@ -0,0 +1,12 @@
digraph statemachine {
rankdir=LR;
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
edge [fontname="Arial", fontsize=10];
_start [shape=circle, label="", fillcolor=black, width=0.1];
_start -> NEW;
SHIPPED [fillcolor=lightgray];
NEW -> PENDING [label="OrderEvent.PAY / λ", style="solid", color="black"];
PENDING -> SHIPPED [label="OrderEvent.SHIP", style="solid", color="black"];
}

View File

@@ -0,0 +1,650 @@
{
"metadata" : {
"triggers" : [ {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 30,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 37,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 44,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 51,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 58,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 65,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.user.UserEvent.SUSPEND",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 72,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "ORDER",
"lineNumber" : 81,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "DOCUMENT",
"lineNumber" : 87,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "USER",
"lineNumber" : 93,
"polymorphicEvents" : null
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/machine/order/pay",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/order/ship",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/submit",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/approve",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/reject",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/user/verify",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/verify",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/user/suspend",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/suspend",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/pay",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.payOrder", "click.kamil.enterprise.web.StateMachineDispatcher.payOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 30,
"polymorphicEvents" : [ "OrderEvent.PAY" ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PENDING",
"event" : "OrderEvent.PAY"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/ship",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.shipOrder", "click.kamil.enterprise.web.StateMachineDispatcher.shipOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 37,
"polymorphicEvents" : [ "OrderEvent.SHIP" ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.PENDING",
"targetState" : "OrderState.SHIPPED",
"event" : "OrderEvent.SHIP"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/submit",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.submitDocument", "click.kamil.enterprise.web.StateMachineDispatcher.submitDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 44,
"polymorphicEvents" : [ "DocumentEvent.SUBMIT" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/approve",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.approveDocument", "click.kamil.enterprise.web.StateMachineDispatcher.approveDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 51,
"polymorphicEvents" : [ "DocumentEvent.APPROVE" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/reject",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.rejectDocument", "click.kamil.enterprise.web.StateMachineDispatcher.rejectDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 58,
"polymorphicEvents" : [ "DocumentEvent.REJECT" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/verify",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/verify",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.verifyUser", "click.kamil.enterprise.web.StateMachineDispatcher.verifyUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 65,
"polymorphicEvents" : [ "UserEvent.VERIFY" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/suspend",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/suspend",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.suspendUser", "click.kamil.enterprise.web.StateMachineDispatcher.suspendUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.SUSPEND",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 72,
"polymorphicEvents" : [ "UserEvent.SUSPEND" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 81,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PENDING",
"event" : "OrderEvent.PAY"
}, {
"sourceState" : "OrderState.PENDING",
"targetState" : "OrderState.SHIPPED",
"event" : "OrderEvent.SHIP"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 87,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PENDING",
"event" : "OrderEvent.PAY"
}, {
"sourceState" : "OrderState.PENDING",
"targetState" : "OrderState.SHIPPED",
"event" : "OrderEvent.SHIP"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 93,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PENDING",
"event" : "OrderEvent.PAY"
}, {
"sourceState" : "OrderState.PENDING",
"targetState" : "OrderState.SHIPPED",
"event" : "OrderEvent.SHIP"
} ]
} ],
"properties" : {
"default" : { }
}
},
"name" : "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration",
"renderChoicesAsDiamonds" : true,
"startStates" : [ "OrderState.NEW" ],
"transitions" : [ {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "OrderState.NEW",
"fullIdentifier" : "OrderState.NEW"
} ],
"targetStates" : [ {
"rawName" : "OrderState.PENDING",
"fullIdentifier" : "OrderState.PENDING"
} ],
"event" : {
"rawName" : "OrderEvent.PAY",
"fullIdentifier" : "OrderEvent.PAY"
},
"guard" : null,
"actions" : [ {
"expression" : "context -> System.out.println(\"Payment processed.\")",
"isLambda" : true,
"internalLogic" : "context -> System.out.println(\"Payment processed.\")",
"lineNumber" : 28,
"className" : "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration",
"sourceFile" : "src/main/java/click/kamil/enterprise/machines/order/OrderStateMachineConfiguration.java"
} ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "OrderState.PENDING",
"fullIdentifier" : "OrderState.PENDING"
} ],
"targetStates" : [ {
"rawName" : "OrderState.SHIPPED",
"fullIdentifier" : "OrderState.SHIPPED"
} ],
"event" : {
"rawName" : "OrderEvent.SHIP",
"fullIdentifier" : "OrderEvent.SHIP"
},
"guard" : null,
"actions" : [ ],
"order" : null
} ],
"endStates" : [ "OrderState.SHIPPED" ]
}

View File

@@ -0,0 +1,32 @@
@startuml
!pragma layout smetana
set separator none
hide empty description
hide stereotype
skinparam state {
BackgroundColor white
BorderColor #94a3b8
BorderThickness 1
FontName Inter
FontSize 9
FontStyle bold
RoundCorner 20
Padding 1
}
skinparam shadowing false
skinparam ArrowFontName JetBrains Mono
skinparam ArrowFontSize 8
skinparam ArrowColor #cbd5e1
skinparam ArrowThickness 1
skinparam dpi 110
skinparam svgLinkTarget _self
[*] --> OrderState.NEW
OrderState.NEW -[#1E90FF,bold]-> OrderState.PENDING <<external>> : OrderEvent.PAY / λ
OrderState.PENDING -[#1E90FF,bold]-> OrderState.SHIPPED <<external>> : OrderEvent.SHIP
OrderState.SHIPPED --> [*]
@enduml

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="NEW">
<state id="NEW">
<transition target="PENDING" event="OrderEvent.PAY"/>
</state>
<state id="PENDING">
<transition target="SHIPPED" event="OrderEvent.SHIP"/>
</state>
<state id="SHIPPED">
</state>
</scxml>

View File

@@ -9,7 +9,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "payload", "event" : "payload",
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService", "className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
@@ -19,7 +21,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
}, { }, {
"event" : "event", "event" : "event",
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService", "className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
@@ -29,7 +33,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 21, "lineNumber" : 21,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -191,7 +197,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.PAY" ] "polymorphicEvents" : [ "OrderEvents.PAY" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -222,7 +230,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.FULFILL" ] "polymorphicEvents" : [ "OrderEvents.FULFILL" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -253,7 +263,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.CANCEL" ] "polymorphicEvents" : [ "OrderEvents.CANCEL" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -284,7 +296,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 17, "lineNumber" : 17,
"polymorphicEvents" : [ "OrderEvents.PAY" ] "polymorphicEvents" : [ "OrderEvents.PAY" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -315,7 +329,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.PAY" ] "polymorphicEvents" : [ "OrderEvents.PAY" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -346,7 +362,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.PAY" ] "polymorphicEvents" : [ "OrderEvents.PAY" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -381,7 +399,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.PAY", "OrderEvents.CANCEL" ] "polymorphicEvents" : [ "OrderEvents.PAY", "OrderEvents.CANCEL" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -416,7 +436,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.PAY" ] "polymorphicEvents" : [ "OrderEvents.PAY" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -447,7 +469,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.FULFILL" ] "polymorphicEvents" : [ "OrderEvents.FULFILL" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -478,7 +502,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 13, "lineNumber" : 13,
"polymorphicEvents" : [ "OrderEvents.CANCEL" ] "polymorphicEvents" : [ "OrderEvents.CANCEL" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -509,7 +535,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 21, "lineNumber" : 21,
"polymorphicEvents" : [ "OrderEvents.ABCD" ] "polymorphicEvents" : [ "OrderEvents.ABCD" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -540,7 +568,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 21, "lineNumber" : 21,
"polymorphicEvents" : [ "OrderEvents.ABCD", "OrderEvents.PAY" ] "polymorphicEvents" : [ "OrderEvents.ABCD", "OrderEvents.PAY" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {

View File

@@ -9,7 +9,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : "event instanceof OrderEvent"
}, { }, {
"event" : "eventProvider", "event" : "eventProvider",
"className" : "click.kamil.service.StateMachineServiceImpl", "className" : "click.kamil.service.StateMachineServiceImpl",
@@ -19,7 +21,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 50, "lineNumber" : 50,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : "event != null"
}, { }, {
"event" : "customMessage", "event" : "customMessage",
"className" : "click.kamil.service.StateMachineServiceImpl", "className" : "click.kamil.service.StateMachineServiceImpl",
@@ -29,7 +33,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 78, "lineNumber" : 78,
"polymorphicEvents" : null "polymorphicEvents" : null,
"external" : false,
"constraint" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -144,7 +150,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : [ "OrderEvent.PROCESS" ] "polymorphicEvents" : [ "OrderEvent.PROCESS" ],
"external" : false,
"constraint" : "event instanceof OrderEvent"
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -175,7 +183,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : [ "OrderEvent.COMPLETE" ] "polymorphicEvents" : [ "OrderEvent.COMPLETE" ],
"external" : false,
"constraint" : "event instanceof OrderEvent"
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -206,7 +216,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : [ "OrderEvent.CANCEL" ] "polymorphicEvents" : [ "OrderEvent.CANCEL" ],
"external" : false,
"constraint" : "event instanceof OrderEvent"
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -241,7 +253,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : [ "OrderEvent.COMPLETE" ] "polymorphicEvents" : [ "OrderEvent.COMPLETE" ],
"external" : false,
"constraint" : "event instanceof OrderEvent"
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -276,7 +290,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 78, "lineNumber" : 78,
"polymorphicEvents" : [ "OrderEvent.PROCESS" ] "polymorphicEvents" : [ "OrderEvent.PROCESS" ],
"external" : false,
"constraint" : null
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -311,7 +327,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 25, "lineNumber" : 25,
"polymorphicEvents" : [ "OrderEvent.PROCESS" ] "polymorphicEvents" : [ "OrderEvent.PROCESS" ],
"external" : false,
"constraint" : "event instanceof OrderEvent"
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {
@@ -346,7 +364,9 @@
"stateMachineId" : null, "stateMachineId" : null,
"sourceState" : null, "sourceState" : null,
"lineNumber" : 50, "lineNumber" : 50,
"polymorphicEvents" : [ "OrderEvent.CANCEL" ] "polymorphicEvents" : [ "OrderEvent.CANCEL" ],
"external" : false,
"constraint" : "event != null"
}, },
"contextMachineId" : null, "contextMachineId" : null,
"matchedTransitions" : [ { "matchedTransitions" : [ {

View File

@@ -0,0 +1,12 @@
digraph statemachine {
rankdir=LR;
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
edge [fontname="Arial", fontsize=10];
_start [shape=circle, label="", fillcolor=black, width=0.1];
_start -> GUEST;
VERIFIED [fillcolor=lightgray];
GUEST -> REGISTERED [label="UserEvent.REGISTER", style="solid", color="black"];
REGISTERED -> VERIFIED [label="UserEvent.VERIFY", style="solid", color="black"];
}

View File

@@ -0,0 +1,639 @@
{
"metadata" : {
"triggers" : [ {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 30,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 37,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 44,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 51,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 58,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 65,
"polymorphicEvents" : null
}, {
"event" : "click.kamil.enterprise.machines.user.UserEvent.SUSPEND",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 72,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "ORDER",
"lineNumber" : 81,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "DOCUMENT",
"lineNumber" : 87,
"polymorphicEvents" : null
}, {
"event" : "event",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : "USER",
"lineNumber" : 93,
"polymorphicEvents" : null
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/machine/order/pay",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/order/ship",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/submit",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/approve",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/document/reject",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/user/verify",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/verify",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/user/suspend",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/suspend",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
}, {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/pay",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/pay",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.payOrder", "click.kamil.enterprise.web.StateMachineDispatcher.payOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "payOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 30,
"polymorphicEvents" : [ "OrderEvent.PAY" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/order/ship",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/order/ship",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.shipOrder", "click.kamil.enterprise.web.StateMachineDispatcher.shipOrder" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "shipOrder",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 37,
"polymorphicEvents" : [ "OrderEvent.SHIP" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/submit",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/submit",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.submitDocument", "click.kamil.enterprise.web.StateMachineDispatcher.submitDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "submitDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 44,
"polymorphicEvents" : [ "DocumentEvent.SUBMIT" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/approve",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/approve",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.approveDocument", "click.kamil.enterprise.web.StateMachineDispatcher.approveDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "approveDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 51,
"polymorphicEvents" : [ "DocumentEvent.APPROVE" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/document/reject",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/document/reject",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.rejectDocument", "click.kamil.enterprise.web.StateMachineDispatcher.rejectDocument" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "rejectDocument",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 58,
"polymorphicEvents" : [ "DocumentEvent.REJECT" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/verify",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/verify",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.verifyUser", "click.kamil.enterprise.web.StateMachineDispatcher.verifyUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "verifyUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 65,
"polymorphicEvents" : [ "UserEvent.VERIFY" ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "UserState.REGISTERED",
"targetState" : "UserState.VERIFIED",
"event" : "UserEvent.VERIFY"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/user/suspend",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/user/suspend",
"verb" : "POST"
},
"parameters" : [ {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.suspendUser", "click.kamil.enterprise.web.StateMachineDispatcher.suspendUser" ],
"triggerPoint" : {
"event" : "click.kamil.enterprise.machines.user.UserEvent.SUSPEND",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "suspendUser",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 72,
"polymorphicEvents" : [ "UserEvent.SUSPEND" ]
},
"contextMachineId" : null,
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 81,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "UserState.GUEST",
"targetState" : "UserState.REGISTERED",
"event" : "UserEvent.REGISTER"
}, {
"sourceState" : "UserState.REGISTERED",
"targetState" : "UserState.VERIFIED",
"event" : "UserEvent.VERIFY"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 87,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "UserState.GUEST",
"targetState" : "UserState.REGISTERED",
"event" : "UserEvent.REGISTER"
}, {
"sourceState" : "UserState.REGISTERED",
"targetState" : "UserState.VERIFIED",
"event" : "UserEvent.VERIFY"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/machine/{machineType}/transition/{event}",
"className" : "click.kamil.enterprise.web.StateMachineController",
"methodName" : "transition",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
"metadata" : {
"path" : "/api/machine/{machineType}/transition/{event}",
"verb" : "POST"
},
"parameters" : [ {
"name" : "machineType",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "event",
"type" : "String",
"annotations" : [ "PathVariable" ]
}, {
"name" : "userId",
"type" : "String",
"annotations" : [ "RequestParam" ]
} ]
},
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
"triggerPoint" : {
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
"methodName" : "dispatch",
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 93,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "UserState.GUEST",
"targetState" : "UserState.REGISTERED",
"event" : "UserEvent.REGISTER"
}, {
"sourceState" : "UserState.REGISTERED",
"targetState" : "UserState.VERIFIED",
"event" : "UserEvent.VERIFY"
} ]
} ],
"properties" : {
"default" : { }
}
},
"name" : "click.kamil.enterprise.machines.user.UserStateMachineConfiguration",
"renderChoicesAsDiamonds" : true,
"startStates" : [ "UserState.GUEST" ],
"transitions" : [ {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "UserState.GUEST",
"fullIdentifier" : "UserState.GUEST"
} ],
"targetStates" : [ {
"rawName" : "UserState.REGISTERED",
"fullIdentifier" : "UserState.REGISTERED"
} ],
"event" : {
"rawName" : "UserEvent.REGISTER",
"fullIdentifier" : "UserEvent.REGISTER"
},
"guard" : null,
"actions" : [ ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "UserState.REGISTERED",
"fullIdentifier" : "UserState.REGISTERED"
} ],
"targetStates" : [ {
"rawName" : "UserState.VERIFIED",
"fullIdentifier" : "UserState.VERIFIED"
} ],
"event" : {
"rawName" : "UserEvent.VERIFY",
"fullIdentifier" : "UserEvent.VERIFY"
},
"guard" : null,
"actions" : [ ],
"order" : null
} ],
"endStates" : [ "UserState.VERIFIED" ]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,32 @@
@startuml
!pragma layout smetana
set separator none
hide empty description
hide stereotype
skinparam state {
BackgroundColor white
BorderColor #94a3b8
BorderThickness 1
FontName Inter
FontSize 9
FontStyle bold
RoundCorner 20
Padding 1
}
skinparam shadowing false
skinparam ArrowFontName JetBrains Mono
skinparam ArrowFontSize 8
skinparam ArrowColor #cbd5e1
skinparam ArrowThickness 1
skinparam dpi 110
skinparam svgLinkTarget _self
[*] --> UserState.GUEST
UserState.GUEST -[#1E90FF,bold]-> UserState.REGISTERED <<external>> : UserEvent.REGISTER
UserState.REGISTERED -[#1E90FF,bold]-> UserState.VERIFIED <<external>> : UserEvent.VERIFY
UserState.VERIFIED --> [*]
@enduml

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="GUEST">
<state id="GUEST">
<transition target="REGISTERED" event="UserEvent.REGISTER"/>
</state>
<state id="REGISTERED">
<transition target="VERIFIED" event="UserEvent.VERIFY"/>
</state>
<state id="VERIFIED">
</state>
</scxml>

View File

@@ -0,0 +1,30 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.3'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'click.kamil'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.statemachine:spring-statemachine-starter:3.2.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
bootJar { enabled = false }
jar { enabled = true }

View File

@@ -0,0 +1,11 @@
package click.kamil.enterprise;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EnterpriseStateMachineApplication {
public static void main(String[] args) {
SpringApplication.run(EnterpriseStateMachineApplication.class, args);
}
}

View File

@@ -0,0 +1,50 @@
package click.kamil.enterprise.core;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public abstract class AbstractStateMachine<S extends Enum<S> & EnterpriseState, E extends Enum<E> & EnterpriseEvent> {
private S currentState;
private final Map<E, S> transitions = new ConcurrentHashMap<>();
protected AbstractStateMachine(S initialState) {
this.currentState = initialState;
configureTransitions();
}
public abstract String getMachineType();
protected abstract void configureTransitions();
protected void addTransition(E event, S targetState) {
transitions.put(event, targetState);
}
public synchronized void trigger(E event, TransitionContext<?> context) {
S target = transitions.get(event);
if (target == null) {
throw new IllegalStateException("Invalid event " + event + " for state " + currentState);
}
executePreChecks(currentState, target, context);
S oldState = currentState;
currentState = target;
onTransition(oldState, currentState, event, context);
}
protected void executePreChecks(S from, S to, TransitionContext<?> context) {
// Parent checking logic
System.out.println("[Abstract] Executing pre-checks from " + from.name() + " to " + to.name() + " by user " + context.getUserId());
}
protected abstract void onTransition(S from, S to, E event, TransitionContext<?> context);
public S getCurrentState() {
return currentState;
}
public Class<E> getEventType() {
return (Class<E>) transitions.keySet().iterator().next().getClass();
}
}

View File

@@ -0,0 +1,25 @@
package click.kamil.enterprise.core;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public abstract class AbstractStateMachineConfiguration<S extends Enum<S> & EnterpriseState, E extends Enum<E> & EnterpriseEvent> extends EnumStateMachineConfigurerAdapter<S, E> {
@Override
public void configure(StateMachineStateConfigurer<S, E> states) throws Exception {
configureStates(states);
}
@Override
public void configure(StateMachineTransitionConfigurer<S, E> transitions) throws Exception {
configureTransitions(transitions);
}
protected abstract void configureStates(StateMachineStateConfigurer<S, E> states) throws Exception;
protected abstract void configureTransitions(StateMachineTransitionConfigurer<S, E> transitions) throws Exception;
protected void commonProtectedMethod() {
System.out.println("Common protected method called in AbstractStateMachineConfiguration.");
}
}

View File

@@ -0,0 +1,5 @@
package click.kamil.enterprise.core;
public interface EnterpriseEvent {
String name();
}

View File

@@ -0,0 +1,5 @@
package click.kamil.enterprise.core;
public interface EnterpriseState {
String name();
}

View File

@@ -0,0 +1,17 @@
package click.kamil.enterprise.core;
import lombok.Data;
import java.time.Instant;
@Data
public class TransitionContext<T> {
private final String userId;
private final Instant timestamp;
private final T payload;
public TransitionContext(String userId, T payload) {
this.userId = userId;
this.timestamp = Instant.now();
this.payload = payload;
}
}

View File

@@ -0,0 +1,7 @@
package click.kamil.enterprise.machines.document;
import click.kamil.enterprise.core.EnterpriseEvent;
public enum DocumentEvent implements EnterpriseEvent {
SUBMIT, APPROVE, REJECT;
}

View File

@@ -0,0 +1,7 @@
package click.kamil.enterprise.machines.document;
import click.kamil.enterprise.core.EnterpriseState;
public enum DocumentState implements EnterpriseState {
DRAFT, IN_REVIEW, APPROVED;
}

View File

@@ -0,0 +1,30 @@
package click.kamil.enterprise.machines.document;
import click.kamil.enterprise.core.AbstractStateMachine;
import click.kamil.enterprise.core.TransitionContext;
import org.springframework.stereotype.Component;
@Component
public class DocumentStateMachine extends AbstractStateMachine<DocumentState, DocumentEvent> {
public DocumentStateMachine() {
super(DocumentState.DRAFT);
}
@Override
public String getMachineType() {
return "DOCUMENT";
}
@Override
protected void configureTransitions() {
addTransition(DocumentEvent.SUBMIT, DocumentState.IN_REVIEW);
addTransition(DocumentEvent.APPROVE, DocumentState.APPROVED);
addTransition(DocumentEvent.REJECT, DocumentState.DRAFT);
}
@Override
protected void onTransition(DocumentState from, DocumentState to, DocumentEvent event, TransitionContext<?> context) {
System.out.println("[Document] Document modified by " + context.getUserId() + ". Now " + to);
}
}

View File

@@ -0,0 +1,35 @@
package click.kamil.enterprise.machines.document;
import click.kamil.enterprise.core.AbstractStateMachineConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
@Configuration
@EnableStateMachineFactory(name = "documentStateMachineFactory")
public class DocumentStateMachineConfiguration extends AbstractStateMachineConfiguration<DocumentState, DocumentEvent> {
@Override
protected void configureStates(StateMachineStateConfigurer<DocumentState, DocumentEvent> states) throws Exception {
states
.withStates()
.initial(DocumentState.DRAFT)
.state(DocumentState.IN_REVIEW)
.end(DocumentState.APPROVED);
}
@Override
protected void configureTransitions(StateMachineTransitionConfigurer<DocumentState, DocumentEvent> transitions) throws Exception {
commonProtectedMethod();
transitions
.withExternal()
.source(DocumentState.DRAFT).target(DocumentState.IN_REVIEW).event(DocumentEvent.SUBMIT)
.and()
.withExternal()
.source(DocumentState.IN_REVIEW).target(DocumentState.APPROVED).event(DocumentEvent.APPROVE)
.and()
.withExternal()
.source(DocumentState.IN_REVIEW).target(DocumentState.DRAFT).event(DocumentEvent.REJECT);
}
}

View File

@@ -0,0 +1,7 @@
package click.kamil.enterprise.machines.order;
import click.kamil.enterprise.core.EnterpriseEvent;
public enum OrderEvent implements EnterpriseEvent {
PAY, SHIP;
}

View File

@@ -0,0 +1,7 @@
package click.kamil.enterprise.machines.order;
import click.kamil.enterprise.core.EnterpriseState;
public enum OrderState implements EnterpriseState {
NEW, PENDING, SHIPPED;
}

View File

@@ -0,0 +1,38 @@
package click.kamil.enterprise.machines.order;
import click.kamil.enterprise.core.AbstractStateMachine;
import click.kamil.enterprise.core.TransitionContext;
import org.springframework.stereotype.Component;
@Component
public class OrderStateMachine extends AbstractStateMachine<OrderState, OrderEvent> {
public OrderStateMachine() {
super(OrderState.NEW);
}
@Override
public String getMachineType() {
return "ORDER";
}
@Override
protected void configureTransitions() {
addTransition(OrderEvent.PAY, OrderState.PENDING);
addTransition(OrderEvent.SHIP, OrderState.SHIPPED);
}
@Override
protected void executePreChecks(OrderState from, OrderState to, TransitionContext<?> context) {
super.executePreChecks(from, to, context);
if (to == OrderState.SHIPPED && from != OrderState.PENDING) {
throw new IllegalStateException("Must be PENDING to ship!");
}
System.out.println("[Order] Specific pre-checks passed for order.");
}
@Override
protected void onTransition(OrderState from, OrderState to, OrderEvent event, TransitionContext<?> context) {
System.out.println("[Order] Transitioned from " + from + " to " + to + " via " + event);
}
}

View File

@@ -0,0 +1,33 @@
package click.kamil.enterprise.machines.order;
import click.kamil.enterprise.core.AbstractStateMachineConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
@Configuration
@EnableStateMachineFactory(name = "orderStateMachineFactory")
public class OrderStateMachineConfiguration extends AbstractStateMachineConfiguration<OrderState, OrderEvent> {
@Override
protected void configureStates(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderState.NEW)
.state(OrderState.PENDING)
.end(OrderState.SHIPPED);
}
@Override
protected void configureTransitions(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
commonProtectedMethod();
transitions
.withExternal()
.source(OrderState.NEW).target(OrderState.PENDING).event(OrderEvent.PAY)
.action(context -> System.out.println("Payment processed."))
.and()
.withExternal()
.source(OrderState.PENDING).target(OrderState.SHIPPED).event(OrderEvent.SHIP);
}
}

View File

@@ -0,0 +1,7 @@
package click.kamil.enterprise.machines.user;
import click.kamil.enterprise.core.EnterpriseEvent;
public enum UserEvent implements EnterpriseEvent {
REGISTER, VERIFY, SUSPEND;
}

View File

@@ -0,0 +1,7 @@
package click.kamil.enterprise.machines.user;
import click.kamil.enterprise.core.EnterpriseState;
public enum UserState implements EnterpriseState {
GUEST, REGISTERED, VERIFIED;
}

View File

@@ -0,0 +1,29 @@
package click.kamil.enterprise.machines.user;
import click.kamil.enterprise.core.AbstractStateMachine;
import click.kamil.enterprise.core.TransitionContext;
import org.springframework.stereotype.Component;
@Component
public class UserStateMachine extends AbstractStateMachine<UserState, UserEvent> {
public UserStateMachine() {
super(UserState.GUEST);
}
@Override
public String getMachineType() {
return "USER";
}
@Override
protected void configureTransitions() {
addTransition(UserEvent.REGISTER, UserState.REGISTERED);
addTransition(UserEvent.VERIFY, UserState.VERIFIED);
}
@Override
protected void onTransition(UserState from, UserState to, UserEvent event, TransitionContext<?> context) {
System.out.println("[User] " + context.getUserId() + " is now " + to);
}
}

View File

@@ -0,0 +1,32 @@
package click.kamil.enterprise.machines.user;
import click.kamil.enterprise.core.AbstractStateMachineConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
@Configuration
@EnableStateMachineFactory(name = "userStateMachineFactory")
public class UserStateMachineConfiguration extends AbstractStateMachineConfiguration<UserState, UserEvent> {
@Override
protected void configureStates(StateMachineStateConfigurer<UserState, UserEvent> states) throws Exception {
states
.withStates()
.initial(UserState.GUEST)
.state(UserState.REGISTERED)
.end(UserState.VERIFIED);
}
@Override
protected void configureTransitions(StateMachineTransitionConfigurer<UserState, UserEvent> transitions) throws Exception {
commonProtectedMethod();
transitions
.withExternal()
.source(UserState.GUEST).target(UserState.REGISTERED).event(UserEvent.REGISTER)
.and()
.withExternal()
.source(UserState.REGISTERED).target(UserState.VERIFIED).event(UserEvent.VERIFY);
}
}

View File

@@ -0,0 +1,65 @@
package click.kamil.enterprise.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/machine")
public class StateMachineController {
private final StateMachineDispatcher dispatcher;
@Autowired
public StateMachineController(StateMachineDispatcher dispatcher) {
this.dispatcher = dispatcher;
}
@PostMapping("/order/pay")
public ResponseEntity<String> payOrder(@RequestParam(defaultValue = "system-user") String userId) {
return ResponseEntity.ok(dispatcher.payOrder(userId));
}
@PostMapping("/order/ship")
public ResponseEntity<String> shipOrder(@RequestParam(defaultValue = "system-user") String userId) {
return ResponseEntity.ok(dispatcher.shipOrder(userId));
}
@PostMapping("/document/submit")
public ResponseEntity<String> submitDocument(@RequestParam(defaultValue = "system-user") String userId) {
return ResponseEntity.ok(dispatcher.submitDocument(userId));
}
@PostMapping("/document/approve")
public ResponseEntity<String> approveDocument(@RequestParam(defaultValue = "system-user") String userId) {
return ResponseEntity.ok(dispatcher.approveDocument(userId));
}
@PostMapping("/document/reject")
public ResponseEntity<String> rejectDocument(@RequestParam(defaultValue = "system-user") String userId) {
return ResponseEntity.ok(dispatcher.rejectDocument(userId));
}
@PostMapping("/user/verify")
public ResponseEntity<String> verifyUser(@RequestParam(defaultValue = "system-user") String userId) {
return ResponseEntity.ok(dispatcher.verifyUser(userId));
}
@PostMapping("/user/suspend")
public ResponseEntity<String> suspendUser(@RequestParam(defaultValue = "system-user") String userId) {
return ResponseEntity.ok(dispatcher.suspendUser(userId));
}
@PostMapping("/{machineType}/transition/{event}")
public ResponseEntity<String> transition(
@PathVariable String machineType,
@PathVariable String event,
@RequestParam(defaultValue = "system-user") String userId) {
try {
String result = dispatcher.dispatch(machineType, event);
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}

View File

@@ -0,0 +1,98 @@
package click.kamil.enterprise.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.stereotype.Service;
import click.kamil.enterprise.machines.order.OrderEvent;
import click.kamil.enterprise.machines.order.OrderState;
import click.kamil.enterprise.machines.document.DocumentEvent;
import click.kamil.enterprise.machines.document.DocumentState;
import click.kamil.enterprise.machines.user.UserEvent;
import click.kamil.enterprise.machines.user.UserState;
@Service
public class StateMachineDispatcher {
@Autowired
private StateMachineFactory<OrderState, OrderEvent> orderStateMachineFactory;
@Autowired
private StateMachineFactory<DocumentState, DocumentEvent> documentStateMachineFactory;
@Autowired
private StateMachineFactory<UserState, UserEvent> userStateMachineFactory;
public String payOrder(String userId) {
StateMachine<OrderState, OrderEvent> machine = orderStateMachineFactory.getStateMachine();
machine.start();
machine.sendEvent(OrderEvent.PAY);
return "Order paid by " + userId;
}
public String shipOrder(String userId) {
StateMachine<OrderState, OrderEvent> machine = orderStateMachineFactory.getStateMachine();
machine.start();
machine.sendEvent(OrderEvent.SHIP);
return "Order shipped by " + userId;
}
public String submitDocument(String userId) {
StateMachine<DocumentState, DocumentEvent> machine = documentStateMachineFactory.getStateMachine();
machine.start();
machine.sendEvent(DocumentEvent.SUBMIT);
return "Document submitted by " + userId;
}
public String approveDocument(String userId) {
StateMachine<DocumentState, DocumentEvent> machine = documentStateMachineFactory.getStateMachine();
machine.start();
machine.sendEvent(DocumentEvent.APPROVE);
return "Document approved by " + userId;
}
public String rejectDocument(String userId) {
StateMachine<DocumentState, DocumentEvent> machine = documentStateMachineFactory.getStateMachine();
machine.start();
machine.sendEvent(DocumentEvent.REJECT);
return "Document rejected by " + userId;
}
public String verifyUser(String userId) {
StateMachine<UserState, UserEvent> machine = userStateMachineFactory.getStateMachine();
machine.start();
machine.sendEvent(UserEvent.VERIFY);
return "User verified: " + userId;
}
public String suspendUser(String userId) {
StateMachine<UserState, UserEvent> machine = userStateMachineFactory.getStateMachine();
machine.start();
machine.sendEvent(UserEvent.SUSPEND);
return "User suspended: " + userId;
}
public String dispatch(String machineType, String eventString) {
if ("ORDER".equalsIgnoreCase(machineType)) {
StateMachine<OrderState, OrderEvent> machine = orderStateMachineFactory.getStateMachine();
machine.start();
OrderEvent event = OrderEvent.valueOf(eventString.toUpperCase());
machine.sendEvent(event);
return "Triggered " + event;
} else if ("DOCUMENT".equalsIgnoreCase(machineType)) {
StateMachine<DocumentState, DocumentEvent> machine = documentStateMachineFactory.getStateMachine();
machine.start();
DocumentEvent event = DocumentEvent.valueOf(eventString.toUpperCase());
machine.sendEvent(event);
return "Triggered " + event;
} else if ("USER".equalsIgnoreCase(machineType)) {
StateMachine<UserState, UserEvent> machine = userStateMachineFactory.getStateMachine();
machine.start();
UserEvent event = UserEvent.valueOf(eventString.toUpperCase());
machine.sendEvent(event);
return "Triggered " + event;
}
throw new IllegalArgumentException("Unknown machine type: " + machineType);
}
}