Skip to content

Fix issue 132 and refactor entry point detection logic. #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=2.3.2
version=2.3.3
222 changes: 6 additions & 216 deletions src/main/java/com/ibm/cldk/SymbolTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc;
import com.github.javaparser.ast.stmt.*;
import com.ibm.cldk.javaee.EntrypointsFinderFactory;
import org.apache.commons.lang3.tuple.Pair;

import com.github.javaparser.JavaParser;
Expand Down Expand Up @@ -72,7 +73,7 @@
import com.ibm.cldk.javaee.utils.enums.CRUDQueryType;
import com.ibm.cldk.utils.Log;

@SuppressWarnings("rawtypes")
@SuppressWarnings({"unchecked", "rawtypes"})
public class SymbolTable {

private static JavaSymbolSolver javaSymbolSolver;
Expand Down Expand Up @@ -466,149 +467,8 @@ private static Map<String, Object> mapRecordConstructorDefaults(RecordDeclaratio
}

private static boolean isEntryPointClass(TypeDeclaration typeDecl) {
return isSpringEntrypointClass(typeDecl) || isStrutsEntryPointClass(typeDecl)
|| isCamelEntryPointClass(typeDecl) || isJaxRSEntrypointClass(typeDecl)
|| isJakartaServletEntryPointClass(typeDecl);

}

private static boolean isSpringEntrypointClass(TypeDeclaration typeDeclaration) {
List<AnnotationExpr> annotations = typeDeclaration.getAnnotations();
for (AnnotationExpr annotation : annotations) {
// Existing checks
if (annotation.getNameAsString().contains("RestController")
|| annotation.getNameAsString().contains("Controller")
|| annotation.getNameAsString().contains("HandleInterceptor")
|| annotation.getNameAsString().contains("HandlerInterceptor")) {
return true;
}

// Spring Boot specific checks
if (annotation.getNameAsString().contains("SpringBootApplication")
|| annotation.getNameAsString().contains("Configuration")
|| annotation.getNameAsString().contains("Component")
|| annotation.getNameAsString().contains("Service")
|| annotation.getNameAsString().contains("Repository")) {
return true;
}
}

// Check if class implements CommandLineRunner or ApplicationRunner
if (typeDeclaration instanceof ClassOrInterfaceDeclaration) {
ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration;
for (ClassOrInterfaceType implementedType : classDecl.getImplementedTypes()) {
String typeName = implementedType.getNameAsString();
if (typeName.equals("CommandLineRunner") || typeName.equals("ApplicationRunner")) {
return true;
}
}
}

return false;
}

private static boolean isJaxRSEntrypointClass(TypeDeclaration typeDeclaration) {
List<CallableDeclaration> callableDeclarations = typeDeclaration.findAll(CallableDeclaration.class);
for (CallableDeclaration callableDeclaration : callableDeclarations) {
if (callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("POST"))
|| callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("PUT"))
|| callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("GET"))
|| callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("HEAD"))
|| callableDeclaration.getAnnotations().stream().anyMatch(a -> a.toString().contains("DELETE"))) {
return true;
}
}

return false;
}

private static boolean isStrutsEntryPointClass(TypeDeclaration typeDeclaration) {
if (!(typeDeclaration instanceof ClassOrInterfaceDeclaration)) {
return false;
}

ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration;

// Check class-level Struts annotations
if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Action")
|| a.getNameAsString().contains("Namespace") || a.getNameAsString().contains("InterceptorRef"))) {
return true;
}

// Check if extends ActionSupport or implements Interceptor
try {
ResolvedReferenceTypeDeclaration resolved = classDecl.resolve();
return resolved.getAllAncestors().stream().anyMatch(ancestor -> {
String name = ancestor.getQualifiedName();
return name.contains("ActionSupport") || name.contains("Interceptor");
});
} catch (UnsolvedSymbolException e) {
Log.warn("Could not resolve class: " + e.getMessage());
}

return false;
}

private static boolean isCamelEntryPointClass(TypeDeclaration typeDeclaration) {
if (!(typeDeclaration instanceof ClassOrInterfaceDeclaration)) {
return false;
}

ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDeclaration;

// Check Camel class annotations
if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Component"))) {
return true;
}

