diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml
index e11de7fffe8..d5e383a827a 100644
--- a/spring-boot-cli/pom.xml
+++ b/spring-boot-cli/pom.xml
@@ -122,6 +122,21 @@
groovy-templates
provided
+
+ org.springframework.boot
+ spring-boot
+ provided
+
+
+ org.springframework
+ spring-web
+ provided
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
junit
junit
diff --git a/spring-boot-cli/src/it/java/org/springframework/boot/cli/CommandLineIT.java b/spring-boot-cli/src/it/java/org/springframework/boot/cli/CommandLineIT.java
index 0a6e14fe440..e19a85e0e73 100644
--- a/spring-boot-cli/src/it/java/org/springframework/boot/cli/CommandLineIT.java
+++ b/spring-boot-cli/src/it/java/org/springframework/boot/cli/CommandLineIT.java
@@ -44,7 +44,7 @@ public class CommandLineIT {
assertThat(cli.await(), equalTo(0));
assertThat("Unexpected error: \n" + cli.getErrorOutput(),
cli.getErrorOutput().length(), equalTo(0));
- assertThat(cli.getStandardOutputLines().size(), equalTo(10));
+ assertThat(cli.getStandardOutputLines().size(), equalTo(11));
}
@Test
diff --git a/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java b/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java
index 62494e157e2..c59e8fb37f2 100644
--- a/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java
+++ b/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java
@@ -19,7 +19,7 @@ package org.springframework.boot.cli;
import java.io.File;
import org.junit.Test;
-import org.springframework.boot.cli.command.jar.JarCommand;
+import org.springframework.boot.cli.command.archive.JarCommand;
import org.springframework.boot.cli.infrastructure.CommandLineInvoker;
import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation;
import org.springframework.boot.loader.tools.JavaExecutable;
diff --git a/spring-boot-cli/src/it/java/org/springframework/boot/cli/WarCommandIT.java b/spring-boot-cli/src/it/java/org/springframework/boot/cli/WarCommandIT.java
new file mode 100644
index 00000000000..4af85a41e3a
--- /dev/null
+++ b/spring-boot-cli/src/it/java/org/springframework/boot/cli/WarCommandIT.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012-2015 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;
+
+import java.io.File;
+
+import org.junit.Test;
+import org.springframework.boot.cli.command.archive.WarCommand;
+import org.springframework.boot.cli.infrastructure.CommandLineInvoker;
+import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation;
+import org.springframework.boot.loader.tools.JavaExecutable;
+import org.springframework.util.SocketUtils;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Integration test for {@link WarCommand}.
+ *
+ * @author Andrey Stolyarov
+ */
+public class WarCommandIT {
+
+ private final CommandLineInvoker cli = new CommandLineInvoker(
+ new File("src/it/resources/war-command"));
+
+ @Test
+ public void warCreation() throws Exception {
+ int port = SocketUtils.findAvailableTcpPort();
+ File war = new File("target/test-app.war");
+ Invocation invocation = this.cli.invoke("war", war.getAbsolutePath(),
+ "war.groovy");
+ invocation.await();
+ assertTrue(war.exists());
+ Process process = new JavaExecutable()
+ .processBuilder("-jar", war.getAbsolutePath(), "--server.port=" + port)
+ .start();
+ invocation = new Invocation(process);
+ invocation.await();
+ assertThat(invocation.getErrorOutput(), containsString("onStart error"));
+ assertThat(invocation.getStandardOutput(), containsString("Tomcat started"));
+ assertThat(invocation.getStandardOutput(),
+ containsString("/WEB-INF/lib-provided/tomcat-embed-core"));
+ assertThat(invocation.getStandardOutput(),
+ containsString("/WEB-INF/lib-provided/tomcat-embed-core"));
+ process.destroy();
+ }
+
+}
diff --git a/spring-boot-cli/src/it/resources/war-command/war.groovy b/spring-boot-cli/src/it/resources/war-command/war.groovy
new file mode 100644
index 00000000000..1a8ca70ecad
--- /dev/null
+++ b/spring-boot-cli/src/it/resources/war-command/war.groovy
@@ -0,0 +1,16 @@
+package org.test
+
+@RestController
+class WarExample implements CommandLineRunner {
+
+ @RequestMapping("/")
+ public String hello() {
+ return "Hello"
+ }
+
+ void run(String... args) {
+ println getClass().getResource('/org/apache/tomcat/InstanceManager.class')
+ throw new RuntimeException("onStart error")
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java
index c13a6c7b899..b809c9d56ec 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/DefaultCommandFactory.java
@@ -22,12 +22,13 @@ import java.util.List;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.CommandFactory;
+import org.springframework.boot.cli.command.archive.JarCommand;
+import org.springframework.boot.cli.command.archive.WarCommand;
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;
import org.springframework.boot.cli.command.run.RunCommand;
import org.springframework.boot.cli.command.test.TestCommand;
@@ -40,7 +41,7 @@ public class DefaultCommandFactory implements CommandFactory {
private static final List DEFAULT_COMMANDS = Arrays.asList(
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
- new JarCommand(), new InstallCommand(), new UninstallCommand(),
+ new JarCommand(), new WarCommand(), new InstallCommand(), new UninstallCommand(),
new InitCommand());
@Override
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/app/SpringApplicationWebApplicationInitializer.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/app/SpringApplicationWebApplicationInitializer.java
new file mode 100644
index 00000000000..80023f0f47f
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/app/SpringApplicationWebApplicationInitializer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012-2015 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.app;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.Manifest;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.web.SpringBootServletInitializer;
+
+/**
+ * {@link SpringBootServletInitializer} for CLI packaged WAR files.
+ *
+ * @author Phillip Webb
+ * @since 1.3.0
+ */
+public class SpringApplicationWebApplicationInitializer
+ extends SpringBootServletInitializer {
+
+ /**
+ * The entry containing the source class.
+ */
+ public static final String SOURCE_ENTRY = "Spring-Application-Source-Classes";
+
+ private String[] sources;
+
+ @Override
+ public void onStartup(ServletContext servletContext) throws ServletException {
+ try {
+ this.sources = getSources(servletContext);
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ super.onStartup(servletContext);
+ }
+
+ private String[] getSources(ServletContext servletContext) throws IOException {
+ Manifest manifest = getManifest(servletContext);
+ if (manifest == null) {
+ throw new IllegalStateException("Unable to read manifest");
+ }
+ String sources = manifest.getMainAttributes().getValue(SOURCE_ENTRY);
+ return sources.split(",");
+ }
+
+ private Manifest getManifest(ServletContext servletContext) throws IOException {
+ InputStream stream = servletContext.getResourceAsStream("/META-INF/MANIFEST.MF");
+ Manifest manifest = (stream == null ? null : new Manifest(stream));
+ return manifest;
+ }
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
+ try {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ Class>[] sourceClasses = new Class>[this.sources.length];
+ for (int i = 0; i < this.sources.length; i++) {
+ sourceClasses[i] = classLoader.loadClass(this.sources[i]);
+ }
+ return builder.sources(sourceClasses)
+ .properties("spring.groovy.template.check-template-location=false");
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.java
similarity index 98%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.java
index 36dce8e53ec..e05c6d12c9a 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.jar;
+package org.springframework.boot.cli.archive;
import java.net.URL;
import java.net.URLClassLoader;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/package-info.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/package-info.java
similarity index 86%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/package-info.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/package-info.java
index fddeefa35b3..3824714ed5d 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/package-info.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/package-info.java
@@ -16,6 +16,6 @@
/**
* Class that are packaged as part of CLI generated JARs.
- * @see org.springframework.boot.cli.command.jar.JarCommand
+ * @see org.springframework.boot.cli.command.archive.JarCommand
*/
-package org.springframework.boot.cli.jar;
+package org.springframework.boot.cli.archive;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java
similarity index 80%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java
index 91702fe8cb7..090ec456778 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/archive/ArchiveCommand.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command.jar;
+package org.springframework.boot.cli.command.archive;
import java.io.File;
import java.io.FileInputStream;
@@ -38,10 +38,12 @@ import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.springframework.boot.cli.app.SpringApplicationLauncher;
+import org.springframework.boot.cli.archive.PackagedSpringApplicationLauncher;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand;
-import org.springframework.boot.cli.command.jar.ResourceMatcher.MatchedResource;
+import org.springframework.boot.cli.command.archive.ResourceMatcher.MatchedResource;
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
+import org.springframework.boot.cli.command.options.OptionHandler;
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
import org.springframework.boot.cli.command.options.SourceOptions;
import org.springframework.boot.cli.command.status.ExitStatus;
@@ -49,8 +51,8 @@ import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
-import org.springframework.boot.cli.jar.PackagedSpringApplicationLauncher;
import org.springframework.boot.loader.tools.JarWriter;
+import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCallback;
@@ -65,51 +67,66 @@ import joptsimple.OptionSet;
import joptsimple.OptionSpec;
/**
- * {@link Command} to create a self-contained executable jar file from a CLI application.
+ * Abstract {@link Command} to create a self-contained executable archive file from a CLI
+ * application.
*
* @author Andy Wilkinson
* @author Phillip Webb
+ * @author Andrey Stolyarov
*/
-public class JarCommand extends OptionParsingCommand {
+abstract class ArchiveCommand extends OptionParsingCommand {
- public JarCommand() {
- super("jar",
- "Create a self-contained "
- + "executable jar file from a Spring Groovy script",
- new JarOptionHandler());
+ protected ArchiveCommand(String name, String description,
+ OptionHandler optionHandler) {
+ super(name, description, optionHandler);
}
@Override
public String getUsageHelp() {
- return "[options] ";
+ return "[options] <" + getName() + "-name> ";
}
- private static final class JarOptionHandler extends CompilerOptionHandler {
+ /**
+ * Abstract base {@link CompilerOptionHandler} for archive commands.
+ */
+ protected abstract static class ArchiveOptionHandler extends CompilerOptionHandler {
+
+ private final String type;
+
+ private final Layout layout;
private OptionSpec includeOption;
private OptionSpec excludeOption;
+ public ArchiveOptionHandler(String type, Layout layout) {
+ this.type = type;
+ this.layout = layout;
+ }
+
@Override
protected void doOptions() {
this.includeOption = option("include",
- "Pattern applied to directories on the classpath to find files to include in the resulting jar")
- .withRequiredArg().withValuesSeparatedBy(",").defaultsTo("");
+ "Pattern applied to directories on the classpath to find files to "
+ + "include in the resulting ").withRequiredArg()
+ .withValuesSeparatedBy(",").defaultsTo("");
this.excludeOption = option("exclude",
- "Pattern applied to directories on the classpath to find files to exclude from the resulting jar")
- .withRequiredArg().withValuesSeparatedBy(",").defaultsTo("");
+ "Pattern applied to directories on the classpath to find files to "
+ + "exclude from the resulting " + this.type).withRequiredArg()
+ .withValuesSeparatedBy(",").defaultsTo("");
}
@Override
protected ExitStatus run(OptionSet options) throws Exception {
List> nonOptionArguments = new ArrayList