Skip to content

Commit 9d0e50c

Browse files
committed
Support of spring initializr meta-data v2.1
Update the `init` command to support the latest meta-data format. Recent Spring Initializr version also supports Spring Boot CLI now and generates a textual service capabilities when requested. The command no longer generates the capabilities of the service unless said service does not support it. Closes spring-projectsgh-2515
1 parent 9af3045 commit 9d0e50c

File tree

8 files changed

+343
-45
lines changed

8 files changed

+343
-45
lines changed

spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ class InitializrService {
4848

4949
private static final Charset UTF_8 = Charset.forName("UTF-8");
5050

51+
/**
52+
* Accept header to use to retrieve the json meta-data.
53+
*/
54+
public static final String ACCEPT_META_DATA =
55+
"application/vnd.initializr.v2.1+json,application/vnd.initializr.v2+json";
56+
57+
/**
58+
* Accept header to use to retrieve the service capabilities of the service. If the
59+
* service does not offer such feature, the json meta-data are retrieved instead.
60+
*/
61+
public static final String ACCEPT_SERVICE_CAPABILITIES =
62+
"text/plain," + ACCEPT_META_DATA;
63+
5164
/**
5265
* Late binding HTTP client.
5366
*/
@@ -80,12 +93,7 @@ public ProjectGenerationResponse generate(ProjectGenerationRequest request)
8093
URI url = request.generateUrl(metadata);
8194
CloseableHttpResponse httpResponse = executeProjectGenerationRequest(url);
8295
HttpEntity httpEntity = httpResponse.getEntity();
83-
if (httpEntity == null) {
84-
throw new ReportableException("No content received from server '" + url + "'");
85-
}
86-
if (httpResponse.getStatusLine().getStatusCode() != 200) {
87-
throw createException(request.getServiceUrl(), httpResponse);
88-
}
96+
validateResponse(httpResponse, request.getServiceUrl());
8997
return createResponse(httpResponse, httpEntity);
9098
}
9199

