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
33
34
35
36
37
38
39
40
41
42
43
44
45
46 public class UCCWrapper {
47
48
49
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
84
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
96
97
98
99
100
101 public void setUnrealHome(String unrealHome) {
102 this.unrealHome = unrealHome;
103 }
104
105
106
107
108
109 public UCCWrapperConf setStartOnUnusedPort(boolean startOnUnusedPort) {
110 this.startOnUnusedPort = startOnUnusedPort;
111 return this;
112 }
113
114
115
116
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
130
131
132 public UCCWrapperConf setGameType(String gameType) {
133 this.gameType = gameType;
134 return this;
135 }
136
137
138
139
140
141 public UCCWrapperConf setOptions(String options) {
142 this.options = options;
143 return this;
144 }
145
146
147
148
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
193 protected LogCategory uccLog;
194 protected static int fileCounter = 0;
195 Process uccProcess = null;
196
197 protected int gbPort = -1;
198
199 protected int controlPort = -1;
200
201 protected int observerPort = -1;
202 protected IUT2004Server utServer = null;
203
204 protected static final int basePort = 39782;
205 protected static Integer nextUccWrapperUID = 0;
206
207 protected int uccWrapperUID = 0;
208 protected UCCWrapperConf configuration = null;
209
210
211
212
213
214
215
216
217 public Logger getLogger() {
218 return uccLog;
219 }
220
221 public UCCWrapperConf getConfiguration() {
222 return configuration;
223 }
224
225
226
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
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
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
296
297
298 }
299 }
300 }
301
302
303
304
305 public class ScannerSink extends StreamSink {
306
307 public long startingTimeout = 2 * 60 * 1000;
308
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
328
329
330
331 Timer timer = new Timer("UCC start timeout");
332 TimerTask task = null;
333
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
345 if (portsBindedLatch.getCount() != 0) {
346
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
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
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
402 String uccFile = "ucc.exe";
403
404
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
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
479
480
481 public Process getProcess() {
482 return uccProcess;
483 }
484
485 protected boolean stopped = false;
486
487
488
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
499 } catch (InterruptedException e) {
500
501 }
502 }
503 }
504
505
506
507
508 public int getBotPort() {
509 stopCheck();
510 return gbPort;
511 }
512
513
514
515
516 public int getObserverPort() {
517 stopCheck();
518 return observerPort;
519 }
520
521
522
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 }