Add init command to the CLI

This commit adds a new command to the CLI that allows to initialize a new
project from the command line. It uses the Spring initializr service to
actually generate the project.

The command offers two main operations:

1. Listing the capabilities of the service (--list or -l). This basically
dumps the defaults of a given service and the list of dependencies and
project types it supports
2. Generating a project. By default, http://start.spring.io is used and
its configured defaults are applied. Running spring init would therefore
have the same effect as clicking the 'generate project' on the UI without
entering any extra information. No file is overwritten by default.

The generation can be customized with the following options:

* --boot-version (-bv) Spring Boot version the project should use
* --dependencies (-d) comma separated list of dependencies to add to the
generated project
* --java-version (-jv) Java version to use
* --packaging (-p) the packaging for the project (jar, war)
* --target the url of the service to use

The actual type of the project can be defined in several ways:

1. Using the --type (-t) option that identifies a type that is supported
by the service
2. A combination of --build and/or --format that can be used to uniquely
identify matching these tags. Build represents the build system to use
(e.g. maven or gradle) while --format defines the format of the generated
project.

The project is saved on disk with the name provided by the server through
the Content-Disposition header, if any. It is possible to force it with
the --output option. It is possible to overwrite existing files by adding
the --force (-f) flag.

The --extract (-x) option allows to extract the project instead of saving
the zip archive. By default, the project is extracted in the current
working directory but it is possible to specify an alternate directory
using the --output option.

Fixes gh-1751
This commit is contained in:
Stephane Nicoll 2014-10-23 18:10:55 +02:00
parent 4b45ce9e57
commit b2fe2dd912
23 changed files with 2838 additions and 2 deletions

View File

@ -57,6 +57,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-aether-provider</artifactId>

View File

@ -43,7 +43,7 @@ public class CommandLineIT {
Invocation cli = this.cli.invoke("hint");
assertThat(cli.await(), equalTo(0));
assertThat(cli.getErrorOutput().length(), equalTo(0));
assertThat(cli.getStandardOutputLines().size(), equalTo(9));
assertThat(cli.getStandardOutputLines().size(), equalTo(10));
}
@Test

View File

