View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.utils;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.io.InputStreamReader;
8   import java.util.Timer;
9   import java.util.TimerTask;
10  import java.util.concurrent.CountDownLatch;
11  import java.util.concurrent.TimeUnit;
12  import java.util.logging.Level;
13  import java.util.logging.Logger;
14  import java.util.regex.Matcher;
15  
16  import cz.cuni.amis.pogamut.base.agent.impl.AgentId;
17  import cz.cuni.amis.pogamut.base.communication.connection.impl.socket.SocketConnectionAddress;
18  import cz.cuni.amis.pogamut.base.utils.Pogamut;
19  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
20  import cz.cuni.amis.pogamut.base.utils.logging.LogPublisher;
21  import cz.cuni.amis.pogamut.ut2004.factory.direct.remoteagent.UT2004ServerFactory;
22  import cz.cuni.amis.pogamut.ut2004.server.IUT2004Server;
23  import cz.cuni.amis.pogamut.ut2004.server.exception.UCCStartException;
24  import cz.cuni.amis.utils.exception.PogamutException;
25  import cz.cuni.amis.utils.flag.Flag;
26  
27  /**
28   * Wrapper of running instance of UCC server. Implements pooling of instances.
29   * Usage scenario is:
30   * <code>
31   * UCCWrapper ucc = UCCWrapper.create();
32   * ...
33   * ucc.release();
34   * </code>
35   * The location of UCC executabe will be determined by an environment variable
36   * pogamut.ut2004.home (e.g. c:\Games\UT2004). The property cam be set via <i>java ...
37   * -Dpogamut.ut2004.home=c:\Games\UT2004</i>. Another posibility is to set it
38   * by code <code>System.setProperty("pogamut.ut2004.home", "c:\\Unreal Anthology\\UT2004");</code>.
39   * 
40   * @author Ik
41   */
42  public class UCCWrapper {
43  	
44      /** Loger containing all output from running instance of UCC. */
45      protected LogCategory uccLog;
46      protected static int fileCounter = 0;
47      Process uccProcess = null;
48      /** Port for bots. */
49      protected int gbPort = -1;
50      /** Port for server connection. */
51      protected int controlPort = -1;
52      /** Port for observer connection. */
53      protected int observerPort = -1;
54      protected IUT2004Server utServer = null;
55      /** First port assigned to a ucc instance. */
56      protected static final int basePort = 39782;
57      protected static Integer nextUccWrapperUID = 0;
58      /** ID of the wrapper object. Useful for debuging. */
59      protected int uccWrapperUID = 0;
60      protected UCCWrapperConf conf = null;
61      
62      /** 
63       * Tells whether UCC is in "IN END GAME" phase, which indicates that the FIRST MATCH has ended. DOES NOT WORK WHEN UCC IS USING MAP-SWITCHES! 
64       * Determined by {@link UCCWrapperPatterns#getGameEndingPattern()}.
65       **/
66      protected Flag<Boolean> gameEnding = new Flag<Boolean>(false);
67      //protected String mapToLoad
68  
69      /**
70       * @return Log with output of UCC. If you want to listen also for messages 
71       * from the startup sequence then use UCCWrapper.create(Logger parent). Set
72       * Parent logger of this log and register listeners before creating this
73       * instance of UCCWrapper.  
74       */
75      public Logger getLogger() {
76          return uccLog;
77      }
78  
79      public UCCWrapperConf getConfiguration() {
80  		return conf;
81  	}
82  
83  	/**
84       * @return Server connected to this UCC instance.
85       */
86      @SuppressWarnings("unchecked")
87  	public IUT2004Server getUTServer() {
88          stopCheck();
89          if (utServer == null) {
90              UT2004ServerFactory factory = new UT2004ServerFactory();
91              UT2004ServerRunner serverRunner = new UT2004ServerRunner(factory, "NBUTServer", "localhost", controlPort);
92              utServer = serverRunner.startAgent();
93          }
94          return utServer;
95      }
96  
97      protected String getUnrealHome() {
98          if (conf.getUnrealHome() == null) {
99              return Pogamut.getPlatform().getProperty(PogamutUT2004Property.POGAMUT_UNREAL_HOME.getKey());
100         } else {
101             return conf.getUnrealHome();
102         }
103     }
104 
105     public UCCWrapper(UCCWrapperConf configuration) throws UCCStartException {
106     	uccLog = new LogCategory("Wrapper");
107     	uccLog.addHandler(new LogPublisher.ConsolePublisher(new AgentId("UCC")));
108     	if (configuration.log != null) {
109             uccLog.setParent(configuration.log);
110         }
111         this.conf = configuration;
112         uccWrapperUID = nextUccWrapperUID++;
113         initUCCWrapper();
114         Runtime.getRuntime().addShutdownHook(shutDownHook);
115     }
116     /**
117      * Task that will kill the UCC process when user forgets to do so.
118      */
119     Thread shutDownHook = new Thread("UCC wrapper finalizer") {
120 
121         @Override
122         public void run() {
123             if (uccProcess != null) uccProcess.destroy();
124         }
125     };
126 
127     /**
128      * Reads content of the stream and discards it.
129      */
130     protected class StreamSink extends Thread {
131 
132         protected InputStream os = null;
133 
134         public StreamSink(InputStream os) {
135             setName("UCC Stream handler");
136             this.os = os;
137         }
138 
139         protected void handleInput(String str) {
140             if (uccLog.isLoggable(Level.INFO)) uccLog.info("ID" + uccWrapperUID + " " + str);
141         }
142 
143         @Override
144         public void run() {
145             BufferedReader stdInput = new BufferedReader(new InputStreamReader(os));
146 
147             String s = null;
148             try {
149                 while ((s = stdInput.readLine()) != null) {
150                     handleInput(s);
151                 }
152                 os.close();
153             } catch (IOException ex) {
154                 // the process has been closed so reading the line has failed, 
155                 // don't worry about it
156                 //ex.printStackTrace();
157             }
158         }
159     }
160 
161     /**
162      * Scanns the output of UCC for some specific srings (Ports bounded. START MATCH). 
163      */
164     public class ScannerSink extends StreamSink {
165 
166         public long startingTimeout = 2 * 60 * 1000;
167         /** Exception that ended the startig. Should be checked after the latch is raised. */
168         public UCCStartException exception = null;
169 
170         public ScannerSink(InputStream is) {
171             super(is);
172             timer.schedule(task = new TimerTask() {
173 
174                 @Override
175                 public void run() {
176                     exception = new UCCStartException("Starting timed out. Ports weren't bound in the required time (" + startingTimeout + " ms).", this);
177                     timer.cancel();
178                     portsBindedLatch.countDown();
179                 }
180             }, startingTimeout);
181         }
182         public CountDownLatch portsBindedLatch = new CountDownLatch(1);
183         public int controlPort = -1;
184         public int botsPort = -1;
185         /**
186          * Thread that kills ucc process after specified time if the ports aren't 
187          * read from the console. This prevents freezing the ScannerSink when ucc
188          * fails to start.
189          */
190         Timer timer = new Timer("UCC start timeout");
191         TimerTask task = null;
192         Matcher matcher;
193         
194         @Override
195         protected void handleInput(String str) {
196             super.handleInput(str);
197             //System.out.println("UCCPRNT " + str);
198             if (portsBindedLatch.getCount() != 0) {
199                 // ports still haven't been found, try to scan the line                
200                 if (conf.getPatterns().getObserverPortPattern() != null) {
201                 	matcher = conf.getPatterns().getObserverPortPattern().matcher(str);
202                 	if (matcher.find()) {
203                 		observerPort = Integer.parseInt(matcher.group(1));
204                 	}
205                 }
206                 if (conf.getPatterns().getControlPortPattern() != null) {
207                 	matcher = conf.getPatterns().getControlPortPattern().matcher(str);
208                 	if (matcher.find()) {
209                 		controlPort = Integer.parseInt(matcher.group(1));
210                 	}
211                 }
212                 if (conf.getPatterns().getBotPortPattern() != null) {
213 	                matcher = conf.getPatterns().getBotPortPattern().matcher(str);
214 	                if (matcher.find()) {
215 	                    botsPort = Integer.parseInt(matcher.group(1));
216 	                    raiseLatch();
217 	                }
218                 }
219                 if (conf.getPatterns().getCommandletNotFoundPattern() != null) {
220 	                matcher = conf.getPatterns().getCommandletNotFoundPattern().matcher(str);
221 	                if (matcher.find()) {
222 	                    exception = new UCCStartException("UCC failed to start due to: Commandlet server not found.", this);
223 	                    raiseLatch();
224 	                }
225                 }
226                 if (conf.getPatterns().getMapNotFoundPattern() != null) {
227 	                matcher = conf.getPatterns().getMapNotFoundPattern().matcher(str);
228 	                if (matcher.find()) {
229 	                    exception = new UCCStartException("UCC failed to start due to: Map not found.", this);
230 	                    raiseLatch();
231 	                }
232                 }
233                 
234                 if (conf.getPatterns().getExitingErrorPattern() != null) {
235 	                matcher = conf.getPatterns().getExitingErrorPattern().matcher(str);
236 	                if (matcher.find()) {
237 	                	exception = new UCCStartException("UCC failed to start to due to some error.", this);
238 	                	raiseLatch();
239 	                }
240                 }
241 
242                 if (conf.getPatterns().getMatchStartedPattern() != null) {
243 	                matcher = conf.getPatterns().getMatchStartedPattern().matcher(str);
244 	                if (matcher.find()) {
245 	                    // The match has started, raise the latch
246 	                    raiseLatch();
247 	                }
248                 } else {
249                 	exception = new UCCStartException("conf.getPatterns().getMatchStartedPattern() is NULL, there is no way how to recognize successful UCC startup.", this);
250                 	raiseLatch();
251                 }
252             } else {
253             	if (conf.getPatterns().getGameEndingPattern() != null) {
254             		matcher = conf.getPatterns().getGameEndingPattern().matcher(str);
255             		if (matcher.find()) {
256             			gameEnding.setFlag(true);
257             		}            		
258             	}
259             }
260 
261         }
262 
263         protected void raiseLatch() {
264             timer.cancel();
265             task.cancel();
266             portsBindedLatch.countDown();
267         }
268     }
269     public static long stamp = System.currentTimeMillis();
270 
271     protected void initUCCWrapper() throws UCCStartException {
272     	boolean exception = false;
273         try {
274             // start new ucc instance
275             String id = System.currentTimeMillis() + "a" + fileCounter++;
276             String fileWithPorts = "GBports" + id;
277             String uccHomePath = getUnrealHome();
278             String systemDirPath = uccHomePath + File.separator + "System" + File.separator;
279 
280             // default ucc executable for Windows
281             String uccFile = "ucc.exe";
282 
283             // determine OS type, if it isn't win then add option to ucc 
284             String options = "";
285             if (!System.getProperty("os.name").contains("Windows")) {
286                 options = " -nohomedir";
287                 uccFile = "ucc";
288                 if (System.getProperty("os.name").toLowerCase().contains("linux")) {
289                     uccFile = "ucc-bin";
290                     if (System.getProperty("os.arch").toLowerCase().contains("amd64")) {
291                         Logger.getLogger("UCCWrapper").info("64bit arch detected (os.arch property contains keyword amd64). Using 64bit binarry.");
292                         uccFile += "-linux-amd64";
293                     }
294                 }
295             }
296 
297             String execStr = systemDirPath + uccFile;
298             String portsSetting = conf.startOnUnusedPort ? "?PortsLog=" + fileWithPorts + "?bRandomPorts=true" : "";
299             String playerPortSetting = conf.playerPort != -1 ? "-port=" + conf.playerPort : "";
300 
301             String parameter = conf.mapName
302                     + "?game=" + conf.gameBotsPack + "." + conf.gameType
303                     + portsSetting + conf.options + options;
304 
305             ProcessBuilder procBuilder = new ProcessBuilder(execStr, "server", parameter, playerPortSetting);
306             procBuilder.directory(new File(systemDirPath));
307 
308             uccProcess = procBuilder.start();
309             ScannerSink scanner = new ScannerSink(uccProcess.getInputStream());
310             scanner.start();
311             new StreamSink(uccProcess.getErrorStream()).start();
312 
313             scanner.portsBindedLatch.await(3, TimeUnit.MINUTES);
314             if (scanner.exception != null) {
315                 // ucc failed to start 
316                 try {
317                 	uccProcess.destroy();
318                 } catch (Exception e) {                	
319                 }
320                 uccProcess = null;
321                 throw scanner.exception;
322             }
323             if (scanner.portsBindedLatch.getCount() > 0) {
324             	scanner.interrupt();
325             	try {
326             		uccProcess.destroy();
327             	} catch (Exception e) {            		
328             	}
329             	uccProcess = null;
330             	throw new UCCStartException("UCC did not start in 3 minutes, timeout.", this);
331             }
332 
333             controlPort = scanner.controlPort;
334             gbPort = scanner.botsPort;
335         } catch (InterruptedException ex1) {
336         	exception = true;
337             throw new UCCStartException("Interrupted.", ex1);          
338         } catch (IOException ex2) {
339         	exception = true;
340             throw new UCCStartException("IO Exception.", ex2);
341         } catch (UCCStartException ex3) {
342         	exception = true;
343         	throw ex3;
344         } catch (Exception ex3) {
345         	exception = true;
346             throw new UCCStartException("Exception.", ex3);
347         } finally {
348         	if (exception) {
349         		try {
350         			stop();
351         		} catch (Exception e){        			
352         		}
353         	}
354         }
355     }
356 
357     /**
358      * Process of the
359      * @return
360      */
361     public Process getProcess() {
362         return uccProcess;
363     }
364     /** Was this instance already released? */
365     protected boolean stopped = false;
366 
367     /**
368      * Stops the UCC server.
369      */
370     public synchronized void stop() {
371         stopped = true;
372         if (uccProcess != null) {
373         	uccProcess.destroy();
374         	Runtime.getRuntime().removeShutdownHook(shutDownHook);
375         	uccProcess = null;
376         	try {
377 				Thread.sleep(1000);
378 				// give the process some time to terminate
379 			} catch (InterruptedException e) {
380 
381 			}
382         }
383     }
384 
385     /**
386      * @return Port for GameBots connection.
387      */
388     public int getBotPort() {
389         stopCheck();
390         return gbPort;
391     }
392     
393     /**
394      * @return Port of the Observer of GameBots2004.
395      */
396     public int getObserverPort() {
397     	stopCheck();
398     	return observerPort;
399     }
400 
401     /**
402      * @return Port for control connection.
403      */
404     public int getControlPort() {
405         stopCheck();
406         return controlPort;
407     }
408 
409     protected void stopCheck() {
410         if (stopped) {
411             throw new PogamutException("UCC already stopped.", this);
412         }
413     }
414 
415 	public String getHost() {
416 		return "localhost";
417 	}
418 	
419 	public SocketConnectionAddress getBotAddress() {
420 		if (getBotPort() <= 0) {
421 			throw new RuntimeException("Bot port is unavailable, wrong bot-port matching pattern?");
422 		}
423 		return new SocketConnectionAddress(getHost(), getBotPort());
424 	}
425 	
426 	public SocketConnectionAddress getServerAddress() {
427 		if (getControlPort() <= 0) {
428 			throw new RuntimeException("Control port is unavailable, wrong control-port matching pattern?");
429 		}
430 		return new SocketConnectionAddress(getHost(), getControlPort());
431 	}
432 	
433 	public SocketConnectionAddress getObserverAddress() {
434 		if (getObserverPort() <= 0) {
435 			throw new RuntimeException("Observer port is unavailable, wrong observer-port matching pattern?");
436 		}
437 		return new SocketConnectionAddress(getHost(), getObserverPort());
438 	}
439 
440 	/**
441 	 * Returns flag that is indicating FIRST GAME end.
442 	 * @return
443 	 */
444 	public Flag<Boolean> getGameEnding() {
445 		return gameEnding;
446 	}
447 	
448 }