From c89b399956f6d2613c2674e0ee8e8ba9287f2f6c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 27 Sep 2019 12:31:11 +0200 Subject: [PATCH] Disable @ProjectGenerationContext bean overriding by default This commit extends ProjectGenerator so that the configuration of the ProjectGenerationContext can be externalized if necessary. By default, a context that does not allow bean overriding is provided. Closes gh-999 --- .../generator/project/ProjectGenerator.java | 63 ++++++++++++++++--- .../project/ProjectGeneratorTests.java | 26 ++++++++ 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java index c8a1c9f5..ba860e8a 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java @@ -24,6 +24,7 @@ import java.util.function.Supplier; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.type.AnnotationMetadata; @@ -35,20 +36,62 @@ import org.springframework.core.type.AnnotationMetadata; */ public class ProjectGenerator { - private final Consumer projectGenerationContext; + private final Consumer contextConsumer; + + private final Supplier contextFactory; /** - * Create an instance with a customizer for the project generator application context. - * @param projectGenerationContext a consumer of the project generation context before - * it is refreshed. + * Create an instance with a customizer for the project generator application context + * and a factory for the {@link ProjectGenerationContext}. + * @param contextConsumer a consumer of the project generation context after + * contributors and the {@link ProjectDescription} have been registered but before it + * is refreshed + * @param contextFactory the factory to use to create {@link ProjectGenerationContext} + * instances */ - public ProjectGenerator(Consumer projectGenerationContext) { - this.projectGenerationContext = projectGenerationContext; + public ProjectGenerator(Consumer contextConsumer, + Supplier contextFactory) { + this.contextConsumer = contextConsumer; + this.contextFactory = contextFactory; } /** - * Generate project assets using the specified {@link ProjectAssetGenerator}. Invokes - * known {@link ProjectDescriptionCustomizer} if applicable prior to asset generation. + * Create an instance with a customizer for the {@link ProjectGenerationContext} and a + * default factory for the {@link ProjectGenerationContext} that disables bean + * definition overriding. + * @param contextConsumer a consumer of the project generation context after + * contributors and the {@link ProjectDescription} have been registered but before it + * is refreshed + * @see GenericApplicationContext#setAllowBeanDefinitionOverriding(boolean) + */ + public ProjectGenerator(Consumer contextConsumer) { + this(contextConsumer, defaultContextFactory()); + } + + private static Supplier defaultContextFactory() { + return () -> { + ProjectGenerationContext context = new ProjectGenerationContext(); + context.setAllowBeanDefinitionOverriding(false); + return context; + }; + } + + /** + * Generate project assets using the specified {@link ProjectAssetGenerator} for the + * specified {@link ProjectDescription}. + *

+ * Create a dedicated {@link ProjectGenerationContext} with the following + * characteristics: + *

    + *
  • {@linkplain ProjectGenerationContext#setAllowBeanDefinitionOverriding(boolean) + * Bean overriding} disabled by default.
  • + *
  • Register a {@link ProjectDescription} bean based on the given + * {@code description} post-processed by available + * {@link ProjectDescriptionCustomizer} beans.
  • + *
  • Process all registered {@link ProjectGenerationConfiguration} classes.
  • + *
+ * Before the context is refreshed, it can be further customized using the customizer + * registered {@linkplain #ProjectGenerator(Consumer) on this instance}. * @param description the description of the project to generate * @param projectAssetGenerator the {@link ProjectAssetGenerator} to invoke * @param the type that gathers the project assets @@ -57,10 +100,10 @@ public class ProjectGenerator { */ public T generate(ProjectDescription description, ProjectAssetGenerator projectAssetGenerator) throws ProjectGenerationException { - try (ProjectGenerationContext context = new ProjectGenerationContext()) { + try (ProjectGenerationContext context = this.contextFactory.get()) { context.registerBean(ProjectDescription.class, resolve(description, context)); context.register(CoreConfiguration.class); - this.projectGenerationContext.accept(context); + this.contextConsumer.accept(context); context.refresh(); try { return projectAssetGenerator.generate(context); diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java index 7b8a3e8c..e6e8f16f 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java @@ -17,13 +17,17 @@ package io.spring.initializr.generator.project; import java.io.IOException; +import java.util.Map; import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.mockito.InOrder; +import org.springframework.beans.factory.support.BeanDefinitionOverrideException; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.inOrder; @@ -112,6 +116,28 @@ public class ProjectGeneratorTests { .isInstanceOf(ProjectGenerationException.class).hasCause(exception); } + @Test + void generateDoesNotAllowBeanDefinitionOverridingByDefault() { + ProjectGenerator generator = new ProjectGenerator((context) -> { + context.registerBean("testBean", String.class, () -> "test"); + context.registerBean("testBean", String.class, () -> "duplicate"); + }); + ProjectAssetGenerator assetGenerator = mock(ProjectAssetGenerator.class); + assertThatThrownBy(() -> generator.generate(new MutableProjectDescription(), assetGenerator)) + .isInstanceOf(BeanDefinitionOverrideException.class).hasMessageContaining("testBean"); + } + + @Test + void generateCanBeConfiguredToAllowBeanDefinitionOverriding() { + ProjectGenerator generator = new ProjectGenerator((context) -> { + context.registerBean("testBean", String.class, () -> "test"); + context.registerBean("testBean", String.class, () -> "duplicate"); + }, ProjectGenerationContext::new); + Map candidates = generator.generate(new MutableProjectDescription(), + (context) -> context.getBeansOfType(String.class)); + assertThat(candidates).containsOnly(entry("testBean", "duplicate")); + } + @SuppressWarnings("unchecked") private Consumer mockContextInitializr() { return mock(Consumer.class);