diff --git a/initializr/src/main/groovy/io/spring/initializr/generator/ProjectRequest.groovy b/initializr/src/main/groovy/io/spring/initializr/generator/ProjectRequest.groovy index 138dfc6a..327af8a5 100644 --- a/initializr/src/main/groovy/io/spring/initializr/generator/ProjectRequest.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/generator/ProjectRequest.groovy @@ -84,6 +84,8 @@ class ProjectRequest { */ void resolve(InitializrMetadata metadata) { List depIds = style ? style : dependencies + String actualBootVersion = bootVersion ?: metadata.bootVersions.default.id + Version requestedVersion = Version.parse(actualBootVersion) resolvedDependencies = depIds.collect { def dependency = metadata.dependencies.get(it) if (dependency == null) { @@ -94,10 +96,8 @@ class ProjectRequest { dependency = new Dependency() dependency.asSpringBootStarter(it) } - dependency + dependency.resolve(requestedVersion) } - String actualBootVersion = bootVersion ?: metadata.bootVersions.default.id - Version requestedVersion = Version.parse(actualBootVersion) Set bomIds = [] resolvedDependencies.each { it.facets.each { diff --git a/initializr/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy b/initializr/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy index ea391f65..b3620d24 100644 --- a/initializr/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/metadata/Dependency.groovy @@ -17,8 +17,11 @@ package io.spring.initializr.metadata import com.fasterxml.jackson.annotation.JsonInclude +import groovy.transform.AutoClone +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.VersionRange /** @@ -29,7 +32,8 @@ import io.spring.initializr.util.VersionRange * @since 1.0 */ @ToString(ignoreNulls = true, includePackage = false) -@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@AutoClone(style = AutoCloneStyle.COPY_CONSTRUCTOR) class Dependency extends MetadataElement { static final String SCOPE_COMPILE = 'compile' @@ -51,8 +55,18 @@ class Dependency extends MetadataElement { String artifactId + /** + * The default version, can be {@code null} to indicate that the + * version is managed by the project and does not need to be specified. + */ String version + /** + * Versions mapping if the version differs according to the Spring Boot + * version. If no mapping matches, {@code version} is used. + */ + List versions = [] + String scope = SCOPE_COMPILE String description @@ -140,6 +154,29 @@ class Dependency extends MetadataElement { "dependency with id '$id'") } } + versions.each { + try { + it.range = VersionRange.parse(it.versionRange) + } catch (InvalidVersionException ex) { + throw new InvalidInitializrMetadataException("Invalid version range $it.versionRange for $this", ex) + } + } + } + + /** + * Resolve this instance according to the specified Spring Boot {@link Version}. Return + * a {@link Dependency} instance that has its state resolved against the specified version. + */ + Dependency resolve(Version bootVersion) { + for (Mapping mapping : versions) { + if (mapping.range.match(bootVersion)) { + def dependency = new Dependency(this) + dependency.version = mapping.version ? mapping.version : this.version + dependency.versions = null + return dependency + } + } + return this } /** @@ -155,4 +192,14 @@ class Dependency extends MetadataElement { id = sb.toString() } + static class Mapping { + + String versionRange + + String version + + private VersionRange range + + } + } diff --git a/initializr/src/main/groovy/io/spring/initializr/metadata/MetadataElement.groovy b/initializr/src/main/groovy/io/spring/initializr/metadata/MetadataElement.groovy index e80c4365..26e85f4b 100644 --- a/initializr/src/main/groovy/io/spring/initializr/metadata/MetadataElement.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/metadata/MetadataElement.groovy @@ -16,12 +16,16 @@ package io.spring.initializr.metadata +import groovy.transform.AutoClone +import groovy.transform.AutoCloneStyle + /** * A basic meta-data element * * @author Stephane Nicoll * @since 1.0 */ +@AutoClone(style = AutoCloneStyle.COPY_CONSTRUCTOR) class MetadataElement { /** diff --git a/initializr/src/test/groovy/io/spring/initializr/generator/ProjectRequestTests.groovy b/initializr/src/test/groovy/io/spring/initializr/generator/ProjectRequestTests.groovy index 3775b74e..f866e09c 100644 --- a/initializr/src/test/groovy/io/spring/initializr/generator/ProjectRequestTests.groovy +++ b/initializr/src/test/groovy/io/spring/initializr/generator/ProjectRequestTests.groovy @@ -116,7 +116,7 @@ class ProjectRequestTests { def metadata = InitializrMetadataTestBuilder.withDefaults() .addDependencyGroup('code', 'org.foo:bar').build() - request.style << 'org.foo:acme' // does not exist and + request.style << 'org.foo:acme' // does not exist thrown.expect(InvalidProjectRequestException) thrown.expectMessage('org.foo:acme') @@ -154,6 +154,29 @@ class ProjectRequestTests { request.resolve(metadata) } + @Test + void resolveDependencyVersion() { + def dependency = createDependency('org.foo', 'bar', '1.2.0.RELEASE') + dependency.versions << new Dependency.Mapping( + version: '0.1.0.RELEASE', versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)') + dependency.versions << new Dependency.Mapping( + version: '0.2.0.RELEASE', versionRange: '1.1.0.RELEASE') + def metadata = InitializrMetadataTestBuilder.withDefaults() + .addDependencyGroup('code', dependency).build() + + def request = new ProjectRequest() + request.bootVersion = '1.0.5.RELEASE' + request.style << 'org.foo:bar' + request.resolve(metadata) + assertDependency(request.resolvedDependencies[0], 'org.foo', 'bar', '0.1.0.RELEASE') + + def anotherRequest = new ProjectRequest() + anotherRequest.bootVersion = '1.1.0.RELEASE' + anotherRequest.style << 'org.foo:bar' + anotherRequest.resolve(metadata) + assertDependency(anotherRequest.resolvedDependencies[0], 'org.foo', 'bar', '0.2.0.RELEASE') + } + @Test void resolveBuild() { def request = new ProjectRequest() diff --git a/initializr/src/test/groovy/io/spring/initializr/metadata/DependencyTests.groovy b/initializr/src/test/groovy/io/spring/initializr/metadata/DependencyTests.groovy index 24f977fe..b8fa122c 100644 --- a/initializr/src/test/groovy/io/spring/initializr/metadata/DependencyTests.groovy +++ b/initializr/src/test/groovy/io/spring/initializr/metadata/DependencyTests.groovy @@ -16,12 +16,14 @@ package io.spring.initializr.metadata +import io.spring.initializr.util.Version import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException import static org.junit.Assert.assertEquals import static org.junit.Assert.assertNull +import static org.junit.Assert.assertSame /** * @author Stephane Nicoll @@ -136,4 +138,47 @@ class DependencyTests { dependency.generateId() } + @Test + void resolveNoMapping() { + def dependency = new Dependency(id: 'web') + dependency.resolve() + assertSame dependency, dependency.resolve(Version.parse('1.2.0.RELEASE')) + } + + @Test + void resolveInvalidMapping() { + def dependency = new Dependency(id: 'web') + dependency.versions << new Dependency.Mapping( + versionRange: 'foo-bar', version: '0.1.0.RELEASE') + thrown.expect(InvalidInitializrMetadataException) + thrown.expectMessage('foo-bar') + dependency.resolve() + } + + @Test + void resolveMatchingMapping() { + def dependency = new Dependency(id: 'web', description: 'A web dependency', version: '0.3.0.RELEASE', + keywords: ['foo', 'bar'], aliases: ['the-web'], facets: ['web'] ) + dependency.versions << new Dependency.Mapping( + versionRange: '[1.1.0.RELEASE, 1.2.0.RELEASE)', version: '0.1.0.RELEASE') + dependency.versions << new Dependency.Mapping( + versionRange: '[1.2.0.RELEASE, 1.3.0.RELEASE)', version: '0.2.0.RELEASE') + dependency.resolve() + + validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')), '0.1.0.RELEASE') + validateResolvedWebDependency(dependency.resolve(Version.parse('1.2.0.RELEASE')), '0.2.0.RELEASE') + validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')), '0.3.0.RELEASE') // default + } + + static void validateResolvedWebDependency(def dependency, def expectedVersion) { + assertEquals expectedVersion, dependency.version + assertEquals 'web', dependency.id + assertEquals 'org.springframework.boot', dependency.groupId + assertEquals 'spring-boot-starter-web', dependency.artifactId + assertEquals 2, dependency.keywords.size() + assertEquals 1, dependency.aliases.size() + assertEquals 1, dependency.facets.size() + + } + } diff --git a/initializr/src/test/resources/metadata/config/test-default.json b/initializr/src/test/resources/metadata/config/test-default.json index 12347881..2455670b 100644 --- a/initializr/src/test/resources/metadata/config/test-default.json +++ b/initializr/src/test/resources/metadata/config/test-default.json @@ -57,9 +57,6 @@ { "content": [ { - "aliases": [], - "keywords": [], - "weight": 0, "starter": true, "artifactId": "spring-boot-starter-web", "description": "Web dependency description", @@ -70,12 +67,8 @@ "scope": "compile" }, { - "aliases": [], - "keywords": [], - "weight": 0, "starter": true, "artifactId": "spring-boot-starter-security", - "facets": [], "groupId": "org.springframework.boot", "id": "security", "name": "Security", @@ -83,11 +76,8 @@ }, { "aliases": ["jpa"], - "keywords": [], - "weight": 0, "starter": true, "artifactId": "spring-boot-starter-data-jpa", - "facets": [], "groupId": "org.springframework.boot", "id": "data-jpa", "name": "Data JPA", @@ -99,9 +89,7 @@ { "content": [ { - "aliases": [], "artifactId": "foo", - "facets": [], "groupId": "org.acme", "id": "org.acme:foo", "name": "Foo", @@ -112,12 +100,8 @@ "version": "1.3.5" }, { - "aliases": [], - "keywords": [], - "weight": 0, "starter": true, "artifactId": "bar", - "facets": [], "groupId": "org.acme", "id": "org.acme:bar", "name": "Bar", @@ -125,12 +109,8 @@ "version": "2.1.0" }, { - "aliases": [], - "keywords": [], - "weight": 0, "starter": true, "artifactId": "biz", - "facets": [], "groupId": "org.acme", "id": "org.acme:biz", "name": "Biz", @@ -139,12 +119,8 @@ "versionRange": "1.2.0.BUILD-SNAPSHOT" }, { - "aliases": [], - "keywords": [], - "weight": 0, "starter": true, "artifactId": "bur", - "facets": [], "groupId": "org.acme", "id": "org.acme:bur", "name": "Bur", @@ -153,12 +129,8 @@ "versionRange": "[1.1.4.RELEASE,1.2.0.BUILD-SNAPSHOT)" }, { - "aliases": [], - "keywords": [], - "weight": 0, "starter": true, "artifactId": "my-api", - "facets": [], "groupId": "org.acme", "id": "my-api", "name": "My API",