Allow contributors to be filtered

This commit exposes an additional protected method in ProjectGenerator
that sub-classes can override to alter the list of
`@ProjectGenerationConfiguration`-annotated types that are used to load
candidate contributors.

Closes gh-870
This commit is contained in:
Stephane Nicoll
2020-11-24 14:47:20 +01:00
parent 68f5c04e1d
commit a10c830fd5
3 changed files with 102 additions and 32 deletions

View File

@@ -21,12 +21,9 @@ import java.util.List;
import java.util.function.Consumer;
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.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
/**
* Main entry point for project generation that processes a {@link ProjectDescription} by
@@ -104,8 +101,8 @@ public class ProjectGenerator {
public <T> T generate(ProjectDescription description, ProjectAssetGenerator<T> projectAssetGenerator)
throws ProjectGenerationException {
try (ProjectGenerationContext context = this.contextFactory.get()) {
context.registerBean(ProjectDescription.class, resolve(description, context));
context.register(CoreConfiguration.class);
registerProjectDescription(context, description);
registerProjectContributors(context, description);
this.contextConsumer.accept(context);
context.refresh();
try {
@@ -117,6 +114,30 @@ public class ProjectGenerator {
}
}
/**
* Return the {@link ProjectGenerationConfiguration} class names that should be
* considered. By default this method will load candidates using
* {@link SpringFactoriesLoader} with {@link ProjectGenerationConfiguration}.
* @param description the description of the project to generate
* @return a list of candidate configurations
*/
protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescription description) {
return SpringFactoriesLoader.loadFactoryNames(ProjectGenerationConfiguration.class,
getClass().getClassLoader());
}
private void registerProjectDescription(ProjectGenerationContext context, ProjectDescription description) {
context.registerBean(ProjectDescription.class, resolve(description, context));
}
private void registerProjectContributors(ProjectGenerationContext context, ProjectDescription description) {
getCandidateProjectGenerationConfigurations(description).forEach((configurationClassName) -> {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(configurationClassName);
context.registerBeanDefinition(configurationClassName, beanDefinition);
});
}
private Supplier<ProjectDescription> resolve(ProjectDescription description, ProjectGenerationContext context) {
return () -> {
if (description instanceof MutableProjectDescription) {
@@ -134,30 +155,4 @@ public class ProjectGenerator {
};
}
/**
* {@link Configuration} class that registers all available
* {@link ProjectGenerationConfiguration} classes.
*/
@Configuration
@Import(ProjectGenerationImportSelector.class)
static class CoreConfiguration {
}
/**
* {@link ImportSelector} for loading classes configured in {@code spring.factories}
* using the
* {@code io.spring.initializr.generator.project.ProjectGenerationConfiguration} key.
*/
static class ProjectGenerationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> factories = SpringFactoriesLoader.loadFactoryNames(ProjectGenerationConfiguration.class,
getClass().getClassLoader());
return factories.toArray(new String[0]);
}
}
}

View File

@@ -17,10 +17,16 @@
package io.spring.initializr.generator.project;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem;
import io.spring.initializr.generator.project.contributor.TestProjectGenerationConfiguration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InOrder;
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
@@ -166,6 +172,27 @@ public class ProjectGeneratorTests {
assertThat(candidates).containsOnly(entry("testBean", "duplicate"));
}
@Test
void generateCanBeExtendedToFilterProjectContributors(@TempDir Path projectDir) {
ProjectDescription description = mock(ProjectDescription.class);
given(description.getArtifactId()).willReturn("test-custom-contributor");
given(description.getBuildSystem()).willReturn(new MavenBuildSystem());
ProjectGenerator generator = new ProjectGenerator(mockContextInitializr()) {
@Override
protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescription description) {
assertThat(description).isSameAs(description);
return Collections.singletonList(TestProjectGenerationConfiguration.class.getName());
}
};
DefaultProjectAssetGenerator assetGenerator = new DefaultProjectAssetGenerator((desc) -> projectDir);
Path outputDir = generator.generate(description, assetGenerator);
Path expectedFile = outputDir.resolve("artifact-id.txt");
assertThat(expectedFile).isRegularFile();
assertThat(expectedFile).hasContent("test-custom-contributor");
verify(description).getArtifactId();
verify(description).getBuildSystem();
}
@SuppressWarnings("unchecked")
private Consumer<ProjectGenerationContext> mockContextInitializr() {
return mock(Consumer.class);

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 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 io.spring.initializr.generator.project.contributor;
import java.nio.file.Files;
import java.nio.file.Path;
import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem;
import io.spring.initializr.generator.condition.ConditionalOnBuildSystem;
import io.spring.initializr.generator.project.ProjectDescription;
import io.spring.initializr.generator.project.ProjectGenerationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.util.FileCopyUtils;
/**
* Test contributors.
*
* @author Stephane Nicoll
*/
@ProjectGenerationConfiguration
public class TestProjectGenerationConfiguration {
@Bean
@ConditionalOnBuildSystem(MavenBuildSystem.ID)
public ProjectContributor testArtifactIdContributor(ProjectDescription description) {
return (projectRoot) -> {
Path testFile = projectRoot.resolve("artifact-id.txt");
Files.createFile(testFile);
FileCopyUtils.copy(description.getArtifactId(), Files.newBufferedWriter(testFile));
};
}
}