@ -24,6 +24,7 @@ import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.CommandFactory;
import org.springframework.boot.cli.command.core.VersionCommand;
import org.springframework.boot.cli.command.grab.GrabCommand;
import org.springframework.boot.cli.command.init.InitCommand;
import org.springframework.boot.cli.command.install.InstallCommand;
import org.springframework.boot.cli.command.install.UninstallCommand;
import org.springframework.boot.cli.command.jar.JarCommand;
@ -39,7 +40,7 @@ public class DefaultCommandFactory implements CommandFactory {
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
new JarCommand(), new InstallCommand(), new UninstallCommand());
new JarCommand(), new InstallCommand(), new UninstallCommand(), new InitCommand());
@Override
public Collection<Command> getCommands() {

View File

@ -0,0 +1,56 @@
/*
* 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;
/**
* Provide some basic information about a dependency.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
class Dependency {
private String id;
private String name;
private String description;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -0,0 +1,40 @@
/*
* 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 org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand;
/**
* {@link Command} that initializes a project using Spring initializr.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
public class InitCommand extends OptionParsingCommand {
InitCommand(InitCommandOptionHandler handler) {
super("init", "Initialize a new project structure using Spring Initializr", handler);
}
public InitCommand() {
this(new InitCommandOptionHandler(HttpClientBuilder.create().build()));
}
}

View File

@ -0,0 +1,280 @@
/*
* 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(listMetadata)) {
return listServiceCapabilities(options, httpClient);
}
else {
return generateProject(options, 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();
}
}
}

View File

@ -0,0 +1,215 @@
/*
* 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;
}
}

View File

@ -0,0 +1,236 @@
/*
* 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.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Define the metadata available for a particular service instance.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
class InitializrServiceMetadata {
private static final String DEPENDENCIES_EL = "dependencies";
private static final String TYPES_EL = "types";
private static final String DEFAULTS_EL = "defaults";
private static final String CONTENT_EL = "content";
private static final String NAME_ATTRIBUTE = "name";
private static final String ID_ATTRIBUTE = "id";
private static final String DESCRIPTION_ATTRIBUTE = "description";
private static final String ACTION_ATTRIBUTE = "action";
private static final String DEFAULT_ATTRIBUTE = "default";
private final Map<String, Dependency> dependencies;
private final MetadataHolder<String, ProjectType> projectTypes;
private final Map<String, String> defaults;
/**
* Creates a new instance using the specified root {@link JSONObject}.
*/
InitializrServiceMetadata(JSONObject root) {
this.dependencies = parseDependencies(root);
this.projectTypes = parseProjectTypes(root);
this.defaults = Collections.unmodifiableMap(parseDefaults(root));
}
InitializrServiceMetadata(ProjectType defaultProjectType) {
this.dependencies = new HashMap<String, Dependency>();
this.projectTypes = new MetadataHolder<String, ProjectType>();
this.projectTypes.getContent().put(defaultProjectType.getId(), defaultProjectType);
this.projectTypes.setDefaultItem(defaultProjectType);
this.defaults = new HashMap<String, String>();
}
/**
* Return the dependencies supported by the service.
*/
public Collection<Dependency> getDependencies() {
return dependencies.values();
}
/**
* Return the dependency with the specified id or {@code null} if no
* such dependency exists.
*/
public Dependency getDependency(String id) {
return dependencies.get(id);
}
/**
* Return the project types supported by the service.
*/
public Map<String, ProjectType> getProjectTypes() {
return projectTypes.getContent();
}
/**
* Return the default type to use or {@code null} or the metadata does
* not define any default.
*/
public ProjectType getDefaultType() {
if (projectTypes.getDefaultItem() != null) {
return projectTypes.getDefaultItem();
}
String defaultTypeId = getDefaults().get("type");
if (defaultTypeId != null) {
return projectTypes.getContent().get(defaultTypeId);
}
return null;
}
/**
* Returns the defaults applicable to the service.
*/
public Map<String, String> getDefaults() {
return defaults;
}
private Map<String, Dependency> parseDependencies(JSONObject root) {
Map<String, Dependency> result = new HashMap<String, Dependency>();
if (!root.has(DEPENDENCIES_EL)) {
return result;
}
JSONArray array = root.getJSONArray(DEPENDENCIES_EL);
for (int i = 0; i < array.length(); i++) {
JSONObject group = array.getJSONObject(i);
parseGroup(group, result);
}
return result;
}
private MetadataHolder<String, ProjectType> parseProjectTypes(JSONObject root) {
MetadataHolder<String, ProjectType> result = new MetadataHolder<String, ProjectType>();
if (!root.has(TYPES_EL)) {
return result;
}
JSONArray array = root.getJSONArray(TYPES_EL);
for (int i = 0; i < array.length(); i++) {
JSONObject typeJson = array.getJSONObject(i);
ProjectType projectType = parseType(typeJson);
result.getContent().put(projectType.getId(), projectType);
if (projectType.isDefaultType()) {
result.setDefaultItem(projectType);
}
}
return result;
}
private Map<String, String> parseDefaults(JSONObject root) {
Map<String, String> result = new HashMap<String, String>();
if (!root.has(DEFAULTS_EL)) {
return result;
}
JSONObject defaults = root.getJSONObject(DEFAULTS_EL);
result.putAll(parseStringItems(defaults));
return result;
}
private void parseGroup(JSONObject group, Map<String, Dependency> dependencies) {
if (group.has(CONTENT_EL)) {
JSONArray content = group.getJSONArray(CONTENT_EL);
for (int i = 0; i < content.length(); i++) {
Dependency dependency = parseDependency(content.getJSONObject(i));
dependencies.put(dependency.getId(), dependency);
}
}
}
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;
}
private ProjectType parseType(JSONObject object) {
String id = getStringValue(object, ID_ATTRIBUTE, null);
String name = getStringValue(object, NAME_ATTRIBUTE, null);
String action = getStringValue(object, ACTION_ATTRIBUTE, null);
boolean defaultType = getBooleanValue(object, DEFAULT_ATTRIBUTE, false);
Map<String, String> tags = new HashMap<String, String>();
if (object.has("tags")) {
JSONObject jsonTags = object.getJSONObject("tags");
tags.putAll(parseStringItems(jsonTags));
}
return new ProjectType(id, name, action, defaultType, tags);
}
private String getStringValue(JSONObject object, String name, String defaultValue) {
return object.has(name) ? object.getString(name) : defaultValue;
}
private boolean getBooleanValue(JSONObject object, String name, boolean defaultValue) {
return object.has(name) ? object.getBoolean(name) : defaultValue;
}
private Map<String, String> parseStringItems(JSONObject json) {
Map<String, String> result = new HashMap<String, String>();
for (Object k : json.keySet()) {
String key = (String) k;
Object value = json.get(key);
if (value instanceof String) {
result.put(key, (String) value);
}
}
return result;
}
private static class MetadataHolder<K, T> {
private final Map<K, T> content;
private T defaultItem;
private MetadataHolder() {
this.content = new HashMap<K, T>();
}
public Map<K, T> getContent() {
return content;
}
public T getDefaultItem() {
return defaultItem;
}
public void setDefaultItem(T defaultItem) {
this.defaultItem = defaultItem;
}
}
}

View File

