Add project request post processing

This commit introduces `ProjectRequestPostProcessor` as a mean to modify
an incoming `ProjectRequest` before and after its resolution against the
meta-data.

By default, all beans of that type are invoked automatically and they
are ordered against their `@Order` or `Ordered` settings.

Closes gh-260
This commit is contained in:
Stephane Nicoll
2016-07-19 11:37:38 +02:00
parent fb7b8eb5cb
commit 56210d9bf2
9 changed files with 307 additions and 3 deletions

View File

@@ -55,6 +55,9 @@ class ProjectGenerator {
@Autowired
InitializrMetadataProvider metadataProvider
@Autowired
ProjectRequestResolver requestResolver
@Autowired
ProjectResourceLocator projectResourceLocator = new ProjectResourceLocator()
@@ -210,12 +213,12 @@ class ProjectGenerator {
* @param request the request to handle
* @return a model for that request
*/
protected Map resolveModel(ProjectRequest request) {
Assert.notNull request.bootVersion, 'boot version must not be null'
protected Map resolveModel(ProjectRequest originalRequest) {
Assert.notNull originalRequest.bootVersion, 'boot version must not be null'
def model = [:]
def metadata = metadataProvider.get()
request.resolve(metadata)
ProjectRequest request = requestResolver.resolve(originalRequest, metadata)
// request resolved so we can log what has been requested
def dependencies = request.resolvedDependencies

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2016 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
*
* http://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
import io.spring.initializr.metadata.InitializrMetadata
/**
* Project generation hook that allows for custom modification of {@link ProjectRequest}
* instances, e.g. adding custom dependencies or forcing certain settings based on custom
* logic.
*
* @author Stephane Nicoll
*/
interface ProjectRequestPostProcessor {
/**
* Apply this post processor to the given {@code ProjectRequest} <i>before</i> it
* gets resolved against the specified {@code InitializrMetadata}.
* <p>Consider using this hook to customize basic settings of the {@code request};
* for more advanced logic (in particular with regards to dependencies), consider
* using {@code postProcessAfterResolution}.
* @param request an unresolved {@link ProjectRequest}
* @param metadata the metadata to use to resolve this request
* @see ProjectRequest#resolve(InitializrMetadata)
*/
void postProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata);
/**
* Apply this post processor to the given {@code ProjectRequest} <i>after</i> it has
* been resolved against the specified {@code InitializrMetadata}.
* <p>Dependencies, repositories, bills of materials, default properties and others
* aspects of the request will have been resolved prior to invocation. In particular,
* note that no further validation checks will be performed.
* @param request an resolved {@code ProjectRequest}
* @param metadata the metadata that were used to resolve this request
*/
void postProcessAfterResolution(ProjectRequest request, InitializrMetadata metadata);
}

View File

@@ -0,0 +1,21 @@
package io.spring.initializr.generator
import io.spring.initializr.metadata.InitializrMetadata
/**
* An implementation of {@link ProjectRequestPostProcessor} with empty methods allowing
* sub-classes to override only the methods they're interested in.
*
* @author Stephane Nicoll
*/
class ProjectRequestPostProcessorAdapter implements ProjectRequestPostProcessor {
@Override
void postProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata) {
}
@Override
void postProcessAfterResolution(ProjectRequest request, InitializrMetadata metadata) {
}
}

View File

@@ -0,0 +1,40 @@
package io.spring.initializr.generator
import io.spring.initializr.metadata.InitializrMetadata
import org.springframework.util.Assert
/**
* Resolve {@link ProjectRequest} instances, honoring callback hook points.
*
* @author Stephane Nicoll
*/
class ProjectRequestResolver {
private final List<ProjectRequestPostProcessor> postProcessors
ProjectRequestResolver(List<ProjectRequestPostProcessor> postProcessors) {
this.postProcessors = new ArrayList<>(postProcessors)
}
ProjectRequest resolve(ProjectRequest request, InitializrMetadata metadata) {
Assert.notNull(request, "Request must not be null")
applyPostProcessBeforeResolution(request, metadata)
request.resolve(metadata)
applyPostProcessAfterResolution(request, metadata)
request
}
private void applyPostProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata) {
for (ProjectRequestPostProcessor processor : postProcessors) {
processor.postProcessBeforeResolution(request, metadata)
}
}
private void applyPostProcessAfterResolution(ProjectRequest request, InitializrMetadata metadata) {
for (ProjectRequestPostProcessor processor : postProcessors) {
processor.postProcessAfterResolution(request, metadata)
}
}
}

View File

@@ -57,6 +57,7 @@ abstract class AbstractProjectGeneratorTests {
.addDependencyGroup('test', 'security', 'data-jpa', 'aop', 'batch', 'integration').build()
applyMetadata(metadata)
projectGenerator.eventPublisher = eventPublisher
projectGenerator.requestResolver = new ProjectRequestResolver([])
projectGenerator.tmpdir = folder.newFolder().absolutePath
}

View File

@@ -18,6 +18,7 @@ package io.spring.initializr.generator
import io.spring.initializr.metadata.BillOfMaterials
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import org.junit.Rule
import org.junit.Test
@@ -642,6 +643,34 @@ class ProjectGeneratorTests extends AbstractProjectGeneratorTests {
.doesNotContain('ignore.property')
}
@Test
void versionRangeWithPostProcessor() {
Dependency foo = new Dependency(id: 'foo', groupId: 'org.acme', artifactId: 'foo')
foo.mappings << new Dependency.Mapping(versionRange: '[1.2.0.RELEASE,1.3.0.M1)', version: '1.0.0')
foo.mappings << new Dependency.Mapping(versionRange: '1.3.0.M1', version: '1.2.0')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('foo', foo).build()
applyMetadata(metadata)
// First without processor, get the correct version
def request = createProjectRequest('foo')
request.bootVersion = '1.2.5.RELEASE'
generateMavenPom(request).hasDependency(
new Dependency(id: 'foo', groupId: 'org.acme', artifactId: 'foo', version: '1.0.0'))
// First after processor that flips Spring Boot version
projectGenerator.requestResolver = new ProjectRequestResolver(Collections.singletonList(
new ProjectRequestPostProcessorAdapter() {
@Override
void postProcessBeforeResolution(ProjectRequest r, InitializrMetadata m) {
r.bootVersion = '1.3.0.M2'
}
}
))
generateMavenPom(request).hasDependency(
new Dependency(id: 'foo', groupId: 'org.acme', artifactId: 'foo', version: '1.2.0'))
}
@Test
void invalidProjectTypeMavenPom() {
def request = createProjectRequest('web')

View File

@@ -0,0 +1,86 @@
package io.spring.initializr.generator
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertEquals
/**
* Tests for {@link ProjectRequestResolver}.
*
* @author Stephane Nicoll
*/
class ProjectRequestResolverTests {
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('test', 'web', 'security', 'data-jpa')
.build()
final List<ProjectRequestPostProcessor> postProcessors = []
final GenericProjectRequestPostProcessor processor = new GenericProjectRequestPostProcessor()
@Before
void setup() {
this.postProcessors << processor
}
@Test
void beforeResolution() {
processor.before['javaVersion'] = '1.2'
ProjectRequest request = resolve(createMavenProjectRequest(), postProcessors)
assertEquals '1.2', request.javaVersion
assertEquals '1.2', request.buildProperties.versions['java.version'].call()
}
@Test
void afterResolution() {
postProcessors << new ProjectRequestPostProcessorAdapter() {
@Override
void postProcessAfterResolution(ProjectRequest request, InitializrMetadata metadata) {
request.buildProperties.maven.clear()
request.buildProperties.maven['foo'] = { 'bar' }
}
}
ProjectRequest request = resolve(createMavenProjectRequest(), postProcessors)
assertEquals 1, request.buildProperties.maven.size()
assertEquals 'bar', request.buildProperties.maven['foo'].call()
}
ProjectRequest resolve(def request, def processors) {
new ProjectRequestResolver(processors)
.resolve(request, metadata)
}
ProjectRequest createMavenProjectRequest(String... styles) {
def request = createProjectRequest(styles)
request.type = 'maven-project'
request
}
ProjectRequest createProjectRequest(String... styles) {
def request = new ProjectRequest()
request.initialize(metadata)
request.style.addAll Arrays.asList(styles)
request
}
static class GenericProjectRequestPostProcessor implements ProjectRequestPostProcessor {
final Map<String, Object> before = [:]
final Map<String, Object> after = [:]
@Override
void postProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata) {
before.forEach { k, v -> request.setProperty(k, v) }
}
@Override
void postProcessAfterResolution(ProjectRequest request, InitializrMetadata metadata) {
after.forEach { k, v -> request.setProperty(k, v) }
}
}
}