v2 extended analysis render
This commit is contained in:
@@ -5,6 +5,7 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@@ -12,11 +13,23 @@ import java.util.Map;
|
||||
@Jacksonized
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
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 String name; // e.g., "POST /orders" or "Kafka Topic: orders"
|
||||
private final String className;
|
||||
private final String methodName;
|
||||
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.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||
import click.kamil.springstatemachineexporter.analysis.resolver.PropertyResolver;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -21,6 +20,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
private final ConstantResolver constantResolver = new ConstantResolver();
|
||||
private final GenericEventDetector eventDetector;
|
||||
private final SpringMvcDetector mvcDetector;
|
||||
private final MessagingDetector messagingDetector;
|
||||
private final CallGraphBuilder callGraphBuilder;
|
||||
private final LifecycleDetector lifecycleDetector;
|
||||
private final InterceptorDetector interceptorDetector;
|
||||
@@ -30,6 +30,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
this.rootDir = rootDir;
|
||||
this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints());
|
||||
this.mvcDetector = new SpringMvcDetector(context, constantResolver);
|
||||
this.messagingDetector = new MessagingDetector(context);
|
||||
this.callGraphBuilder = new CallGraphBuilder(context);
|
||||
this.lifecycleDetector = new LifecycleDetector(context);
|
||||
this.interceptorDetector = new InterceptorDetector(context);
|
||||
@@ -37,7 +38,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
|
||||
@Override
|
||||
public List<TriggerPoint> findTriggerPoints() {
|
||||
List<TriggerPoint> allTriggers = new java.util.ArrayList<>();
|
||||
List<TriggerPoint> allTriggers = new ArrayList<>();
|
||||
var cus = context.getCompilationUnits();
|
||||
log.info("JdtIntelligenceProvider scanning {} compilation units for triggers", cus.size());
|
||||
for (CompilationUnit cu : cus) {
|
||||
@@ -50,14 +51,15 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
|
||||
@Override
|
||||
public List<EntryPoint> findEntryPoints() {
|
||||
List<EntryPoint> allEntryPoints = new java.util.ArrayList<>();
|
||||
List<EntryPoint> allEntryPoints = new ArrayList<>();
|
||||
var cus = context.getCompilationUnits();
|
||||
log.info("JdtIntelligenceProvider scanning {} compilation units for entry points", cus.size());
|
||||
for (CompilationUnit cu : cus) {
|
||||
allEntryPoints.addAll(mvcDetector.detect(cu));
|
||||
allEntryPoints.addAll(messagingDetector.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
if (modifier instanceof Annotation annotation) {
|
||||
String name = annotation.getTypeName().getFullyQualifiedName();
|
||||
if (name.endsWith("RestController") || name.endsWith("Controller")) {
|
||||
if (name.endsWith("RestController") || name.endsWith("Controller") || name.endsWith("Path")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,10 @@ public class SpringMvcDetector {
|
||||
|
||||
private void processController(TypeDeclaration controller, List<EntryPoint> entryPoints) {
|
||||
String basePath = getMappingPathHierarchical(controller);
|
||||
if (basePath.isEmpty()) {
|
||||
basePath = getJaxRsPath(controller);
|
||||
}
|
||||
|
||||
for (MethodDeclaration method : controller.getMethods()) {
|
||||
EntryPoint ep = processMethodHierarchical(method, basePath, controller);
|
||||
if (ep != null) {
|
||||
@@ -55,11 +59,9 @@ public class SpringMvcDetector {
|
||||
}
|
||||
|
||||
private String getMappingPathHierarchical(TypeDeclaration td) {
|
||||
// 1. Check class itself
|
||||
String path = getMappingPath(td);
|
||||
if (!path.isEmpty()) return path;
|
||||
|
||||
// 2. Check interfaces
|
||||
for (Object interfaceType : td.superInterfaceTypes()) {
|
||||
TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
|
||||
if (itd != null) {
|
||||
@@ -68,7 +70,6 @@ public class SpringMvcDetector {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check superclass
|
||||
if (td.getSuperclassType() != null) {
|
||||
TypeDeclaration std = context.getTypeDeclaration(td.getSuperclassType().toString());
|
||||
if (std != null) {
|
||||
@@ -80,11 +81,9 @@ public class SpringMvcDetector {
|
||||
}
|
||||
|
||||
private EntryPoint processMethodHierarchical(MethodDeclaration method, String basePath, TypeDeclaration controller) {
|
||||
// 1. Check the method itself
|
||||
EntryPoint ep = processMethod(method, basePath, controller);
|
||||
if (ep != null) return ep;
|
||||
|
||||
// 2. Check interface methods
|
||||
String methodName = method.getName().getIdentifier();
|
||||
for (Object interfaceType : controller.superInterfaceTypes()) {
|
||||
TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
|
||||
@@ -108,18 +107,24 @@ public class SpringMvcDetector {
|
||||
String verb = getHttpVerb(name);
|
||||
if (verb != null) {
|
||||
String methodPath = getMappingPath(annotation);
|
||||
if (methodPath.isEmpty()) {
|
||||
methodPath = getJaxRsPath(method);
|
||||
}
|
||||
String fullPath = combinePaths(basePath, methodPath);
|
||||
|
||||
Map<String, String> metadata = new HashMap<>();
|
||||
metadata.put("path", fullPath);
|
||||
metadata.put("verb", verb);
|
||||
|
||||
List<EntryPoint.Parameter> parameters = extractParameters(method);
|
||||
|
||||
return EntryPoint.builder()
|
||||
.type(EntryPoint.Type.REST)
|
||||
.name(verb + " " + fullPath)
|
||||
.className(context.getFqn(controller))
|
||||
.methodName(method.getName().getIdentifier())
|
||||
.metadata(metadata)
|
||||
.parameters(parameters)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -127,12 +132,21 @@ public class SpringMvcDetector {
|
||||
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) {
|
||||
if (annotationName.endsWith("PostMapping")) return "POST";
|
||||
if (annotationName.endsWith("GetMapping")) return "GET";
|
||||
if (annotationName.endsWith("PutMapping")) return "PUT";
|
||||
if (annotationName.endsWith("DeleteMapping")) return "DELETE";
|
||||
if (annotationName.endsWith("PatchMapping")) return "PATCH";
|
||||
if (annotationName.endsWith("PostMapping") || annotationName.endsWith("POST")) return "POST";
|
||||
if (annotationName.endsWith("GetMapping") || annotationName.endsWith("GET")) return "GET";
|
||||
if (annotationName.endsWith("PutMapping") || annotationName.endsWith("PUT")) return "PUT";
|
||||
if (annotationName.endsWith("DeleteMapping") || annotationName.endsWith("DELETE")) return "DELETE";
|
||||
if (annotationName.endsWith("PatchMapping") || annotationName.endsWith("PATCH")) return "PATCH";
|
||||
if (annotationName.endsWith("RequestMapping")) return "REQUEST";
|
||||
return null;
|
||||
}
|
||||
@@ -140,7 +154,8 @@ public class SpringMvcDetector {
|
||||
private String getMappingPath(TypeDeclaration td) {
|
||||
for (Object modifier : td.modifiers()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -169,6 +184,29 @@ public class SpringMvcDetector {
|
||||
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) {
|
||||
String b = base.startsWith("/") ? base : "/" + base;
|
||||
if (b.endsWith("/")) b = b.substring(0, b.length() - 1);
|
||||
|
||||
Reference in New Issue
Block a user