diff --git a/initializr-docs/src/main/asciidoc/configuration-guide.adoc b/initializr-docs/src/main/asciidoc/configuration-guide.adoc index 0f67ec5d..efe58772 100644 --- a/initializr-docs/src/main/asciidoc/configuration-guide.adoc +++ b/initializr-docs/src/main/asciidoc/configuration-guide.adoc @@ -542,21 +542,30 @@ it. The versions are *not* applied to the dependency itself, but rather used to the dependency, or modify it, when different versions of Spring Boot are selected for the generated project. -A typical version is composed of four parts: a major revision, a minor revision, a patch -revision and a qualifier. Qualifiers are ordered as follows: +A version is composed of four parts: a major revision, a minor revision, a patch +revision and an optional qualifier. Spring Initializr supports two version formats: + +* `V1` is the original format where the qualifier is separated from the version by a dot. +It also uses well-defined qualifiers for snapshots (`BUILD-SNAPSHOT`) and General +Availability (`RELEASE`). +* `V2` is an improved format that is SemVer compliant, and therefore uses a dash to +separate the qualifier. There is no qualifier for GAs. + +Speaking of qualifiers, they are ordered as follows: * `M` for milestones (e.g. `2.0.0.M1` is the first milestone of the upcoming 2.0.0 release): can be seen as "beta" release -* `RC` for release candidates (e.g. `2.0.0.RC2` is the second release candidate of +* `RC` for release candidates (e.g. `2.0.0-RC2` is the second release candidate of upcoming 2.0.0 release) -* `RELEASE` for general availability (e.g. `2.0.0.RELEASE` is 2.0.0 proper) * `BUILD-SNAPSHOT` for development build (`2.1.0.BUILD-SNAPSHOT` represents the latest -available development build of the upcoming 2.1.0 release). - -TIP: snapshots are in a bit special in that scheme as they always represents the "latest -state" of a release. `M1` represents the most oldest version for a given major, minor and -patch revisions. +available development build of the upcoming 2.1.0 release). For the `V2` format, it is +simply `SNAPSHOT`, i.e. `2.1.0-SNAPSHOT`. +* `RELEASE` for general availability (e.g. `2.0.0.RELEASE` is 2.0.0 proper) +TIP: snapshots are in a bit special in that scheme as they always represent the "latest +state" of a release. `M1` represents the oldest version for a given major, minor and +patch revisions, and it can therefore be safely used when referring to the "first" release +in that line. A version range has a lower and an upper bound, and if the bound is inclusive it is denoted as a square bracket (`[` or `]`), otherwise it is exclusive and denoted by a @@ -572,7 +581,7 @@ an hard-coded version. For instance, `1.4.x.BUILD-SNAPSHOT` is the latest snapsh of the 1.4.x line. For instance, if you want to restrict a dependency from `1.1.0.RELEASE` to the latest stable release of the 1.3.x line, you'd use `[1.1.0.RELEASE,1.3.x.RELEASE]`. -Snapshots are naturally ordered higher than released versions, so if you are looking to +Snapshots are naturally the most recent version of a given line, so if you are looking to match a dependency to only the latest snapshots of Spring Boot, you could use a version range of `1.5.x.BUILD-SNAPSHOT` (assuming 1.5 was the latest). @@ -580,7 +589,8 @@ TIP: Remember to quote the values of a version range in YAML configuration files double quotes ""). See below in the section on <> for more examples -and idioms. +and idioms. See also how the <> can +be configured. @@ -1017,6 +1027,30 @@ These dependencies, by default, will be available only for Spring Boot versions +[[howto-platform-version-format]] +=== Configure platform version format +Spring Initializr supports two formats: `V1` is the original format defined by metadata +up to `2.1`. `V2` is the SemVer format provided alongside `V1` as of metadata `2.2`. In +order to serve backward compatible content, the version range for each format should be +configured so that translations can happen accordingly. + +Let's assume that an instance only supports `2.0.0` and later and the platform version is +using the original format up to `2.4.0` (excluded). As of `2.4.0`, the improved, SemVer +format is used. The following configures the instance to adapt version format +automatically: + +[source,yaml,indent=0] +---- + initializr: + env: + platform: + compatibility-range: "2.0.0.RELEASE" + v1-format-compatibility-range: "[2.0.0.RELEASE,2.4.0-M1)" + v2-format-compatibility-range: "2.4.0-M1" +---- + + + [[create-instance-advanced-config]] == Advanced configuration diff --git a/initializr-docs/src/main/asciidoc/metadata-format.adoc b/initializr-docs/src/main/asciidoc/metadata-format.adoc index 928593ff..35a6b25e 100644 --- a/initializr-docs/src/main/asciidoc/metadata-format.adoc +++ b/initializr-docs/src/main/asciidoc/metadata-format.adoc @@ -13,10 +13,16 @@ sent to the service. A good structure for a user agent is `clientId/clientVersio == Service Capabilities Any third party client can retrieve the capabilities of the service by issuing a `GET` on the root URL using the following `Accept` header: -`application/vnd.initializr.v2.1+json`. Please note that the metadata may evolve in a +`application/vnd.initializr.v2.2+json`. Please note that the metadata may evolve in a non backward compatible way in the future so adding this header ensures the service returns the metadata format you expect. +The following versions are supported: + +* `v2` initial version, with support of V1 version format only +* `v2.1` support compatibility range and dependencies links +* `v2.2` (current) support for V1 and V2 version formats. + This is an example output for a service running at `https://start.spring.io`: .request diff --git a/initializr-docs/src/main/asciidoc/using-the-stubs.adoc b/initializr-docs/src/main/asciidoc/using-the-stubs.adoc index 8837586e..b282384f 100644 --- a/initializr-docs/src/main/asciidoc/using-the-stubs.adoc +++ b/initializr-docs/src/main/asciidoc/using-the-stubs.adoc @@ -110,7 +110,7 @@ include::{test-examples}/stub/ClientApplicationTests.java[tag=test] Then you have a server that returns the stub of the JSON metadata (`metadataWithCurrentAcceptHeader.json`) when you send it a header -`Accept:application/vnd.initializr.v2.1+json` (as recommended). +`Accept:application/vnd.initializr.v2.2+json` (as recommended). diff --git a/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java b/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java index 627ec2f0..70fcc547 100644 --- a/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java +++ b/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java @@ -26,6 +26,7 @@ import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.DependencyGroup; import io.spring.initializr.metadata.InitializrConfiguration.Env.Kotlin; import io.spring.initializr.metadata.InitializrConfiguration.Env.Maven.ParentPom; +import io.spring.initializr.metadata.InitializrConfiguration.Platform; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.InitializrMetadataBuilder; import io.spring.initializr.metadata.Repository; @@ -188,7 +189,16 @@ public class InitializrMetadataTestBuilder { public InitializrMetadataTestBuilder setPlatformCompatibilityRange(String platformCompatibilityRange) { this.builder.withCustomizer( - (it) -> it.getConfiguration().getEnv().setPlatformCompatibilityRange(platformCompatibilityRange)); + (it) -> it.getConfiguration().getEnv().getPlatform().setCompatibilityRange(platformCompatibilityRange)); + return this; + } + + public InitializrMetadataTestBuilder setPlatformVersionFormatCompatibilityRange(String v1Range, String v2Range) { + this.builder.withCustomizer((it) -> { + Platform platform = it.getConfiguration().getEnv().getPlatform(); + platform.setV1FormatCompatibilityRange(v1Range); + platform.setV2FormatCompatibilityRange(v2Range); + }); return this; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/version/Version.java b/initializr-generator/src/main/java/io/spring/initializr/generator/version/Version.java index 0a1c47e4..78bc4b57 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/version/Version.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/version/Version.java @@ -23,19 +23,25 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.StringJoiner; +import java.util.function.Function; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Define the version number of a module. A typical version is represented as - * {@code MAJOR.MINOR.PATCH.QUALIFIER} where the qualifier can have an extra version. + * Define a version. A typical version is represented as + * {@code MAJOR.MINOR.PATCH[QUALIFIER]} where the qualifier is optional and can have an + * extra version. *

* 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) + * {@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). *

