diff --git a/spring-boot-samples/spring-boot-sample-data-jpa/pom.xml b/spring-boot-samples/spring-boot-sample-data-jpa/pom.xml
index efdad5b7288..deac9faa789 100644
--- a/spring-boot-samples/spring-boot-sample-data-jpa/pom.xml
+++ b/spring-boot-samples/spring-boot-sample-data-jpa/pom.xml
@@ -43,6 +43,13 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+ org.springframework
+ springloaded
+ 1.2.0.RELEASE
+
+
diff --git a/spring-boot-samples/spring-boot-sample-web-ui/pom.xml b/spring-boot-samples/spring-boot-sample-web-ui/pom.xml
index 0b416483d37..8895e781dfa 100644
--- a/spring-boot-samples/spring-boot-sample-web-ui/pom.xml
+++ b/spring-boot-samples/spring-boot-sample-web-ui/pom.xml
@@ -42,7 +42,7 @@
org.springframework
springloaded
- 1.1.5.RELEASE
+ 1.2.0.RELEASE
diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JavaExecutable.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JavaExecutable.java
new file mode 100644
index 00000000000..3720893fe50
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JavaExecutable.java
@@ -0,0 +1,67 @@
+/*
+ * 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.loader.tools;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Provides access to the java binary executable, regardless of OS.
+ *
+ * @author Phillip Webb
+ */
+public class JavaExecutable {
+
+ private File file;
+
+ public JavaExecutable() {
+ String javaHome = System.getProperty("java.home");
+ Assert.state(StringUtils.hasLength(javaHome),
+ "Unable to find java executable due to missing 'java.home'");
+ this.file = findInJavaHome(javaHome);
+ }
+
+ private File findInJavaHome(String javaHome) {
+ File bin = new File(new File(javaHome), "bin");
+ File command = new File(bin, "java.exe");
+ command = (command.exists() ? command : new File(bin, "java"));
+ Assert.state(command.exists(), "Unable to find java in " + javaHome);
+ return command;
+ }
+
+ // TODO: is this used?
+ public ProcessBuilder processBuilder(String... arguments) {
+ ProcessBuilder processBuilder = new ProcessBuilder(toString());
+ processBuilder.command().addAll(Arrays.asList(arguments));
+ return processBuilder;
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return this.file.getCanonicalPath();
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+}
diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java
new file mode 100644
index 00000000000..da16c80c00b
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java
@@ -0,0 +1,146 @@
+/*
+ * 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.loader.tools;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Special utility used to run a process.
+ *
+ * @author Phillip Webb
+ * @author Dave Syer
+ */
+public class RunProcess {
+
+ private static final Method INHERIT_IO_METHOD = ReflectionUtils.findMethod(
+ ProcessBuilder.class, "inheritIO");
+
+ private static final long JUST_ENDED_LIMIT = 500;
+
+ private final String[] command;
+
+ private volatile Process process;
+
+ private volatile long endTime;
+
+ public RunProcess(String... command) {
+ this.command = command;
+ }
+
+ public void run(String... args) throws Exception {
+ run(Arrays.asList(args));
+ }
+
+ protected void run(Collection args) throws IOException {
+ ProcessBuilder builder = new ProcessBuilder(this.command);
+ builder.command().addAll(args);
+ builder.redirectErrorStream(true);
+ boolean inheritedIO = inheritIO(builder);
+ try {
+ this.process = builder.start();
+ if (!inheritedIO) {
+ redirectOutput(this.process);
+ }
+ try {
+ this.process.waitFor();
+ }
+ catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ finally {
+ this.endTime = System.currentTimeMillis();
+ this.process = null;
+ }
+ }
+
+ private boolean inheritIO(ProcessBuilder builder) {
+ try {
+ INHERIT_IO_METHOD.invoke(builder);
+ return true;
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+
+ private void redirectOutput(Process process) {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(
+ process.getInputStream()));
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ String line = reader.readLine();
+ while (line != null) {
+ System.out.println(line);
+ line = reader.readLine();
+ }
+ reader.close();
+ }
+ catch (Exception ex) {
+ }
+ };
+ }.start();
+ }
+
+ /**
+ * @return the running process or {@code null}
+ */
+ public Process getRunningProcess() {
+ return this.process;
+ }
+
+ /**
+ * @return {@code true} if the process was stopped.
+ */
+ public boolean handleSigInt() {
+
+ // if the process has just ended, probably due to this SIGINT, consider handled.
+ if (hasJustEnded()) {
+ return true;
+ }
+
+ // destroy the running process
+ Process process = this.process;
+ if (process != null) {
+ try {
+ process.destroy();
+ process.waitFor();
+ this.process = null;
+ return true;
+ }
+ catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return false;
+ }
+
+ public boolean hasJustEnded() {
+ return System.currentTimeMillis() < (this.endTime + JUST_ENDED_LIMIT);
+ }
+
+}
diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java
index acac3de08c3..627fd6c8095 100644
--- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java
+++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java
@@ -18,10 +18,8 @@ package org.springframework.boot.maven;
import java.io.File;
import java.io.IOException;
-import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
-import java.net.URLClassLoader;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.List;
@@ -37,9 +35,10 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
-import org.springframework.boot.loader.tools.AgentAttacher;
import org.springframework.boot.loader.tools.FileUtils;
+import org.springframework.boot.loader.tools.JavaExecutable;
import org.springframework.boot.loader.tools.MainClassFinder;
+import org.springframework.boot.loader.tools.RunProcess;
/**
* MOJO that can be used to run a executable archive application directly from Maven.
@@ -106,17 +105,6 @@ public class RunMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
- findAgent();
- if (this.agent != null) {
- getLog().info("Attaching agent: " + this.agent);
- if (this.noverify != null && this.noverify && !AgentAttacher.hasNoVerify()) {
- throw new MojoExecutionException(
- "The JVM must be started with -noverify for "
- + "this agent to work. You can use MAVEN_OPTS=-noverify "
- + "to add that flag.");
- }
- AgentAttacher.attach(this.agent);
- }
final String startClassName = getStartClass();
run(startClassName);
}
@@ -139,16 +127,43 @@ public class RunMojo extends AbstractMojo {
catch (ClassNotFoundException ex) {
// ignore;
}
+ if (this.noverify == null) {
+ this.noverify = false;
+ }
}
private void run(String startClassName) throws MojoExecutionException {
- IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
- Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
- this.arguments), startClassName + ".main()");
- launchThread.setContextClassLoader(getClassLoader());
- launchThread.start();
- join(threadGroup);
- threadGroup.rethrowUncaughtException();
+ findAgent();
+ int extras = 0;
+ if (this.agent != null) {
+ getLog().info("Attaching agent: " + this.agent);
+ extras = 1;
+ }
+ if (this.noverify) {
+ extras++;
+ }
+ String[] args = new String[this.arguments.length + extras + 3];
+ System.arraycopy(this.arguments, 0, args, extras + 3, this.arguments.length);
+ if (extras > 0) {
+ args[0] = "-javaagent:" + this.agent;
+ }
+ if (this.noverify) {
+ args[1] = "-noverify";
+ }
+ args[extras + 2] = startClassName;
+ try {
+ StringBuilder classpath = new StringBuilder();
+ for (URL ele : getClassPathUrls()) {
+ classpath = classpath.append((classpath.length() > 0 ? File.pathSeparator
+ : "") + ele);
+ }
+ args[extras] = "-cp";
+ args[extras + 1] = classpath.toString();
+ new RunProcess(new JavaExecutable().toString()).run(args);
+ }
+ catch (Exception e) {
+ throw new MojoExecutionException("Could not exec java", e);
+ }
}
private final String getStartClass() throws MojoExecutionException {
@@ -168,11 +183,6 @@ public class RunMojo extends AbstractMojo {
return mainClass;
}
- private ClassLoader getClassLoader() throws MojoExecutionException {
- URL[] urls = getClassPathUrls();
- return new URLClassLoader(urls);
- }
-
private URL[] getClassPathUrls() throws MojoExecutionException {
try {
List urls = new ArrayList();
@@ -223,92 +233,4 @@ public class RunMojo extends AbstractMojo {
}
}
- private void join(ThreadGroup threadGroup) {
- boolean hasNonDaemonThreads;
- do {
- hasNonDaemonThreads = false;
- Thread[] threads = new Thread[threadGroup.activeCount()];
- threadGroup.enumerate(threads);
- for (Thread thread : threads) {
- if (thread != null && !thread.isDaemon()) {
- try {
- hasNonDaemonThreads = true;
- thread.join();
- }
- catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
- }
- }
- }
- while (hasNonDaemonThreads);
- }
-
- /**
- * Isolated {@link ThreadGroup} to capture uncaught exceptions.
- */
- class IsolatedThreadGroup extends ThreadGroup {
-
- private Throwable exception;
-
- public IsolatedThreadGroup(String name) {
- super(name);
- }
-
- @Override
- public void uncaughtException(Thread thread, Throwable ex) {
- if (!(ex instanceof ThreadDeath)) {
- synchronized (this) {
- this.exception = (this.exception == null ? ex : this.exception);
- }
- getLog().warn(ex);
- }
- }
-
- public synchronized void rethrowUncaughtException() throws MojoExecutionException {
- if (this.exception != null) {
- throw new MojoExecutionException("An exception occured while running. "
- + this.exception.getMessage(), this.exception);
- }
- }
- }
-
- /**
- * Runner used to launch the application.
- */
- class LaunchRunner implements Runnable {
-
- private final String startClassName;
- private final String[] args;
-
- public LaunchRunner(String startClassName, String... args) {
- this.startClassName = startClassName;
- this.args = (args != null ? args : new String[] {});
- }
-
- @Override
- public void run() {
- Thread thread = Thread.currentThread();
- ClassLoader classLoader = thread.getContextClassLoader();
- try {
- Class> startClass = classLoader.loadClass(this.startClassName);
- Method mainMethod = startClass.getMethod("main",
- new Class[] { String[].class });
- if (!mainMethod.isAccessible()) {
- mainMethod.setAccessible(true);
- }
- mainMethod.invoke(null, new Object[] { this.args });
- }
- catch (NoSuchMethodException ex) {
- Exception wrappedEx = new Exception(
- "The specified mainClass doesn't contain a "
- + "main method with appropriate signature.", ex);
- thread.getThreadGroup().uncaughtException(thread, wrappedEx);
- }
- catch (Exception ex) {
- thread.getThreadGroup().uncaughtException(thread, ex);
- }
- }
- }
-
}