Polish CLI init command
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
parent
b89e5e0ab7
commit
830ce80824
@ -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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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