// Check Camel parent classes and interfaces
try {
ResolvedReferenceTypeDeclaration resolved = classDecl.resolve();
return resolved.getAllAncestors().stream().anyMatch(ancestor -> {
String name = ancestor.getQualifiedName();
return name.contains("RouteBuilder") || name.contains("Processor") || name.contains("Producer")
|| name.contains("Consumer");
});
} catch (UnsolvedSymbolException e) {
Log.warn("Could not resolve class: " + e.getMessage());
}

return false;
}

/**
* Checks if the given class is a Jakarta Servlet entry point class.
*
* @param typeDecl Type declaration to check
* @return True if the class is a Jakarta Servlet entry point class, false
* otherwise
*/
private static boolean isJakartaServletEntryPointClass(TypeDeclaration typeDecl) {
if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) {
return false;
}

ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl;

// Check annotations
if (classDecl.getAnnotations().stream()
.anyMatch(a -> a.getNameAsString().contains("WebServlet") || a.getNameAsString().contains("WebFilter")
|| a.getNameAsString().contains("WebListener") || a.getNameAsString().contains("ServerEndpoint")
|| a.getNameAsString().contains("MessageDriven")
|| a.getNameAsString().contains("WebService"))) {
return true;
}

// Check types
return classDecl.getExtendedTypes().stream()
.map(ClassOrInterfaceType::getNameAsString)
.anyMatch(name -> name.contains("HttpServlet") || name.contains("GenericServlet"))
|| classDecl.getImplementedTypes().stream().map(
ClassOrInterfaceType::asString).anyMatch(
name -> name.contains("ServletContextListener")
|| name.contains("HttpSessionListener")
|| name.contains("ServletRequestListener")
|| name.contains("MessageListener"));
return EntrypointsFinderFactory.getEntrypointFinders()
.anyMatch(finder -> finder.isEntrypointClass(typeDecl));
}

