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}