diff --git a/pom.xml b/pom.xml
index 7ec86c87a1..efa483af19 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-commons
- 3.0.0-SNAPSHOT
+ 3.0.0-dependencies-SNAPSHOT
Spring Data Core
Core Spring concepts underpinning every Spring Data module.
@@ -32,6 +32,7 @@
2.11.7
1.4.24
+ 1.0.0
spring.data.commons
@@ -334,13 +335,6 @@
true
-
- de.schauderhaft.degraph
- degraph-check
- 0.1.4
- test
-
-
org.jmolecules.integrations
jmolecules-spring
@@ -348,6 +342,13 @@
true
+
+ com.tngtech.archunit
+ archunit
+ ${archunit.version}
+ test
+
+
diff --git a/src/main/java/org/springframework/data/querydsl/QuerydslRepositoryRuntimeHints.java b/src/main/java/org/springframework/data/querydsl/QuerydslRepositoryRuntimeHints.java
new file mode 100644
index 0000000000..542298de6e
--- /dev/null
+++ b/src/main/java/org/springframework/data/querydsl/QuerydslRepositoryRuntimeHints.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.querydsl;
+
+import com.querydsl.core.types.Predicate;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+import org.springframework.aot.hint.TypeReference;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.data.domain.Example;
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
+import org.springframework.data.repository.core.support.RepositoryFragment;
+import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean;
+import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
+import org.springframework.data.repository.query.FluentQuery;
+import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
+import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+
+import java.util.Arrays;
+import java.util.Properties;
+
+/**
+ * {@link RuntimeHintsRegistrar} holding required hints to bootstrap Querydsl repositories.
+ * Already registered via {@literal aot.factories}.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 3.0
+ */
+class QuerydslRepositoryRuntimeHints implements RuntimeHintsRegistrar {
+
+ private static final boolean PROJECT_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Flux",
+ QuerydslRepositoryRuntimeHints.class.getClassLoader());
+
+ @Override
+ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
+
+ if (QuerydslUtils.QUERY_DSL_PRESENT) {
+
+ // repository infrastructure
+ hints.reflection().registerTypes(Arrays.asList( //
+ TypeReference.of(Predicate.class), //
+ TypeReference.of(QuerydslPredicateExecutor.class)), builder -> {
+ builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
+ });
+
+ if (PROJECT_REACTOR_PRESENT) {
+ // repository infrastructure
+ hints.reflection().registerTypes(Arrays.asList( //
+ TypeReference.of(ReactiveQuerydslPredicateExecutor.class)), builder -> {
+ builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
+ });
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/FallbackRepositoryAotConfigurationPostProcessor.java b/src/main/java/org/springframework/data/repository/aot/FallbackRepositoryAotConfigurationPostProcessor.java
new file mode 100644
index 0000000000..1c3fdac53a
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/FallbackRepositoryAotConfigurationPostProcessor.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.repository.aot;
+
+import org.springframework.data.repository.config.RepositoryConfigurationPostProcessor;
+
+/**
+ * default post processing to be applied when no other {@link RepositoryConfigurationPostProcessor} is applied.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 3.0
+ */
+class FallbackRepositoryAotConfigurationPostProcessor extends RepositoryRegistrationAotProcessor implements RepositoryConfigurationPostProcessor.FallbackRepositoryConfigurationPostProcessor {
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessor.java b/src/main/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessor.java
index 7a562e7705..9209c1517d 100644
--- a/src/main/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessor.java
+++ b/src/main/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessor.java
@@ -38,6 +38,7 @@
import org.springframework.data.aot.TypeContributor;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
+import org.springframework.data.repository.config.RepositoryConfigurationPostProcessor;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -56,16 +57,14 @@
* provide custom logic for contributing additional (eg. reflection) configuration. By default, reflection configuration
* will be added for types reachable from the repository declaration and query methods as well as all used
* {@link Annotation annotations} from the {@literal org.springframework.data} namespace.
- *
- * The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()} and
- * gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
+
*
* @author Christoph Strobl
* @author John Blum
* @since 3.0
*/
@SuppressWarnings("unused")
-public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
+public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware, RepositoryConfigurationPostProcessor {
private ConfigurableListableBeanFactory beanFactory;
@@ -117,6 +116,7 @@ protected ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
+ @Override
public void setConfigMap(@Nullable Map> configMap) {
this.configMap = configMap;
}
diff --git a/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java b/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java
index 568e02d7be..002b52f96f 100644
--- a/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java
+++ b/src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java
@@ -87,24 +87,6 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
});
}
- if (QuerydslUtils.QUERY_DSL_PRESENT) {
-
- // repository infrastructure
- hints.reflection().registerTypes(Arrays.asList( //
- TypeReference.of(Predicate.class), //
- TypeReference.of(QuerydslPredicateExecutor.class)), builder -> {
- builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
- });
-
- if (PROJECT_REACTOR_PRESENT) {
- // repository infrastructure
- hints.reflection().registerTypes(Arrays.asList( //
- TypeReference.of(ReactiveQuerydslPredicateExecutor.class)), builder -> {
- builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
- });
- }
- }
-
// named queries
hints.reflection().registerTypes(Arrays.asList( //
TypeReference.of(Properties.class), //
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
index e34474c694..965c2fd7ba 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java
@@ -46,6 +46,7 @@
import org.springframework.core.log.LogMessage;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
+import org.springframework.data.repository.config.RepositoryConfigurationPostProcessor.FallbackRepositoryConfigurationPostProcessor;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -83,11 +84,11 @@ public class RepositoryConfigurationDelegate {
* {@link ResourceLoader} and {@link Environment}.
*
* @param configurationSource must not be {@literal null}.
- * @param resourceLoader must not be {@literal null}.
- * @param environment must not be {@literal null}.
+ * @param resourceLoader must not be {@literal null}.
+ * @param environment must not be {@literal null}.
*/
public RepositoryConfigurationDelegate(RepositoryConfigurationSource configurationSource,
- ResourceLoader resourceLoader, Environment environment) {
+ ResourceLoader resourceLoader, Environment environment) {
this.isXml = configurationSource instanceof XmlRepositoryConfigurationSource;
boolean isAnnotation = configurationSource instanceof AnnotationRepositoryConfigurationSource;
@@ -106,13 +107,13 @@ public RepositoryConfigurationDelegate(RepositoryConfigurationSource configurati
* Defaults the environment in case the given one is null. Used as fallback, in case the legacy constructor was
* invoked.
*
- * @param environment can be {@literal null}.
+ * @param environment can be {@literal null}.
* @param resourceLoader can be {@literal null}.
* @return the given {@link Environment} if not {@literal null}, a configured {@link Environment}, or a default
- * {@link Environment}.
+ * {@link Environment}.
*/
private static Environment defaultEnvironment(@Nullable Environment environment,
- @Nullable ResourceLoader resourceLoader) {
+ @Nullable ResourceLoader resourceLoader) {
if (environment != null) {
return environment;
@@ -125,14 +126,14 @@ private static Environment defaultEnvironment(@Nullable Environment environment,
/**
* Registers the discovered repositories in the given {@link BeanDefinitionRegistry}.
*
- * @param registry {@link BeanDefinitionRegistry} in which to register the repository bean.
+ * @param registry {@link BeanDefinitionRegistry} in which to register the repository bean.
* @param extension {@link RepositoryConfigurationExtension} for the module.
* @return {@link BeanComponentDefinition}s for all repository bean definitions found.
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension
* @see org.springframework.beans.factory.support.BeanDefinitionRegistry
*/
public List registerRepositoriesIn(BeanDefinitionRegistry registry,
- RepositoryConfigurationExtension extension) {
+ RepositoryConfigurationExtension extension) {
if (logger.isInfoEnabled()) {
logger.info(LogMessage.format("Bootstrapping Spring Data %s repositories in %s mode.", //
@@ -210,29 +211,52 @@ public List registerRepositoriesIn(BeanDefinitionRegist
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
}
- // TODO: AOT Processing -> guard this one with a flag so it's not always present
- // TODO: With regard to AOT Processing, perhaps we need to be smart and detect whether "core" AOT components are
- // (or rather configuration is) present on the classpath to enable Spring Data AOT component registration.
- registerAotComponents(registry, extension, metadataByRepositoryBeanName);
+ registerPostProcessorComponents(registry, extension, metadataByRepositoryBeanName);
return definitions;
}
- private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
- Map> metadataByRepositoryBeanName) {
+ private void registerPostProcessorComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
+ Map> metadataByRepositoryBeanName) {
- // module-specific repository aot processor
- String repositoryAotProcessorBeanName = String.format("data-%s.repository-aot-processor" /* might be duplicate */,
- extension.getModuleIdentifier());
+ boolean hasPostProcessors = false;
+ List processors = SpringFactoriesLoader.loadFactories(RepositoryConfigurationPostProcessor.class, resourceLoader.getClassLoader());
+ for (RepositoryConfigurationPostProcessor processor : processors) {
- if (!registry.isBeanNameInUse(repositoryAotProcessorBeanName)) {
+ if (!processor.supports(extension)) {
+ continue;
+ }
+
+ hasPostProcessors = true;
+ registerRepositoryPostProcessor(registry, extension, processor.getClass().getName(), metadataByRepositoryBeanName);
+ }
+
+ if (!hasPostProcessors) {
- BeanDefinitionBuilder repositoryAotProcessor = BeanDefinitionBuilder
- .rootBeanDefinition(extension.getRepositoryAotProcessor()).setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ List fallback = SpringFactoriesLoader.loadFactories(FallbackRepositoryConfigurationPostProcessor.class, resourceLoader.getClassLoader());
- repositoryAotProcessor.addPropertyValue("configMap", metadataByRepositoryBeanName);
+ for (RepositoryConfigurationPostProcessor processor : fallback) {
- registry.registerBeanDefinition(repositoryAotProcessorBeanName, repositoryAotProcessor.getBeanDefinition());
+ if (!processor.supports(extension)) {
+ continue;
+ }
+
+ registerRepositoryPostProcessor(registry, extension, processor.getClass().getName(), metadataByRepositoryBeanName);
+ }
+ }
+ }
+
+ private static void registerRepositoryPostProcessor(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension, String className, Map> metadataByRepositoryBeanName) {
+
+ String repositoryProcessorBeanName = String.format("data-%s.repository-post-processor.%s",
+ extension.getModuleIdentifier(), className);
+
+ if (!registry.isBeanNameInUse(repositoryProcessorBeanName)) {
+ BeanDefinitionBuilder repositoryPostProcessor = BeanDefinitionBuilder
+ .rootBeanDefinition(className).setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+
+ repositoryPostProcessor.addPropertyValue("configMap", metadataByRepositoryBeanName);
+ registry.registerBeanDefinition(repositoryProcessorBeanName, repositoryPostProcessor.getBeanDefinition());
}
}
@@ -242,10 +266,10 @@ private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryCo
* augment the {@link LazyRepositoryInjectionPointResolver}'s configuration if there already is one configured.
*
* @param configurations must not be {@literal null}.
- * @param registry must not be {@literal null}.
+ * @param registry must not be {@literal null}.
*/
private static void potentiallyLazifyRepositories(Map> configurations,
- BeanDefinitionRegistry registry, BootstrapMode mode) {
+ BeanDefinitionRegistry registry, BootstrapMode mode) {
if (!DefaultListableBeanFactory.class.isInstance(registry) || BootstrapMode.DEFAULT.equals(mode)) {
return;
@@ -283,7 +307,7 @@ private static void potentiallyLazifyRepositories(Map> configurations) {
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java
index e7ab33fd63..99d124b20c 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtension.java
@@ -18,13 +18,10 @@
import java.util.Collection;
import java.util.Locale;
-import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.io.ResourceLoader;
-import org.springframework.data.repository.aot.RepositoryRegistrationAotProcessor;
-import org.springframework.lang.NonNull;
/**
* SPI to implement store specific extension to the repository bean definition registration process.
@@ -54,20 +51,6 @@ default String getModuleIdentifier() {
*/
String getModuleName();
- /**
- * Returns the {@link BeanRegistrationAotProcessor} type responsible for contributing AOT/native configuration
- * required by the Spring Data Repository infrastructure components at native runtime.
- *
- * @return the {@link BeanRegistrationAotProcessor} type responsible for contributing AOT/native configuration.
- * Defaults to {@link RepositoryRegistrationAotProcessor}. Must not be {@literal null}.
- * @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
- * @since 3.0
- */
- @NonNull
- default Class extends BeanRegistrationAotProcessor> getRepositoryAotProcessor() {
- return RepositoryRegistrationAotProcessor.class;
- }
-
/**
* Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}.
*
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationPostProcessor.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationPostProcessor.java
new file mode 100644
index 0000000000..82ac8027de
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationPostProcessor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.repository.config;
+
+import java.util.Map;
+
+/**
+ * Interface for components that post-process repository configuration of discovered repository interfaces.
+ *
+ * @author Jens Schauder
+ * @author Mark Paluch
+ * @since 3.0
+ */
+public interface RepositoryConfigurationPostProcessor {
+
+ /**
+ * Set the repository configuration map that contains repository bean names to {@link RepositoryConfiguration}.
+ *
+ * @param configMap
+ */
+ void setConfigMap(Map> configMap);
+
+
+ /**
+ * Method to test whether this configuration post-processor can be applied to the given {@link RepositoryConfigurationExtension}.
+ *
+ * @param extension
+ * @return {@code true} if applicable; {@code false} otherwise to skip processing.
+ */
+ default boolean supports(RepositoryConfigurationExtension extension) {
+ return true;
+ }
+
+ /**
+ * Marker interface for default fallback post-processors.
+ */
+ interface FallbackRepositoryConfigurationPostProcessor extends RepositoryConfigurationPostProcessor {
+ }
+
+}
diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories
index fc6274377f..8ab5772dd3 100644
--- a/src/main/resources/META-INF/spring.factories
+++ b/src/main/resources/META-INF/spring.factories
@@ -1,3 +1,4 @@
org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.web.config.SpringDataJacksonConfiguration
org.springframework.data.util.CustomCollectionRegistrar=org.springframework.data.util.CustomCollections.VavrCollections, \
org.springframework.data.util.CustomCollections.EclipseCollections
+org.springframework.data.repository.config.RepositoryConfigurationPostProcessor$FallbackRepositoryConfigurationPostProcessor=org.springframework.data.repository.aot.FallbackRepositoryAotConfigurationPostProcessor
diff --git a/src/main/resources/META-INF/spring/aot.factories b/src/main/resources/META-INF/spring/aot.factories
index bd2257e5e9..11efe4fbb0 100644
--- a/src/main/resources/META-INF/spring/aot.factories
+++ b/src/main/resources/META-INF/spring/aot.factories
@@ -2,7 +2,8 @@ org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.data.aot.ManagedTypesBeanFactoryInitializationAotProcessor
org.springframework.aot.hint.RuntimeHintsRegistrar=\
- org.springframework.data.repository.aot.hint.RepositoryRuntimeHints
+ org.springframework.data.repository.aot.hint.RepositoryRuntimeHints,\
+ org.springframework.data.querydsl.QuerydslRepositoryRuntimeHints
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.data.aot.AuditingBeanRegistrationAotProcessor
diff --git a/src/test/java/org/springframework/data/DependencyTests.java b/src/test/java/org/springframework/data/DependencyTests.java
index 8a3e7a5114..a5ddaae75c 100644
--- a/src/test/java/org/springframework/data/DependencyTests.java
+++ b/src/test/java/org/springframework/data/DependencyTests.java
@@ -15,29 +15,160 @@
*/
package org.springframework.data;
-import static de.schauderhaft.degraph.check.JCheck.*;
-import static org.junit.Assert.*;
-
-import org.junit.jupiter.api.Disabled;
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.core.importer.ImportOption;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.library.dependencies.SliceAssignment;
+import com.tngtech.archunit.library.dependencies.SliceIdentifier;
+import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
+import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test;
+import org.springframework.data.repository.core.RepositoryMetadata;
/**
+ * Tests for package and slice cycles.
+ *
+ * All packages that have the same start including the part after {@literal data} are considered one slice.
+ * For example {@literal org.springframework.data.repository} and {@literal org.springframework.data.repository.support}
+ * are part of the same slice {@literal repository}.
+ *
* @author Jens Schauder
*/
-@Disabled("Requires newer version of ASM 5.1")
public class DependencyTests {
+ JavaClasses importedClasses = new ClassFileImporter() //
+ .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) //
+ .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) // we just analyze the code of this module.
+ .importPackages("org.springframework.data") //
+ .that(onlySpringData()) //
+ .that(ignore(RepositoryMetadata.class)) // new cycle
+ ;
+
+ @Test
+ void cycleFreeSlices() {
+
+ ArchRule rule = SlicesRuleDefinition.slices() //
+ .matching("org.springframework.data.(*)..") //
+ .should() //
+ .beFreeOfCycles();
+
+ rule.check(importedClasses);
+ }
+
+ @Test
+ void cycleFreePackages() {
+
+ ArchRule rule = SlicesRuleDefinition.slices() //
+ .matching("org.springframework.data.(**)") //
+ .should() //
+ .beFreeOfCycles();
+
+ rule.check(importedClasses);
+ }
+
@Test
- public void noInternalPackageCycles() {
-
- assertThat(
- classpath() //
- .noJars() //
- .including("org.springframework.data.**") //
- .filterClasspath("*target/classes") //
- .printOnFailure("degraph.graphml"), //
- violationFree() //
- );
+ void testGetFirstPackagePart() {
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(getFirstPackagePart("a.b.c")).isEqualTo("a");
+ softly.assertThat(getFirstPackagePart("a")).isEqualTo("a");
+ });
+ }
+
+ @Test
+ void testSubModule() {
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(subModule("a.b", "a.b.c.d")).isEqualTo("c");
+ softly.assertThat(subModule("a.b", "a.b.c")).isEqualTo("c");
+ softly.assertThat(subModule("a.b", "a.b")).isEqualTo("");
+ });
+ }
+
+ private DescribedPredicate onlySpringData() {
+
+ return new DescribedPredicate<>("Spring Data Classes") {
+ @Override
+ public boolean test(JavaClass input) {
+ return input.getPackageName().startsWith("org.springframework.data");
+ }
+ };
+ }
+
+ private DescribedPredicate ignore(Class> type) {
+
+ return new DescribedPredicate<>("ignored class " + type.getName()) {
+ @Override
+ public boolean test(JavaClass input) {
+ return !input.getFullName().startsWith(type.getName());
+ }
+ };
+ }
+
+ private DescribedPredicate ignorePackage(String type) {
+
+ return new DescribedPredicate<>("ignored class " + type) {
+ @Override
+ public boolean test(JavaClass input) {
+ return !input.getPackageName().equals(type);
+ }
+ };
+ }
+
+ private String getFirstPackagePart(String subpackage) {
+
+ int index = subpackage.indexOf(".");
+ if (index < 0) {
+ return subpackage;
+ }
+ return subpackage.substring(0, index);
+ }
+
+ private String subModule(String basePackage, String packageName) {
+
+ if (packageName.startsWith(basePackage) && packageName.length() > basePackage.length()) {
+
+ final int index = basePackage.length() + 1;
+ String subpackage = packageName.substring(index);
+ return getFirstPackagePart(subpackage);
+ }
+ return "";
+ }
+
+ private SliceAssignment subModuleSlicing() {
+ return new SliceAssignment() {
+
+ @Override
+ public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
+
+ String packageName = javaClass.getPackageName();
+
+ String subModule = subModule("org.springframework.data.jdbc", packageName);
+ if (!subModule.isEmpty()) {
+ return SliceIdentifier.of(subModule);
+ }
+
+ subModule = subModule("org.springframework.data.relational", packageName);
+ if (!subModule.isEmpty()) {
+ return SliceIdentifier.of(subModule);
+ }
+
+ subModule = subModule("org.springframework.data", packageName);
+ if (!subModule.isEmpty()) {
+ return SliceIdentifier.of(subModule);
+ }
+
+ return SliceIdentifier.ignore();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Submodule";
+ }
+ };
}
}