Auto-updatable version ranges

This commit improves the version format so that the minor and patch
elements can hold a special 'x' character besides the version, i.e.
`1.x.x.RELEASE` or `2.4.x.BUILD-SNAPSHOT`. A `VersionParser` now takes
care to resolve those against a list of known Spring Boot versions.

This is particularly useful in version ranges that have to change when
the latest Spring Boot versions change. Spring Initializr already auto-
udpates itself based on the sagan metadata. When a range is using this
feature, it is also automatically updated.

It might be hard to track the actual range values on a given instance so
an `InfoContributor` is now automatically exposed to list them.

Closes gh-328
This commit is contained in:
Stephane Nicoll 2016-12-06 16:39:23 +01:00
parent 814a4ad260
commit 827b9d6e93
22 changed files with 673 additions and 148 deletions

View File

@ -0,0 +1,40 @@
/*
* 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.actuate.autoconfigure
import io.spring.initializr.actuate.info.BomRangesInfoContributor
import io.spring.initializr.metadata.InitializrMetadataProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} to improve actuator endpoints with initializr specific information.
*
* @author Stephane Nicoll
*/
@Configuration
class InitializrActuatorEndpointsAutoConfiguration {
@Bean
BomRangesInfoContributor bomRangesInfoContributor(
InitializrMetadataProvider metadataProvider) {
return new BomRangesInfoContributor(metadataProvider)
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.actuate.info
import io.spring.initializr.metadata.InitializrMetadataProvider
import org.springframework.boot.actuate.info.Info
import org.springframework.boot.actuate.info.InfoContributor
/**
* An {@link InfoContributor} that exposes the actual ranges used by each bom
* defined in the project.
*
* @author Stephane Nicoll
*/
class BomRangesInfoContributor implements InfoContributor {
private final InitializrMetadataProvider metadataProvider
BomRangesInfoContributor(InitializrMetadataProvider metadataProvider) {
this.metadataProvider = metadataProvider
}
@Override
void contribute(Info.Builder builder) {
def details = [:]
metadataProvider.get().configuration.env.boms.each { k, v ->
if (v.mappings) {
def bom = [:]
v.mappings.each {
String requirement = "Spring Boot ${it.determineVersionRangeRequirement()}"
bom[it.version] = requirement
}
details[k] = bom
}
}
if (details) {
builder.withDetail('bom-ranges', details)
}
}
}

View File

@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.spring.initializr.actuate.autoconfigure.InitializrActuatorEndpointsAutoConfiguration,\
io.spring.initializr.actuate.autoconfigure.InitializrStatsAutoConfiguration,\
io.spring.initializr.actuate.autoconfigure.InitializrMetricsConfiguration

View File

@ -0,0 +1,82 @@
/*
* 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.actuate.info
import io.spring.initializr.metadata.BillOfMaterials
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.SimpleInitializrMetadataProvider
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import org.junit.Test
import org.springframework.boot.actuate.info.Info
import static org.assertj.core.api.Assertions.assertThat
import static org.assertj.core.api.Assertions.entry
/**
* Tests for {@link BomRangesInfoContributor}
*
* @author Stephane Nicoll
*/
class BomRangesInfoContributorTests {
@Test
void noBom() {
def metadata = InitializrMetadataTestBuilder.withDefaults().build()
def info = getInfo(metadata)
assertThat(info.details).doesNotContainKeys('bom-ranges')
}
@Test
void noMapping() {
def bom = new BillOfMaterials(groupId: 'com.example', artifactId: 'bom', version: '1.0.0')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addBom('foo', bom)
.build()
def info = getInfo(metadata)
assertThat(info.details).doesNotContainKeys('bom-ranges')
}
@Test
void withMappings() {
BillOfMaterials bom = new BillOfMaterials(groupId: 'com.example',
artifactId: 'bom', version: '1.0.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '[1.3.0.RELEASE,1.3.8.RELEASE]', version: '1.1.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '1.3.8.BUILD-SNAPSHOT', version: '1.1.1-SNAPSHOT')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addBom('foo', bom)
.build()
def info = getInfo(metadata)
assertThat(info.details).containsKeys('bom-ranges')
Map<String,Object> ranges = info.details['bom-ranges'] as Map<String, Object>
assertThat(ranges).containsOnlyKeys('foo')
Map<String,Object> foo = ranges['foo'] as Map<String, Object>
assertThat(foo).containsExactly(
entry('1.1.0', 'Spring Boot >=1.3.0.RELEASE and <=1.3.8.RELEASE'),
entry('1.1.1-SNAPSHOT', 'Spring Boot >=1.3.8.BUILD-SNAPSHOT'))
}
private static Info getInfo(InitializrMetadata metadata) {
Info.Builder builder = new Info.Builder()
new BomRangesInfoContributor(new SimpleInitializrMetadataProvider(metadata))
.contribute(builder)
builder.build()
}
}

View File

@ -19,7 +19,6 @@ package io.spring.initializr.generator
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.Type
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.util.VersionRange
/**
* Generate help pages for command-line clients.
@ -144,7 +143,7 @@ class CommandLineHelpGenerator {
String[] data = new String[3]
data[0] = dep.id
data[1] = dep.description ?: dep.name
data[2] = buildVersionRangeRepresentation(dep.versionRange)
data[2] = dep.versionRequirement
dependencyTable[i + 1] = data
}
TableGenerator.generate(dependencyTable)
@ -182,18 +181,6 @@ class CommandLineHelpGenerator {
result
}
private static String buildVersionRangeRepresentation(String range) {
if (!range) {
return null
}
VersionRange versionRange = VersionRange.parse(range)
if (versionRange.higherVersion == null) {
return ">= $range"
} else {
return range.trim()
}
}
private static String buildTagRepresentation(Type type) {
if (type.tags.isEmpty()) {
return "";

View File

@ -93,12 +93,9 @@ class ProjectRequest extends BasicProjectRequest {
facets.add(it)
}
}
if (it.versionRange) {
def range = VersionRange.parse(it.versionRange)
if (!range.match(requestedVersion)) {
throw new InvalidProjectRequestException("Dependency '$it.id' is not compatible " +
"with Spring Boot $bootVersion")
}
if (!it.match(requestedVersion)) {
throw new InvalidProjectRequestException("Dependency '$it.id' is not compatible " +
"with Spring Boot $requestedVersion")
}
if (it.bom) {
resolveBom(metadata, it.bom, requestedVersion)

View File

@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonInclude
import groovy.transform.ToString
import io.spring.initializr.util.InvalidVersionException
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionParser
import io.spring.initializr.util.VersionRange
/**
@ -75,9 +76,13 @@ class BillOfMaterials {
if (!version && !mappings) {
throw new InvalidInitializrMetadataException("No version available for $this");
}
updateVersionRange(VersionParser.DEFAULT)
}
void updateVersionRange(VersionParser versionParser) {
mappings.each {
try {
it.range = VersionRange.parse(it.versionRange)
it.range = versionParser.parseRange(it.versionRange)
} catch (InvalidVersionException ex) {
throw new InvalidInitializrMetadataException("Invalid version range $it.versionRange for $this", ex)
}
@ -119,6 +124,10 @@ class BillOfMaterials {
private VersionRange range
String determineVersionRangeRequirement() {
range.toString()
}
}
}

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.
@ -16,6 +16,8 @@
package io.spring.initializr.metadata
import io.spring.initializr.util.VersionParser
/**
* A {@link ServiceCapability} listing the available dependencies defined as a
* {@link ServiceCapabilityType#HIERARCHICAL_MULTI_SELECT} capability.
@ -53,6 +55,12 @@ class DependenciesCapability extends ServiceCapability<List<DependencyGroup>> {
index()
}
void updateVersionRange(VersionParser versionParser) {
indexedDependencies.values().each {
it.updateVersionRanges(versionParser)
}
}
@Override
void merge(List<DependencyGroup> otherContent) {
otherContent.each { group ->

View File

@ -23,6 +23,7 @@ import groovy.transform.AutoCloneStyle
import groovy.transform.ToString
import io.spring.initializr.util.InvalidVersionException
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionParser
import io.spring.initializr.util.VersionRange
/**
@ -84,6 +85,8 @@ class Dependency extends MetadataElement {
@JsonIgnore
String versionRequirement
private VersionRange range
String bom
@ -159,18 +162,22 @@ class Dependency extends MetadataElement {
"Invalid dependency, id should have the form groupId:artifactId[:version] but got $id")
}
}
updateVersionRanges(VersionParser.DEFAULT)
}
def updateVersionRanges(VersionParser versionParser) {
if (versionRange) {
try {
def range = VersionRange.parse(versionRange)
range = versionParser.parseRange(versionRange)
versionRequirement = range.toString()
} catch (InvalidVersionException ex) {
throw new InvalidInitializrMetadataException("Invalid version range '$versionRange' for " +
"dependency with id '$id'")
"dependency with id '$id'", ex)
}
}
mappings.each {
try {
it.range = VersionRange.parse(it.versionRange)
it.range = versionParser.parseRange(it.versionRange)
} catch (InvalidVersionException ex) {
throw new InvalidInitializrMetadataException("Invalid version range $it.versionRange for $this", ex)
}
@ -200,8 +207,8 @@ class Dependency extends MetadataElement {
* Specify if this dependency is available for the specified Spring Boot version.
*/
boolean match(Version version) {
if (versionRange) {
return VersionRange.parse(versionRange).match(version)
if (range) {
return range.match(version)
}
true
}

View File

@ -16,6 +16,9 @@
package io.spring.initializr.metadata
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionParser
/**
* Meta-data used to generate a project.
*
@ -132,7 +135,23 @@ class InitializrMetadata {
}
}
}
}
/**
* Update the available Spring Boot versions with the specified capabilities.
* @param versionsMetadata the Spring Boot boot versions metadata to use
*/
void updateSpringBootVersions(List<DefaultMetadataElement> versionsMetadata) {
bootVersions.content.clear()
bootVersions.content.addAll(versionsMetadata)
List<Version> bootVersions = bootVersions.content.collect {
Version.parse(it.id)
}
VersionParser parser = new VersionParser(bootVersions)
dependencies.updateVersionRange(parser)
configuration.env.boms.values().each {
it.updateVersionRange(parser)
}
}
/**

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.
@ -44,13 +44,22 @@ final class Version implements Serializable, Comparable<Version> {
private static final VersionQualifierComparator qualifierComparator = new VersionQualifierComparator()
Integer major
Integer minor
Integer patch
Qualifier qualifier
private static final VersionParser parser = new VersionParser(Collections.EMPTY_LIST)
final Integer major
final Integer minor
final Integer patch
final Qualifier qualifier
Version(Integer major, Integer minor, Integer patch, Qualifier qualifier) {
this.major = major
this.minor = minor
this.patch = patch
this.qualifier = qualifier
}
@Override
public String toString() {
String toString() {
"${major}.${minor}.${patch}" + (qualifier?".${qualifier.qualifier}${qualifier.version?:''}" : '')
}
@ -60,29 +69,10 @@ final class Version implements Serializable, Comparable<Version> {
* @param text the version text
* @return a Version instance for the specified version text
* @throws InvalidVersionException if the version text could not be parsed
* @see #safeParse(java.lang.String)
* @see {@link VersionParser}
*/
static Version parse(String text) {
Assert.notNull(text, 'Text must not be null')
def matcher = (text.trim() =~ VERSION_REGEX)
if (!matcher.matches()) {
throw new InvalidVersionException("Could not determine version based on '$text': version format " +
"is Minor.Major.Patch.Qualifier (i.e. 1.0.5.RELEASE)")
}
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
return parser.parse(text)
}
/**
@ -91,7 +81,7 @@ final class Version implements Serializable, Comparable<Version> {
* 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)
* @see {@link VersionParser}
*/
static safeParse(String text) {
try {
@ -129,7 +119,7 @@ final class Version implements Serializable, Comparable<Version> {
@ToString
@EqualsAndHashCode
public static class Qualifier {
static class Qualifier {
String qualifier
Integer version
}

View File

@ -0,0 +1,143 @@
/*
* 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.util
import org.springframework.util.Assert
/**
* Parser for {@link Version} and {@link VersionRange} that allows to resolve the minor
* and patch value against a configurable list of "latest versions".
* <p>
* For example a parser that is configured with {@code 1.3.7.RELEASE} and
* {@code 1.4.2.RELEASE} as latest versions can parse {@code 1.3.x.RELEASE} to
* {@code 1.3.7.RELEASE}. Note that the qualifier is important here:
* {@code 1.3.8.BUILD-SNAPSHOT} would be parsed as {@code 1.3.999.BUILD-SNAPSHOT} as the
* parser doesn't know the latest {@code BUILD-SNAPSHOT} in the {@code 1.3.x} release
* line.
*
* @author Stephane Nicoll
*/
class VersionParser {
public static final VersionParser DEFAULT = new VersionParser(Collections.emptyList())
private static final String VERSION_REGEX = '^(\\d+)\\.(\\d+|x)\\.(\\d+|x)(?:\\.([^0-9]+)(\\d+)?)?$'
private static final String RANGE_REGEX = "(\\(|\\[)(.*),(.*)(\\)|\\])"
private final List<Version> latestVersions;
VersionParser(List<Version> latestVersions) {
this.latestVersions = latestVersions
}
/**
* Parse the string representation of a {@link Version}. Throws an
* {@link InvalidVersionException} if the version could not be parsed.
* @param text the version text
* @return a Version instance for the specified version text
* @throws InvalidVersionException if the version text could not be parsed
* @see #safeParse(java.lang.String)
*/
Version parse(String text) {
Assert.notNull(text, 'Text must not be null')
def matcher = (text.trim() =~ VERSION_REGEX)
if (!matcher.matches()) {
throw new InvalidVersionException("Could not determine version based on '$text': version format " +
"is Minor.Major.Patch.Qualifier (e.g. 1.0.5.RELEASE)")
}
Integer major = Integer.valueOf(matcher[0][1])
String minor = matcher[0][2]
String patch = matcher[0][3]
def qualifier = null;
String qualifierId = matcher[0][4]
if (qualifierId) {
qualifier = new Version.Qualifier(qualifier: qualifierId)
String o = matcher[0][5]
if (o != null) {
qualifier.version = Integer.valueOf(o)
}
}
if (minor == "x" || patch == "x") {
Integer minorInt = minor == "x" ? null : Integer.parseInt(minor)
Version latest = findLatestVersion(major, minorInt, qualifier)
if (!latest) {
return new Version(major, (minor == "x" ? 999 : Integer.parseInt(minor)),
(patch == "x" ? 999 : Integer.parseInt(patch)), qualifier)
}
return new Version(major, latest.minor, latest.patch, latest.qualifier)
} else {
return new Version(major, Integer.parseInt(minor), Integer.parseInt(patch), qualifier)
}
}
/**
* 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)
*/
Version safeParse(String text) {
try {
return parse(text)
} catch (InvalidVersionException ex) {
return null
}
}
/**
* Parse the string representation of a {@link VersionRange}. Throws an
* {@link InvalidVersionException} if the range could not be parsed.
* @param text the range text
* @return a VersionRange instance for the specified range text
* @throws InvalidVersionException if the range text could not be parsed
*/
VersionRange parseRange(String text) {
Assert.notNull(text, "Text must not be null")
def matcher = (text.trim() =~ RANGE_REGEX)
if (!matcher.matches()) {
// Try to read it as simple string
Version version = parse(text)
return new VersionRange(version, true, null, true)
}
boolean lowerInclusive = matcher[0][1].equals('[')
Version lowerVersion = parse(matcher[0][2])
Version higherVersion = parse(matcher[0][3])
boolean higherInclusive = matcher[0][4].equals(']')
new VersionRange(lowerVersion, lowerInclusive, higherVersion, higherInclusive)
}
private Version findLatestVersion(Integer major, Integer minor,
Version.Qualifier qualifier) {
def matches = this.latestVersions.findAll {
if (major && major != it.major) {
return false;
}
if (minor && minor != it.minor) {
return false;
}
if (qualifier && it.qualifier != qualifier) {
return false;
}
return true;
}
return (matches.size() == 1 ? matches[0] : null)
}
}

View File

@ -17,7 +17,6 @@
package io.spring.initializr.util
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import org.springframework.util.Assert
@ -41,15 +40,13 @@ import org.springframework.util.Assert
@EqualsAndHashCode
class VersionRange {
private static final String RANGE_REGEX = "(\\(|\\[)(.*),(.*)(\\)|\\])"
final Version lowerVersion
final boolean lowerInclusive
final Version higherVersion
final boolean higherInclusive
private VersionRange(Version lowerVersion, boolean lowerInclusive,
Version higherVersion, boolean higherInclusive) {
protected VersionRange(Version lowerVersion, boolean lowerInclusive,
Version higherVersion, boolean higherInclusive) {
this.lowerVersion = lowerVersion
this.lowerInclusive = lowerInclusive
this.higherVersion = higherVersion
@ -86,31 +83,9 @@ class VersionRange {
sb.append("${lowerInclusive ? '>=' : '>'}${lowerVersion}")
}
if (higherVersion) {
sb.append(" and ${higherInclusive ? '<=' : '<'}${higherVersion}")
sb.append(" and ${higherInclusive ? '<=' : '<'}${higherVersion}")
}
return sb.toString()
}
/**
* Parse the string representation of a {@link VersionRange}. Throws an
* {@link InvalidVersionException} if the range could not be parsed.
* @param text the range text
* @return a VersionRange instance for the specified range text
* @throws InvalidVersionException if the range text could not be parsed
*/
static VersionRange parse(String text) {
Assert.notNull(text, "Text must not be null")
def matcher = (text.trim() =~ RANGE_REGEX)
if (!matcher.matches()) {
// Try to read it as simple string
Version version = Version.parse(text)
return new VersionRange(version, true, null, true)
}
boolean lowerInclusive = matcher[0][1].equals('[')
Version lowerVersion = Version.parse(matcher[0][2])
Version higherVersion = Version.parse(matcher[0][3])
boolean higherInclusive = matcher[0][4].equals(']')
new VersionRange(lowerVersion, lowerInclusive, higherVersion, higherInclusive)
}
}

View File

@ -112,11 +112,11 @@ class CommandLineHelpGeneratorTests {
def second = new Dependency(id: 'second', description: 'second desc', versionRange: ' [1.2.0.RELEASE,1.3.0.M1) ')
def metadata = InitializrMetadataTestBuilder.withDefaults().addDependencyGroup("test", first, second).build()
String content = generator.generateSpringBootCliCapabilities(metadata, "https://fake-service")
assertThat content, containsString('| first | first desc | >= 1.2.0.RELEASE |')
assertThat content, containsString('| second | second desc | [1.2.0.RELEASE,1.3.0.M1) |')
assertThat content, containsString('| first | first desc | >=1.2.0.RELEASE |')
assertThat content, containsString('| second | second desc | >=1.2.0.RELEASE and <1.3.0.M1 |')
}
private assertCommandLineCapabilities(String content) {
private static assertCommandLineCapabilities(String content) {
assertThat content, containsString("| Rel")
assertThat content, containsString("| dependencies")
assertThat content, containsString("| applicationName")
@ -124,11 +124,11 @@ class CommandLineHelpGeneratorTests {
assertThat content, not(containsString('| Tags'))
}
private static def createDependency(String id, String name) {
private static createDependency(String id, String name) {
createDependency(id, name, null)
}
private static def createDependency(String id, String name, String description) {
private static createDependency(String id, String name, String description) {
new Dependency(id: id, name: name, description: description)
}

View File

@ -17,6 +17,7 @@
package io.spring.initializr.metadata
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionParser
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
@ -108,4 +109,25 @@ class BillOfMaterialsTests {
bom.resolve(Version.parse('1.4.1.RELEASE'))
}
@Test
void resolveRangeWithVariablePatch() {
BillOfMaterials bom = new BillOfMaterials(groupId: 'com.example',
artifactId: 'bom', version: '1.0.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '[1.3.0.RELEASE,1.3.x.RELEASE]', version: '1.1.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '[1.3.x.BUILD-SNAPSHOT,1.4.0.RELEASE)', version: '1.1.1-SNAPSHOT')
bom.validate()
bom.updateVersionRange(new VersionParser(Arrays.asList(
Version.parse("1.3.8.RELEASE"), Version.parse("1.3.9.BUILD-SNAPSHOT"))))
assertThat(bom.resolve(Version.parse('1.3.8.RELEASE')).version, equalTo('1.1.0'))
assertThat(bom.resolve(Version.parse('1.3.9.RELEASE')).version, equalTo('1.1.1-SNAPSHOT'))
bom.updateVersionRange(new VersionParser(Arrays.asList(
Version.parse("1.3.9.RELEASE"), Version.parse("1.3.10.BUILD-SNAPSHOT"))))
assertThat(bom.resolve(Version.parse('1.3.8.RELEASE')).version, equalTo('1.1.0'))
assertThat(bom.resolve(Version.parse('1.3.9.RELEASE')).version, equalTo('1.1.0'))
}
}

View File

@ -17,6 +17,7 @@
package io.spring.initializr.metadata
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionParser
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
@ -201,6 +202,37 @@ class DependencyTests {
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
}
@Test
void resolveMatchingVersionWithVariablePatch() {
def dependency = new Dependency(id: 'web', description: 'A web dependency', version: '0.3.0.RELEASE',
keywords: ['foo', 'bar'], aliases: ['the-web'], facets: ['web'])
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.1.0.RELEASE, 1.1.x.RELEASE]', version: '0.1.0.RELEASE')
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.1.x.BUILD-SNAPSHOT, 1.2.0.RELEASE)', version: '0.2.0.RELEASE')
dependency.resolve()
dependency.updateVersionRanges(new VersionParser(Arrays.asList(
Version.parse("1.1.5.RELEASE"), Version.parse("1.1.6.BUILD-SNAPSHOT"))))
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')),
'org.springframework.boot', 'spring-boot-starter-web', '0.1.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.6.BUILD-SNAPSHOT')),
'org.springframework.boot', 'spring-boot-starter-web', '0.2.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')),
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
dependency.updateVersionRanges(new VersionParser(Arrays.asList(
Version.parse("1.1.6.RELEASE"), Version.parse("1.1.7.BUILD-SNAPSHOT"))))
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')),
'org.springframework.boot', 'spring-boot-starter-web', '0.1.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.6.RELEASE')),
'org.springframework.boot', 'spring-boot-starter-web', '0.1.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.7.BUILD-SNAPSHOT')),
'org.springframework.boot', 'spring-boot-starter-web', '0.2.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')),
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
}
static void validateResolvedWebDependency(
def dependency, def expectedGroupId, def expectedArtifactId, def expectedVersion) {
assertEquals expectedVersion, dependency.version

View File

@ -17,10 +17,13 @@
package io.spring.initializr.metadata
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import io.spring.initializr.util.Version
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static org.assertj.core.api.Assertions.assertThat
/**
* @author Stephane Nicoll
*/
@ -146,6 +149,34 @@ class InitializrMetadataTests {
builder.build()
}
@Test
void updateSpringBootVersions() {
def bom = new BillOfMaterials(groupId: 'org.acme', artifactId: 'foo-bom')
bom.mappings << new BillOfMaterials.Mapping(versionRange: '[1.3.0.RELEASE,1.3.x.RELEASE]', version: '1.0.0')
bom.mappings << new BillOfMaterials.Mapping(versionRange: '1.3.x.BUILD-SNAPSHOT', version: '1.1.0-BUILD-SNAPSHOT')
def dependency = new Dependency(id: 'bar')
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.3.0.RELEASE, 1.3.x.RELEASE]', version: '0.1.0.RELEASE')
dependency.mappings << new Dependency.Mapping(
versionRange: '1.3.x.BUILD-SNAPSHOT', version: '0.2.0.RELEASE')
InitializrMetadata metadata = InitializrMetadataTestBuilder
.withDefaults().addDependencyGroup("test", dependency)
.addBom('foo-bom', bom).build();
List<DefaultMetadataElement> bootVersions = Arrays.asList(
new DefaultMetadataElement(id: '1.3.6.RELEASE', name: '1.3.6'),
new DefaultMetadataElement(id: '1.3.7.BUILD-SNAPSHOT', name: '1.3.7'))
metadata.updateSpringBootVersions(bootVersions)
assertThat(metadata.configuration.env.boms['foo-bom']
.resolve(Version.parse('1.3.6.RELEASE')).version).isEqualTo('1.0.0')
assertThat(metadata.configuration.env.boms['foo-bom']
.resolve(Version.parse('1.3.7.BUILD-SNAPSHOT')).version).isEqualTo('1.1.0-BUILD-SNAPSHOT')
assertThat(metadata.dependencies.get('bar')
.resolve(Version.parse('1.3.6.RELEASE')).version).isEqualTo('0.1.0.RELEASE')
assertThat(metadata.dependencies.get('bar')
.resolve(Version.parse('1.3.7.BUILD-SNAPSHOT')).version).isEqualTo('0.2.0.RELEASE')
}
@Test
void invalidParentMissingVersion() {
InitializrMetadataTestBuilder builder = InitializrMetadataTestBuilder

View File

@ -0,0 +1,134 @@
/*
* 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.util
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.Matchers.equalTo
import static org.hamcrest.Matchers.lessThan
import static org.junit.Assert.assertNull
/**
* Tests for {@link VersionParser}.
*
* @author Stephane Nicoll
*/
class VersionParserTests {
@Rule
public final ExpectedException thrown = ExpectedException.none()
private VersionParser parser = new VersionParser(Collections.EMPTY_LIST)
@Test
void noQualifierString() {
def version = parser.parse('1.2.0')
assertThat(version.toString(), equalTo('1.2.0'))
}
@Test
void withQualifierString() {
def version = parser.parse('1.2.0.RELEASE')
assertThat(version.toString(), equalTo('1.2.0.RELEASE'))
}
@Test
void withQualifierAndVersionString() {
def version = parser.parse('1.2.0.RC2')
assertThat(version.toString(), equalTo('1.2.0.RC2'))
}
@Test
void parseInvalidVersion() {
thrown.expect(InvalidVersionException)
parser.parse('foo')
}
@Test
void safeParseInvalidVersion() {
assertNull parser.safeParse('foo')
}
@Test
void parseVersionWithSpaces() {
assertThat(parser.parse(' 1.2.0.RC3 '),
lessThan(parser.parse('1.3.0.RELEASE')))
}
@Test
void parseVariableVersionMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.3.x.BUILD-SNAPSHOT').toString(),
equalTo('1.3.9.BUILD-SNAPSHOT'))
}
@Test
void parseVariableVersionNoPatchMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.x.x.RELEASE').toString(),
equalTo('1.3.8.RELEASE'))
}
@Test
void parseVariableVersionNoQualifierMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.4.0.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.4.x').toString(),
equalTo('1.4.0.BUILD-SNAPSHOT'))
}
@Test
void parseVariableVersionNoMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.4.x.BUILD-SNAPSHOT').toString(),
equalTo("1.4.999.BUILD-SNAPSHOT"))
}
@Test
void parseVariableVersionNoPatchNoMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('2.x.x.RELEASE').toString(),
equalTo("2.999.999.RELEASE"))
}
@Test
void parseVariableVersionNoQualifierNoMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.4.0.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.2.x').toString(), equalTo("1.2.999"))
}
@Test
void invalidRange() {
thrown.expect(InvalidVersionException)
parser.parseRange("foo-bar")
}
}

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.
@ -83,29 +83,54 @@ class VersionRangeTests {
assertThat('1.1.9.RELEASE', not(match('1.2.0.RELEASE')))
}
@Test
void invalidRange() {
thrown.expect(InvalidVersionException)
VersionRange.parse("foo-bar")
}
@Test
void rangeWithSpaces() {
assertThat('1.2.0.RC3', match('[ 1.2.0.RC1 , 1.2.0.RC5]'))
}
@Test
void matchLatestVersion() {
assertThat('1.2.8.RELEASE', match('[1.2.0.RELEASE,1.2.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.2.9.BUILD-SNAPSHOT')))))
}
@Test
void matchOverLatestVersion() {
assertThat('1.2.10.RELEASE', not(match('[1.2.0.RELEASE,1.2.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.2.9.BUILD-SNAPSHOT'))))))
}
@Test
void matchAsOfCurrentVersion() {
assertThat('1.3.5.RELEASE', match('[1.3.x.RELEASE,1.3.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.3.4.RELEASE'),
Version.parse('1.3.6.BUILD-SNAPSHOT')))))
}
@Test
void matchOverAsOfCurrentVersion() {
assertThat('1.3.5.RELEASE', not(match('[1.3.x.RELEASE,1.3.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.3.7.RELEASE'),
Version.parse('1.3.6.BUILD-SNAPSHOT'))))))
}
private static VersionRangeMatcher match(String range) {
new VersionRangeMatcher(range)
new VersionRangeMatcher(range, new VersionParser(Collections.EMPTY_LIST))
}
private static VersionRangeMatcher match(String range, VersionParser parser) {
new VersionRangeMatcher(range, parser)
}
static class VersionRangeMatcher extends BaseMatcher<String> {
private final VersionRange range;
private final VersionParser parser;
VersionRangeMatcher(String text) {
this.range = VersionRange.parse(text)
VersionRangeMatcher(String text, VersionParser parser) {
this.parser = parser
this.range = parser.parseRange(text)
}
@Override
@ -113,12 +138,13 @@ class VersionRangeTests {
if (!item instanceof String) {
return false;
}
return this.range.match(Version.parse(item))
return this.range.match(this.parser.parse((String) item))
}
@Override
void describeTo(Description description) {
description.appendText(range)
description.appendText(range.toString())
}
}
}

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.
@ -16,41 +16,20 @@
package io.spring.initializr.util
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static io.spring.initializr.util.Version.parse
import static io.spring.initializr.util.Version.safeParse
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertNull
import static org.hamcrest.Matchers.comparesEqualTo
import static org.hamcrest.Matchers.equalTo
import static org.hamcrest.Matchers.greaterThan
import static org.hamcrest.Matchers.lessThan
/**
* @author Stephane Nicoll
*/
class VersionTests {
@Rule
public final ExpectedException thrown = ExpectedException.none()
@Test
void noQualifierString() {
def version = parse('1.2.0')
assertThat(version.toString(), equalTo('1.2.0'))
}
@Test
void withQualifierString() {
def version = parse('1.2.0.RELEASE')
assertThat(version.toString(), equalTo('1.2.0.RELEASE'))
}
@Test
void withQualifierAndVersionString() {
def version = parse('1.2.0.RC2')
assertThat(version.toString(), equalTo('1.2.0.RC2'))
}
private static final VersionParser parser = new VersionParser(Collections.EMPTY_LIST)
@Test
void equalNoQualifier() {
@ -146,20 +125,9 @@ class VersionTests {
assertThat(parse('1.2.0.BUILD-SNAPSHOT'), lessThan(parse('1.2.0.RELEASE')))
}
@Test
void parseInvalidVersion() {
thrown.expect(InvalidVersionException)
parse('foo')
}
@Test
void safeParseInvalidVersion() {
assertNull safeParse('foo')
}
@Test
void parseVersionWithSpaces() {
assertThat(parse(' 1.2.0.RC3 '), lessThan(parse('1.3.0.RELEASE')))
private static Version parse(String text) {
def version = parser.parse(text)
version
}
}

View File

@ -55,8 +55,7 @@ class DefaultInitializrMetadataProvider implements InitializrMetadataProvider {
if (!bootVersions.find { it.default }) { // No default specified
bootVersions[0].default = true
}
metadata.bootVersions.content.clear()
metadata.bootVersions.content.addAll(bootVersions)
metadata.updateSpringBootVersions(bootVersions)
}
}

View File

@ -22,7 +22,6 @@ import groovy.json.JsonBuilder
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionRange
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
@ -52,7 +51,7 @@ class UiController {
dependencyGroups.each { g ->
g.content.each { d ->
if (v && d.versionRange) {
if (VersionRange.parse(d.versionRange).match(v)) {
if (d.match(v)) {
content << new DependencyItem(g.name, d)
}
} else {