/**
Expand Down Expand Up @@ -752,80 +612,10 @@ private static Pair<String, Callable> processCallableDeclaration(CallableDeclara
}

private static boolean isEntryPointMethod(CallableDeclaration callableDecl) {
return isServletEntrypointMethod(callableDecl) || isJaxRsEntrypointMethod(callableDecl)
|| isSpringEntrypointMethod(callableDecl) | isStrutsEntryPointMethod(callableDecl);
}

@SuppressWarnings("unchecked")
private static boolean isServletEntrypointMethod(CallableDeclaration callableDecl) {
return ((NodeList<Parameter>) callableDecl.getParameters()).stream()
.anyMatch(parameter -> parameter.getType().asString().contains("HttpServletRequest") ||
parameter.getType().asString().contains("HttpServletResponse"))
&& callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Override"));
}

@SuppressWarnings("unchecked")
private static boolean isJaxRsEntrypointMethod(CallableDeclaration callableDecl) {
return callableDecl.getAnnotations().stream()
.anyMatch(a -> a.toString().contains("POST") || a.toString().contains("PUT")
|| a.toString().contains("GET") || a.toString().contains("HEAD")
|| a.toString().contains("DELETE"));
return EntrypointsFinderFactory.getEntrypointFinders()
.anyMatch(finder -> finder.isEntrypointMethod(callableDecl));
}

@SuppressWarnings("unchecked")
private static boolean isSpringEntrypointMethod(CallableDeclaration callableDecl) {
return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("GetMapping") ||
a.toString().contains("PostMapping") ||
a.toString().contains("PutMapping") ||
a.toString().contains("DeleteMapping") ||
a.toString().contains("PatchMapping") ||
a.toString().contains("RequestMapping") ||
a.toString().contains("EventListener") ||
a.toString().contains("Scheduled") ||
a.toString().contains("KafkaListener") ||
a.toString().contains("RabbitListener") ||
a.toString().contains("JmsListener") ||
a.toString().contains("PreAuthorize") ||
a.toString().contains("PostAuthorize") ||
a.toString().contains("PostConstruct") ||
a.toString().contains("PreDestroy") ||
a.toString().contains("Around") ||
a.toString().contains("Before") ||
a.toString().contains("After") ||
a.toString().contains("JobScope") ||
a.toString().contains("StepScope"));
}

@SuppressWarnings("unchecked")
private static boolean isStrutsEntryPointMethod(CallableDeclaration callableDecl) {
// First check if this method is in a Struts Action class
Optional<Node> parentNode = callableDecl.getParentNode();
if (parentNode.isEmpty() || !(parentNode.get() instanceof ClassOrInterfaceDeclaration)) {
return false;
}

ClassOrInterfaceDeclaration parentClass = (ClassOrInterfaceDeclaration) parentNode.get();
if (parentClass.getExtendedTypes().stream()
.map(ClassOrInterfaceType::asString)
.noneMatch(type -> type.contains("ActionSupport") || type.contains("Action")))
return false;

return callableDecl.getAnnotations().stream().anyMatch(a -> a.toString().contains("Action") ||
a.toString().contains("Actions") ||
a.toString().contains("ValidationMethod") ||
a.toString().contains("InputConfig") ||
a.toString().contains("BeforeResult") ||
a.toString().contains("After") ||
a.toString().contains("Before") ||
a.toString().contains("Result") ||
a.toString().contains("Results")) || callableDecl.getNameAsString().equals("execute"); // Check for
// execute()
// method which
// is the default
// action method
// of the Action
// class
}

/**
* Computes cyclomatic complexity for the given callable.
Expand Down
13 changes: 6 additions & 7 deletions src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.ibm.cldk.javaee;

import com.ibm.cldk.javaee.utils.interfaces.AbstractCRUDFinder;
import com.ibm.cldk.javaee.camel.CamelEntrypointFinder;
import com.ibm.cldk.javaee.jax.JaxRsEntrypointFinder;
import com.ibm.cldk.javaee.spring.SpringEntrypointFinder;
import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder;
import com.ibm.cldk.javaee.jakarta.JPACRUDFinder;
import com.ibm.cldk.javaee.jakarta.JakartaEntrypointFinder;
import com.ibm.cldk.javaee.jdbc.JDBCCRUDFinder;
import com.ibm.cldk.javaee.spring.SpringCRUDFinder;
import com.ibm.cldk.javaee.struts.StrutsEntrypointFinder;
import org.apache.commons.lang3.NotImplementedException;

Expand All @@ -17,7 +16,7 @@ public static AbstractEntrypointFinder getEntrypointFinder(String framework) {
case "jakarta":
return new JakartaEntrypointFinder();
case "spring":
return new StrutsEntrypointFinder();
return new SpringEntrypointFinder();
case "camel":
throw new NotImplementedException("Camel CRUD finder not implemented yet");
case "struts":
Expand All @@ -27,7 +26,7 @@ public static AbstractEntrypointFinder getEntrypointFinder(String framework) {
}
}

public static Stream<AbstractCRUDFinder> getEntrypointFinders() {
return Stream.of(new JPACRUDFinder(), new SpringCRUDFinder(), new JDBCCRUDFinder());
public static Stream<AbstractEntrypointFinder> getEntrypointFinders() {
return Stream.of(new JakartaEntrypointFinder(), new StrutsEntrypointFinder(), new SpringEntrypointFinder(), new CamelEntrypointFinder(), new JaxRsEntrypointFinder());
}
}
42 changes: 40 additions & 2 deletions src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
package com.ibm.cldk.javaee.camel;

import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder;
import com.ibm.cldk.utils.Log;
import com.ibm.cldk.utils.annotations.NotImplemented;

@NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.")
public class CamelEntrypointFinder extends AbstractEntrypointFinder {
/**
* Detect if the method is an entrypoint.
*
* @param typeDecl@return True if the method is an entrypoint, false otherwise.
*/
@Override
public boolean isEntrypointClass(String receiverType, String name) {
public boolean isEntrypointClass(TypeDeclaration typeDecl) {
if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) {
return false;
}

ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) typeDecl;

// Check Camel class annotations
if (classDecl.getAnnotations().stream().anyMatch(a -> a.getNameAsString().contains("Component"))) {
return true;
}

// Check Camel parent classes and interfaces
try {
ResolvedReferenceTypeDeclaration resolved = classDecl.resolve();
return resolved.getAllAncestors().stream().anyMatch(ancestor -> {
String name = ancestor.getQualifiedName();
return name.contains("RouteBuilder") || name.contains("Processor") || name.contains("Producer")
|| name.contains("Consumer");
});
} catch (UnsolvedSymbolException e) {
Log.warn("Could not resolve class: " + e.getMessage());
}

return false;
}

/**
* @param callableDecl
* @return
*/
@Override
public boolean isEntrypointMethod(String receiverType, String name) {
public boolean isEntrypointMethod(CallableDeclaration callableDecl) {
return false;
}
}
Loading