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.io.Serializable;
9   import java.util.Timer;
10  import java.util.TimerTask;
11  import java.util.concurrent.CountDownLatch;
12  import java.util.concurrent.TimeUnit;
13  import java.util.logging.Level;
14  import java.util.logging.Logger;
15  import java.util.regex.Matcher;
16  import java.util.regex.Pattern;
17  
18  import cz.cuni.amis.pogamut.base.agent.impl.AgentId;
19  import cz.cuni.amis.pogamut.base.agent.params.IRemoteAgentParameters;
20  import cz.cuni.amis.pogamut.base.communication.connection.impl.socket.ISocketConnectionAddress;
21  import cz.cuni.amis.pogamut.base.communication.connection.impl.socket.SocketConnectionAddress;
22  import cz.cuni.amis.pogamut.base.utils.Pogamut;
23  import cz.cuni.amis.pogamut.base.utils.PogamutProperty;
24  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
25  import cz.cuni.amis.pogamut.base.utils.logging.LogPublisher;
26  import cz.cuni.amis.pogamut.ut2004.factory.direct.remoteagent.UT2004ServerFactory;
27  import cz.cuni.amis.pogamut.ut2004.server.IUT2004Server;
28  import cz.cuni.amis.pogamut.ut2004.server.exception.UCCStartException;
29  import cz.cuni.amis.utils.exception.PogamutException;
30  
31  /**
32   * Wrapper of running instance of UCC server. Implements pooling of instances.
33   * Usage scenario is:
34   * <code>
35   * UCCWrapper ucc = UCCWrapper.create();
36   * ...
37   * ucc.release();
38   * </code>
39   * The location of UCC executabe will be determined by an environment variable
40   * pogamut.ut2004.home (e.g. c:\Games\UT2004). The property cam be set via <i>java ...
41   * -Dpogamut.ut2004.home=c:\Games\UT2004</i>. Another posibility is to set it
42   * by code <code>System.setProperty("pogamut.ut2004.home", "c:\\Unreal Anthology\\UT2004");</code>.
43   * 
44   * @author Ik
45   */
46  public class UCCWrapper {
47  
48      /**
49       * Configuration object of the UCC wrapper instance.
50       */
51      public static class UCCWrapperConf implements Serializable {
52  
53      	String unrealHome = null;
54          String mapName = "DM-TrainingDay";
55          String gameBotsPack = "GameBots2004";
56          String gameType = "BotDeathMatch";
57          String mutators = "";
58          String options = "";
59          boolean startOnUnusedPort = true;
60          transient Logger log = null;
61  
62          public UCCWrapperConf() {        	
63          }
64          
65          public UCCWrapperConf(UCCWrapperConf uccConf) {
66  			this.unrealHome = uccConf.getUnrealHome();
67  			this.mapName = uccConf.getMapName();
68  			this.gameBotsPack = uccConf.getGameBotsPack();
69  			this.gameType = uccConf.getGameType();
70  			this.mutators = uccConf.getMutators();
71  			this.options = uccConf.getOptions();
72  			this.startOnUnusedPort = uccConf.isStartOnUnusedPort();
73  			this.log = uccConf.getLog();
74          }
75  
76  		@Override
77          public String toString() {
78          	if (this == null) return "UCCWrapperConf";
79          	return getClass().getSimpleName() + "[unrealHome=" + unrealHome + ", gameBotsPack=" + gameBotsPack + ", gameType=" + gameType + ", mutators=" + mutators + ", options=" + options + ", startOnUnusedPorts=" + startOnUnusedPort + "]";
80          }
81          
82          /**
83           * Returns Pogamut.getPlatform().getProperty(PogamutUT2004Property.POGAMUT_UNREAL_HOME) if not specified.
84           * @return
85           */
86          public String getUnrealHome() {
87          	if (unrealHome == null) {
88                  return Pogamut.getPlatform().getProperty(PogamutUT2004Property.POGAMUT_UNREAL_HOME.getKey());
89              } else {
90                  return unrealHome;
91              }
92  		}
93  
94          /**
95           * Sets path to the UT2004 directory, e.g., 'd:\\games\\ut2004'.
96           * <p><p>
97           * Should not need to be set if provided via property file.
98           * 
99           * @param unrealHome
100          */
101 		public void setUnrealHome(String unrealHome) {
102 			this.unrealHome = unrealHome;
103 		}
104 
105 		/**
106          * Forces UCC to find free port and start on it, otherwise it will start on ports 3000 + 3001.
107          * @param startOnUnusedPort
108          */
109         public UCCWrapperConf setStartOnUnusedPort(boolean startOnUnusedPort) {
110             this.startOnUnusedPort = startOnUnusedPort;
111             return this;
112         }
113 
114         /**
115          * Eg. GameBots2004, GBSceanrio etc.
116          * @param gameBotsPack
117          */
118         public UCCWrapperConf setGameBotsPack(String gameBotsPack) {
119             this.gameBotsPack = gameBotsPack;
120             return this;
121         }
122 
123         public UCCWrapperConf setMapName(String mapName) {
124             this.mapName = mapName;
125             return this;
126         }
127 
128         /**
129          * Eg. BotDeathMatch, BotCTFGame etc. Consult GameBots documentation for
130          * complete list available game types.
131          */
132         public UCCWrapperConf setGameType(String gameType) {
133             this.gameType = gameType;
134             return this;
135         }
136 
137         /**
138          * Can be used for setting mutators etc.
139          * @param options
140          */
141         public UCCWrapperConf setOptions(String options) {
142             this.options = options;
143             return this;
144         }
145 
146         /**
147          * Logger used by the UCC.
148          * @param log
149          */
150         public UCCWrapperConf setLogger(Logger log) {
151             this.log = log;
152             return this;
153         }
154 
155 		public String getMutators() {
156 			return mutators;
157 		}
158 
159 		public void setMutators(String mutators) {
160 			this.mutators = mutators;
161 		}
162 
163 		public Logger getLog() {
164 			return log;
165 		}
166 
167 		public void setLog(Logger log) {
168 			this.log = log;
169 		}
170 
171 		public String getMapName() {
172 			return mapName;
173 		}
174 
175 		public String getGameBotsPack() {
176 			return gameBotsPack;
177 		}
178 
179 		public String getGameType() {
180 			return gameType;
181 		}
182 
183 		public String getOptions() {
184 			return options;
185 		}
186 
187 		public boolean isStartOnUnusedPort() {
188 			return startOnUnusedPort;
189 		}
190         
191     }
192     /** Loger containing all output from running instance of UCC. */
193     protected LogCategory uccLog;
194     protected static int fileCounter = 0;
195     Process uccProcess = null;
196     /** Port for bots. */
197     protected int gbPort = -1;
198     /** Port for server connection. */
199     protected int controlPort = -1;
200     /** Port for observer connection. */
201     protected int observerPort = -1;
202     protected IUT2004Server utServer = null;
203     /** First port assigned to a ucc instance. */
204     protected static final int basePort = 39782;
205     protected static Integer nextUccWrapperUID = 0;
206     /** ID of the wrapper object. Useful for debuging. */
207     protected int uccWrapperUID = 0;
208     protected UCCWrapperConf configuration = null;
209     //protected String mapToLoad
210 
211     /**
212      * @return Log with output of UCC. If you want to listen also for messages 
213      * from the startup sequence then use UCCWrapper.create(Logger parent). Set
214      * Parent logger of this log and register listeners before creating this
215      * instance of UCCWrapper.  
216      */
217     public Logger getLogger() {
218         return uccLog;
219     }
220 
221     public UCCWrapperConf getConfiguration() {
222 		return configuration;
223 	}
224 
225 	/**
226      * @return Server connected to this UCC instance.
227      */
228     public IUT2004Server getUTServer() {
229         stopCheck();
230         if (utServer == null) {
231             UT2004ServerFactory factory = new UT2004ServerFactory();
232             UT2004ServerRunner serverRunner = new UT2004ServerRunner(factory, "NBUTServer", "localhost", controlPort);
233             utServer = serverRunner.startAgent();
234         }
235         return utServer;
236     }
237 
238     protected String getUnrealHome() {
239         if (configuration.getUnrealHome() == null) {
240             return Pogamut.getPlatform().getProperty(PogamutUT2004Property.POGAMUT_UNREAL_HOME.getKey());
241         } else {
242             return configuration.getUnrealHome();
243         }
244     }
245 
246     public UCCWrapper(UCCWrapperConf configuration) throws UCCStartException {
247     	uccLog = new LogCategory("Wrapper");
248     	uccLog.addHandler(new LogPublisher.ConsolePublisher(new AgentId("UCC")));
249     	if (configuration.log != null) {
250             uccLog.setParent(configuration.log);
251         }
252         this.configuration = configuration;
253         uccWrapperUID = nextUccWrapperUID++;
254         initUCCWrapper();
255         Runtime.getRuntime().addShutdownHook(shutDownHook);
256     }
257     /**
258      * Task that will kill the UCC process when user forgets to do so.
259      */
260     Thread shutDownHook = new Thread("UCC wrapper finalizer") {
261 
262         @Override
263         public void run() {
264             if (uccProcess != null) uccProcess.destroy();
265         }
266     };
267 
268     /**
269      * Reads content of the stream and discards it.
270      */
271     protected class StreamSink extends Thread {
272 
273         protected InputStream os = null;
274 
275         public StreamSink(InputStream os) {
276             setName("UCC Stream handler");
277             this.os = os;
278         }
279 
280         protected void handleInput(String str) {
281             if (uccLog.isLoggable(Level.INFO)) uccLog.info("ID" + uccWrapperUID + " " + str);
282         }
283 
284         @Override
285         public void run() {
286             BufferedReader stdInput = new BufferedReader(new InputStreamReader(os));
287 
288             String s = null;
289             try {
290                 while ((s = stdInput.readLine()) != null) {
291                     handleInput(s);
292                 }
293                 os.close();
294             } catch (IOException ex) {
295                 // the process has been closed so reading the line has failed, 
296                 // don't worry about it
297                 //ex.printStackTrace();
298             }
299         }
300     }
301 
302     /**
303      * Scanns the output of UCC for some specific srings (Ports bounded. START MATCH). 
304      */
305     public class ScannerSink extends StreamSink {
306 
307         public long startingTimeout = 2 * 60 * 1000;
308         /** Exception that ended the startig. Should be checked after the latch is raised. */
309         public UCCStartException exception = null;
310 
311         public ScannerSink(InputStream is) {
312             super(is);
313             timer.schedule(task = new TimerTask() {
314 
315                 @Override
316                 public void run() {
317                     exception = new UCCStartException("Starting timed out. Ports weren't bound in the required time (" + startingTimeout + " ms).", this);
318                     timer.cancel();
319                     portsBindedLatch.countDown();
320                 }
321             }, startingTimeout);
322         }
323         public CountDownLatch portsBindedLatch = new CountDownLatch(1);
324         public int controlPort = -1;
325         public int botsPort = -1;
326         /**
327          * Thread that kills ucc process after specified time if the ports aren't 
328          * read from the console. This prevents freezing the ScannerSink when ucc
329          * fails to start.
330          */
331         Timer timer = new Timer("UCC start timeout");
332         TimerTask task = null;
333 //        private final Pattern portPattern = Pattern.compile("BotServerPort:(\\d*) ControlServerPort:(\\d*) ObservingServerPort:(\\d*)");
334         private final Pattern botPortPattern = Pattern.compile("BotServerPort:(\\d*)");
335         private final Pattern controlPortPattern = Pattern.compile("ControlServerPort:(\\d*)");
336         private final Pattern observerPortPattern = Pattern.compile("ObservingServerPort:(\\d*)");
337         private final Pattern commandletNotFoundPattern = Pattern.compile("Commandlet server not found");
338         private final Pattern mapNotFoundPattern = Pattern.compile("No maplist entries found matching the current command line.*");
339         private final Pattern matchStartedPattern = Pattern.compile("START MATCH");
340 
341         @Override
342         protected void handleInput(String str) {
343             super.handleInput(str);
344             //System.out.println("UCCPRNT " + str);
345             if (portsBindedLatch.getCount() != 0) {
346                 // ports still havent been found, try to scan the line
347 
348                 Matcher matcher;
349                 matcher = observerPortPattern.matcher(str);
350                 if (matcher.find()) {
351                     observerPort = Integer.parseInt(matcher.group(1));
352                 }
353                 matcher = controlPortPattern.matcher(str);
354                 if (matcher.find()) {
355                     controlPort = Integer.parseInt(matcher.group(1));
356                 }
357                 matcher = botPortPattern.matcher(str);
358                 if (matcher.find()) {
359                     botsPort = Integer.parseInt(matcher.group(1));
360                     raiseLatch();
361                 }
362 
363                 matcher = commandletNotFoundPattern.matcher(str);
364                 if (matcher.find()) {
365                     exception = new UCCStartException("UCC failed to start due to: Commandlet server not found.", this);
366                     raiseLatch();
367                 }
368 
369                 matcher = mapNotFoundPattern.matcher(str);
370                 if (matcher.find()) {
371                     exception = new UCCStartException("UCC failed to start due to: Map not found.", this);
372                     raiseLatch();
373                 }
374 
375                 matcher = matchStartedPattern.matcher(str);
376                 if (matcher.find()) {
377                     // The match has started, raise the latch
378                     raiseLatch();
379                 }
380             }
381 
382         }
383 
384         protected void raiseLatch() {
385             timer.cancel();
386             task.cancel();
387             portsBindedLatch.countDown();
388         }
389     }
390     public static long stamp = System.currentTimeMillis();
391 
392     protected void initUCCWrapper() throws UCCStartException {
393     	boolean exception = false;
394         try {
395             // start new ucc instance
396             String id = System.currentTimeMillis() + "a" + fileCounter++;
397             String fileWithPorts = "GBports" + id;
398             String uccHomePath = getUnrealHome();
399             String systemDirPath = uccHomePath + File.separator + "System" + File.separator;
400 
401             // default ucc executable for Windows
402             String uccFile = "ucc.exe";
403 
404             // determine OS type, if it isn't win then add option to ucc 
405             String options = "";
406             if (!System.getProperty("os.name").contains("Windows")) {
407                 options = " -nohomedir";
408                 uccFile = "ucc";
409                 if (System.getProperty("os.name").toLowerCase().contains("linux")) {
410                     uccFile = "ucc-bin";
411                     if (System.getProperty("os.arch").toLowerCase().contains("amd64")) {
412                         Logger.getLogger("UCCWrapper").info("64bit arch detected (os.arch property contains keyword amd64). Using 64bit binarry.");
413                         uccFile += "-linux-amd64";
414                     }
415                 }
416             }
417 
418             String execStr = systemDirPath + uccFile;
419             String portsSetting = configuration.startOnUnusedPort ? "?PortsLog=" + fileWithPorts + "?bRandomPorts=true" : "";
420 
421             String parameter = configuration.mapName
422                     + "?game=" + configuration.gameBotsPack + "." + configuration.gameType
423                     + portsSetting + configuration.options + options;
424 
425             ProcessBuilder procBuilder = new ProcessBuilder(execStr, "server", parameter);
426             procBuilder.directory(new File(systemDirPath));
427 
428             uccProcess = procBuilder.start();
429             ScannerSink scanner = new ScannerSink(uccProcess.getInputStream());
430             scanner.start();
431             new StreamSink(uccProcess.getErrorStream()).start();
432 
433             scanner.portsBindedLatch.await(3, TimeUnit.MINUTES);
434             if (scanner.exception != null) {
435                 // ucc failed to start 
436                 try {
437                 	uccProcess.destroy();
438                 } catch (Exception e) {                	
439                 }
440                 uccProcess = null;
441                 throw scanner.exception;
442             }
443             if (scanner.portsBindedLatch.getCount() > 0) {
444             	scanner.interrupt();
445             	try {
446             		uccProcess.destroy();
447             	} catch (Exception e) {            		
448             	}
449             	uccProcess = null;
450             	throw new UCCStartException("UCC did not start in 3 minutes, timeout.", this);
451             }
452 
453             controlPort = scanner.controlPort;
454             gbPort = scanner.botsPort;
455         } catch (InterruptedException ex1) {
456         	exception = true;
457             throw new UCCStartException("Interrupted.", ex1);          
458         } catch (IOException ex2) {
459         	exception = true;
460             throw new UCCStartException("IO Exception.", ex2);
461         } catch (UCCStartException ex3) {
462         	exception = true;
463         	throw ex3;
464         } catch (Exception ex3) {
465         	exception = true;
466             throw new UCCStartException("Exception.", ex3);
467         } finally {
468         	if (exception) {
469         		try {
470         			stop();
471         		} catch (Exception e){        			
472         		}
473         	}
474         }
475     }
476 
477     /**
478      * Process of the
479      * @return
480      */
481     public Process getProcess() {
482         return uccProcess;
483     }
484     /** Was this instance already released? */
485     protected boolean stopped = false;
486 
487     /**
488      * Stops the UCC server.
489      */
490     public synchronized void stop() {
491         stopped = true;
492         if (uccProcess != null) {
493         	uccProcess.destroy();
494         	Runtime.getRuntime().removeShutdownHook(shutDownHook);
495         	uccProcess = null;
496         	try {
497 				Thread.sleep(1000);
498 				// give the process some time to terminate
499 			} catch (InterruptedException e) {
500 
501 			}
502         }
503     }
504 
505     /**
506      * @return Port for GameBots connection.
507      */
508     public int getBotPort() {
509         stopCheck();
510         return gbPort;
511     }
512     
513     /**
514      * @return Port of the Observer of GameBots2004.
515      */
516     public int getObserverPort() {
517     	stopCheck();
518     	return observerPort;
519     }
520 
521     /**
522      * @return Port for control connection.
523      */
524     public int getControlPort() {
525         stopCheck();
526         return controlPort;
527     }
528 
529     protected void stopCheck() {
530         if (stopped) {
531             throw new PogamutException("UCC already stopped.", this);
532         }
533     }
534 
535 	public String getHost() {
536 		return "localhost";
537 	}
538 	
539 	public SocketConnectionAddress getBotAddress() {
540 		return new SocketConnectionAddress(getHost(), getBotPort());
541 	}
542 	
543 	public SocketConnectionAddress getServerAddress() {
544 		return new SocketConnectionAddress(getHost(), getControlPort());
545 	}
546 	
547 	public SocketConnectionAddress getObserverAddress() {
548 		return new SocketConnectionAddress(getHost(), getObserverPort());
549 	}
550 	
551 }