diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/Dependency.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/Dependency.java index 909c0b39137..f810fc482cc 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/Dependency.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/Dependency.java @@ -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; - } - } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java index 1523c3df3b8..b0090efc318 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java @@ -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 target; + + private OptionSpec listCapabilities; + + private OptionSpec bootVersion; + + private OptionSpec dependencies; + + private OptionSpec javaVersion; + + private OptionSpec packaging; + + private OptionSpec build; + + private OptionSpec format; + + private OptionSpec type; + + private OptionSpec extract; + + private OptionSpec force; + + private OptionSpec 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; + } + } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommandOptionHandler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommandOptionHandler.java deleted file mode 100644 index 167cbfcfc71..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommandOptionHandler.java +++ /dev/null @@ -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 target; - - private OptionSpec listMetadata; - - // Project generation options - - private OptionSpec bootVersion; - - private OptionSpec dependencies; - - private OptionSpec javaVersion; - - private OptionSpec packaging; - - private OptionSpec build; - - private OptionSpec format; - - private OptionSpec type; - - // Other options - - private OptionSpec extract; - - private OptionSpec force; - - private OptionSpec 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(); - } - } - -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java new file mode 100644 index 00000000000..2be1cd7f83a --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java @@ -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; + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceHttpInvoker.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceHttpInvoker.java deleted file mode 100644 index 7b3f2d01408..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceHttpInvoker.java +++ /dev/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; - } - -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java index 22eab9fd36e..e15a429de54 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java @@ -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(); this.projectTypes = new MetadataHolder(); 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; } + } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ListMetadataCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ListMetadataCommand.java deleted file mode 100644 index 825efc2c045..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ListMetadataCommand.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2012-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.cli.command.init; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.apache.http.impl.client.CloseableHttpClient; -import org.codehaus.plexus.util.StringUtils; - -/** - * A helper class generating a report from the metadata of a particular service. - * - * @author Stephane Nicoll - * @since 1.2.0 - */ -class ListMetadataCommand { - - private static final String NEW_LINE = System.getProperty("line.separator"); - - private final InitializrServiceHttpInvoker initializrServiceInvoker; - - /** - * Creates an instance using the specified {@link CloseableHttpClient}. - */ - ListMetadataCommand(CloseableHttpClient httpClient) { - this.initializrServiceInvoker = new InitializrServiceHttpInvoker(httpClient); - } - - /** - * Generate a report for the specified service. The report contains the available - * capabilities as advertized by the root endpoint. - */ - String generateReport(String serviceUrl) throws IOException { - InitializrServiceMetadata metadata = this.initializrServiceInvoker - .loadMetadata(serviceUrl); - String header = "Capabilities of " + serviceUrl; - int size = header.length(); - - StringBuilder sb = new StringBuilder(); - sb.append(StringUtils.repeat("=", size)).append(NEW_LINE).append(header) - .append(NEW_LINE).append(StringUtils.repeat("=", size)).append(NEW_LINE) - .append(NEW_LINE).append("Available dependencies:").append(NEW_LINE) - .append("-----------------------").append(NEW_LINE); - - List dependencies = new ArrayList( - metadata.getDependencies()); - Collections.sort(dependencies, new Comparator() { - @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 typeIds = new ArrayList(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> it = type.getTags().entrySet() - .iterator(); - while (it.hasNext()) { - Map.Entry 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 defaultsKeys = new ArrayList(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(); - } - -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java index 8d75274730e..1a61d0af1ce 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java @@ -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)")); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationResponse.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationResponse.java index f00df17da73..063e002c815 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationResponse.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationResponse.java @@ -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. */ diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java new file mode 100644 index 00000000000..9fa35b97173 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java @@ -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 + "'"); + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectType.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectType.java index 98c234fc7a3..32393fa78d5 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectType.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectType.java @@ -68,4 +68,5 @@ class ProjectType { public Map getTags() { return Collections.unmodifiableMap(this.tags); } + } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationException.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ReportableException.java similarity index 74% rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationException.java rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ReportableException.java index 7237e0ff864..bf57818667e 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationException.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ReportableException.java @@ -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); + } + } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGenerator.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGenerator.java new file mode 100644 index 00000000000..1df8bb06a99 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGenerator.java @@ -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 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 getSortedDependencies(InitializrServiceMetadata metadata) { + ArrayList dependencies = new ArrayList( + metadata.getDependencies()); + Collections.sort(dependencies, new Comparator() { + @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 typeIds = new ArrayList(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 tags = type.getTags(); + Iterator> iterator = tags.entrySet().iterator(); + report.append(" ["); + while (iterator.hasNext()) { + Map.Entry 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 defaultsKeys = new ArrayList(metadata.getDefaults().keySet()); + Collections.sort(defaultsKeys); + for (String defaultsKey : defaultsKeys) { + String defaultsValue = metadata.getDefaults().get(defaultsKey); + report.append(defaultsKey + ": " + defaultsValue + NEW_LINE); + } + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/status/ExitStatus.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/status/ExitStatus.java index ccbcee4d2c9..6a0dbda6e90 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/status/ExitStatus.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/status/ExitStatus.java @@ -95,4 +95,9 @@ public final class ExitStatus { return new ExitStatus(this.code, this.name, true); } + @Override + public String toString() { + return getName() + ":" + getCode(); + } + } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/AbstractHttpClientMockTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/AbstractHttpClientMockTests.java index 3597850e294..1f3c6e06841 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/AbstractHttpClientMockTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/AbstractHttpClientMockTests.java @@ -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 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 { @@ -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()); } } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java index 89230f053c6..75dc737d862 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java @@ -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 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; } + } } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java index 9d0d0b302a2..32f2f8ef060 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java @@ -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(); diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceHttpInvokerTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceTests.java similarity index 87% rename from spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceHttpInvokerTests.java rename to spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceTests.java index d24192d66a3..808ba842c55 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceHttpInvokerTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceTests.java @@ -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); } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java index 69d9ec7f57f..96c6631e98c 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java @@ -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); } } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ListMetadataCommandTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGeneratorTests.java similarity index 76% rename from spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ListMetadataCommandTests.java rename to spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGeneratorTests.java index 02e1695b76f..0779006824b 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ListMetadataCommandTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ServiceCapabilitiesReportGeneratorTests.java @@ -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")); diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 37eaceeb126..111bcc6c83c 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -89,7 +89,6 @@ 2.13 2.4 1.2.2 - 20140107 0.9.1 1.2 4.11 @@ -986,11 +985,6 @@ jdom2 ${jdom2.version} - - org.json - json - ${json.version} - org.liquibase liquibase-core @@ -1504,4 +1498,4 @@ integration-test - \ No newline at end of file + diff --git a/spring-boot-parent/pom.xml b/spring-boot-parent/pom.xml index bd184c54850..0275a7c49e8 100644 --- a/spring-boot-parent/pom.xml +++ b/spring-boot-parent/pom.xml @@ -18,10 +18,11 @@ .. - 0.9.1.v20140329 1.6 UTF-8 UTF-8 + 0.9.1.v20140329 + 20140107 3.1.1 @@ -184,6 +185,11 @@ gradle-plugins ${gradle.version} + + org.json + json + ${json.version} + org.zeroturnaround zt-zip