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-1751
This commit is contained in:
@@ -22,36 +22,30 @@ package org.springframework.boot.cli.command.init;
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class Dependency {
|
||||
final class Dependency {
|
||||
|
||||
private String id;
|
||||
private final String id;
|
||||
|
||||
private String name;
|
||||
private final String name;
|
||||
|
||||
private String description;
|
||||
private final String description;
|
||||
|
||||
public Dependency(String id, String name, String description) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,18 @@
|
||||
|
||||
package org.springframework.boot.cli.command.init;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.options.OptionHandler;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* {@link Command} that initializes a project using Spring initializr.
|
||||
@@ -28,13 +37,168 @@ import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
*/
|
||||
public class InitCommand extends OptionParsingCommand {
|
||||
|
||||
InitCommand(InitCommandOptionHandler handler) {
|
||||
super("init", "Initialize a new project structure from Spring Initializr",
|
||||
handler);
|
||||
public InitCommand() {
|
||||
this(new InitOptionHandler(getInitializrService()));
|
||||
}
|
||||
|
||||
public InitCommand() {
|
||||
this(new InitCommandOptionHandler(HttpClientBuilder.create().build()));
|
||||
public InitCommand(InitOptionHandler handler) {
|
||||
super("init", "Initialize a new project using Spring "
|
||||
+ "Initialzr (start.spring.io)", handler);
|
||||
}
|
||||
|
||||
private static InitializrService getInitializrService() {
|
||||
return new InitializrService(HttpClientBuilder.create().build());
|
||||
}
|
||||
|
||||
static class InitOptionHandler extends OptionHandler {
|
||||
|
||||
private final ServiceCapabilitiesReportGenerator serviceCapabilitiesReport;
|
||||
|
||||
private final ProjectGenerator projectGenerator;
|
||||
|
||||
private OptionSpec<String> target;
|
||||
|
||||
private OptionSpec<Void> listCapabilities;
|
||||
|
||||
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;
|
||||
|
||||
private OptionSpec<Void> extract;
|
||||
|
||||
private OptionSpec<Void> force;
|
||||
|
||||
private OptionSpec<String> output;
|
||||
|
||||
InitOptionHandler(InitializrService initializrService) {
|
||||
this.serviceCapabilitiesReport = new ServiceCapabilitiesReportGenerator(
|
||||
initializrService);
|
||||
this.projectGenerator = new ProjectGenerator(initializrService);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void options() {
|
||||
this.target = option(Arrays.asList("target"), "URL of the service to use")
|
||||
.withRequiredArg().defaultsTo(
|
||||
ProjectGenerationRequest.DEFAULT_SERVICE_URL);
|
||||
this.listCapabilities = option(Arrays.asList("list", "l"),
|
||||
"List the capabilities of the service. Use it to discover the "
|
||||
+ "dependencies and the types that are available");
|
||||
projectGenerationOptions();
|
||||
otherOptions();
|
||||
}
|
||||
|
||||
private void projectGenerationOptions() {
|
||||
this.bootVersion = option(Arrays.asList("boot-version", "b"),
|
||||
"Spring Boot version to use (for example '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", "j"),
|
||||
"Java version to use (for example '1.8')").withRequiredArg();
|
||||
this.packaging = option(Arrays.asList("packaging", "p"),
|
||||
"Packaging type to use (for example 'jar')").withRequiredArg();
|
||||
this.build = option("build",
|
||||
"The build system to use (for example 'maven' or 'gradle')")
|
||||
.withRequiredArg().defaultsTo("maven");
|
||||
this.format = option(
|
||||
"format",
|
||||
"The format of the generated content (for example 'build' for a build file, "
|
||||
+ "'project' for a project archive)").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();
|
||||
}
|
||||
|
||||
private void otherOptions() {
|
||||
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 {
|
||||
try {
|
||||
if (options.has(this.listCapabilities)) {
|
||||
generateReport(options);
|
||||
}
|
||||
else {
|
||||
generateProject(options);
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
catch (ReportableException ex) {
|
||||
Log.error(ex.getMessage());
|
||||
return ExitStatus.ERROR;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.error(ex);
|
||||
return ExitStatus.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private void generateReport(OptionSet options) throws IOException {
|
||||
Log.info(this.serviceCapabilitiesReport.generate(options.valueOf(this.target)));
|
||||
}
|
||||
|
||||
protected void generateProject(OptionSet options) throws IOException {
|
||||
ProjectGenerationRequest request = createProjectGenerationRequest(options);
|
||||
this.projectGenerator.generateProject(request, options.has(this.force),
|
||||
options.has(this.extract), options.valueOf(this.output));
|
||||
}
|
||||
|
||||
protected ProjectGenerationRequest createProjectGenerationRequest(
|
||||
OptionSet options) {
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
request.setServiceUrl(options.valueOf(this.target));
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,13 +59,13 @@ class InitializrServiceMetadata {
|
||||
/**
|
||||
* Creates a new instance using the specified root {@link JSONObject}.
|
||||
*/
|
||||
InitializrServiceMetadata(JSONObject root) {
|
||||
public InitializrServiceMetadata(JSONObject root) {
|
||||
this.dependencies = parseDependencies(root);
|
||||
this.projectTypes = parseProjectTypes(root);
|
||||
this.defaults = Collections.unmodifiableMap(parseDefaults(root));
|
||||
}
|
||||
|
||||
InitializrServiceMetadata(ProjectType defaultProjectType) {
|
||||
public InitializrServiceMetadata(ProjectType defaultProjectType) {
|
||||
this.dependencies = new HashMap<String, Dependency>();
|
||||
this.projectTypes = new MetadataHolder<String, ProjectType>();
|
||||
this.projectTypes.getContent()
|
||||
@@ -169,11 +169,10 @@ class InitializrServiceMetadata {
|
||||
}
|
||||
|
||||
private Dependency parseDependency(JSONObject object) {
|
||||
Dependency dependency = new Dependency();
|
||||
dependency.setName(getStringValue(object, NAME_ATTRIBUTE, null));
|
||||
dependency.setId(getStringValue(object, ID_ATTRIBUTE, null));
|
||||
dependency.setDescription(getStringValue(object, DESCRIPTION_ATTRIBUTE, null));
|
||||
return dependency;
|
||||
String id = getStringValue(object, ID_ATTRIBUTE, null);
|
||||
String name = getStringValue(object, NAME_ATTRIBUTE, null);
|
||||
String description = getStringValue(object, DESCRIPTION_ATTRIBUTE, null);
|
||||
return new Dependency(id, name, description);
|
||||
}
|
||||
|
||||
private ProjectType parseType(JSONObject object) {
|
||||
@@ -230,6 +229,7 @@ class InitializrServiceMetadata {
|
||||
public void setDefaultItem(T defaultItem) {
|
||||
this.defaultItem = defaultItem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -201,7 +201,7 @@ class ProjectGenerationRequest {
|
||||
return builder.build();
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new ProjectGenerationException("Invalid service URL (" + e.getMessage()
|
||||
throw new ReportableException("Invalid service URL (" + e.getMessage()
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
@@ -210,7 +210,7 @@ class ProjectGenerationRequest {
|
||||
if (this.type != null) {
|
||||
ProjectType result = metadata.getProjectTypes().get(this.type);
|
||||
if (result == null) {
|
||||
throw new ProjectGenerationException(("No project type with id '"
|
||||
throw new ReportableException(("No project type with id '"
|
||||
+ this.type + "' - check the service capabilities (--list)"));
|
||||
}
|
||||
}
|
||||
@@ -227,19 +227,19 @@ class ProjectGenerationRequest {
|
||||
return types.values().iterator().next();
|
||||
}
|
||||
else if (types.size() == 0) {
|
||||
throw new ProjectGenerationException("No type found with build '"
|
||||
throw new ReportableException("No type found with build '"
|
||||
+ this.build + "' and format '" + this.format
|
||||
+ "' check the service capabilities (--list)");
|
||||
}
|
||||
else {
|
||||
throw new ProjectGenerationException("Multiple types found with build '"
|
||||
throw new ReportableException("Multiple types found with build '"
|
||||
+ this.build + "' and format '" + this.format
|
||||
+ "' use --type with a more specific value " + types.keySet());
|
||||
}
|
||||
}
|
||||
ProjectType defaultType = metadata.getDefaultType();
|
||||
if (defaultType == null) {
|
||||
throw new ProjectGenerationException(
|
||||
throw new ReportableException(
|
||||
("No project type is set and no default is defined. "
|
||||
+ "Check the service capabilities (--list)"));
|
||||
}
|
||||
|
||||
@@ -26,13 +26,14 @@ import org.apache.http.entity.ContentType;
|
||||
*/
|
||||
class ProjectGenerationResponse {
|
||||
|
||||
private ContentType contentType;
|
||||
private final ContentType contentType;
|
||||
|
||||
private byte[] content;
|
||||
|
||||
private String fileName;
|
||||
|
||||
ProjectGenerationResponse() {
|
||||
public ProjectGenerationResponse(ContentType contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,10 +43,6 @@ class ProjectGenerationResponse {
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
public void setContentType(ContentType contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The generated project archive or file.
|
||||
*/
|
||||
|
||||
@@ -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 + "'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,4 +68,5 @@ class ProjectType {
|
||||
public Map<String, String> getTags() {
|
||||
return Collections.unmodifiableMap(this.tags);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,15 +17,19 @@
|
||||
package org.springframework.boot.cli.command.init;
|
||||
|
||||
/**
|
||||
* Thrown when a project could not be generated.
|
||||
* Exception with a message that can be reported to the user.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ProjectGenerationException extends RuntimeException {
|
||||
public class ReportableException extends RuntimeException {
|
||||
|
||||
public ProjectGenerationException(String message) {
|
||||
public ReportableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ReportableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -95,4 +95,9 @@ public final class ExitStatus {
|
||||
return new ExitStatus(this.code, this.name, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + ":" + getCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,18 +34,19 @@ import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Abstract base class for tests that use a mock {@link CloseableHttpClient}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public abstract class AbstractHttpClientMockTests {
|
||||
|
||||
protected final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
|
||||
protected final CloseableHttpClient http = mock(CloseableHttpClient.class);
|
||||
|
||||
protected void mockSuccessfulMetadataGet() throws IOException {
|
||||
mockSuccessfulMetadataGet("1.1.0");
|
||||
@@ -58,34 +59,31 @@ public abstract class AbstractHttpClientMockTests {
|
||||
byte[] content = StreamUtils.copyToByteArray(resource.getInputStream());
|
||||
mockHttpEntity(response, content, "application/json");
|
||||
mockStatus(response, 200);
|
||||
when(this.httpClient.execute(argThat(getForJsonData()))).thenReturn(response);
|
||||
given(this.http.execute(argThat(getForJsonData()))).willReturn(response);
|
||||
}
|
||||
|
||||
protected void mockSuccessfulProjectGeneration(
|
||||
MockHttpProjectGenerationRequest request) throws IOException {
|
||||
// Required for project generation as the metadata is read first
|
||||
mockSuccessfulMetadataGet();
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
mockHttpEntity(response, request.content, request.contentType);
|
||||
mockStatus(response, 200);
|
||||
|
||||
String header = request.fileName != null ? contentDispositionValue(request.fileName)
|
||||
: null;
|
||||
String header = (request.fileName != null ? contentDispositionValue(request.fileName)
|
||||
: null);
|
||||
mockHttpHeader(response, "Content-Disposition", header);
|
||||
when(this.httpClient.execute(argThat(getForNonJsonData()))).thenReturn(response);
|
||||
given(this.http.execute(argThat(getForNonJsonData()))).willReturn(response);
|
||||
}
|
||||
|
||||
protected void mockProjectGenerationError(int status, String message)
|
||||
throws IOException {
|
||||
// Required for project generation as the metadata is read first
|
||||
mockSuccessfulMetadataGet();
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
mockHttpEntity(response, createJsonError(status, message).getBytes(),
|
||||
"application/json");
|
||||
mockStatus(response, status);
|
||||
when(this.httpClient.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
given(this.http.execute(isA(HttpGet.class))).willReturn(response);
|
||||
}
|
||||
|
||||
protected void mockMetadataGetError(int status, String message) throws IOException {
|
||||
@@ -93,35 +91,35 @@ public abstract class AbstractHttpClientMockTests {
|
||||
mockHttpEntity(response, createJsonError(status, message).getBytes(),
|
||||
"application/json");
|
||||
mockStatus(response, status);
|
||||
when(this.httpClient.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
given(this.http.execute(isA(HttpGet.class))).willReturn(response);
|
||||
}
|
||||
|
||||
protected HttpEntity mockHttpEntity(CloseableHttpResponse response, byte[] content,
|
||||
String contentType) {
|
||||
try {
|
||||
HttpEntity entity = mock(HttpEntity.class);
|
||||
when(entity.getContent()).thenReturn(new ByteArrayInputStream(content));
|
||||
given(entity.getContent()).willReturn(new ByteArrayInputStream(content));
|
||||
Header contentTypeHeader = contentType != null ? new BasicHeader(
|
||||
"Content-Type", contentType) : null;
|
||||
when(entity.getContentType()).thenReturn(contentTypeHeader);
|
||||
when(response.getEntity()).thenReturn(entity);
|
||||
given(entity.getContentType()).willReturn(contentTypeHeader);
|
||||
given(response.getEntity()).willReturn(entity);
|
||||
return entity;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException("Should not happen", e);
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Should not happen", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected void mockStatus(CloseableHttpResponse response, int status) {
|
||||
StatusLine statusLine = mock(StatusLine.class);
|
||||
when(statusLine.getStatusCode()).thenReturn(status);
|
||||
when(response.getStatusLine()).thenReturn(statusLine);
|
||||
given(statusLine.getStatusCode()).willReturn(status);
|
||||
given(response.getStatusLine()).willReturn(statusLine);
|
||||
}
|
||||
|
||||
protected void mockHttpHeader(CloseableHttpResponse response, String headerName,
|
||||
String value) {
|
||||
Header header = value != null ? new BasicHeader(headerName, value) : null;
|
||||
when(response.getFirstHeader(headerName)).thenReturn(header);
|
||||
given(response.getFirstHeader(headerName)).willReturn(header);
|
||||
}
|
||||
|
||||
protected Matcher<HttpGet> getForJsonData() {
|
||||
@@ -153,6 +151,10 @@ public abstract class AbstractHttpClientMockTests {
|
||||
|
||||
byte[] content = new byte[] { 0, 0, 0, 0 };
|
||||
|
||||
public MockHttpProjectGenerationRequest(String contentType, String fileName) {
|
||||
this(contentType, fileName, new byte[] { 0, 0, 0, 0 });
|
||||
}
|
||||
|
||||
public MockHttpProjectGenerationRequest(String contentType, String fileName,
|
||||
byte[] content) {
|
||||
this.contentType = contentType;
|
||||
@@ -160,9 +162,6 @@ public abstract class AbstractHttpClientMockTests {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public MockHttpProjectGenerationRequest(String contentType, String fileName) {
|
||||
this(contentType, fileName, new byte[] { 0, 0, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
private static class HasAcceptHeader extends ArgumentMatcher<HttpGet> {
|
||||
@@ -186,10 +185,7 @@ public abstract class AbstractHttpClientMockTests {
|
||||
if (this.shouldMatch) {
|
||||
return acceptHeader != null && this.value.equals(acceptHeader.getValue());
|
||||
}
|
||||
else {
|
||||
return acceptHeader == null
|
||||
|| !this.value.equals(acceptHeader.getValue());
|
||||
}
|
||||
return acceptHeader == null || !this.value.equals(acceptHeader.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.util.zip.ZipOutputStream;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
@@ -45,12 +44,17 @@ import static org.junit.Assert.assertTrue;
|
||||
public class InitCommandTests extends AbstractHttpClientMockTests {
|
||||
|
||||
@Rule
|
||||
public final TemporaryFolder folder = new TemporaryFolder();
|
||||
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private final TestableInitCommandOptionHandler handler = new TestableInitCommandOptionHandler(
|
||||
this.httpClient);
|
||||
private final TestableInitCommandOptionHandler handler;
|
||||
|
||||
private final InitCommand command = new InitCommand(this.handler);
|
||||
private final InitCommand command;
|
||||
|
||||
public InitCommandTests() {
|
||||
InitializrService initializrService = new InitializrService(this.http);
|
||||
this.handler = new TestableInitCommandOptionHandler(initializrService);
|
||||
this.command = new InitCommand(this.handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listServiceCapabilities() throws Exception {
|
||||
@@ -61,160 +65,143 @@ public class InitCommandTests extends AbstractHttpClientMockTests {
|
||||
@Test
|
||||
public void generateProject() throws Exception {
|
||||
String fileName = UUID.randomUUID().toString() + ".zip";
|
||||
File f = new File(fileName);
|
||||
assertFalse("file should not exist", f.exists());
|
||||
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
File file = new File(fileName);
|
||||
assertFalse("file should not exist", file.exists());
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", fileName);
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
try {
|
||||
assertEquals(ExitStatus.OK, this.command.run());
|
||||
assertTrue("file should have been created", f.exists());
|
||||
assertTrue("file should have been created", file.exists());
|
||||
}
|
||||
finally {
|
||||
assertTrue("failed to delete test file", f.delete());
|
||||
assertTrue("failed to delete test file", file.delete());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateProjectNoFileNameAvailable() throws Exception {
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", null);
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals(ExitStatus.ERROR, this.command.run());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateProjectAndExtract() throws Exception {
|
||||
File f = this.folder.newFolder();
|
||||
|
||||
File folder = this.temporaryFolder.newFolder();
|
||||
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", "demo.zip", archive);
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals(ExitStatus.OK,
|
||||
this.command.run("--extract", "--output=" + f.getAbsolutePath()));
|
||||
File archiveFile = new File(f, "test.txt");
|
||||
assertTrue(
|
||||
"Archive not extracted properly " + f.getAbsolutePath() + " not found",
|
||||
archiveFile.exists());
|
||||
this.command.run("--extract", "--output=" + folder.getAbsolutePath()));
|
||||
File archiveFile = new File(folder, "test.txt");
|
||||
assertTrue("Archive not extracted properly " + folder.getAbsolutePath()
|
||||
+ " not found", archiveFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateProjectAndExtractUnsupportedArchive() throws Exception {
|
||||
File f = this.folder.newFolder();
|
||||
File folder = this.temporaryFolder.newFolder();
|
||||
String fileName = UUID.randomUUID().toString() + ".zip";
|
||||
File archiveFile = new File(fileName);
|
||||
assertFalse("file should not exist", archiveFile.exists());
|
||||
|
||||
File file = new File(fileName);
|
||||
assertFalse("file should not exist", file.exists());
|
||||
try {
|
||||
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/foobar", fileName, archive);
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals(ExitStatus.OK,
|
||||
this.command.run("--extract", "--output=" + f.getAbsolutePath()));
|
||||
assertTrue("file should have been saved instead", archiveFile.exists());
|
||||
this.command.run("--extract", "--output=" + folder.getAbsolutePath()));
|
||||
assertTrue("file should have been saved instead", file.exists());
|
||||
}
|
||||
finally {
|
||||
assertTrue("failed to delete test file", archiveFile.delete());
|
||||
assertTrue("failed to delete test file", file.delete());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateProjectAndExtractUnknownContentType() throws Exception {
|
||||
File f = this.folder.newFolder();
|
||||
File folder = this.temporaryFolder.newFolder();
|
||||
String fileName = UUID.randomUUID().toString() + ".zip";
|
||||
File archiveFile = new File(fileName);
|
||||
assertFalse("file should not exist", archiveFile.exists());
|
||||
|
||||
File file = new File(fileName);
|
||||
assertFalse("file should not exist", file.exists());
|
||||
try {
|
||||
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
null, fileName, archive);
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals(ExitStatus.OK,
|
||||
this.command.run("--extract", "--output=" + f.getAbsolutePath()));
|
||||
assertTrue("file should have been saved instead", archiveFile.exists());
|
||||
this.command.run("--extract", "--output=" + folder.getAbsolutePath()));
|
||||
assertTrue("file should have been saved instead", file.exists());
|
||||
}
|
||||
finally {
|
||||
assertTrue("failed to delete test file", archiveFile.delete());
|
||||
assertTrue("failed to delete test file", file.delete());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileNotOverwrittenByDefault() throws Exception {
|
||||
File f = this.folder.newFile();
|
||||
long fileLength = f.length();
|
||||
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", f.getAbsolutePath());
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
|
||||
File file = this.temporaryFolder.newFile();
|
||||
long fileLength = file.length();
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", file.getAbsolutePath());
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals("Should have failed", ExitStatus.ERROR, this.command.run());
|
||||
assertEquals("File should not have changed", fileLength, f.length());
|
||||
assertEquals("File should not have changed", fileLength, file.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overwriteFile() throws Exception {
|
||||
File f = this.folder.newFile();
|
||||
long fileLength = f.length();
|
||||
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", f.getAbsolutePath());
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
File file = this.temporaryFolder.newFile();
|
||||
long fileLength = file.length();
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", file.getAbsolutePath());
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals("Should not have failed", ExitStatus.OK, this.command.run("--force"));
|
||||
assertTrue("File should have changed", fileLength != f.length());
|
||||
assertTrue("File should have changed", fileLength != file.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileInArchiveNotOverwrittenByDefault() throws Exception {
|
||||
File f = this.folder.newFolder();
|
||||
File conflict = new File(f, "test.txt");
|
||||
File folder = this.temporaryFolder.newFolder();
|
||||
File conflict = new File(folder, "test.txt");
|
||||
assertTrue("Should have been able to create file", conflict.createNewFile());
|
||||
long fileLength = conflict.length();
|
||||
|
||||
// also contains test.txt
|
||||
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", "demo.zip", archive);
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals(ExitStatus.ERROR,
|
||||
this.command.run("--extract", "--output=" + f.getAbsolutePath()));
|
||||
this.command.run("--extract", "--output=" + folder.getAbsolutePath()));
|
||||
assertEquals("File should not have changed", fileLength, conflict.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overwriteFileInArchive() throws Exception {
|
||||
File f = this.folder.newFolder();
|
||||
File conflict = new File(f, "test.txt");
|
||||
File folder = this.temporaryFolder.newFolder();
|
||||
File conflict = new File(folder, "test.txt");
|
||||
assertTrue("Should have been able to create file", conflict.createNewFile());
|
||||
long fileLength = conflict.length();
|
||||
|
||||
// also contains test.txt
|
||||
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
|
||||
MockHttpProjectGenerationRequest mockHttpRequest = new MockHttpProjectGenerationRequest(
|
||||
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
|
||||
"application/zip", "demo.zip", archive);
|
||||
mockSuccessfulProjectGeneration(mockHttpRequest);
|
||||
|
||||
mockSuccessfulProjectGeneration(request);
|
||||
assertEquals(
|
||||
ExitStatus.OK,
|
||||
this.command.run("--force", "--extract",
|
||||
"--output=" + f.getAbsolutePath()));
|
||||
"--output=" + folder.getAbsolutePath()));
|
||||
assertTrue("File should have changed", fileLength != conflict.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseProjectOptions() throws Exception {
|
||||
this.handler.disableProjectGeneration();
|
||||
this.command.run("-bv=1.2.0.RELEASE", "-d=web,data-jpa", "-jv=1.9", "-p=war",
|
||||
this.command.run("-b=1.2.0.RELEASE", "-d=web,data-jpa", "-j=1.9", "-p=war",
|
||||
"--build=grunt", "--format=web", "-t=ant-project");
|
||||
|
||||
assertEquals("1.2.0.RELEASE", this.handler.lastRequest.getBootVersion());
|
||||
List<String> dependencies = this.handler.lastRequest.getDependencies();
|
||||
assertEquals(2, dependencies.size());
|
||||
@@ -261,36 +248,34 @@ public class InitCommandTests extends AbstractHttpClientMockTests {
|
||||
public void parseOutput() throws Exception {
|
||||
this.handler.disableProjectGeneration();
|
||||
this.command.run("--output=foobar.zip");
|
||||
|
||||
assertEquals("foobar.zip", this.handler.lastRequest.getOutput());
|
||||
}
|
||||
|
||||
private byte[] createFakeZipArchive(String fileName, String content)
|
||||
throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ZipOutputStream zos = new ZipOutputStream(out);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ZipOutputStream zos = new ZipOutputStream(bos);
|
||||
try {
|
||||
ZipEntry entry = new ZipEntry(fileName);
|
||||
|
||||
zos.putNextEntry(entry);
|
||||
zos.write(content.getBytes());
|
||||
zos.closeEntry();
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
bos.close();
|
||||
}
|
||||
return out.toByteArray();
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
private static class TestableInitCommandOptionHandler extends
|
||||
InitCommandOptionHandler {
|
||||
InitCommand.InitOptionHandler {
|
||||
|
||||
private boolean disableProjectGeneration;
|
||||
|
||||
ProjectGenerationRequest lastRequest;
|
||||
private ProjectGenerationRequest lastRequest;
|
||||
|
||||
TestableInitCommandOptionHandler(CloseableHttpClient httpClient) {
|
||||
super(httpClient);
|
||||
public TestableInitCommandOptionHandler(InitializrService initializrService) {
|
||||
super(initializrService);
|
||||
}
|
||||
|
||||
void disableProjectGeneration() {
|
||||
@@ -298,14 +283,13 @@ public class InitCommandTests extends AbstractHttpClientMockTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExitStatus generateProject(OptionSet options,
|
||||
CloseableHttpClient httpClient) {
|
||||
protected void generateProject(OptionSet options) throws IOException {
|
||||
this.lastRequest = createProjectGenerationRequest(options);
|
||||
if (!this.disableProjectGeneration) {
|
||||
return super.generateProject(options, httpClient);
|
||||
super.generateProject(options);
|
||||
}
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ public class InitializrServiceMetadataTests {
|
||||
try {
|
||||
return new InitializrServiceMetadata(readJson(version));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to read json", e);
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to read json", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ public class InitializrServiceMetadataTests {
|
||||
+ ".json");
|
||||
InputStream stream = resource.getInputStream();
|
||||
try {
|
||||
String json = StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
|
||||
return new JSONObject(json);
|
||||
return new JSONObject(StreamUtils.copyToString(stream,
|
||||
Charset.forName("UTF-8")));
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
|
||||
@@ -33,17 +33,16 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link InitializrServiceHttpInvoker}
|
||||
* Tests for {@link InitializrService}
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTests {
|
||||
public class InitializrServiceTests extends AbstractHttpClientMockTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private final InitializrServiceHttpInvoker invoker = new InitializrServiceHttpInvoker(
|
||||
this.httpClient);
|
||||
private final InitializrService invoker = new InitializrService(this.http);
|
||||
|
||||
@Test
|
||||
public void loadMetadata() throws IOException {
|
||||
@@ -86,8 +85,7 @@ public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTes
|
||||
mockProjectGenerationError(400, jsonMessage);
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
request.getDependencies().add("foo:bar");
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage(jsonMessage);
|
||||
this.invoker.generate(request);
|
||||
}
|
||||
@@ -95,9 +93,8 @@ public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTes
|
||||
@Test
|
||||
public void generateProjectBadRequestNoExtraMessage() throws IOException {
|
||||
mockProjectGenerationError(400, null);
|
||||
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage("unexpected 400 error");
|
||||
this.invoker.generate(request);
|
||||
}
|
||||
@@ -105,14 +102,11 @@ public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTes
|
||||
@Test
|
||||
public void generateProjectNoContent() throws IOException {
|
||||
mockSuccessfulMetadataGet();
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
mockStatus(response, 500);
|
||||
when(this.httpClient.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
|
||||
when(this.http.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage("No content received from server");
|
||||
this.invoker.generate(request);
|
||||
}
|
||||
@@ -122,8 +116,7 @@ public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTes
|
||||
String jsonMessage = "whatever error on the server";
|
||||
mockMetadataGetError(500, jsonMessage);
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage(jsonMessage);
|
||||
this.invoker.generate(request);
|
||||
}
|
||||
@@ -133,10 +126,9 @@ public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTes
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
mockHttpEntity(response, "Foo-Bar-Not-JSON".getBytes(), "application/json");
|
||||
mockStatus(response, 200);
|
||||
when(this.httpClient.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
|
||||
when(this.http.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage("Invalid content received from server");
|
||||
this.invoker.generate(request);
|
||||
}
|
||||
@@ -145,11 +137,9 @@ public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTes
|
||||
public void loadMetadataNoContent() throws IOException {
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
mockStatus(response, 500);
|
||||
when(this.httpClient.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
|
||||
when(this.http.execute(isA(HttpGet.class))).thenReturn(response);
|
||||
ProjectGenerationRequest request = new ProjectGenerationRequest();
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage("No content received from server");
|
||||
this.invoker.generate(request);
|
||||
}
|
||||
@@ -105,7 +105,6 @@ public class ProjectGenerationRequestTests {
|
||||
ProjectType projectType = new ProjectType("custom", "Custom Type", "/foo", true,
|
||||
EMPTY_TAGS);
|
||||
InitializrServiceMetadata metadata = new InitializrServiceMetadata(projectType);
|
||||
|
||||
this.request.setType("custom");
|
||||
this.request.getDependencies().add("data-rest");
|
||||
assertEquals(new URI(ProjectGenerationRequest.DEFAULT_SERVICE_URL
|
||||
@@ -116,8 +115,7 @@ public class ProjectGenerationRequestTests {
|
||||
public void buildNoMatch() {
|
||||
InitializrServiceMetadata metadata = readMetadata();
|
||||
setBuildAndFormat("does-not-exist", null);
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage("does-not-exist");
|
||||
this.request.generateUrl(metadata);
|
||||
}
|
||||
@@ -126,8 +124,7 @@ public class ProjectGenerationRequestTests {
|
||||
public void buildMultipleMatch() {
|
||||
InitializrServiceMetadata metadata = readMetadata("types-conflict");
|
||||
setBuildAndFormat("gradle", null);
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage("gradle-project");
|
||||
this.thrown.expectMessage("gradle-project-2");
|
||||
this.request.generateUrl(metadata);
|
||||
@@ -137,7 +134,6 @@ public class ProjectGenerationRequestTests {
|
||||
public void buildOneMatch() {
|
||||
InitializrServiceMetadata metadata = readMetadata();
|
||||
setBuildAndFormat("gradle", null);
|
||||
|
||||
assertEquals(createDefaultUrl("?type=gradle-project"),
|
||||
this.request.generateUrl(metadata));
|
||||
}
|
||||
@@ -145,15 +141,13 @@ public class ProjectGenerationRequestTests {
|
||||
@Test
|
||||
public void invalidType() throws URISyntaxException {
|
||||
this.request.setType("does-not-exist");
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.request.generateUrl(createDefaultMetadata());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noTypeAndNoDefault() throws URISyntaxException {
|
||||
|
||||
this.thrown.expect(ProjectGenerationException.class);
|
||||
this.thrown.expect(ReportableException.class);
|
||||
this.thrown.expectMessage("no default is defined");
|
||||
this.request.generateUrl(readMetadata("types-conflict"));
|
||||
}
|
||||
@@ -163,8 +157,8 @@ public class ProjectGenerationRequestTests {
|
||||
return new URI(ProjectGenerationRequest.DEFAULT_SERVICE_URL + "/starter.zip"
|
||||
+ param);
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new IllegalStateException(e);
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +187,8 @@ public class ProjectGenerationRequestTests {
|
||||
JSONObject json = new JSONObject(content);
|
||||
return new InitializrServiceMetadata(json);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to read metadata", e);
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to read metadata", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,19 +23,19 @@ import org.junit.Test;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for {@link ListMetadataCommand}
|
||||
* Tests for {@link ServiceCapabilitiesReportGenerator}
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class ListMetadataCommandTests extends AbstractHttpClientMockTests {
|
||||
public class ServiceCapabilitiesReportGeneratorTests extends AbstractHttpClientMockTests {
|
||||
|
||||
private final ListMetadataCommand command = new ListMetadataCommand(this.httpClient);
|
||||
private final ServiceCapabilitiesReportGenerator command = new ServiceCapabilitiesReportGenerator(
|
||||
new InitializrService(this.http));
|
||||
|
||||
@Test
|
||||
public void listMetadata() throws IOException {
|
||||
mockSuccessfulMetadataGet();
|
||||
String content = this.command.generateReport("http://localhost");
|
||||
|
||||
String content = this.command.generate("http://localhost");
|
||||
assertTrue(content.contains("aop - AOP"));
|
||||
assertTrue(content.contains("security - Security: Security description"));
|
||||
assertTrue(content.contains("type: maven-project"));
|
||||
@@ -89,7 +89,6 @@
|
||||
<jersey.version>2.13</jersey.version>
|
||||
<joda-time.version>2.4</joda-time.version>
|
||||
<jolokia.version>1.2.2</jolokia.version>
|
||||
<json.version>20140107</json.version>
|
||||
<json-path.version>0.9.1</json-path.version>
|
||||
<jstl.version>1.2</jstl.version>
|
||||
<junit.version>4.11</junit.version>
|
||||
@@ -986,11 +985,6 @@
|
||||
<artifactId>jdom2</artifactId>
|
||||
<version>${jdom2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>${json.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
@@ -1504,4 +1498,4 @@
|
||||
<id>integration-test</id>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>..</main.basedir>
|
||||
<aether.version>0.9.1.v20140329</aether.version>
|
||||
<java.version>1.6</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<aether.version>0.9.1.v20140329</aether.version>
|
||||
<json.version>20140107</json.version>
|
||||
<maven.version>3.1.1</maven.version>
|
||||
</properties>
|
||||
<prerequisites>
|
||||
@@ -184,6 +185,11 @@
|
||||
<artifactId>gradle-plugins</artifactId>
|
||||
<version>${gradle.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>${json.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zeroturnaround</groupId>
|
||||
<artifactId>zt-zip</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user