Fire event if a project could not be generated

Previously, only an invalid type or an invalid dependency would lead to
an exception and in such case, no event is fired at all.

This commit adds validation for language and packaging as well as a new
event that is fired when the project could not be generated.

The metrics infrastructure has been updated to handle ProjectFailedEvent;
when such an event is fired, the 'failures' counter is increased and we
still record all the other metrics.

Closes gh-188
This commit is contained in:
Stephane Nicoll 2016-02-01 15:03:03 +01:00
parent 3f1b4eca13
commit f165405b26
8 changed files with 237 additions and 49 deletions

View File

@ -0,0 +1,37 @@
/*
* 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
/**
* Event published when an error occured trying to generate a project.
*
* @author Stephane Nicoll
* @since 1.0
*/
class ProjectFailedEvent extends ProjectRequestEvent {
/**
* The cause of the failure.
*/
final Exception cause
ProjectFailedEvent(ProjectRequest projectRequest, Exception cause) {
super(projectRequest)
this.cause = cause
}
}

View File

@ -17,26 +17,15 @@
package io.spring.initializr.generator
/**
* Event fired when a new project has been generated successfully.
* Event published when a new project has been generated successfully.
*
* @author Stephane Nicoll
* @since 1.0
*/
class ProjectGeneratedEvent {
/**
* The {@link ProjectRequest} used to generate the project.
*/
final ProjectRequest projectRequest
/**
* The timestamp at which the project was generated
*/
final long timestamp
class ProjectGeneratedEvent extends ProjectRequestEvent {
ProjectGeneratedEvent(ProjectRequest projectRequest) {
this.projectRequest = projectRequest
this.timestamp = System.currentTimeMillis()
super(projectRequest)
}
}

View File

@ -39,7 +39,16 @@ class ProjectGenerationMetricsListener {
@EventListener
void onGeneratedProject(ProjectGeneratedEvent event) {
def request = event.projectRequest
handleProjectRequest(event.projectRequest)
}
@EventListener
void onFailedProject(ProjectFailedEvent event) {
handleProjectRequest(event.projectRequest)
increment(key('failures'))
}
protected void handleProjectRequest(ProjectRequest request) {
increment(key('requests')) // Total number of requests
handleDependencies(request)
handleType(request)

View File

@ -17,6 +17,7 @@
package io.spring.initializr.generator
import groovy.util.logging.Slf4j
import io.spring.initializr.InitializrException
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.Version
@ -60,20 +61,30 @@ class ProjectGenerator {
* Generate a Maven pom for the specified {@link ProjectRequest}.
*/
byte[] generateMavenPom(ProjectRequest request) {
def model = initializeModel(request)
def content = doGenerateMavenPom(model)
publishProjectGeneratedEvent(request)
content
try {
def model = initializeModel(request)
def content = doGenerateMavenPom(model)
publishProjectGeneratedEvent(request)
content
} catch (InitializrException ex) {
publishProjectFailedEvent(request, ex)
throw ex
}
}
/**
* Generate a Gradle build file for the specified {@link ProjectRequest}.
*/
byte[] generateGradleBuild(ProjectRequest request) {
def model = initializeModel(request)
def content = doGenerateGradleBuild(model)
publishProjectGeneratedEvent(request)
content
try {
def model = initializeModel(request)
def content = doGenerateGradleBuild(model)
publishProjectGeneratedEvent(request)
content
} catch (InitializrException ex) {
publishProjectFailedEvent(request, ex)
throw ex
}
}
/**
@ -81,6 +92,15 @@ class ProjectGenerator {
* a directory containing the project.
*/
File generateProjectStructure(ProjectRequest request) {
try {
doGenerateProjectStructure(request)
} catch (InitializrException ex) {
publishProjectFailedEvent(request, ex)
throw ex
}
}
protected File doGenerateProjectStructure(ProjectRequest request) {
def model = initializeModel(request)
def rootDir = File.createTempFile('tmp', '', new File(tmpdir))
@ -171,6 +191,11 @@ class ProjectGenerator {
eventPublisher.publishEvent(event)
}
private void publishProjectFailedEvent(ProjectRequest request, Exception cause) {
ProjectFailedEvent event = new ProjectFailedEvent(request, cause)
eventPublisher.publishEvent(event)
}
protected Map initializeModel(ProjectRequest request) {
Assert.notNull request.bootVersion, 'boot version must not be null'
def model = [:]

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* 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.
@ -137,6 +137,18 @@ class ProjectRequest {
this.build = buildTag
}
}
if (this.packaging) {
def packaging = metadata.packagings.get(this.packaging)
if (!packaging) {
throw new InvalidProjectRequestException("Unknown packaging '${this.packaging}' check project metadata")
}
}
if (this.language) {
def language = metadata.languages.get(this.language)
if (!language) {
throw new InvalidProjectRequestException("Unknown language '${this.language}' check project metadata")
}
}
if (!applicationName) {
this.applicationName = metadata.configuration.generateApplicationName(this.name)

View File

@ -0,0 +1,44 @@
/*
* 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
/**
* Event published when a {@link ProjectRequest} has been processed.
*
* @author Stephane Nicoll
* @since 1.0
* @see ProjectGeneratedEvent
* @see ProjectFailedEvent
*/
abstract class ProjectRequestEvent {
/**
* The {@link ProjectRequest} used to generate the project.
*/
final ProjectRequest projectRequest
/**
* The timestamp at which the request was processed.
*/
final long timestamp
protected ProjectRequestEvent(ProjectRequest projectRequest) {
this.projectRequest = projectRequest
this.timestamp = System.currentTimeMillis()
}
}

View File

@ -47,16 +47,25 @@ class ProjectGenerationMetricsListenerTests {
void projectGenerationCount() {
def request = initialize()
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests')
}
@Test
void projectGenerationCountWithFailure() {
def request = initialize()
request.resolve(metadata)
fireProjectFailedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests')
metricsAssert.hasValue(1, 'initializr.failures')
}
@Test
void dependencies() {
def request = initialize()
request.style << 'security' << 'spring-data'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.dependency.security',
'initializr.dependency.spring-data')
}
@ -65,7 +74,7 @@ class ProjectGenerationMetricsListenerTests {
void noDependencies() {
def request = initialize()
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasNoValue('initializr.dependency.')
}
@ -75,7 +84,7 @@ class ProjectGenerationMetricsListenerTests {
request.style << 'spring-data'
request.packaging = 'war'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.dependency.web',
'initializr.dependency.spring-data')
}
@ -91,7 +100,7 @@ class ProjectGenerationMetricsListenerTests {
request.initialize(metadata)
request.style << 'foo-old'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.dependency.foo') // standard id is used
}
@ -99,7 +108,7 @@ class ProjectGenerationMetricsListenerTests {
void defaultType() {
def request = initialize()
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.type.maven-project')
}
@ -108,7 +117,7 @@ class ProjectGenerationMetricsListenerTests {
def request = initialize()
request.type = 'gradle-build'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.type.gradle-build')
}
@ -116,7 +125,7 @@ class ProjectGenerationMetricsListenerTests {
void defaultPackaging() {
def request = initialize()
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.packaging.jar')
}
@ -125,7 +134,7 @@ class ProjectGenerationMetricsListenerTests {
def request = initialize()
request.packaging = 'war'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.packaging.war')
}
@ -133,7 +142,7 @@ class ProjectGenerationMetricsListenerTests {
void defaultJavaVersion() {
def request = initialize()
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.java_version.1_8')
}
@ -142,7 +151,7 @@ class ProjectGenerationMetricsListenerTests {
def request = initialize()
request.javaVersion = '1.7'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.java_version.1_7')
}
@ -150,7 +159,7 @@ class ProjectGenerationMetricsListenerTests {
void defaultLanguage() {
def request = initialize()
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.language.java')
}
@ -159,7 +168,7 @@ class ProjectGenerationMetricsListenerTests {
def request = initialize()
request.language = 'groovy'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.language.groovy')
}
@ -167,7 +176,7 @@ class ProjectGenerationMetricsListenerTests {
void defaultBootVersion() {
def request = initialize()
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.boot_version.1_2_3_RELEASE')
}
@ -176,7 +185,7 @@ class ProjectGenerationMetricsListenerTests {
def request = initialize()
request.bootVersion = '1.0.2.RELEASE'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.boot_version.1_0_2_RELEASE')
}
@ -191,7 +200,7 @@ class ProjectGenerationMetricsListenerTests {
request.bootVersion = '1.0.2.RELEASE'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests',
'initializr.dependency.web', 'initializr.dependency.security',
'initializr.type.gradle-project', 'initializr.packaging.jar',
@ -204,24 +213,28 @@ class ProjectGenerationMetricsListenerTests {
def request = initialize()
request.style << 'security' << 'spring-data'
request.resolve(metadata)
fireEvent(request)
fireProjectGeneratedEvent(request)
metricsAssert.hasValue(1, 'initializr.requests',
'initializr.dependency.security', 'initializr.dependency.spring-data')
def anotherRequest = initialize()
anotherRequest.style << 'web' << 'spring-data'
anotherRequest.resolve(metadata)
fireEvent(anotherRequest)
fireProjectGeneratedEvent(anotherRequest)
metricsAssert.hasValue(2, 'initializr.dependency.spring-data',
'initializr.dependency.spring-data')
metricsAssert.hasValue(1, 'initializr.dependency.web',
'initializr.dependency.security')
}
private fireEvent(ProjectRequest projectRequest) {
private fireProjectGeneratedEvent(ProjectRequest projectRequest) {
listener.onGeneratedProject(new ProjectGeneratedEvent(projectRequest))
}
private fireProjectFailedEvent(ProjectRequest projectRequest) {
listener.onFailedProject(new ProjectFailedEvent(projectRequest, null))
}
private ProjectRequest initialize() {
def request = new ProjectRequest()
request.initialize(metadata)

View File

@ -36,6 +36,9 @@ import org.springframework.context.ApplicationEventPublisher
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import static org.hamcrest.CoreMatchers.containsString
import static org.junit.Assert.assertThat
import static org.junit.Assert.fail
import static org.mockito.Matchers.argThat
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.times
@ -67,14 +70,14 @@ class ProjectGeneratorTests {
def request = createProjectRequest('web')
generateMavenPom(request).hasNoRepository()
.hasSpringBootStarterDependency('web')
verify(eventPublisher, times(1)).publishEvent(argThat(new EventMatcher(request)))
verify(eventPublisher, times(1)).publishEvent(argThat(new ProjectGeneratedEventMatcher(request)))
}
@Test
void defaultGradleBuild() {
def request = createProjectRequest('web')
generateGradleBuild(request)
verify(eventPublisher, times(1)).publishEvent(argThat(new EventMatcher(request)))
verify(eventPublisher, times(1)).publishEvent(argThat(new ProjectGeneratedEventMatcher(request)))
}
@Test
@ -82,7 +85,7 @@ class ProjectGeneratorTests {
def request = createProjectRequest('web')
generateProject(request).isJavaProject().isMavenProject().pomAssert()
.hasNoRepository().hasSpringBootStarterDependency('web')
verify(eventPublisher, times(1)).publishEvent(argThat(new EventMatcher(request)))
verify(eventPublisher, times(1)).publishEvent(argThat(new ProjectGeneratedEventMatcher(request)))
}
@Test
@ -523,6 +526,45 @@ class ProjectGeneratorTests {
.hasDependenciesCount(3)
}
@Test
void invalidType() {
def request = createProjectRequest('web')
request.type = 'foo-bar'
try {
generateMavenPom(request)
fail("Should have failed to generate project")
} catch (InvalidProjectRequestException ex) {
assertThat ex.message, containsString('foo-bar')
verify(eventPublisher, times(1)).publishEvent(argThat(new ProjectFailedEventMatcher(request, ex)))
}
}
@Test
void invalidPackaging() {
def request = createProjectRequest('web')
request.packaging = 'foo-bar'
try {
generateGradleBuild(request)
fail("Should have failed to generate project")
} catch (InvalidProjectRequestException ex) {
assertThat ex.message, containsString('foo-bar')
verify(eventPublisher, times(1)).publishEvent(argThat(new ProjectFailedEventMatcher(request, ex)))
}
}
@Test
void invalidLanguage() {
def request = createProjectRequest('web')
request.language = 'foo-bar'
try {
generateProject(request)
fail("Should have failed to generate project")
} catch (InvalidProjectRequestException ex) {
assertThat ex.message, containsString('foo-bar')
verify(eventPublisher, times(1)).publishEvent(argThat(new ProjectFailedEventMatcher(request, ex)))
}
}
PomAssert generateMavenPom(ProjectRequest request) {
def content = new String(projectGenerator.generateMavenPom(request))
@ -555,18 +597,35 @@ class ProjectGeneratorTests {
}
}
private static class EventMatcher extends ArgumentMatcher<ProjectGeneratedEvent> {
private static class ProjectGeneratedEventMatcher extends ArgumentMatcher<ProjectGeneratedEvent> {
private final ProjectRequest request
EventMatcher(ProjectRequest request) {
ProjectGeneratedEventMatcher(ProjectRequest request) {
this.request = request
}
@Override
boolean matches(Object argument) {
ProjectGeneratedEvent event = (ProjectGeneratedEvent) argument
return request.equals(event.getProjectRequest())
return request.equals(event.projectRequest)
}
}
private static class ProjectFailedEventMatcher extends ArgumentMatcher<ProjectFailedEvent> {
private final ProjectRequest request
private final Exception cause
ProjectFailedEventMatcher(ProjectRequest request, Exception cause) {
this.request = request
this.cause = cause
}
@Override
boolean matches(Object argument) {
ProjectFailedEvent event = (ProjectFailedEvent) argument
return request.equals(event.projectRequest) && cause.equals(event.cause)
}
}