Merge branch 'gh-1092'

Closes gh-1092
This commit is contained in:
Stephane Nicoll
2020-06-03 14:57:21 +02:00
37 changed files with 1166 additions and 146 deletions

View File

@@ -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 <<howto-link-boot-version,linking versions>> for more examples
and idioms.
and idioms. See also how the <<howto-platform-version-format,platform version format>> 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

View File

@@ -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

View File

@@ -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).

View File

@@ -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;
}

View File

@@ -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.
* <p>
* For example: {@code 1.2.0.RC1} is the first release candidate of 1.2.0 and
* {@code 1.5.0.M4} is the fourth milestone of 1.5.0. The special {@code RELEASE}
* qualifier indicates a final release (a.k.a. GA)
* {@code 1.5.0-M4} is the fourth milestone of 1.5.0. The special {@code RELEASE}
* qualifier indicates a final release (a.k.a. GA).
* <p>
* The main purpose of parsing a version is to compare it with another version, see
* {@link Comparable}.
* 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).
* <p>
* 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<Version> {
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<String, String> 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<Version> {
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<Version> {
}
/**
* 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<Qualifier> {
static final String RELEASE = "RELEASE";

View File

@@ -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;
* <ul>
* <li>"[1.2.0.RELEASE,1.3.0.RELEASE)" version 1.2.0 and any version after this, up to,
* but not including, version 1.3.0.</li>
* <li>"(2.0.0.RELEASE,3.2.0.RELEASE]" any version after 2.0.0 up to and including version
* 3.2.0.</li>
* <li>"1.4.5.RELEASE", version 1.4.5 and all later versions.</li>
* <li>"(2.0.0,3.2.0]" any version after 2.0.0 up to and including version 3.2.0.</li>
* <li>"2.5.0-M1", the first milestone of 2.5.0 and any version after that.</li>
* </ul>
*
* @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;
}

View File

@@ -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<String> match(String range) {
return match(range, new VersionParser(Collections.emptyList()));
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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<ProjectRequest> projectGenerationController(
InitializrMetadataProvider metadataProvider, ApplicationContext applicationContext) {
InitializrMetadataProvider metadataProvider,
ObjectProvider<ProjectRequestPlatformVersionTransformer> platformVersionTransformer,
ApplicationContext applicationContext) {
ProjectGenerationInvoker<ProjectRequest> projectGenerationInvoker = new ProjectGenerationInvoker<>(
applicationContext, new DefaultProjectRequestToDescriptionConverter());
applicationContext, new DefaultProjectRequestToDescriptionConverter(platformVersionTransformer
.getIfAvailable(DefaultProjectRequestPlatformVersionTransformer::new)));
return new DefaultProjectGenerationController(metadataProvider, projectGenerationInvoker);
}

View File

@@ -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<String> serviceCapabilitiesV22() {
return serviceCapabilitiesFor(InitializrMetadataVersion.V2_2);
}
@RequestMapping(path = { "/", "/metadata/client" },
produces = { "application/vnd.initializr.v2.1+json", "application/json" })
public ResponseEntity<String> 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();
}
}

View File

@@ -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);

View File

@@ -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
* <p>
* 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();
}
}

View File

@@ -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<MetadataElement, ObjectNode> 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());

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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<ProjectRequest> {
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<Dependency> resolvedDependencies = getResolvedDependencies(request, springBootVersion, metadata);
validateDependencyRange(springBootVersion, resolvedDependencies);
Version platformVersion = getPlatformVersion(request, metadata);
List<Dependency> 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<Dependency> resolvedDependencies) {
private void validateDependencyRange(Version platformVersion, List<Dependency> 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<Dependency> getResolvedDependencies(ProjectRequest request, String springBootVersion,
private List<Dependency> getResolvedDependencies(ProjectRequest request, Version platformVersion,
InitializrMetadata metadata) {
List<String> 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());
}

View File

@@ -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);
}

View File

@@ -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<String> response) {
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE);
validateCurrentMetadata(response.getBody());
protected void validateDefaultMetadata(ResponseEntity<String> response) {
validateContentType(response, DEFAULT_METADATA_MEDIA_TYPE);
validateMetadata(response.getBody(), "2.1.0");
}
protected void validateCurrentMetadata(String json) {
protected void validateCurrentMetadata(ResponseEntity<String> 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) {

View File

@@ -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<String> 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<String> 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<String> 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

View File

@@ -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.

View File

@@ -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");
}
}

View File

@@ -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<String> response = execute("/metadata/client", String.class, null, "application/json");
validateCurrentMetadata(response);
validateDefaultMetadata(response);
}
@Test
void noBootVersion() throws JSONException {
ResponseEntity<String> 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<String> response = execute("/dependencies?bootVersion=2.2.1.RELEASE", String.class, null,
ResponseEntity<String> 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 {

View File

@@ -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<String> 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<String> response = invokeHome(null, "application/vnd.initializr.v2.1+json");
validateMetadata(response, InitializrMetadataVersion.V2_1.getMediaType(), "2.1.0", JSONCompareMode.STRICT);
}
@Test
void metadataWithV22AcceptHeader() {
ResponseEntity<String> 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<String> response = invokeHome(null, "application/vnd.initializr.v2.1+json");
ResponseEntity<String> 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<String> response = invokeHome(null, "application/vnd.initializr.v2.1+json",
ResponseEntity<String> 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<String> 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<String> 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() {

View File

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

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -5,6 +5,7 @@ initializr:
fallbackApplicationName: FooBarApplication
invalidApplicationNames:
- InvalidApplication
platform-compatibility-range: "2.0.0.RELEASE"
kotlin:
default-version: 1.0.0-beta-2423
default-version: 1.0.0-beta-2423
platform:
compatibility-range: "2.0.0.RELEASE"

View File

@@ -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

View File

@@ -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,

View File

@@ -1,5 +1,5 @@
{
"bootVersion": "2.2.1.RELEASE",
"bootVersion": "2.4.1.RELEASE",
"repositories": {
"my-api-repo-2": {
"name": "repo2",

View File

@@ -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"
},
{

View File

@@ -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"
},
{

View File

@@ -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"
},
{

View File

@@ -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"
},
{

View File

@@ -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"
}
}