diff --git a/gradle.properties b/gradle.properties index 2928d190..50169955 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.3.2 +version=2.3.3 diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index b1219e35..d5b5bc84 100644 --- a/src/main/java/com/ibm/cldk/SymbolTable.java +++ b/src/main/java/com/ibm/cldk/SymbolTable.java @@ -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; @@ -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; @@ -466,149 +467,8 @@ private static Map 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 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 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)); } /** @@ -752,80 +612,10 @@ private static Pair 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) 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 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. diff --git a/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java b/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java index 5b081295..cee58f27 100644 --- a/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java +++ b/src/main/java/com/ibm/cldk/javaee/EntrypointsFinderFactory.java @@ -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; @@ -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": @@ -27,7 +26,7 @@ public static AbstractEntrypointFinder getEntrypointFinder(String framework) { } } - public static Stream getEntrypointFinders() { - return Stream.of(new JPACRUDFinder(), new SpringCRUDFinder(), new JDBCCRUDFinder()); + public static Stream getEntrypointFinders() { + return Stream.of(new JakartaEntrypointFinder(), new StrutsEntrypointFinder(), new SpringEntrypointFinder(), new CamelEntrypointFinder(), new JaxRsEntrypointFinder()); } } diff --git a/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java index 4bd9f2e8..82b58adf 100644 --- a/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/camel/CamelEntrypointFinder.java @@ -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; } } diff --git a/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java index 083a7e44..91e5e9cf 100644 --- a/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/jakarta/JakartaEntrypointFinder.java @@ -1,17 +1,48 @@ package com.ibm.cldk.javaee.jakarta; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; -import com.ibm.cldk.utils.annotations.NotImplemented; -@NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.") +@SuppressWarnings({"unchecked", "rawtypes"}) public class JakartaEntrypointFinder extends AbstractEntrypointFinder { @Override - public boolean isEntrypointClass(String receiverType, String name) { - return false; + public boolean isEntrypointClass(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(n -> n.contains("HttpServlet") || n.contains("GenericServlet")) + || classDecl.getImplementedTypes().stream().map( + ClassOrInterfaceType::asString).anyMatch( + n -> n.contains("ServletContextListener") + || n.contains("HttpSessionListener") + || n.contains("ServletRequestListener") + || n.contains("MessageListener")); } @Override - public boolean isEntrypointMethod(String receiverType, String name) { - return false; + public boolean isEntrypointMethod(CallableDeclaration callableDecl) { + return ((NodeList) callableDecl.getParameters()).stream() + .anyMatch(parameter -> parameter.getType().asString().contains("HttpServletRequest") || + parameter.getType().asString().contains("HttpServletResponse")); } } diff --git a/src/main/java/com/ibm/cldk/javaee/jax/JaxRsEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/jax/JaxRsEntrypointFinder.java new file mode 100644 index 00000000..fe0bcfcf --- /dev/null +++ b/src/main/java/com/ibm/cldk/javaee/jax/JaxRsEntrypointFinder.java @@ -0,0 +1,42 @@ +package com.ibm.cldk.javaee.jax; + +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; + +import java.util.List; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class JaxRsEntrypointFinder extends AbstractEntrypointFinder { + /** + * Detect if the method is an entrypoint. + * + * @return True if the method is an entrypoint, false otherwise. + */ + @Override + public boolean isEntrypointClass(TypeDeclaration typeDeclaration) { + List 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; + } + + /** + * @param callableDecl + * @return + */ + @Override + public boolean isEntrypointMethod(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")); + } +} diff --git a/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java index f31063fd..b1137ffd 100644 --- a/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java @@ -1,17 +1,70 @@ package com.ibm.cldk.javaee.spring; +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.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.ibm.cldk.javaee.utils.interfaces.AbstractEntrypointFinder; -import com.ibm.cldk.utils.annotations.NotImplemented; -@NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.") +import java.util.List; + public class SpringEntrypointFinder extends AbstractEntrypointFinder { @Override - public boolean isEntrypointClass(String receiverType, String name) { + public boolean isEntrypointClass(TypeDeclaration typeDeclaration) { + List 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; } @Override - public boolean isEntrypointMethod(String receiverType, String name) { - return false; - } + public boolean isEntrypointMethod(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")); } } diff --git a/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java index 311d248f..3586ff73 100644 --- a/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/struts/StrutsEntrypointFinder.java @@ -1,15 +1,68 @@ package com.ibm.cldk.javaee.struts; +import com.github.javaparser.ast.Node; +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.ast.type.ClassOrInterfaceType; +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 java.util.Optional; public class StrutsEntrypointFinder extends AbstractEntrypointFinder { @Override - public boolean isEntrypointClass(String receiverType, String name) { + public boolean isEntrypointClass(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; } @Override - public boolean isEntrypointMethod(String receiverType, String name) { - return false; + public boolean isEntrypointMethod(CallableDeclaration callableDecl) { + // First check if this method is in a Struts Action class + Optional 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"); } } diff --git a/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java b/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java index e3b8f326..7fca2d2f 100644 --- a/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java +++ b/src/main/java/com/ibm/cldk/javaee/utils/interfaces/AbstractEntrypointFinder.java @@ -1,13 +1,15 @@ package com.ibm.cldk.javaee.utils.interfaces; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; import com.ibm.cldk.utils.annotations.NotImplemented; -@NotImplemented(comment = "This class is not implemented yet. Leaving this here to refactor entrypoint detection.") +@SuppressWarnings({"unchecked", "rawtypes"}) public abstract class AbstractEntrypointFinder { /** * Enum for rules. */ - enum Rulest{ + enum Rulset{ } /** @@ -17,7 +19,7 @@ enum Rulest{ * @param name The name of the method. * @return True if the method is an entrypoint, false otherwise. */ - public abstract boolean isEntrypointClass(String receiverType, String name); + public abstract boolean isEntrypointClass(TypeDeclaration typeDecl); - public abstract boolean isEntrypointMethod(String receiverType, String name); + public abstract boolean isEntrypointMethod(CallableDeclaration callableDecl); } diff --git a/src/main/java/com/ibm/cldk/utils/BuildProject.java b/src/main/java/com/ibm/cldk/utils/BuildProject.java index 65d25732..a2533eaa 100644 --- a/src/main/java/com/ibm/cldk/utils/BuildProject.java +++ b/src/main/java/com/ibm/cldk/utils/BuildProject.java @@ -239,7 +239,7 @@ public static boolean downloadLibraryDependencies(String projectPath, String pro )); } Log.info("Found pom.xml in the project directory. Using Maven to download dependencies."); - String[] mavenCommand = {MAVEN_CMD, "--no-transfer-progress", "-f", Paths.get(projectRoot, "pom.xml").toAbsolutePath().toString(), "dependency:copy-dependencies", "-DoutputDirectory=" + libDownloadPath.toString(), "-Doverwrite=true"}; + String[] mavenCommand = {MAVEN_CMD, "--no-transfer-progress", "-f", Paths.get(projectRoot, "pom.xml").toAbsolutePath().toString(), "dependency:copy-dependencies", "-DoutputDirectory=" + libDownloadPath.toString(), "-Doverwrite=true", "--fail-never"}; return buildWithTool(mavenCommand); } else if (new File(projectRoot, "build.gradle").exists() || new File(projectRoot, "build.gradle.kts").exists()) { libDownloadPath = Paths.get(projectPath, "build", LIB_DEPS_DOWNLOAD_DIR).toAbsolutePath(); diff --git a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java index da29af15..41887235 100644 --- a/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java +++ b/src/test/java/com/ibm/cldk/CodeAnalyzerIntegrationTest.java @@ -209,6 +209,7 @@ void shouldBeAbleToGenerateAnalysisArtifactForDaytrader8() throws Exception { Assertions.assertTrue(runCodeAnalyzerOnDaytrader8.getStdout().contains("\"is_entrypoint\": true"), "No entry point methods found"); } + @Test void shouldBeAbleToDetectCRUDOperationsAndQueriesForPlantByWebsphere() throws Exception { var runCodeAnalyzerOnPlantsByWebsphere = container.execInContainer( diff --git a/src/test/resources/test-applications/.gitignore b/src/test/resources/test-applications/.gitignore index bc07142b..11609223 100644 --- a/src/test/resources/test-applications/.gitignore +++ b/src/test/resources/test-applications/.gitignore @@ -52,3 +52,6 @@ target/ # Ignore everything in codeql-db except the directory itself codeql-db/* !codeql-db/.keep + +# Ignore hidden apps +hidden-apps/ \ No newline at end of file