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) }
}
}
}

View File

@ -20,6 +20,8 @@ import java.util.concurrent.TimeUnit
import com.google.common.cache.CacheBuilder
import io.spring.initializr.generator.ProjectGenerator
import io.spring.initializr.generator.ProjectRequestPostProcessor
import io.spring.initializr.generator.ProjectRequestResolver
import io.spring.initializr.generator.ProjectResourceLocator
import io.spring.initializr.metadata.DependencyMetadataProvider
import io.spring.initializr.metadata.InitializrMetadataBuilder
@ -30,6 +32,7 @@ import io.spring.initializr.web.support.DefaultDependencyMetadataProvider
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider
import io.spring.initializr.web.ui.UiController
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.CacheManager
@ -56,6 +59,9 @@ import org.springframework.context.annotation.Configuration
@EnableConfigurationProperties(InitializrProperties)
class InitializrAutoConfiguration {
@Autowired(required = false)
List<ProjectRequestPostProcessor> postProcessors = []
@Bean
WebConfig webConfig() {
new WebConfig()
@ -79,6 +85,12 @@ class InitializrAutoConfiguration {
new ProjectGenerator()
}
@Bean
@ConditionalOnMissingBean
ProjectRequestResolver projectRequestResolver() {
new ProjectRequestResolver(postProcessors)
}
@Bean
ProjectResourceLocator projectResourceLocator() {
return new ProjectResourceLocator()

View File

@ -0,0 +1,59 @@
package io.spring.initializr.web.project
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.generator.ProjectRequestPostProcessor
import io.spring.initializr.generator.ProjectRequestPostProcessorAdapter
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests
import org.junit.Test
import org.springframework.boot.test.SpringApplicationConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.test.context.ActiveProfiles
@ActiveProfiles('test-default')
@SpringApplicationConfiguration(classes = [Config, ProjectRequestPostProcessorConfiguration])
class ProjectGenerationPostProcessorTests extends AbstractInitializrControllerIntegrationTests {
@Test
void postProcessorsInvoked() {
downloadZip('/starter.zip?bootVersion=1.2.4.RELEASE&javaVersion=1.6')
.isJavaProject()
.isMavenProject().pomAssert()
.hasSpringBootParent('1.2.3.RELEASE')
.hasProperty('java.version', '1.7')
}
@Configuration
static class ProjectRequestPostProcessorConfiguration {
@Bean
@Order(2)
ProjectRequestPostProcessor secondPostProcessor() {
new ProjectRequestPostProcessorAdapter() {
@Override
void postProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata) {
request.javaVersion = '1.7'
}
}
}
@Bean
@Order(1)
ProjectRequestPostProcessor firstPostProcessor() {
new ProjectRequestPostProcessorAdapter() {
@Override
void postProcessBeforeResolution(ProjectRequest request, InitializrMetadata metadata) {
request.javaVersion = '1.2'
request.bootVersion = '1.2.3.RELEASE'
}
}
}
}
}