diff --git a/pom.xml b/pom.xml index ae43a190..1874414b 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 1.8 - 2.2.2 + main-snapshot https://repo1.maven.org/maven2 diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java new file mode 100644 index 00000000..4c698a6f --- /dev/null +++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java @@ -0,0 +1,109 @@ +/* + * creedengo - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.greencodeinitiative.creedengo.java.checks; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.*; + +public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue { + + @Autowired + private AuthorRepository authorRepository; + + public List smellGetAllAuthors() { + List authors = authorRepository.findAll(); + for (Author author : authors) { + List books = author.getBooks(); // Noncompliant {{ Detection of the "N+1 problem" on Spring Data JPA repositories }} + } + return authors; + } + + + public class Author { + + private Long id; + private String name; + + private List books; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getBooks() { + return books; + } + + public void setBooks(List books) { + this.books = books; + } + } + + + public class Book { + + private Long id; + private String title; + + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + public interface AuthorRepository extends JpaRepository { + + } + +} diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java index 791f0cef..0fe301ae 100644 --- a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java +++ b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java @@ -50,7 +50,8 @@ public class JavaCheckRegistrar implements CheckRegistrar { FreeResourcesOfAutoCloseableInterface.class, AvoidMultipleIfElseStatement.class, UseOptionalOrElseGetVsOrElse.class, - MakeNonReassignedVariablesConstants.class + MakeNonReassignedVariablesConstants.class, + AvoidNPlusOneProblemInJPAEntitiesCheck.class ); /** diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java new file mode 100644 index 00000000..c06c3bf4 --- /dev/null +++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheck.java @@ -0,0 +1,102 @@ +package org.greencodeinitiative.creedengo.java.checks; + +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; +import org.sonar.plugins.java.api.tree.*; + +import java.util.*; + +@Rule(key = "GCI604") +public class AvoidNPlusOneProblemInJPAEntitiesCheck extends IssuableSubscriptionVisitor { + + protected static final String RULE_MESSAGE = " Detection of the \"N+1 problem\" on Spring Data JPA repositories "; + + private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository"; + + private static final MethodMatchers SPRING_REPOSITORY_METHOD_FIND_ALL = + MethodMatchers.create() + .ofSubTypes(SPRING_REPOSITORY) + .names("findAll") + .withAnyParameters() + .build(); + + private final Map repositoryFindAllVars = new HashMap<>(); + + @Override + public List nodesToVisit() { + return Arrays.asList(Tree.Kind.VARIABLE, Tree.Kind.METHOD_INVOCATION, Tree.Kind.FOR_EACH_STATEMENT); + } + + @Override + public void visitNode(Tree tree) { + if (tree.is(Tree.Kind.VARIABLE)) { + VariableTree variableTree = (VariableTree) tree; + ExpressionTree initializer = variableTree.initializer(); + if (initializer != null && initializer.is(Tree.Kind.METHOD_INVOCATION)) { + MethodInvocationTree methodInvocation = (MethodInvocationTree) initializer; + if (SPRING_REPOSITORY_METHOD_FIND_ALL.matches(methodInvocation)) { + VariableSymbol symbol = (VariableSymbol) variableTree.symbol(); + repositoryFindAllVars.put(symbol, tree); + } + } + } + + // Cas d'un foreach sur une variable issue de findAll() + if (tree.is(Tree.Kind.FOR_EACH_STATEMENT)) { + ForEachStatement forEach = (ForEachStatement) tree; + ExpressionTree iterable = forEach.expression(); + if (iterable.is(Tree.Kind.IDENTIFIER)) { + Symbol symbol = ((IdentifierTree) iterable).symbol(); + if (repositoryFindAllVars.containsKey(symbol)) { + // On marque la variable d'itération comme issue d'un findAll() + VariableSymbol loopVar = (VariableSymbol) forEach.variable().symbol(); + repositoryFindAllVars.put(loopVar, tree); + } + } + } + + // Détection d'un appel de getter sur une variable issue de findAll() + if (tree.is(Tree.Kind.METHOD_INVOCATION)) { + MethodInvocationTree methodInvocation = (MethodInvocationTree) tree; + + // Check if the call is something like post.getAuthor() or post.getAuthor().getName() + ExpressionTree select = methodInvocation.methodSelect(); + if (select.is(Tree.Kind.MEMBER_SELECT)) { + MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) select; + ExpressionTree root = memberSelect.expression(); + + if (root.is(Tree.Kind.IDENTIFIER)) { + Symbol symbol = ((IdentifierTree) root).symbol(); + if (repositoryFindAllVars.containsKey(symbol) && isGetter(memberSelect.identifier().name())) { + reportIssue(methodInvocation, RULE_MESSAGE); + } + } + + // Handle nested getter chains (e.g., post.getAuthor().getName()) + if (root.is(Tree.Kind.METHOD_INVOCATION)) { + MethodInvocationTree rootInvocation = (MethodInvocationTree) root; + ExpressionTree deeperSelect = rootInvocation.methodSelect(); + if (deeperSelect.is(Tree.Kind.MEMBER_SELECT)) { + MemberSelectExpressionTree deeperMemberSelect = (MemberSelectExpressionTree) deeperSelect; + ExpressionTree deeperRoot = deeperMemberSelect.expression(); + + if (deeperRoot.is(Tree.Kind.IDENTIFIER)) { + Symbol rootSymbol = ((IdentifierTree) deeperRoot).symbol(); + if (repositoryFindAllVars.containsKey(rootSymbol) && isGetter(deeperMemberSelect.identifier().name())) { + reportIssue(methodInvocation, RULE_MESSAGE); + } + } + } + } + } + } + } + + // Méthode utilitaire pour détecter un getter + private boolean isGetter(String methodName) { + return methodName.startsWith("get") && methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3)); + } +} diff --git a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json index 059bf0f5..299073b9 100644 --- a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json +++ b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json @@ -18,6 +18,7 @@ "GCI78", "GCI79", "GCI82", - "GCI94" + "GCI94", + "GCI604" ] } diff --git a/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java new file mode 100644 index 00000000..4c698a6f --- /dev/null +++ b/src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java @@ -0,0 +1,109 @@ +/* + * creedengo - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.greencodeinitiative.creedengo.java.checks; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.*; + +public class AvoidNPlusOneProblemInJPAEntitiesCheckIssue { + + @Autowired + private AuthorRepository authorRepository; + + public List smellGetAllAuthors() { + List authors = authorRepository.findAll(); + for (Author author : authors) { + List books = author.getBooks(); // Noncompliant {{ Detection of the "N+1 problem" on Spring Data JPA repositories }} + } + return authors; + } + + + public class Author { + + private Long id; + private String name; + + private List books; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getBooks() { + return books; + } + + public void setBooks(List books) { + this.books = books; + } + } + + + public class Book { + + private Long id; + private String title; + + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + public interface AuthorRepository extends JpaRepository { + + } + +} diff --git a/src/test/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckTest.java b/src/test/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckTest.java new file mode 100644 index 00000000..a960c569 --- /dev/null +++ b/src/test/java/org/greencodeinitiative/creedengo/java/checks/AvoidNPlusOneProblemInJPAEntitiesCheckTest.java @@ -0,0 +1,35 @@ +/* + * creedengo - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.greencodeinitiative.creedengo.java.checks; + +import org.greencodeinitiative.creedengo.java.utils.FilesUtils; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class AvoidNPlusOneProblemInJPAEntitiesCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile("src/test/files/AvoidNPlusOneProblemInJPAEntitiesCheckIssue.java") + .withCheck(new AvoidNPlusOneProblemInJPAEntitiesCheck()) + .withClassPath(FilesUtils.getClassPath("target/test-jars")) + .verifyIssues(); + } + +}