View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.tournament.botexecution;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.util.logging.Level;
6   import java.util.logging.Logger;
7   
8   import cz.cuni.amis.pogamut.ut2004.utils.PogamutUT2004Property;
9   import cz.cuni.amis.utils.StreamSink;
10  import cz.cuni.amis.utils.exception.PogamutException;
11  import cz.cuni.amis.utils.exception.PogamutIOException;
12  import cz.cuni.amis.utils.flag.Flag;
13  import cz.cuni.amis.utils.flag.FlagListener;
14  import cz.cuni.amis.utils.flag.ImmutableFlag;
15  
16  /**
17   * Class that wrapps the execution of the bot from the jar defined by {@link UT2004BotExecutionConfig}.
18   * <p><p>
19   * The class is not suitable for inheritance ... better copy-paste and adjust for yourself.
20   * 
21   * @author Jimmy
22   */
23  public class UT2004BotExecution {
24  	
25  	protected UT2004BotExecutionConfig config;
26  	
27  	/**
28  	 * Flag whether the bot is running, we're synchronizing access to this class on this flag.
29  	 * <p><p>
30  	 * Set to TRUE ONLY IN {@link UT2004BotExecution#start()} and back to FALSE ONLY IN {@link UT2004BotExecution#shutdown(boolean)}.
31  	 * Do not alter the flag from anywhere else!
32  	 */
33  	protected Flag<Boolean> running = new Flag<Boolean>(false);
34  	
35  	/**
36  	 * When {@link UT2004BotExecution#start()}ed it contains the bot process.
37  	 */
38  	protected Process botProcess = null;
39  	
40  	/**
41  	 * Sink for the STDOUT of the bot, it may be redirected to {@link UT2004BotExecution#log}.
42  	 */
43  	protected StreamSink streamSinkOutput = null;
44  	
45  	/**
46  	 * Sink for the STDERR of the bot, it may be redirected to {@link UT2004BotExecution#log}.
47  	 */
48  	protected StreamSink streamSinkError = null;
49  
50  	/**
51  	 * Log used by the object, specified during the construction.
52  	 */
53  	protected Logger log;
54  
55  	/**
56  	 * @param config bot execution configuration (contains path to jar to run)
57  	 * @param log log to be used (stdout/stderr of the bot will be redirected there as well with {@link Level#INFO})
58  	 */
59  	public UT2004BotExecution(UT2004BotExecutionConfig config, Logger log) {
60  		this.log = log;
61  		this.config = config;
62  	}
63  		
64  	/**
65  	 * Task that will kill the BOT process when user forgets to do so before the JVM is killed.
66       */
67      protected Runnable shutDownHook = new Runnable(){
68  
69          @Override
70          public void run() {
71              if (botProcess != null) botProcess.destroy();
72          }
73      };
74      
75      protected Thread shutDownHookThread;
76      
77      /**
78       * Task that is waiting for the end of the bot process to switch {@link UT2004BotExecution#running} back to false
79       * and shutdown all other utility threads (like sink).
80       */
81      protected Runnable waitForEnd = new Runnable() {
82  
83  		@Override
84  		public void run() {
85  			try {
86  				botProcess.waitFor();
87  				// BOT PROCESS HAS STOPPED WHEN THE JVM REACHES HERE
88  			} catch (InterruptedException e) {
89  				// we've been interrupted!
90  				synchronized(running) {
91  					if (!running.getFlag()) {
92  						// okey we're not running, screw it...
93  						return;
94  					}
95  				}
96  				// hups, we're still running, warn the user!
97  				if (log != null && log.isLoggable(Level.WARNING)) {
98  					log.warning("Interrupted while waiting for the botProcess(" + config.getBotId().getToken() + ") to end!");
99  				}
100 				
101 			} finally {
102 				// perform clean-up
103 				synchronized(running) {
104 					if (!running.getFlag()) {
105 						// okey we're not running, somebody has already shutdown, screw it...
106 						return;
107 					}
108 					if (waitForEndThread == Thread.currentThread()) {
109 						// we're still in the middle of the same execution... (shut down only if it is the same bot execution)
110 						shutdown(true);
111 					}
112 				}
113 			}
114 		}
115     };
116     
117     protected Thread waitForEndThread;
118 	
119     /**
120      * Start the bot process. Throws {@link PogamutIOException} if it fails to start the JVM.
121      * <p><p>
122      * It is wise to observe the state of the {@link UT2004BotExecution#getRunning()} flag to obtain the state of the process
123      * (whether the process is running or is dead). Usage of {@link FlagListener} is advised, see {@link ImmutableFlag#addListener(FlagListener)}.
124      * 
125      * @param host GB2004 host
126      * @param port GB2004 bot port
127      */
128 	public void start(String host, int port) throws PogamutIOException {
129 		synchronized(running) {
130 			if (running.getFlag()) {
131 				throw new PogamutException("Could not start the bot again, it is already running! stop() it first!", log, this);
132 			}
133 			
134 			if (log != null && log.isLoggable(Level.WARNING)) {
135 				log.warning("Starting bot: " + config);
136 			}
137 			
138 			if (!config.isBotJarExist()) {
139 				throw new PogamutException("Could not start the bot according to config " + config + " as the bot jar does not exist at specified place " + config.getJarFile().getAbsolutePath() + "!", this);
140 			}
141 			
142 			String javaHome = System.getProperty("JAVA_HOME");
143 			
144 			boolean linux = System.getProperty("os.name").toLowerCase().contains("linux");
145 			
146 				
147 			
148 			String command = 
149 					(javaHome == null 
150 						? (linux ? "java" : "java.exe")
151 						: javaHome + (linux ? "/bin/java" : "\\bin\\java.exe"));
152 			
153 			String fullCommand = command + " \"-D" + PogamutUT2004Property.POGAMUT_UT2004_BOT_HOST.getKey() + "=" + host + "\""
154 							             + " \"-D" + PogamutUT2004Property.POGAMUT_UT2004_BOT_PORT.getKey() + "=" + port + "\""
155 							             + " -jar \"" + config.getJarFile().getAbsolutePath() + "\"";
156 			
157 			if (log != null && log.isLoggable(Level.INFO)) {
158 				log.info("Executing command: " + fullCommand);
159 			}
160 			
161 			ProcessBuilder procBuilder = 
162 				new ProcessBuilder(
163 						command, 
164 						"-D" + PogamutUT2004Property.POGAMUT_UT2004_BOT_HOST.getKey() + "=" + host,
165 						"-D" + PogamutUT2004Property.POGAMUT_UT2004_BOT_PORT.getKey() + "=" + port,
166 						"-jar", 
167 						config.getJarFile().getAbsolutePath()						
168 				);				            
169 	        procBuilder.directory(new File(config.getJarFile().getParent()));
170 	        
171 	        try {
172 	        	botProcess = procBuilder.start();
173 	        } catch (IOException e) {
174 	        	// failed to start the process
175 	        	if (log != null && log.isLoggable(Level.SEVERE)) {
176 	        		log.severe("Could not start the bot: " + e.getMessage());
177 	        	}
178 	        	botProcess = null;
179 	        	throw new PogamutIOException("Failed to start the botProcess(" + config.getBotId().getToken() + "). IOException: " + e.getMessage(), e, this);
180 	        }
181 	        
182 	        // INITIALIZE ALL THREADS
183 	        if (config.isRedirectStdErr()) {
184 	        	streamSinkError = new StreamSink(config.getBotId().getToken()+"-StdErrSink", botProcess.getErrorStream(), log, config.getBotId().getToken()+"-StdErr");
185 	        } else {
186 	        	streamSinkError = new StreamSink(config.getBotId().getToken()+"-StdErrSink", botProcess.getErrorStream());
187 	        }
188 	        streamSinkError.start();
189 	        if (config.isRedirectStdOut()) {
190 	        	streamSinkOutput = new StreamSink(config.getBotId().getToken()+"-StdOutSink", botProcess.getInputStream(), log, config.getBotId().getToken()+"-StdOut");
191 	        } else {
192 	        	streamSinkOutput = new StreamSink(config.getBotId().getToken()+"-StdOutSink", botProcess.getInputStream());
193 	        }
194 	        streamSinkOutput.start();
195 	        shutDownHookThread = new Thread(shutDownHook, config.getBotId().getToken()+"-JVMShutdownHook");
196 	        Runtime.getRuntime().addShutdownHook(shutDownHookThread);
197 	        waitForEndThread = new Thread(waitForEnd, config.getBotId().getToken()+"-WaitForProcessEnd");
198 	        running.setFlag(true);    // we're ONLINE!
199 	        waitForEndThread.start(); // execute the thread that will wait for the end of the process to switch 'running' back to false
200 		}
201 	}
202 	
203 	/**
204 	 * Shutdowns the process and cleans-up everything, preparing the object to be {@link UT2004BotExecution#start()}ed again.
205 	 * @param waitForEndThread whether we're being called from the {@link UT2004BotExecution#waitForEnd} thread.
206 	 */
207 	protected void shutdown(boolean waitForEndThread) {
208 		synchronized(running) {
209 			if (!running.getFlag()) {
210 				// we're not running, screw it!
211 				return;
212 			}
213 			
214 			if (log != null && log.isLoggable(Level.WARNING)) {
215 				log.warning("Shutting down botProcess(" + config.getBotId().getToken() + ")!");
216 			}
217 
218 			if (log != null && log.isLoggable(Level.WARNING)) {
219 				log.warning("... destroying botProcess(" + config.getBotId().getToken() + ").");
220 			}
221 			if (botProcess != null) {
222 				try {
223 					botProcess.destroy();
224 				} catch (Exception e) {
225 				}
226 			}
227 			botProcess = null;
228 			
229 			if (log != null && log.isLoggable(Level.WARNING)) {
230 				log.warning("... destroying streamSinkError(" + config.getBotId().getToken() + ").");
231 			}
232 			try {
233 				if (streamSinkError != null) streamSinkError.interrupt();
234 			} catch (Exception e) {
235 			}
236 			streamSinkError = null;
237 			
238 			if (log != null && log.isLoggable(Level.WARNING)) {
239 				log.warning("... destroying streamSinkOutput(" + config.getBotId().getToken() + ").");
240 			}
241 			try {
242 				if (streamSinkOutput != null) streamSinkOutput.interrupt();
243 			} catch (Exception e) {
244 			}
245 			streamSinkOutput = null;
246 			
247 			if (log != null && log.isLoggable(Level.WARNING)) {
248 				log.warning("... destroying waitForEnd(" + config.getBotId().getToken() + ").");
249 			}
250 			if (!waitForEndThread) {
251 				try {
252 					if (this.waitForEndThread != null) this.waitForEndThread.interrupt();
253 				} catch (Exception e) {
254 				}
255 			}
256 			this.waitForEndThread = null;
257 			
258 			if (log != null && log.isLoggable(Level.WARNING)) {
259 				log.warning("... removing shutDownHook(" + config.getBotId().getToken() + ").");
260 			}
261 			if (shutDownHookThread != null) {
262 				try {
263 					Runtime.getRuntime().removeShutdownHook(shutDownHookThread);
264 				} catch (Exception e) {
265 				}	
266 			}
267 			shutDownHookThread = null;
268 			
269 			if (log != null && log.isLoggable(Level.WARNING)) {
270 				log.warning("... setting running-flag(" + config.getBotId().getToken() + ") to FALSE.");
271 			}
272 			running.setFlag(false);
273 			
274 			if (log != null && log.isLoggable(Level.WARNING)) {
275 				log.warning("Shutdown(" + config.getBotId().getToken() + ") finished.");
276 			}
277 		}
278 	}
279 	
280 	/**
281 	 * Shuts down the bot process (if it is running, otherwise does nothing).
282 	 */
283 	public void stop() {
284 		shutdown(false);
285 	}
286     
287     /**
288      * Process of the bot (non-null if started).
289      * @return
290      */
291     public Process getBotProcess() {
292         return botProcess;
293     }
294 	
295     /**
296      * Flag of the state of the bot process. True == process is running (has been started using {@link UT2004BotExecution#start()}), 
297      * False == process was not started or has been already terminated, either by itself or via {@link UT2004BotExecution#stop()}.
298      * @return
299      */
300 	public ImmutableFlag<Boolean> getRunning() {
301 		return running.getImmutable();
302 	}
303 	
304 	/**
305 	 * Flag of the state of the bot process. True == process is running (has been started using {@link UT2004BotExecution#start()}), 
306      * False == process was not started or has been already terminated, either by itself or via {@link UT2004BotExecution#stop()}.
307 	 * @return
308 	 */
309 	public boolean isRunning() {
310 		return running.getFlag();
311 	}
312 	
313 }