From 41f844a3adddd17e9800c99fc544754c4260de7a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 2 Jun 2020 18:06:23 +0200 Subject: [PATCH] Serve version format that is backward compatible This commit makes sure the metadata format uses a backward compatible version format even if the new format is used. It also introduces a new metadata version (2.2) that can be used by clients that support the new version format. See gh-1092 --- .../src/main/asciidoc/metadata-format.adoc | 8 +- .../src/main/asciidoc/using-the-stubs.adoc | 2 +- .../controller/ProjectMetadataController.java | 10 +- .../InitializrMetadataV21JsonMapper.java | 12 +- .../InitializrMetadataV22JsonMapper.java | 42 ++++ .../InitializrMetadataV2JsonMapper.java | 27 +- .../web/mapper/InitializrMetadataVersion.java | 9 +- .../AbstractInitializrIntegrationTests.java | 25 +- ...ineMetadataControllerIntegrationTests.java | 14 +- ...trollerCustomDefaultsIntegrationTests.java | 12 +- ...ectMetadataControllerIntegrationTests.java | 30 ++- ...InitializrMetadataV21JsonMapperTests.java} | 46 +++- .../InitializrMetadataV22JsonMapperTests.java | 86 +++++++ .../resources/application-test-default.yml | 4 +- .../metadata/config/test-default.json | 4 +- ....2.1.json => test-dependencies-2.4.1.json} | 2 +- .../metadata/test-default-2.0.0-ssl.json | 2 +- .../metadata/test-default-2.0.0.json | 2 +- .../metadata/test-default-2.1.0-ssl.json | 4 +- .../metadata/test-default-2.1.0.json | 4 +- .../metadata/test-default-2.2.0.json | 232 ++++++++++++++++++ 21 files changed, 523 insertions(+), 54 deletions(-) create mode 100644 initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapper.java rename initializr-web/src/test/java/io/spring/initializr/web/mapper/{InitializrMetadataJsonMapperTests.java => InitializrMetadataV21JsonMapperTests.java} (61%) create mode 100755 initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV22JsonMapperTests.java rename initializr-web/src/test/resources/metadata/dependencies/{test-dependencies-2.2.1.json => test-dependencies-2.4.1.json} (97%) create mode 100644 initializr-web/src/test/resources/metadata/test-default-2.2.0.json 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-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..c5ec6691 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 @@ -31,6 +31,7 @@ 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() { @@ -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/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/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/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..f81d0648 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" }, { @@ -230,7 +230,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