001/* 002 * Copyright 2012-2018 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; 023import java.util.Map; 024 025import org.apache.maven.plugin.MojoExecutionException; 026import org.apache.maven.plugins.annotations.Execute; 027import org.apache.maven.plugins.annotations.LifecyclePhase; 028import org.apache.maven.plugins.annotations.Mojo; 029import org.apache.maven.plugins.annotations.ResolutionScope; 030 031import org.springframework.boot.loader.tools.JavaExecutable; 032import org.springframework.boot.loader.tools.RunProcess; 033 034/** 035 * Run an executable archive application. 036 * 037 * @author Phillip Webb 038 * @author Dmytro Nosan 039 * @author Stephane Nicoll 040 * @author Andy Wilkinson 041 */ 042@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.TEST) 043@Execute(phase = LifecyclePhase.TEST_COMPILE) 044public class RunMojo extends AbstractRunMojo { 045 046 private static final int EXIT_CODE_SIGINT = 130; 047 048 private static final String RESTARTER_CLASS_LOCATION = "org/springframework/boot/devtools/restart/Restarter.class"; 049 050 /** 051 * Devtools presence flag to avoid checking for it several times per execution. 052 */ 053 private Boolean hasDevtools; 054 055 @Override 056 protected boolean enableForkByDefault() { 057 return super.enableForkByDefault() || hasDevtools(); 058 } 059 060 @Override 061 protected void logDisabledFork() { 062 super.logDisabledFork(); 063 if (hasDevtools()) { 064 getLog().warn("Fork mode disabled, devtools will be disabled"); 065 } 066 } 067 068 @Override 069 protected void runWithForkedJvm(File workingDirectory, List<String> args, 070 Map<String, String> environmentVariables) throws MojoExecutionException { 071 try { 072 RunProcess runProcess = new RunProcess(workingDirectory, 073 new JavaExecutable().toString()); 074 Runtime.getRuntime() 075 .addShutdownHook(new Thread(new RunProcessKiller(runProcess))); 076 int exitCode = runProcess.run(true, args, environmentVariables); 077 if (exitCode == 0 || exitCode == EXIT_CODE_SIGINT) { 078 return; 079 } 080 throw new MojoExecutionException( 081 "Application finished with exit code: " + exitCode); 082 } 083 catch (Exception ex) { 084 throw new MojoExecutionException("Could not exec java", ex); 085 } 086 } 087 088 @Override 089 protected void runWithMavenJvm(String startClassName, String... arguments) 090 throws MojoExecutionException { 091 IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName); 092 Thread launchThread = new Thread(threadGroup, 093 new LaunchRunner(startClassName, arguments), "main"); 094 launchThread.setContextClassLoader(new URLClassLoader(getClassPathUrls())); 095 launchThread.start(); 096 join(threadGroup); 097 threadGroup.rethrowUncaughtException(); 098 } 099 100 private void join(ThreadGroup threadGroup) { 101 boolean hasNonDaemonThreads; 102 do { 103 hasNonDaemonThreads = false; 104 Thread[] threads = new Thread[threadGroup.activeCount()]; 105 threadGroup.enumerate(threads); 106 for (Thread thread : threads) { 107 if (thread != null && !thread.isDaemon()) { 108 try { 109 hasNonDaemonThreads = true; 110 thread.join(); 111 } 112 catch (InterruptedException ex) { 113 Thread.currentThread().interrupt(); 114 } 115 } 116 } 117 } 118 while (hasNonDaemonThreads); 119 } 120 121 private boolean hasDevtools() { 122 if (this.hasDevtools == null) { 123 this.hasDevtools = checkForDevtools(); 124 } 125 return this.hasDevtools; 126 } 127 128 private boolean checkForDevtools() { 129 try { 130 URL[] urls = getClassPathUrls(); 131 try (URLClassLoader classLoader = new URLClassLoader(urls)) { 132 return (classLoader.findResource(RESTARTER_CLASS_LOCATION) != null); 133 } 134 } 135 catch (Exception ex) { 136 return false; 137 } 138 } 139 140 private static final class RunProcessKiller implements Runnable { 141 142 private final RunProcess runProcess; 143 144 private RunProcessKiller(RunProcess runProcess) { 145 this.runProcess = runProcess; 146 } 147 148 @Override 149 public void run() { 150 this.runProcess.kill(); 151 } 152 153 } 154 155}