- * The main purpose of parsing a version is to compare it with another version, see - * {@link Comparable}. + * Two formats are currently supported, {@link Format#V1} that uses a dot to separate the + * qualifier from the version itself and {@link Format#V2} that is SemVer compliant (and + * therefore uses a dash to separate the qualifier). + *

+ * The main purpose of parsing a version is to compare it with another version. * * @author Stephane Nicoll */ @@ -54,11 +60,68 @@ public final class Version implements Serializable, Comparable { private final Qualifier qualifier; + private final Format format; + public Version(Integer major, Integer minor, Integer patch, Qualifier qualifier) { this.major = major; this.minor = minor; this.patch = patch; this.qualifier = qualifier; + this.format = determineFormat(qualifier); + } + + private static Format determineFormat(Qualifier qualifier) { + if (qualifier == null) { + return Format.V2; + } + return (qualifier.getSeparator().equals(".")) ? Format.V1 : Format.V2; + } + + /** + * Format this version to the specified {@link Format}. + * @param format the format to use + * @return a version compliant with the specified format. + */ + public Version format(Format format) { + Assert.notNull(format, () -> "Format must not be null"); + if (this.format == format) { + return this; + } + if (format == Format.V1) { + Qualifier qualifier = formatQualifier(".", this::toV1Qualifier); + return new Version(this.major, this.minor, this.patch, qualifier); + } + Qualifier qualifier = formatQualifier("-", this::toV2Qualifier); + return new Version(this.major, this.minor, this.patch, qualifier); + } + + private Qualifier formatQualifier(String newSeparator, Function idTransformer) { + String originalQualifier = (this.qualifier != null) ? this.qualifier.getId() : null; + String newId = idTransformer.apply(originalQualifier); + if (newId != null) { + return new Qualifier(newId, (this.qualifier != null) ? this.qualifier.getVersion() : null, newSeparator); + } + return null; + } + + private String toV1Qualifier(String id) { + if ("SNAPSHOT".equals(id)) { + return "BUILD-SNAPSHOT"; + } + if (id == null) { + return "RELEASE"; + } + return id; + } + + private String toV2Qualifier(String id) { + if ("BUILD-SNAPSHOT".equals(id)) { + return "SNAPSHOT"; + } + if ("RELEASE".equals(id)) { + return null; + } + return id; } public Integer getMajor() { @@ -77,6 +140,10 @@ public final class Version implements Serializable, Comparable { return this.qualifier; } + public Format getFormat() { + return this.format; + } + /** * Parse the string representation of a {@link Version}. Throws an * {@link InvalidVersionException} if the version could not be parsed. @@ -262,6 +329,26 @@ public final class Version implements Serializable, Comparable { } + /** + * Define the supported version format. + */ + public enum Format { + + /** + * Original version format, i.e. {@code Major.Minor.Patch.Qualifier} using + * {@code BUILD-SNAPSHOT} as the qualifier for snapshots and {@code RELEASE} for + * GAs. + */ + V1, + + /** + * SemVer-compliant format, i.e. {@code Major.Minor.Patch-Qualifier} using + * {@code SNAPSHOT} as the qualifier for snapshots and no qualifier for GAs. + */ + V2; + + } + private static class VersionQualifierComparator implements Comparator { static final String RELEASE = "RELEASE"; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/version/VersionRange.java b/initializr-generator/src/main/java/io/spring/initializr/generator/version/VersionRange.java index 8b1d11fa..506a475f 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/version/VersionRange.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/version/VersionRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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.generator.version; +import io.spring.initializr.generator.version.Version.Format; + import org.springframework.util.Assert; /** @@ -27,9 +29,8 @@ import org.springframework.util.Assert; *

* * @author Stephane Nicoll @@ -83,6 +84,17 @@ public class VersionRange { return true; } + /** + * Format this version range to the specified {@link Format}. + * @param format the version format to use + * @return a version range whose boundaries are compliant with the specified format. + */ + public VersionRange format(Format format) { + Version lower = this.lowerVersion.format(format); + Version higher = (this.higherVersion != null) ? this.higherVersion.format(format) : null; + return new VersionRange(lower, this.lowerInclusive, higher, this.higherInclusive); + } + public Version getLowerVersion() { return this.lowerVersion; } diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionRangeTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionRangeTests.java index 1d68de48..ccb06861 100755 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionRangeTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionRangeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -19,12 +19,15 @@ package io.spring.initializr.generator.version; import java.util.Arrays; import java.util.Collections; +import io.spring.initializr.generator.version.Version.Format; import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** + * Tests for {@link VersionRange}. + * * @author Stephane Nicoll */ class VersionRangeTests { @@ -148,6 +151,34 @@ class VersionRangeTests { assertThat(range.toRangeString()).isEqualTo("(1.3.5.RELEASE,1.5.5.RELEASE)"); } + @Test + void formatLowerOnlyV1toV2() { + VersionRange range = parse("1.2.0.RELEASE").format(Format.V2); + assertThat(range.toRangeString()).isEqualTo("1.2.0"); + } + + @Test + void formatV1toV2() { + VersionRange range = parse("[1.2.0.RELEASE,1.3.0.M1)").format(Format.V2); + assertThat(range.toRangeString()).isEqualTo("[1.2.0,1.3.0-M1)"); + } + + @Test + void formatLowerOnlyV2toV1() { + VersionRange range = parse("1.2.0").format(Format.V1); + assertThat(range.toRangeString()).isEqualTo("1.2.0.RELEASE"); + } + + @Test + void formatV2toV1() { + VersionRange range = parse("[1.2.0,1.3.0-M1)").format(Format.V1); + assertThat(range.toRangeString()).isEqualTo("[1.2.0.RELEASE,1.3.0.M1)"); + } + + private static VersionRange parse(String text) { + return new VersionParser(Collections.emptyList()).parseRange(text); + } + private static Condition match(String range) { return match(range, new VersionParser(Collections.emptyList())); } diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionTests.java index 4aef3f48..9366482f 100755 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/version/VersionTests.java @@ -21,11 +21,14 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import io.spring.initializr.generator.version.Version.Format; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** + * Tests for {@link Version}. + * * @author Stephane Nicoll */ class VersionTests { @@ -153,6 +156,54 @@ class VersionTests { "2020.0.0-SNAPSHOT", "2020.0.0"); } + @Test + void formatV1toV1() { + Version version = Version.parse("1.2.0.RELEASE"); + assertThat(version.format(Format.V1)).isSameAs(version); + } + + @Test + void formatV1SnapshotToV2() { + Version version = Version.parse("1.2.0.BUILD-SNAPSHOT"); + assertThat(version.format(Format.V2)).hasToString("1.2.0-SNAPSHOT"); + } + + @Test + void formatV1GAToV2() { + Version version = Version.parse("1.2.0.RELEASE"); + assertThat(version.format(Format.V2)).hasToString("1.2.0"); + } + + @Test + void formatNoQualifierToV1() { + Version version = Version.parse("1.2.0"); + assertThat(version.format(Format.V1)).hasToString("1.2.0.RELEASE"); + } + + @Test + void formatV2toV2() { + Version version = Version.parse("1.2.0-RC1"); + assertThat(version.format(Format.V2)).isSameAs(version); + } + + @Test + void formatV2SnapshotToV1() { + Version version = Version.parse("1.2.0-SNAPSHOT"); + assertThat(version.format(Format.V1)).hasToString("1.2.0.BUILD-SNAPSHOT"); + } + + @Test + void formatV2GAToV1() { + Version version = Version.parse("1.2.0"); + assertThat(version.format(Format.V1)).hasToString("1.2.0.RELEASE"); + } + + @Test + void formatNoQualifierToV2() { + Version version = Version.parse("1.2.0"); + assertThat(version.format(Format.V2)).hasToString("1.2.0"); + } + private Version parse(String text) { return this.parser.parse(text); } diff --git a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrConfiguration.java b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrConfiguration.java index ab70810e..f8260f58 100644 --- a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrConfiguration.java +++ b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrConfiguration.java @@ -30,6 +30,7 @@ import javax.lang.model.SourceVersion; import com.fasterxml.jackson.annotation.JsonIgnore; import io.spring.initializr.generator.version.InvalidVersionException; import io.spring.initializr.generator.version.Version; +import io.spring.initializr.generator.version.Version.Format; import io.spring.initializr.generator.version.VersionParser; import io.spring.initializr.generator.version.VersionRange; @@ -210,16 +211,6 @@ public class InitializrConfiguration { */ private boolean forceSsl; - /** - * Compatibility range of supported platform versions. Requesting metadata or - * project generation with a platform version that does not match this range is - * not supported. - */ - private String platformCompatibilityRange; - - @JsonIgnore - private VersionRange compatibilityRange; - /** * The "BillOfMaterials" that are referenced in this instance, identified by an * arbitrary identifier that can be used in the dependencies definition. @@ -250,6 +241,12 @@ public class InitializrConfiguration { @NestedConfigurationProperty private final Maven maven = new Maven(); + /** + * Platform-specific settings. + */ + @NestedConfigurationProperty + private final Platform platform = new Platform(); + public Env() { try { this.repositories.put("spring-snapshots", @@ -310,14 +307,6 @@ public class InitializrConfiguration { this.forceSsl = forceSsl; } - public String getPlatformCompatibilityRange() { - return this.platformCompatibilityRange; - } - - public void setPlatformCompatibilityRange(String platformCompatibilityRange) { - this.platformCompatibilityRange = platformCompatibilityRange; - } - public String getArtifactRepository() { return this.artifactRepository; } @@ -342,6 +331,10 @@ public class InitializrConfiguration { return this.maven; } + public Platform getPlatform() { + return this.platform; + } + public void setArtifactRepository(String artifactRepository) { if (!artifactRepository.endsWith("/")) { artifactRepository = artifactRepository + "/"; @@ -359,8 +352,7 @@ public class InitializrConfiguration { public void updateCompatibilityRange(VersionParser versionParser) { this.getBoms().values().forEach((it) -> it.updateCompatibilityRange(versionParser)); this.getKotlin().updateCompatibilityRange(versionParser); - this.compatibilityRange = (this.platformCompatibilityRange != null) - ? versionParser.parseRange(this.platformCompatibilityRange) : null; + this.getPlatform().updateCompatibilityRange(versionParser); } public void merge(Env other) { @@ -370,29 +362,14 @@ public class InitializrConfiguration { this.fallbackApplicationName = other.fallbackApplicationName; this.invalidApplicationNames = other.invalidApplicationNames; this.forceSsl = other.forceSsl; - this.platformCompatibilityRange = other.platformCompatibilityRange; - this.compatibilityRange = other.compatibilityRange; this.gradle.merge(other.gradle); this.kotlin.merge(other.kotlin); this.maven.merge(other.maven); + this.platform.merge(other.platform); other.boms.forEach(this.boms::putIfAbsent); other.repositories.forEach(this.repositories::putIfAbsent); } - /** - * Specify whether the specified {@linkplain Version platform version} is - * supported. - * @param platformVersion the platform version to check - * @return {@code true} if this version is supported, {@code false} otherwise - */ - public boolean isCompatiblePlatformVersion(Version platformVersion) { - return (this.compatibilityRange == null || this.compatibilityRange.match(platformVersion)); - } - - public String determinePlatformCompatibilityRangeRequirement() { - return this.compatibilityRange.toString(); - } - /** * Gradle details. */ @@ -662,4 +639,112 @@ public class InitializrConfiguration { } + /** + * Platform-specific settings. + */ + public static class Platform { + + /** + * Compatibility range of supported platform versions. Requesting metadata or + * project generation with a platform version that does not match this range is + * not supported. + */ + private String compatibilityRange; + + @JsonIgnore + private VersionRange range; + + /** + * Compatibility range of platform versions using the first version format. + */ + private String v1FormatCompatibilityRange; + + @JsonIgnore + private VersionRange v1FormatRange; + + /** + * Compatibility range of platform versions using the second version format. + */ + private String v2FormatCompatibilityRange; + + @JsonIgnore + private VersionRange v2FormatRange; + + public void updateCompatibilityRange(VersionParser versionParser) { + this.range = (this.compatibilityRange != null) ? versionParser.parseRange(this.compatibilityRange) : null; + this.v1FormatRange = (this.v1FormatCompatibilityRange != null) + ? versionParser.parseRange(this.v1FormatCompatibilityRange) : null; + this.v2FormatRange = (this.v2FormatCompatibilityRange != null) + ? versionParser.parseRange(this.v2FormatCompatibilityRange) : null; + } + + private void merge(Platform other) { + this.compatibilityRange = other.compatibilityRange; + this.range = other.range; + this.v1FormatCompatibilityRange = other.v1FormatCompatibilityRange; + this.v1FormatRange = other.v1FormatRange; + this.v2FormatCompatibilityRange = other.v2FormatCompatibilityRange; + this.v2FormatRange = other.v2FormatRange; + } + + /** + * Specify whether the specified {@linkplain Version platform version} is + * supported. + * @param platformVersion the platform version to check + * @return {@code true} if this version is supported, {@code false} otherwise + */ + public boolean isCompatibleVersion(Version platformVersion) { + return (this.range == null || this.range.match(platformVersion)); + } + + public String determineCompatibilityRangeRequirement() { + return this.range.toString(); + } + + /** + * Format the expected {@link Version platform version}. + * @param platformVersion a platform version + * @return a platform version in the suitable format + */ + public Version formatPlatformVersion(Version platformVersion) { + Format format = getExpectedVersionFormat(platformVersion); + return platformVersion.format(format); + } + + private Format getExpectedVersionFormat(Version version) { + if (this.v2FormatRange != null && this.v2FormatRange.match(version)) { + return Format.V2; + } + if (this.v1FormatRange != null && this.v1FormatRange.match(version)) { + return Format.V1; + } + return version.getFormat(); + } + + public String getCompatibilityRange() { + return this.compatibilityRange; + } + + public void setCompatibilityRange(String compatibilityRange) { + this.compatibilityRange = compatibilityRange; + } + + public String getV1FormatCompatibilityRange() { + return this.v1FormatCompatibilityRange; + } + + public void setV1FormatCompatibilityRange(String v1FormatCompatibilityRange) { + this.v1FormatCompatibilityRange = v1FormatCompatibilityRange; + } + + public String getV2FormatCompatibilityRange() { + return this.v2FormatCompatibilityRange; + } + + public void setV2FormatCompatibilityRange(String v2FormatCompatibilityRange) { + this.v2FormatCompatibilityRange = v2FormatCompatibilityRange; + } + + } + } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java index 552fc3bd..5b2c6f92 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java @@ -38,9 +38,11 @@ import io.spring.initializr.web.controller.DefaultProjectGenerationController; import io.spring.initializr.web.controller.ProjectGenerationController; import io.spring.initializr.web.controller.ProjectMetadataController; import io.spring.initializr.web.controller.SpringCliDistributionController; +import io.spring.initializr.web.project.DefaultProjectRequestPlatformVersionTransformer; import io.spring.initializr.web.project.DefaultProjectRequestToDescriptionConverter; import io.spring.initializr.web.project.ProjectGenerationInvoker; import io.spring.initializr.web.project.ProjectRequest; +import io.spring.initializr.web.project.ProjectRequestPlatformVersionTransformer; import io.spring.initializr.web.support.DefaultDependencyMetadataProvider; import io.spring.initializr.web.support.DefaultInitializrMetadataProvider; import io.spring.initializr.web.support.DefaultInitializrMetadataUpdateStrategy; @@ -144,9 +146,12 @@ public class InitializrAutoConfiguration { @Bean @ConditionalOnMissingBean ProjectGenerationController projectGenerationController( - InitializrMetadataProvider metadataProvider, ApplicationContext applicationContext) { + InitializrMetadataProvider metadataProvider, + ObjectProvider platformVersionTransformer, + ApplicationContext applicationContext) { ProjectGenerationInvoker projectGenerationInvoker = new ProjectGenerationInvoker<>( - applicationContext, new DefaultProjectRequestToDescriptionConverter()); + applicationContext, new DefaultProjectRequestToDescriptionConverter(platformVersionTransformer + .getIfAvailable(DefaultProjectRequestPlatformVersionTransformer::new))); return new DefaultProjectGenerationController(metadataProvider, projectGenerationInvoker); } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java index 8bc7a1ac..692d9fec 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java @@ -24,13 +24,14 @@ import javax.servlet.http.HttpServletResponse; import io.spring.initializr.generator.version.Version; import io.spring.initializr.metadata.DependencyMetadata; import io.spring.initializr.metadata.DependencyMetadataProvider; -import io.spring.initializr.metadata.InitializrConfiguration.Env; +import io.spring.initializr.metadata.InitializrConfiguration.Platform; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.InitializrMetadataProvider; import io.spring.initializr.metadata.InvalidInitializrMetadataException; import io.spring.initializr.web.mapper.DependencyMetadataV21JsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataJsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataV21JsonMapper; +import io.spring.initializr.web.mapper.InitializrMetadataV22JsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataV2JsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataVersion; import io.spring.initializr.web.project.InvalidProjectRequestException; @@ -77,6 +78,11 @@ public class ProjectMetadataController extends AbstractMetadataController { return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1, HAL_JSON_CONTENT_TYPE); } + @RequestMapping(path = { "/", "/metadata/client" }, produces = { "application/vnd.initializr.v2.2+json" }) + public ResponseEntity serviceCapabilitiesV22() { + return serviceCapabilitiesFor(InitializrMetadataVersion.V2_2); + } + @RequestMapping(path = { "/", "/metadata/client" }, produces = { "application/vnd.initializr.v2.1+json", "application/json" }) public ResponseEntity serviceCapabilitiesV21() { @@ -120,10 +126,10 @@ public class ProjectMetadataController extends AbstractMetadataController { InitializrMetadata metadata = this.metadataProvider.get(); Version v = (bootVersion != null) ? Version.parse(bootVersion) : Version.parse(metadata.getBootVersions().getDefault().getId()); - Env env = metadata.getConfiguration().getEnv(); - if (!env.isCompatiblePlatformVersion(v)) { + Platform platform = metadata.getConfiguration().getEnv().getPlatform(); + if (!platform.isCompatibleVersion(v)) { throw new InvalidProjectRequestException("Invalid Spring Boot version '" + bootVersion - + "', Spring Boot compatibility range is " + env.determinePlatformCompatibilityRangeRequirement()); + + "', Spring Boot compatibility range is " + platform.determineCompatibilityRangeRequirement()); } DependencyMetadata dependencyMetadata = this.dependencyMetadataProvider.get(metadata, v); String content = new DependencyMetadataV21JsonMapper().write(dependencyMetadata); @@ -147,8 +153,10 @@ public class ProjectMetadataController extends AbstractMetadataController { switch (version) { case V2: return new InitializrMetadataV2JsonMapper(); - default: + case V2_1: return new InitializrMetadataV21JsonMapper(); + default: + return new InitializrMetadataV22JsonMapper(); } } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.java index 06b77156..196172b5 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -19,6 +19,8 @@ package io.spring.initializr.web.mapper; import java.util.List; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.spring.initializr.generator.version.Version.Format; +import io.spring.initializr.generator.version.VersionRange; import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.Type; @@ -55,8 +57,8 @@ public class InitializrMetadataV21JsonMapper extends InitializrMetadataV2JsonMap @Override protected ObjectNode mapDependency(Dependency dependency) { ObjectNode content = mapValue(dependency); - if (dependency.getCompatibilityRange() != null) { - content.put("versionRange", dependency.getCompatibilityRange()); + if (dependency.getRange() != null) { + content.put("versionRange", formatVersionRange(dependency.getRange())); } if (dependency.getLinks() != null && !dependency.getLinks().isEmpty()) { content.set("_links", LinkMapper.mapLinks(dependency.getLinks())); @@ -64,6 +66,10 @@ public class InitializrMetadataV21JsonMapper extends InitializrMetadataV2JsonMap return content; } + protected String formatVersionRange(VersionRange versionRange) { + return versionRange.format(Format.V1).toRangeString(); + } + private ObjectNode dependenciesLink(String appUrl) { String uri = (appUrl != null) ? appUrl + "/dependencies" : "/dependencies"; UriTemplate uriTemplate = UriTemplate.of(uri, this.dependenciesVariables); diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapper.java new file mode 100644 index 00000000..c1d34122 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapper.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.web.mapper; + +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.generator.version.VersionRange; + +/** + * A {@link InitializrMetadataJsonMapper} handling the metadata format for v2.2 + *

+ * Version 2.2 adds support for {@linkplain Version.Format#V2 SemVer version format}. Any + * previous version formats versions to {@link Version.Format#V1}. + * + * @author Stephane Nicoll + */ +public class InitializrMetadataV22JsonMapper extends InitializrMetadataV21JsonMapper { + + @Override + protected String formatVersion(String versionId) { + return versionId; + } + + @Override + protected String formatVersionRange(VersionRange versionRange) { + return versionRange.toRangeString(); + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.java index c0864fb7..4d99813b 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV2JsonMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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,12 +17,16 @@ package io.spring.initializr.web.mapper; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.generator.version.Version.Format; +import io.spring.initializr.generator.version.VersionParser; import io.spring.initializr.metadata.DefaultMetadataElement; import io.spring.initializr.metadata.DependenciesCapability; import io.spring.initializr.metadata.Dependency; @@ -79,7 +83,7 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap singleSelect(delegate, metadata.getPackagings()); singleSelect(delegate, metadata.getJavaVersions()); singleSelect(delegate, metadata.getLanguages()); - singleSelect(delegate, metadata.getBootVersions()); + singleSelect(delegate, metadata.getBootVersions(), this::mapVersionMetadata); text(delegate, metadata.getGroupId()); text(delegate, metadata.getArtifactId()); text(delegate, metadata.getVersion()); @@ -133,6 +137,11 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap } protected void singleSelect(ObjectNode parent, SingleSelectCapability capability) { + singleSelect(parent, capability, this::mapValue); + } + + protected void singleSelect(ObjectNode parent, SingleSelectCapability capability, + Function valueMapper) { ObjectNode single = nodeFactory.objectNode(); single.put("type", capability.getType().getName()); DefaultMetadataElement defaultType = capability.getDefault(); @@ -140,7 +149,7 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap single.put("default", defaultType.getId()); } ArrayNode values = nodeFactory.arrayNode(); - values.addAll(capability.getContent().stream().map(this::mapValue).collect(Collectors.toList())); + values.addAll(capability.getContent().stream().map(valueMapper).collect(Collectors.toList())); single.set("values", values); parent.set(capability.getId(), single); } @@ -189,6 +198,18 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap return result; } + private ObjectNode mapVersionMetadata(MetadataElement value) { + ObjectNode result = nodeFactory.objectNode(); + result.put("id", formatVersion(value.getId())); + result.put("name", value.getName()); + return result; + } + + protected String formatVersion(String versionId) { + Version version = VersionParser.DEFAULT.safeParse(versionId); + return (version != null) ? version.format(Format.V1).toString() : versionId; + } + protected ObjectNode mapValue(MetadataElement value) { ObjectNode result = nodeFactory.objectNode(); result.put("id", value.getId()); diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java index 237e1692..c635394e 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -35,7 +35,12 @@ public enum InitializrMetadataVersion { * versions are compatible with it. Also provide a separate "dependencies" endpoint to * query dependencies metadata. */ - V2_1("application/vnd.initializr.v2.1+json"); + V2_1("application/vnd.initializr.v2.1+json"), + + /** + * Add support for SemVer compliant version format. + */ + V2_2("application/vnd.initializr.v2.2+json"); private final MediaType mediaType; diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestPlatformVersionTransformer.java b/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestPlatformVersionTransformer.java new file mode 100644 index 00000000..cc9299e6 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestPlatformVersionTransformer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.web.project; + +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.metadata.InitializrMetadata; + +/** + * A default {@link DefaultProjectRequestPlatformVersionTransformer} that uses configured + * ranges to format the version if necessary. + * + * @author Stephane Nicoll + */ +public class DefaultProjectRequestPlatformVersionTransformer implements ProjectRequestPlatformVersionTransformer { + + @Override + public Version transform(Version platformVersion, InitializrMetadata metadata) { + return metadata.getConfiguration().getEnv().getPlatform().formatPlatformVersion(platformVersion); + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java b/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java index d5b93870..a066b504 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java @@ -27,22 +27,38 @@ import io.spring.initializr.generator.project.ProjectDescription; import io.spring.initializr.generator.version.Version; import io.spring.initializr.metadata.DefaultMetadataElement; import io.spring.initializr.metadata.Dependency; -import io.spring.initializr.metadata.InitializrConfiguration.Env; +import io.spring.initializr.metadata.InitializrConfiguration.Platform; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.Type; import io.spring.initializr.metadata.support.MetadataBuildItemMapper; +import org.springframework.util.Assert; + /** * A default {@link ProjectRequestToDescriptionConverter} implementation that uses the * {@link InitializrMetadata metadata} to set default values for missing attributes if - * necessary. + * necessary. Transparently transform the platform version if necessary using a + * {@link ProjectRequestPlatformVersionTransformer}. * * @author Madhura Bhave * @author HaiTao Zhang + * @author Stephane Nicoll */ public class DefaultProjectRequestToDescriptionConverter implements ProjectRequestToDescriptionConverter { + private final ProjectRequestPlatformVersionTransformer platformVersionTransformer; + + public DefaultProjectRequestToDescriptionConverter() { + this((version, metadata) -> version); + } + + public DefaultProjectRequestToDescriptionConverter( + ProjectRequestPlatformVersionTransformer platformVersionTransformer) { + Assert.notNull(platformVersionTransformer, "PlatformVersionTransformer must not be null"); + this.platformVersionTransformer = platformVersionTransformer; + } + @Override public ProjectDescription convert(ProjectRequest request, InitializrMetadata metadata) { MutableProjectDescription description = new MutableProjectDescription(); @@ -60,9 +76,9 @@ public class DefaultProjectRequestToDescriptionConverter */ public void convert(ProjectRequest request, MutableProjectDescription description, InitializrMetadata metadata) { validate(request, metadata); - String springBootVersion = getSpringBootVersion(request, metadata); - List resolvedDependencies = getResolvedDependencies(request, springBootVersion, metadata); - validateDependencyRange(springBootVersion, resolvedDependencies); + Version platformVersion = getPlatformVersion(request, metadata); + List resolvedDependencies = getResolvedDependencies(request, platformVersion, metadata); + validateDependencyRange(platformVersion, resolvedDependencies); description.setApplicationName(request.getApplicationName()); description.setArtifactId(request.getArtifactId()); @@ -74,26 +90,26 @@ public class DefaultProjectRequestToDescriptionConverter description.setName(request.getName()); description.setPackageName(request.getPackageName()); description.setPackaging(Packaging.forId(request.getPackaging())); - description.setPlatformVersion(Version.parse(springBootVersion)); + description.setPlatformVersion(platformVersion); description.setVersion(request.getVersion()); resolvedDependencies.forEach((dependency) -> description.addDependency(dependency.getId(), MetadataBuildItemMapper.toDependency(dependency))); } private void validate(ProjectRequest request, InitializrMetadata metadata) { - validateSpringBootVersion(request, metadata); + validatePlatformVersion(request, metadata); validateType(request.getType(), metadata); validateLanguage(request.getLanguage(), metadata); validatePackaging(request.getPackaging(), metadata); validateDependencies(request, metadata); } - private void validateSpringBootVersion(ProjectRequest request, InitializrMetadata metadata) { - Version bootVersion = Version.safeParse(request.getBootVersion()); - Env env = metadata.getConfiguration().getEnv(); - if (bootVersion != null && !env.isCompatiblePlatformVersion(bootVersion)) { - throw new InvalidProjectRequestException("Invalid Spring Boot version '" + bootVersion - + "', Spring Boot compatibility range is " + env.determinePlatformCompatibilityRangeRequirement()); + private void validatePlatformVersion(ProjectRequest request, InitializrMetadata metadata) { + Version platformVersion = Version.safeParse(request.getBootVersion()); + Platform platform = metadata.getConfiguration().getEnv().getPlatform(); + if (platformVersion != null && !platform.isCompatibleVersion(platformVersion)) { + throw new InvalidProjectRequestException("Invalid Spring Boot version '" + platformVersion + + "', Spring Boot compatibility range is " + platform.determineCompatibilityRangeRequirement()); } } @@ -139,11 +155,11 @@ public class DefaultProjectRequestToDescriptionConverter }); } - private void validateDependencyRange(String springBootVersion, List resolvedDependencies) { + private void validateDependencyRange(Version platformVersion, List resolvedDependencies) { resolvedDependencies.forEach((dep) -> { - if (!dep.match(Version.parse(springBootVersion))) { - throw new InvalidProjectRequestException("Dependency '" + dep.getId() + "' is not compatible " - + "with Spring Boot " + springBootVersion); + if (!dep.match(platformVersion)) { + throw new InvalidProjectRequestException( + "Dependency '" + dep.getId() + "' is not compatible " + "with Spring Boot " + platformVersion); } }); } @@ -153,18 +169,19 @@ public class DefaultProjectRequestToDescriptionConverter return BuildSystem.forId(typeFromMetadata.getTags().get("build")); } - private String getSpringBootVersion(ProjectRequest request, InitializrMetadata metadata) { - return (request.getBootVersion() != null) ? request.getBootVersion() + private Version getPlatformVersion(ProjectRequest request, InitializrMetadata metadata) { + String versionText = (request.getBootVersion() != null) ? request.getBootVersion() : metadata.getBootVersions().getDefault().getId(); + Version version = Version.parse(versionText); + return this.platformVersionTransformer.transform(version, metadata); } - private List getResolvedDependencies(ProjectRequest request, String springBootVersion, + private List getResolvedDependencies(ProjectRequest request, Version platformVersion, InitializrMetadata metadata) { List depIds = request.getDependencies(); - Version requestedVersion = Version.parse(springBootVersion); return depIds.stream().map((it) -> { Dependency dependency = metadata.getDependencies().get(it); - return dependency.resolve(requestedVersion); + return dependency.resolve(platformVersion); }).collect(Collectors.toList()); } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequestPlatformVersionTransformer.java b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequestPlatformVersionTransformer.java new file mode 100644 index 00000000..45538bf0 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequestPlatformVersionTransformer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.web.project; + +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.metadata.InitializrMetadata; + +/** + * Strategy interface to transform the platform version of a {@link ProjectRequest}. + * + * @author Stephane Nicoll + */ +@FunctionalInterface +public interface ProjectRequestPlatformVersionTransformer { + + /** + * Transform the platform version of a {@link ProjectRequest} if necessary. + * @param platformVersion the candidate platform version + * @param metadata the metadata instance to use + * @return the platform version to use + */ + Version transform(Version platformVersion, InitializrMetadata metadata); + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java index f7b9a3bd..27633523 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -72,7 +72,9 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = Config.class) public abstract class AbstractInitializrIntegrationTests { - protected static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1.getMediaType(); + protected static final MediaType DEFAULT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1.getMediaType(); + + protected static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_2.getMediaType(); private static final ObjectMapper objectMapper = new ObjectMapper(); @@ -125,14 +127,23 @@ public abstract class AbstractInitializrIntegrationTests { } } - protected void validateCurrentMetadata(ResponseEntity response) { - validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); + protected void validateDefaultMetadata(ResponseEntity response) { + validateContentType(response, DEFAULT_METADATA_MEDIA_TYPE); + validateMetadata(response.getBody(), "2.1.0"); } - protected void validateCurrentMetadata(String json) { + protected void validateCurrentMetadata(ResponseEntity response) { + validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); + validateMetadata(response.getBody(), "2.2.0"); + } + + protected void validateDefaultMetadata(String json) { + validateMetadata(json, "2.1.0"); + } + + protected void validateMetadata(String json, String version) { try { - JSONObject expected = readMetadataJson("2.1.0"); + JSONObject expected = readMetadataJson(version); JSONAssert.assertEquals(expected, new JSONObject(json), JSONCompareMode.STRICT); } catch (JSONException ex) { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerIntegrationTests.java index d94af304..e73fb337 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -45,8 +45,8 @@ public class CommandLineMetadataControllerIntegrationTests extends AbstractIniti // make sure curl can still receive metadata with json void curlWithAcceptHeaderJson() { ResponseEntity response = invokeHome("curl/1.2.4", "application/json"); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); + validateContentType(response, AbstractInitializrIntegrationTests.DEFAULT_METADATA_MEDIA_TYPE); + validateDefaultMetadata(response.getBody()); } @Test @@ -65,8 +65,8 @@ public class CommandLineMetadataControllerIntegrationTests extends AbstractIniti // make sure curl can still receive metadata with json void httpieWithAcceptHeaderJson() { ResponseEntity response = invokeHome("HTTPie/0.8.0", "application/json"); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); + validateContentType(response, AbstractInitializrIntegrationTests.DEFAULT_METADATA_MEDIA_TYPE); + validateDefaultMetadata(response.getBody()); } @Test @@ -84,8 +84,8 @@ public class CommandLineMetadataControllerIntegrationTests extends AbstractIniti @Test void springBootCliReceivesJsonByDefault() { ResponseEntity response = invokeHome("SpringBootCli/1.2.0", "*/*"); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); + validateContentType(response, AbstractInitializrIntegrationTests.DEFAULT_METADATA_MEDIA_TYPE); + validateDefaultMetadata(response.getBody()); } @Test diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomEnvIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomEnvIntegrationTests.java index b0c07e7a..e6be8d59 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomEnvIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomEnvIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomVersionTransformerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomVersionTransformerIntegrationTests.java new file mode 100644 index 00000000..92294949 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomVersionTransformerIntegrationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.web.controller; + +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import org.junit.jupiter.api.Test; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ProjectGenerationController} with a custom platform + * version compatibility range. + * + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +@TestPropertySource(properties = "initializr.env.platform.v2-format-compatibility-range=2.4.0-M1") +class ProjectGenerationControllerCustomVersionTransformerIntegrationTests + extends AbstractInitializrControllerIntegrationTests { + + @Test + void projectGenerationInvokeProjectRequestVersionTransformer() { + ProjectStructure project = downloadZip("/starter.zip?bootVersion=2.4.0.RELEASE"); + assertThat(project).mavenBuild().hasParent("org.springframework.boot", "spring-boot-starter-parent", "2.4.0"); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerCustomDefaultsIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerCustomDefaultsIntegrationTests.java index b01a53be..0dfc64fa 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerCustomDefaultsIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerCustomDefaultsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -84,24 +84,24 @@ class ProjectMetadataControllerCustomDefaultsIntegrationTests extends AbstractFu @Test void metadataClientEndpoint() { ResponseEntity response = execute("/metadata/client", String.class, null, "application/json"); - validateCurrentMetadata(response); + validateDefaultMetadata(response); } @Test void noBootVersion() throws JSONException { ResponseEntity response = execute("/dependencies", String.class, null, "application/json"); assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); + validateContentType(response, DEFAULT_METADATA_MEDIA_TYPE); validateDependenciesOutput("2.1.4", response.getBody()); } @Test void filteredDependencies() throws JSONException { - ResponseEntity response = execute("/dependencies?bootVersion=2.2.1.RELEASE", String.class, null, + ResponseEntity response = execute("/dependencies?bootVersion=2.4.1.RELEASE", String.class, null, "application/json"); assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); - validateDependenciesOutput("2.2.1", response.getBody()); + validateContentType(response, DEFAULT_METADATA_MEDIA_TYPE); + validateDependenciesOutput("2.4.1", response.getBody()); } protected void validateDependenciesOutput(String version, String actual) throws JSONException { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java index 55472cba..9a9df149 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -43,7 +43,7 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ void metadataWithNoAcceptHeader() { // rest template sets application/json by default ResponseEntity response = invokeHome(null, "*/*"); - validateCurrentMetadata(response); + validateDefaultMetadata(response); } @Test @@ -60,6 +60,18 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ validateMetadata(response, InitializrMetadataVersion.V2.getMediaType(), "2.0.0", JSONCompareMode.STRICT); } + @Test + void metadataWithV21AcceptHeader() { + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.1+json"); + validateMetadata(response, InitializrMetadataVersion.V2_1.getMediaType(), "2.1.0", JSONCompareMode.STRICT); + } + + @Test + void metadataWithV22AcceptHeader() { + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.2+json"); + validateMetadata(response, InitializrMetadataVersion.V2_2.getMediaType(), "2.2.0", JSONCompareMode.STRICT); + } + @Test void metadataWithInvalidPlatformVersion() { try { @@ -76,18 +88,18 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ void metadataWithCurrentAcceptHeader() { getRequests().setFields("_links.maven-project", "dependencies.values[0]", "type.values[0]", "javaVersion.values[0]", "packaging.values[0]", "bootVersion.values[0]", "language.values[0]"); - ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.1+json"); + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.2+json"); assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); + validateMetadata(response.getBody(), "2.2.0"); } @Test void metadataWithSeveralAcceptHeader() { - ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.1+json", + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.2+json", "application/vnd.initializr.v2+json"); validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); + validateCurrentMetadata(response); } @Test @@ -95,7 +107,7 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ ResponseEntity response = invokeHome(null, "application/hal+json"); assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); validateContentType(response, ProjectMetadataController.HAL_JSON_CONTENT_TYPE); - validateCurrentMetadata(response.getBody()); + validateDefaultMetadata(response.getBody()); } @Test @@ -117,13 +129,13 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ @Test void unknownAgentReceivesJsonByDefault() { ResponseEntity response = invokeHome("foo/1.0", "*/*"); - validateCurrentMetadata(response); + validateDefaultMetadata(response); } @Test // Test that the current output is exactly what we expect void validateCurrentProjectMetadata() { - validateCurrentMetadata(getMetadataJson()); + validateDefaultMetadata(getMetadataJson()); } private String getMetadataJson() { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.java b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapperTests.java similarity index 61% rename from initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapperTests.java index 4e00c79a..3e508b5d 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataJsonMapperTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV21JsonMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -18,8 +18,10 @@ package io.spring.initializr.web.mapper; import java.io.IOException; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.spring.initializr.generator.test.InitializrMetadataTestBuilder; import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.InitializrMetadata; @@ -29,13 +31,15 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** + * Tests for {@link InitializrMetadataV21JsonMapper}. + * * @author Stephane Nicoll */ -class InitializrMetadataJsonMapperTests { +class InitializrMetadataV21JsonMapperTests { private static final ObjectMapper objectMapper = new ObjectMapper(); - private final InitializrMetadataJsonMapper jsonMapper = new InitializrMetadataV21JsonMapper(); + private final InitializrMetadataV21JsonMapper jsonMapper = new InitializrMetadataV21JsonMapper(); @Test void withNoAppUrl() throws IOException { @@ -74,6 +78,42 @@ class InitializrMetadataJsonMapperTests { assertThat(second).isGreaterThan(0); } + @Test + void versionRangesUsingSemVerUseBackwardCompatibleFormat() { + Dependency dependency = Dependency.withId("test"); + dependency.setCompatibilityRange("[1.1.1-RC1,1.2.0-M1)"); + dependency.resolve(); + ObjectNode node = this.jsonMapper.mapDependency(dependency); + assertThat(node.get("versionRange").textValue()).isEqualTo("[1.1.1.RC1,1.2.0.M1)"); + } + + @Test + void versionRangesUsingSemVerSnapshotReplacedByBackwardCompatibleSnapshotQualifier() { + Dependency dependency = Dependency.withId("test"); + dependency.setCompatibilityRange("1.2.0-SNAPSHOT"); + dependency.resolve(); + ObjectNode node = this.jsonMapper.mapDependency(dependency); + assertThat(node.get("versionRange").textValue()).isEqualTo("1.2.0.BUILD-SNAPSHOT"); + } + + @Test + void platformVersionUsingSemVerUseBackwardCompatibleFormat() throws JsonProcessingException { + InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("2.5.0-SNAPSHOT", false) + .addBootVersion("2.5.0-M2", false).addBootVersion("2.4.2", true).build(); + String json = this.jsonMapper.write(metadata, null); + JsonNode result = objectMapper.readTree(json); + JsonNode versions = result.get("bootVersion").get("values"); + assertThat(versions).hasSize(3); + assertVersionMetadata(versions.get(0), "2.5.0.BUILD-SNAPSHOT", "2.5.0-SNAPSHOT"); + assertVersionMetadata(versions.get(1), "2.5.0.M2", "2.5.0-M2"); + assertVersionMetadata(versions.get(2), "2.4.2.RELEASE", "2.4.2"); + } + + private void assertVersionMetadata(JsonNode node, String id, String name) { + assertThat(node.get("id").textValue()).isEqualTo(id); + assertThat(node.get("name").textValue()).isEqualTo(name); + } + private Object get(JsonNode result, String path) { String[] nodes = path.split("\\."); for (int i = 0; i < nodes.length - 1; i++) { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapperTests.java b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapperTests.java new file mode 100755 index 00000000..0c5b64fb --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapperTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.web.mapper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.spring.initializr.generator.test.InitializrMetadataTestBuilder; +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.InitializrMetadata; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link InitializrMetadataV22JsonMapper}. + * + * @author Stephane Nicoll + */ +class InitializrMetadataV22JsonMapperTests { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private final InitializrMetadataV22JsonMapper jsonMapper = new InitializrMetadataV22JsonMapper(); + + @Test + void versionRangesUsingSemVerIsNotChanged() { + Dependency dependency = Dependency.withId("test"); + dependency.setCompatibilityRange("[1.1.1-RC1,1.2.0-M1)"); + dependency.resolve(); + ObjectNode node = this.jsonMapper.mapDependency(dependency); + assertThat(node.get("versionRange").textValue()).isEqualTo("[1.1.1-RC1,1.2.0-M1)"); + } + + @Test + void versionRangesUsingSemVerSnapshotIsNotChanged() { + Dependency dependency = Dependency.withId("test"); + dependency.setCompatibilityRange("1.2.0-SNAPSHOT"); + dependency.resolve(); + ObjectNode node = this.jsonMapper.mapDependency(dependency); + assertThat(node.get("versionRange").textValue()).isEqualTo("1.2.0-SNAPSHOT"); + } + + @Test + void platformVersionUsingSemVerUIsNotChanged() throws JsonProcessingException { + InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("2.5.0-SNAPSHOT", false) + .addBootVersion("2.5.0-M2", false).addBootVersion("2.4.2", true).build(); + String json = this.jsonMapper.write(metadata, null); + JsonNode result = objectMapper.readTree(json); + JsonNode versions = result.get("bootVersion").get("values"); + assertThat(versions).hasSize(3); + assertVersionMetadata(versions.get(0), "2.5.0-SNAPSHOT", "2.5.0-SNAPSHOT"); + assertVersionMetadata(versions.get(1), "2.5.0-M2", "2.5.0-M2"); + assertVersionMetadata(versions.get(2), "2.4.2", "2.4.2"); + } + + private void assertVersionMetadata(JsonNode node, String id, String name) { + assertThat(node.get("id").textValue()).isEqualTo(id); + assertThat(node.get("name").textValue()).isEqualTo(name); + } + + private Object get(JsonNode result, String path) { + String[] nodes = path.split("\\."); + for (int i = 0; i < nodes.length - 1; i++) { + String node = nodes[i]; + result = result.path(node); + } + return result.get(nodes[nodes.length - 1]).textValue(); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestPlatformVersionTransformerTests.java b/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestPlatformVersionTransformerTests.java new file mode 100644 index 00000000..886f52f8 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestPlatformVersionTransformerTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.web.project; + +import io.spring.initializr.generator.test.InitializrMetadataTestBuilder; +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.metadata.InitializrMetadata; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DefaultProjectRequestPlatformVersionTransformer}. + * + * @author Stephane Nicoll + */ +class DefaultProjectRequestPlatformVersionTransformerTests { + + private final DefaultProjectRequestPlatformVersionTransformer transformer = new DefaultProjectRequestPlatformVersionTransformer(); + + @Test + void formatV1WhenV2IsExpected() { + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() + .setPlatformVersionFormatCompatibilityRange("[2.0.0.RELEASE,2.4.0-M1)", "2.4.0-M1").build(); + assertThat(this.transformer.transform(Version.parse("2.4.0.RELEASE"), metadata)).hasToString("2.4.0"); + } + + @Test + void formatV1WhenV1IsExpected() { + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() + .setPlatformVersionFormatCompatibilityRange("[2.0.0.RELEASE,2.4.0-M1)", "2.4.0-M1").build(); + Version version = Version.parse("2.2.0.RELEASE"); + assertThat(this.transformer.transform(version, metadata)).isSameAs(version); + } + + @Test + void formatV2WhenV1IsExpected() { + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() + .setPlatformVersionFormatCompatibilityRange("[2.0.0.RELEASE,2.4.0-M1)", "2.4.0-M1").build(); + assertThat(this.transformer.transform(Version.parse("2.3.0-SNAPSHOT"), metadata)) + .hasToString("2.3.0.BUILD-SNAPSHOT"); + } + + @Test + void formatV2WhenV2IsExpected() { + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() + .setPlatformVersionFormatCompatibilityRange("[2.0.0.RELEASE,2.4.0-M1)", "2.4.0-M1").build(); + Version version = Version.parse("2.4.0"); + assertThat(this.transformer.transform(version, metadata)).isSameAs(version); + } + + @Test + void formatV1WhenNoRangeIsConfigured() { + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults().build(); + Version version = Version.parse("2.4.0.RELEASE"); + assertThat(this.transformer.transform(version, metadata)).isSameAs(version); + } + + @Test + void formatV2WhenNoRangeIsConfigured() { + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults().build(); + Version version = Version.parse("2.2.0-SNAPSHOT"); + assertThat(this.transformer.transform(version, metadata)).isSameAs(version); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverterTests.java b/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverterTests.java index bd2b1902..f6d42b5f 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverterTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -30,6 +30,9 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link DefaultProjectRequestToDescriptionConverter}. @@ -74,6 +77,19 @@ class DefaultProjectRequestToDescriptionConverterTests { .isEqualTo(Version.parse("1.5.9.RELEASE")); } + @Test + void convertShouldCallProjectRequestVersionTransformer() { + ProjectRequestPlatformVersionTransformer transformer = mock(ProjectRequestPlatformVersionTransformer.class); + Version v1Format = Version.parse("2.4.0.RELEASE"); + given(transformer.transform(v1Format, this.metadata)).willReturn(Version.parse("2.4.0")); + ProjectRequest request = createProjectRequest(); + request.setBootVersion("2.4.0.RELEASE"); + ProjectDescription description = new DefaultProjectRequestToDescriptionConverter(transformer).convert(request, + this.metadata); + assertThat(description.getPlatformVersion()).hasToString("2.4.0"); + verify(transformer).transform(v1Format, this.metadata); + } + @Test void convertWhenSpringBootVersionInvalidShouldThrowException() { this.metadata = InitializrMetadataTestBuilder.withDefaults() diff --git a/initializr-web/src/test/resources/application-test-custom-env.yml b/initializr-web/src/test/resources/application-test-custom-env.yml index 8676d91f..de11a9cb 100644 --- a/initializr-web/src/test/resources/application-test-custom-env.yml +++ b/initializr-web/src/test/resources/application-test-custom-env.yml @@ -5,6 +5,7 @@ initializr: fallbackApplicationName: FooBarApplication invalidApplicationNames: - InvalidApplication - platform-compatibility-range: "2.0.0.RELEASE" kotlin: - default-version: 1.0.0-beta-2423 \ No newline at end of file + default-version: 1.0.0-beta-2423 + platform: + compatibility-range: "2.0.0.RELEASE" diff --git a/initializr-web/src/test/resources/application-test-default.yml b/initializr-web/src/test/resources/application-test-default.yml index 948dc2b0..ca97aaf8 100644 --- a/initializr-web/src/test/resources/application-test-default.yml +++ b/initializr-web/src/test/resources/application-test-default.yml @@ -90,7 +90,7 @@ initializr: id: org.acme:bur version: 2.1.0 scope: test - compatibilityRange: "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)" + compatibilityRange: "[2.1.4.RELEASE,2.4.0-SNAPSHOT)" - name: My API id : my-api groupId: org.acme @@ -152,7 +152,7 @@ initializr: default: false bootVersions: - name : Latest SNAPSHOT - id: 2.2.0.BUILD-SNAPSHOT + id: 2.4.0-SNAPSHOT default: false - name: 2.1.4 id: 2.1.4.RELEASE diff --git a/initializr-web/src/test/resources/metadata/config/test-default.json b/initializr-web/src/test/resources/metadata/config/test-default.json index 142d1e2c..da57c7a6 100644 --- a/initializr-web/src/test/resources/metadata/config/test-default.json +++ b/initializr-web/src/test/resources/metadata/config/test-default.json @@ -10,7 +10,7 @@ "content": [ { "default": false, - "id": "2.2.0.BUILD-SNAPSHOT", + "id": "2.4.0-SNAPSHOT", "name": "Latest SNAPSHOT" }, { @@ -33,7 +33,6 @@ "artifactRepository": "https://repo.spring.io/release/", "fallbackApplicationName": "Application", "forceSsl": false, - "platformCompatibilityRange": null, "gradle": { "dependencyManagementPluginVersion": "1.0.0.RELEASE" }, @@ -58,6 +57,11 @@ "includeSpringBootBom": false } }, + "platform": { + "compatibilityRange": null, + "v1FormatCompatibilityRange": null, + "v2FormatCompatibilityRange": null + }, "googleAnalyticsTrackingCode": null, "invalidApplicationNames": [ "SpringApplication", @@ -230,7 +234,7 @@ "name": "Bur", "scope": "test", "version": "2.1.0", - "compatibilityRange": "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)" + "compatibilityRange": "[2.1.4.RELEASE,2.4.0-SNAPSHOT)" }, { "starter": true, diff --git a/initializr-web/src/test/resources/metadata/dependencies/test-dependencies-2.2.1.json b/initializr-web/src/test/resources/metadata/dependencies/test-dependencies-2.4.1.json similarity index 97% rename from initializr-web/src/test/resources/metadata/dependencies/test-dependencies-2.2.1.json rename to initializr-web/src/test/resources/metadata/dependencies/test-dependencies-2.4.1.json index 565656e6..596f7148 100644 --- a/initializr-web/src/test/resources/metadata/dependencies/test-dependencies-2.2.1.json +++ b/initializr-web/src/test/resources/metadata/dependencies/test-dependencies-2.4.1.json @@ -1,5 +1,5 @@ { - "bootVersion": "2.2.1.RELEASE", + "bootVersion": "2.4.1.RELEASE", "repositories": { "my-api-repo-2": { "name": "repo2", diff --git a/initializr-web/src/test/resources/metadata/test-default-2.0.0-ssl.json b/initializr-web/src/test/resources/metadata/test-default-2.0.0-ssl.json index 3f1b431e..5ed2dcb5 100644 --- a/initializr-web/src/test/resources/metadata/test-default-2.0.0-ssl.json +++ b/initializr-web/src/test/resources/metadata/test-default-2.0.0-ssl.json @@ -154,7 +154,7 @@ "default": "2.1.4.RELEASE", "values": [ { - "id": "2.2.0.BUILD-SNAPSHOT", + "id": "2.4.0.BUILD-SNAPSHOT", "name": "Latest SNAPSHOT" }, { diff --git a/initializr-web/src/test/resources/metadata/test-default-2.0.0.json b/initializr-web/src/test/resources/metadata/test-default-2.0.0.json index ed3c0989..8b2c3263 100644 --- a/initializr-web/src/test/resources/metadata/test-default-2.0.0.json +++ b/initializr-web/src/test/resources/metadata/test-default-2.0.0.json @@ -154,7 +154,7 @@ "default": "2.1.4.RELEASE", "values": [ { - "id": "2.2.0.BUILD-SNAPSHOT", + "id": "2.4.0.BUILD-SNAPSHOT", "name": "Latest SNAPSHOT" }, { diff --git a/initializr-web/src/test/resources/metadata/test-default-2.1.0-ssl.json b/initializr-web/src/test/resources/metadata/test-default-2.1.0-ssl.json index aaf50df4..d005ee74 100644 --- a/initializr-web/src/test/resources/metadata/test-default-2.1.0-ssl.json +++ b/initializr-web/src/test/resources/metadata/test-default-2.1.0-ssl.json @@ -85,7 +85,7 @@ { "id": "org.acme:bur", "name": "Bur", - "versionRange": "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)" + "versionRange": "[2.1.4.RELEASE,2.4.0.BUILD-SNAPSHOT)" }, { "id": "my-api", @@ -192,7 +192,7 @@ "default": "2.1.4.RELEASE", "values": [ { - "id": "2.2.0.BUILD-SNAPSHOT", + "id": "2.4.0.BUILD-SNAPSHOT", "name": "Latest SNAPSHOT" }, { diff --git a/initializr-web/src/test/resources/metadata/test-default-2.1.0.json b/initializr-web/src/test/resources/metadata/test-default-2.1.0.json index 7d0abdf0..150752ef 100644 --- a/initializr-web/src/test/resources/metadata/test-default-2.1.0.json +++ b/initializr-web/src/test/resources/metadata/test-default-2.1.0.json @@ -85,7 +85,7 @@ { "id": "org.acme:bur", "name": "Bur", - "versionRange": "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)" + "versionRange": "[2.1.4.RELEASE,2.4.0.BUILD-SNAPSHOT)" }, { "id": "my-api", @@ -192,7 +192,7 @@ "default": "2.1.4.RELEASE", "values": [ { - "id": "2.2.0.BUILD-SNAPSHOT", + "id": "2.4.0.BUILD-SNAPSHOT", "name": "Latest SNAPSHOT" }, { diff --git a/initializr-web/src/test/resources/metadata/test-default-2.2.0.json b/initializr-web/src/test/resources/metadata/test-default-2.2.0.json new file mode 100644 index 00000000..df76991c --- /dev/null +++ b/initializr-web/src/test/resources/metadata/test-default-2.2.0.json @@ -0,0 +1,232 @@ +{ + "_links": { + "dependencies": { + "href": "http://@host@/dependencies{?bootVersion}", + "templated": true + }, + "maven-build": { + "href": "http://@host@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}", + "templated": true + }, + "maven-project": { + "href": "http://@host@/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}", + "templated": true + }, + "gradle-build": { + "href": "http://@host@/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}", + "templated": true + }, + "gradle-project": { + "href": "http://@host@/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}", + "templated": true + } + }, + "dependencies": { + "type": "hierarchical-multi-select", + "values": [ + { + "name": "Core", + "values": [ + { + "id": "web", + "name": "Web", + "description": "Web dependency description", + "_links": { + "guide": { + "href": "https://example.com/guide", + "title": "Building a RESTful Web Service" + }, + "reference": { + "href": "https://example.com/doc" + } + } + }, + { + "id": "security", + "name": "Security" + }, + { + "id": "data-jpa", + "name": "Data JPA" + } + ] + }, + { + "name": "Other", + "values": [ + { + "id": "org.acme:foo", + "name": "Foo", + "_links": { + "guide": [ + { + "href": "https://example.com/guide1" + }, + { + "href": "https://example.com/guide2", + "title": "Some guide for foo" + } + ], + "reference": { + "href": "https://example.com/{bootVersion}/doc", + "templated": true + } + } + }, + { + "id": "org.acme:bar", + "name": "Bar" + }, + { + "id": "org.acme:biz", + "name": "Biz", + "versionRange": "2.2.0.BUILD-SNAPSHOT" + }, + { + "id": "org.acme:bur", + "name": "Bur", + "versionRange": "[2.1.4.RELEASE,2.4.0-SNAPSHOT)" + }, + { + "id": "my-api", + "name": "My API" + } + ] + } + ] + }, + "type": { + "type": "action", + "default": "maven-project", + "values": [ + { + "id": "maven-build", + "name": "Maven POM", + "action": "/pom.xml", + "tags": { + "build": "maven", + "format": "build" + } + }, + { + "id": "maven-project", + "name": "Maven Project", + "action": "/starter.zip", + "tags": { + "build": "maven", + "format": "project" + } + }, + { + "id": "gradle-build", + "name": "Gradle Config", + "action": "/build.gradle", + "tags": { + "build": "gradle", + "format": "build" + } + }, + { + "id": "gradle-project", + "name": "Gradle Project", + "action": "/starter.zip", + "tags": { + "build": "gradle", + "format": "project" + } + } + ] + }, + "packaging": { + "type": "single-select", + "default": "jar", + "values": [ + { + "id": "jar", + "name": "Jar" + }, + { + "id": "war", + "name": "War" + } + ] + }, + "javaVersion": { + "type": "single-select", + "default": "1.8", + "values": [ + { + "id": "1.6", + "name": "1.6" + }, + { + "id": "1.7", + "name": "1.7" + }, + { + "id": "1.8", + "name": "1.8" + } + ] + }, + "language": { + "type": "single-select", + "default": "java", + "values": [ + { + "id": "groovy", + "name": "Groovy" + }, + { + "id": "java", + "name": "Java" + }, + { + "id": "kotlin", + "name": "Kotlin" + } + ] + }, + "bootVersion": { + "type": "single-select", + "default": "2.1.4.RELEASE", + "values": [ + { + "id": "2.4.0-SNAPSHOT", + "name": "Latest SNAPSHOT" + }, + { + "id": "2.1.4.RELEASE", + "name": "2.1.4" + }, + { + "id": "1.5.17.RELEASE", + "name": "1.5.17" + } + ] + }, + "groupId": { + "type": "text", + "default": "com.example" + }, + "artifactId": { + "type": "text", + "default": "demo" + }, + "version": { + "type": "text", + "default": "0.0.1-SNAPSHOT" + }, + "name": { + "type": "text", + "default": "demo" + }, + "description": { + "type": "text", + "default": "Demo project for Spring Boot" + }, + "packageName": { + "type": "text", + "default": "com.example.demo" + } +} \ No newline at end of file