Use SpringBootApplication when available

If Spring Boot 1.2 or later is used, the generated project uses the new
SpringBootApplication annotation as a replacement for what used to be
configured by Configuration, ComponentScan and EnableAutoConfiguration.

Also add an infrastructure to parse and compare versions.

Closes gh-46
This commit is contained in:
Stephane Nicoll 2015-01-14 15:56:25 +01:00
parent b8e26a2d3a
commit d35482a61b
8 changed files with 464 additions and 9 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
@ -17,6 +17,7 @@
package io.spring.initializr
import groovy.util.logging.Slf4j
import io.spring.initializr.support.Version
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
@ -34,6 +35,8 @@ import static io.spring.initializr.support.GroovyTemplate.template
@Slf4j
class ProjectGenerator {
private static final VERSION_1_2_0_RC1 = Version.parse('1.2.0.RC1')
@Autowired
InitializrMetadata metadata
@ -164,6 +167,10 @@ class ProjectGenerator {
log.info("Processing request{type=$request.type, dependencies=$dependencies}")
request.properties.each { model[it.key] = it.value }
// @SpringBootApplication available as from 1.2.0.RC1
model['useSpringBootApplication'] = VERSION_1_2_0_RC1
.compareTo(Version.safeParse(request.bootVersion)) <= 0
model
}

View File

@ -0,0 +1,169 @@
/*
* Copyright 2012-2015 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.support
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
* Define the version number of a module. A typical version is represented
* as {@code MAJOR.MINOR.PATCH.QUALIFER} where the qualifier can have an
* extra version.
* <p>
* For example: {@code 1.2.0.RC1} is the first release candidate of 1.2.0
* and {@code 1.5.0.M4} is the fourth milestone of 1.5.0. The special
* {@code RELEASE} qualifier indicates a final release (a.k.a. GA)
* <p>
* The main purpose of parsing a version is to compare it with another
* version, see {@link Comparable}.
*
* @author Stephane Nicoll
* @since 1.0
*/
@ToString
@EqualsAndHashCode
class Version implements Comparable<Version> {
private static final String VERSION_REGEX = '^(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.([^0-9]+)(\\d+)?)?$'
private static final VersionQualifierComparator qualifierComparator = new VersionQualifierComparator()
Integer major
Integer minor
Integer patch
Qualifier qualifier
/**
* Parse the string representation of a {@link Version}. Throws an
* {@link IllegalArgumentException} if the version could not be parsed.
* @param text the version text
* @return a Version instance for the specified version text
* @throws IllegalArgumentException if the version text could not be parsed
* @see #safeParse(java.lang.String)
*/
static Version parse(String text) {
def matcher = (text =~ VERSION_REGEX)
if (!matcher.matches()) {
throw new IllegalArgumentException("Could not determine version based on $text")
}
Version version = new Version()
version.major = Integer.valueOf(matcher[0][1])
version.minor = Integer.valueOf(matcher[0][2])
version.patch = Integer.valueOf(matcher[0][3])
String qualifierId = matcher[0][4]
if (qualifierId) {
Qualifier qualifier = new Qualifier(qualifier: qualifierId)
String o = matcher[0][5]
if (o != null) {
qualifier.version = Integer.valueOf(o)
}
version.qualifier = qualifier
}
version
}
/**
* Parse safely the specified string representation of a {@link Version}.
* <p>
* Return {@code null} if the text represents an invalid version.
* @param text the version text
* @return a Version instance for the specified version text
* @see #parse(java.lang.String)
*/
static safeParse(String text) {
try {
return parse(text)
} catch (IllegalArgumentException e) {
return null
}
}
@Override
int compareTo(Version other) {
if (other == null) {
return 1
}
int majorDiff = safeCompare(this.major, other.major)
if (majorDiff != 0) {
return majorDiff
}
int minorDiff = safeCompare(this.minor, other.minor)
if (minorDiff != 0) {
return minorDiff
}
int patch = safeCompare(this.patch, other.patch)
if (patch != 0) {
return patch
}
qualifierComparator.compare(this.qualifier, other.qualifier)
}
private static int safeCompare(Integer first, Integer second) {
int firstIndex = first ?: 0
int secondIndex = second ?: 0
return firstIndex.compareTo(secondIndex)
}
@ToString
@EqualsAndHashCode
public static class Qualifier {
String qualifier
Integer version
}
private static class VersionQualifierComparator implements Comparator<Qualifier> {
static final String RELEASE = 'RELEASE'
static final String SNAPSHOT = 'BUILD-SNAPSHOT'
static final String MILESTONE = 'M'
static final String RC = 'RC'
static final List<String> KNOWN_QUALIFIERS = Arrays.asList(MILESTONE, RC, SNAPSHOT, RELEASE)
@Override
int compare(Qualifier o1, Qualifier o2) {
Qualifier first = o1 ?: new Qualifier(qualifier: RELEASE)
Qualifier second = o2 ?: new Qualifier(qualifier: RELEASE)
int qualifier = compareQualifier(first, second)
qualifier ? qualifier : compareQualifierVersion(first, second)
}
private static int compareQualifierVersion(Qualifier first, Qualifier second) {
int firstVersion = first.getVersion() ?: 0
int secondVersion = second.getVersion() ?: 0
firstVersion.compareTo(secondVersion)
}
private static int compareQualifier(Qualifier first, Qualifier second) {
int firstIndex = getQualifierIndex(first.qualifier)
int secondIndex = getQualifierIndex(second.qualifier)
if (firstIndex == -1 && secondIndex == -1) { // Unknown qualifier, alphabetic ordering
return first.qualifier.compareTo(second.qualifier)
} else {
return firstIndex.compareTo(secondIndex)
}
}
private static int getQualifierIndex(String qualifier) {
qualifier ? KNOWN_QUALIFIERS.indexOf(qualifier) : RELEASE
}
}
}

View File

@ -1,13 +1,15 @@
package ${packageName}
import org.springframework.boot.SpringApplication
import org.springframework.boot.SpringApplication<% if (useSpringBootApplication) { %>
import org.springframework.boot.autoconfigure.SpringBootApplication<% } else { %>
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Configuration<% } %>
<% if (useSpringBootApplication) { %>
@SpringBootApplication<% } else { %>
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableAutoConfiguration <% } %>
class ${applicationName} {
static void main(String[] args) {

View File

@ -1,13 +1,15 @@
package ${packageName};
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplication;<% if (useSpringBootApplication) { %>
import org.springframework.boot.autoconfigure.SpringBootApplication;<% } else { %>
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Configuration;<% } %>
<% if (useSpringBootApplication) { %>
@SpringBootApplication<% } else { %>
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableAutoConfiguration <% } %>
public class ${applicationName} {
public static void main(String[] args) {

View File

@ -25,6 +25,11 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import static org.mockito.Mockito.*
/**
@ -146,6 +151,60 @@ class ProjectGeneratorTests {
}
@Test
void springBoot11UseEnableAutoConfigurationJava() {
def request = createProjectRequest('web')
request.bootVersion = '1.1.9.RELEASE'
request.name = 'MyDemo'
request.packageName = 'foo'
generateProject(request).sourceCodeAssert('src/main/java/foo/MyDemoApplication.java')
.hasImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name)
.doesNotHaveImports(SpringBootApplication.class.name)
.contains('@EnableAutoConfiguration', '@Configuration', '@ComponentScan')
.doesNotContain('@SpringBootApplication')
}
@Test
void springBootUseSpringBootApplicationJava() {
def request = createProjectRequest('web')
request.bootVersion = '1.2.0.RC1'
request.name = 'MyDemo'
request.packageName = 'foo'
generateProject(request).sourceCodeAssert('src/main/java/foo/MyDemoApplication.java')
.hasImports(SpringBootApplication.class.name)
.doesNotHaveImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name)
.contains('@SpringBootApplication')
.doesNotContain('@EnableAutoConfiguration', '@Configuration', '@ComponentScan')
}
@Test
void springBoot11UseEnableAutoConfigurationGroovy() {
def request = createProjectRequest('web')
request.language = 'groovy'
request.bootVersion = '1.1.9.RELEASE'
request.name = 'MyDemo'
request.packageName = 'foo'
generateProject(request).sourceCodeAssert('src/main/groovy/foo/MyDemoApplication.groovy')
.hasImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name)
.doesNotHaveImports(SpringBootApplication.class.name)
.contains('@EnableAutoConfiguration', '@Configuration', '@ComponentScan')
.doesNotContain('@SpringBootApplication')
}
@Test
void springBootUseSpringBootApplicationGroovy() {
def request = createProjectRequest('web')
request.language = 'groovy'
request.bootVersion = '1.2.0.RC1'
request.name = 'MyDemo'
request.packageName = 'foo'
generateProject(request).sourceCodeAssert('src/main/groovy/foo/MyDemoApplication.groovy')
.hasImports(SpringBootApplication.class.name)
.doesNotHaveImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name)
.contains('@SpringBootApplication')
.doesNotContain('@EnableAutoConfiguration', '@Configuration', '@ComponentScan')
}
PomAssert generateMavenPom(ProjectRequest request) {
def content = new String(projectGenerator.generateMavenPom(request))
new PomAssert(content).validateProjectRequest(request)

View File

@ -0,0 +1,142 @@
/*
* Copyright 2012-2015 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.support
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static io.spring.initializr.support.Version.parse
import static io.spring.initializr.support.Version.safeParse
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertNull
/**
* @author Stephane Nicoll
*/
class VersionTests {
@Rule
public final ExpectedException thrown = ExpectedException.none()
@Test
void equalNoQualifier() {
def first = parse('1.2.0')
def second = parse('1.2.0')
assertThat(first, comparesEqualTo(second))
assertThat(first, equalTo(second))
}
@Test
void equalQualifierNoVersion() {
def first = parse('1.2.0.RELEASE')
def second = parse('1.2.0.RELEASE')
assertThat(first, comparesEqualTo(second))
assertThat(first, equalTo(second))
}
@Test
void equalQualifierVersion() {
def first = parse('1.2.0.RC1')
def second = parse('1.2.0.RC1')
assertThat(first, comparesEqualTo(second))
assertThat(first, equalTo(second))
}
@Test
void compareMajorOnly() {
assertThat(parse('2.2.0'), greaterThan(parse('1.8.0')))
}
@Test
void compareMinorOnly() {
assertThat(parse('2.2.0'), greaterThan(parse('2.1.9')))
}
@Test
void comparePatchOnly() {
assertThat(parse('2.2.4'), greaterThan(parse('2.2.3')))
}
@Test
void compareHigherVersion() {
assertThat(parse('1.2.0.RELEASE'), greaterThan(parse('1.1.9.RELEASE')))
}
@Test
void compareHigherQualifier() {
assertThat(parse('1.2.0.RC1'), greaterThan(parse('1.2.0.M1')))
}
@Test
void compareHigherQualifierVersion() {
assertThat(parse('1.2.0.RC2'), greaterThan(parse('1.2.0.RC1')))
}
@Test
void compareLowerVersion() {
assertThat(parse('1.0.5.RELEASE'), lessThan(parse('1.1.9.RELEASE')))
}
@Test
void compareLowerQualifier() {
assertThat(parse('1.2.0.RC1'), lessThan(parse('1.2.0.RELEASE')))
}
@Test
void compareLessQualifierVersion() {
assertThat(parse('1.2.0.RC2'), lessThan(parse('1.2.0.RC3')))
}
@Test
void compareWithNull() {
assertThat(parse('1.2.0.RC2'), greaterThan(null))
}
@Test
void compareUnknownQualifier() {
assertThat(parse('1.2.0.Beta'), lessThan(parse('1.2.0.CR')))
}
@Test
void compareUnknownQualifierVersion() {
assertThat(parse('1.2.0.Beta1'), lessThan(parse('1.2.0.Beta2')))
}
@Test
void snapshotGreaterThanRC() {
assertThat(parse('1.2.0.BUILD-SNAPSHOT'), greaterThan(parse('1.2.0.RC1')))
}
@Test
void snapshotLowerThanRelease() {
assertThat(parse('1.2.0.BUILD-SNAPSHOT'), lessThan(parse('1.2.0.RELEASE')))
}
@Test
void parseInvalidVersion() {
thrown.expect(IllegalArgumentException)
parse('foo')
}
@Test
void safeParseInvalidVersion() {
assertNull safeParse('foo')
}
}

View File

@ -47,6 +47,14 @@ class ProjectAssert {
new PomAssert(file('pom.xml').text)
}
/**
* Return a {@link SourceCodeAssert} for the specified source code.
*/
SourceCodeAssert sourceCodeAssert(String sourceCodePath) {
hasFile(sourceCodePath)
new SourceCodeAssert(sourceCodePath, file(sourceCodePath).text)
}
ProjectAssert isMavenProject() {
hasFile('pom.xml').hasNoFile('build.gradle')
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2012-2015 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.test
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertTrue
/**
* Source code assertions.
*
* @author Stephane Nicoll
* @since 1.0
*/
class SourceCodeAssert {
private final String name
private final String content
SourceCodeAssert(String name, String content) {
this.name = name
this.content = content
}
SourceCodeAssert hasImports(String... classNames) {
for (String className : classNames) {
contains("import $className")
}
this
}
SourceCodeAssert doesNotHaveImports(String... classNames) {
for (String className : classNames) {
doesNotContain("import $className")
}
this
}
SourceCodeAssert contains(String... expressions) {
for (String expression : expressions) {
assertTrue "$expression has not been found in source code '$name'", content.contains(expression)
}
this
}
SourceCodeAssert doesNotContain(String... expressions) {
for (String expression : expressions) {
assertFalse "$expression should not have been found in source code '$name'", content.contains(expression)
}
this
}
}