Browse Source
Rename a few classes and methods and extract some logic into helper classes. Also change 2 char shortcuts to a single char. Closes gh-1751pull/1815/head
22 changed files with 811 additions and 861 deletions
@ -1,306 +0,0 @@
@@ -1,306 +0,0 @@
|
||||
/* |
||||
* Copyright 2012-2014 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 |
||||
* |
||||
* http://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 org.springframework.boot.cli.command.init; |
||||
|
||||
import java.io.BufferedOutputStream; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.Arrays; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipInputStream; |
||||
|
||||
import joptsimple.OptionSet; |
||||
import joptsimple.OptionSpec; |
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.springframework.boot.cli.command.options.OptionHandler; |
||||
import org.springframework.boot.cli.command.status.ExitStatus; |
||||
import org.springframework.boot.cli.util.Log; |
||||
import org.springframework.util.StreamUtils; |
||||
|
||||
/** |
||||
* The {@link OptionHandler} implementation for the init command. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 1.2.0 |
||||
*/ |
||||
public class InitCommandOptionHandler extends OptionHandler { |
||||
|
||||
private final CloseableHttpClient httpClient; |
||||
|
||||
private OptionSpec<String> target; |
||||
|
||||
private OptionSpec<Void> listMetadata; |
||||
|
||||
// Project generation options
|
||||
|
||||
private OptionSpec<String> bootVersion; |
||||
|
||||
private OptionSpec<String> dependencies; |
||||
|
||||
private OptionSpec<String> javaVersion; |
||||
|
||||
private OptionSpec<String> packaging; |
||||
|
||||
private OptionSpec<String> build; |
||||
|
||||
private OptionSpec<String> format; |
||||
|
||||
private OptionSpec<String> type; |
||||
|
||||
// Other options
|
||||
|
||||
private OptionSpec<Void> extract; |
||||
|
||||
private OptionSpec<Void> force; |
||||
|
||||
private OptionSpec<String> output; |
||||
|
||||
InitCommandOptionHandler(CloseableHttpClient httpClient) { |
||||
this.httpClient = httpClient; |
||||
} |
||||
|
||||
@Override |
||||
protected void options() { |
||||
this.target = option(Arrays.asList("target"), "URL of the service to use") |
||||
.withRequiredArg().defaultsTo( |
||||
ProjectGenerationRequest.DEFAULT_SERVICE_URL); |
||||
this.listMetadata = option(Arrays.asList("list", "l"), |
||||
"List the capabilities of the service. Use it to " |
||||
+ "discover the dependencies and the types that are available."); |
||||
|
||||
// Project generation settings
|
||||
this.bootVersion = option(Arrays.asList("boot-version", "bv"), |
||||
"Spring Boot version to use (e.g. 1.2.0.RELEASE)").withRequiredArg(); |
||||
this.dependencies = option(Arrays.asList("dependencies", "d"), |
||||
"Comma separated list of dependencies to include in the generated project") |
||||
.withRequiredArg(); |
||||
this.javaVersion = option(Arrays.asList("java-version", "jv"), |
||||
"Java version to use (e.g. 1.8)").withRequiredArg(); |
||||
this.packaging = option(Arrays.asList("packaging", "p"), |
||||
"Packaging type to use (e.g. jar)").withRequiredArg(); |
||||
|
||||
this.build = option( |
||||
"build", |
||||
"The build system to use (e.g. maven, gradle). To be used alongside " |
||||
+ "--format to uniquely identify one type that is supported by the service. " |
||||
+ "Use --type in case of conflict").withRequiredArg().defaultsTo( |
||||
"maven"); |
||||
this.format = option( |
||||
"format", |
||||
"The format of the generated content (e.g. build for a build file, " |
||||
+ "project for a project archive). To be used alongside --build to uniquely identify one type " |
||||
+ "that is supported by the service. Use --type in case of conflict") |
||||
.withRequiredArg().defaultsTo("project"); |
||||
this.type = option( |
||||
Arrays.asList("type", "t"), |
||||
"The project type to use. Not normally needed if you " |
||||
+ "use --build and/or --format. Check the capabilities of the service (--list) for " |
||||
+ "more details.").withRequiredArg(); |
||||
|
||||
// Others
|
||||
this.extract = option(Arrays.asList("extract", "x"), |
||||
"Extract the project archive"); |
||||
this.force = option(Arrays.asList("force", "f"), |
||||
"Force overwrite of existing files"); |
||||
this.output = option( |
||||
Arrays.asList("output", "o"), |
||||
"Location of the generated project. Can be an absolute or a relative reference and " |
||||
+ "should refer to a directory when --extract is used.") |
||||
.withRequiredArg(); |
||||
} |
||||
|
||||
@Override |
||||
protected ExitStatus run(OptionSet options) throws Exception { |
||||
if (options.has(this.listMetadata)) { |
||||
return listServiceCapabilities(options, this.httpClient); |
||||
} |
||||
else { |
||||
return generateProject(options, this.httpClient); |
||||
} |
||||
} |
||||
|
||||
public ProjectGenerationRequest createProjectGenerationRequest(OptionSet options) { |
||||
ProjectGenerationRequest request = new ProjectGenerationRequest(); |
||||
request.setServiceUrl(determineServiceUrl(options)); |
||||
|
||||
if (options.has(this.bootVersion)) { |
||||
request.setBootVersion(options.valueOf(this.bootVersion)); |
||||
} |
||||
if (options.has(this.dependencies)) { |
||||
for (String dep : options.valueOf(this.dependencies).split(",")) { |
||||
request.getDependencies().add(dep.trim()); |
||||
} |
||||
} |
||||
if (options.has(this.javaVersion)) { |
||||
request.setJavaVersion(options.valueOf(this.javaVersion)); |
||||
} |
||||
if (options.has(this.packaging)) { |
||||
request.setPackaging(options.valueOf(this.packaging)); |
||||
} |
||||
request.setBuild(options.valueOf(this.build)); |
||||
request.setFormat(options.valueOf(this.format)); |
||||
request.setDetectType(options.has(this.build) || options.has(this.format)); |
||||
if (options.has(this.type)) { |
||||
request.setType(options.valueOf(this.type)); |
||||
} |
||||
if (options.has(this.output)) { |
||||
request.setOutput(options.valueOf(this.output)); |
||||
} |
||||
return request; |
||||
} |
||||
|
||||
protected ExitStatus listServiceCapabilities(OptionSet options, |
||||
CloseableHttpClient httpClient) throws IOException { |
||||
ListMetadataCommand command = new ListMetadataCommand(httpClient); |
||||
Log.info(command.generateReport(determineServiceUrl(options))); |
||||
return ExitStatus.OK; |
||||
} |
||||
|
||||
protected ExitStatus generateProject(OptionSet options, CloseableHttpClient httpClient) { |
||||
ProjectGenerationRequest request = createProjectGenerationRequest(options); |
||||
boolean forceValue = options.has(this.force); |
||||
try { |
||||
ProjectGenerationResponse entity = new InitializrServiceHttpInvoker( |
||||
httpClient).generate(request); |
||||
if (options.has(this.extract)) { |
||||
if (isZipArchive(entity)) { |
||||
return extractProject(entity, options.valueOf(this.output), |
||||
forceValue); |
||||
} |
||||
else { |
||||
Log.info("Could not extract '" + entity.getContentType() + "'"); |
||||
} |
||||
} |
||||
String outputFileName = entity.getFileName() != null ? entity.getFileName() |
||||
: options.valueOf(this.output); |
||||
if (outputFileName == null) { |
||||
Log.error("Could not save the project, the server did not set a preferred " |
||||
+ "file name. Use --output to specify the output location for the project."); |
||||
return ExitStatus.ERROR; |
||||
} |
||||
return writeProject(entity, outputFileName, forceValue); |
||||
} |
||||
catch (ProjectGenerationException ex) { |
||||
Log.error(ex.getMessage()); |
||||
return ExitStatus.ERROR; |
||||
} |
||||
catch (Exception ex) { |
||||
Log.error(ex); |
||||
return ExitStatus.ERROR; |
||||
} |
||||
} |
||||
|
||||
private String determineServiceUrl(OptionSet options) { |
||||
return options.valueOf(this.target); |
||||
} |
||||
|
||||
private ExitStatus writeProject(ProjectGenerationResponse entity, |
||||
String outputFileName, boolean overwrite) throws IOException { |
||||
|
||||
File f = new File(outputFileName); |
||||
if (f.exists()) { |
||||
if (overwrite) { |
||||
if (!f.delete()) { |
||||
throw new IllegalStateException("Failed to delete existing file " |
||||
+ f.getPath()); |
||||
} |
||||
} |
||||
else { |
||||
Log.error("File '" + f.getName() |
||||
+ "' already exists. Use --force if you want to " |
||||
+ "overwrite or --output to specify an alternate location."); |
||||
return ExitStatus.ERROR; |
||||
} |
||||
} |
||||
FileOutputStream stream = new FileOutputStream(f); |
||||
try { |
||||
StreamUtils.copy(entity.getContent(), stream); |
||||
Log.info("Content saved to '" + outputFileName + "'"); |
||||
return ExitStatus.OK; |
||||
} |
||||
finally { |
||||
stream.close(); |
||||
} |
||||
} |
||||
|
||||
private boolean isZipArchive(ProjectGenerationResponse entity) { |
||||
if (entity.getContentType() == null) { |
||||
return false; |
||||
} |
||||
try { |
||||
return "application/zip".equals(entity.getContentType().getMimeType()); |
||||
} |
||||
catch (Exception e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private ExitStatus extractProject(ProjectGenerationResponse entity, |
||||
String outputValue, boolean overwrite) throws IOException { |
||||
File output = outputValue != null ? new File(outputValue) : new File( |
||||
System.getProperty("user.dir")); |
||||
if (!output.exists()) { |
||||
output.mkdirs(); |
||||
} |
||||
ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream( |
||||
entity.getContent())); |
||||
try { |
||||
ZipEntry entry = zipIn.getNextEntry(); |
||||
while (entry != null) { |
||||
File f = new File(output, entry.getName()); |
||||
if (f.exists() && !overwrite) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append(f.isDirectory() ? "Directory" : "File") |
||||
.append(" '") |
||||
.append(f.getName()) |
||||
.append("' already exists. Use --force if you want to " |
||||
+ "overwrite or --output to specify an alternate location."); |
||||
Log.error(sb.toString()); |
||||
return ExitStatus.ERROR; |
||||
} |
||||
if (!entry.isDirectory()) { |
||||
extractZipEntry(zipIn, f); |
||||
} |
||||
else { |
||||
f.mkdir(); |
||||
} |
||||
zipIn.closeEntry(); |
||||
entry = zipIn.getNextEntry(); |
||||
} |
||||
Log.info("Project extracted to '" + output.getAbsolutePath() + "'"); |
||||
return ExitStatus.OK; |
||||
} |
||||
finally { |
||||
zipIn.close(); |
||||
} |
||||
} |
||||
|
||||
private void extractZipEntry(ZipInputStream in, File outputFile) throws IOException { |
||||
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream( |
||||
outputFile)); |
||||
try { |
||||
StreamUtils.copy(in, out); |
||||
} |
||||
finally { |
||||
out.close(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,195 @@
@@ -0,0 +1,195 @@
|
||||
/* |
||||
* Copyright 2012-2014 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 |
||||
* |
||||
* http://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 org.springframework.boot.cli.command.init; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URI; |
||||
import java.nio.charset.Charset; |
||||
|
||||
import org.apache.http.Header; |
||||
import org.apache.http.HttpEntity; |
||||
import org.apache.http.HttpHeaders; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.client.methods.HttpGet; |
||||
import org.apache.http.client.methods.HttpUriRequest; |
||||
import org.apache.http.entity.ContentType; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicHeader; |
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
import org.springframework.boot.cli.util.Log; |
||||
import org.springframework.util.FileCopyUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Invokes the initializr service over HTTP. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 1.2.0 |
||||
*/ |
||||
class InitializrService { |
||||
|
||||
private static final String FILENAME_HEADER_PREFIX = "filename=\""; |
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8"); |
||||
|
||||
private final CloseableHttpClient http; |
||||
|
||||
/** |
||||
* Create a new instance with the given {@link CloseableHttpClient HTTP client}. |
||||
*/ |
||||
public InitializrService(CloseableHttpClient http) { |
||||
this.http = http; |
||||
} |
||||
|
||||
/** |
||||
* Generate a project based on the specified {@link ProjectGenerationRequest} |
||||
* @return an entity defining the project |
||||
*/ |
||||
public ProjectGenerationResponse generate(ProjectGenerationRequest request) |
||||
throws IOException { |
||||
Log.info("Using service at " + request.getServiceUrl()); |
||||
InitializrServiceMetadata metadata = loadMetadata(request.getServiceUrl()); |
||||
URI url = request.generateUrl(metadata); |
||||
CloseableHttpResponse httpResponse = executeProjectGenerationRequest(url); |
||||
HttpEntity httpEntity = httpResponse.getEntity(); |
||||
if (httpEntity == null) { |
||||
throw new ReportableException("No content received from server '" + url + "'"); |
||||
} |
||||
if (httpResponse.getStatusLine().getStatusCode() != 200) { |
||||
throw createException(request.getServiceUrl(), httpResponse); |
||||
} |
||||
return createResponse(httpResponse, httpEntity); |
||||
} |
||||
|
||||
/** |
||||
* Load the {@link InitializrServiceMetadata} at the specified url. |
||||
*/ |
||||
public InitializrServiceMetadata loadMetadata(String serviceUrl) throws IOException { |
||||
CloseableHttpResponse httpResponse = executeInitializrMetadataRetrieval(serviceUrl); |
||||
if (httpResponse.getEntity() == null) { |
||||
throw new ReportableException("No content received from server '" |
||||
+ serviceUrl + "'"); |
||||
} |
||||
if (httpResponse.getStatusLine().getStatusCode() != 200) { |
||||
throw createException(serviceUrl, httpResponse); |
||||
} |
||||
try { |
||||
HttpEntity httpEntity = httpResponse.getEntity(); |
||||
return new InitializrServiceMetadata(getContentAsJson(httpEntity)); |
||||
} |
||||
catch (JSONException ex) { |
||||
throw new ReportableException("Invalid content received from server (" |
||||
+ ex.getMessage() + ")", ex); |
||||
} |
||||
} |
||||
|
||||
private ProjectGenerationResponse createResponse(CloseableHttpResponse httpResponse, |
||||
HttpEntity httpEntity) throws IOException { |
||||
ProjectGenerationResponse response = new ProjectGenerationResponse( |
||||
ContentType.getOrDefault(httpEntity)); |
||||
response.setContent(FileCopyUtils.copyToByteArray(httpEntity.getContent())); |
||||
String fileName = extractFileName(httpResponse |
||||
.getFirstHeader("Content-Disposition")); |
||||
if (fileName != null) { |
||||
response.setFileName(fileName); |
||||
} |
||||
return response; |
||||
} |
||||
|
||||
/** |
||||
* Request the creation of the project using the specified URL |
||||
*/ |
||||
private CloseableHttpResponse executeProjectGenerationRequest(URI url) { |
||||
return execute(new HttpGet(url), url, "generate project"); |
||||
} |
||||
|
||||
/** |
||||
* Retrieves the meta-data of the service at the specified URL |
||||
*/ |
||||
private CloseableHttpResponse executeInitializrMetadataRetrieval(String url) { |
||||
HttpGet request = new HttpGet(url); |
||||
request.setHeader(new BasicHeader(HttpHeaders.ACCEPT, "application/json")); |
||||
return execute(request, url, "retrieve metadata"); |
||||
} |
||||
|
||||
private CloseableHttpResponse execute(HttpUriRequest request, Object url, |
||||
String description) { |
||||
try { |
||||
return this.http.execute(request); |
||||
} |
||||
catch (IOException ex) { |
||||
throw new ReportableException("Failed to " + description |
||||
+ " from service at '" + url + "' (" + ex.getMessage() + ")"); |
||||
} |
||||
} |
||||
|
||||
private ReportableException createException(String url, |
||||
CloseableHttpResponse httpResponse) { |
||||
String message = "Initializr service call failed using '" + url |
||||
+ "' - service returned " |
||||
+ httpResponse.getStatusLine().getReasonPhrase(); |
||||
String error = extractMessage(httpResponse.getEntity()); |
||||
if (StringUtils.hasText(error)) { |
||||
message += ": '" + error + "'"; |
||||
} |
||||
else { |
||||
int statusCode = httpResponse.getStatusLine().getStatusCode(); |
||||
message += " (unexpected " + statusCode + " error)"; |
||||
} |
||||
throw new ReportableException(message.toString()); |
||||
} |
||||
|
||||
private String extractMessage(HttpEntity entity) { |
||||
if (entity != null) { |
||||
try { |
||||
JSONObject error = getContentAsJson(entity); |
||||
if (error.has("message")) { |
||||
return error.getString("message"); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private JSONObject getContentAsJson(HttpEntity entity) throws IOException { |
||||
ContentType contentType = ContentType.getOrDefault(entity); |
||||
Charset charset = contentType.getCharset(); |
||||
charset = (charset != null ? charset : UTF_8); |
||||
byte[] content = FileCopyUtils.copyToByteArray(entity.getContent()); |
||||
return new JSONObject(new String(content, charset)); |
||||
} |
||||
|
||||
private String extractFileName(Header header) { |
||||
if (header != null) { |
||||
String value = header.getValue(); |
||||
int start = value.indexOf(FILENAME_HEADER_PREFIX); |
||||
if (start != -1) { |
||||
value = value.substring(start + FILENAME_HEADER_PREFIX.length(), |
||||
value.length()); |
||||
int end = value.indexOf("\""); |
||||
if (end != -1) { |
||||
return value.substring(0, end); |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -1,225 +0,0 @@
@@ -1,225 +0,0 @@
|
||||
/* |
||||
* Copyright 2012-2014 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 |
||||
* |
||||
* http://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 org.springframework.boot.cli.command.init; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.net.URI; |
||||
import java.nio.charset.Charset; |
||||
|
||||
import org.apache.http.Header; |
||||
import org.apache.http.HttpEntity; |
||||
import org.apache.http.HttpHeaders; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.client.methods.HttpGet; |
||||
import org.apache.http.entity.ContentType; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicHeader; |
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
import org.springframework.boot.cli.util.Log; |
||||
import org.springframework.util.StreamUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Invokes the initializr service over HTTP. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 1.2.0 |
||||
*/ |
||||
class InitializrServiceHttpInvoker { |
||||
|
||||
private final CloseableHttpClient httpClient; |
||||
|
||||
/** |
||||
* Create a new instance with the given {@link CloseableHttpClient http client}. |
||||
*/ |
||||
InitializrServiceHttpInvoker(CloseableHttpClient httpClient) { |
||||
this.httpClient = httpClient; |
||||
} |
||||
|
||||
/** |
||||
* Generate a project based on the specified {@link ProjectGenerationRequest} |
||||
* @return an entity defining the project |
||||
*/ |
||||
ProjectGenerationResponse generate(ProjectGenerationRequest request) |
||||
throws IOException { |
||||
Log.info("Using service at " + request.getServiceUrl()); |
||||
InitializrServiceMetadata metadata = loadMetadata(request.getServiceUrl()); |
||||
URI url = request.generateUrl(metadata); |
||||
CloseableHttpResponse httpResponse = executeProjectGenerationRequest(url); |
||||
|
||||
HttpEntity httpEntity = httpResponse.getEntity(); |
||||
if (httpEntity == null) { |
||||
throw new ProjectGenerationException( |
||||
"No content received from server using '" + url + "'"); |
||||
} |
||||
if (httpResponse.getStatusLine().getStatusCode() != 200) { |
||||
throw buildProjectGenerationException(request.getServiceUrl(), httpResponse); |
||||
} |
||||
return createResponse(httpResponse, httpEntity); |
||||
} |
||||
|
||||
/** |
||||
* Load the {@link InitializrServiceMetadata} at the specified url. |
||||
*/ |
||||
InitializrServiceMetadata loadMetadata(String serviceUrl) throws IOException { |
||||
CloseableHttpResponse httpResponse = executeInitializrMetadataRetrieval(serviceUrl); |
||||
if (httpResponse.getEntity() == null) { |
||||
throw new ProjectGenerationException( |
||||
"No content received from server using '" + serviceUrl + "'"); |
||||
} |
||||
if (httpResponse.getStatusLine().getStatusCode() != 200) { |
||||
throw buildProjectGenerationException(serviceUrl, httpResponse); |
||||
} |
||||
try { |
||||
HttpEntity httpEntity = httpResponse.getEntity(); |
||||
JSONObject root = getContentAsJson(getContent(httpEntity), |
||||
getContentType(httpEntity)); |
||||
return new InitializrServiceMetadata(root); |
||||
} |
||||
catch (JSONException e) { |
||||
throw new ProjectGenerationException("Invalid content received from server (" |
||||
+ e.getMessage() + ")"); |
||||
} |
||||
} |
||||
|
||||
private ProjectGenerationResponse createResponse(CloseableHttpResponse httpResponse, |
||||
HttpEntity httpEntity) throws IOException { |
||||
ProjectGenerationResponse response = new ProjectGenerationResponse(); |
||||
ContentType contentType = ContentType.getOrDefault(httpEntity); |
||||
response.setContentType(contentType); |
||||
|
||||
InputStream in = httpEntity.getContent(); |
||||
try { |
||||
response.setContent(StreamUtils.copyToByteArray(in)); |
||||
} |
||||
finally { |
||||
in.close(); |
||||
} |
||||
|
||||
String detectedFileName = extractFileName(httpResponse |
||||
.getFirstHeader("Content-Disposition")); |
||||
if (detectedFileName != null) { |
||||
response.setFileName(detectedFileName); |
||||
} |
||||
return response; |
||||
} |
||||
|
||||
/** |
||||
* Request the creation of the project using the specified url |
||||
*/ |
||||
private CloseableHttpResponse executeProjectGenerationRequest(URI url) { |
||||
try { |
||||
HttpGet get = new HttpGet(url); |
||||
return this.httpClient.execute(get); |
||||
} |
||||
catch (IOException e) { |
||||
throw new ProjectGenerationException("Failed to invoke server at '" + url |
||||
+ "' (" + e.getMessage() + ")"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Retrieves the metadata of the service at the specified url |
||||
*/ |
||||
private CloseableHttpResponse executeInitializrMetadataRetrieval(String serviceUrl) { |
||||
try { |
||||
HttpGet get = new HttpGet(serviceUrl); |
||||
get.setHeader(new BasicHeader(HttpHeaders.ACCEPT, "application/json")); |
||||
return this.httpClient.execute(get); |
||||
} |
||||
catch (IOException e) { |
||||
throw new ProjectGenerationException( |
||||
"Failed to retrieve metadata from service at '" + serviceUrl + "' (" |
||||
+ e.getMessage() + ")"); |
||||
} |
||||
} |
||||
|
||||
private byte[] getContent(HttpEntity httpEntity) throws IOException { |
||||
InputStream in = httpEntity.getContent(); |
||||
try { |
||||
return StreamUtils.copyToByteArray(in); |
||||
} |
||||
finally { |
||||
in.close(); |
||||
} |
||||
} |
||||
|
||||
private ContentType getContentType(HttpEntity httpEntity) { |
||||
return ContentType.getOrDefault(httpEntity); |
||||
} |
||||
|
||||
private JSONObject getContentAsJson(byte[] content, ContentType contentType) { |
||||
Charset charset = contentType.getCharset() != null ? contentType.getCharset() |
||||
: Charset.forName("UTF-8"); |
||||
String data = new String(content, charset); |
||||
return new JSONObject(data); |
||||
} |
||||
|
||||
private ProjectGenerationException buildProjectGenerationException(String url, |
||||
CloseableHttpResponse httpResponse) { |
||||
StringBuilder sb = new StringBuilder("Project generation failed using '"); |
||||
sb.append(url).append("' - service returned ") |
||||
.append(httpResponse.getStatusLine().getReasonPhrase()); |
||||
String error = extractMessage(httpResponse.getEntity()); |
||||
if (StringUtils.hasText(error)) { |
||||
sb.append(": '").append(error).append("'"); |
||||
} |
||||
else { |
||||
sb.append(" (unexpected ") |
||||
.append(httpResponse.getStatusLine().getStatusCode()) |
||||
.append(" error)"); |
||||
} |
||||
throw new ProjectGenerationException(sb.toString()); |
||||
} |
||||
|
||||
private String extractMessage(HttpEntity entity) { |
||||
if (entity == null) { |
||||
return null; |
||||
} |
||||
try { |
||||
JSONObject error = getContentAsJson(getContent(entity), |
||||
getContentType(entity)); |
||||
if (error.has("message")) { |
||||
return error.getString("message"); |
||||
} |
||||
return null; |
||||
} |
||||
catch (Exception e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private static String extractFileName(Header h) { |
||||
if (h == null) { |
||||
return null; |
||||
} |
||||
String value = h.getValue(); |
||||
String prefix = "filename=\""; |
||||
int start = value.indexOf(prefix); |
||||
if (start != -1) { |
||||
value = value.substring(start + prefix.length(), value.length()); |
||||
int end = value.indexOf("\""); |
||||
if (end != -1) { |
||||
return value.substring(0, end); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -1,119 +0,0 @@
@@ -1,119 +0,0 @@
|
||||
/* |
||||
* Copyright 2012-2014 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 |
||||
* |
||||
* http://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 org.springframework.boot.cli.command.init; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.codehaus.plexus.util.StringUtils; |
||||
|
||||
/** |
||||
* A helper class generating a report from the metadata of a particular service. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 1.2.0 |
||||
*/ |
||||
class ListMetadataCommand { |
||||
|
||||
private static final String NEW_LINE = System.getProperty("line.separator"); |
||||
|
||||
private final InitializrServiceHttpInvoker initializrServiceInvoker; |
||||
|
||||
/** |
||||
* Creates an instance using the specified {@link CloseableHttpClient}. |
||||
*/ |
||||
ListMetadataCommand(CloseableHttpClient httpClient) { |
||||
this.initializrServiceInvoker = new InitializrServiceHttpInvoker(httpClient); |
||||
} |
||||
|
||||
/** |
||||
* Generate a report for the specified service. The report contains the available |
||||
* capabilities as advertized by the root endpoint. |
||||
*/ |
||||
String generateReport(String serviceUrl) throws IOException { |
||||
InitializrServiceMetadata metadata = this.initializrServiceInvoker |
||||
.loadMetadata(serviceUrl); |
||||
String header = "Capabilities of " + serviceUrl; |
||||
int size = header.length(); |
||||
|
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append(StringUtils.repeat("=", size)).append(NEW_LINE).append(header) |
||||
.append(NEW_LINE).append(StringUtils.repeat("=", size)).append(NEW_LINE) |
||||
.append(NEW_LINE).append("Available dependencies:").append(NEW_LINE) |
||||
.append("-----------------------").append(NEW_LINE); |
||||
|
||||
List<Dependency> dependencies = new ArrayList<Dependency>( |
||||
metadata.getDependencies()); |
||||
Collections.sort(dependencies, new Comparator<Dependency>() { |
||||
@Override |
||||
public int compare(Dependency o1, Dependency o2) { |
||||
return o1.getId().compareTo(o2.getId()); |
||||
} |
||||
}); |
||||
for (Dependency dependency : dependencies) { |
||||
sb.append(dependency.getId()).append(" - ").append(dependency.getName()); |
||||
if (dependency.getDescription() != null) { |
||||
sb.append(": ").append(dependency.getDescription()); |
||||
} |
||||
sb.append(NEW_LINE); |
||||
} |
||||
|
||||
sb.append(NEW_LINE).append("Available project types:").append(NEW_LINE) |
||||
.append("------------------------").append(NEW_LINE); |
||||
List<String> typeIds = new ArrayList<String>(metadata.getProjectTypes().keySet()); |
||||
Collections.sort(typeIds); |
||||
for (String typeId : typeIds) { |
||||
ProjectType type = metadata.getProjectTypes().get(typeId); |
||||
sb.append(typeId).append(" - ").append(type.getName()); |
||||
if (!type.getTags().isEmpty()) { |
||||
sb.append(" ["); |
||||
Iterator<Map.Entry<String, String>> it = type.getTags().entrySet() |
||||
.iterator(); |
||||
while (it.hasNext()) { |
||||
Map.Entry<String, String> entry = it.next(); |
||||
sb.append(entry.getKey()).append(":").append(entry.getValue()); |
||||
if (it.hasNext()) { |
||||
sb.append(", "); |
||||
} |
||||
} |
||||
sb.append("]"); |
||||
} |
||||
if (type.isDefaultType()) { |
||||
sb.append(" (default)"); |
||||
} |
||||
sb.append(NEW_LINE); |
||||
} |
||||
|
||||
sb.append(NEW_LINE).append("Defaults:").append(NEW_LINE).append("---------") |
||||
.append(NEW_LINE); |
||||
|
||||
List<String> defaultsKeys = new ArrayList<String>(metadata.getDefaults().keySet()); |
||||
Collections.sort(defaultsKeys); |
||||
for (String defaultsKey : defaultsKeys) { |
||||
sb.append(defaultsKey).append(": ") |
||||
.append(metadata.getDefaults().get(defaultsKey)).append(NEW_LINE); |
||||
} |
||||
return sb.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,137 @@
@@ -0,0 +1,137 @@
|
||||
/* |
||||
* Copyright 2012-2014 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 |
||||
* |
||||
* http://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 org.springframework.boot.cli.command.init; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipInputStream; |
||||
|
||||
import org.springframework.boot.cli.util.Log; |
||||
import org.springframework.util.FileCopyUtils; |
||||
import org.springframework.util.StreamUtils; |
||||
|
||||
/** |
||||
* @author Stephane Nicoll |
||||
* @since 1.2.0 |
||||
*/ |
||||
public class ProjectGenerator { |
||||
|
||||
private static final String ZIP_MIME_TYPE = "application/zip"; |
||||
|
||||
private final InitializrService initializrService; |
||||
|
||||
public ProjectGenerator(InitializrService initializrService) { |
||||
this.initializrService = initializrService; |
||||
} |
||||
|
||||
public void generateProject(ProjectGenerationRequest request, boolean force, |
||||
boolean extract, String output) throws IOException { |
||||
ProjectGenerationResponse response = this.initializrService.generate(request); |
||||
if (extract) { |
||||
if (isZipArchive(response)) { |
||||
extractProject(response, output, force); |
||||
return; |
||||
} |
||||
else { |
||||
Log.info("Could not extract '" + response.getContentType() + "'"); |
||||
} |
||||
} |
||||
String fileName = response.getFileName(); |
||||
fileName = (fileName != null ? fileName : output); |
||||
if (fileName == null) { |
||||
throw new ReportableException( |
||||
"Could not save the project, the server did not set a preferred " |
||||
+ "file name. Use --output to specify the output location " |
||||
+ "for the project."); |
||||
} |
||||
writeProject(response, fileName, force); |
||||
} |
||||
|
||||
private boolean isZipArchive(ProjectGenerationResponse entity) { |
||||
if (entity.getContentType() != null) { |
||||
try { |
||||
return ZIP_MIME_TYPE.equals(entity.getContentType().getMimeType()); |
||||
} |
||||
catch (Exception ex) { |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private void extractProject(ProjectGenerationResponse entity, String output, |
||||
boolean overwrite) throws IOException { |
||||
File outputFolder = (output != null ? new File(output) : new File( |
||||
System.getProperty("user.dir"))); |
||||
if (!outputFolder.exists()) { |
||||
outputFolder.mkdirs(); |
||||
} |
||||
ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream( |
||||
entity.getContent())); |
||||
try { |
||||
extractFromStream(zipStream, overwrite, outputFolder); |
||||
Log.info("Project extracted to '" + outputFolder.getAbsolutePath() + "'"); |
||||
} |
||||
finally { |
||||
zipStream.close(); |
||||
} |
||||
} |
||||
|
||||
private void extractFromStream(ZipInputStream zipStream, boolean overwrite, |
||||
File outputFolder) throws IOException { |
||||
ZipEntry entry = zipStream.getNextEntry(); |
||||
while (entry != null) { |
||||
File file = new File(outputFolder, entry.getName()); |
||||
if (file.exists() && !overwrite) { |
||||
throw new ReportableException(file.isDirectory() ? "Directory" : "File" |
||||
+ " '" + file.getName() |
||||
+ "' already exists. Use --force if you want to overwrite or " |
||||
+ "--output to specify an alternate location."); |
||||
} |
||||
if (!entry.isDirectory()) { |
||||
FileCopyUtils.copy(StreamUtils.nonClosing(zipStream), |
||||
new FileOutputStream(file)); |
||||
} |
||||
else { |
||||
file.mkdir(); |
||||
} |
||||
zipStream.closeEntry(); |
||||
entry = zipStream.getNextEntry(); |
||||
} |
||||
} |
||||
|
||||
private void writeProject(ProjectGenerationResponse entity, String output, |
||||
boolean overwrite) throws IOException { |
||||
File outputFile = new File(output); |
||||
if (outputFile.exists()) { |
||||
if (!overwrite) { |
||||
throw new ReportableException("File '" + outputFile.getName() |
||||
+ "' already exists. Use --force if you want to " |
||||
+ "overwrite or --output to specify an alternate location."); |
||||
} |
||||
if (!outputFile.delete()) { |
||||
throw new ReportableException("Failed to delete existing file " |
||||
+ outputFile.getPath()); |
||||
} |
||||
} |
||||
FileCopyUtils.copy(entity.getContent(), outputFile); |
||||
Log.info("Content saved to '" + output + "'"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,139 @@
@@ -0,0 +1,139 @@
|
||||
/* |
||||
* Copyright 2012-2014 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 |
||||
* |
||||
* http://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 org.springframework.boot.cli.command.init; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.codehaus.plexus.util.StringUtils; |
||||
|
||||
/** |
||||
* A helper class generating a report from the meta-data of a particular service. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 1.2.0 |
||||
*/ |
||||
class ServiceCapabilitiesReportGenerator { |
||||
|
||||
private static final String NEW_LINE = System.getProperty("line.separator"); |
||||
|
||||
private final InitializrService initializrService; |
||||
|
||||
/** |
||||
* Creates an instance using the specified {@link CloseableHttpClient}. |
||||
*/ |
||||
ServiceCapabilitiesReportGenerator(InitializrService initializrService) { |
||||
this.initializrService = initializrService; |
||||
} |
||||
|
||||
/** |
||||
* Generate a report for the specified service. The report contains the available |
||||
* capabilities as advertized by the root endpoint. |
||||
*/ |
||||
public String generate(String url) throws IOException { |
||||
InitializrServiceMetadata metadata = this.initializrService.loadMetadata(url); |
||||
String header = "Capabilities of " + url; |
||||
StringBuilder report = new StringBuilder(); |
||||
report.append(StringUtils.repeat("=", header.length()) + NEW_LINE); |
||||
report.append(header + NEW_LINE); |
||||
report.append(StringUtils.repeat("=", header.length()) + NEW_LINE); |
||||
report.append(NEW_LINE); |
||||
reportAvailableDependencies(metadata, report); |
||||
report.append(NEW_LINE); |
||||
reportAvilableProjectTypes(metadata, report); |
||||
report.append(NEW_LINE); |
||||
z(metadata, report); |
||||
return report.toString(); |
||||
} |
||||
|
||||
private void reportAvailableDependencies(InitializrServiceMetadata metadata, |
||||
StringBuilder report) { |
||||
report.append("Available dependencies:" + NEW_LINE); |
||||
report.append("-----------------------" + NEW_LINE); |
||||
List<Dependency> dependencies = getSortedDependencies(metadata); |
||||
for (Dependency dependency : dependencies) { |
||||
report.append(dependency.getId() + " - " + dependency.getName()); |
||||
if (dependency.getDescription() != null) { |
||||
report.append(": " + dependency.getDescription()); |
||||
} |
||||
report.append(NEW_LINE); |
||||
} |
||||
} |
||||
|
||||
private List<Dependency> getSortedDependencies(InitializrServiceMetadata metadata) { |
||||
ArrayList<Dependency> dependencies = new ArrayList<Dependency>( |
||||
metadata.getDependencies()); |
||||
Collections.sort(dependencies, new Comparator<Dependency>() { |
||||
@Override |
||||
public int compare(Dependency o1, Dependency o2) { |
||||
return o1.getId().compareTo(o2.getId()); |
||||
} |
||||
}); |
||||
return dependencies; |
||||
} |
||||
|
||||
private void reportAvilableProjectTypes(InitializrServiceMetadata metadata, |
||||
StringBuilder report) { |
||||
report.append("Available project types:" + NEW_LINE); |
||||
report.append("------------------------" + NEW_LINE); |
||||
List<String> typeIds = new ArrayList<String>(metadata.getProjectTypes().keySet()); |
||||
Collections.sort(typeIds); |
||||
for (String typeId : typeIds) { |
||||
ProjectType type = metadata.getProjectTypes().get(typeId); |
||||
report.append(typeId + " - " + type.getName()); |
||||
if (!type.getTags().isEmpty()) { |
||||
reportTags(report, type); |
||||
} |
||||
if (type.isDefaultType()) { |
||||
report.append(" (default)"); |
||||
} |
||||
report.append(NEW_LINE); |
||||
} |
||||
} |
||||
|
||||
private void reportTags(StringBuilder report, ProjectType type) { |
||||
Map<String, String> tags = type.getTags(); |
||||
Iterator<Map.Entry<String, String>> iterator = tags.entrySet().iterator(); |
||||
report.append(" ["); |
||||
while (iterator.hasNext()) { |
||||
Map.Entry<String, String> entry = iterator.next(); |
||||
report.append(entry.getKey() + ":" + entry.getValue()); |
||||
if (iterator.hasNext()) { |
||||
report.append(", "); |
||||
} |
||||
} |
||||
report.append("]"); |
||||
} |
||||
|
||||
private void z(InitializrServiceMetadata metadata, StringBuilder report) { |
||||
report.append("Defaults:" + NEW_LINE); |
||||
report.append("---------" + NEW_LINE); |
||||
List<String> defaultsKeys = new ArrayList<String>(metadata.getDefaults().keySet()); |
||||
Collections.sort(defaultsKeys); |
||||
for (String defaultsKey : defaultsKeys) { |
||||
String defaultsValue = metadata.getDefaults().get(defaultsKey); |
||||
report.append(defaultsKey + ": " + defaultsValue + NEW_LINE); |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue