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
pull/1815/head
Phillip Webb 10 years ago
parent b89e5e0ab7
commit 830ce80824

@ -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 String getId() {
return this.id;
public Dependency(String id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}
public void setId(String id) {
this.id = id;
public String getId() {
return this.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>

Loading…
Cancel
Save