001/* 002 * Copyright 2012-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.maven; 018 019import java.io.File; 020import java.net.URL; 021import java.net.URLClassLoader; 022import java.util.List; 023 024import org.apache.maven.plugin.MojoExecutionException; 025import org.apache.maven.plugins.annotations.Execute; 026import org.apache.maven.plugins.annotations.LifecyclePhase; 027import org.apache.maven.plugins.annotations.Mojo; 028import org.apache.maven.plugins.annotations.ResolutionScope; 029 030import org.springframework.boot.loader.tools.JavaExecutable; 031import org.springframework.boot.loader.tools.RunProcess; 032 033/** 034 * Run an executable archive application. 035 * 036 * @author Phillip Webb 037 * @author Stephane Nicoll 038 * @author Andy Wilkinson 039 */ 040@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.TEST) 041@Execute(phase = LifecyclePhase.TEST_COMPILE) 042public class RunMojo extends AbstractRunMojo { 043 044 private static final int EXIT_CODE_SIGINT = 130; 045 046 private static final String RESTARTER_CLASS_LOCATION = "org/springframework/boot/devtools/restart/Restarter.class"; 047 048 /** 049 * Devtools presence flag to avoid checking for it several times per execution. 050 */ 051 private Boolean hasDevtools; 052 053 @Override 054 protected boolean enableForkByDefault() { 055 return super.enableForkByDefault() || hasDevtools(); 056 } 057 058 @Override 059 protected void logDisabledFork() { 060 super.logDisabledFork(); 061 if (hasDevtools()) { 062 getLog().warn("Fork mode disabled, devtools will be disabled"); 063 } 064 } 065 066 @Override 067 protected void runWithForkedJvm(File workingDirectory, List<String> args) 068 throws MojoExecutionException { 069 try { 070 RunProcess runProcess = new RunProcess(workingDirectory, 071 new JavaExecutable().toString()); 072 Runtime.getRuntime() 073 .addShutdownHook(new Thread(new RunProcessKiller(runProcess))); 074 int exitCode = runProcess.run(true, args.toArray(new String[args.size()])); 075 if (exitCode == 0 || exitCode == EXIT_CODE_SIGINT) { 076 return; 077 } 078 throw new MojoExecutionException( 079 "Application finished with exit code: " + exitCode); 080 } 081 catch (Exception ex) { 082 throw new MojoExecutionException("Could not exec java", ex); 083 } 084 } 085 086 @Override 087 protected void runWithMavenJvm(String startClassName, String... arguments) 088 throws MojoExecutionException { 089 IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName); 090 Thread launchThread = new Thread(threadGroup, 091 new LaunchRunner(startClassName, arguments), "main"); 092 launchThread.setContextClassLoader(new URLClassLoader(getClassPathUrls())); 093 launchThread.start(); 094 join(threadGroup); 095 threadGroup.rethrowUncaughtException(); 096 } 097 098 private void join(ThreadGroup threadGroup) { 099 boolean hasNonDaemonThreads; 100 do { 101 hasNonDaemonThreads = false; 102 Thread[] threads = new Thread[threadGroup.activeCount()]; 103 threadGroup.enumerate(threads); 104 for (Thread thread : threads) { 105 if (thread != null && !thread.isDaemon()) { 106 try { 107 hasNonDaemonThreads = true; 108 thread.join(); 109 } 110 catch (InterruptedException ex) { 111 Thread.currentThread().interrupt(); 112 } 113 } 114 } 115 } 116 while (hasNonDaemonThreads); 117 } 118 119 private boolean hasDevtools() { 120 if (this.hasDevtools == null) { 121 this.hasDevtools = checkForDevtools(); 122 } 123 return this.hasDevtools; 124 } 125 126 @SuppressWarnings("resource") 127 private boolean checkForDevtools() { 128 try { 129 URL[] urls = getClassPathUrls(); 130 URLClassLoader classLoader = new URLClassLoader(urls); 131 return (classLoader.findResource(RESTARTER_CLASS_LOCATION) != null); 132 } 133 catch (Exception ex) { 134 return false; 135 } 136 } 137 138 private static final class RunProcessKiller implements Runnable { 139 140 private final RunProcess runProcess; 141 142 private RunProcessKiller(RunProcess runProcess) { 143 this.runProcess = runProcess; 144 } 145 146 @Override 147 public void run() { 148 this.runProcess.kill(); 149 } 150 151 } 152 153}