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); - } - } - } - }