diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/AnnotationModulithMetadata.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/AnnotationModulithMetadata.java index e99588d7..2853f226 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/AnnotationModulithMetadata.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/AnnotationModulithMetadata.java @@ -15,6 +15,7 @@ */ package org.springframework.modulith.core; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -34,6 +35,7 @@ class AnnotationModulithMetadata implements ModulithMetadata { private final Class modulithType; private final Modulithic annotation; + private final String basePackage; /** * Creates a new {@link AnnotationModulithMetadata} for the given type and annotation. @@ -48,6 +50,7 @@ class AnnotationModulithMetadata implements ModulithMetadata { this.modulithType = modulithType; this.annotation = annotation; + this.basePackage = modulithType.getPackageName(); } /** @@ -123,4 +126,17 @@ class AnnotationModulithMetadata implements ModulithMetadata { .filter(StringUtils::hasText) // .or(() -> Optional.of(modulithType.getSimpleName())); } + + /* + * (non-Javadoc) + * @see org.springframework.modulith.core.ModulithMetadata#getBasePackages() + */ + @Override + public List getBasePackages() { + + var result = new ArrayList<>(List.of(basePackage)); + result.addAll(List.of(annotation.additionalPackages())); + + return result; + } } diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java index 8625d1c2..808a55f9 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java @@ -107,9 +107,40 @@ public class ApplicationModules implements Iterable { private boolean verified; + /** + * Creates a new {@link ApplicationModules} instance. + * + * @param metadata must not be {@literal null}. + * @param ignored must not be {@literal null}. + * @param useFullyQualifiedModuleNames can be {@literal null}. + * @param option must not be {@literal null}. + */ + protected ApplicationModules(ModulithMetadata metadata, + DescribedPredicate ignored, boolean useFullyQualifiedModuleNames, ImportOption option) { + this(metadata, metadata.getBasePackages(), ignored, useFullyQualifiedModuleNames, option); + } + + /** + * Creates a new {@link ApplicationModules} instance. + * + * @param metadata must not be {@literal null}. + * @param packages must not be {@literal null}. + * @param ignored must not be {@literal null}. + * @param useFullyQualifiedModuleNames can be {@literal null}. + * @param option must not be {@literal null}. + * @deprecated since 1.2, for removal in 1.3. Use {@link ApplicationModules(ModulithMetadata, DescribedPredicate, + * boolean, ImportOption)} instead and set up {@link ModulithMetadata} to contain the packages you want to + * use. + */ + @Deprecated(forRemoval = true) protected ApplicationModules(ModulithMetadata metadata, Collection packages, DescribedPredicate ignored, boolean useFullyQualifiedModuleNames, ImportOption option) { + Assert.notNull(metadata, "ModulithMetadata must not be null!"); + Assert.notNull(packages, "Base packages must not be null!"); + Assert.notNull(ignored, "Ignores must not be null!"); + Assert.notNull(option, "ImportOptions must not be null!"); + this.metadata = metadata; this.allClasses = new ClassFileImporter() // .withImportOption(option) // @@ -190,10 +221,10 @@ public class ApplicationModules implements Iterable { } /** - * Creates a new {@link ApplicationModules} relative to the given modulith type, a - * {@link ApplicationModuleDetectionStrategy} and a {@link DescribedPredicate} which types and packages to ignore. - * Will inspect the {@link org.springframework.modulith.Modulith} and {@link org.springframework.modulith.Modulithic} - * annotations on the class given for advanced customizations of the module setup. + * Creates a new {@link ApplicationModules} relative to the given modulith type, and a {@link DescribedPredicate} + * which types and packages to ignore. Will inspect the {@link org.springframework.modulith.Modulith} and + * {@link org.springframework.modulith.Modulithic} annotations on the class given for advanced customizations of the + * module setup. * * @param modulithType must not be {@literal null}. * @param ignored must not be {@literal null}. @@ -201,15 +232,22 @@ public class ApplicationModules implements Iterable { */ public static ApplicationModules of(Class modulithType, DescribedPredicate ignored) { - CacheKey key = new TypeKey(modulithType, ignored); + Assert.notNull(modulithType, "Modulith root type must not be null!"); + Assert.notNull(ignored, "Predicate to describe ignored types must not be null!"); - return CACHE.computeIfAbsent(key, it -> { + return of(CacheKey.of(modulithType, ignored, IMPORT_OPTION)); + } - Assert.notNull(modulithType, "Modulith root type must not be null!"); - Assert.notNull(ignored, "Predicate to describe ignored types must not be null!"); - - return of(key); - }); + /** + * Creates a new {@link ApplicationModules} instance for the given type and {@link ImportOption}. + * + * @param modulithType must not be {@literal null}. + * @param options must not be {@literal null}. + * @return will never be {@literal null}. + * @since 1.2 + */ + public static ApplicationModules of(Class modulithType, ImportOption options) { + return of(CacheKey.of(modulithType, alwaysFalse(), options)); } /** @@ -231,15 +269,22 @@ public class ApplicationModules implements Iterable { */ public static ApplicationModules of(String javaPackage, DescribedPredicate ignored) { - CacheKey key = new PackageKey(javaPackage, ignored); + Assert.hasText(javaPackage, "Base package must not be null or empty!"); + Assert.notNull(ignored, "Predicate to describe ignored types must not be null!"); - return CACHE.computeIfAbsent(key, it -> { + return of(CacheKey.of(javaPackage, ignored, IMPORT_OPTION)); + } - Assert.hasText(javaPackage, "Base package must not be null or empty!"); - Assert.notNull(ignored, "Predicate to describe ignored types must not be null!"); - - return of(key); - }); + /** + * Creates a new {@link ApplicationModules} instance for the given package and {@link ImportOption}. + * + * @param javaPackage must not be {@literal null}. + * @param options must not be {@literal null}. + * @return will never be {@literal null}. + * @since 1.2 + */ + public static ApplicationModules of(String javaPackage, ImportOption options) { + return of(CacheKey.of(javaPackage, alwaysFalse(), options)); } /** @@ -571,13 +616,8 @@ public class ApplicationModules implements Iterable { Assert.notNull(key, "Cache key must not be null!"); var metadata = key.getMetadata(); - - var basePackages = new HashSet(); - basePackages.add(key.getBasePackage()); - basePackages.addAll(metadata.getAdditionalPackages()); - - var modules = new ApplicationModules(metadata, basePackages, key.getIgnored(), - metadata.useFullyQualifiedModuleNames(), IMPORT_OPTION); + var modules = new ApplicationModules(metadata, key.getIgnored(), + metadata.useFullyQualifiedModuleNames(), key.getOptions()); var sharedModules = metadata.getSharedModuleNames() // .map(modules::getRequiredModule) // @@ -623,59 +663,39 @@ public class ApplicationModules implements Iterable { } } - private static interface CacheKey { + private static class CacheKey { - String getBasePackage(); - - DescribedPredicate getIgnored(); - - ModulithMetadata getMetadata(); - } - - private static final class TypeKey implements CacheKey { - - private final Class type; private final DescribedPredicate ignored; + private final ImportOption options; + private final Supplier metadata; - /** - * Creates a new {@link TypeKey} for the given type and {@link DescribedPredicate} of ignored {@link JavaClass}es. - * - * @param type must not be {@literal null}. - * @param ignored must not be {@literal null}. - */ - TypeKey(Class type, DescribedPredicate ignored) { + public CacheKey(DescribedPredicate ignored, ImportOption options, Supplier metadata) { - this.type = type; this.ignored = ignored; + this.options = options; + this.metadata = SingletonSupplier.of(metadata); } - /* - * (non-Javadoc) - * @see org.springframework.modulith.model.Modules.CacheKey#getBasePackage() - */ - @Override - public String getBasePackage() { - return type.getPackage().getName(); + static CacheKey of(String pkg, DescribedPredicate ignored, ImportOption options) { + return new CacheKey(ignored, options, () -> ModulithMetadata.of(pkg)); } - /* - * (non-Javadoc) - * @see org.springframework.modulith.model.Modules.CacheKey#getMetadata() - */ - @Override - public ModulithMetadata getMetadata() { - return ModulithMetadata.of(type); + static CacheKey of(Class type, DescribedPredicate ignored, ImportOption options) { + return new CacheKey(ignored, options, () -> ModulithMetadata.of(type)); } - /* - * (non-Javadoc) - * @see org.springframework.modulith.model.ApplicationModules.CacheKey#getIgnored() - */ - @Override - public DescribedPredicate getIgnored() { + DescribedPredicate getIgnored() { return ignored; } + ModulithMetadata getMetadata() { + return metadata.get(); + } + + ImportOption getOptions() { + return options; + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) @@ -683,98 +703,17 @@ public class ApplicationModules implements Iterable { @Override public boolean equals(Object obj) { - if (this == obj) { + if (obj == this) { return true; } - if (!(obj instanceof TypeKey other)) { + if (!(obj instanceof CacheKey that)) { return false; } - return Objects.equals(this.type, other.type) // - && Objects.equals(this.ignored, other.ignored); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return Objects.hash(type, ignored); - } - } - - private static final class PackageKey implements CacheKey { - - private final String basePackage; - private final DescribedPredicate ignored; - - /** - * Creates a new {@link PackageKey} for the given base package and {@link DescribedPredicate} of ignored - * {@link JavaClass}es. - * - * @param basePackage must not be {@literal null}. - * @param ignored must not be {@literal null}. - */ - PackageKey(String basePackage, DescribedPredicate ignored) { - - this.basePackage = basePackage; - this.ignored = ignored; - } - - /* - * (non-Javadoc) - * @see org.springframework.modulith.model.ApplicationModules.CacheKey#getBasePackage() - */ - @Override - public String getBasePackage() { - return basePackage; - } - - /* - * (non-Javadoc) - * @see org.springframework.modulith.model.ApplicationModules.CacheKey#getIgnored() - */ - public DescribedPredicate getIgnored() { - return ignored; - } - - /* - * (non-Javadoc) - * @see org.springframework.modulith.model.Modules.CacheKey#getMetadata() - */ - @Override - public ModulithMetadata getMetadata() { - return ModulithMetadata.of(basePackage); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (this == obj) { - return true; - } - - if (!(obj instanceof PackageKey that)) { - return false; - } - - return Objects.equals(this.basePackage, that.basePackage) // - && Objects.equals(this.ignored, that.ignored); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return Objects.hash(basePackage, ignored); + return Objects.equals(this.ignored, that.ignored) + && Objects.equals(this.options, that.options) + && Objects.equals(this.metadata.get(), that.metadata.get()); } } diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java index faf52196..a0a1bffd 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ModulithMetadata.java @@ -25,6 +25,11 @@ import org.springframework.modulith.Modulithic; import org.springframework.modulith.core.Types.SpringTypes; import org.springframework.util.Assert; +/** + * Core metadata about the modulithic application. + * + * @author Oliver Drotbohm + */ public interface ModulithMetadata { static final String ANNOTATION_MISSING = "Modules can only be retrieved from a root type, but %s is not annotated with either @%s, @%s or @%s!"; @@ -81,7 +86,9 @@ public interface ModulithMetadata { * consider all direct sub-packages modules by default. * * @return will never be {@literal null}. + * @deprecated since 1.2, rather use {@link #getBasePackages()} that includes all packages already. */ + @Deprecated List getAdditionalPackages(); /** @@ -105,4 +112,12 @@ public interface ModulithMetadata { * @return will never be {@literal null}. */ Optional getSystemName(); + + /** + * Returns all base packages of the modulith. + * + * @return will never be {@literal null}. + * @since 1.2 + */ + List getBasePackages(); } diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java index e900f2bb..be365289 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/SpringBootModulithMetadata.java @@ -21,17 +21,14 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; -import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.lang.NonNull; -import org.springframework.modulith.Modulith; -import org.springframework.modulith.Modulithic; import org.springframework.modulith.core.Types.SpringTypes; import org.springframework.util.Assert; /** - * Creates a new {@link ModulithMetadata} representing the defaults of {@link Modulithic} but without the annotation - * present. + * Creates a new {@link ModulithMetadata} representing the defaults of {@link org.springframework.modulith.Modulithic} + * but without the annotation present. * * @author Oliver Drotbohm */ @@ -41,24 +38,29 @@ class SpringBootModulithMetadata implements ModulithMetadata { .loadIfPresent(SpringTypes.AT_SPRING_BOOT_APPLICATION); private final @NonNull Object source; - private final String systemName; + private final String systemName, basePackage; /** * Creates a new {@link SpringBootModulithMetadata} for the given source. * * @param source must not be {@literal null}. + * @param systemName can be {@literal null}. + * @param basePackage must not be {@literal null}. */ - private SpringBootModulithMetadata(Object source, String systemName) { + private SpringBootModulithMetadata(Object source, String systemName, String basePackage) { Assert.notNull(source, "Source must not be null!"); + Assert.notNull(basePackage, "Base package must not be null!"); this.source = source; + this.basePackage = basePackage; this.systemName = systemName; } /** * Creates a new {@link ModulithMetadata} representing the defaults of a class annotated with - * {@link SpringBootApplication} but not customized with {@link Modulithic} or {@link Modulith}. + * {@link org.springframework.boot.autoconfigure.SpringBootApplication} but not customized with + * {@link org.springframework.modulith.Modulithic} or {@link org.springframework.modulith.Modulith}. * * @param annotated must not be {@literal null}. * @return will never be {@literal null}. @@ -69,7 +71,7 @@ class SpringBootModulithMetadata implements ModulithMetadata { return Optional.ofNullable(AT_SPRING_BOOT_APPLICATION) // .filter(it -> AnnotatedElementUtils.hasAnnotation(annotated, it)) // - .map(__ -> new SpringBootModulithMetadata(annotated, annotated.getSimpleName())); + .map(__ -> new SpringBootModulithMetadata(annotated, annotated.getSimpleName(), annotated.getPackageName())); } /** @@ -82,7 +84,7 @@ class SpringBootModulithMetadata implements ModulithMetadata { Assert.hasText(javaPackage, "Package name must not be null or empty!"); - return new SpringBootModulithMetadata(javaPackage, null); + return new SpringBootModulithMetadata(javaPackage, null, javaPackage); } /* @@ -138,4 +140,13 @@ class SpringBootModulithMetadata implements ModulithMetadata { public Optional getSystemName() { return Optional.ofNullable(systemName); } + + /* + * (non-Javadoc) + * @see org.springframework.modulith.core.ModulithMetadata#getBasePackages() + */ + @Override + public List getBasePackages() { + return List.of(basePackage); + } } diff --git a/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModulithMetadataUnitTest.java b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModulithMetadataUnitTest.java index eba52196..6c12242c 100644 --- a/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModulithMetadataUnitTest.java +++ b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ModulithMetadataUnitTest.java @@ -38,7 +38,7 @@ class ModulithMetadataUnitTest { .map(ModulithMetadata::of) // .forEach(it -> { - assertThat(it.getAdditionalPackages()).containsExactly("com.acme.foo"); + assertThat(it.getBasePackages()).containsExactly("org.springframework.modulith.core", "com.acme.foo"); assertThat(it.getSharedModuleNames()).containsExactly("shared.module"); assertThat(it.getSystemName()).hasValue("systemName"); assertThat(it.useFullyQualifiedModuleNames()).isTrue(); @@ -50,7 +50,7 @@ class ModulithMetadataUnitTest { ModulithMetadata metadata = ModulithMetadata.of(SpringBootApplicationAnnotated.class); - assertThat(metadata.getAdditionalPackages()).isEmpty(); + assertThat(metadata.getBasePackages()).contains("org.springframework.modulith.core"); assertThat(metadata.getSharedModuleNames()).isEmpty(); assertThat(metadata.getSystemName()).hasValue(SpringBootApplicationAnnotated.class.getSimpleName()); assertThat(metadata.useFullyQualifiedModuleNames()).isFalse(); diff --git a/spring-modulith-core/src/test/java/org/springframework/modulith/core/TestUtils.java b/spring-modulith-core/src/test/java/org/springframework/modulith/core/TestUtils.java index 24c43477..f1e8ce83 100644 --- a/spring-modulith-core/src/test/java/org/springframework/modulith/core/TestUtils.java +++ b/spring-modulith-core/src/test/java/org/springframework/modulith/core/TestUtils.java @@ -17,7 +17,6 @@ package org.springframework.modulith.core; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*; -import java.util.List; import java.util.function.Supplier; import org.jmolecules.ddd.annotation.AggregateRoot; @@ -80,7 +79,7 @@ class TestUtils { } public static ApplicationModules of(String basePackage, String... ignoredPackages) { - return of(ModulithMetadata.of(basePackage), basePackage, JavaClass.Predicates.resideInAnyPackage(ignoredPackages)); + return of(ModulithMetadata.of(basePackage), JavaClass.Predicates.resideInAnyPackage(ignoredPackages)); } public static ApplicationModule getApplicationModule(String packageName) { @@ -99,9 +98,7 @@ class TestUtils { .importPackages(packageName)); } - private static ApplicationModules of(ModulithMetadata metadata, String basePackage, - DescribedPredicate ignores) { - return new ApplicationModules(metadata, List.of(basePackage), ignores, false, - new ImportOption.OnlyIncludeTests()) {}; + private static ApplicationModules of(ModulithMetadata metadata, DescribedPredicate ignores) { + return new ApplicationModules(metadata, ignores, false, new ImportOption.OnlyIncludeTests()) {}; } } diff --git a/spring-modulith-test/src/main/java/org/springframework/modulith/test/TestApplicationModules.java b/spring-modulith-test/src/main/java/org/springframework/modulith/test/TestApplicationModules.java index 07e3ed14..64137043 100644 --- a/spring-modulith-test/src/main/java/org/springframework/modulith/test/TestApplicationModules.java +++ b/spring-modulith-test/src/main/java/org/springframework/modulith/test/TestApplicationModules.java @@ -15,14 +15,9 @@ */ package org.springframework.modulith.test; -import java.util.ArrayList; -import java.util.List; - import org.springframework.modulith.core.ApplicationModules; import org.springframework.modulith.core.ApplicationModulesFactory; -import org.springframework.modulith.core.ModulithMetadata; -import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.importer.ImportOption; /** @@ -32,6 +27,8 @@ import com.tngtech.archunit.core.importer.ImportOption; */ public class TestApplicationModules { + private static ImportOption ONLY_TESTS = new ImportOption.OnlyIncludeTests(); + /** * Creates an {@link ApplicationModules} instance from the given package but only inspecting the test code. * @@ -39,7 +36,7 @@ public class TestApplicationModules { * @return will never be {@literal null}. */ public static ApplicationModules of(String basePackage) { - return of(ModulithMetadata.of(basePackage), basePackage); + return ApplicationModules.of(basePackage, ONLY_TESTS); } /** @@ -50,16 +47,7 @@ public class TestApplicationModules { * @since 1.2 */ public static ApplicationModules of(Class applicationClass) { - return of(ModulithMetadata.of(applicationClass), applicationClass.getPackageName()); - } - - private static ApplicationModules of(ModulithMetadata metadata, String basePackage) { - - var packages = new ArrayList<>(List.of(basePackage)); - packages.addAll(metadata.getAdditionalPackages()); - - return new ApplicationModules(metadata, packages, DescribedPredicate.alwaysFalse(), false, - new ImportOption.OnlyIncludeTests()) {}; + return ApplicationModules.of(applicationClass, ONLY_TESTS); } /**