From 039e409495a36bf4bc09580b85be8952c122cf41 Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Wed, 15 Mar 2023 12:25:33 +0100 Subject: [PATCH 1/2] Added scope related tests ThirdPartyRulesTest.java: - combined all third-party rules inside ThirdPartyRulesTest.java - deleted distinct rule test files - changed all ArchRules to public as a part of the preparation for task #19 - rules 1, 3, and 4 utilize now the present PackageStructure type. --- rules 1 and 4 also test scope-related conditions for classic architecture projects now. --- .../ThirdPartyRulesE1TransactionalTest.java | 48 ---- .../ThirdPartyRulesE3JpaCheckTest.java | 61 ----- .../ThirdPartyRulesE4HibernateCheckTest.java | 98 ------- .../sample/archunit/ThirdPartyRulesTest.java | 247 ++++++++++++++++-- 4 files changed, 230 insertions(+), 224 deletions(-) delete mode 100644 src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE1TransactionalTest.java delete mode 100644 src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE3JpaCheckTest.java delete mode 100644 src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE4HibernateCheckTest.java diff --git a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE1TransactionalTest.java b/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE1TransactionalTest.java deleted file mode 100644 index 45eb8e8..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE1TransactionalTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.devonfw.sample.archunit; - -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - -import com.tngtech.archunit.core.domain.Dependency; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; - -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ThirdPartyRulesE1TransactionalTest { - - private static boolean isUsingSpringframeworkTransactionalAnnotation(String targetPackageFullName) { - - if (targetPackageFullName.equals("org.springframework.transaction.annotation.Transactional")) { - return true; - } - return false; - } - - static final ArchCondition misuse_springframework_transactional_annotation = new ArchCondition( - "misuse @Transactional (Rule-E1)") { - @Override - public void check(JavaClass sourceClass, ConditionEvents events) { - - for (Dependency dependency : sourceClass.getDirectDependenciesFromSelf()) { - String targetFullName = dependency.getTargetClass().getFullName(); - String targetClassDescription = dependency.getDescription(); - if (isUsingSpringframeworkTransactionalAnnotation(targetFullName) == true) { - String message = String.format( - "Use JEE standard (javax.transaction.Transactional from javax.transaction:javax.transaction-api:1.2+). The use (%s) is discouraged. Violated in (%s)", - targetFullName, targetClassDescription); - events.add(new SimpleConditionEvent(sourceClass, true, message)); - } - } - } - }; - - @ArchTest - static final ArchRule verifying_proper_transactional_use_from_jee = noClasses() - .should(misuse_springframework_transactional_annotation).allowEmptyShould(true); - -} diff --git a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE3JpaCheckTest.java b/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE3JpaCheckTest.java deleted file mode 100644 index 0051be1..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE3JpaCheckTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.devonfw.sample.archunit; - -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.tngtech.archunit.core.domain.Dependency; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; - -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ThirdPartyRulesE3JpaCheckTest { - private static final Pattern PATTERN_COMMON = Pattern.compile(PackageRuleTest.COMMON_PATTERN); - - private static final Pattern PATTERN_DATAACCESS = Pattern.compile(PackageRuleTest.DATAACCESS_PATTERN); - - private static boolean isUsingJavaxPersistenceDataAccessOrEmbeddablesInCommon(JavaClass source, - String targetPackageFullName) { - - if (targetPackageFullName.startsWith("javax.persistence")) { - Matcher commonMatcher = PATTERN_COMMON.matcher(source.getPackageName()); - Matcher dataaccessMatcher = PATTERN_DATAACCESS.matcher(source.getPackageName()); - if (dataaccessMatcher.matches()) { - return true; - } - if (commonMatcher.matches() && source.getSimpleName().contains("Embeddable")) { - return true; - } - return false; - } - return true; - } - - static final ArchCondition misuse_jpa = new ArchCondition( - "use JPA outside of dataaccess layer or embeddables in common layer (Rule-E3)") { - @Override - public void check(JavaClass item, ConditionEvents events) { - - for (Dependency access : item.getDirectDependenciesFromSelf()) { - String targetPackageFullName = access.getTargetClass().getFullName(); - String targetClassDescription = access.getDescription(); - if (isUsingJavaxPersistenceDataAccessOrEmbeddablesInCommon(item, targetPackageFullName) == false) { - String message = String.format( - "JPA (%s) shall only be used in dataaccess layer or for embeddables in common layer. Violated in (%s)", - targetPackageFullName, targetClassDescription); - events.add(new SimpleConditionEvent(item, true, message)); - } - } - } - }; - - @ArchTest - static final ArchRule verifying_proper_jpa_use = noClasses().should(misuse_jpa).allowEmptyShould(true); -} diff --git a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE4HibernateCheckTest.java b/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE4HibernateCheckTest.java deleted file mode 100644 index cdf0899..0000000 --- a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesE4HibernateCheckTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.devonfw.sample.archunit; - -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.tngtech.archunit.core.domain.Dependency; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; - -/** - * {@link DevonArchitecture3rdPartyCheck} verifying that the {@code JPA} is properly used. - */ -@AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) -public class ThirdPartyRulesE4HibernateCheckTest { - - private static final Set DISCOURAGED_HIBERNATE_ANNOTATIONS = new HashSet<>( - Arrays.asList("OrderBy", "Entity", "AccessType", "ForeignKey", "Cascade", "Index", "IndexColumn")); - - private static final String ORG_HIBERNATE_ENVERS = "org.hibernate.envers"; - - private static final String ORG_HIBERNATE_VALIDATOR = "org.hibernate.validator"; - - private static final String ORG_HIBERNATE_ANNOTATIONS = "org.hibernate.annotations"; - - private static final Pattern PATTERN_DATAACCESS = Pattern.compile(PackageRuleTest.DATAACCESS_PATTERN); - - private static boolean isUsingHibernateOutsideOfDataaccessLayer(JavaClass source, String targetPackageName) { - - Matcher dataaccessMatcher = PATTERN_DATAACCESS.matcher(source.getPackageName()); - if (!dataaccessMatcher.matches()) { - return true; - } - return false; - } - - private static boolean isUsingProprietaryHibernateAnnotation(String targetPackageName, String targetSimpleName) { - - if (targetPackageName.equals(ORG_HIBERNATE_ANNOTATIONS) - && DISCOURAGED_HIBERNATE_ANNOTATIONS.contains(targetSimpleName)) { - return true; - } - return false; - } - - private static boolean isImplementingHibernateEnversInternalsDirectly(String targetPackageName) { - - if (targetPackageName.startsWith(ORG_HIBERNATE_ENVERS) && targetPackageName.contains("internal")) { - return true; - } - return false; - } - - static final ArchCondition misUseHibernate = new ArchCondition("misuse hibernate (Rule-E4).") { - @Override - public void check(JavaClass source, ConditionEvents events) { - - for (Dependency dependency : source.getDirectDependenciesFromSelf()) { - String targetPackageName = dependency.getTargetClass().getPackageName(); - String targetPackageFullName = dependency.getTargetClass().getFullName(); - String targetClassDescription = dependency.getDescription(); - String targetSimpleName = dependency.getTargetClass().getSimpleName(); - if (targetPackageName.startsWith("org.hibernate") && !targetPackageName.startsWith(ORG_HIBERNATE_VALIDATOR)) { - if (isUsingHibernateOutsideOfDataaccessLayer(source, targetPackageName) == true) { - String message = String.format("Hibernate (%s) should only be used in dataaccess layer. Violated in (%s)", - targetPackageFullName, targetClassDescription); - events.add(new SimpleConditionEvent(source, true, message)); - } - if (isUsingProprietaryHibernateAnnotation(targetPackageName, targetSimpleName) == true) { - String message = String.format( - "Standard JPA annotations should be used instead of this proprietary hibernate annotation (%s). Violated in (%s)", - targetPackageFullName, targetClassDescription); - events.add(new SimpleConditionEvent(source, true, message)); - } - if (isImplementingHibernateEnversInternalsDirectly(targetPackageName) == true) { - String message = String.format( - "Hibernate envers internals (%s) should never be used directly. Violated in (%s)", - targetPackageFullName, targetClassDescription); - events.add(new SimpleConditionEvent(source, true, message)); - } - } - } - } - }; - - @ArchTest - static final ArchRule jpa_is_used_as_encouraged = noClasses().should(misUseHibernate).allowEmptyShould(true); -} diff --git a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java b/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java index fec9265..5916646 100644 --- a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java @@ -1,31 +1,244 @@ package com.devonfw.sample.archunit; -import com.tngtech.archunit.core.importer.ImportOption; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; @AnalyzeClasses(packages = "com.devonfw.sample.archunit", importOptions = ImportOption.DoNotIncludeTests.class) public class ThirdPartyRulesTest { + private static final Set DISCOURAGED_HIBERNATE_ANNOTATIONS = new HashSet<>( + Arrays.asList("OrderBy", "Entity", "AccessType", "ForeignKey", "Cascade", "Index", "IndexColumn")); + + private static final String ORG_HIBERNATE_ENVERS = "org.hibernate.envers"; + + private static final String ORG_HIBERNATE_VALIDATOR = "org.hibernate.validator"; + + private static final String ORG_HIBERNATE_ANNOTATIONS = "org.hibernate.annotations"; + + @ArchTest + public static ArchRule check_object_dependency = noClasses().should().dependOnClassesThat() + .haveFullyQualifiedName("com.google.common.base.Objects") + .because("Use Java standards instead (java.util.Objects)."); + + @ArchTest + public static ArchRule check_converter_dependency = noClasses().should().dependOnClassesThat() + .haveFullyQualifiedName("javax.persistence.Convert") + .because("Use the javax.persistence.Converter annotation on a custom converter" + + " which implements the javax.persistence.AttributeConverter instead of the 'javax.persistance.Convert' annotation"); + + @ArchTest + public static ArchRule check_mysema_dependency = noClasses().should().dependOnClassesThat() + .resideInAPackage("com.mysema.query..") + .because("Use official QueryDSL (com.querydsl.* e.g. from com.querydsl:querydsl-jpa)."); + + /* + * E1 + */ + private static boolean isApiScopedClassUsingTransactional(JavaClass source, String targetPackageFullName) { + + PackageStructure sourcePkg = PackageStructure.of(source); + + if (sourcePkg.isScopeApi() && targetPackageFullName.equals("javax.transaction.Transactional")) { + return true; + } + return false; + } + + private static boolean isUsingSpringframeworkTransactionalAnnotation(String targetPackageFullName) { + + if (targetPackageFullName.equals("org.springframework.transaction.annotation.Transactional")) { + return true; + } + return false; + } + + public static final ArchCondition misuse_springframework_transactional_annotation = new ArchCondition( + "misuse @Transactional (Rule-E1)") { + @Override + public void check(JavaClass source, ConditionEvents events) { + + for (Dependency dependency : source.getDirectDependenciesFromSelf()) { + String targetFullName = dependency.getTargetClass().getFullName(); + String targetClassDescription = dependency.getDescription(); + if (isUsingSpringframeworkTransactionalAnnotation(targetFullName) == true) { + String message = String.format( + "Use JEE standard (javax.transaction.Transactional from javax.transaction:javax.transaction-api:1.2+). The use (%s) is discouraged. Violated in (%s)", + targetFullName, targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + /* + * In case the project has a classic architecture using scopes, check that no API scoped class is using + * 'javax.transaction.Transactional' + */ + if (isApiScopedClassUsingTransactional(source, targetFullName) == true) { + String message = String.format( + "The use of @Transactional in API is discouraged. Instead use it to annotate implementations. Violated in (%s)", + targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + } + } + }; + + @ArchTest + public static final ArchRule E1_verifying_proper_transactional_use_from_jee = noClasses() + .should(misuse_springframework_transactional_annotation).allowEmptyShould(true); + + /* + * E3 + */ + private static boolean isUsingJavaxPersistenceDataAccessOrEmbeddablesInCommon(JavaClass source, + String targetPackageFullName) { + + if (targetPackageFullName.startsWith("javax.persistence")) { + PackageStructure sourcePkg = PackageStructure.of(source); + if (sourcePkg.isLayerDataAccess()) { + return true; + } + if (sourcePkg.isLayerCommon() && source.getSimpleName().contains("Embeddable")) { + return true; + } + return false; + } + return true; + } + + public static final ArchCondition misuse_jpa = new ArchCondition( + "use JPA outside of dataaccess layer or embeddables in common layer (Rule-E3)") { + @Override + public void check(JavaClass source, ConditionEvents events) { + + for (Dependency dependency : source.getDirectDependenciesFromSelf()) { + String targetPackageFullName = dependency.getTargetClass().getFullName(); + String targetClassDescription = dependency.getDescription(); + if (isUsingJavaxPersistenceDataAccessOrEmbeddablesInCommon(source, targetPackageFullName) == false) { + String message = String.format( + "JPA (%s) shall only be used in dataaccess layer or for embeddables in common layer. Violated in (%s)", + targetPackageFullName, targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + } + } + }; + + @ArchTest + public static final ArchRule E3_verifying_proper_jpa_use = noClasses().should(misuse_jpa).allowEmptyShould(true); + + /* + * E4 + */ + private static boolean isUsingHibernateOutsideOfDataaccessLayer(JavaClass source, String targetPackageName) { + + PackageStructure sourcePkg = PackageStructure.of(source); + if (!sourcePkg.isLayerDataAccess()) { + return true; + } + return false; + } + + private static boolean isUsingProprietaryHibernateAnnotation(String targetPackageName, String targetSimpleName) { + + if (targetPackageName.equals(ORG_HIBERNATE_ANNOTATIONS) + && DISCOURAGED_HIBERNATE_ANNOTATIONS.contains(targetSimpleName)) { + return true; + } + return false; + } + + private static boolean isImplementingHibernateEnversInternalsDirectly(String targetPackageName) { + + if (targetPackageName.startsWith(ORG_HIBERNATE_ENVERS) && targetPackageName.contains("internal")) { + return true; + } + return false; + } + + private static boolean isUsingHibernateOutsideOfImplScope(JavaClass source, String targetPackageName) { + + PackageStructure sourcePkg = PackageStructure.of(source); + if (!sourcePkg.isScopeImpl() && targetPackageName.startsWith("org.hibernate") + && !targetPackageName.startsWith(ORG_HIBERNATE_VALIDATOR)) { + return true; + } + return false; + } + + private static boolean isNotImplementingHibernateEnversInImplScope(JavaClass source, String targetPackageName, + String targetPackageFullName, String targetSimpleName) { - @ArchTest - private static ArchRule check_object_dependency = noClasses() - .should().dependOnClassesThat() - .haveFullyQualifiedName("com.google.common.base.Objects") - .because("Use Java standards instead (java.util.Objects)."); + PackageStructure sourcePkg = PackageStructure.of(source); + if (!sourcePkg.isScopeImpl() && targetPackageName.startsWith(ORG_HIBERNATE_ENVERS) + && (!targetPackageFullName.equals(ORG_HIBERNATE_ENVERS) || targetSimpleName.startsWith("Default") + || targetSimpleName.contains("Listener") || targetSimpleName.contains("Reader"))) { + return true; + } + return false; + } - @ArchTest - private static ArchRule check_converter_dependency = noClasses() - .should().dependOnClassesThat() - .haveFullyQualifiedName("javax.persistence.Convert") - .because("Use the javax.persistence.Converter annotation on a custom converter" - + " which implements the javax.persistence.AttributeConverter instead of the 'javax.persistance.Convert' annotation"); + public static final ArchCondition misUseHibernate = new ArchCondition( + "misuse hibernate (Rule-E4).") { + @Override + public void check(JavaClass source, ConditionEvents events) { - @ArchTest - private static ArchRule check_mysema_dependency = noClasses() - .should().dependOnClassesThat().resideInAPackage("com.mysema.query..") - .because("Use official QueryDSL (com.querydsl.* e.g. from com.querydsl:querydsl-jpa)."); + for (Dependency dependency : source.getDirectDependenciesFromSelf()) { + String targetPackageName = dependency.getTargetClass().getPackageName(); + String targetPackageFullName = dependency.getTargetClass().getFullName(); + String targetClassDescription = dependency.getDescription(); + String targetSimpleName = dependency.getTargetClass().getSimpleName(); + if (targetPackageName.startsWith("org.hibernate") && !targetPackageName.startsWith(ORG_HIBERNATE_VALIDATOR)) { + if (isUsingHibernateOutsideOfDataaccessLayer(source, targetPackageName) == true) { + String message = String.format("Hibernate (%s) should only be used in dataaccess layer. Violated in (%s)", + targetPackageFullName, targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + if (isUsingProprietaryHibernateAnnotation(targetPackageName, targetSimpleName) == true) { + String message = String.format( + "Standard JPA annotations should be used instead of this proprietary hibernate annotation (%s). Violated in (%s)", + targetPackageFullName, targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + if (isImplementingHibernateEnversInternalsDirectly(targetPackageName) == true) { + String message = String.format( + "Hibernate envers internals (%s) should never be used directly. Violated in (%s)", + targetPackageFullName, targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + /* + * In case the project has a classic architecture that uses scopes, check that Hibernate.Envers are only + * utilized inside the impl scope of the dataaccess layer. In addition, Hibernate internals also need to be + * used inside the impl scope of the dataaccess layer. + */ + if (isNotImplementingHibernateEnversInImplScope(source, targetPackageName, targetPackageFullName, + targetSimpleName) == true) { + String message = String.format( + "Hibernate envers implementation (%s) should only be used in impl scope of dataaccess layer. Violated in (%s)", + targetPackageFullName, targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + if (isUsingHibernateOutsideOfImplScope(source, targetPackageName) == true) { + String message = String.format( + "Hibernate internals (%s) should only be used in impl scope of dataaccess layer. Violated in (%s)", + targetPackageFullName, targetClassDescription); + events.add(new SimpleConditionEvent(source, true, message)); + } + } + } + } + }; + @ArchTest + public static final ArchRule E4_jpa_is_used_as_encouraged = noClasses().should(misUseHibernate) + .allowEmptyShould(true); } \ No newline at end of file From 639545e426a042e05fb64f60eb04840bf05df3dc Mon Sep 17 00:00:00 2001 From: Vladislav Sehtman Date: Tue, 21 Mar 2023 11:10:54 +0100 Subject: [PATCH 2/2] Update ThirdPartyRulesTest.java -Implemented the most recent annotation of @hohwille. -Minor refactoring. --- .../sample/archunit/ThirdPartyRulesTest.java | 82 ++++++++----------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java b/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java index 5916646..e79ba10 100644 --- a/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java +++ b/src/test/java/com/devonfw/sample/archunit/ThirdPartyRulesTest.java @@ -43,9 +43,6 @@ public class ThirdPartyRulesTest { .resideInAPackage("com.mysema.query..") .because("Use official QueryDSL (com.querydsl.* e.g. from com.querydsl:querydsl-jpa)."); - /* - * E1 - */ private static boolean isApiScopedClassUsingTransactional(JavaClass source, String targetPackageFullName) { PackageStructure sourcePkg = PackageStructure.of(source); @@ -56,28 +53,14 @@ private static boolean isApiScopedClassUsingTransactional(JavaClass source, Stri return false; } - private static boolean isUsingSpringframeworkTransactionalAnnotation(String targetPackageFullName) { - - if (targetPackageFullName.equals("org.springframework.transaction.annotation.Transactional")) { - return true; - } - return false; - } - - public static final ArchCondition misuse_springframework_transactional_annotation = new ArchCondition( - "misuse @Transactional (Rule-E1)") { + public static ArchCondition verifyingTransactionalAnnotationIsNotUsedInsideApi = new ArchCondition( + "use @Transactional in API") { @Override public void check(JavaClass source, ConditionEvents events) { for (Dependency dependency : source.getDirectDependenciesFromSelf()) { String targetFullName = dependency.getTargetClass().getFullName(); String targetClassDescription = dependency.getDescription(); - if (isUsingSpringframeworkTransactionalAnnotation(targetFullName) == true) { - String message = String.format( - "Use JEE standard (javax.transaction.Transactional from javax.transaction:javax.transaction-api:1.2+). The use (%s) is discouraged. Violated in (%s)", - targetFullName, targetClassDescription); - events.add(new SimpleConditionEvent(source, true, message)); - } /* * In case the project has a classic architecture using scopes, check that no API scoped class is using * 'javax.transaction.Transactional' @@ -93,12 +76,14 @@ public void check(JavaClass source, ConditionEvents events) { }; @ArchTest - public static final ArchRule E1_verifying_proper_transactional_use_from_jee = noClasses() - .should(misuse_springframework_transactional_annotation).allowEmptyShould(true); + public static ArchRule verifyingSpringframeworkTransactionalIsNotUsed = noClasses().should().dependOnClassesThat() + .haveFullyQualifiedName("org.springframework.transaction.annotation.Transactional") + .because("Use JEE standard (javax.transaction.Transactional from javax.transaction:javax.transaction-api:1.2+)."); + + @ArchTest + public static ArchRule verifyingProperTransactionalUseFromJee = noClasses() + .should(verifyingTransactionalAnnotationIsNotUsedInsideApi).allowEmptyShould(true); - /* - * E3 - */ private static boolean isUsingJavaxPersistenceDataAccessOrEmbeddablesInCommon(JavaClass source, String targetPackageFullName) { @@ -116,7 +101,7 @@ private static boolean isUsingJavaxPersistenceDataAccessOrEmbeddablesInCommon(Ja } public static final ArchCondition misuse_jpa = new ArchCondition( - "use JPA outside of dataaccess layer or embeddables in common layer (Rule-E3)") { + "use JPA outside of dataaccess layer or embeddables in common layer") { @Override public void check(JavaClass source, ConditionEvents events) { @@ -134,12 +119,9 @@ public void check(JavaClass source, ConditionEvents events) { }; @ArchTest - public static final ArchRule E3_verifying_proper_jpa_use = noClasses().should(misuse_jpa).allowEmptyShould(true); + public static final ArchRule verifyingProperJpaUse = noClasses().should(misuse_jpa).allowEmptyShould(true); - /* - * E4 - */ - private static boolean isUsingHibernateOutsideOfDataaccessLayer(JavaClass source, String targetPackageName) { + private static boolean isUsingHibernateOutsideOfDataaccessLayer(JavaClass source) { PackageStructure sourcePkg = PackageStructure.of(source); if (!sourcePkg.isLayerDataAccess()) { @@ -148,8 +130,10 @@ private static boolean isUsingHibernateOutsideOfDataaccessLayer(JavaClass source return false; } - private static boolean isUsingProprietaryHibernateAnnotation(String targetPackageName, String targetSimpleName) { + private static boolean isUsingProprietaryHibernateAnnotation(JavaClass targetClass) { + String targetPackageName = targetClass.getPackageName(); + String targetSimpleName = targetClass.getSimpleName(); if (targetPackageName.equals(ORG_HIBERNATE_ANNOTATIONS) && DISCOURAGED_HIBERNATE_ANNOTATIONS.contains(targetSimpleName)) { return true; @@ -157,16 +141,18 @@ private static boolean isUsingProprietaryHibernateAnnotation(String targetPackag return false; } - private static boolean isImplementingHibernateEnversInternalsDirectly(String targetPackageName) { + private static boolean isImplementingHibernateEnversInternalsDirectly(JavaClass targetClass) { + String targetPackageName = targetClass.getPackageName(); if (targetPackageName.startsWith(ORG_HIBERNATE_ENVERS) && targetPackageName.contains("internal")) { return true; } return false; } - private static boolean isUsingHibernateOutsideOfImplScope(JavaClass source, String targetPackageName) { + private static boolean isUsingHibernateOutsideOfImplScope(JavaClass source, JavaClass targetClass) { + String targetPackageName = targetClass.getPackageName(); PackageStructure sourcePkg = PackageStructure.of(source); if (!sourcePkg.isScopeImpl() && targetPackageName.startsWith("org.hibernate") && !targetPackageName.startsWith(ORG_HIBERNATE_VALIDATOR)) { @@ -175,9 +161,11 @@ private static boolean isUsingHibernateOutsideOfImplScope(JavaClass source, Stri return false; } - private static boolean isNotImplementingHibernateEnversInImplScope(JavaClass source, String targetPackageName, - String targetPackageFullName, String targetSimpleName) { + private static boolean isNotImplementingHibernateEnversInImplScope(JavaClass source, JavaClass targetClass) { + String targetPackageName = targetClass.getPackageName(); + String targetPackageFullName = targetClass.getFullName(); + String targetSimpleName = targetClass.getSimpleName(); PackageStructure sourcePkg = PackageStructure.of(source); if (!sourcePkg.isScopeImpl() && targetPackageName.startsWith(ORG_HIBERNATE_ENVERS) && (!targetPackageFullName.equals(ORG_HIBERNATE_ENVERS) || targetSimpleName.startsWith("Default") @@ -187,29 +175,29 @@ private static boolean isNotImplementingHibernateEnversInImplScope(JavaClass sou return false; } - public static final ArchCondition misUseHibernate = new ArchCondition( - "misuse hibernate (Rule-E4).") { + public static final ArchCondition misUseHibernate = new ArchCondition("misuse hibernate") { @Override public void check(JavaClass source, ConditionEvents events) { for (Dependency dependency : source.getDirectDependenciesFromSelf()) { - String targetPackageName = dependency.getTargetClass().getPackageName(); - String targetPackageFullName = dependency.getTargetClass().getFullName(); + JavaClass targetClass = dependency.getTargetClass(); + String targetPackageName = targetClass.getPackageName(); + String targetPackageFullName = targetClass.getFullName(); String targetClassDescription = dependency.getDescription(); - String targetSimpleName = dependency.getTargetClass().getSimpleName(); + if (targetPackageName.startsWith("org.hibernate") && !targetPackageName.startsWith(ORG_HIBERNATE_VALIDATOR)) { - if (isUsingHibernateOutsideOfDataaccessLayer(source, targetPackageName) == true) { + if (isUsingHibernateOutsideOfDataaccessLayer(source) == true) { String message = String.format("Hibernate (%s) should only be used in dataaccess layer. Violated in (%s)", targetPackageFullName, targetClassDescription); events.add(new SimpleConditionEvent(source, true, message)); } - if (isUsingProprietaryHibernateAnnotation(targetPackageName, targetSimpleName) == true) { + if (isUsingProprietaryHibernateAnnotation(targetClass) == true) { String message = String.format( "Standard JPA annotations should be used instead of this proprietary hibernate annotation (%s). Violated in (%s)", targetPackageFullName, targetClassDescription); events.add(new SimpleConditionEvent(source, true, message)); } - if (isImplementingHibernateEnversInternalsDirectly(targetPackageName) == true) { + if (isImplementingHibernateEnversInternalsDirectly(targetClass) == true) { String message = String.format( "Hibernate envers internals (%s) should never be used directly. Violated in (%s)", targetPackageFullName, targetClassDescription); @@ -220,14 +208,13 @@ public void check(JavaClass source, ConditionEvents events) { * utilized inside the impl scope of the dataaccess layer. In addition, Hibernate internals also need to be * used inside the impl scope of the dataaccess layer. */ - if (isNotImplementingHibernateEnversInImplScope(source, targetPackageName, targetPackageFullName, - targetSimpleName) == true) { + if (isNotImplementingHibernateEnversInImplScope(source, targetClass) == true) { String message = String.format( "Hibernate envers implementation (%s) should only be used in impl scope of dataaccess layer. Violated in (%s)", targetPackageFullName, targetClassDescription); events.add(new SimpleConditionEvent(source, true, message)); } - if (isUsingHibernateOutsideOfImplScope(source, targetPackageName) == true) { + if (isUsingHibernateOutsideOfImplScope(source, targetClass) == true) { String message = String.format( "Hibernate internals (%s) should only be used in impl scope of dataaccess layer. Violated in (%s)", targetPackageFullName, targetClassDescription); @@ -239,6 +226,5 @@ public void check(JavaClass source, ConditionEvents events) { }; @ArchTest - public static final ArchRule E4_jpa_is_used_as_encouraged = noClasses().should(misUseHibernate) - .allowEmptyShould(true); + public static final ArchRule jpaIsUsedAsEncouraged = noClasses().should(misUseHibernate).allowEmptyShould(true); } \ No newline at end of file