@@ -97,15 +105,33 @@ public ProjectGenerationResponse generate(ProjectGenerationRequest request)
97105
*/
98106
public InitializrServiceMetadata loadMetadata(String serviceUrl) throws IOException {
99107
CloseableHttpResponse httpResponse = executeInitializrMetadataRetrieval(serviceUrl);
100-
if (httpResponse.getEntity() == null) {
101-
throw new ReportableException("No content received from server '"
102-
+ serviceUrl + "'");
103-
}
104-
if (httpResponse.getStatusLine().getStatusCode() != 200) {
105-
throw createException(serviceUrl, httpResponse);
108+
validateResponse(httpResponse, serviceUrl);
109+
return parseJsonMetadata(httpResponse.getEntity());
110+
}
111+
112+
/**
113+
* Loads the service capabilities of the service at the specified url.
114+
* <p>If the service supports generating a textual representation of the
115+
* capabilities, it is returned. Otherwhise the json meta-data as a
116+
* {@link JSONObject} is returned.
117+
* @param serviceUrl to url of the initializer service
118+
* @return the service capabilities (as a String) or the metadata describing the service
119+
* @throws IOException if the service capabilities cannot be loaded
120+
*/
121+
public Object loadServiceCapabilities(String serviceUrl) throws IOException {
122+
CloseableHttpResponse httpResponse = executeServiceCapabilitiesRetrieval(serviceUrl);
123+
validateResponse(httpResponse, serviceUrl);
124+
HttpEntity httpEntity = httpResponse.getEntity();
125+
ContentType contentType = ContentType.getOrDefault(httpEntity);
126+
if (contentType.getMimeType().equals("text/plain")) {
127+
return getContent(httpEntity);
128+
} else {
129+
return parseJsonMetadata(httpEntity);
106130
}
131+
}
132+
133+
private InitializrServiceMetadata parseJsonMetadata(HttpEntity httpEntity) throws IOException {
107134
try {
108-
HttpEntity httpEntity = httpResponse.getEntity();
109135
return new InitializrServiceMetadata(getContentAsJson(httpEntity));
110136
}
111137
catch (JSONException ex) {
@@ -114,6 +140,16 @@ public InitializrServiceMetadata loadMetadata(String serviceUrl) throws IOExcept
114140
}
115141
}
116142

143+
private void validateResponse(CloseableHttpResponse httpResponse, String serviceUrl) {
144+
if (httpResponse.getEntity() == null) {
145+
throw new ReportableException("No content received from server '"
146+
+ serviceUrl + "'");
147+
}
148+
if (httpResponse.getStatusLine().getStatusCode() != 200) {
149+
throw createException(serviceUrl, httpResponse);
150+
}
151+
}
152+
117153
private ProjectGenerationResponse createResponse(CloseableHttpResponse httpResponse,
118154
HttpEntity httpEntity) throws IOException {
119155
ProjectGenerationResponse response = new ProjectGenerationResponse(
@@ -139,11 +175,19 @@ private CloseableHttpResponse executeProjectGenerationRequest(URI url) {
139175
*/
140176
private CloseableHttpResponse executeInitializrMetadataRetrieval(String url) {
141177
HttpGet request = new HttpGet(url);
142-
request.setHeader(new BasicHeader(HttpHeaders.ACCEPT,
143-
"application/vnd.initializr.v2+json"));
178+
request.setHeader(new BasicHeader(HttpHeaders.ACCEPT, ACCEPT_META_DATA));
144179
return execute(request, url, "retrieve metadata");
145180
}
146181

182+
/**
183+
* Retrieves the service capabilities of the service at the specified URL
184+
*/
185+
private CloseableHttpResponse executeServiceCapabilitiesRetrieval(String url) {
186+
HttpGet request = new HttpGet(url);
187+
request.setHeader(new BasicHeader(HttpHeaders.ACCEPT, ACCEPT_SERVICE_CAPABILITIES));
188+
return execute(request, url, "retrieve help");
189+
}
190+
147191
private CloseableHttpResponse execute(HttpUriRequest request, Object url,
148192
String description) {
149193
try {
@@ -188,11 +232,15 @@ private String extractMessage(HttpEntity entity) {
188232
}
189233

190234
private JSONObject getContentAsJson(HttpEntity entity) throws IOException {
235+
return new JSONObject(getContent(entity));
236+
}
237+
238+
private String getContent(HttpEntity entity) throws IOException {
191239
ContentType contentType = ContentType.getOrDefault(entity);
192240
Charset charset = contentType.getCharset();
193241
charset = (charset != null ? charset : UTF_8);
194242
byte[] content = FileCopyUtils.copyToByteArray(entity.getContent());
195-
return new JSONObject(new String(content, charset));
243+
return new String(content, charset);
196244
}
197245

198246
private String extractFileName(Header header) {

spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGenerator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ class ServiceCapabilitiesReportGenerator {
5454
* @throws IOException if the report cannot be generated
5555
*/
5656
public String generate(String url) throws IOException {
57-
InitializrServiceMetadata metadata = this.initializrService.loadMetadata(url);
57+
Object content = this.initializrService.loadServiceCapabilities(url);
58+
if (content instanceof InitializrServiceMetadata) {
59+
return generateHelp(url, (InitializrServiceMetadata) content);
60+
} else {
61+
return content.toString();
62+
}
63+
}
64+
65+
private String generateHelp(String url, InitializrServiceMetadata metadata) {
5866
String header = "Capabilities of " + url;
5967
StringBuilder report = new StringBuilder();
6068
report.append(StringUtils.repeat("=", header.length()) + NEW_LINE);

spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/AbstractHttpClientMockTests.java

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -48,37 +48,51 @@ public abstract class AbstractHttpClientMockTests {
4848

4949
protected final CloseableHttpClient http = mock(CloseableHttpClient.class);
5050

51-
protected void mockSuccessfulMetadataGet() throws IOException {
52-
mockSuccessfulMetadataGet("2.0.0");
51+
protected void mockSuccessfulMetadataTextGet() throws IOException {
52+
mockSuccessfulMetadataGet("metadata/service-metadata-2.1.0.txt", "text/plain", true);
5353
}
5454

55-
protected void mockSuccessfulMetadataGet(String version) throws IOException {
55+
protected void mockSuccessfulMetadataGet(boolean serviceCapabilities) throws IOException {
56+
mockSuccessfulMetadataGet("metadata/service-metadata-2.1.0.json",
57+
"application/vnd.initializr.v2.1+json", serviceCapabilities);
58+
}
59+
60+
protected void mockSuccessfulMetadataGetV2(boolean serviceCapabilities) throws IOException {
61+
mockSuccessfulMetadataGet("metadata/service-metadata-2.0.0.json",
62+
"application/vnd.initializr.v2+json", serviceCapabilities);
63+
}
64+
65+
protected void mockSuccessfulMetadataGet(String contentPath, String contentType,
66+
boolean serviceCapabilities) throws IOException {
5667
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
57-
Resource resource = new ClassPathResource("metadata/service-metadata-" + version
58-
+ ".json");
59-
byte[] content = StreamUtils.copyToByteArray(resource.getInputStream());
60-
mockHttpEntity(response, content, "application/vnd.initializr.v2+json");
68+
byte[] content = readClasspathResource(contentPath);
69+
mockHttpEntity(response, content, contentType);
6170
mockStatus(response, 200);
62-
given(this.http.execute(argThat(getForJsonMetadata()))).willReturn(response);
71+
given(this.http.execute(argThat(getForMetadata(serviceCapabilities)))).willReturn(response);
72+
}
73+
74+
protected byte[] readClasspathResource(String contentPath) throws IOException {
75+
Resource resource = new ClassPathResource(contentPath);
76+
return StreamUtils.copyToByteArray(resource.getInputStream());
6377
}
6478

6579
protected void mockSuccessfulProjectGeneration(
6680
MockHttpProjectGenerationRequest request) throws IOException {
6781
// Required for project generation as the metadata is read first
68-
mockSuccessfulMetadataGet();
82+
mockSuccessfulMetadataGet(false);
6983
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
7084
mockHttpEntity(response, request.content, request.contentType);
7185
mockStatus(response, 200);
7286
String header = (request.fileName != null ? contentDispositionValue(request.fileName)
7387
: null);
7488
mockHttpHeader(response, "Content-Disposition", header);
75-
given(this.http.execute(argThat(getForNonJsonMetadata()))).willReturn(response);
89+
given(this.http.execute(argThat(getForNonMetadata()))).willReturn(response);
7690
}
7791

7892
protected void mockProjectGenerationError(int status, String message)
7993
throws IOException {
8094
// Required for project generation as the metadata is read first
81-
mockSuccessfulMetadataGet();
95+
mockSuccessfulMetadataGet(false);
8296
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
8397
mockHttpEntity(response, createJsonError(status, message).getBytes(),
8498
"application/json");
@@ -122,12 +136,17 @@ protected void mockHttpHeader(CloseableHttpResponse response, String headerName,
122136
given(response.getFirstHeader(headerName)).willReturn(header);
123137
}
124138

125-
protected Matcher<HttpGet> getForJsonMetadata() {
126-
return new HasAcceptHeader("application/vnd.initializr.v2+json", true);
139+
private Matcher<HttpGet> getForMetadata(boolean serviceCapabilities) {
140+
if (serviceCapabilities) {
141+
return new HasAcceptHeader(InitializrService.ACCEPT_SERVICE_CAPABILITIES, true);
142+
}
143+
else {
144+
return new HasAcceptHeader(InitializrService.ACCEPT_META_DATA, true);
145+
}
127146
}
128147

129-
protected Matcher<HttpGet> getForNonJsonMetadata() {
130-
return new HasAcceptHeader("application/vnd.initializr.v2+json", false);
148+
private Matcher<HttpGet> getForNonMetadata() {
149+
return new HasAcceptHeader(InitializrService.ACCEPT_META_DATA, false);
131150
}
132151

133152
private String contentDispositionValue(String fileName) {

spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -73,9 +73,21 @@ public InitCommandTests() {
7373
this.command = new InitCommand(this.handler);
7474
}
7575

76+
@Test
77+
public void listServiceCapabilitiesText() throws Exception {
78+
mockSuccessfulMetadataTextGet();
79+
this.command.run("--list", "--target=http://fake-service");
80+
}
81+
7682
@Test
7783
public void listServiceCapabilities() throws Exception {
78-
mockSuccessfulMetadataGet();
84+
mockSuccessfulMetadataGet(true);
85+
this.command.run("--list", "--target=http://fake-service");
86+
}
87+
88+
@Test
89+
public void listServiceCapabilitiesV2() throws Exception {
90+
mockSuccessfulMetadataGetV2(true);
7991
this.command.run("--list", "--target=http://fake-service");
8092
}
8193

spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ public class InitializrServiceTests extends AbstractHttpClientMockTests {
4646

4747
@Test
4848
public void loadMetadata() throws IOException {
49-
mockSuccessfulMetadataGet();
49+
mockSuccessfulMetadataGet(false);
5050
InitializrServiceMetadata metadata = this.invoker.loadMetadata("http://foo/bar");
5151
assertNotNull(metadata);
5252
}
@@ -101,7 +101,7 @@ public void generateProjectBadRequestNoExtraMessage() throws IOException {
101101

102102
@Test
103103
public void generateProjectNoContent() throws IOException {
104-
mockSuccessfulMetadataGet();
104+
mockSuccessfulMetadataGet(false);
105105
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
106106
mockStatus(response, 500);
107107
when(this.http.execute(isA(HttpGet.class))).thenReturn(response);

spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGeneratorTests.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,7 +20,9 @@
2020

2121
import org.junit.Test;
2222

23-
import static org.junit.Assert.assertTrue;
23+
import static org.hamcrest.CoreMatchers.equalTo;
24+
import static org.hamcrest.core.StringContains.containsString;
25+
import static org.junit.Assert.assertThat;
2426

2527
/**
2628
* Tests for {@link ServiceCapabilitiesReportGenerator}
@@ -32,14 +34,32 @@ public class ServiceCapabilitiesReportGeneratorTests extends AbstractHttpClientM
3234
private final ServiceCapabilitiesReportGenerator command = new ServiceCapabilitiesReportGenerator(
3335
new InitializrService(this.http));
3436

37+
@Test
38+
public void listMetadataFromServer() throws IOException {
39+
mockSuccessfulMetadataTextGet();
40+
String expected = new String(readClasspathResource("metadata/service-metadata-2.1.0.txt"));
41+
String content = this.command.generate("http://localhost");
42+
assertThat(content, equalTo(expected));
43+
}
44+
3545
@Test
3646
public void listMetadata() throws IOException {
37-
mockSuccessfulMetadataGet();
47+
mockSuccessfulMetadataGet(true);
48+
doTestGenerateCapabilitiesFromJson();
49+
}
50+
51+
@Test
52+
public void listMetadataV2() throws IOException {
53+
mockSuccessfulMetadataGetV2(true);
54+
doTestGenerateCapabilitiesFromJson();
55+
}
56+
57+
private void doTestGenerateCapabilitiesFromJson() throws IOException {
3858
String content = this.command.generate("http://localhost");
39-
assertTrue(content.contains("aop - AOP"));
40-
assertTrue(content.contains("security - Security: Security description"));
41-
assertTrue(content.contains("type: maven-project"));
42-
assertTrue(content.contains("packaging: jar"));
59+
assertThat(content, containsString("aop - AOP"));
60+
assertThat(content, containsString("security - Security: Security description"));
61+
assertThat(content, containsString("type: maven-project"));
62+
assertThat(content, containsString("packaging: jar"));
4363
}
4464

4565
}

0 commit comments

Comments
 (0)