From 8dee0501baa91dd0abd35b11607bfbe848676421 Mon Sep 17 00:00:00 2001 From: zinedine Date: Wed, 21 May 2025 13:56:04 +0200 Subject: [PATCH] GCI1111[PLANET][2025] : Hibernate queries must be paginated to avoid excessive data loading --- pom.xml | 2 +- .../java/integration/tests/GCIRulesIT.java | 22 +++ .../DataInHibernateMustBePaginated.java | 28 ++++ ...DataInHibernateMustBePaginatedNoIssue.java | 22 +++ .../creedengo/java/JavaCheckRegistrar.java | 3 +- .../DataInHibernateMustBePaginated.java | 128 ++++++++++++++++++ .../creedengo/java/creedengo_way_profile.json | 3 +- .../files/DataInHibernateMustBePaginated.java | 23 ++++ ...DataInHibernateMustBePaginatedNoIssue.java | 22 +++ .../DataInHibernateMustBePaginatedTest.java | 39 ++++++ 10 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java create mode 100644 src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedNoIssue.java create mode 100644 src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java create mode 100644 src/test/files/DataInHibernateMustBePaginated.java create mode 100644 src/test/files/DataInHibernateMustBePaginatedNoIssue.java create mode 100644 src/test/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedTest.java diff --git a/pom.xml b/pom.xml index ae43a190..d3b2aa62 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 1.8 - 2.2.2 + 1.0-SNAPSHOT https://repo1.maven.org/maven2 diff --git a/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java b/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java index dfd7957c..40bb7b0b 100644 --- a/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java +++ b/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java @@ -548,4 +548,26 @@ void testGCI94() { checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1MIN); } + + @Test + void testGCI1111() { + String filePath = "src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java"; + String ruleId = "creedengo-java:GCI1111"; + String ruleMsg = "Hibernate queries must be paginated to avoid excessive data loading"; + int[] startLines = new int[]{15, 18, 21}; + int[] endLines = new int[]{15, 18, 21}; + + checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_20MIN); + } + + @Test + void testGCI1111NoIssue() { + String filePath = "src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedNoIssue.java"; + String ruleId = "creedengo-java:GCI1111"; + String ruleMsg = "Hibernate queries must be paginated to avoid excessive data loading"; + int[] startLines = new int[]{}; + int[] endLines = new int[]{}; + checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_20MIN); + + } } diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java new file mode 100644 index 00000000..7245cef4 --- /dev/null +++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java @@ -0,0 +1,28 @@ +package org.greencodeinitiative.creedengo.java.checks; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +// L'interface doit être package-private (pas "public") pour rester dans ce fichier +@Repository +interface DataInHibernateMustBePaginated extends JpaRepository { + + List findAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}} + + @Query(value = "SELECT * FROM users", nativeQuery = true) + List loadAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}} + + @Query("SELECT u FROM User u") + List getUsers(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}} + + Page findAll(Pageable pageable); // OK + + @Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true) + Page findAllNative(Pageable pageable); // OK +} + diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedNoIssue.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedNoIssue.java new file mode 100644 index 00000000..9015db6c --- /dev/null +++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedNoIssue.java @@ -0,0 +1,22 @@ +import java.util.List; +import org.springframework.stereotype.Repository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Page; + +public interface DataInHibernateMustBePaginatedNoIssue { + + List findAll(); + + @Query(value = "SELECT * FROM users", nativeQuery = true) + List loadAll(); + + @Query("SELECT u FROM User u") + List getUsers(); + + Page findAll(Pageable pageable); // OK + + @Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true) + Page findAllNative(Pageable pageable); // OK +} \ No newline at end of file diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java index 791f0cef..6e3bfe0f 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, + DataInHibernateMustBePaginated.class ); /** diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java new file mode 100644 index 00000000..8c253578 --- /dev/null +++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginated.java @@ -0,0 +1,128 @@ +package org.greencodeinitiative.creedengo.java.checks; + +import java.util.*; + +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.InputFileScannerContext; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.*; +import org.sonar.plugins.java.api.tree.Tree.Kind; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Type; + + +import java.util.Collections; +import java.util.List; + +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.*; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Type; + +import java.util.Collections; +import java.util.List; + +@Rule(key = "GCI1111") +public class DataInHibernateMustBePaginated extends IssuableSubscriptionVisitor { + + private static final String MESSAGE = "Hibernate queries must be paginated to avoid excessive data loading"; + + @Override + public List nodesToVisit() { + return Collections.singletonList(Kind.METHOD); + } + + @Override + public void visitNode(Tree tree) { + MethodTree methodTree = (MethodTree) tree; + ClassTree enclosingClass = getEnclosingClass(tree); + + if (enclosingClass == null || !isRepository(enclosingClass)) { + return; // Ne rien faire si ce n’est pas un Repository + } + + + // Récupérer le nom de la méthode + String methodName = methodTree.simpleName().name(); + + // Récupérer le type de retour + String returnType = methodTree.returnType().symbolType().toString(); + + // Vérifier s’il retourne une collection entière (sans pagination) + boolean returnsAllData = retrournAllData(returnType); + + // Vérifier s’il utilise la pagination + boolean hasPaginationParam = isHasPaginationParam(methodTree); + + // Vérifier la présence de l’annotation @Query + boolean usesQueryAnnotation = isUsesQueryAnnotation(methodTree); + + extracted(returnsAllData, hasPaginationParam, methodTree, usesQueryAnnotation); + + } + + private void extracted(boolean returnsAllData, boolean hasPaginationParam, MethodTree methodTree, boolean usesQueryAnnotation) { + // Déclencher la règle + if (returnsAllData && !hasPaginationParam) { + reportIssue(methodTree.simpleName(), + MESSAGE); + } else if (usesQueryAnnotation && returnsAllData && !hasPaginationParam) { + reportIssue(methodTree.simpleName(), + MESSAGE); + } + } + + private static boolean isUsesQueryAnnotation(MethodTree methodTree) { + boolean usesQueryAnnotation = methodTree.modifiers().annotations().stream() + .anyMatch(ann -> ann.annotationType().toString().equals("Query")); + return usesQueryAnnotation; + } + + private static boolean isHasPaginationParam(MethodTree methodTree) { + boolean hasPaginationParam = methodTree.parameters().stream() + .anyMatch(param -> { + String type = param.type().toString(); + return type.contains("Pageable") || type.contains("PageRequest"); + }); + return hasPaginationParam; + } + + public boolean retrournAllData(String returnType) { + return returnType.startsWith("List") + || returnType.startsWith("Set") + || returnType.startsWith("Collection") + || returnType.startsWith("Iterable"); + } + + private ClassTree getEnclosingClass(Tree tree) { + Tree parent = tree.parent(); + while (parent != null && !(parent instanceof ClassTree)) { + parent = parent.parent(); + } + return (ClassTree) parent; + } + + private boolean isRepository(ClassTree classTree) { + // Vérifie si la classe implémente une interface Repository (comme JpaRepository) + Symbol.TypeSymbol symbol = classTree.symbol(); + if (symbol == null) { + return false; + } + + for (Type iface : symbol.type().symbol().interfaces()) { + String ifaceName = iface.fullyQualifiedName(); + if (ifaceName.contains("JpaRepository") || ifaceName.contains("CrudRepository") || ifaceName.contains("PagingAndSortingRepository")) { + return true; + } + } + + // Ou si l'annotation Repository est présente + return classTree.modifiers().annotations().stream() + .anyMatch(ann -> ann.annotationType().toString().endsWith("Repository")); + } + + + + +} \ No newline at end of file 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..417b58e4 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", + "GCI1111" ] } diff --git a/src/test/files/DataInHibernateMustBePaginated.java b/src/test/files/DataInHibernateMustBePaginated.java new file mode 100644 index 00000000..6cd80fe6 --- /dev/null +++ b/src/test/files/DataInHibernateMustBePaginated.java @@ -0,0 +1,23 @@ +import java.util.List; +import org.springframework.stereotype.Repository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Page; + +@Repository +public interface UserRepository extends JpaRepository { + + List findAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}} + + @Query(value = "SELECT * FROM users", nativeQuery = true) + List loadAll(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}} + + @Query("SELECT u FROM User u") + List getUsers(); // Noncompliant {{Hibernate queries must be paginated to avoid excessive data loading}} + + Page findAll(Pageable pageable); // OK + + @Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true) + Page findAllNative(Pageable pageable); // OK +} diff --git a/src/test/files/DataInHibernateMustBePaginatedNoIssue.java b/src/test/files/DataInHibernateMustBePaginatedNoIssue.java new file mode 100644 index 00000000..e8955c57 --- /dev/null +++ b/src/test/files/DataInHibernateMustBePaginatedNoIssue.java @@ -0,0 +1,22 @@ +import java.util.List; +import org.springframework.stereotype.Repository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Page; + +public interface UserRepository { + + List findAll(); + + @Query(value = "SELECT * FROM users", nativeQuery = true) + List loadAll(); + + @Query("SELECT u FROM User u") + List getUsers(); + + Page findAll(Pageable pageable); // OK + + @Query(value = "SELECT * FROM users", countQuery = "SELECT count(*) FROM users", nativeQuery = true) + Page findAllNative(Pageable pageable); // OK +} \ No newline at end of file diff --git a/src/test/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedTest.java b/src/test/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedTest.java new file mode 100644 index 00000000..1bcb9941 --- /dev/null +++ b/src/test/java/org/greencodeinitiative/creedengo/java/checks/DataInHibernateMustBePaginatedTest.java @@ -0,0 +1,39 @@ +/* + * 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.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class DataInHibernateMustBePaginatedTest { + @Test + void test() { + CheckVerifier.newVerifier() + .onFile("src/test/files/DataInHibernateMustBePaginated.java") + .withCheck(new DataInHibernateMustBePaginated()) + .verifyIssues(); + } + + @Test + void testNoIssue() { + CheckVerifier.newVerifier() + .onFile("src/test/files/DataInHibernateMustBePaginatedNoIssue.java") + .withCheck(new DataInHibernateMustBePaginated()) + .verifyNoIssues(); + } +}