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.loader.tools;
018
019import java.io.BufferedReader;
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStreamReader;
023import java.lang.reflect.Method;
024import java.util.Arrays;
025import java.util.Collection;
026
027import org.springframework.util.ReflectionUtils;
028
029/**
030 * Utility used to run a process.
031 *
032 * @author Phillip Webb
033 * @author Dave Syer
034 * @author Andy Wilkinson
035 * @author Stephane Nicoll
036 * @since 1.1.0
037 */
038public class RunProcess {
039
040        private static final Method INHERIT_IO_METHOD = ReflectionUtils
041                        .findMethod(ProcessBuilder.class, "inheritIO");
042
043        private static final long JUST_ENDED_LIMIT = 500;
044
045        private File workingDirectory;
046
047        private final String[] command;
048
049        private volatile Process process;
050
051        private volatile long endTime;
052
053        /**
054         * Creates new {@link RunProcess} instance for the specified command.
055         * @param command the program to execute and its arguments
056         */
057        public RunProcess(String... command) {
058                this(null, command);
059        }
060
061        /**
062         * Creates new {@link RunProcess} instance for the specified working directory and
063         * command.
064         * @param workingDirectory the working directory of the child process or {@code null}
065         * to run in the working directory of the current Java process
066         * @param command the program to execute and its arguments
067         */
068        public RunProcess(File workingDirectory, String... command) {
069                this.workingDirectory = workingDirectory;
070                this.command = command;
071        }
072
073        public int run(boolean waitForProcess, String... args) throws IOException {
074                return run(waitForProcess, Arrays.asList(args));
075        }
076
077        protected int run(boolean waitForProcess, Collection<String> args)
078                        throws IOException {
079                ProcessBuilder builder = new ProcessBuilder(this.command);
080                builder.directory(this.workingDirectory);
081                builder.command().addAll(args);
082                builder.redirectErrorStream(true);
083                boolean inheritedIO = inheritIO(builder);
084                try {
085                        Process process = builder.start();
086                        this.process = process;
087                        if (!inheritedIO) {
088                                redirectOutput(process);
089                        }
090                        SignalUtils.attachSignalHandler(new Runnable() {
091                                @Override
092                                public void run() {
093                                        handleSigInt();
094                                }
095                        });
096                        if (waitForProcess) {
097                                try {
098                                        return process.waitFor();
099                                }
100                                catch (InterruptedException ex) {
101                                        Thread.currentThread().interrupt();
102                                        return 1;
103                                }
104                        }
105                        return 5;
106                }
107                finally {
108                        if (waitForProcess) {
109                                this.endTime = System.currentTimeMillis();
110                                this.process = null;
111                        }
112                }
113        }
114
115        private boolean inheritIO(ProcessBuilder builder) {
116                if (isInheritIOBroken()) {
117                        return false;
118                }
119                try {
120                        INHERIT_IO_METHOD.invoke(builder);
121                        return true;
122                }
123                catch (Exception ex) {
124                        return false;
125                }
126        }
127
128        // There's a bug in the Windows VM (https://bugs.openjdk.java.net/browse/JDK-8023130)
129        // that means we need to avoid inheritIO
130        private static boolean isInheritIOBroken() {
131                if (!System.getProperty("os.name", "none").toLowerCase().contains("windows")) {
132                        return false;
133                }
134                String runtime = System.getProperty("java.runtime.version");
135                if (!runtime.startsWith("1.7")) {
136                        return false;
137                }
138                String[] tokens = runtime.split("_");
139                if (tokens.length < 2) {
140                        return true; // No idea actually, shouldn't happen
141                }
142                try {
143                        Integer build = Integer.valueOf(tokens[1].split("[^0-9]")[0]);
144                        if (build < 60) {
145                                return true;
146                        }
147                }
148                catch (Exception ex) {
149                        return true;
150                }
151                return false;
152        }
153
154        private void redirectOutput(Process process) {
155                final BufferedReader reader = new BufferedReader(
156                                new InputStreamReader(process.getInputStream()));
157                new Thread() {
158
159                        @Override
160                        public void run() {
161                                try {
162                                        String line = reader.readLine();
163                                        while (line != null) {
164                                                System.out.println(line);
165                                                line = reader.readLine();
166                                                System.out.flush();
167                                        }
168                                        reader.close();
169                                }
170                                catch (Exception ex) {
171                                        // Ignore
172                                }
173                        }
174
175                }.start();
176        }
177
178        /**
179         * Return the running process.
180         * @return the process or {@code null}
181         */
182        public Process getRunningProcess() {
183                return this.process;
184        }
185
186        /**
187         * Return if the process was stopped.
188         * @return {@code true} if stopped
189         */
190        public boolean handleSigInt() {
191                // if the process has just ended, probably due to this SIGINT, consider handled.
192                if (hasJustEnded()) {
193                        return true;
194                }
195                return doKill();
196        }
197
198        /**
199         * Kill this process.
200         */
201        public void kill() {
202                doKill();
203        }
204
205        private boolean doKill() {
206                // destroy the running process
207                Process process = this.process;
208                if (process != null) {
209                        try {
210                                process.destroy();
211                                process.waitFor();
212                                this.process = null;
213                                return true;
214                        }
215                        catch (InterruptedException ex) {
216                                Thread.currentThread().interrupt();
217                        }
218                }
219                return false;
220        }
221
222        public boolean hasJustEnded() {
223                return System.currentTimeMillis() < (this.endTime + JUST_ENDED_LIMIT);
224        }
225
226}