From db58c55b840dcd8a4fbca8f5ba82bb1cd03ff7a1 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sat, 20 Jun 2026 16:08:04 +0200 Subject: [PATCH] attempt to adding spring bean registry --- .../enricher/TransitionLinkerEnricher.java | 37 +- .../analysis/model/TriggerPoint.java | 2 + .../analysis/resolver/ConstantResolver.java | 32 ++ .../analysis/service/CallGraphBuilder.java | 138 +++++- .../service/GenericEventDetector.java | 174 +++++++ .../service/JdtIntelligenceProvider.java | 15 +- .../analysis/spring/SpringBean.java | 43 ++ .../analysis/spring/SpringBeanRegistry.java | 256 +++++++++++ .../spring/SpringDependencyResolver.java | 78 ++++ .../ast/common/CodebaseContext.java | 4 + .../service/ExportService.java | 12 + .../EnterpriseStateMachineConfig.json | 36 +- .../ExtendedStateMachineConfig.json | 426 +++++++++++++++++- .../ExtendedStateMachineConfig.json | 426 +++++++++++++++++- .../InheritanceStateMachineConfig.json | 6 +- .../MavenOrderStateMachine.json | 9 +- .../OrderStateMachineConfig.json | 6 +- .../StateMachineConfig.json | 30 +- .../config/PaymentStateMachineConfig.java | 58 +++ .../service/FallbackQuirkService.java | 19 + .../extended/service/NamedQuirkService.java | 19 + .../extended/service/PaymentService.java | 6 + .../extended/service/PaymentServiceImpl.java | 37 ++ .../extended/service/PrimaryQuirkService.java | 21 + .../service/ProfiledQuirkService.java | 21 + .../service/QualifierQuirkService.java | 19 + .../extended/service/QuirkService.java | 5 + .../extended/web/BaseController.java | 10 + .../extended/web/PaymentController.java | 28 ++ .../extended/web/QuirkController.java | 45 ++ 30 files changed, 1914 insertions(+), 104 deletions(-) create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBean.java create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBeanRegistry.java create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolver.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/PaymentStateMachineConfig.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentService.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QuirkService.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/BaseController.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java index 1163428..40c7ff5 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java @@ -125,28 +125,27 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) { - // First, check explicit variable targeting (like contextMachineId) - String targetVar = chain.getContextMachineId(); - if (targetVar == null && chain.getTriggerPoint() != null) { - targetVar = chain.getTriggerPoint().getStateMachineId(); - } + String beanId = chain.getTriggerPoint() != null ? chain.getTriggerPoint().getStateMachineId() : null; - String simplifiedMachineName = currentMachineName != null ? currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase() : ""; - - if (targetVar != null && !targetVar.isEmpty()) { - targetVar = targetVar.toLowerCase(); - // E.g., if target is "myStateMachine", ensure current machine name contains "my" - // If the variable name ends with StateMachine, we extract the prefix - if (targetVar.endsWith("statemachine")) { - String prefix = targetVar.substring(0, targetVar.length() - "statemachine".length()); - if (!prefix.isEmpty() && !simplifiedMachineName.contains(prefix)) { - return false; - } + if (beanId != null && !beanId.isEmpty() && currentMachineName != null) { + // currentMachineName format: FQN (Entry Point) or FQN#beanMethodName + if (currentMachineName.equals(beanId) || currentMachineName.endsWith("#" + beanId)) { + return true; // Exact match! + } + + String simplifiedMachineName = currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase(); + String simplifiedBeanId = beanId.substring(beanId.lastIndexOf('.') + 1).toLowerCase(); + + // If bean was explicitly identified but doesn't match the current machine, try to reject it + if (!simplifiedMachineName.contains(simplifiedBeanId) && !simplifiedBeanId.contains(simplifiedMachineName)) { + String beanPrefix = getFirstCamelCaseWord(beanId.substring(beanId.lastIndexOf('.') + 1)); + String machinePrefix = getFirstCamelCaseWord(currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1)); + if (beanPrefix != null && machinePrefix != null && !beanPrefix.equalsIgnoreCase(machinePrefix)) { + // e.g. PaymentStateMachineFactory vs OrderStateMachineConfig + return false; + } } } - - - // Generic approach combining Prefix, Package, and String-distance heuristics // without relying on a hardcoded list of magic keywords. diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java index ea2ae00..a14e571 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java @@ -21,4 +21,6 @@ public class TriggerPoint { private final String sourceState; // Optional: if we can determine the expected current state private final int lineNumber; private final java.util.List polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism + @Builder.Default + private final Map payloadTypes = new java.util.HashMap<>(); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java index d11135a..e19ab80 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java @@ -180,12 +180,44 @@ public class ConstantResolver { } } } + + // Fallback: Check constructors for assignments + for (MethodDeclaration md : td.getMethods()) { + if (md.isConstructor() && md.getBody() != null) { + String val = findAssignmentInBlock(md.getBody(), fieldName, context, visited); + if (val != null) return val; + } + } } finally { visited.remove(fieldId); } return null; } + private String findAssignmentInBlock(Block block, String fieldName, CodebaseContext context, Set visited) { + if (block == null) return null; + for (Object stmtObj : block.statements()) { + if (stmtObj instanceof ExpressionStatement exprStmt) { + if (exprStmt.getExpression() instanceof Assignment assignment) { + Expression left = assignment.getLeftHandSide(); + boolean match = false; + if (left instanceof SimpleName sn && sn.getIdentifier().equals(fieldName)) { + match = true; + } else if (left instanceof FieldAccess fa && fa.getName().getIdentifier().equals(fieldName)) { + if (fa.getExpression() instanceof ThisExpression) { + match = true; + } + } + + if (match) { + return resolveInternal(assignment.getRightHandSide(), context, visited); + } + } + } + } + return null; + } + private String findValueFromAnnotation(FieldDeclaration fd) { for (Object mod : fd.modifiers()) { if (mod instanceof Annotation ann) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java index 9f99830..aa444b4 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java @@ -13,6 +13,7 @@ import java.util.*; @Slf4j public class CallGraphBuilder { private final CodebaseContext context; + private final click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver springDependencyResolver; private final ConstantResolver constantResolver; private String currentMethodFqn; private Map> graph; @@ -24,7 +25,12 @@ public class CallGraphBuilder { } public CallGraphBuilder(CodebaseContext context) { + this(context, new click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver(context, new click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry(context))); + } + + public CallGraphBuilder(CodebaseContext context, click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver springDependencyResolver) { this.context = context; + this.springDependencyResolver = springDependencyResolver; this.constantResolver = new ConstantResolver(); } @@ -192,7 +198,16 @@ public class CallGraphBuilder { typesToInspect.add(declaredType); } else { typesToInspect.add(declaredType); - typesToInspect.addAll(context.getImplementations(declaredType)); + List resolvedBeans = springDependencyResolver.resolve(declaredType, null, varName); + if (resolvedBeans.isEmpty()) { + typesToInspect.addAll(context.getImplementations(declaredType)); + } else { + for (click.kamil.springstatemachineexporter.analysis.spring.SpringBean bean : resolvedBeans) { + if (!bean.getTypeFqn().equals(declaredType)) { + typesToInspect.add(bean.getTypeFqn()); + } + } + } } System.out.println("DEEP TRACE IMPLS: " + typesToInspect); @@ -540,9 +555,23 @@ public class CallGraphBuilder { String calledMethod = fallbackTypeFqn + "." + emr.getName().getIdentifier(); graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args)); - List impls = context.getImplementations(fallbackTypeFqn); - for (String impl : impls) { - graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args)); + String receiverName = null; + if (emr.getExpression() instanceof SimpleName sn) { + receiverName = sn.getIdentifier(); + } + + List resolvedBeans = springDependencyResolver.resolve(fallbackTypeFqn, null, receiverName); + if (resolvedBeans.isEmpty()) { + List impls = context.getImplementations(fallbackTypeFqn); + for (String impl : impls) { + graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args)); + } + } else { + for (click.kamil.springstatemachineexporter.analysis.spring.SpringBean bean : resolvedBeans) { + if (!bean.getTypeFqn().equals(fallbackTypeFqn)) { + graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(bean.getTypeFqn() + "." + emr.getName().getIdentifier(), args)); + } + } } } } @@ -649,21 +678,91 @@ public class CallGraphBuilder { if (baseCalled == null) return Collections.emptyList(); List allResolved = new ArrayList<>(); - allResolved.add(baseCalled); if (baseCalled.contains(".")) { String className = baseCalled.substring(0, baseCalled.lastIndexOf('.')); String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1); - - List impls = context.getImplementations(className); - for (String impl : impls) { - allResolved.add(impl + "." + methodName); + String receiverName = null; + if (node.getExpression() instanceof SimpleName sn) { + receiverName = sn.getIdentifier(); + } else if (node.getExpression() instanceof FieldAccess fa) { + receiverName = fa.getName().getIdentifier(); } + + String qualifier = extractQualifierFromVariable(node, receiverName); + + List resolvedBeans = springDependencyResolver.resolve(className, qualifier, receiverName); + + if (resolvedBeans.isEmpty()) { + allResolved.add(baseCalled); + List impls = context.getImplementations(className); + for (String impl : impls) { + allResolved.add(impl + "." + methodName); + } + } else { + for (click.kamil.springstatemachineexporter.analysis.spring.SpringBean bean : resolvedBeans) { + allResolved.add(bean.getTypeFqn() + "." + methodName); + } + } + } else { + allResolved.add(baseCalled); } return allResolved; } + private String extractQualifierFromVariable(ASTNode node, String varName) { + if (varName == null) return null; + + MethodDeclaration md = findEnclosingMethod(node); + if (md != null) { + for (Object paramObj : md.parameters()) { + if (paramObj instanceof SingleVariableDeclaration svd) { + if (svd.getName().getIdentifier().equals(varName)) { + return extractQualifierFromModifiers(svd.modifiers()); + } + } + } + } + + TypeDeclaration td = findEnclosingType(node); + if (td != null) { + for (FieldDeclaration fd : td.getFields()) { + for (Object fragObj : fd.fragments()) { + if (fragObj instanceof VariableDeclarationFragment vdf) { + if (vdf.getName().getIdentifier().equals(varName)) { + return extractQualifierFromModifiers(fd.modifiers()); + } + } + } + } + } + return null; + } + + private String extractQualifierFromModifiers(List modifiers) { + for (Object modObj : modifiers) { + if (modObj instanceof NormalAnnotation na) { + if ("Qualifier".equals(na.getTypeName().getFullyQualifiedName()) || "org.springframework.beans.factory.annotation.Qualifier".equals(na.getTypeName().getFullyQualifiedName())) { + for (Object valObj : na.values()) { + if (valObj instanceof MemberValuePair mvp) { + if ("value".equals(mvp.getName().getIdentifier()) && mvp.getValue() instanceof StringLiteral sl) { + return sl.getLiteralValue(); + } + } + } + } + } else if (modObj instanceof SingleMemberAnnotation sma) { + if ("Qualifier".equals(sma.getTypeName().getFullyQualifiedName()) || "org.springframework.beans.factory.annotation.Qualifier".equals(sma.getTypeName().getFullyQualifiedName())) { + if (sma.getValue() instanceof StringLiteral sl) { + return sl.getLiteralValue(); + } + } + } + } + return null; + } + private String resolveCalledMethod(MethodInvocation node) { Expression receiver = node.getExpression(); String methodName = node.getName().getIdentifier(); @@ -818,10 +917,25 @@ public class CallGraphBuilder { } private boolean isHeuristicMatch(String neighbor, String target) { + if (neighbor.equals(target)) return true; if (target.endsWith("." + neighbor)) return true; - String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target; - String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor; - return simpleNeighbor.equals(simpleTarget); + + int nIdx = neighbor.lastIndexOf('.'); + int tIdx = target.lastIndexOf('.'); + if (nIdx > 0 && tIdx > 0) { + String nClass = neighbor.substring(0, nIdx); + String nMethod = neighbor.substring(nIdx + 1); + String tClass = target.substring(0, tIdx); + String tMethod = target.substring(tIdx + 1); + + if (nMethod.equals(tMethod)) { + List impls = context.getImplementations(nClass); + if (impls.contains(tClass)) { + return true; + } + } + } + return false; } private TypeDeclaration findEnclosingType(ASTNode node) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java index baec1fe..57aa220 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java @@ -18,6 +18,11 @@ public class GenericEventDetector { private final CodebaseContext context; private final ConstantResolver constantResolver; private final List hints; + private final click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver springDependencyResolver; + + public GenericEventDetector(CodebaseContext context, ConstantResolver constantResolver, List hints) { + this(context, constantResolver, hints, new click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver(context, new click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry(context))); + } public List detect(CompilationUnit cu) { List triggers = new ArrayList<>(); @@ -142,6 +147,28 @@ public class GenericEventDetector { String sourceState = extractSourceState(node); + String stateMachineId = null; + if (node.getExpression() instanceof SimpleName sn) { + String varName = sn.getIdentifier(); + String className = resolveReceiverTypeFallback(sn, cu); + if (className != null && springDependencyResolver != null) { + List beans = springDependencyResolver.resolve(className, null, varName); + if (beans.size() == 1) { + if (!beans.get(0).getBeanNames().isEmpty()) { + stateMachineId = beans.get(0).getBeanNames().iterator().next(); + } else { + stateMachineId = beans.get(0).getTypeFqn(); + } + } + } + } + + java.util.Map payloadTypes = java.util.Collections.emptyMap(); + if (forcedEvent == null && !node.arguments().isEmpty()) { + Expression eventExpr = (Expression) node.arguments().get(0); + payloadTypes = extractPayloadsFromMessageBuilder(eventExpr, cu); + } + List results = new ArrayList<>(); if (eventValue != null && eventValue.startsWith("ENUM_SET:")) { String[] parts = eventValue.substring(9).split(","); @@ -150,10 +177,12 @@ public class GenericEventDetector { results.add(TriggerPoint.builder() .event(part.trim()) .sourceState(sourceState) + .stateMachineId(stateMachineId) .className(context.getFqn(type)) .methodName(method != null ? method.getName().getIdentifier() : "initializer") .sourceFile(context.getRelativePath(context.getFqn(type))) .lineNumber(cu.getLineNumber(node.getStartPosition())) + .payloadTypes(payloadTypes) .build()); } } @@ -161,10 +190,12 @@ public class GenericEventDetector { results.add(TriggerPoint.builder() .event(eventValue) .sourceState(sourceState) + .stateMachineId(stateMachineId) .className(context.getFqn(type)) .methodName(method != null ? method.getName().getIdentifier() : "initializer") .sourceFile(context.getRelativePath(context.getFqn(type))) .lineNumber(cu.getLineNumber(node.getStartPosition())) + .payloadTypes(payloadTypes) .build()); } @@ -226,6 +257,78 @@ public class GenericEventDetector { return expr.toString(); } + private java.util.Map extractPayloadsFromMessageBuilder(Expression expr, CompilationUnit cu) { + if (expr instanceof CastExpression ce) { + return extractPayloadsFromMessageBuilder(ce.getExpression(), cu); + } + + if (expr instanceof SimpleName sn) { + String varName = sn.getIdentifier(); + MethodDeclaration enclosingMethod = findEnclosingMethod(expr); + if (enclosingMethod != null) { + final Expression[] initializer = new Expression[1]; + enclosingMethod.accept(new ASTVisitor() { + @Override + public boolean visit(VariableDeclarationFragment node) { + if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { + initializer[0] = node.getInitializer(); + } + return super.visit(node); + } + @Override + public boolean visit(Assignment node) { + if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { + initializer[0] = node.getRightHandSide(); + } + return super.visit(node); + } + }); + if (initializer[0] != null) { + return extractPayloadsFromMessageBuilder(initializer[0], cu); + } + } + } + + if (!(expr instanceof MethodInvocation mi)) return java.util.Collections.emptyMap(); + + java.util.Map payloads = new java.util.HashMap<>(); + MethodInvocation current = mi; + while (current != null) { + String name = current.getName().getIdentifier(); + if ("setHeader".equals(name) && current.arguments().size() == 2) { + Expression keyExpr = (Expression) current.arguments().get(0); + Expression valueExpr = (Expression) current.arguments().get(1); + + String key = constantResolver.resolve(keyExpr, context); + if (key == null) { + if (keyExpr instanceof StringLiteral sl) key = sl.getLiteralValue(); + else key = keyExpr.toString(); + } + + String type = null; + if (valueExpr instanceof SimpleName vSn) { + type = resolveReceiverTypeFallback(vSn, cu); + } + if (type == null) type = "Object"; + + // Keep simple type name if possible + if (type.contains(".")) { + type = type.substring(type.lastIndexOf('.') + 1); + } + + payloads.put(key, type); + } + + Expression receiver = current.getExpression(); + if (receiver instanceof MethodInvocation nextMi) { + current = nextMi; + } else { + current = null; + } + } + return payloads; + } + private String extractEventFromMessageBuilder(Expression expr) { if (expr instanceof CastExpression ce) { return extractEventFromMessageBuilder(ce.getExpression()); @@ -316,6 +419,77 @@ public class GenericEventDetector { return null; } + private String resolveReceiverTypeFallback(SimpleName receiverNameNode, CompilationUnit cu) { + String varName = receiverNameNode.getIdentifier(); + + MethodDeclaration enclosingMethod = findEnclosingMethod(receiverNameNode); + if (enclosingMethod != null) { + for (Object paramObj : enclosingMethod.parameters()) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) paramObj; + if (svd.getName().getIdentifier().equals(varName)) { + return resolveTypeToFqn(svd.getType(), cu); + } + } + if (enclosingMethod.getBody() != null) { + Type[] foundType = new Type[1]; + enclosingMethod.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(VariableDeclarationStatement node) { + for (Object fragObj : node.fragments()) { + VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj; + if (frag.getName().getIdentifier().equals(varName)) { + foundType[0] = node.getType(); + } + } + return super.visit(node); + } + }); + if (foundType[0] != null) { + return resolveTypeToFqn(foundType[0], cu); + } + } + } + + TypeDeclaration enclosingType = findEnclosingType(receiverNameNode); + if (enclosingType != null) { + for (FieldDeclaration field : enclosingType.getFields()) { + for (Object fragObj : field.fragments()) { + VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj; + if (frag.getName().getIdentifier().equals(varName)) { + return resolveTypeToFqn(field.getType(), cu); + } + } + } + } + return null; + } + + private String resolveTypeToFqn(Type type, CompilationUnit cu) { + if (type == null) return null; + String simpleName; + if (type.isSimpleType()) { + simpleName = ((SimpleType) type).getName().getFullyQualifiedName(); + } else if (type.isParameterizedType()) { + return resolveTypeToFqn(((ParameterizedType) type).getType(), cu); + } else { + simpleName = type.toString(); + } + + TypeDeclaration td = context.getTypeDeclaration(simpleName, cu); + if (td != null) { + return context.getFqn(td); + } + + for (Object impObj : cu.imports()) { + ImportDeclaration imp = (ImportDeclaration) impObj; + String impName = imp.getName().getFullyQualifiedName(); + if (impName.endsWith("." + simpleName)) { + return impName; + } + } + return simpleName; + } + private MethodDeclaration findEnclosingMethod(ASTNode node) { ASTNode parent = node.getParent(); while (parent != null && !(parent instanceof MethodDeclaration)) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java index d1b4fc4..7315b0f 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java @@ -8,6 +8,9 @@ import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import lombok.extern.slf4j.Slf4j; import org.eclipse.jdt.core.dom.CompilationUnit; +import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry; +import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver; + import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -25,14 +28,22 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider { private final LifecycleDetector lifecycleDetector; private final InterceptorDetector interceptorDetector; private final SpringComponentDetector componentDetector; + private final SpringBeanRegistry springBeanRegistry; + private final SpringDependencyResolver springDependencyResolver; public JdtIntelligenceProvider(CodebaseContext context, Path rootDir) { this.context = context; this.rootDir = rootDir; - this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints()); + + // Initialize Spring Bean Context early + this.springBeanRegistry = new click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry(context); + this.springBeanRegistry.discoverBeans(); + this.springDependencyResolver = new click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver(context, springBeanRegistry); + + this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints(), springDependencyResolver); this.mvcDetector = new SpringMvcDetector(context, constantResolver); this.messagingDetector = new MessagingDetector(context); - this.callGraphBuilder = new CallGraphBuilder(context); + this.callGraphBuilder = new CallGraphBuilder(context, springDependencyResolver); this.lifecycleDetector = new LifecycleDetector(context); this.interceptorDetector = new InterceptorDetector(context); this.componentDetector = new SpringComponentDetector(context); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBean.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBean.java new file mode 100644 index 0000000..0f94893 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBean.java @@ -0,0 +1,43 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import java.util.HashSet; +import java.util.Set; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class SpringBean { + /** + * The fully qualified name of the bean's actual type. + */ + private String typeFqn; + + /** + * The names under which this bean is registered (could be multiple if aliases exist). + */ + @Builder.Default + private Set beanNames = new HashSet<>(); + + /** + * The qualifiers associated with this bean. + */ + @Builder.Default + private Set qualifiers = new HashSet<>(); + + /** + * True if the bean is annotated with @Primary. + */ + private boolean isPrimary; + + /** + * The FQN of the class where this bean is declared (e.g. the @Configuration class). + */ + private String declaringClassFqn; + + /** + * Factory method name if the bean was created via @Bean. Null if it's a component. + */ + private String factoryMethodName; +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBeanRegistry.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBeanRegistry.java new file mode 100644 index 0000000..3140ee1 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBeanRegistry.java @@ -0,0 +1,256 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import click.kamil.springstatemachineexporter.ast.common.AstUtils; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import org.eclipse.jdt.core.dom.*; + +import java.util.*; + +public class SpringBeanRegistry { + private final CodebaseContext context; + private final List beans = new ArrayList<>(); + + private static final Set STEREOTYPES = Set.of( + "Component", "Service", "Repository", "Controller", "RestController", "Configuration" + ); + + public SpringBeanRegistry(CodebaseContext context) { + this.context = context; + } + + public void discoverBeans() { + for (TypeDeclaration td : context.getTypeDeclarations()) { + if (td.isInterface()) continue; // Interfaces themselves aren't beans unless they have @Bean methods, but let's stick to classes for now. + + // 1. Check if the class itself is a bean (Stereotype annotations) + if (isStereotypeAnnotated(td)) { + registerClassBean(td); + } + + // 2. Check for @Bean methods inside the class + for (MethodDeclaration md : td.getMethods()) { + if (hasBeanAnnotation(md)) { + registerMethodBean(td, md); + } + } + } + } + + private void registerClassBean(TypeDeclaration td) { + String fqn = context.getFqn(td); + if (fqn == null) return; + + Set beanNames = new HashSet<>(); + Set qualifiers = new HashSet<>(); + boolean isPrimary = false; + + String defaultName = getDefaultBeanName(td.getName().getIdentifier()); + + // Extract names and qualifiers from annotations + for (Object modObj : td.modifiers()) { + if (modObj instanceof Annotation ann) { + String annName = ann.getTypeName().getFullyQualifiedName(); + String simpleName = getSimpleName(annName); + + if (STEREOTYPES.contains(simpleName)) { + String value = extractValue(ann); + if (value != null && !value.isEmpty()) { + beanNames.add(value); + } + } else if ("Qualifier".equals(simpleName)) { + String value = extractValue(ann); + if (value != null && !value.isEmpty()) { + qualifiers.add(value); + } + } else if ("Primary".equals(simpleName)) { + isPrimary = true; + } + } + } + + if (beanNames.isEmpty()) { + beanNames.add(defaultName); + } + + beans.add(SpringBean.builder() + .typeFqn(fqn) + .beanNames(beanNames) + .qualifiers(qualifiers) + .isPrimary(isPrimary) + .declaringClassFqn(fqn) + .build()); + } + + private void registerMethodBean(TypeDeclaration td, MethodDeclaration md) { + Type returnType = md.getReturnType2(); + if (returnType == null) return; + + String declaredTypeFqn = resolveTypeToFqn(returnType, td); + if (declaredTypeFqn == null) return; + + Set beanNames = new HashSet<>(); + Set qualifiers = new HashSet<>(); + boolean isPrimary = false; + + String defaultName = md.getName().getIdentifier(); + + for (Object modObj : md.modifiers()) { + if (modObj instanceof Annotation ann) { + String annName = ann.getTypeName().getFullyQualifiedName(); + String simpleName = getSimpleName(annName); + + if ("Bean".equals(simpleName)) { + List values = extractArrayValues(ann); + if (!values.isEmpty()) { + beanNames.addAll(values); + } + } else if ("Qualifier".equals(simpleName)) { + String value = extractValue(ann); + if (value != null && !value.isEmpty()) { + qualifiers.add(value); + } + } else if ("Primary".equals(simpleName)) { + isPrimary = true; + } + } + } + + if (beanNames.isEmpty()) { + beanNames.add(defaultName); + } + + beans.add(SpringBean.builder() + .typeFqn(declaredTypeFqn) + .beanNames(beanNames) + .qualifiers(qualifiers) + .isPrimary(isPrimary) + .declaringClassFqn(context.getFqn(td)) + .factoryMethodName(md.getName().getIdentifier()) + .build()); + } + + private boolean isStereotypeAnnotated(TypeDeclaration td) { + return checkStereotypeAnnotated(td, new HashSet<>()); + } + + private boolean checkStereotypeAnnotated(TypeDeclaration td, Set visited) { + String fqn = context.getFqn(td); + if (fqn != null) { + if (!visited.add(fqn)) return false; // Cycle detection + } + + for (Object modObj : td.modifiers()) { + if (modObj instanceof Annotation ann) { + String simpleName = getSimpleName(ann.getTypeName().getFullyQualifiedName()); + if (STEREOTYPES.contains(simpleName)) { + return true; + } + } + } + + // Could check superclasses/interfaces if we want to support inherited annotations, + // but typically Spring Boot relies on annotations on the concrete class for components. + return false; + } + + private boolean hasBeanAnnotation(MethodDeclaration md) { + for (Object modObj : md.modifiers()) { + if (modObj instanceof Annotation ann) { + String simpleName = getSimpleName(ann.getTypeName().getFullyQualifiedName()); + if ("Bean".equals(simpleName)) { + return true; + } + } + } + return false; + } + + private String getSimpleName(String fqn) { + return fqn.contains(".") ? fqn.substring(fqn.lastIndexOf('.') + 1) : fqn; + } + + private String getDefaultBeanName(String className) { + if (className == null || className.isEmpty()) return className; + return Character.toLowerCase(className.charAt(0)) + className.substring(1); + } + + private String extractValue(Annotation ann) { + if (ann instanceof SingleMemberAnnotation sma) { + return stripQuotes(sma.getValue().toString()); + } else if (ann instanceof NormalAnnotation na) { + for (Object pairObj : na.values()) { + MemberValuePair pair = (MemberValuePair) pairObj; + if ("value".equals(pair.getName().getIdentifier()) || "name".equals(pair.getName().getIdentifier())) { + return stripQuotes(pair.getValue().toString()); + } + } + } + return null; + } + + private List extractArrayValues(Annotation ann) { + List results = new ArrayList<>(); + if (ann instanceof SingleMemberAnnotation sma) { + Expression expr = sma.getValue(); + if (expr instanceof ArrayInitializer arr) { + for (Object item : arr.expressions()) { + results.add(stripQuotes(item.toString())); + } + } else { + results.add(stripQuotes(expr.toString())); + } + } else if (ann instanceof NormalAnnotation na) { + for (Object pairObj : na.values()) { + MemberValuePair pair = (MemberValuePair) pairObj; + if ("value".equals(pair.getName().getIdentifier()) || "name".equals(pair.getName().getIdentifier())) { + Expression expr = pair.getValue(); + if (expr instanceof ArrayInitializer arr) { + for (Object item : arr.expressions()) { + results.add(stripQuotes(item.toString())); + } + } else { + results.add(stripQuotes(expr.toString())); + } + } + } + } + return results; + } + + private String stripQuotes(String s) { + if (s == null) return null; + return s.replaceAll("^\"|\"$", ""); + } + + private String resolveTypeToFqn(Type type, TypeDeclaration contextTd) { + String simpleName; + if (type.isSimpleType()) { + simpleName = ((SimpleType) type).getName().getFullyQualifiedName(); + } else if (type.isParameterizedType()) { + return resolveTypeToFqn(((ParameterizedType) type).getType(), contextTd); + } else { + simpleName = type.toString(); + } + + CompilationUnit cu = (CompilationUnit) contextTd.getRoot(); + TypeDeclaration td = context.getTypeDeclaration(simpleName, cu); + if (td != null) { + return context.getFqn(td); + } + + // Fallback to import matching + for (Object impObj : cu.imports()) { + ImportDeclaration imp = (ImportDeclaration) impObj; + String impName = imp.getName().getFullyQualifiedName(); + if (impName.endsWith("." + simpleName)) { + return impName; + } + } + + return simpleName; + } + + public List getBeans() { + return Collections.unmodifiableList(beans); + } +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolver.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolver.java new file mode 100644 index 0000000..5828e78 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolver.java @@ -0,0 +1,78 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class SpringDependencyResolver { + private final CodebaseContext context; + private final SpringBeanRegistry registry; + + public SpringDependencyResolver(CodebaseContext context, SpringBeanRegistry registry) { + this.context = context; + this.registry = registry; + } + + /** + * Resolves the exact SpringBean that would be injected for a given injection point. + * + * @param requiredTypeFqn The FQN of the type being injected. + * @param qualifier The value of the @Qualifier annotation (if any). + * @param injectionName The name of the field or parameter being injected (used as fallback). + * @return A list of resolved beans. Usually 1. If 0, unresolved. If > 1, ambiguous or a Collection injection. + */ + public List resolve(String requiredTypeFqn, String qualifier, String injectionName) { + if (requiredTypeFqn == null) return new ArrayList<>(); + + // 1. Gather all acceptable types (the required type itself + all implementations) + Set acceptableTypes = new HashSet<>(); + acceptableTypes.add(requiredTypeFqn); + acceptableTypes.addAll(context.getImplementations(requiredTypeFqn)); + + // 2. Filter by Type + List candidates = registry.getBeans().stream() + .filter(bean -> acceptableTypes.contains(bean.getTypeFqn())) + .collect(Collectors.toList()); + + if (candidates.isEmpty() || candidates.size() == 1) { + return candidates; + } + + // 3. Filter by Qualifier (if provided) + if (qualifier != null && !qualifier.isEmpty()) { + List qualifiedCandidates = candidates.stream() + .filter(bean -> bean.getQualifiers().contains(qualifier) || bean.getBeanNames().contains(qualifier)) + .collect(Collectors.toList()); + if (qualifiedCandidates.size() == 1) { + return qualifiedCandidates; + } else if (!qualifiedCandidates.isEmpty()) { + candidates = qualifiedCandidates; + } + } + + // 4. Filter by @Primary + List primaryCandidates = candidates.stream() + .filter(SpringBean::isPrimary) + .collect(Collectors.toList()); + if (primaryCandidates.size() == 1) { + return primaryCandidates; + } + + // 5. Fallback to Injection Name (field name or parameter name) + if (injectionName != null && !injectionName.isEmpty()) { + List namedCandidates = candidates.stream() + .filter(bean -> bean.getBeanNames().contains(injectionName)) + .collect(Collectors.toList()); + if (namedCandidates.size() == 1) { + return namedCandidates; + } + } + + // Return whatever candidates remain (could be ambiguous) + return candidates; + } +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java index 4a8557d..28459f2 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java @@ -94,6 +94,10 @@ public class CodebaseContext { this.propertyResolver.setActiveProfiles(profiles); } + public List getActiveProfiles() { + return Collections.unmodifiableList(activeProfiles); + } + public String resolveString(String input) { if (input == null) return null; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java index 48bd6f6..d319e91 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java @@ -45,6 +45,18 @@ public class ExportService { new EntryPointEnricher(), new PropertyEnricher(), new CallChainEnricher(), + (result, context, intelligence) -> { + // Force property resolution before transition linking + if (result.getMetadata() != null && result.getMetadata().getProperties() != null) { + Map> allProps = result.getMetadata().getProperties(); + Map merged = new HashMap<>(); + if (allProps.containsKey("default")) merged.putAll(allProps.get("default")); + for (String profile : context.getActiveProfiles()) { + if (allProps.containsKey(profile)) merged.putAll(allProps.get(profile)); + } + result.applyResolution(merged); + } + }, new click.kamil.springstatemachineexporter.analysis.enricher.TransitionLinkerEnricher() ))); } diff --git a/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json index f44e11f..89ef4b5 100644 --- a/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json @@ -9,7 +9,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 17, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "PLACE_ORDER", "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", @@ -19,7 +20,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 16, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "CANCEL_ORDER", "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", @@ -29,7 +31,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 21, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "PAY_ORDER", "className" : "click.kamil.examples.enterprise.service.ReactivePaymentService", @@ -39,7 +42,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "SHIP_ORDER", "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", @@ -49,7 +53,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 17, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "RETURN_ORDER", "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", @@ -59,7 +64,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 17, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } } ], "entryPoints" : [ { "type" : "REST", @@ -192,7 +198,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 16, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -227,7 +234,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 21, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -257,7 +265,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 17, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : null @@ -288,7 +297,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -323,7 +333,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 17, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -358,7 +369,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 17, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { diff --git a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json index 52eca42..5d7868d 100644 --- a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json @@ -9,7 +9,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 34, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "AUDIT_EVENT", "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", @@ -19,17 +20,52 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 16, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "EXTERNAL_TRIGGER", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", "methodName" : "processSubmit", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceModule" : null, - "stateMachineId" : null, + "stateMachineId" : "externalNotificationService", "sourceState" : null, "lineNumber" : 19, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "SUBMIT_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -39,7 +75,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 20, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "CANCEL_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -49,7 +86,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "[LIFECYCLE:RESTORE]", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -59,7 +97,65 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 29, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "AUTHORIZE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "processPayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 22, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "CAPTURE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 35, + "polymorphicEvents" : null, + "payloadTypes" : { + "paymentId" : "String" + } + }, { + "event" : "[LIFECYCLE:RESTORE]", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : null, + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 28, + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "REACTIVE_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", @@ -69,7 +165,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } } ], "entryPoints" : [ { "type" : "REST", @@ -144,6 +241,80 @@ "interceptorType" : "Spring MVC Interceptor" }, "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "GET /api/base/{id}", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "processBaseEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/base/{id}", + "verb" : "GET" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/payment/{id}/capture", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "capturePaymentEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/payment/{id}/capture", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] } ], "callChains" : [ { "entryPoint" : { @@ -165,17 +336,14 @@ "methodName" : "processSubmit", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceModule" : null, - "stateMachineId" : null, + "stateMachineId" : "externalNotificationService", "sourceState" : null, "lineNumber" : 19, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "START", - "targetState" : "PROCESSING", - "event" : "EXTERNAL_TRIGGER" - } ] + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -199,7 +367,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 20, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -230,7 +399,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -261,7 +431,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 34, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -296,7 +467,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 29, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : "orderId", "matchedTransitions" : null @@ -327,7 +499,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -357,7 +530,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 16, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -365,6 +539,216 @@ "targetState" : "START", "event" : "AUDIT_EVENT" } ] + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /api/base/{id}", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "processBaseEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/base/{id}", + "verb" : "GET" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.processBaseEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.processPayment" ], + "triggerPoint" : { + "event" : "AUTHORIZE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "processPayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 22, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/payment/{id}/capture", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "capturePaymentEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/payment/{id}/capture", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.capturePaymentEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.capturePayment" ], + "triggerPoint" : { + "event" : "CAPTURE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 35, + "polymorphicEvents" : null, + "payloadTypes" : { + "paymentId" : "String" + } + }, + "contextMachineId" : "paymentId", + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/payment/{id}/capture", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "capturePaymentEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/payment/{id}/capture", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.capturePaymentEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.capturePayment" ], + "triggerPoint" : { + "event" : "[LIFECYCLE:RESTORE]", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : null, + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 28, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : "paymentId", + "matchedTransitions" : null } ], "properties" : { "default" : { diff --git a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json index a213249..b50fc06 100644 --- a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json @@ -9,7 +9,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 34, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "AUDIT_EVENT", "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", @@ -19,17 +20,52 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 16, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "EXTERNAL_TRIGGER", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", "methodName" : "processSubmit", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceModule" : null, - "stateMachineId" : null, + "stateMachineId" : "externalNotificationService", "sourceState" : null, "lineNumber" : 19, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "SUBMIT_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -39,7 +75,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 20, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "CANCEL_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -49,7 +86,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "[LIFECYCLE:RESTORE]", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -59,7 +97,65 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 29, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "AUTHORIZE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "processPayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 22, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, { + "event" : "CAPTURE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 35, + "polymorphicEvents" : null, + "payloadTypes" : { + "paymentId" : "String" + } + }, { + "event" : "[LIFECYCLE:RESTORE]", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : null, + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 28, + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "REACTIVE_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", @@ -69,7 +165,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } } ], "entryPoints" : [ { "type" : "REST", @@ -144,6 +241,80 @@ "interceptorType" : "Spring MVC Interceptor" }, "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, { + "type" : "REST", + "name" : "GET /api/base/{id}", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "processBaseEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/base/{id}", + "verb" : "GET" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, { + "type" : "REST", + "name" : "POST /api/payment/{id}/capture", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "capturePaymentEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/payment/{id}/capture", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] } ], "callChains" : [ { "entryPoint" : { @@ -165,17 +336,14 @@ "methodName" : "processSubmit", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceModule" : null, - "stateMachineId" : null, + "stateMachineId" : "externalNotificationService", "sourceState" : null, "lineNumber" : 19, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "START", - "targetState" : "PROCESSING", - "event" : "EXTERNAL_TRIGGER" - } ] + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -199,7 +367,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 20, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -230,7 +399,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -261,7 +431,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 34, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -296,7 +467,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 29, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : "orderId", "matchedTransitions" : null @@ -327,7 +499,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -357,7 +530,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 16, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -365,6 +539,216 @@ "targetState" : "START", "event" : "AUDIT_EVENT" } ] + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /api/base/{id}", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "processBaseEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/base/{id}", + "verb" : "GET" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.processBaseEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.processPayment" ], + "triggerPoint" : { + "event" : "AUTHORIZE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "processPayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 22, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/payment/{id}/capture", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "capturePaymentEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/payment/{id}/capture", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.capturePaymentEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.capturePayment" ], + "triggerPoint" : { + "event" : "CAPTURE", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 35, + "polymorphicEvents" : null, + "payloadTypes" : { + "paymentId" : "String" + } + }, + "contextMachineId" : "paymentId", + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/payment/{id}/capture", + "className" : "click.kamil.examples.statemachine.extended.web.PaymentController", + "methodName" : "capturePaymentEndpoint", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java", + "metadata" : { + "path" : "/api/payment/{id}/capture", + "verb" : "POST" + }, + "parameters" : [ { + "name" : "id", + "type" : "String", + "annotations" : [ "PathVariable" ] + } ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.capturePaymentEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.capturePayment" ], + "triggerPoint" : { + "event" : "[LIFECYCLE:RESTORE]", + "className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl", + "methodName" : "capturePayment", + "sourceFile" : null, + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 28, + "polymorphicEvents" : null, + "payloadTypes" : { } + }, + "contextMachineId" : "paymentId", + "matchedTransitions" : null } ], "properties" : { "default" : { diff --git a/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json index f6e7641..733c5db 100644 --- a/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json @@ -9,7 +9,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 15, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } } ], "entryPoints" : [ { "type" : "REST", @@ -57,7 +58,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 15, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { diff --git a/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json b/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json index 20af568..58a9ad5 100644 --- a/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json +++ b/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json @@ -9,7 +9,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 40, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "ORDER_EVENT", "className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService", @@ -19,7 +20,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 52, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } } ], "entryPoints" : [ { "type" : "REST", @@ -106,7 +108,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 40, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { diff --git a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json index b7f3445..fac3903 100644 --- a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json @@ -9,7 +9,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } } ], "entryPoints" : [ { "type" : "REST", @@ -69,7 +70,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 18, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { diff --git a/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json index 75d0842..974cc6a 100644 --- a/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json @@ -9,7 +9,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "eventProvider", "className" : "click.kamil.service.StateMachineServiceImpl", @@ -19,7 +20,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 50, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } }, { "event" : "customMessage", "className" : "click.kamil.service.StateMachineServiceImpl", @@ -29,7 +31,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 78, - "polymorphicEvents" : null + "polymorphicEvents" : null, + "payloadTypes" : { } } ], "entryPoints" : [ { "type" : "REST", @@ -144,7 +147,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : [ ] + "polymorphicEvents" : [ ], + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -175,7 +179,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : [ ] + "polymorphicEvents" : [ ], + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -206,7 +211,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : [ ] + "polymorphicEvents" : [ ], + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -241,7 +247,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : [ ] + "polymorphicEvents" : [ ], + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -276,7 +283,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 78, - "polymorphicEvents" : [ ] + "polymorphicEvents" : [ ], + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -311,7 +319,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 25, - "polymorphicEvents" : [ ] + "polymorphicEvents" : [ ], + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { @@ -346,7 +355,8 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 50, - "polymorphicEvents" : [ ] + "polymorphicEvents" : [ ], + "payloadTypes" : { } }, "contextMachineId" : null, "matchedTransitions" : [ { diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/PaymentStateMachineConfig.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/PaymentStateMachineConfig.java new file mode 100644 index 0000000..40f9a37 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/PaymentStateMachineConfig.java @@ -0,0 +1,58 @@ +package click.kamil.examples.statemachine.extended.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +@Configuration +@EnableStateMachine(name = "paymentStateMachine") +public class PaymentStateMachineConfig extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("NEW") + .state("AUTHORIZED") + .state("CAPTURED") + .state("DECLINED") + .state("QUIRK1") + .state("QUIRK2") + .state("QUIRK3") + .state("QUIRK4"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source("NEW").target("AUTHORIZED") + .event("AUTHORIZE") + .and() + .withExternal() + .source("AUTHORIZED").target("CAPTURED") + .event("CAPTURE") + .and() + .withExternal() + .source("NEW").target("DECLINED") + .event("DECLINE") + .and() + .withExternal() + .source("NEW").target("QUIRK1") + .event("PRIMARY_EVENT") + .and() + .withExternal() + .source("NEW").target("QUIRK2") + .event("NAMED_EVENT") + .and() + .withExternal() + .source("NEW").target("QUIRK3") + .event("QUALIFIER_EVENT") + .and() + .withExternal() + .source("NEW").target("QUIRK4") + .event("FALLBACK_EVENT"); + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java new file mode 100644 index 0000000..06f1c10 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java @@ -0,0 +1,19 @@ +package click.kamil.examples.statemachine.extended.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.statemachine.StateMachine; +import org.springframework.stereotype.Service; + +@Service +public class FallbackQuirkService implements QuirkService { + + @Autowired + @Qualifier("paymentStateMachine") + private StateMachine stateMachine; + + @Override + public void doQuirk() { + stateMachine.sendEvent("FALLBACK_EVENT"); + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java new file mode 100644 index 0000000..c5162f2 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java @@ -0,0 +1,19 @@ +package click.kamil.examples.statemachine.extended.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.statemachine.StateMachine; +import org.springframework.stereotype.Service; + +@Service("customName") +public class NamedQuirkService implements QuirkService { + + @Autowired + @Qualifier("paymentStateMachine") + private StateMachine stateMachine; + + @Override + public void doQuirk() { + stateMachine.sendEvent("NAMED_EVENT"); + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentService.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentService.java new file mode 100644 index 0000000..4f24e82 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentService.java @@ -0,0 +1,6 @@ +package click.kamil.examples.statemachine.extended.service; + +public interface PaymentService { + void processPayment(String paymentId); + void capturePayment(String paymentId); +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java new file mode 100644 index 0000000..6fcf1c1 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java @@ -0,0 +1,37 @@ +package click.kamil.examples.statemachine.extended.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.persist.StateMachinePersister; +import org.springframework.stereotype.Service; + +@Service +public class PaymentServiceImpl implements PaymentService { + + @Autowired + @Qualifier("paymentStateMachine") + private StateMachine stateMachine; + + @Autowired + private StateMachinePersister persister; + + @Override + public void processPayment(String paymentId) { + // Send a trigger mapped strictly to PaymentStateMachineConfig + stateMachine.sendEvent("AUTHORIZE"); + } + + @Override + public void capturePayment(String paymentId) { + try { + persister.restore(stateMachine, paymentId); + } catch (Exception e) {} + + org.springframework.messaging.Message msg = org.springframework.messaging.support.MessageBuilder + .withPayload("CAPTURE") + .setHeader("paymentId", paymentId) + .build(); + stateMachine.sendEvent(msg); + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java new file mode 100644 index 0000000..75f2032 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java @@ -0,0 +1,21 @@ +package click.kamil.examples.statemachine.extended.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Primary; +import org.springframework.statemachine.StateMachine; +import org.springframework.stereotype.Service; + +@Service +@Primary +public class PrimaryQuirkService implements QuirkService { + + @Autowired + @Qualifier("paymentStateMachine") + private StateMachine stateMachine; + + @Override + public void doQuirk() { + stateMachine.sendEvent("PRIMARY_EVENT"); + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java new file mode 100644 index 0000000..b61317a --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java @@ -0,0 +1,21 @@ +package click.kamil.examples.statemachine.extended.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Profile; +import org.springframework.statemachine.StateMachine; +import org.springframework.stereotype.Service; + +@Service +@Profile("nonexistent") +public class ProfiledQuirkService implements QuirkService { + + @Autowired + @Qualifier("paymentStateMachine") + private StateMachine stateMachine; + + @Override + public void doQuirk() { + stateMachine.sendEvent("PROFILED_EVENT"); + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java new file mode 100644 index 0000000..e87e808 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java @@ -0,0 +1,19 @@ +package click.kamil.examples.statemachine.extended.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.statemachine.StateMachine; +import org.springframework.stereotype.Service; + +@Service +public class QualifierQuirkService implements QuirkService { + + @Autowired + @Qualifier("paymentStateMachine") + private StateMachine stateMachine; + + @Override + public void doQuirk() { + stateMachine.sendEvent("QUALIFIER_EVENT"); + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QuirkService.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QuirkService.java new file mode 100644 index 0000000..f44f8e3 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/service/QuirkService.java @@ -0,0 +1,5 @@ +package click.kamil.examples.statemachine.extended.service; + +public interface QuirkService { + void doQuirk(); +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/BaseController.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/BaseController.java new file mode 100644 index 0000000..d4b8cd0 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/BaseController.java @@ -0,0 +1,10 @@ +package click.kamil.examples.statemachine.extended.web; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +public interface BaseController { + + @GetMapping("/api/base/{id}") + String processBaseEndpoint(@PathVariable String id); +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java new file mode 100644 index 0000000..5bd6c9d --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/PaymentController.java @@ -0,0 +1,28 @@ +package click.kamil.examples.statemachine.extended.web; + +import click.kamil.examples.statemachine.extended.service.PaymentService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; + +@RestController +public class PaymentController implements BaseController { + + private final PaymentService paymentService; + + public PaymentController(PaymentService paymentService) { + this.paymentService = paymentService; + } + + @Override + public String processBaseEndpoint(@PathVariable String id) { + paymentService.processPayment(id); + return "Started Base Payment: " + id; + } + + @PostMapping("/api/payment/{id}/capture") + public String capturePaymentEndpoint(@PathVariable String id) { + paymentService.capturePayment(id); + return "Captured: " + id; + } +} diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java new file mode 100644 index 0000000..33fec46 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java @@ -0,0 +1,45 @@ +package click.kamil.examples.statemachine.extended.web; + +import click.kamil.examples.statemachine.extended.service.QuirkService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class QuirkController { + + @Autowired + private QuirkService quirkService; // Should resolve to PrimaryQuirkService + + @Autowired + @Qualifier("customName") + private QuirkService someService; // Should resolve to NamedQuirkService + + @Autowired + @Qualifier("qualifierQuirkService") + private QuirkService anotherService; // Should resolve to QualifierQuirkService + + @Autowired + private QuirkService fallbackQuirkService; // Should resolve to FallbackQuirkService + + @PostMapping("/api/quirk/primary") + public void testPrimary() { + quirkService.doQuirk(); + } + + @PostMapping("/api/quirk/named") + public void testNamed() { + someService.doQuirk(); + } + + @PostMapping("/api/quirk/qualifier") + public void testQualifier() { + anotherService.doQuirk(); + } + + @PostMapping("/api/quirk/fallback") + public void testFallback() { + fallbackQuirkService.doQuirk(); + } +}