@ -0,0 +1,119 @@
/*
* 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 = 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();
}
}

View File

@ -0,0 +1,31 @@
/*
* 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;
/**
* Thrown when a project could not be generated.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
public class ProjectGenerationException extends RuntimeException {
public ProjectGenerationException(String message) {
super(message);
}
}

View File

@ -0,0 +1,257 @@
/*
* 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.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.http.client.utils.URIBuilder;
/**
* Represent the settings to apply to generating the project.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
class ProjectGenerationRequest {
public static final String DEFAULT_SERVICE_URL = "https://start.spring.io";
private String serviceUrl = DEFAULT_SERVICE_URL;
private String output;
private String bootVersion;
private List<String> dependencies = new ArrayList<String>();
private String javaVersion;
private String packaging;
private String build;
private String format;
private boolean detectType;
private String type;
/**
* The url of the service to use.
* @see #DEFAULT_SERVICE_URL
*/
public String getServiceUrl() {
return serviceUrl;
}
public void setServiceUrl(String serviceUrl) {
this.serviceUrl = serviceUrl;
}
/**
* The location of the generated project.
*/
public String getOutput() {
return output;
}
public void setOutput(String output) {
this.output = output;
}
/**
* The Spring Boot version to use or {@code null} if it should not be customized.
*/
public String getBootVersion() {
return bootVersion;
}
public void setBootVersion(String bootVersion) {
this.bootVersion = bootVersion;
}
/**
* The identifiers of the dependencies to include in the project.
*/
public List<String> getDependencies() {
return dependencies;
}
/**
* The Java version to use or {@code null} if it should not be customized.
*/
public String getJavaVersion() {
return javaVersion;
}
public void setJavaVersion(String javaVersion) {
this.javaVersion = javaVersion;
}
/**
* The packaging type or {@code null} if it should not be customized.
*/
public String getPackaging() {
return packaging;
}
public void setPackaging(String packaging) {
this.packaging = packaging;
}
/**
* The build type to use. Ignored if a type is set. Can be used alongside
* the {@link #getFormat() format} to identify the type to use.
*/
public String getBuild() {
return build;
}
public void setBuild(String build) {
this.build = build;
}
/**
* The project format to use. Ignored if a type is set. Can be used alongside
* the {@link #getBuild() build} to identify the type to use.
*/
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
/**
* Specify if the type should be detected based on the build
* and format value.
*/
public boolean isDetectType() {
return detectType;
}
public void setDetectType(boolean detectType) {
this.detectType = detectType;
}
/**
* The type of project to generate. Should match one of the advertized type
* that the service supports. If not set, the default is retrieved from
* the service metadata.
*/
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
/**
* Generates the URL to use to generate a project represented
* by this request
*/
URI generateUrl(InitializrServiceMetadata metadata) {
try {
URIBuilder builder = new URIBuilder(serviceUrl);
StringBuilder sb = new StringBuilder();
if (builder.getPath() != null) {
sb.append(builder.getPath());
}
ProjectType projectType = determineProjectType(metadata);
this.type = projectType.getId();
sb.append(projectType.getAction());
builder.setPath(sb.toString());
if (this.bootVersion != null) {
builder.setParameter("bootVersion", this.bootVersion);
}
for (String dependency : dependencies) {
builder.addParameter("style", dependency);
}
if (this.javaVersion != null) {
builder.setParameter("javaVersion", this.javaVersion);
}
if (this.packaging != null) {
builder.setParameter("packaging", this.packaging);
}
if (this.type != null) {
builder.setParameter("type", projectType.getId());
}
return builder.build();
}
catch (URISyntaxException e) {
throw new ProjectGenerationException("Invalid service URL (" + e.getMessage() + ")");
}
}
protected ProjectType determineProjectType(InitializrServiceMetadata metadata) {
if (this.type != null) {
ProjectType result = metadata.getProjectTypes().get(this.type);
if (result == null) {
throw new ProjectGenerationException(("No project type with id '" + this.type +
"' - check the service capabilities (--list)"));
}
}
if (isDetectType()) {
Map<String, ProjectType> types = new HashMap<String, ProjectType>(metadata.getProjectTypes());
if (this.build != null) {
filter(types, "build", this.build);
}
if (this.format != null) {
filter(types, "format", this.format);
}
if (types.size() == 1) {
return types.values().iterator().next();
}
else if (types.size() == 0) {
throw new ProjectGenerationException("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 '" + 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(("No project type is set and no default is defined. " +
"Check the service capabilities (--list)"));
}
return defaultType;
}
private static void filter(Map<String, ProjectType> projects, String tag, String tagValue) {
for (Iterator<Map.Entry<String, ProjectType>> it = projects.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, ProjectType> entry = it.next();
String value = entry.getValue().getTags().get(tag);
if (!tagValue.equals(value)) {
it.remove();
}
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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 org.apache.http.entity.ContentType;
/**
* Represent the response of a {@link ProjectGenerationRequest}
*
* @author Stephane Nicoll
* @since 1.2.0
*/
class ProjectGenerationResponse {
private ContentType contentType;
private byte[] content;
private String fileName;
ProjectGenerationResponse() {
}
/**
* Return the {@link ContentType} of this instance
*/
public ContentType getContentType() {
return this.contentType;
}
public void setContentType(ContentType contentType) {
this.contentType = contentType;
}
/**
* The generated project archive or file.
*/
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
/**
* The preferred file name to use to store the entity on disk or {@code null}
* if no preferred value has been set.
*/
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Represent a project type that is supported by a service.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
class ProjectType {
private final String id;
private final String name;
private final String action;
private final boolean defaultType;
private final Map<String, String> tags = new HashMap<String, String>();
public ProjectType(String id, String name, String action, boolean defaultType, Map<String, String> tags) {
this.id = id;
this.name = name;
this.action = action;
this.defaultType = defaultType;
if (tags != null) {
this.tags.putAll(tags);
}
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getAction() {
return action;
}
public boolean isDefaultType() {
return defaultType;
}
public Map<String, String> getTags() {
return Collections.unmodifiableMap(tags);
}
}

View File

@ -0,0 +1,183 @@
/*
* 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.IOException;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.hamcrest.Matcher;
import org.json.JSONObject;
import org.mockito.ArgumentMatcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import static org.mockito.Mockito.*;
/**
*
* @author Stephane Nicoll
*/
public abstract class AbstractHttpClientMockTests {
protected final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
protected void mockSuccessfulMetadataGet() throws IOException {
mockSuccessfulMetadataGet("1.1.0");
}
protected void mockSuccessfulMetadataGet(String version) throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
Resource resource = new ClassPathResource("metadata/service-metadata-" + version + ".json");
byte[] content = StreamUtils.copyToByteArray(resource.getInputStream());
mockHttpEntity(response, content, "application/json");
mockStatus(response, 200);
when(httpClient.execute(argThat(getForJsonData()))).thenReturn(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;
mockHttpHeader(response, "Content-Disposition", header);
when(httpClient.execute(argThat(getForNonJsonData()))).thenReturn(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(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
}
protected void mockMetadataGetError(int status, String message) throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockHttpEntity(response, createJsonError(status, message).getBytes(), "application/json");
mockStatus(response, status);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
}
protected HttpEntity mockHttpEntity(CloseableHttpResponse response, byte[] content, String contentType) {
try {
HttpEntity entity = mock(HttpEntity.class);
when(entity.getContent()).thenReturn(new ByteArrayInputStream(content));
Header contentTypeHeader = contentType != null ? new BasicHeader("Content-Type", contentType) : null;
when(entity.getContentType()).thenReturn(contentTypeHeader);
when(response.getEntity()).thenReturn(entity);
return entity;
}
catch (IOException e) {
throw new IllegalStateException("Should not happen", e);
}
}
protected void mockStatus(CloseableHttpResponse response, int status) {
StatusLine statusLine = mock(StatusLine.class);
when(statusLine.getStatusCode()).thenReturn(status);
when(response.getStatusLine()).thenReturn(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);
}
protected Matcher<HttpGet> getForJsonData() {
return new HasAcceptHeader("application/json", true);
}
protected Matcher<HttpGet> getForNonJsonData() {
return new HasAcceptHeader("application/json", false);
}
private String contentDispositionValue(String fileName) {
return "attachment; filename=\"" + fileName + "\"";
}
private String createJsonError(int status, String message) {
JSONObject json = new JSONObject();
json.put("status", status);
if (message != null) {
json.put("message", message);
}
return json.toString();
}
protected static class MockHttpProjectGenerationRequest {
String contentType;
String fileName;
byte[] content = new byte[] {0, 0, 0, 0};
public MockHttpProjectGenerationRequest(String contentType, String fileName, byte[] content) {
this.contentType = contentType;
this.fileName = fileName;
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> {
private final String value;
private final boolean shouldMatch;
public HasAcceptHeader(String value, boolean shouldMatch) {
this.value = value;
this.shouldMatch = shouldMatch;
}
@Override
public boolean matches(Object argument) {
if (!(argument instanceof HttpGet)) {
return false;
}
HttpGet get = (HttpGet) argument;
Header acceptHeader = get.getFirstHeader(HttpHeaders.ACCEPT);
if (shouldMatch) {
return acceptHeader != null && value.equals(acceptHeader.getValue());
}
else {
return acceptHeader == null || !value.equals(acceptHeader.getValue());
}
}
}
}

View File

@ -0,0 +1,296 @@
/*
* 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.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
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;
import org.springframework.boot.cli.command.status.ExitStatus;
import static org.junit.Assert.*;
/**
* Tests for {@link InitCommand}
*
* @author Stephane Nicoll
*/
public class InitCommandTests extends AbstractHttpClientMockTests {
@Rule
public final TemporaryFolder folder = new TemporaryFolder();
private final TestableInitCommandOptionHandler handler = new TestableInitCommandOptionHandler(httpClient);
private final InitCommand command = new InitCommand(handler);
@Test
public void listServiceCapabilities() throws Exception {
mockSuccessfulMetadataGet();
command.run("--list", "--target=http://fake-service");
}
@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("application/zip", fileName);
mockSuccessfulProjectGeneration(mockHttpRequest);
try {
assertEquals(ExitStatus.OK, command.run());
assertTrue("file should have been created", f.exists());
}
finally {
assertTrue("failed to delete test file", f.delete());
}
}
@Test
public void generateProjectNoFileNameAvailable() throws Exception {
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", null);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.ERROR, command.run());
}
@Test
public void generateProjectAndExtract() throws Exception {
File f = folder.newFolder();
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--extract", "--output=" + f.getAbsolutePath()));
File archiveFile = new File(f, "test.txt");
assertTrue("Archive not extracted properly " + f.getAbsolutePath() + " not found", archiveFile.exists());
}
@Test
public void generateProjectAndExtractUnsupportedArchive() throws Exception {
File f = folder.newFolder();
String fileName = UUID.randomUUID().toString() + ".zip";
File archiveFile = new File(fileName);
assertFalse("file should not exist", archiveFile.exists());
try {
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/foobar", fileName, archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--extract", "--output=" + f.getAbsolutePath()));
assertTrue("file should have been saved instead", archiveFile.exists());
}
finally {
assertTrue("failed to delete test file", archiveFile.delete());
}
}
@Test
public void generateProjectAndExtractUnknownContentType() throws Exception {
File f = folder.newFolder();
String fileName = UUID.randomUUID().toString() + ".zip";
File archiveFile = new File(fileName);
assertFalse("file should not exist", archiveFile.exists());
try {
byte[] archive = createFakeZipArchive("test.txt", "Fake content");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest(null, fileName, archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--extract", "--output=" + f.getAbsolutePath()));
assertTrue("file should have been saved instead", archiveFile.exists());
}
finally {
assertTrue("failed to delete test file", archiveFile.delete());
}
}
@Test
public void fileNotOverwrittenByDefault() throws Exception {
File f = folder.newFile();
long fileLength = f.length();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", f.getAbsolutePath());
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals("Should have failed", ExitStatus.ERROR, command.run());
assertEquals("File should not have changed", fileLength, f.length());
}
@Test
public void overwriteFile() throws Exception {
File f = folder.newFile();
long fileLength = f.length();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/zip", f.getAbsolutePath());
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals("Should not have failed", ExitStatus.OK, command.run("--force"));
assertTrue("File should have changed", fileLength != f.length());
}
@Test
public void fileInArchiveNotOverwrittenByDefault() throws Exception {
File f = folder.newFolder();
File conflict = new File(f, "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("application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.ERROR, command.run("--extract", "--output=" + f.getAbsolutePath()));
assertEquals("File should not have changed", fileLength, conflict.length());
}
@Test
public void overwriteFileInArchive() throws Exception {
File f = folder.newFolder();
File conflict = new File(f, "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("application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(mockHttpRequest);
assertEquals(ExitStatus.OK, command.run("--force", "--extract", "--output=" + f.getAbsolutePath()));
assertTrue("File should have changed", fileLength != conflict.length());
}
@Test
public void parseProjectOptions() throws Exception {
handler.disableProjectGeneration();
command.run("-bv=1.2.0.RELEASE", "-d=web,data-jpa", "-jv=1.9", "-p=war",
"--build=grunt", "--format=web", "-t=ant-project");
assertEquals("1.2.0.RELEASE", handler.lastRequest.getBootVersion());
List<String> dependencies = handler.lastRequest.getDependencies();
assertEquals(2, dependencies.size());
assertTrue(dependencies.contains("web"));
assertTrue(dependencies.contains("data-jpa"));
assertEquals("1.9", handler.lastRequest.getJavaVersion());
assertEquals("war", handler.lastRequest.getPackaging());
assertEquals("grunt", handler.lastRequest.getBuild());
assertEquals("web", handler.lastRequest.getFormat());
assertEquals("ant-project", handler.lastRequest.getType());
}
@Test
public void parseTypeOnly() throws Exception {
handler.disableProjectGeneration();
command.run("-t=ant-project");
assertEquals("maven", handler.lastRequest.getBuild());
assertEquals("project", handler.lastRequest.getFormat());
assertFalse(handler.lastRequest.isDetectType());
assertEquals("ant-project", handler.lastRequest.getType());
}
@Test
public void parseBuildOnly() throws Exception {
handler.disableProjectGeneration();
command.run("--build=ant");
assertEquals("ant", handler.lastRequest.getBuild());
assertEquals("project", handler.lastRequest.getFormat());
assertTrue(handler.lastRequest.isDetectType());
assertNull(handler.lastRequest.getType());
}
@Test
public void parseFormatOnly() throws Exception {
handler.disableProjectGeneration();
command.run("--format=web");
assertEquals("maven", handler.lastRequest.getBuild());
assertEquals("web", handler.lastRequest.getFormat());
assertTrue(handler.lastRequest.isDetectType());
assertNull(handler.lastRequest.getType());
}
@Test
public void parseOutput() throws Exception {
handler.disableProjectGeneration();
command.run("--output=foobar.zip");
assertEquals("foobar.zip", handler.lastRequest.getOutput());
}
private byte[] createFakeZipArchive(String fileName, String content) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(out);
try {
ZipEntry entry = new ZipEntry(fileName);
zos.putNextEntry(entry);
zos.write(content.getBytes());
zos.closeEntry();
}
finally {
out.close();
}
return out.toByteArray();
}
private static class TestableInitCommandOptionHandler extends InitCommandOptionHandler {
private boolean disableProjectGeneration;
ProjectGenerationRequest lastRequest;
TestableInitCommandOptionHandler(CloseableHttpClient httpClient) {
super(httpClient);
}
void disableProjectGeneration() {
disableProjectGeneration = true;
}
@Override
protected ExitStatus generateProject(OptionSet options, CloseableHttpClient httpClient) {
lastRequest = createProjectGenerationRequest(options);
if (!disableProjectGeneration) {
return super.generateProject(options, httpClient);
}
return ExitStatus.OK;
}
}
}

View File

@ -0,0 +1,177 @@
/*
* 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 org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;
/**
* Tests for {@link InitializrServiceHttpInvoker}
*
* @author Stephane Nicoll
*/
public class InitializrServiceHttpInvokerTests extends AbstractHttpClientMockTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final InitializrServiceHttpInvoker invoker = new InitializrServiceHttpInvoker(httpClient);
@Test
public void loadMetadata() throws IOException {
mockSuccessfulMetadataGet();
InitializrServiceMetadata metadata = invoker.loadMetadata("http://foo/bar");
assertNotNull(metadata);
}
@Test
public void generateSimpleProject() throws IOException {
ProjectGenerationRequest request = new ProjectGenerationRequest();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/xml", "foo.zip");
ProjectGenerationResponse entity = generateProject(request, mockHttpRequest);
assertProjectEntity(entity, mockHttpRequest.contentType, mockHttpRequest.fileName);
}
@Test
public void generateProjectCustomTargetFilename() throws IOException {
ProjectGenerationRequest request = new ProjectGenerationRequest();
request.setOutput("bar.zip");
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/xml", null);
ProjectGenerationResponse entity = generateProject(request, mockHttpRequest);
assertProjectEntity(entity, mockHttpRequest.contentType, null);
}
@Test
public void generateProjectNoDefaultFileName() throws IOException {
ProjectGenerationRequest request = new ProjectGenerationRequest();
MockHttpProjectGenerationRequest mockHttpRequest =
new MockHttpProjectGenerationRequest("application/xml", null);
ProjectGenerationResponse entity = generateProject(request, mockHttpRequest);
assertProjectEntity(entity, mockHttpRequest.contentType, null);
}
@Test
public void generateProjectBadRequest() throws IOException {
String jsonMessage = "Unknown dependency foo:bar";
mockProjectGenerationError(400, jsonMessage);
ProjectGenerationRequest request = new ProjectGenerationRequest();
request.getDependencies().add("foo:bar");
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage(jsonMessage);
invoker.generate(request);
}
@Test
public void generateProjectBadRequestNoExtraMessage() throws IOException {
mockProjectGenerationError(400, null);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("unexpected 400 error");
invoker.generate(request);
}
@Test
public void generateProjectNoContent() throws IOException {
mockSuccessfulMetadataGet();
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockStatus(response, 500);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("No content received from server");
invoker.generate(request);
}
@Test
public void loadMetadataBadRequest() throws IOException {
String jsonMessage = "whatever error on the server";
mockMetadataGetError(500, jsonMessage);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage(jsonMessage);
invoker.generate(request);
}
@Test
public void loadMetadataInvalidJson() throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockHttpEntity(response, "Foo-Bar-Not-JSON".getBytes(), "application/json");
mockStatus(response, 200);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("Invalid content received from server");
invoker.generate(request);
}
@Test
public void loadMetadataNoContent() throws IOException {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
mockStatus(response, 500);
when(httpClient.execute(isA(HttpGet.class))).thenReturn(response);
ProjectGenerationRequest request = new ProjectGenerationRequest();
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("No content received from server");
invoker.generate(request);
}
private ProjectGenerationResponse generateProject(ProjectGenerationRequest request,
MockHttpProjectGenerationRequest mockRequest) throws IOException {
mockSuccessfulProjectGeneration(mockRequest);
ProjectGenerationResponse entity = invoker.generate(request);
assertArrayEquals("wrong body content", mockRequest.content, entity.getContent());
return entity;
}
private static void assertProjectEntity(ProjectGenerationResponse entity, String mimeType, String fileName) {
if (mimeType == null) {
assertNull("No content type expected", entity.getContentType());
}
else {
assertEquals("wrong mime type", mimeType, entity.getContentType().getMimeType());
}
assertEquals("wrong filename", fileName, entity.getFileName());
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.nio.charset.Charset;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import static org.junit.Assert.*;
/**
* Tests for {@link InitializrServiceMetadata}
*
* @author Stephane Nicoll
*/
public class InitializrServiceMetadataTests {
@Test
public void parseDefaults() {
InitializrServiceMetadata metadata = createInstance("1.0.0");
assertEquals("maven-project", metadata.getDefaults().get("type"));
assertEquals("jar", metadata.getDefaults().get("packaging"));
assertEquals("java", metadata.getDefaults().get("language"));
}
@Test
public void parseDependencies() {
InitializrServiceMetadata metadata = createInstance("1.0.0");
assertEquals(5, metadata.getDependencies().size());
// Security description
assertEquals("AOP", metadata.getDependency("aop").getName());
assertEquals("Security", metadata.getDependency("security").getName());
assertEquals("Security description", metadata.getDependency("security").getDescription());
assertEquals("JDBC", metadata.getDependency("jdbc").getName());
assertEquals("JPA", metadata.getDependency("data-jpa").getName());
assertEquals("MongoDB", metadata.getDependency("data-mongodb").getName());
}
@Test
public void parseTypesNoTag() {
InitializrServiceMetadata metadata = createInstance("1.0.0");
ProjectType projectType = metadata.getProjectTypes().get("maven-project");
assertNotNull(projectType);
assertEquals(0, projectType.getTags().size());
}
@Test
public void parseTypesWithTags() {
InitializrServiceMetadata metadata = createInstance("1.1.0");
ProjectType projectType = metadata.getProjectTypes().get("maven-project");
assertNotNull(projectType);
assertEquals("maven", projectType.getTags().get("build"));
assertEquals("project", projectType.getTags().get("format"));
}
private static InitializrServiceMetadata createInstance(String version) {
try {
return new InitializrServiceMetadata(readJson(version));
}
catch (IOException e) {
throw new IllegalStateException("Failed to read json", e);
}
}
private static JSONObject readJson(String version) throws IOException {
Resource resource = new ClassPathResource("metadata/service-metadata-" + version + ".json");
InputStream stream = resource.getInputStream();
try {
String json = StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
return new JSONObject(json);
}
finally {
stream.close();
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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 org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests for {@link ListMetadataCommand}
*
* @author Stephane Nicoll
*/
public class ListMetadataCommandTests extends AbstractHttpClientMockTests {
private final ListMetadataCommand command = new ListMetadataCommand(httpClient);
@Test
public void listMetadata() throws IOException {
mockSuccessfulMetadataGet();
String content = command.generateReport("http://localhost");
assertTrue(content.contains("aop - AOP"));
assertTrue(content.contains("security - Security: Security description"));
assertTrue(content.contains("type: maven-project"));
assertTrue(content.contains("packaging: jar"));
}
}

View File

@ -0,0 +1,194 @@
/*
* 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.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Map;
import org.json.JSONObject;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import static org.junit.Assert.*;
/**
* Tests for {@link ProjectGenerationRequest}
*
* @author Stephane Nicoll
*/
public class ProjectGenerationRequestTests {
public static final Map<String, String> EMPTY_TAGS = Collections.<String, String>emptyMap();
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final ProjectGenerationRequest request = new ProjectGenerationRequest();
@Test
public void defaultSettings() {
assertEquals(createDefaultUrl("?type=test-type"), request.generateUrl(createDefaultMetadata()));
}
@Test
public void customServer() throws URISyntaxException {
String customServerUrl = "http://foo:8080/initializr";
request.setServiceUrl(customServerUrl);
request.getDependencies().add("security");
assertEquals(new URI(customServerUrl + "/starter.zip?style=security&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customBootVersion() {
request.setBootVersion("1.2.0.RELEASE");
assertEquals(createDefaultUrl("?bootVersion=1.2.0.RELEASE&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void singleDependency() {
request.getDependencies().add("web");
assertEquals(createDefaultUrl("?style=web&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void multipleDependencies() {
request.getDependencies().add("web");
request.getDependencies().add("data-jpa");
assertEquals(createDefaultUrl("?style=web&style=data-jpa&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customJavaVersion() {
request.setJavaVersion("1.8");
assertEquals(createDefaultUrl("?javaVersion=1.8&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customPackaging() {
request.setPackaging("war");
assertEquals(createDefaultUrl("?packaging=war&type=test-type"),
request.generateUrl(createDefaultMetadata()));
}
@Test
public void customType() throws URISyntaxException {
ProjectType projectType = new ProjectType("custom", "Custom Type", "/foo", true, EMPTY_TAGS);
InitializrServiceMetadata metadata = new InitializrServiceMetadata(projectType);
request.setType("custom");
request.getDependencies().add("data-rest");
assertEquals(new URI(ProjectGenerationRequest.DEFAULT_SERVICE_URL + "/foo?style=data-rest&type=custom"),
request.generateUrl(metadata));
}
@Test
public void buildNoMatch() {
InitializrServiceMetadata metadata = readMetadata();
setBuildAndFormat("does-not-exist", null);
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("does-not-exist");
request.generateUrl(metadata);
}
@Test
public void buildMultipleMatch() {
InitializrServiceMetadata metadata = readMetadata("types-conflict");
setBuildAndFormat("gradle", null);
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("gradle-project");
thrown.expectMessage("gradle-project-2");
request.generateUrl(metadata);
}
@Test
public void buildOneMatch() {
InitializrServiceMetadata metadata = readMetadata();
setBuildAndFormat("gradle", null);
assertEquals(createDefaultUrl("?type=gradle-project"), request.generateUrl(metadata));
}
@Test
public void invalidType() throws URISyntaxException {
request.setType("does-not-exist");
thrown.expect(ProjectGenerationException.class);
request.generateUrl(createDefaultMetadata());
}
@Test
public void noTypeAndNoDefault() throws URISyntaxException {
thrown.expect(ProjectGenerationException.class);
thrown.expectMessage("no default is defined");
request.generateUrl(readMetadata("types-conflict"));
}
private static URI createDefaultUrl(String param) {
try {
return new URI(ProjectGenerationRequest.DEFAULT_SERVICE_URL + "/starter.zip" + param);
}
catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}
public void setBuildAndFormat(String build, String format) {
request.setBuild(build != null ? build : "maven");
request.setFormat(format != null ? format : "project");
request.setDetectType(true);
}
private static InitializrServiceMetadata createDefaultMetadata() {
ProjectType projectType = new ProjectType("test-type", "The test type", "/starter.zip", true, EMPTY_TAGS);
return new InitializrServiceMetadata(projectType);
}
private static InitializrServiceMetadata readMetadata() {
return readMetadata("1.1.0");
}
private static InitializrServiceMetadata readMetadata(String version) {
try {
Resource resource = new ClassPathResource("metadata/service-metadata-" + version + ".json");
String content = StreamUtils.copyToString(resource.getInputStream(), Charset.forName("UTF-8"));
JSONObject json = new JSONObject(content);
return new InitializrServiceMetadata(json);
}
catch (IOException e) {
throw new IllegalStateException("Failed to read metadata", e);
}
}
}

View File

@ -0,0 +1,134 @@
{"dependencies": [
{
"name": "Core",
"content": [
{
"name": "Security",
"id": "security",
"description": "Security description"
},
{
"name": "AOP",
"id": "aop"
}
]
},
{
"name": "Data",
"content": [
{
"name": "JDBC",
"id": "jdbc"
},
{
"name": "JPA",
"id": "data-jpa"
},
{
"name": "MongoDB",
"id": "data-mongodb"
}
]
}
], "types": [
{
"name": "Maven POM",
"id": "maven-build",
"action": "/pom.xml",
"default": false
},
{
"name": "Maven Project",
"id": "maven-project",
"action": "/starter.zip",
"default": true
},
{
"name": "Gradle Config",
"id": "gradle-build",
"action": "/build.gradle",
"default": false
},
{
"name": "Gradle Project",
"id": "gradle-project",
"action": "/starter.zip",
"default": false
}
], "packagings": [
{
"name": "Jar",
"id": "jar",
"default": true
},
{
"name": "War",
"id": "war",
"default": false
}
], "javaVersions": [
{
"name": "1.6",
"id": "1.6",
"default": false
},
{
"name": "1.7",
"id": "1.7",
"default": true
},
{
"name": "1.8",
"id": "1.8",
"default": false
}
], "languages": [
{
"name": "Groovy",
"id": "groovy",
"default": false
},
{
"name": "Java",
"id": "java",
"default": true
}
], "bootVersions": [
{
"name": "1.2.0 M2",
"id": "1.2.0.M2",
"default": false
},
{
"name": "1.2.0 (SNAPSHOT)",
"id": "1.2.0.BUILD-SNAPSHOT",
"default": false
},
{
"name": "1.1.8",
"id": "1.1.8.RELEASE",
"default": true
},
{
"name": "1.1.8 (SNAPSHOT)",
"id": "1.1.8.BUILD-SNAPSHOT",
"default": false
},
{
"name": "1.0.2",
"id": "1.0.2.RELEASE",
"default": false
}
], "defaults": {
"groupId": "org.test",
"artifactId": "demo",
"version": "0.0.1-SNAPSHOT",
"name": "demo",
"description": "Demo project for Spring Boot",
"packageName": "demo",
"type": "maven-project",
"packaging": "jar",
"javaVersion": "1.7",
"language": "java",
"bootVersion": "1.1.8.RELEASE"
}}

View File

@ -0,0 +1,150 @@
{"dependencies": [
{
"name": "Core",
"content": [
{
"name": "Security",
"id": "security",
"description": "Security description"
},
{
"name": "AOP",
"id": "aop"
}
]
},
{
"name": "Data",
"content": [
{
"name": "JDBC",
"id": "jdbc"
},
{
"name": "JPA",
"id": "data-jpa"
},
{
"name": "MongoDB",
"id": "data-mongodb"
}
]
}
], "types": [
{
"name": "Maven POM",
"id": "maven-build",
"action": "/pom.xml",
"tags": {
"build": "maven",
"format": "build"
},
"default": false
},
{
"name": "Maven Project",
"id": "maven-project",
"action": "/starter.zip",
"tags": {
"build": "maven",
"format": "project"
},
"default": true
},
{
"name": "Gradle Config",
"id": "gradle-build",
"action": "/build.gradle",
"tags": {
"build": "gradle",
"format": "build"
},
"default": false
},
{
"name": "Gradle Project",
"id": "gradle-project",
"action": "/starter.zip",
"tags": {
"build": "gradle",
"format": "project"
},
"default": false
}
], "packagings": [
{
"name": "Jar",
"id": "jar",
"default": true
},
{
"name": "War",
"id": "war",
"default": false
}
], "javaVersions": [
{
"name": "1.6",
"id": "1.6",
"default": false
},
{
"name": "1.7",
"id": "1.7",
"default": true
},
{
"name": "1.8",
"id": "1.8",
"default": false
}
], "languages": [
{
"name": "Groovy",
"id": "groovy",
"default": false
},
{
"name": "Java",
"id": "java",
"default": true
}
], "bootVersions": [
{
"name": "1.2.0 M2",
"id": "1.2.0.M2",
"default": false
},
{
"name": "1.2.0 (SNAPSHOT)",
"id": "1.2.0.BUILD-SNAPSHOT",
"default": false
},
{
"name": "1.1.8",
"id": "1.1.8.RELEASE",
"default": true
},
{
"name": "1.1.8 (SNAPSHOT)",
"id": "1.1.8.BUILD-SNAPSHOT",
"default": false
},
{
"name": "1.0.2",
"id": "1.0.2.RELEASE",
"default": false
}
], "defaults": {
"groupId": "org.test",
"artifactId": "demo",
"version": "0.0.1-SNAPSHOT",
"name": "demo",
"description": "Demo project for Spring Boot",
"packageName": "demo",
"type": "maven-project",
"packaging": "jar",
"javaVersion": "1.7",
"language": "java",
"bootVersion": "1.1.8.RELEASE"
}}

View File

@ -0,0 +1,169 @@
{"dependencies": [
{
"name": "Core",
"content": [
{
"name": "Security",
"id": "security",
"description": "Security description"
},
{
"name": "AOP",
"id": "aop"
}
]
},
{
"name": "Data",
"content": [
{
"name": "JDBC",
"id": "jdbc"
},
{
"name": "JPA",
"id": "data-jpa"
},
{
"name": "MongoDB",
"id": "data-mongodb"
}
]
}
], "types": [
{
"name": "Maven POM",
"id": "maven-build",
"action": "/pom.xml",
"tags": {
"build": "maven",
"format": "build"
},
"default": false
},
{
"name": "Maven Project",
"id": "maven-project",
"action": "/starter.zip",
"tags": {
"build": "maven",
"format": "project"
},
"default": false
},
{
"name": "Another Maven Project",
"id": "maven-project-2",
"action": "/starter.zip",
"tags": {
"build": "maven",
"format": "project"
},
"default": false
},
{
"name": "Gradle Config",
"id": "gradle-build",
"action": "/build.gradle",
"tags": {
"build": "gradle",
"format": "build"
},
"default": false
},
{
"name": "Gradle Project",
"id": "gradle-project",
"action": "/starter.zip",
"tags": {
"build": "gradle",
"format": "project"
},
"default": false
},
{
"name": "Another gradle Project",
"id": "gradle-project-2",
"action": "/starter.zip",
"tags": {
"build": "gradle",
"format": "project"
},
"default": false
}
], "packagings": [
{
"name": "Jar",
"id": "jar",
"default": true
},
{
"name": "War",
"id": "war",
"default": false
}
], "javaVersions": [
{
"name": "1.6",
"id": "1.6",
"default": false
},
{
"name": "1.7",
"id": "1.7",
"default": true
},
{
"name": "1.8",
"id": "1.8",
"default": false
}
], "languages": [
{
"name": "Groovy",
"id": "groovy",
"default": false
},
{
"name": "Java",
"id": "java",
"default": true
}
], "bootVersions": [
{
"name": "1.2.0 M2",
"id": "1.2.0.M2",
"default": false
},
{
"name": "1.2.0 (SNAPSHOT)",
"id": "1.2.0.BUILD-SNAPSHOT",
"default": false
},
{
"name": "1.1.8",
"id": "1.1.8.RELEASE",
"default": true
},
{
"name": "1.1.8 (SNAPSHOT)",
"id": "1.1.8.BUILD-SNAPSHOT",
"default": false
},
{
"name": "1.0.2",
"id": "1.0.2.RELEASE",
"default": false
}
], "defaults": {
"groupId": "org.test",
"artifactId": "demo",
"version": "0.0.1-SNAPSHOT",
"name": "demo",
"description": "Demo project for Spring Boot",
"packageName": "demo",
"packaging": "jar",
"javaVersion": "1.7",
"language": "java",
"bootVersion": "1.1.8.RELEASE"
}}

View File

@ -91,6 +91,7 @@
<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>
@ -987,6 +988,11 @@
<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>