From 595a0527029915ae0d2be932d1b50c6e9dc4d077 Mon Sep 17 00:00:00 2001 From: Michael Weirauch Date: Mon, 6 Mar 2023 23:38:22 +0100 Subject: [PATCH] GH-160 - Prevent missing ArchUnit runtime dependency. Adds ArchUnit as an explicit dependency to spring-modulith-runtime. Registers auto-configuration that throws an exception in case ArchUnit is missing from the classpath (as it is likely that it's declared as test scope dependency only) and a corresponding failure analyzer to give guidance on how to solve the problem. --- spring-modulith-runtime/pom.xml | 6 +++ .../MissingRuntimeDependency.java | 37 +++++++++++++++++++ ...ssingRuntimeDependencyFailureAnalyzer.java | 36 ++++++++++++++++++ ...pringModulithRuntimeAutoConfiguration.java | 11 ++++++ .../main/resources/META-INF/spring.factories | 3 ++ ...timeAutoConfigurationIntegrationTests.java | 23 ++++++++++++ 6 files changed, 116 insertions(+) create mode 100644 spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependency.java create mode 100644 spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java create mode 100644 spring-modulith-runtime/src/main/resources/META-INF/spring.factories diff --git a/spring-modulith-runtime/pom.xml b/spring-modulith-runtime/pom.xml index 691ad396..0c8acfa5 100644 --- a/spring-modulith-runtime/pom.xml +++ b/spring-modulith-runtime/pom.xml @@ -22,6 +22,12 @@ ${project.version} + + com.tngtech.archunit + archunit + ${archunit.version} + + org.jgrapht jgrapht-core diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependency.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependency.java new file mode 100644 index 00000000..a6d612cd --- /dev/null +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependency.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 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.modulith.runtime.autoconfigure; + +/** + * An Exception carrying information about a missing runtime dependency to be + * analyzed by {@link MissingRuntimeDependencyFailureAnalyzer}. + * + * @author Michael Weirauch + */ +class MissingRuntimeDependencyException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final String dependencyName; + + public MissingRuntimeDependencyException(String dependencyName) { + this.dependencyName = dependencyName; + } + + public String getDependencyName() { + return dependencyName; + } +} diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java new file mode 100644 index 00000000..bbde4897 --- /dev/null +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/MissingRuntimeDependencyFailureAnalyzer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 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.modulith.runtime.autoconfigure; + +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.diagnostics.FailureAnalyzer; + +/** + * {@link FailureAnalyzer} for {@link MissingRuntimeDependencyException}. + * + * @author Michael Weirauch + */ +class MissingRuntimeDependencyFailureAnalyzer extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, MissingRuntimeDependencyException cause) { + return new FailureAnalysis( + String.format("Spring Modulith requires the dependency '%s' to be on the runtime classpath.", + cause.getDependencyName()), + "Add the missing dependency to the runtime classpath of your project.", cause); + } +} diff --git a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java index 10f64174..2ec1ebf9 100644 --- a/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java +++ b/spring-modulith-runtime/src/main/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -45,10 +46,20 @@ import org.springframework.util.Assert; * Bean. * * @author Oliver Drotbohm + * @author Michael Weirauch */ @AutoConfiguration class SpringModulithRuntimeAutoConfiguration { + @AutoConfiguration + @ConditionalOnMissingClass("com.tngtech.archunit.core.importer.ClassFileImporter") + static class ArchUnitRuntimeDependencyMissingConfiguration { + + ArchUnitRuntimeDependencyMissingConfiguration() { + throw new MissingRuntimeDependencyException("archunit"); + } + } + private static final Logger LOGGER = LoggerFactory.getLogger(SpringModulithRuntimeAutoConfiguration.class); private final AsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); diff --git a/spring-modulith-runtime/src/main/resources/META-INF/spring.factories b/spring-modulith-runtime/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..b62c8e88 --- /dev/null +++ b/spring-modulith-runtime/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Failure analyzers +org.springframework.boot.diagnostics.FailureAnalyzer=\ +org.springframework.modulith.runtime.autoconfigure.MissingRuntimeDependencyFailureAnalyzer diff --git a/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java b/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java index e1b3537a..02e7b40e 100644 --- a/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java +++ b/spring-modulith-runtime/src/test/java/org/springframework/modulith/runtime/autoconfigure/SpringModulithRuntimeAutoConfigurationIntegrationTests.java @@ -18,16 +18,21 @@ package org.springframework.modulith.runtime.autoconfigure; import static org.assertj.core.api.Assertions.*; import org.junit.jupiter.api.Test; +import org.springframework.beans.BeanInstantiationException; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.modulith.runtime.ApplicationModulesRuntime; import org.springframework.modulith.runtime.ApplicationRuntime; +import com.tngtech.archunit.core.importer.ClassFileImporter; + /** * Integration test for {@link SpringModulithRuntimeAutoConfiguration}. * * @author Oliver Drotbohm + * @author Michael Weirauch */ class SpringModulithRuntimeAutoConfigurationIntegrationTests { @@ -48,4 +53,22 @@ class SpringModulithRuntimeAutoConfigurationIntegrationTests { context.close(); }); } + + @Test + void missingArchUnitRuntimeDependencyEscalatesOnContextStartup() { + + new ApplicationContextRunner() + .withUserConfiguration(SampleApp.class) + .withConfiguration(AutoConfigurations.of(SpringModulithRuntimeAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(ClassFileImporter.class)) + .run(context -> { + + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure().getCause()) + .isInstanceOf(BeanInstantiationException.class) + .cause().isInstanceOf(MissingRuntimeDependencyException.class); + + context.close(); + }); + } }