v2 extended analysis render
This commit is contained in:
@@ -15,4 +15,5 @@ include ':state_machines:inheritance_extra_functions3_state_machine'
|
|||||||
include ':state_machines:simple_state_machine'
|
include ':state_machines:simple_state_machine'
|
||||||
include ':state_machines:extended_analysis_sample'
|
include ':state_machines:extended_analysis_sample'
|
||||||
include ':state_machines:inheritance_sample'
|
include ':state_machines:inheritance_sample'
|
||||||
|
include ':state_machines:ultimate_ecosystem_sm'
|
||||||
include ':state_machines:enterprise_order_system'
|
include ':state_machines:enterprise_order_system'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -12,11 +13,23 @@ import java.util.Map;
|
|||||||
@Jacksonized
|
@Jacksonized
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class EntryPoint {
|
public class EntryPoint {
|
||||||
public enum Type { REST, KAFKA, RABBIT, JMS, CUSTOM }
|
public enum Type { REST, KAFKA, RABBIT, JMS, SQS, SNS, CUSTOM }
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class Parameter {
|
||||||
|
private final String name;
|
||||||
|
private final String type;
|
||||||
|
private final List<String> annotations;
|
||||||
|
}
|
||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
private final String name; // e.g., "POST /orders" or "Kafka Topic: orders"
|
private final String name; // e.g., "POST /orders" or "Kafka Topic: orders"
|
||||||
private final String className;
|
private final String className;
|
||||||
private final String methodName;
|
private final String methodName;
|
||||||
private final Map<String, String> metadata; // e.g., path, topic, exchange
|
private final Map<String, String> metadata; // e.g., path, topic, exchange
|
||||||
|
@Builder.Default
|
||||||
|
private final List<Parameter> parameters = java.util.Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
|||||||
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
import click.kamil.springstatemachineexporter.analysis.resolver.PropertyResolver;
|
|
||||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -21,6 +20,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
|||||||
private final ConstantResolver constantResolver = new ConstantResolver();
|
private final ConstantResolver constantResolver = new ConstantResolver();
|
||||||
private final GenericEventDetector eventDetector;
|
private final GenericEventDetector eventDetector;
|
||||||
private final SpringMvcDetector mvcDetector;
|
private final SpringMvcDetector mvcDetector;
|
||||||
|
private final MessagingDetector messagingDetector;
|
||||||
private final CallGraphBuilder callGraphBuilder;
|
private final CallGraphBuilder callGraphBuilder;
|
||||||
private final LifecycleDetector lifecycleDetector;
|
private final LifecycleDetector lifecycleDetector;
|
||||||
private final InterceptorDetector interceptorDetector;
|
private final InterceptorDetector interceptorDetector;
|
||||||
@@ -30,6 +30,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
|||||||
this.rootDir = rootDir;
|
this.rootDir = rootDir;
|
||||||
this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints());
|
this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints());
|
||||||
this.mvcDetector = new SpringMvcDetector(context, constantResolver);
|
this.mvcDetector = new SpringMvcDetector(context, constantResolver);
|
||||||
|
this.messagingDetector = new MessagingDetector(context);
|
||||||
this.callGraphBuilder = new CallGraphBuilder(context);
|
this.callGraphBuilder = new CallGraphBuilder(context);
|
||||||
this.lifecycleDetector = new LifecycleDetector(context);
|
this.lifecycleDetector = new LifecycleDetector(context);
|
||||||
this.interceptorDetector = new InterceptorDetector(context);
|
this.interceptorDetector = new InterceptorDetector(context);
|
||||||
@@ -37,7 +38,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TriggerPoint> findTriggerPoints() {
|
public List<TriggerPoint> findTriggerPoints() {
|
||||||
List<TriggerPoint> allTriggers = new java.util.ArrayList<>();
|
List<TriggerPoint> allTriggers = new ArrayList<>();
|
||||||
var cus = context.getCompilationUnits();
|
var cus = context.getCompilationUnits();
|
||||||
log.info("JdtIntelligenceProvider scanning {} compilation units for triggers", cus.size());
|
log.info("JdtIntelligenceProvider scanning {} compilation units for triggers", cus.size());
|
||||||
for (CompilationUnit cu : cus) {
|
for (CompilationUnit cu : cus) {
|
||||||
@@ -50,14 +51,15 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<EntryPoint> findEntryPoints() {
|
public List<EntryPoint> findEntryPoints() {
|
||||||
List<EntryPoint> allEntryPoints = new java.util.ArrayList<>();
|
List<EntryPoint> allEntryPoints = new ArrayList<>();
|
||||||
var cus = context.getCompilationUnits();
|
var cus = context.getCompilationUnits();
|
||||||
log.info("JdtIntelligenceProvider scanning {} compilation units for entry points", cus.size());
|
log.info("JdtIntelligenceProvider scanning {} compilation units for entry points", cus.size());
|
||||||
for (CompilationUnit cu : cus) {
|
for (CompilationUnit cu : cus) {
|
||||||
allEntryPoints.addAll(mvcDetector.detect(cu));
|
allEntryPoints.addAll(mvcDetector.detect(cu));
|
||||||
|
allEntryPoints.addAll(messagingDetector.detect(cu));
|
||||||
allEntryPoints.addAll(interceptorDetector.detect(cu));
|
allEntryPoints.addAll(interceptorDetector.detect(cu));
|
||||||
}
|
}
|
||||||
log.info("Found {} entry points (including interceptors) in total", allEntryPoints.size());
|
log.info("Found {} entry points (including interceptors and listeners) in total", allEntryPoints.size());
|
||||||
return allEntryPoints;
|
return allEntryPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MessagingDetector {
|
||||||
|
private final CodebaseContext context;
|
||||||
|
private final ConstantResolver constantResolver = new ConstantResolver();
|
||||||
|
|
||||||
|
public List<EntryPoint> detect(CompilationUnit cu) {
|
||||||
|
List<EntryPoint> entryPoints = new ArrayList<>();
|
||||||
|
cu.accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(MethodDeclaration node) {
|
||||||
|
processMethod(node, entryPoints);
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return entryPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processMethod(MethodDeclaration method, List<EntryPoint> entryPoints) {
|
||||||
|
for (Object modifier : method.modifiers()) {
|
||||||
|
if (modifier instanceof Annotation annotation) {
|
||||||
|
String typeName = annotation.getTypeName().getFullyQualifiedName();
|
||||||
|
|
||||||
|
EntryPoint.Type type = null;
|
||||||
|
String destination = "";
|
||||||
|
|
||||||
|
if (typeName.endsWith("JmsListener")) {
|
||||||
|
type = EntryPoint.Type.JMS;
|
||||||
|
destination = resolveValue(annotation, "destination");
|
||||||
|
} else if (typeName.endsWith("RabbitListener")) {
|
||||||
|
type = EntryPoint.Type.RABBIT;
|
||||||
|
destination = resolveValue(annotation, "queues");
|
||||||
|
} else if (typeName.endsWith("SqsListener")) {
|
||||||
|
type = EntryPoint.Type.SQS;
|
||||||
|
destination = resolveValue(annotation, "value");
|
||||||
|
if ("unknown".equals(destination)) {
|
||||||
|
destination = resolveValue(annotation, "queueNames");
|
||||||
|
}
|
||||||
|
} else if (typeName.endsWith("SnsListener")) {
|
||||||
|
type = EntryPoint.Type.SNS;
|
||||||
|
destination = resolveValue(annotation, "value");
|
||||||
|
if ("unknown".equals(destination)) {
|
||||||
|
destination = resolveValue(annotation, "topicNames");
|
||||||
|
}
|
||||||
|
} else if (typeName.endsWith("KafkaListener")) {
|
||||||
|
type = EntryPoint.Type.KAFKA;
|
||||||
|
destination = resolveValue(annotation, "topics");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
Map<String, String> metadata = new HashMap<>();
|
||||||
|
metadata.put("destination", destination);
|
||||||
|
|
||||||
|
String protocolName = type.name();
|
||||||
|
if (typeName.endsWith("SqsListener")) protocolName = "SQS";
|
||||||
|
if (typeName.endsWith("SnsListener")) protocolName = "SNS";
|
||||||
|
metadata.put("protocol", protocolName);
|
||||||
|
|
||||||
|
entryPoints.add(EntryPoint.builder()
|
||||||
|
.type(type)
|
||||||
|
.name(protocolName + ": " + destination)
|
||||||
|
.className(context.getFqn((TypeDeclaration) method.getParent()))
|
||||||
|
.methodName(method.getName().getIdentifier())
|
||||||
|
.metadata(metadata)
|
||||||
|
.parameters(extractParameters(method))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveValue(Annotation annotation, String memberName) {
|
||||||
|
Expression value = null;
|
||||||
|
if (annotation instanceof SingleMemberAnnotation sma) {
|
||||||
|
value = sma.getValue();
|
||||||
|
} else if (annotation instanceof NormalAnnotation na) {
|
||||||
|
for (Object pairObj : na.values()) {
|
||||||
|
MemberValuePair pair = (MemberValuePair) pairObj;
|
||||||
|
if (memberName.equals(pair.getName().getIdentifier())) {
|
||||||
|
value = pair.getValue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
if (value instanceof ArrayInitializer ai) {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
for (Object o : ai.expressions()) {
|
||||||
|
String resolved = constantResolver.resolve((Expression) o, context);
|
||||||
|
values.add(resolved != null ? resolved : o.toString());
|
||||||
|
}
|
||||||
|
return String.join(", ", values);
|
||||||
|
}
|
||||||
|
String resolved = constantResolver.resolve(value, context);
|
||||||
|
return resolved != null ? resolved : value.toString();
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EntryPoint.Parameter> extractParameters(MethodDeclaration method) {
|
||||||
|
List<EntryPoint.Parameter> parameters = new ArrayList<>();
|
||||||
|
for (Object paramObj : method.parameters()) {
|
||||||
|
if (paramObj instanceof SingleVariableDeclaration param) {
|
||||||
|
List<String> annotations = new ArrayList<>();
|
||||||
|
for (Object mod : param.modifiers()) {
|
||||||
|
if (mod instanceof Annotation ann) {
|
||||||
|
annotations.add(ann.getTypeName().getFullyQualifiedName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parameters.add(EntryPoint.Parameter.builder()
|
||||||
|
.name(param.getName().getIdentifier())
|
||||||
|
.type(param.getType().toString())
|
||||||
|
.annotations(annotations)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ public class SpringMvcDetector {
|
|||||||
for (Object modifier : node.modifiers()) {
|
for (Object modifier : node.modifiers()) {
|
||||||
if (modifier instanceof Annotation annotation) {
|
if (modifier instanceof Annotation annotation) {
|
||||||
String name = annotation.getTypeName().getFullyQualifiedName();
|
String name = annotation.getTypeName().getFullyQualifiedName();
|
||||||
if (name.endsWith("RestController") || name.endsWith("Controller")) {
|
if (name.endsWith("RestController") || name.endsWith("Controller") || name.endsWith("Path")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,10 @@ public class SpringMvcDetector {
|
|||||||
|
|
||||||
private void processController(TypeDeclaration controller, List<EntryPoint> entryPoints) {
|
private void processController(TypeDeclaration controller, List<EntryPoint> entryPoints) {
|
||||||
String basePath = getMappingPathHierarchical(controller);
|
String basePath = getMappingPathHierarchical(controller);
|
||||||
|
if (basePath.isEmpty()) {
|
||||||
|
basePath = getJaxRsPath(controller);
|
||||||
|
}
|
||||||
|
|
||||||
for (MethodDeclaration method : controller.getMethods()) {
|
for (MethodDeclaration method : controller.getMethods()) {
|
||||||
EntryPoint ep = processMethodHierarchical(method, basePath, controller);
|
EntryPoint ep = processMethodHierarchical(method, basePath, controller);
|
||||||
if (ep != null) {
|
if (ep != null) {
|
||||||
@@ -55,11 +59,9 @@ public class SpringMvcDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getMappingPathHierarchical(TypeDeclaration td) {
|
private String getMappingPathHierarchical(TypeDeclaration td) {
|
||||||
// 1. Check class itself
|
|
||||||
String path = getMappingPath(td);
|
String path = getMappingPath(td);
|
||||||
if (!path.isEmpty()) return path;
|
if (!path.isEmpty()) return path;
|
||||||
|
|
||||||
// 2. Check interfaces
|
|
||||||
for (Object interfaceType : td.superInterfaceTypes()) {
|
for (Object interfaceType : td.superInterfaceTypes()) {
|
||||||
TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
|
TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
|
||||||
if (itd != null) {
|
if (itd != null) {
|
||||||
@@ -68,7 +70,6 @@ public class SpringMvcDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check superclass
|
|
||||||
if (td.getSuperclassType() != null) {
|
if (td.getSuperclassType() != null) {
|
||||||
TypeDeclaration std = context.getTypeDeclaration(td.getSuperclassType().toString());
|
TypeDeclaration std = context.getTypeDeclaration(td.getSuperclassType().toString());
|
||||||
if (std != null) {
|
if (std != null) {
|
||||||
@@ -80,11 +81,9 @@ public class SpringMvcDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private EntryPoint processMethodHierarchical(MethodDeclaration method, String basePath, TypeDeclaration controller) {
|
private EntryPoint processMethodHierarchical(MethodDeclaration method, String basePath, TypeDeclaration controller) {
|
||||||
// 1. Check the method itself
|
|
||||||
EntryPoint ep = processMethod(method, basePath, controller);
|
EntryPoint ep = processMethod(method, basePath, controller);
|
||||||
if (ep != null) return ep;
|
if (ep != null) return ep;
|
||||||
|
|
||||||
// 2. Check interface methods
|
|
||||||
String methodName = method.getName().getIdentifier();
|
String methodName = method.getName().getIdentifier();
|
||||||
for (Object interfaceType : controller.superInterfaceTypes()) {
|
for (Object interfaceType : controller.superInterfaceTypes()) {
|
||||||
TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
|
TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
|
||||||
@@ -108,18 +107,24 @@ public class SpringMvcDetector {
|
|||||||
String verb = getHttpVerb(name);
|
String verb = getHttpVerb(name);
|
||||||
if (verb != null) {
|
if (verb != null) {
|
||||||
String methodPath = getMappingPath(annotation);
|
String methodPath = getMappingPath(annotation);
|
||||||
|
if (methodPath.isEmpty()) {
|
||||||
|
methodPath = getJaxRsPath(method);
|
||||||
|
}
|
||||||
String fullPath = combinePaths(basePath, methodPath);
|
String fullPath = combinePaths(basePath, methodPath);
|
||||||
|
|
||||||
Map<String, String> metadata = new HashMap<>();
|
Map<String, String> metadata = new HashMap<>();
|
||||||
metadata.put("path", fullPath);
|
metadata.put("path", fullPath);
|
||||||
metadata.put("verb", verb);
|
metadata.put("verb", verb);
|
||||||
|
|
||||||
|
List<EntryPoint.Parameter> parameters = extractParameters(method);
|
||||||
|
|
||||||
return EntryPoint.builder()
|
return EntryPoint.builder()
|
||||||
.type(EntryPoint.Type.REST)
|
.type(EntryPoint.Type.REST)
|
||||||
.name(verb + " " + fullPath)
|
.name(verb + " " + fullPath)
|
||||||
.className(context.getFqn(controller))
|
.className(context.getFqn(controller))
|
||||||
.methodName(method.getName().getIdentifier())
|
.methodName(method.getName().getIdentifier())
|
||||||
.metadata(metadata)
|
.metadata(metadata)
|
||||||
|
.parameters(parameters)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,12 +132,21 @@ public class SpringMvcDetector {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getJaxRsPath(BodyDeclaration node) {
|
||||||
|
for (Object mod : node.modifiers()) {
|
||||||
|
if (mod instanceof Annotation ann && ann.getTypeName().getFullyQualifiedName().endsWith("Path")) {
|
||||||
|
return getMappingPath(ann);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
private String getHttpVerb(String annotationName) {
|
private String getHttpVerb(String annotationName) {
|
||||||
if (annotationName.endsWith("PostMapping")) return "POST";
|
if (annotationName.endsWith("PostMapping") || annotationName.endsWith("POST")) return "POST";
|
||||||
if (annotationName.endsWith("GetMapping")) return "GET";
|
if (annotationName.endsWith("GetMapping") || annotationName.endsWith("GET")) return "GET";
|
||||||
if (annotationName.endsWith("PutMapping")) return "PUT";
|
if (annotationName.endsWith("PutMapping") || annotationName.endsWith("PUT")) return "PUT";
|
||||||
if (annotationName.endsWith("DeleteMapping")) return "DELETE";
|
if (annotationName.endsWith("DeleteMapping") || annotationName.endsWith("DELETE")) return "DELETE";
|
||||||
if (annotationName.endsWith("PatchMapping")) return "PATCH";
|
if (annotationName.endsWith("PatchMapping") || annotationName.endsWith("PATCH")) return "PATCH";
|
||||||
if (annotationName.endsWith("RequestMapping")) return "REQUEST";
|
if (annotationName.endsWith("RequestMapping")) return "REQUEST";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -140,7 +154,8 @@ public class SpringMvcDetector {
|
|||||||
private String getMappingPath(TypeDeclaration td) {
|
private String getMappingPath(TypeDeclaration td) {
|
||||||
for (Object modifier : td.modifiers()) {
|
for (Object modifier : td.modifiers()) {
|
||||||
if (modifier instanceof Annotation annotation) {
|
if (modifier instanceof Annotation annotation) {
|
||||||
if (annotation.getTypeName().getFullyQualifiedName().endsWith("RequestMapping")) {
|
String name = annotation.getTypeName().getFullyQualifiedName();
|
||||||
|
if (name.endsWith("RequestMapping") || name.endsWith("Path")) {
|
||||||
return getMappingPath(annotation);
|
return getMappingPath(annotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,6 +184,29 @@ public class SpringMvcDetector {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<EntryPoint.Parameter> extractParameters(MethodDeclaration method) {
|
||||||
|
List<EntryPoint.Parameter> parameters = new ArrayList<>();
|
||||||
|
for (Object paramObj : method.parameters()) {
|
||||||
|
if (paramObj instanceof SingleVariableDeclaration param) {
|
||||||
|
List<String> annotations = new ArrayList<>();
|
||||||
|
for (Object mod : param.modifiers()) {
|
||||||
|
if (mod instanceof Annotation ann) {
|
||||||
|
String name = ann.getTypeName().getFullyQualifiedName();
|
||||||
|
if (name.endsWith("PathParam")) name = "PathVariable";
|
||||||
|
if (name.endsWith("QueryParam")) name = "RequestParam";
|
||||||
|
annotations.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parameters.add(EntryPoint.Parameter.builder()
|
||||||
|
.name(param.getName().getIdentifier())
|
||||||
|
.type(param.getType().toString())
|
||||||
|
.annotations(annotations)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
private String combinePaths(String base, String method) {
|
private String combinePaths(String base, String method) {
|
||||||
String b = base.startsWith("/") ? base : "/" + base;
|
String b = base.startsWith("/") ? base : "/" + base;
|
||||||
if (b.endsWith("/")) b = b.substring(0, b.length() - 1);
|
if (b.endsWith("/")) b = b.substring(0, b.length() - 1);
|
||||||
|
|||||||
@@ -98,13 +98,13 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SVG Highlighting - Sleek Technical Tracers */
|
/* SVG Interactivity Styles */
|
||||||
svg .stereotype { display: none !important; }
|
svg a { text-decoration: none !important; }
|
||||||
|
|
||||||
.active-path path {
|
.active-path {
|
||||||
stroke: var(--accent) !important;
|
stroke: var(--accent) !important;
|
||||||
stroke-width: 2.0px !important;
|
stroke-width: 2.5px !important;
|
||||||
filter: drop-shadow(0 0 6px rgba(239, 68, 68, 0.3));
|
filter: drop-shadow(0 0 8px rgba(239, 68, 68, 0.3));
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dimmed {
|
.dimmed {
|
||||||
opacity: 0.25 !important;
|
opacity: 0.2 !important;
|
||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
transition: opacity 0.4s ease;
|
transition: opacity 0.4s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -151,6 +151,11 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.payload-param {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.payload-param:last-child { margin-bottom: 0; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -344,7 +349,8 @@
|
|||||||
"metadata" : {
|
"metadata" : {
|
||||||
"path" : "/api/enterprise/orders/place",
|
"path" : "/api/enterprise/orders/place",
|
||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
}, {
|
}, {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
"name" : "POST /api/enterprise/orders/{id}/cancel",
|
"name" : "POST /api/enterprise/orders/{id}/cancel",
|
||||||
@@ -353,7 +359,12 @@
|
|||||||
"metadata" : {
|
"metadata" : {
|
||||||
"path" : "/api/enterprise/orders/{id}/cancel",
|
"path" : "/api/enterprise/orders/{id}/cancel",
|
||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "id",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
} ]
|
||||||
}, {
|
}, {
|
||||||
"type" : "CUSTOM",
|
"type" : "CUSTOM",
|
||||||
"name" : "INTERCEPTOR: SecurityInterceptor.preHandle",
|
"name" : "INTERCEPTOR: SecurityInterceptor.preHandle",
|
||||||
@@ -361,7 +372,8 @@
|
|||||||
"methodName" : "preHandle",
|
"methodName" : "preHandle",
|
||||||
"metadata" : {
|
"metadata" : {
|
||||||
"interceptorType" : "Spring MVC Interceptor"
|
"interceptorType" : "Spring MVC Interceptor"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
}, {
|
}, {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
"name" : "POST /api/enterprise/payments",
|
"name" : "POST /api/enterprise/payments",
|
||||||
@@ -370,7 +382,12 @@
|
|||||||
"metadata" : {
|
"metadata" : {
|
||||||
"path" : "/api/enterprise/payments",
|
"path" : "/api/enterprise/payments",
|
||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "orderId",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "RequestBody" ]
|
||||||
|
} ]
|
||||||
} ],
|
} ],
|
||||||
"callChains" : [ {
|
"callChains" : [ {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
@@ -381,7 +398,8 @@
|
|||||||
"metadata" : {
|
"metadata" : {
|
||||||
"path" : "/api/enterprise/orders/place",
|
"path" : "/api/enterprise/orders/place",
|
||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
@@ -401,7 +419,12 @@
|
|||||||
"metadata" : {
|
"metadata" : {
|
||||||
"path" : "/api/enterprise/orders/{id}/cancel",
|
"path" : "/api/enterprise/orders/{id}/cancel",
|
||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "id",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "PathVariable" ]
|
||||||
|
} ]
|
||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
@@ -420,7 +443,8 @@
|
|||||||
"methodName" : "preHandle",
|
"methodName" : "preHandle",
|
||||||
"metadata" : {
|
"metadata" : {
|
||||||
"interceptorType" : "Spring MVC Interceptor"
|
"interceptorType" : "Spring MVC Interceptor"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.SecurityInterceptor.preHandle" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.SecurityInterceptor.preHandle" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
@@ -440,7 +464,12 @@
|
|||||||
"metadata" : {
|
"metadata" : {
|
||||||
"path" : "/api/enterprise/payments",
|
"path" : "/api/enterprise/payments",
|
||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
}
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "orderId",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "RequestBody" ]
|
||||||
|
} ]
|
||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.ReactivePaymentController.pay", "click.kamil.examples.enterprise.service.ReactivePaymentService.processPayment" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.ReactivePaymentController.pay", "click.kamil.examples.enterprise.service.ReactivePaymentService.processPayment" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
@@ -500,7 +529,7 @@
|
|||||||
|
|
||||||
const svg = document.querySelector('svg');
|
const svg = document.querySelector('svg');
|
||||||
|
|
||||||
// Dim all shapes and texts
|
// Dim everything first
|
||||||
svg.querySelectorAll('path, rect, circle, ellipse, text, polygon').forEach(el => {
|
svg.querySelectorAll('path, rect, circle, ellipse, text, polygon').forEach(el => {
|
||||||
el.classList.add('dimmed');
|
el.classList.add('dimmed');
|
||||||
});
|
});
|
||||||
@@ -513,7 +542,7 @@
|
|||||||
svg.querySelectorAll(`a[*|href="${linkId}"], a[href="${linkId}"]`).forEach(link => {
|
svg.querySelectorAll(`a[*|href="${linkId}"], a[href="${linkId}"]`).forEach(link => {
|
||||||
link.classList.add('active-path');
|
link.classList.add('active-path');
|
||||||
link.querySelectorAll('*').forEach(child => child.classList.remove('dimmed'));
|
link.querySelectorAll('*').forEach(child => child.classList.remove('dimmed'));
|
||||||
|
|
||||||
// Surgical Selection: Grab ONLY the immediate path and polygon
|
// Surgical Selection: Grab ONLY the immediate path and polygon
|
||||||
let prev = link.previousElementSibling;
|
let prev = link.previousElementSibling;
|
||||||
let foundPath = false;
|
let foundPath = false;
|
||||||
@@ -618,24 +647,40 @@
|
|||||||
if (chains && chains.length > 0) {
|
if (chains && chains.length > 0) {
|
||||||
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:10px; border-top:1px solid #eee; padding-top:10px">Triggered by</div>`;
|
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:10px; border-top:1px solid #eee; padding-top:10px">Triggered by</div>`;
|
||||||
chains.forEach(c => {
|
chains.forEach(c => {
|
||||||
html += `<div style="margin-bottom:15px">
|
html += `<div style="margin-bottom:20px">
|
||||||
<div style="font-weight:700; font-size:0.85rem; color:var(--text);">${c.entryPoint.name}</div>
|
<div style="font-weight:700; font-size:0.85rem; color:var(--text);">${c.entryPoint.name}</div>
|
||||||
<div style="margin-left:10px; border-left:2px solid #e2e8f0; padding-left:12px; margin-top:8px">
|
|
||||||
|
${renderParameters(c.entryPoint.parameters)}
|
||||||
|
|
||||||
|
<div style="margin-left:10px; border-left:2px solid #e2e8f0; padding-left:12px; margin-top:10px">
|
||||||
${c.methodChain.map((m, i) => `<div style="font-size:0.7rem; color:#475569; margin-top:4px; font-family:'JetBrains Mono', monospace;"><b>${i+1}</b> ${m}</div>`).join('')}
|
${c.methodChain.map((m, i) => `<div style="font-size:0.7rem; color:#475569; margin-top:4px; font-family:'JetBrains Mono', monospace;"><b>${i+1}</b> ${m}</div>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placeholder for Data Object
|
html += `</div>`;
|
||||||
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:8px; border-top:1px solid #eee; padding-top:10px">Data Payload (Planned)</div>
|
return html;
|
||||||
<div class="payload-box">
|
}
|
||||||
<span style="color:#ef4444">OrderRequest</span> {<br>
|
|
||||||
<span style="color:#64748b">// Full AST field mapping to be implemented</span><br>
|
function renderParameters(params) {
|
||||||
id: "ORD-123",<br>
|
if (!params || params.length === 0) return "";
|
||||||
quantity: 5<br>
|
|
||||||
}
|
let html = `<div style="font-size:0.65rem; font-weight:700; color:#94a3b8; margin-top:8px; margin-bottom:4px; text-transform:uppercase;">Input Data</div>`;
|
||||||
</div></div>`;
|
html += `<div class="payload-box">`;
|
||||||
|
|
||||||
|
params.forEach(p => {
|
||||||
|
const annotation = (p.annotations || []).find(a => a.endsWith("RequestBody") || a.endsWith("PathVariable") || a.endsWith("RequestParam"));
|
||||||
|
const cleanAnn = annotation ? "@" + annotation.substring(annotation.lastIndexOf('.') + 1) : "";
|
||||||
|
|
||||||
|
html += `<div class="payload-param">
|
||||||
|
<span style="color:#f43f5e; font-size:0.65rem; font-weight:bold;">${cleanAnn}</span>
|
||||||
|
<span style="color:#38bdf8;">${p.type}</span>
|
||||||
|
<span style="color:#a3e635;">${p.name}</span>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -79,6 +79,10 @@
|
|||||||
.type-rest { background: #eff6ff; color: #1d4ed8; }
|
.type-rest { background: #eff6ff; color: #1d4ed8; }
|
||||||
.type-custom { background: #fef2f2; color: #b91c1c; }
|
.type-custom { background: #fef2f2; color: #b91c1c; }
|
||||||
.type-jms { background: #f5f3ff; color: #7c3aed; }
|
.type-jms { background: #f5f3ff; color: #7c3aed; }
|
||||||
|
.type-sqs { background: #fff7ed; color: #ea580c; }
|
||||||
|
.type-sns { background: #fff1f2; color: #e11d48; }
|
||||||
|
.type-kafka { background: #fefce8; color: #854d0e; }
|
||||||
|
.type-rabbit { background: #f0fdf4; color: #166534; }
|
||||||
|
|
||||||
.ep-name { font-weight: 700; font-size: 0.95rem; margin-bottom: 6px; color: var(--text); }
|
.ep-name { font-weight: 700; font-size: 0.95rem; margin-bottom: 6px; color: var(--text); }
|
||||||
.ep-fqn { font-family: 'JetBrains Mono', monospace; font-size: 0.65rem; color: var(--text-muted); opacity: 0.8; }
|
.ep-fqn { font-family: 'JetBrains Mono', monospace; font-size: 0.65rem; color: var(--text-muted); opacity: 0.8; }
|
||||||
@@ -98,13 +102,13 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SVG Highlighting - Sleek Technical Tracers */
|
/* SVG Interactivity Styles */
|
||||||
svg .stereotype { display: none !important; }
|
svg a { text-decoration: none !important; }
|
||||||
|
|
||||||
.active-path path {
|
.active-path {
|
||||||
stroke: var(--accent) !important;
|
stroke: var(--accent) !important;
|
||||||
stroke-width: 2.0px !important;
|
stroke-width: 2.5px !important;
|
||||||
filter: drop-shadow(0 0 6px rgba(239, 68, 68, 0.3));
|
filter: drop-shadow(0 0 8px rgba(239, 68, 68, 0.3));
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dimmed {
|
.dimmed {
|
||||||
opacity: 0.25 !important;
|
opacity: 0.2 !important;
|
||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
transition: opacity 0.4s ease;
|
transition: opacity 0.4s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -151,6 +155,11 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.payload-param {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.payload-param:last-child { margin-bottom: 0; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -217,7 +226,7 @@
|
|||||||
|
|
||||||
const svg = document.querySelector('svg');
|
const svg = document.querySelector('svg');
|
||||||
|
|
||||||
// Dim all shapes and texts
|
// Dim everything first
|
||||||
svg.querySelectorAll('path, rect, circle, ellipse, text, polygon').forEach(el => {
|
svg.querySelectorAll('path, rect, circle, ellipse, text, polygon').forEach(el => {
|
||||||
el.classList.add('dimmed');
|
el.classList.add('dimmed');
|
||||||
});
|
});
|
||||||
@@ -230,7 +239,7 @@
|
|||||||
svg.querySelectorAll(`a[*|href="${linkId}"], a[href="${linkId}"]`).forEach(link => {
|
svg.querySelectorAll(`a[*|href="${linkId}"], a[href="${linkId}"]`).forEach(link => {
|
||||||
link.classList.add('active-path');
|
link.classList.add('active-path');
|
||||||
link.querySelectorAll('*').forEach(child => child.classList.remove('dimmed'));
|
link.querySelectorAll('*').forEach(child => child.classList.remove('dimmed'));
|
||||||
|
|
||||||
// Surgical Selection: Grab ONLY the immediate path and polygon
|
// Surgical Selection: Grab ONLY the immediate path and polygon
|
||||||
let prev = link.previousElementSibling;
|
let prev = link.previousElementSibling;
|
||||||
let foundPath = false;
|
let foundPath = false;
|
||||||
@@ -335,24 +344,40 @@
|
|||||||
if (chains && chains.length > 0) {
|
if (chains && chains.length > 0) {
|
||||||
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:10px; border-top:1px solid #eee; padding-top:10px">Triggered by</div>`;
|
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:10px; border-top:1px solid #eee; padding-top:10px">Triggered by</div>`;
|
||||||
chains.forEach(c => {
|
chains.forEach(c => {
|
||||||
html += `<div style="margin-bottom:15px">
|
html += `<div style="margin-bottom:20px">
|
||||||
<div style="font-weight:700; font-size:0.85rem; color:var(--text);">${c.entryPoint.name}</div>
|
<div style="font-weight:700; font-size:0.85rem; color:var(--text);">${c.entryPoint.name}</div>
|
||||||
<div style="margin-left:10px; border-left:2px solid #e2e8f0; padding-left:12px; margin-top:8px">
|
|
||||||
|
${renderParameters(c.entryPoint.parameters)}
|
||||||
|
|
||||||
|
<div style="margin-left:10px; border-left:2px solid #e2e8f0; padding-left:12px; margin-top:10px">
|
||||||
${c.methodChain.map((m, i) => `<div style="font-size:0.7rem; color:#475569; margin-top:4px; font-family:'JetBrains Mono', monospace;"><b>${i+1}</b> ${m}</div>`).join('')}
|
${c.methodChain.map((m, i) => `<div style="font-size:0.7rem; color:#475569; margin-top:4px; font-family:'JetBrains Mono', monospace;"><b>${i+1}</b> ${m}</div>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placeholder for Data Object
|
html += `</div>`;
|
||||||
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:8px; border-top:1px solid #eee; padding-top:10px">Data Payload (Planned)</div>
|
return html;
|
||||||
<div class="payload-box">
|
}
|
||||||
<span style="color:#ef4444">OrderRequest</span> {<br>
|
|
||||||
<span style="color:#64748b">// Full AST field mapping to be implemented</span><br>
|
function renderParameters(params) {
|
||||||
id: "ORD-123",<br>
|
if (!params || params.length === 0) return "";
|
||||||
quantity: 5<br>
|
|
||||||
}
|
let html = `<div style="font-size:0.65rem; font-weight:700; color:#94a3b8; margin-top:8px; margin-bottom:4px; text-transform:uppercase;">Input Data</div>`;
|
||||||
</div></div>`;
|
html += `<div class="payload-box">`;
|
||||||
|
|
||||||
|
params.forEach(p => {
|
||||||
|
const annotation = (p.annotations || []).find(a => a.endsWith("RequestBody") || a.endsWith("PathVariable") || a.endsWith("RequestParam"));
|
||||||
|
const cleanAnn = annotation ? "@" + annotation.substring(annotation.lastIndexOf('.') + 1) : "";
|
||||||
|
|
||||||
|
html += `<div class="payload-param">
|
||||||
|
<span style="color:#f43f5e; font-size:0.65rem; font-weight:bold;">${cleanAnn}</span>
|
||||||
|
<span style="color:#38bdf8;">${p.type}</span>
|
||||||
|
<span style="color:#a3e635;">${p.name}</span>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
41
state_machines/ultimate_ecosystem_sm/build.gradle
Normal file
41
state_machines/ultimate_ecosystem_sm/build.gradle
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot' version '3.5.3'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'click.kamil'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.statemachine:spring-statemachine-starter:4.0.0'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-activemq'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-amqp'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-jersey'
|
||||||
|
|
||||||
|
// AWS SQS/SNS
|
||||||
|
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs:3.1.1'
|
||||||
|
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sns:3.1.1'
|
||||||
|
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar { enabled = false }
|
||||||
|
jar { enabled = true }
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package click.kamil.ultimate.api;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Path("/jaxrs/ticket")
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JerseyResource {
|
||||||
|
private final StateMachine<String, String> stateMachine;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public void createJaxRs(String payload) {
|
||||||
|
stateMachine.sendEvent("CREATE_JAXRS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("/{id}/resolve")
|
||||||
|
public void resolve(@PathParam("id") String id) {
|
||||||
|
stateMachine.sendEvent("RESOLVE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package click.kamil.ultimate.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachine;
|
||||||
|
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
public class UltimateStateMachineConfig extends EnumStateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("NEW")
|
||||||
|
.state("TRIAGE")
|
||||||
|
.state("IN_PROGRESS")
|
||||||
|
.state("ESCALATED")
|
||||||
|
.end("RESOLVED")
|
||||||
|
.end("CLOSED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("NEW").target("TRIAGE").event("CREATE")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("NEW").target("IN_PROGRESS").event("CREATE_REACTIVE")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("NEW").target("IN_PROGRESS").event("CREATE_JAXRS")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("TRIAGE").target("IN_PROGRESS").event("ASSIGN")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("IN_PROGRESS").target("ESCALATED").event("ESCALATE")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("IN_PROGRESS").target("RESOLVED").event("RESOLVE")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("ESCALATED").target("RESOLVED").event("RESOLVE")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("RESOLVED").target("CLOSED").event("CLOSE_TICKET")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("IN_PROGRESS").target("TRIAGE").event("SNS_UPDATE")
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source("TRIAGE").target("ESCALATED").event("SQS_UPDATE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package click.kamil.ultimate.messaging;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.jms.annotation.JmsListener;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JmsTicketListener {
|
||||||
|
private final StateMachine<String, String> stateMachine;
|
||||||
|
|
||||||
|
@JmsListener(destination = "ticket.close.queue")
|
||||||
|
public void onCloseMessage(String ticketId) {
|
||||||
|
stateMachine.sendEvent("CLOSE_TICKET");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package click.kamil.ultimate.messaging;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RabbitTicketListener {
|
||||||
|
private final StateMachine<String, String> stateMachine;
|
||||||
|
|
||||||
|
@RabbitListener(queues = "ticket.escalate.exchange")
|
||||||
|
public void onEscalate(String payload) {
|
||||||
|
stateMachine.sendEvent("ESCALATE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package click.kamil.ultimate.messaging;
|
||||||
|
|
||||||
|
import io.awspring.cloud.sns.annotation.SnsListener;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SnsTicketListener {
|
||||||
|
private final StateMachine<String, String> stateMachine;
|
||||||
|
|
||||||
|
@SnsListener("ticket-sns-topic")
|
||||||
|
public void onSnsMessage(String subject, String message) {
|
||||||
|
stateMachine.sendEvent("SNS_UPDATE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package click.kamil.ultimate.messaging;
|
||||||
|
|
||||||
|
import io.awspring.cloud.sqs.annotation.SqsListener;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SqsTicketListener {
|
||||||
|
private final StateMachine<String, String> stateMachine;
|
||||||
|
|
||||||
|
@SqsListener("ticket-updates-queue")
|
||||||
|
public void onUpdate(String message) {
|
||||||
|
stateMachine.sendEvent("SQS_UPDATE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package click.kamil.ultimate.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TicketRequest {
|
||||||
|
private String subject;
|
||||||
|
private String description;
|
||||||
|
private Priority priority;
|
||||||
|
|
||||||
|
public enum Priority { LOW, MEDIUM, HIGH }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package click.kamil.ultimate.web;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/ticket/reactive")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ReactiveController {
|
||||||
|
private final StateMachine<String, String> stateMachine;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Mono<Void> createReactive(@RequestBody String data) {
|
||||||
|
return Mono.fromRunnable(() -> stateMachine.sendEvent("CREATE_REACTIVE"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package click.kamil.ultimate.web;
|
||||||
|
|
||||||
|
import click.kamil.ultimate.model.TicketRequest;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/ticket")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebController {
|
||||||
|
private final StateMachine<String, String> stateMachine;
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
public void createTicket(@RequestBody TicketRequest request) {
|
||||||
|
stateMachine.sendEvent("CREATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/assign")
|
||||||
|
public void assignTicket(@PathVariable String id, @RequestParam String userId) {
|
||||||
|
stateMachine.sendEvent("ASSIGN");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user