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}