Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclusion of ProjectGenerationConfiguration #1581

Open
zambrovski opened this issue Nov 7, 2024 · 4 comments · May be fixed by #1584
Open

Exclusion of ProjectGenerationConfiguration #1581

zambrovski opened this issue Nov 7, 2024 · 4 comments · May be fixed by #1584
Assignees
Labels
status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged

Comments

@zambrovski
Copy link
Contributor

zambrovski commented Nov 7, 2024

Hi folks,

we are using initializr to create larger enterprise-wide setup and are extending it in several directions.

One of the important things that we observed was the inability to exclude ProjectGenerationConfiguration from being processed. This is a little problematic, because once on the classpath, they are loaded via Spring SPI Loader. And since the initializr-spring module provides many useful things, we wanted to be able to disable configuration explicitly.

Do you think this is a good idea?

We implemented the following solution and would like to contribute it back, if you think it makes sense. In general, we managed to subclass the ProjectGenerator and overwrite the method getCandidateProjectGenerationConfigurations. In our implementation, we filter the detected candidates by comparing them with another list, beefore returning:

protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescription description) {
    var excludedNames = SpringFactoriesLoader.loadFactoryNames(
        ExcludedProjectGenerationConfiguration.class, getClass().getClassLoader()
    );
    log.trace("Found excluded configurations: {}", String.join(", ", excludedNames));
    return super.getCandidateProjectGenerationConfigurations(description)
        .stream()
        .filter(candidate -> !excludedNames.contains(candidate))
        .toList();
}

By doing so, I can specify in spring.factories:

io.spring.initializr.generator.project.ExcludedProjectGenerationConfiguration=\
  io.spring.initializr.generator.spring.code.java.JavaProjectGenerationConfiguration, \  
io.spring.initializr.generator.spring.configuration.ApplicationConfigurationProjectGenerationConfiguration

and exclude those two generation configurations, ExcludedProjectGenerationConfiguration being just a marker annotation and not effectively doing anything else.

In general, by providing this small change, we would benefit of throwing away a lot of code related to replacement of the ProjectGeneratorInvokerand the surronding ecosystem (because it is not very easy to replace the ProjectGenerator creation / invocation).

Are you interested?

We would love to contribute back.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 7, 2024
@mhalbritter mhalbritter self-assigned this Nov 8, 2024
@mhalbritter
Copy link
Contributor

Hey,

thanks for the idea. I think that can be useful. However, I don't like the proposed implementation. I'd think it would make sense to add a new interface:

/**
 * Allows to vote against the inclusion of a {@link ProjectGenerationConfiguration}.
 *
 * @author Moritz Halbritter
 */
public interface ProjectGenerationConfigurationVetoer {

	/**
	 * Whether the given configuration class should be excluded.
	 * @param configurationClass the configuration class
	 * @return whether the configuration class should be excluded
	 */
	boolean exclude(Class<?> configurationClass);

}

where implementations can be registered via spring.factories. This interface is used in the ProjectGenerator to exclude configurations:

	/**
	 * Return the {@link ProjectGenerationConfiguration} class names that should be
	 * considered. By default this method will load candidates using
	 * {@link SpringFactoriesLoader} with {@link ProjectGenerationConfiguration}.
	 * {@link ProjectGenerationConfigurationVetoer} loaded using
	 * {@link SpringFactoriesLoader} can be used to veto the loading of a
	 * {@link ProjectGenerationConfiguration}. If one vetoer vetoes againsg the
	 * configuration, the configuration is excluded.
	 * @param description the description of the project to generate
	 * @return a list of candidate configurations
	 */
	@SuppressWarnings("deprecation")
	protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescription description) {
		List<String> candidates = SpringFactoriesLoader.loadFactoryNames(ProjectGenerationConfiguration.class,
				getClass().getClassLoader());
		ProjectGenerationConfigurationVetoer vetoer = getProjectGenerationConfigurationVetoer();
		List<String> result = new ArrayList<>(candidates);
		for (String candidate : candidates) {
			Class<?> configurationClass = resolveClass(candidate);
			if (configurationClass != null) {
				if (vetoer.exclude(configurationClass)) {
					result.remove(candidate);
				}
			}
		}
		return result;
	}

I've prepared something in https://github.com/mhalbritter/initializr/tree/mh/1581-exclusion-of-projectgenerationconfiguration

Would that suit your needs?

@mhalbritter mhalbritter added the status: waiting-for-feedback We need additional information before we can continue label Nov 8, 2024
@zambrovski
Copy link
Contributor Author

Hi Moritz,

thank you for the response. I had a look on your proposal.

If I get it right, you prefer the type-based exclusion instead of solution proposed by me. By doing so, you exclude the configurations if there is at least one instance of ProjectGenerationConfigurationVetoer registered via spring.factories that answers true for configuration. So if I want to exclude a number of configurations, I would implement a ProjectGenerationConfigurationVetoer, register it and this would imperative (instead of declarative) rejects the configurations that I need to exclude.

This sounds like a good solution and solves my problem. I would like to exclude some "own" configurations and some of the configurations provided by the spring-initializer as default.

P.S. In this case you provided the solutions, but next time I would be happy to contribute code / PRs, if you agree on the feature.

P.P.S We are building a multi-module Maven Build setup (replacing the BuildSystem, introducing modules, and allowing generation of more elaborate setups). As I was replacing the ProjectGenerator and ProjectGeneratorInvoker I found some small inaccuracies and would just create a PR with few minor corrections. They are all non-critical but I'll still report it to you.

Thanks and cheers,

Simon

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Nov 8, 2024
@mhalbritter
Copy link
Contributor

Hey Simon,

you got that right.

However, i've done more thinking on the feature, and while I still like my "strategy interface" idea, I now lean towards implementing it differently. Instead of calling that thing "vetoer" which can veto the inclusion, i'd go for Filter (more matching org.springframework.boot.context.TypeExcludeFilter or org.springframework.core.type.filter.TypeFilter) and use a match method, which returns true if the configuration should be excluded (which would be the same as the behavior for org.springframework.context.annotation.ComponentScan#excludeFilters).

If you're up to it, you could implement that idea, feel free to use the ideas in my branch if you like.

and would just create a PR with few minor corrections.

Sure, go for it! Thanks!

@mhalbritter mhalbritter added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Nov 11, 2024
@zambrovski
Copy link
Contributor Author

Sure, I'm on it...

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Nov 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants