From fab5c66cc1b315ccae7446e5cfd0078f6b2d61f1 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sun, 21 Jun 2026 08:25:48 +0200 Subject: [PATCH] beans --- .../analysis/service/JdtCallGraphEngine.java | 986 ++++++++++++++++++ .../service/JdtIntelligenceProvider.java | 16 +- .../spring/InjectionPointAnalyzer.java | 83 ++ .../analysis/spring/SpringBean.java | 26 + .../analysis/spring/SpringBeanRegistry.java | 17 + .../analysis/spring/SpringContextScanner.java | 179 ++++ .../spring/SpringDependencyResolver.java | 67 ++ .../JdtCallGraphEngineIntegrationTest.java | 138 +++ .../SpringContextScannerIntegrationTest.java | 89 ++ .../spring/SpringDependencyResolverTest.java | 95 ++ .../ExtendedStateMachineConfig.json | 787 ++++++++++++++ .../ExtendedStateMachineConfig.json | 787 ++++++++++++++ .../extended_analysis_sample/.gitignore | 39 +- .../extended/config/MockBeanConfig.java | 13 + .../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 + 26 files changed, 3605 insertions(+), 5 deletions(-) create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/InjectionPointAnalyzer.java 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/SpringContextScanner.java create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolver.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngineIntegrationTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringContextScannerIntegrationTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolverTest.java create mode 100644 state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/MockBeanConfig.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/service/JdtCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java new file mode 100644 index 0000000..f73028e --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java @@ -0,0 +1,986 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.analysis.model.CallChain; +import click.kamil.springstatemachineexporter.analysis.model.CallEdge; +import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.jdt.core.dom.*; + +import java.util.*; + +import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer; + +@Slf4j +public class JdtCallGraphEngine implements CallGraphEngine { + private final CodebaseContext context; + private final ConstantResolver constantResolver; + private final InjectionPointAnalyzer injectionAnalyzer; + private String currentMethodFqn; + private Map> graph; + + public JdtCallGraphEngine(CodebaseContext context, InjectionPointAnalyzer injectionAnalyzer) { + this.context = context; + this.constantResolver = new ConstantResolver(); + this.injectionAnalyzer = injectionAnalyzer; + } + + public List findChains(List entryPoints, List triggers) { + Map> callGraph = buildCallGraph(); + List chains = new ArrayList<>(); + + for (EntryPoint ep : entryPoints) { + String startMethod = ep.getClassName() + "." + ep.getMethodName(); + boolean foundAny = false; + for (TriggerPoint tp : triggers) { + String targetMethod = tp.getClassName() + "." + tp.getMethodName(); + List path = findPath(startMethod, targetMethod, callGraph, new HashSet<>()); + if (path != null) { + foundAny = true; + TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph); + String contextMachineId = extractContextMachineId(path, callGraph); + chains.add(CallChain.builder() + .entryPoint(ep) + .triggerPoint(resolvedTp) + .methodChain(path) + .contextMachineId(contextMachineId) + .build()); + } + } + if (!foundAny && log.isDebugEnabled()) { + log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet()); + } + } + return chains; + } + + private TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List path, Map> callGraph) { + if (path.size() < 2) return tp; + + String event = tp.getEvent(); + if (event == null || event.isEmpty() || event.contains("\"")) return tp; + + String currentParamName = event; + String resolvedValue = event; + String methodSuffix = ""; + + // Extract method calls like .getType() so we can trace the base parameter + int dotIndex = currentParamName.indexOf('.'); + if (dotIndex > 0 && dotIndex + 1 < currentParamName.length()) { + char nextChar = currentParamName.charAt(dotIndex + 1); + if (Character.isLowerCase(nextChar)) { + methodSuffix = currentParamName.substring(dotIndex); + currentParamName = currentParamName.substring(0, dotIndex); + } + } + + // Walk backwards up the call chain + for (int i = path.size() - 1; i > 0; i--) { + String target = path.get(i); + String caller = path.get(i - 1); + + // Find parameter index in target method + int paramIndex = getParameterIndex(target, currentParamName); + if (paramIndex < 0) { + // Not a parameter. Maybe it's a local variable initialized from a parameter or method call? + String tracedVar = traceLocalVariable(target, currentParamName); + if (tracedVar != null && !tracedVar.equals(currentParamName)) { + // Extract method calls like .getType() from the traced variable + int dotIdx = tracedVar.indexOf('.'); + if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) { + methodSuffix = tracedVar.substring(dotIdx) + methodSuffix; + tracedVar = tracedVar.substring(0, dotIdx); + } + currentParamName = tracedVar; + resolvedValue = tracedVar + methodSuffix; + paramIndex = getParameterIndex(target, currentParamName); + } + } + if (paramIndex < 0) { + break; // Parameter name changed or not found, stop tracing + } + + // Find the edge from caller to target to get the argument passed + List edges = callGraph.get(caller); + boolean found = false; + if (edges != null) { + for (CallEdge edge : edges) { + if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) { + if (paramIndex < edge.getArguments().size()) { + String arg = edge.getArguments().get(paramIndex); + if (arg != null) { + // If the argument passed has a method call, extract it + int dotIdx = arg.indexOf('.'); + if (dotIdx > 0 && dotIdx + 1 < arg.length() && Character.isLowerCase(arg.charAt(dotIdx + 1))) { + methodSuffix = arg.substring(dotIdx) + methodSuffix; + arg = arg.substring(0, dotIdx); + } + currentParamName = arg; + resolvedValue = arg + methodSuffix; + found = true; + break; + } + } + } + } + } + if (!found) break; // Could not map argument + } + + // Final check on the entry method + String entryMethod = path.get(0); + int entryParamIndex = getParameterIndex(entryMethod, currentParamName); + if (entryParamIndex < 0) { + String tracedVar = traceLocalVariable(entryMethod, currentParamName); + if (tracedVar != null && !tracedVar.equals(currentParamName)) { + int dotIdx = tracedVar.indexOf('.'); + if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) { + methodSuffix = tracedVar.substring(dotIdx) + methodSuffix; + tracedVar = tracedVar.substring(0, dotIdx); + } + currentParamName = tracedVar; + resolvedValue = tracedVar + methodSuffix; + } + } + + List polymorphicEvents = new ArrayList<>(); + if (resolvedValue.matches(".*\\.[a-zA-Z0-9_]+\\(\\)")) { + int lastDot = resolvedValue.lastIndexOf('.'); + int firstDot = resolvedValue.indexOf('.'); + int openParen = resolvedValue.indexOf('(', lastDot); + + if (lastDot > 0 && openParen > lastDot) { + String varName = resolvedValue.substring(0, firstDot); + if (varName.contains("(")) { + varName = null; + } + String methodName = resolvedValue.substring(lastDot + 1, openParen); + + String declaredType = null; + String sourceMethod = null; + + if (varName != null) { + for (String methodFqn : path) { + declaredType = getVariableDeclaredType(methodFqn, varName); + if (declaredType != null) { + sourceMethod = methodFqn; + break; + } + } + } else { + String baseExpr = resolvedValue.substring(0, lastDot); + if (baseExpr.contains("new ")) { + int newIdx = baseExpr.indexOf("new "); + int openParenBase = baseExpr.indexOf('(', newIdx); + if (openParenBase > newIdx) { + declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim(); + if (declaredType.contains("<")) { + declaredType = declaredType.substring(0, declaredType.indexOf('<')); + } + sourceMethod = "inline-instantiation"; + } + } + } + + if (declaredType != null) { + System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType); + List typesToInspect = new ArrayList<>(); + if ("inline-instantiation".equals(sourceMethod)) { + typesToInspect.add(declaredType); + } else { + typesToInspect.add(declaredType); + typesToInspect.addAll(context.getImplementations(declaredType)); + } + System.out.println("DEEP TRACE IMPLS: " + typesToInspect); + + for (String type : typesToInspect) { + Set visited = new HashSet<>(); + TypeDeclaration baseTd = context.getTypeDeclaration(type); + CompilationUnit cuToUse = null; + if (baseTd != null && baseTd.getRoot() instanceof CompilationUnit) { + cuToUse = (CompilationUnit) baseTd.getRoot(); + } else { + String firstPathMethod = path.get(0); + String entryClassName = firstPathMethod.substring(0, firstPathMethod.lastIndexOf('.')); + TypeDeclaration entryTd = context.getTypeDeclaration(entryClassName); + if (entryTd != null && entryTd.getRoot() instanceof CompilationUnit) { + cuToUse = (CompilationUnit) entryTd.getRoot(); + } + } + List constants = resolveMethodReturnConstant(type, methodName, 0, visited, cuToUse); + System.out.println("DEEP TRACE RETURN: " + type + " -> " + constants); + for (String constant : constants) { + if (!polymorphicEvents.contains(constant)) { + polymorphicEvents.add(constant); + } + } + } + } + } + } + + // LAST RESORT FALLBACK: If AST deep trace failed to find a valid ALL_CAPS constant + // (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly + // for string literals or enum-like constants. + boolean hasValidConstant = false; + for (String ev : polymorphicEvents) { + String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev; + if (val.equals(val.toUpperCase()) && val.length() > 2) { + hasValidConstant = true; + break; + } + } + + if (!hasValidConstant && resolvedValue.contains("(")) { + java.util.List scraped = new java.util.ArrayList<>(); + + // Extract "STRING_LITERALS" + java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue); + while (m1.find()) { + scraped.add(m1.group(1)); + } + + // Extract ENUM_LIKE_CONSTANTS + java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue); + while (m2.find()) { + if (!scraped.contains(m2.group(1))) { + scraped.add(m2.group(1)); + } + } + + if (!scraped.isEmpty()) { + polymorphicEvents.clear(); + polymorphicEvents.addAll(scraped); + } + } + + // Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones + if (polymorphicEvents.size() > 1) { + polymorphicEvents.removeIf(e -> { + String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e; + return !val.equals(val.toUpperCase()) || val.length() <= 2; + }); + } + + + if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) { + return TriggerPoint.builder() + .event(resolvedValue) + .className(tp.getClassName()) + .methodName(tp.getMethodName()) + .sourceFile(tp.getSourceFile()) + .lineNumber(tp.getLineNumber()) + .polymorphicEvents(polymorphicEvents) + .build(); + } + + return tp; + } + + private int getParameterIndex(String methodFqn, String paramName) { + if (methodFqn == null || !methodFqn.contains(".")) return -1; + String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); + String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); + TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null) { + for (int i = 0; i < md.parameters().size(); i++) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i); + if (svd.getName().getIdentifier().equals(paramName)) { + return i; + } + } + } + } + return -1; + } + + private String getVariableDeclaredType(String methodFqn, String varName) { + if (methodFqn == null || !methodFqn.contains(".")) return null; + String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); + String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); + TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null) { + for (Object pObj : md.parameters()) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj; + if (svd.getName().getIdentifier().equals(varName)) { + return svd.getType().toString(); + } + } + final String[] foundType = new String[1]; + if (md.getBody() != null) { + md.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().toString(); + } + } + return super.visit(node); + } + }); + } + return foundType[0]; + } + } + return null; + } + + private List resolveMethodReturnConstant(String className, String methodName, int depth, Set visited, CompilationUnit contextCu) { + if (depth > 20) return Collections.emptyList(); + String fqn = className + "." + methodName; + if (!visited.add(fqn)) return Collections.emptyList(); + + List constants = new ArrayList<>(); + TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null; + if (tempTd == null) { + tempTd = context.getTypeDeclaration(className); + } + final TypeDeclaration td = tempTd; + if (td == null) { + System.out.println("DEEP TRACE FAILED TO FIND TD: " + className); + } + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null && md.getBody() != null) { + md.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(ReturnStatement node) { + Expression retExpr = node.getExpression(); + if (retExpr != null) { + boolean handled = false; + if (retExpr instanceof MethodInvocation mi) { + // Follow delegation first + String called = resolveCalledMethod(mi); + System.out.println("DEEP TRACE RESOLVED CALLED: " + called); + if (called != null && called.contains(".")) { + if (visited.contains(called)) { + handled = true; + } else { + String cName = called.substring(0, called.lastIndexOf('.')); + String mName = called.substring(called.lastIndexOf('.') + 1); + + TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName); + if (targetTd == null) targetTd = context.getTypeDeclaration(cName); + + if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { + List delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); + constants.addAll(delegationResult); + handled = true; + } + } + } + } + if (!handled) { + String val = constantResolver.resolve(retExpr, context); + if (val != null) { + if (val.startsWith("ENUM_SET:")) { + for (String eVal : val.substring(9).split(",")) { + constants.add(eVal.substring(eVal.lastIndexOf('.') + 1)); + } + } else { + constants.add(val); + } + } else if (retExpr instanceof QualifiedName qn) { + constants.add(qn.toString()); + } else if (retExpr instanceof SimpleName sn) { + List consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited); + if (!consts.isEmpty()) { + constants.addAll(consts); + } else { + constants.add(sn.toString()); + } + } + } + } + return super.visit(node); + } + }); + } + } + visited.remove(fqn); + return constants; + } + + private String traceLocalVariable(String methodFqn, String varName) { + if (methodFqn == null || !methodFqn.contains(".")) return null; + String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); + String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); + TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null && md.getBody() != null) { + final Expression[] initializer = new Expression[1]; + md.getBody().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) { + Expression expr = traceVariable(initializer[0]); + if (expr instanceof MethodInvocation mi) { + // Unwrapper logic: If wrapper method is called, extract its arguments recursively + Expression innerMost = unwrapMethodInvocation(mi, 0); + if (innerMost instanceof MethodInvocation innerMi) { + if (innerMi.getExpression() instanceof SimpleName sn) { + return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()"; + } + return innerMi.getName().getIdentifier() + "()"; + } + if (innerMost instanceof SimpleName sn) { + return sn.getIdentifier(); + } + return innerMost.toString(); + } + if (expr instanceof SimpleName sn) { + return sn.getIdentifier(); + } + return expr.toString(); + } + } + } + return null; + } + + private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) { + if (depth > 5) return mi; + if (!mi.arguments().isEmpty()) { + Expression arg = (Expression) mi.arguments().get(0); + if (arg instanceof MethodInvocation innerMi) { + return unwrapMethodInvocation(innerMi, depth + 1); + } + return arg; + } + return mi; + } + + private String extractContextMachineId(List path, Map> callGraph) { + for (String node : path) { + List edges = callGraph.get(node); + if (edges != null) { + for (CallEdge edge : edges) { + String target = edge.getTargetMethod(); + if (target != null && (target.contains(".restore") || target.contains(".read"))) { + // Persister signatures usually like: restore(stateMachine, contextObj) + if (edge.getArguments().size() >= 2) { + return edge.getArguments().get(1); // The contextObj / machineId + } + } + } + } + } + return null; + } + + private MethodDeclaration findEnclosingMethod(ASTNode node) { + ASTNode parent = node.getParent(); + while (parent != null && !(parent instanceof MethodDeclaration)) { + parent = parent.getParent(); + } + return (MethodDeclaration) parent; + } + + private Map> buildCallGraph() { + graph = new HashMap<>(); + for (CompilationUnit cu : context.getCompilationUnits()) { + cu.accept(new ASTVisitor() { + + @Override + public boolean visit(MethodInvocation node) { + MethodDeclaration md = findEnclosingMethod(node); + if (md != null) { + TypeDeclaration td = findEnclosingType(md); + if (td != null) { + String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier(); + List calledMethods = resolveCalledMethodsPolymorphic(node); + List args = resolveArguments(node.arguments()); + for (String calledMethod : calledMethods) { + graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args)); + } + for (Object argObj : node.arguments()) { + if (argObj instanceof ExpressionMethodReference emr) { + String typeName = emr.getExpression().toString(); + if ("this".equals(typeName) || "super".equals(typeName)) { + TypeDeclaration td2 = findEnclosingType(node); + if (td2 != null) { + String refMethod = resolveMethodInType(td2, emr.getName().getIdentifier()); + if (refMethod != null) { + graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, args)); + } + } + } else { + String fallbackTypeFqn = null; + ITypeBinding binding = emr.getExpression().resolveTypeBinding(); + if (binding != null) { + fallbackTypeFqn = binding.getQualifiedName(); + } else if (emr.getExpression() instanceof SimpleName sn) { + fallbackTypeFqn = resolveReceiverTypeFallback(sn); + } + if (fallbackTypeFqn != null) { + 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)); + } + } + } + } + } + } + } + return super.visit(node); + } + + @Override + public boolean visit(SuperMethodInvocation node) { + MethodDeclaration md = findEnclosingMethod(node); + if (md != null) { + TypeDeclaration tdOuter = findEnclosingType(md); + if (tdOuter != null) { + String currentMethodFqn = context.getFqn(tdOuter) + "." + md.getName().getIdentifier(); + String methodName = node.getName().getIdentifier(); + TypeDeclaration td = findEnclosingType(node); + if (td != null) { + String superFqn = context.getSuperclassFqn(td); + if (superFqn != null) { + TypeDeclaration superTd = context.getTypeDeclaration(superFqn); + String calledMethod = null; + if (superTd != null) { + calledMethod = resolveMethodInType(superTd, methodName); + } + if (calledMethod == null) { + calledMethod = superFqn + "." + methodName; + } + List args = resolveArguments(node.arguments()); + graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args)); + } + } + } + } + return super.visit(node); + } + }); + } + return graph; + } + + private List resolveArguments(List astArguments) { + List args = new ArrayList<>(); + for (Object argObj : astArguments) { + Expression expr = (Expression) argObj; + + // Extract from lambda + if (expr instanceof LambdaExpression le) { + ASTNode body = le.getBody(); + if (body instanceof Expression bodyExpr) { + expr = bodyExpr; + } else if (body instanceof Block block) { + for (Object stmtObj : block.statements()) { + if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) { + expr = rs.getExpression(); + break; + } + } + } + } + + if (expr instanceof ExpressionMethodReference emr) { + TypeDeclaration td = findEnclosingType(expr); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true); + if (md != null && md.getBody() != null) { + for (Object stmtObj : md.getBody().statements()) { + if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) { + expr = rs.getExpression(); + break; + } + } + } + } + } + + // Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...)) + Expression tracedExpr = traceVariable(expr); + if (tracedExpr instanceof QualifiedName || tracedExpr instanceof ClassInstanceCreation || tracedExpr instanceof StringLiteral || tracedExpr instanceof NumberLiteral) { + expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor + } + + if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) { + Expression firstArg = (Expression) cic.arguments().get(0); + String resolved = constantResolver.resolve(firstArg, context); + if (resolved != null) { + expr = firstArg; // Only unwrap if it's actually a constant + } + } + + String val = constantResolver.resolve(expr, context); + if (val == null) { + val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc) + } + args.add(val); + } + return args; + } + + private List resolveCalledMethodsPolymorphic(MethodInvocation node) { + String baseCalled = resolveCalledMethod(node); + 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); + } + } + + return allResolved; + } + + private String resolveCalledMethod(MethodInvocation node) { + Expression receiver = node.getExpression(); + String methodName = node.getName().getIdentifier(); + + if (receiver == null) { + TypeDeclaration td = findEnclosingType(node); + if (td != null) { + return resolveMethodInType(td, methodName); + } + return null; + } + + if (injectionAnalyzer != null) { + IBinding nameBinding = null; + if (receiver instanceof SimpleName sn) { + nameBinding = sn.resolveBinding(); + } else if (receiver instanceof FieldAccess fa) { + nameBinding = fa.resolveFieldBinding(); + } + if (nameBinding instanceof IVariableBinding varBinding) { + String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding); + if (concreteFqn != null) { + return concreteFqn + "." + methodName; + } + } + } + + ITypeBinding binding = receiver.resolveTypeBinding(); + if (binding != null) { + return binding.getErasure().getQualifiedName() + "." + methodName; + } + + if (receiver instanceof ThisExpression) { + TypeDeclaration td = findEnclosingType(node); + if (td != null) { + return context.getFqn(td) + "." + methodName; + } + } + + if (receiver instanceof SimpleName sn) { + String fallbackTypeFqn = resolveReceiverTypeFallback(sn); + if (fallbackTypeFqn != null) { + return fallbackTypeFqn + "." + methodName; + } + String receiverName = sn.getIdentifier(); + return receiverName + "." + methodName; + } + + return null; + } + + private String resolveReceiverTypeFallback(SimpleName receiverNameNode) { + String varName = receiverNameNode.getIdentifier(); + + // 1. Check local variables in enclosing method + MethodDeclaration enclosingMethod = findEnclosingMethod(receiverNameNode); + if (enclosingMethod != null) { + // Check parameters + for (Object paramObj : enclosingMethod.parameters()) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) paramObj; + if (svd.getName().getIdentifier().equals(varName)) { + return resolveTypeToFqn(svd.getType(), receiverNameNode); + } + } + // Check method body (local variables) + 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], receiverNameNode); + } + } + } + + // 2. Check fields in enclosing class + 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(), receiverNameNode); + } + } + } + } + + return null; + } + + private String resolveTypeToFqn(Type type, ASTNode contextNode) { + if (type == null) return null; + String simpleName; + if (type.isSimpleType()) { + simpleName = ((SimpleType) type).getName().getFullyQualifiedName(); + } else if (type.isParameterizedType()) { + return resolveTypeToFqn(((ParameterizedType) type).getType(), contextNode); + } else { + simpleName = type.toString(); + } + + CompilationUnit cu = (CompilationUnit) contextNode.getRoot(); + TypeDeclaration td = context.getTypeDeclaration(simpleName, cu); + if (td != null) { + return context.getFqn(td); + } + + // Fallback to import matching if CodebaseContext doesn't know it (e.g., external library) + for (Object impObj : cu.imports()) { + ImportDeclaration imp = (ImportDeclaration) impObj; + String impName = imp.getName().getFullyQualifiedName(); + if (impName.endsWith("." + simpleName)) { + return impName; + } + } + + return simpleName; + } + + private String resolveMethodInType(TypeDeclaration td, String methodName) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null) { + TypeDeclaration declaringTd = findEnclosingType(md); + return context.getFqn(declaringTd) + "." + methodName; + } + return null; + } + + private List findPath(String start, String target, Map> graph, Set visited) { + if (start.equals(target)) return new ArrayList<>(List.of(start)); + if (!visited.add(start)) return null; // Path-scoped cycle detection + + List neighbors = graph.get(start); + if (neighbors != null) { + for (CallEdge edge : neighbors) { + String neighbor = edge.getTargetMethod(); + if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) { + visited.remove(start); + return new ArrayList<>(List.of(start, target)); + } + List path = findPath(neighbor, target, graph, visited); + if (path != null) { + path.add(0, start); + visited.remove(start); + return path; + } + } + } + + if (log.isDebugEnabled()) { + log.debug("Path search dead-end at {} when looking for {}", start, target); + } + + visited.remove(start); + return null; + } + + private boolean isHeuristicMatch(String neighbor, String target) { + 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); + } + + private TypeDeclaration findEnclosingType(ASTNode node) { + ASTNode parent = node.getParent(); + while (parent != null && !(parent instanceof TypeDeclaration)) { + parent = parent.getParent(); + } + return (TypeDeclaration) parent; + } + private Expression traceVariable(Expression expr) { + 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 traceVariable(initializer[0]); + } + } + } + return expr; + } + + private List traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set visited) { + List results = new ArrayList<>(); + + // 1. Check Field Declarations + for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) { + for (Object fragObj : fd.fragments()) { + org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj; + if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) { + Expression right = frag.getInitializer(); + if (right instanceof MethodInvocation mi) { + String calledName = mi.getName().getIdentifier(); + String targetClass = context.getFqn(td); + CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; + results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); + } else { + String val = constantResolver.resolve(right, context); + if (val != null) results.add(val); + } + } + } + } + + org.eclipse.jdt.core.dom.ASTVisitor assignmentVisitor = new org.eclipse.jdt.core.dom.ASTVisitor() { + @Override + public boolean visit(Assignment node) { + Expression left = node.getLeftHandSide(); + if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) || + (left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) { + + Expression right = node.getRightHandSide(); + if (right instanceof MethodInvocation mi) { + String calledName = mi.getName().getIdentifier(); + String targetClass = context.getFqn(td); + + if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) { + targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName(); + } else if (mi.getExpression() instanceof SimpleName) { + String exprName = ((SimpleName) mi.getExpression()).getIdentifier(); + CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; + TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu); + if (targetTd != null) { + targetClass = context.getFqn(targetTd); + } + } + + CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; + results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); + } else { + String val = constantResolver.resolve(right, context); + if (val != null) results.add(val); + } + } + return super.visit(node); + } + @Override + public boolean visit(ConstructorInvocation node) { + for (Object argObj : node.arguments()) { + Expression arg = (Expression) argObj; + String val = constantResolver.resolve(arg, context); + if (val != null) { + if (val.startsWith("ENUM_SET:")) { + for (String eVal : val.substring(9).split(",")) { + results.add(eVal.substring(eVal.lastIndexOf('.') + 1)); + } + } else { + results.add(val); + } + } + } + return super.visit(node); + } + + @Override + public boolean visit(SuperConstructorInvocation node) { + for (Object argObj : node.arguments()) { + Expression arg = (Expression) argObj; + String val = constantResolver.resolve(arg, context); + if (val != null) { + if (val.startsWith("ENUM_SET:")) { + for (String eVal : val.substring(9).split(",")) { + results.add(eVal.substring(eVal.lastIndexOf('.') + 1)); + } + } else { + results.add(val); + } + } + } + return super.visit(node); + } + }; + + // 2. Check Constructors + for (MethodDeclaration md : td.getMethods()) { + if (md.isConstructor() && md.getBody() != null) { + md.getBody().accept(assignmentVisitor); + } + } + + // 3. Check Initializers + for (Object bodyDecl : td.bodyDeclarations()) { + if (bodyDecl instanceof org.eclipse.jdt.core.dom.Initializer init && init.getBody() != null) { + init.getBody().accept(assignmentVisitor); + } + } + + return results; + } + +} \ No newline at end of file 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 f99989e..b554d3c 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 @@ -4,6 +4,10 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain; import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; +import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer; +import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry; +import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner; +import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import lombok.extern.slf4j.Slf4j; import org.eclipse.jdt.core.dom.CompilationUnit; @@ -32,7 +36,17 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider { this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints()); this.mvcDetector = new SpringMvcDetector(context, constantResolver); this.messagingDetector = new MessagingDetector(context); - this.callGraphEngine = new HeuristicCallGraphEngine(context); + + SpringBeanRegistry registry = new SpringBeanRegistry(); + SpringContextScanner scanner = new SpringContextScanner(registry); + for (CompilationUnit cu : context.getCompilationUnits()) { + cu.accept(scanner); + } + SpringDependencyResolver dependencyResolver = new SpringDependencyResolver(registry); + InjectionPointAnalyzer injectionAnalyzer = new InjectionPointAnalyzer(dependencyResolver); + + this.callGraphEngine = new JdtCallGraphEngine(context, injectionAnalyzer); + 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/InjectionPointAnalyzer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/InjectionPointAnalyzer.java new file mode 100644 index 0000000..ca241d7 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/InjectionPointAnalyzer.java @@ -0,0 +1,83 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IMemberValuePairBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; + +import java.util.List; + +public class InjectionPointAnalyzer { + + private final SpringDependencyResolver resolver; + + public InjectionPointAnalyzer(SpringDependencyResolver resolver) { + this.resolver = resolver; + } + + /** + * Resolves the Spring bean injected into the given variable. + * + * @param variableBinding The binding of the field or parameter being injected. + * @return The FQN of the resolved concrete bean class, or null if unresolved. + */ + public String resolveInjectedBeanFqn(IVariableBinding variableBinding) { + if (variableBinding == null || variableBinding.getType() == null) { + return null; + } + + String requiredTypeFqn = variableBinding.getType().getQualifiedName(); + String injectionName = variableBinding.getName(); + String qualifier = extractQualifier(variableBinding); + + List resolvedBeans = resolver.resolve(requiredTypeFqn, qualifier, injectionName); + + if (resolvedBeans != null && resolvedBeans.size() == 1) { + return resolvedBeans.get(0).getTypeFqn(); + } + + return null; + } + + private String extractQualifier(IVariableBinding binding) { + String qualifier = getQualifierValue(binding.getAnnotations()); + if (qualifier != null) { + return qualifier; + } + + if (binding.isField()) { + org.eclipse.jdt.core.dom.ITypeBinding declaringClass = binding.getDeclaringClass(); + if (declaringClass != null) { + for (org.eclipse.jdt.core.dom.IMethodBinding method : declaringClass.getDeclaredMethods()) { + if (method.isConstructor()) { + for (int i = 0; i < method.getParameterTypes().length; i++) { + org.eclipse.jdt.core.dom.ITypeBinding paramType = method.getParameterTypes()[i]; + try { + IAnnotationBinding[] paramAnns = method.getParameterAnnotations(i); + String paramQual = getQualifierValue(paramAnns); + if (paramQual != null && paramType.getErasure().isEqualTo(binding.getType().getErasure())) { + return paramQual; + } + } catch (Exception e) { + // Ignore + } + } + } + } + } + } + return null; + } + + private String getQualifierValue(IAnnotationBinding[] annotations) { + for (IAnnotationBinding ann : annotations) { + if (ann.getAnnotationType() != null && "org.springframework.beans.factory.annotation.Qualifier".equals(ann.getAnnotationType().getQualifiedName())) { + for (IMemberValuePairBinding pair : ann.getDeclaredMemberValuePairs()) { + if ("value".equals(pair.getName()) && pair.getValue() instanceof String) { + return (String) pair.getValue(); + } + } + } + } + return null; + } +} 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..a91f80c --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBean.java @@ -0,0 +1,26 @@ +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 { + private String typeFqn; + + @Builder.Default + private Set assignableTypes = new HashSet<>(); + + @Builder.Default + private Set beanNames = new HashSet<>(); + + @Builder.Default + private Set qualifiers = new HashSet<>(); + + private boolean isPrimary; + private String declaringClassFqn; + 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..e793546 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringBeanRegistry.java @@ -0,0 +1,17 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SpringBeanRegistry { + private final List beans = new ArrayList<>(); + + public void addBean(SpringBean bean) { + beans.add(bean); + } + + public List getBeans() { + return Collections.unmodifiableList(beans); + } +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringContextScanner.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringContextScanner.java new file mode 100644 index 0000000..a69ef37 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringContextScanner.java @@ -0,0 +1,179 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import org.eclipse.jdt.core.dom.*; + +import java.util.HashSet; +import java.util.Set; + +public class SpringContextScanner extends ASTVisitor { + private final SpringBeanRegistry registry; + + public SpringContextScanner(SpringBeanRegistry registry) { + this.registry = registry; + } + + @Override + public boolean visit(TypeDeclaration node) { + ITypeBinding typeBinding = node.resolveBinding(); + if (typeBinding == null) return true; + + if (isSpringComponent(typeBinding)) { + SpringBean bean = SpringBean.builder() + .typeFqn(typeBinding.getQualifiedName()) + .assignableTypes(getAssignableTypes(typeBinding)) + .isPrimary(hasAnnotation(typeBinding, "org.springframework.context.annotation.Primary")) + .qualifiers(extractQualifiers(typeBinding)) + .declaringClassFqn(typeBinding.getQualifiedName()) + .build(); + + // Default bean name is uncapitalized simple name + String defaultName = Character.toLowerCase(node.getName().getIdentifier().charAt(0)) + node.getName().getIdentifier().substring(1); + + // Check if @Component or @Service specifies a name (e.g., @Service("myService")) + String explicitName = extractStereotypeValue(typeBinding); + if (explicitName != null && !explicitName.isEmpty()) { + bean.getBeanNames().add(explicitName); + } else { + bean.getBeanNames().add(defaultName); + } + + registry.addBean(bean); + } + return true; + } + + @Override + public boolean visit(MethodDeclaration node) { + IMethodBinding methodBinding = node.resolveBinding(); + if (methodBinding == null) return true; + + if (hasAnnotation(methodBinding, "org.springframework.context.annotation.Bean")) { + ITypeBinding returnType = methodBinding.getReturnType(); + if (returnType != null) { + SpringBean bean = SpringBean.builder() + .typeFqn(returnType.getQualifiedName()) + .assignableTypes(getAssignableTypes(returnType)) + .isPrimary(hasAnnotation(methodBinding, "org.springframework.context.annotation.Primary")) + .qualifiers(extractQualifiers(methodBinding)) + .declaringClassFqn(methodBinding.getDeclaringClass().getQualifiedName()) + .factoryMethodName(node.getName().getIdentifier()) + .build(); + + // Default bean name is method name + String defaultName = node.getName().getIdentifier(); + + // Check if @Bean specifies a name (e.g., @Bean("myBean")) + String explicitName = extractBeanName(methodBinding); + if (explicitName != null && !explicitName.isEmpty()) { + bean.getBeanNames().add(explicitName); + } else { + bean.getBeanNames().add(defaultName); + } + + registry.addBean(bean); + } + } + return true; + } + + private boolean isSpringComponent(ITypeBinding binding) { + return hasMetaAnnotation(binding, "org.springframework.stereotype.Component") || + hasMetaAnnotation(binding, "org.springframework.web.bind.annotation.RestController"); + } + + private boolean hasMetaAnnotation(ITypeBinding binding, String targetFqn) { + return checkMetaAnnotation(binding.getAnnotations(), targetFqn, new HashSet<>()); + } + + private boolean checkMetaAnnotation(IAnnotationBinding[] annotations, String targetFqn, Set visited) { + for (IAnnotationBinding ann : annotations) { + ITypeBinding annType = ann.getAnnotationType(); + if (annType != null) { + String fqn = annType.getQualifiedName(); + if (fqn.equals(targetFqn)) return true; + + // Avoid circular meta-annotations (e.g. Documented -> Documented) + if (visited.add(fqn)) { + if (checkMetaAnnotation(annType.getAnnotations(), targetFqn, visited)) { + return true; + } + } + } + } + return false; + } + + private boolean hasAnnotation(IBinding binding, String annotationFqn) { + IAnnotationBinding[] annotations = binding.getAnnotations(); + for (IAnnotationBinding ann : annotations) { + if (ann.getAnnotationType() != null && annotationFqn.equals(ann.getAnnotationType().getQualifiedName())) { + return true; + } + } + return false; + } + + private Set getAssignableTypes(ITypeBinding binding) { + Set types = new HashSet<>(); + collectAssignableTypes(binding, types); + return types; + } + + private void collectAssignableTypes(ITypeBinding binding, Set types) { + if (binding == null) return; + types.add(binding.getQualifiedName()); + types.add(binding.getErasure().getQualifiedName()); // Handles generics (e.g., List -> List) + + collectAssignableTypes(binding.getSuperclass(), types); + for (ITypeBinding iface : binding.getInterfaces()) { + collectAssignableTypes(iface, types); + } + } + + private Set extractQualifiers(IBinding binding) { + Set qualifiers = new HashSet<>(); + for (IAnnotationBinding ann : binding.getAnnotations()) { + if (ann.getAnnotationType() != null && "org.springframework.beans.factory.annotation.Qualifier".equals(ann.getAnnotationType().getQualifiedName())) { + for (IMemberValuePairBinding pair : ann.getDeclaredMemberValuePairs()) { + if ("value".equals(pair.getName()) && pair.getValue() instanceof String) { + qualifiers.add((String) pair.getValue()); + } + } + } + } + return qualifiers; + } + + private String extractStereotypeValue(ITypeBinding binding) { + for (IAnnotationBinding ann : binding.getAnnotations()) { + // Check if annotation itself is meta-annotated with @Component + if (checkMetaAnnotation(new IAnnotationBinding[]{ann}, "org.springframework.stereotype.Component", new HashSet<>()) || + "org.springframework.stereotype.Component".equals(ann.getAnnotationType().getQualifiedName())) { + for (IMemberValuePairBinding pair : ann.getDeclaredMemberValuePairs()) { + if ("value".equals(pair.getName()) && pair.getValue() instanceof String) { + return (String) pair.getValue(); + } + } + } + } + return null; + } + + private String extractBeanName(IMethodBinding binding) { + for (IAnnotationBinding ann : binding.getAnnotations()) { + if ("org.springframework.context.annotation.Bean".equals(ann.getAnnotationType().getQualifiedName())) { + for (IMemberValuePairBinding pair : ann.getDeclaredMemberValuePairs()) { + if (("value".equals(pair.getName()) || "name".equals(pair.getName())) && pair.getValue() instanceof String) { + return (String) pair.getValue(); + } else if (("value".equals(pair.getName()) || "name".equals(pair.getName())) && pair.getValue() instanceof Object[]) { + Object[] vals = (Object[]) pair.getValue(); + if (vals.length > 0 && vals[0] instanceof String) { + return (String) vals[0]; + } + } + } + } + } + return null; + } +} 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..cb02aaa --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolver.java @@ -0,0 +1,67 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class SpringDependencyResolver { + private final SpringBeanRegistry registry; + + public SpringDependencyResolver(SpringBeanRegistry registry) { + this.registry = registry; + } + + /** + * Resolves the exact SpringBean that would be injected for a given injection point. + * + * @param requiredTypeFqn The exact FQN of the type requested at the injection point. + * @param qualifier The value of the @Qualifier annotation on the injection point (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. + */ + public List resolve(String requiredTypeFqn, String qualifier, String injectionName) { + if (requiredTypeFqn == null) return new ArrayList<>(); + + // 1. Filter by Type (Exact FQN or Assignable Type) + List candidates = registry.getBeans().stream() + .filter(bean -> requiredTypeFqn.equals(bean.getTypeFqn()) || bean.getAssignableTypes().contains(requiredTypeFqn)) + .collect(Collectors.toList()); + + if (candidates.isEmpty() || candidates.size() == 1) { + return candidates; + } + + // 2. 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; + } + } + + // 3. Filter by @Primary + List primaryCandidates = candidates.stream() + .filter(SpringBean::isPrimary) + .collect(Collectors.toList()); + if (primaryCandidates.size() == 1) { + return primaryCandidates; + } + + // 4. 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/test/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngineIntegrationTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngineIntegrationTest.java new file mode 100644 index 0000000..6e45f55 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngineIntegrationTest.java @@ -0,0 +1,138 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.analysis.model.CallChain; +import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer; +import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry; +import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner; +import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import click.kamil.springstatemachineexporter.analysis.service.DynamicClasspathResolver; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class JdtCallGraphEngineIntegrationTest { + + private CodebaseContext context; + private SpringBeanRegistry registry; + private JdtCallGraphEngine engine; + + @BeforeEach + void setUp() throws IOException { + Path projectRoot = Path.of("../state_machines/extended_analysis_sample").toAbsolutePath().normalize(); + + context = new CodebaseContext(); + context.setProjectRoot(projectRoot); + context.setSourcepath(List.of(projectRoot.resolve("src/main/java").toString())); + + DynamicClasspathResolver resolver = new DynamicClasspathResolver(); + List cp = resolver.resolveClasspath(projectRoot); + if (!cp.isEmpty()) { + context.setClasspath(cp); + } + context.setResolveBindings(true); + + context.scan(Set.of(projectRoot), Collections.emptySet()); + + registry = new SpringBeanRegistry(); + SpringContextScanner scanner = new SpringContextScanner(registry); + + for (var cu : context.getCompilationUnits()) { + cu.accept(scanner); + } + + SpringDependencyResolver dependencyResolver = new SpringDependencyResolver(registry); + InjectionPointAnalyzer injectionAnalyzer = new InjectionPointAnalyzer(dependencyResolver); + + engine = new JdtCallGraphEngine(context, injectionAnalyzer); + } + + @Test + void shouldResolvePrimaryBeanInjection() { + EntryPoint ep = EntryPoint.builder() + .className("click.kamil.examples.statemachine.extended.web.QuirkController") + .methodName("testPrimary") + .build(); + TriggerPoint tp = TriggerPoint.builder() + .className("click.kamil.examples.statemachine.extended.service.PrimaryQuirkService") + .methodName("doQuirk") + .build(); + + List chains = engine.findChains(List.of(ep), List.of(tp)); + assertThat(chains).isNotEmpty(); + assertThat(chains.get(0).getMethodChain()) + .containsExactly( + "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", + "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" + ); + } + + @Test + void shouldResolveQualifierBeanInjection() { + EntryPoint ep = EntryPoint.builder() + .className("click.kamil.examples.statemachine.extended.web.QuirkController") + .methodName("testQualifier") + .build(); + TriggerPoint tp = TriggerPoint.builder() + .className("click.kamil.examples.statemachine.extended.service.QualifierQuirkService") + .methodName("doQuirk") + .build(); + + List chains = engine.findChains(List.of(ep), List.of(tp)); + assertThat(chains).isNotEmpty(); + assertThat(chains.get(0).getMethodChain()) + .containsExactly( + "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", + "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" + ); + } + + @Test + void shouldResolveNamedBeanInjection() { + EntryPoint ep = EntryPoint.builder() + .className("click.kamil.examples.statemachine.extended.web.QuirkController") + .methodName("testNamed") + .build(); + TriggerPoint tp = TriggerPoint.builder() + .className("click.kamil.examples.statemachine.extended.service.NamedQuirkService") + .methodName("doQuirk") + .build(); + + List chains = engine.findChains(List.of(ep), List.of(tp)); + assertThat(chains).isNotEmpty(); + assertThat(chains.get(0).getMethodChain()) + .containsExactly( + "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", + "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" + ); + } + + @Test + void shouldResolveFallbackBeanInjection() { + EntryPoint ep = EntryPoint.builder() + .className("click.kamil.examples.statemachine.extended.web.QuirkController") + .methodName("testFallback") + .build(); + TriggerPoint tp = TriggerPoint.builder() + .className("click.kamil.examples.statemachine.extended.service.FallbackQuirkService") + .methodName("doQuirk") + .build(); + + List chains = engine.findChains(List.of(ep), List.of(tp)); + assertThat(chains).isNotEmpty(); + assertThat(chains.get(0).getMethodChain()) + .containsExactly( + "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", + "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" + ); + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringContextScannerIntegrationTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringContextScannerIntegrationTest.java new file mode 100644 index 0000000..a51121b --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringContextScannerIntegrationTest.java @@ -0,0 +1,89 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import click.kamil.springstatemachineexporter.analysis.service.DynamicClasspathResolver; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class SpringContextScannerIntegrationTest { + + private CodebaseContext context; + private SpringBeanRegistry registry; + + @BeforeEach + void setUp() throws IOException { + Path projectRoot = Path.of("../state_machines/extended_analysis_sample").toAbsolutePath().normalize(); + + context = new CodebaseContext(); + context.setProjectRoot(projectRoot); + context.setSourcepath(List.of(projectRoot.resolve("src/main/java").toString())); + + DynamicClasspathResolver resolver = new DynamicClasspathResolver(); + List cp = resolver.resolveClasspath(projectRoot); + if (!cp.isEmpty()) { + context.setClasspath(cp); + } + context.setResolveBindings(true); + + context.scan(Set.of(projectRoot), Collections.emptySet()); + + registry = new SpringBeanRegistry(); + SpringContextScanner scanner = new SpringContextScanner(registry); + + for (var cu : context.getCompilationUnits()) { + cu.accept(scanner); + } + } + + @Test + void shouldFindStereotypeBeans() { + List beans = registry.getBeans(); + + // PrimaryQuirkService + assertThat(beans).anySatisfy(bean -> { + assertThat(bean.getTypeFqn()).isEqualTo("click.kamil.examples.statemachine.extended.service.PrimaryQuirkService"); + assertThat(bean.isPrimary()).isTrue(); + assertThat(bean.getAssignableTypes()).contains("click.kamil.examples.statemachine.extended.service.QuirkService"); + assertThat(bean.getBeanNames()).contains("primaryQuirkService"); + }); + + // QualifierQuirkService + assertThat(beans).anySatisfy(bean -> { + assertThat(bean.getTypeFqn()).isEqualTo("click.kamil.examples.statemachine.extended.service.QualifierQuirkService"); + assertThat(bean.isPrimary()).isFalse(); + assertThat(bean.getBeanNames()).contains("qualifierQuirkService"); + }); + + // NamedQuirkService (should have explicit name via @Service("customName")) + assertThat(beans).anySatisfy(bean -> { + assertThat(bean.getTypeFqn()).isEqualTo("click.kamil.examples.statemachine.extended.service.NamedQuirkService"); + assertThat(bean.getBeanNames()).contains("customName"); + }); + + // PaymentController (should be detected via @RestController) + assertThat(beans).anySatisfy(bean -> { + assertThat(bean.getTypeFqn()).isEqualTo("click.kamil.examples.statemachine.extended.web.PaymentController"); + }); + } + + @Test + void shouldFindMethodBeans() { + List beans = registry.getBeans(); + + // Beans created via @Bean + assertThat(beans).anySatisfy(bean -> { + assertThat(bean.getTypeFqn()).isEqualTo("java.lang.String"); + assertThat(bean.getFactoryMethodName()).isEqualTo("myStringBean"); + assertThat(bean.getBeanNames()).contains("customMockBean"); + assertThat(bean.getDeclaringClassFqn()).isEqualTo("click.kamil.examples.statemachine.extended.config.MockBeanConfig"); + }); + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolverTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolverTest.java new file mode 100644 index 0000000..2480dac --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/spring/SpringDependencyResolverTest.java @@ -0,0 +1,95 @@ +package click.kamil.springstatemachineexporter.analysis.spring; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class SpringDependencyResolverTest { + + private SpringBeanRegistry registry; + private SpringDependencyResolver resolver; + + @BeforeEach + void setUp() { + registry = new SpringBeanRegistry(); + resolver = new SpringDependencyResolver(registry); + } + + @Test + void shouldResolveSingleMatchByType() { + registry.addBean(SpringBean.builder() + .typeFqn("com.example.PaymentServiceImpl") + .assignableTypes(Set.of("com.example.PaymentService")) + .build()); + + List resolved = resolver.resolve("com.example.PaymentService", null, "paymentService"); + + assertThat(resolved).hasSize(1); + assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.PaymentServiceImpl"); + } + + @Test + void shouldResolveByPrimaryWhenMultipleImplementationsExist() { + registry.addBean(SpringBean.builder() + .typeFqn("com.example.NormalService") + .assignableTypes(Set.of("com.example.PaymentService")) + .isPrimary(false) + .build()); + + registry.addBean(SpringBean.builder() + .typeFqn("com.example.PrimaryService") + .assignableTypes(Set.of("com.example.PaymentService")) + .isPrimary(true) + .build()); + + List resolved = resolver.resolve("com.example.PaymentService", null, "paymentService"); + + assertThat(resolved).hasSize(1); + assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.PrimaryService"); + } + + @Test + void shouldResolveByQualifierWhenMultipleImplementationsExist() { + registry.addBean(SpringBean.builder() + .typeFqn("com.example.ServiceA") + .assignableTypes(Set.of("com.example.PaymentService")) + .qualifiers(Set.of("serviceA")) + .build()); + + registry.addBean(SpringBean.builder() + .typeFqn("com.example.ServiceB") + .assignableTypes(Set.of("com.example.PaymentService")) + .qualifiers(Set.of("serviceB")) + .build()); + + List resolved = resolver.resolve("com.example.PaymentService", "serviceB", "paymentService"); + + assertThat(resolved).hasSize(1); + assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceB"); + } + + @Test + void shouldResolveByInjectionNameFallback() { + registry.addBean(SpringBean.builder() + .typeFqn("com.example.ServiceA") + .assignableTypes(Set.of("com.example.PaymentService")) + .beanNames(Set.of("serviceA")) + .build()); + + registry.addBean(SpringBean.builder() + .typeFqn("com.example.ServiceB") + .assignableTypes(Set.of("com.example.PaymentService")) + .beanNames(Set.of("specialService")) + .build()); + + // Qualifier is null, Primary is false. Should match by variable name "specialService" + List resolved = resolver.resolve("com.example.PaymentService", null, "specialService"); + + assertThat(resolved).hasSize(1); + assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceB"); + } +} 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..f097d90 100644 --- a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json @@ -20,6 +20,36 @@ "sourceState" : null, "lineNumber" : 16, "polymorphicEvents" : null + }, { + "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 + }, { + "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 + }, { + "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 }, { "event" : "EXTERNAL_TRIGGER", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -60,6 +90,56 @@ "sourceState" : null, "lineNumber" : 29, "polymorphicEvents" : null + }, { + "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 + }, { + "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 + }, { + "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 + }, { + "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 + }, { + "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 }, { "event" : "REACTIVE_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", @@ -144,6 +224,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" : { @@ -365,6 +519,639 @@ "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.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 + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.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 + }, + "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.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 + }, + "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.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 + }, + "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 + }, + "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.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 + }, + "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 + }, + "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.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 + }, + "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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 + }, + "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.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 + }, + "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.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 + }, + "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 + }, + "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 + }, + "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 + }, + "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..b29b8f4 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 @@ -20,6 +20,36 @@ "sourceState" : null, "lineNumber" : 16, "polymorphicEvents" : null + }, { + "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 + }, { + "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 + }, { + "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 }, { "event" : "EXTERNAL_TRIGGER", "className" : "click.kamil.examples.statemachine.extended.service.OrderService", @@ -60,6 +90,56 @@ "sourceState" : null, "lineNumber" : 29, "polymorphicEvents" : null + }, { + "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 + }, { + "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 + }, { + "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 + }, { + "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 + }, { + "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 }, { "event" : "REACTIVE_EVENT", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", @@ -144,6 +224,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" : { @@ -365,6 +519,639 @@ "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.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 + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.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 + }, + "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.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 + }, + "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.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 + }, + "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 + }, + "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.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 + }, + "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 + }, + "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.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 + }, + "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.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "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 + }, + "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 + }, + "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.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 + }, + "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.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 + }, + "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 + }, + "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 + }, + "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 + }, + "contextMachineId" : "paymentId", + "matchedTransitions" : null } ], "properties" : { "default" : { diff --git a/state_machines/extended_analysis_sample/.gitignore b/state_machines/extended_analysis_sample/.gitignore index 60166cb..c2065bc 100644 --- a/state_machines/extended_analysis_sample/.gitignore +++ b/state_machines/extended_analysis_sample/.gitignore @@ -1,6 +1,37 @@ +HELP.md +.gradle build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr out/ -.gradle/ -.idea/ -*.class -*.log +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/MockBeanConfig.java b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/MockBeanConfig.java new file mode 100644 index 0000000..05346c3 --- /dev/null +++ b/state_machines/extended_analysis_sample/src/main/java/click/kamil/examples/statemachine/extended/config/MockBeanConfig.java @@ -0,0 +1,13 @@ +package click.kamil.examples.statemachine.extended.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MockBeanConfig { + + @Bean(name = {"customMockBean", "aliasMockBean"}) + public String myStringBean() { + return "Hello World"; + } +} 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(); + } +}