View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server;
2   
3   import java.io.IOException;
4   import java.net.InetSocketAddress;
5   import java.util.ArrayList;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.Map;
9   import java.util.Map.Entry;
10  import java.util.Set;
11  import java.util.logging.Logger;
12  
13  import org.apache.mina.core.service.IoAcceptor;
14  import org.apache.mina.core.service.IoHandler;
15  import org.apache.mina.core.session.IdleStatus;
16  import org.apache.mina.core.session.IoSession;
17  import org.apache.mina.filter.codec.ProtocolCodecFilter;
18  import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
19  import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
20  
21  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
22  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerMessage;
23  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestCreateChannel;
24  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestDestroyChannel;
25  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestGetStatus;
26  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestJoinChannel;
27  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestLeaveChannel;
28  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestRegister;
29  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCInfoMessage;
30  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCMessage;
31  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCRecipient;
32  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCRequestData;
33  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCRequestMessage;
34  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.model.TCChannel;
35  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.model.TCTeam;
36  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoBotJoined;
37  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoBotLeft;
38  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoRequestFailed;
39  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoRequestFailureType;
40  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoStatus;
41  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelBotJoined;
42  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelBotLeft;
43  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelCreated;
44  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelDestroyed;
45  import cz.cuni.amis.pogamut.ut2004.teamcomm.server.UT2004TCServer;
46  import cz.cuni.amis.utils.ExceptionToString;
47  import cz.cuni.amis.utils.NullCheck;
48  import cz.cuni.amis.utils.exception.PogamutException;
49  import cz.cuni.amis.utils.flag.Flag;
50  import cz.cuni.amis.utils.flag.ImmutableFlag;
51  import cz.cuni.amis.utils.maps.LazyMap;
52  
53  public class TCMinaServer implements IoHandler {
54  	
55  	private UT2004TCServer owner;
56  	
57  	private InetSocketAddress address;
58  
59  	private Logger log;
60  	
61  	private Object mutex = new Object();
62  	
63  	private Flag<Boolean> running = new Flag<Boolean>(false);
64  
65  	private IoAcceptor ioAcceptor;
66  	
67  	private Map<Integer, TCTeam> teams = new LazyMap<Integer, TCTeam>() {
68  
69  		@Override
70  		protected TCTeam create(Integer key) {
71  			return new TCTeam(key);
72  		}
73  		
74  	};
75  	
76  	private Map<UnrealId, PlayerMessage> registeredBots = new HashMap<UnrealId, PlayerMessage>();
77  	
78  	private Map<UnrealId, IoSession> botSessions = new HashMap<UnrealId, IoSession>();
79  	
80  	public TCMinaServer(UT2004TCServer owner, InetSocketAddress bindAddress, Logger log) {
81  		this.owner = owner;
82  		this.address = bindAddress;
83  		NullCheck.check(this.address, "bindAddress");
84  		this.log = log;
85  		NullCheck.check(this.log, "log");
86  	}
87  	
88  	// ================
89  	// PUBLIC INTERFACE
90  	// ================
91  	
92  	/**
93  	 * 'owner' is reporting that some bot has left the UT2004 server. 
94  	 *
95  	 * @param botId
96  	 */
97  	public void botLeft(UnrealId botId) {		
98  		if (registeredBots.containsKey(botId)) {
99  			unregisterBot(botId);			
100 		}		
101 	}
102 	
103 	/**
104 	 * Whether the TCMinaServer is running. 
105 	 *
106 	 * @return
107 	 */
108 	public ImmutableFlag<Boolean> getRunning() {
109 		return running.getImmutable();
110 	}
111 	
112 	/**
113 	 * Starts this {@link TCMinaServer}. 
114 	 *
115 	 * @throws PogamutException
116 	 */
117 	public void start() throws PogamutException {
118 		try {
119 			synchronized(mutex) {
120 				log.warning("Starting TCMinaServer!");
121 				log.warning("Opening TC Socket: " + address.getHostName() + ":" + address.getPort());
122 				
123 				ioAcceptor = new NioSocketAcceptor();
124 				
125 				ioAcceptor.setHandler(this);
126 				
127 				ioAcceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
128 				
129 				try {
130 					ioAcceptor.bind(address);
131 				} catch (IOException e) {
132 					stop();
133 					throw new PogamutException("Failed to open server socket for TCMinaServer at " + address, e, log);				
134 				}
135 				
136 				log.warning("TCMinaServer running!");
137 				running.setFlag(true);
138 			}
139 		} catch (Exception e) {
140 			log.severe(ExceptionToString.process("Failed to start TCMinaServer!", e));
141 			stop();
142 		}
143 	}
144 	
145 	/**
146 	 * Stops this {@link TCMinaServer}. 
147 	 *
148 	 * @throws PogamutException
149 	 */
150 	public void stop() {
151 		synchronized(mutex) {
152 			log.warning("Stopping TCMinaServer!");
153 			if (ioAcceptor != null) {
154 				log.warning("Closing TCMinaServer Socket!");
155 				try {
156 					ioAcceptor.unbind();
157 				} catch (Exception e) {				
158 				}
159 				try {
160 					ioAcceptor.dispose();
161 				} catch (Exception e) {
162 					ioAcceptor = null;
163 				}			
164 			}
165 			
166 			botSessions.clear();
167 			registeredBots.clear();
168 			teams.clear();
169 			
170 			try {
171 				running.setFlag(false);
172 			} catch (Exception e) {			
173 			}
174 			
175 			log.warning("TCMinaServer stopped!");
176 		}
177 	}
178 	
179 	// ================================
180 	// IMPLEMENTATING {@link IOHandler}
181 	// ================================
182 
183 	@Override
184 	public void exceptionCaught(IoSession session, Throwable exception) throws Exception {
185 		sessionError(session, ExceptionToString.process("TCMinaServer uncaught exception!", exception));
186 	}
187 
188 	@Override
189 	public void messageReceived(IoSession session, Object message) throws Exception {
190 		if (message == null) {
191 			sessionError(session, "NULL message received.");
192 			return;
193 		}
194 		if (!(message instanceof TCMessage)) {
195 			sessionError(session, "Received message that was not TCMessage.");
196 			return;
197 		}
198 		
199 		TCMessage tcMessage = (TCMessage)message;
200 		
201 		UnrealId source = tcMessage.getSource();
202 		if (source == null) {
203 			sessionError(session, "TCMessage.getSource() is NULL.");
204 			return;
205 		}
206 						
207 		if (!ensureSession(source, session)) {
208 			// FAILED TO BIND 'session' WITH ITS 'source'
209 			sessionError(session, "Failed to bind the session with the source.");
210 			return;
211 		}
212 		
213 		if (tcMessage.getMessageType() == null) {
214 			invalidRequest(session, TCInfoRequestFailureType.INVALID_MESSAGE_TYPE, "TCMessage.getMessageType() is NULL, cannot process: " + String.valueOf(message), -1, source);
215 			return;
216 		}
217 		if (tcMessage.getTarget() == null) {
218 			invalidRequest(session, TCInfoRequestFailureType.INVALID_RECIPIENT, "TCMessage.getTarget() is is NULL, cannot process the message: " + String.valueOf(message), -1, source);
219 			return;
220 		}
221 		
222 		PlayerMessage player = owner.getPlayer(source);
223 		
224 		if (registeredBots.containsKey(source)) {
225 		
226 			if (player == null) {
227 				log.warning(source.getStringId() + ": Cannot process the message as the bot has already left the UT2004 server!");
228 				unregisterBot(source);
229 				return;
230 			}
231 			
232 			// BOT HAS ITS SESSION BOUND AT THIS POINT
233 			
234 		} else {
235 		
236 			if (player == null) {
237 				invalidRequest(session, TCInfoRequestFailureType.UNKNOWN_BOT_UNREALID, "TCServer does not have info that this bot is connected to UT2004. If you are, please try again later (in about 5 secs).", -1, source);
238 				return;
239 			}
240 			
241 			if (tcMessage.getTarget() == TCRecipient.TC_REQUEST && tcMessage.getMessageType() == TCRequestRegister.MESSAGE_TYPE && tcMessage instanceof TCRequestMessage) {
242 				TCRequestRegister data;
243 				try {
244 					data = (TCRequestRegister)tcMessage.getMessage();					
245 				} catch (Exception e) {
246 					invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_DESERIALIZE_REQUEST_DATA, "Invalid request data, failed to deserialize TCMessage.getMessage() as TCRequestData.", -1, source);
247 					return;
248 				}
249 				// REGISTER THE BOT!
250 				if (!registerBot(session, source, player, (TCRequestMessage)tcMessage, data)) {
251 					invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_REGISTER_THE_BOT, "TCServer could not register the bot bot with UnrealId '" + source.getStringId() + "'.", -1, source);
252 					return;
253 				}
254 				// BOT HAS BEEN REGISTERED
255 				return;
256 			}	
257 			
258 		}
259 		
260 		switch(tcMessage.getTarget()) {
261 		case CHANNEL:			
262 			channelMessage(tcMessage, session, source, player);
263 			return;
264 		case GLOBAL:
265 			globalMessage(tcMessage, session, source, player);
266 			return;
267 		case PRIVATE:
268 			privateMessage(tcMessage, session, source, player);
269 			return;
270 		case TEAM:
271 			teamMessage(tcMessage, session, source, player);
272 			return;
273 		case TC_INFO:
274 			invalidRequest(session, TCInfoRequestFailureType.INVALID_RECIPIENT, "TCServer does not process TC_INFO messages, they are not meant to be sent to TCServer EVER!", -1, source);
275 			return;
276 		case TC_REQUEST:
277 			TCRequestData requestData = null;
278 			try {
279 				if (tcMessage instanceof TCRequestMessage) {
280 					requestData = (TCRequestData)tcMessage.getMessage();					
281 				} else {
282 					invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_DESERIALIZE_REQUEST_DATA, "TCMessage recipient is TC_REQUEST, but the message is not TCRequestMessage.", -1, source);
283 					return;
284 				}
285 			} catch (Exception e) {
286 				invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_DESERIALIZE_REQUEST_DATA, "Invalid request data, failed to deserialize TCMessage.getMessage() as TCRequestData.", -1, source);
287 				return;
288 			}
289 			requestMessage((TCRequestMessage)tcMessage, requestData, session, source, player);			
290 			return;
291 		}
292 	}
293 	
294 	@Override
295 	public void messageSent(IoSession session, Object message) throws Exception {
296 	}
297 
298 	@Override
299 	public void sessionClosed(IoSession session) throws Exception {
300 	}
301 
302 	@Override
303 	public void sessionCreated(IoSession session) throws Exception {
304 	}
305 
306 	@Override
307 	public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
308 	}
309 
310 	@Override
311 	public void sessionOpened(IoSession session) throws Exception {
312 	}
313 
314 	// ==================
315 	// TCMESSAGE HANDLERS
316 	// ==================
317 	
318 	private void globalMessage(TCMessage message, IoSession session, UnrealId source, PlayerMessage player) {
319 		sendGlobalExcept(message, source);
320 	}
321 
322 	private void teamMessage(TCMessage message, IoSession session, UnrealId source, PlayerMessage player) {
323 		sendTeamExcept(message, player.getTeam(), source);
324 	}
325 	
326 	private void channelMessage(TCMessage message, IoSession session, UnrealId source, PlayerMessage player) {
327 		sendChannelExcept(message, player.getTeam(), message.getChannelId(), source);
328 	}
329 	
330 	private void privateMessage(TCMessage message, IoSession session, UnrealId source, PlayerMessage player) {
331 		sendPrivate(message, message.getTargetId());
332 	}
333 
334 	private void requestMessage(TCRequestMessage message, TCRequestData data, IoSession session, UnrealId source, PlayerMessage player) {
335 		if (data == null) {
336 			invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_DESERIALIZE_REQUEST_DATA, "Request data are NULL", -1, source);
337 			return;
338 		}
339 		
340 		if (data instanceof TCRequestCreateChannel) {
341 			requestCreateChannel(message, (TCRequestCreateChannel)data, session, source, player);
342 		} else
343 		if (data instanceof TCRequestDestroyChannel) {
344 			requestDestroyChannel(message, (TCRequestDestroyChannel)data, session, source, player);
345 		} else
346 		if (data instanceof TCRequestGetStatus) {
347 			requestGetStatus(message, (TCRequestGetStatus)data, session, source, player);
348 		} else
349 		if (data instanceof TCRequestJoinChannel) {
350 			requestJoinChannel(message, (TCRequestJoinChannel)data, session, source, player);
351 		} else
352 		if (data instanceof TCRequestLeaveChannel) {
353 			requestLeaveChannel(message, (TCRequestLeaveChannel)data, session, source, player);
354 		} else
355 		if (data instanceof TCRequestRegister) {
356 			throw new RuntimeException("Should not reach here!");
357 		} else {
358 			invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_PROCESS_REQUEST, "Unknown request.", data.getRequestId(), source);
359 		}
360 	}
361 	
362 	// ================
363 	// REQUEST HANDLERS
364 	// ================
365 
366 	private void requestCreateChannel(TCRequestMessage message, TCRequestCreateChannel data, IoSession session, UnrealId source, PlayerMessage player) {		
367 		synchronized(mutex) {
368 			if (!running.getFlag()) return;
369 			TCTeam team = teams.get(player.getTeam());
370 			synchronized(team) {
371 				Map<Integer, TCChannel> channels = team.getChannels();
372 				synchronized(channels) {
373 					int newChannelId = team.getNextChannelId();
374 					TCChannel newChannel = new TCChannel();
375 					newChannel.setChannelId(newChannelId);
376 					newChannel.setCreator(source);
377 					newChannel.getConnectedBots().add(source);
378 					channels.put(newChannelId, newChannel);
379 									
380 					TCInfoTeamChannelCreated infoDataTeam = new TCInfoTeamChannelCreated(-1, owner.getSimTime());
381 					infoDataTeam.setChannel(newChannel.clone());
382 					TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
383 					sendTeamExcept(infoMessageTeam, player.getTeam(), source);
384 					
385 					TCInfoTeamChannelCreated infoDataRequestee = new TCInfoTeamChannelCreated(data.getRequestId(), owner.getSimTime());
386 					infoDataRequestee.setChannel(newChannel.clone());
387 					TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
388 					sendPrivate(infoMessageRequestee, source);				
389 				}
390 			}
391 		}
392 	}
393 	
394 	private void requestDestroyChannel(TCRequestMessage message, TCRequestDestroyChannel data, IoSession session, UnrealId source, PlayerMessage player) {
395 		synchronized(mutex) {
396 			if (!running.getFlag()) return;
397 			TCTeam team = teams.get(player.getTeam());
398 			synchronized(team) {
399 				Map<Integer, TCChannel> channels = team.getChannels();
400 				synchronized(channels) {
401 					TCChannel channel = channels.get(data.getChannelId());
402 					if (channel == null) {
403 						invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "Cannot destroy team " + player.getTeam() + " channel " + data.getChannelId() + " as it does not exist.", data.getRequestId(), source);
404 						return;
405 					}
406 					if (channel.getCreator() != source) {
407 						invalidRequest(session, TCInfoRequestFailureType.CHANNEL_NOT_OWNED_BY_YOU, "Cannot destroy team " + player.getTeam() + " channel " + data.getChannelId() + " as it is not OWNED by you!", data.getRequestId(), source);
408 						return;
409 					}					
410 					
411 					channels.remove(data.getChannelId());
412 					
413 					TCInfoTeamChannelDestroyed infoDataTeam = new TCInfoTeamChannelDestroyed(-1, owner.getSimTime());
414 					infoDataTeam.setChannelId(data.getChannelId());
415 					infoDataTeam.setDestroyer(source);
416 					TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
417 					sendTeamExcept(infoMessageTeam, player.getTeam(), source);
418 					
419 					TCInfoTeamChannelDestroyed infoDataRequestee = new TCInfoTeamChannelDestroyed(data.getRequestId(), owner.getSimTime());
420 					infoDataRequestee.setChannelId(data.getChannelId());
421 					infoDataRequestee.setDestroyer(source);
422 					TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
423 					sendPrivate(infoMessageRequestee, source);
424 				}
425 			}
426 		}
427 	}
428 	
429 	private void requestGetStatus(TCRequestMessage message, TCRequestGetStatus data, IoSession session, UnrealId source, PlayerMessage player) {
430 		TCInfoStatus infoData = new TCInfoStatus(data.getRequestId(), owner.getSimTime());		
431 		
432 		synchronized(registeredBots) {
433 			infoData.setAllBots(new ArrayList<UnrealId>(registeredBots.keySet()));
434 		}
435 		
436 		TCTeam team = teams.get(player.getTeam());
437 		synchronized(team) {
438 			infoData.setTeam(team.clone());
439 		}
440 		
441 		TCInfoMessage infoMessage = new TCInfoMessage(infoData);
442 		
443 		sendPrivate(infoMessage, source);
444 	}
445 	
446 	private void requestJoinChannel(TCRequestMessage message, TCRequestJoinChannel data, IoSession session, UnrealId source, PlayerMessage player) {
447 		synchronized(mutex) {
448 			if (!running.getFlag()) return;
449 			TCTeam team = teams.get(player.getTeam());
450 			
451 			TCChannel channel = team.getChannels().get(data.getChannelId());
452 			
453 			if (channel == null) {
454 				invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "You cannot join team " + player.getTeam() + " channel " + data.getChannelId() + " as it does not exist.", data.getRequestId(), source);
455 				return;
456 			}
457 			
458 			if (channel.getConnectedBots().contains(source)) {
459 				invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "You are already coonnected to team " + player.getTeam() + " channel " + data.getChannelId() + ".", data.getRequestId(), source);
460 				return;
461 			}
462 			
463 			synchronized(channel) {
464 				channel.getConnectedBots().add(source);
465 				
466 				TCInfoTeamChannelBotJoined infoDataTeam = new TCInfoTeamChannelBotJoined(-1, owner.getSimTime());
467 				infoDataTeam.setChannelId(data.getChannelId());
468 				infoDataTeam.setBotId(source);
469 				TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
470 				sendTeamExcept(infoMessageTeam, player.getTeam(), source);
471 				
472 				TCInfoTeamChannelBotJoined infoDataRequestee = new TCInfoTeamChannelBotJoined(data.getRequestId(), owner.getSimTime());
473 				infoDataRequestee.setChannelId(data.getChannelId());
474 				infoDataRequestee.setBotId(source);
475 				TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
476 				sendPrivate(infoMessageRequestee, source);			
477 			}
478 		}
479 	}
480 	
481 	private void requestLeaveChannel(TCRequestMessage message, TCRequestLeaveChannel data, IoSession session, UnrealId source, PlayerMessage player) {
482 		synchronized(mutex) {
483 			if (!running.getFlag()) return;
484 			TCTeam team = teams.get(player.getTeam());
485 			
486 			TCChannel channel = team.getChannels().get(data.getChannelId());
487 			
488 			if (channel == null) {
489 				invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "You cannot leave team " + player.getTeam() + " channel " + data.getChannelId() + " as it does not exist.", data.getRequestId(), source);
490 				return;
491 			}
492 			
493 			if (!channel.getConnectedBots().contains(source)) {
494 				invalidRequest(session, TCInfoRequestFailureType.NOT_CONNECTED_TO_CHANNEL, "You are not coonnected to team " + player.getTeam() + " channel " + data.getChannelId() + ".", data.getRequestId(), source);
495 				return;
496 			}
497 			
498 			synchronized(channel) {
499 				channel.getConnectedBots().remove(source);
500 				
501 				TCInfoTeamChannelBotLeft infoDataTeam = new TCInfoTeamChannelBotLeft(-1, owner.getSimTime());
502 				infoDataTeam.setChannelId(data.getChannelId());
503 				infoDataTeam.setBotId(source);
504 				TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
505 				sendTeamExcept(infoMessageTeam, player.getTeam(), source);
506 				
507 				TCInfoTeamChannelBotLeft infoDataRequestee = new TCInfoTeamChannelBotLeft(data.getRequestId(), owner.getSimTime());
508 				infoDataRequestee.setChannelId(data.getChannelId());
509 				infoDataRequestee.setBotId(source);
510 				TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
511 				sendPrivate(infoMessageRequestee, source);			
512 			}
513 		}
514 	}
515 	
516 	// ============
517 	// BROADCASTING
518 	// ============
519 
520 	private void sendGlobalExcept(TCMessage message, UnrealId except) {
521 		synchronized(botSessions) {
522 			for (Entry<UnrealId, IoSession> botSessionEntry : botSessions.entrySet()) {
523 				if (botSessionEntry.getKey() == except) continue;
524 				synchronized(botSessionEntry.getValue()) {
525 					botSessionEntry.getValue().write(message);
526 				}
527 			}
528 		}
529 	}
530 	
531 	private void sendTeamExcept(TCMessage message, int teamNum, UnrealId except) {
532 		TCTeam team = teams.get(teamNum);
533 		if (team == null) return;
534 		synchronized(team) {
535 			synchronized(team.getConnectedBots()) {
536 				for (UnrealId target : team.getConnectedBots()) {
537 					if (target == except) continue;
538 					IoSession targetSession = botSessions.get(target);
539 					synchronized(targetSession) {
540 						targetSession.write(message);
541 					}
542 				}
543 			}
544 		}
545 	}
546 	
547 	private void sendChannelExcept(TCMessage message, int teamNum, int channelId, UnrealId except) {
548 		TCTeam team = teams.get(teamNum);
549 		if (team == null) return;
550 		TCChannel channel = team.getChannels().get(channelId);
551 		if (channel == null) return;
552 		synchronized(channel) {
553 			synchronized(channel.getConnectedBots()) {
554 				for (UnrealId target : channel.getConnectedBots()) {
555 					if (target == except) continue;
556 					IoSession targetSession = botSessions.get(target);
557 					synchronized(targetSession) {
558 						targetSession.write(message);
559 					}
560 				}
561 			}
562 		}
563 	}
564 	
565 	private void sendPrivate(TCMessage message, UnrealId target) {
566 		IoSession targetSession = botSessions.get(target);
567 		if (targetSession != null) {
568 			synchronized(targetSession) {
569 				targetSession.write(message);
570 			}
571 		}
572 	}
573 	
574 	private void sendInfo(TCInfoMessage message, IoSession session) {
575 		synchronized(session) {
576 			session.write(message);
577 		}
578 	}
579 	
580 	// ======================================
581 	// ENSURE SESSION / UN/REGISTER BOT UTILS
582 	// ======================================
583 	
584 	/**
585 	 * Returns whether the session is OK.
586 	 *
587 	 * @param source
588 	 * @param session
589 	 * @return
590 	 */
591 	private boolean ensureSession(UnrealId source, IoSession session) {
592 		if (source == null) {
593 			return false;
594 		}
595 		if (session == null) {
596 			return false;
597 		}
598 		IoSession existing = botSessions.get(source);
599 		if (existing == null) {
600 			// BIND NEW SESSION
601 			synchronized(mutex) {
602 				if (!running.getFlag()) return false;
603 				existing = botSessions.get(source);
604 				if (existing == null) {
605 					log.warning(source.getStringId() + ": Binding new session for this bot.");
606 					synchronized(botSessions) {
607 						botSessions.put(source, session);
608 					}
609 					return true;
610 				}
611 			}
612 		}
613 		// SESSION ALREADY EXIST FOR THE SOURCE!
614 		if (existing != session) {
615 			return false;
616 		} else {
617 			return true;
618 		}
619 	}
620 	
621 	/**
622 	 * Registers the bot into all internal data structures.
623 	 *  
624 	 * @param session
625 	 * @param source
626 	 * @param player
627 	 * @param tcMessage
628 	 * @param registerData
629 	 * @return whether the bot has been successfully registered
630 	 */
631 	private boolean registerBot(IoSession session, UnrealId source, PlayerMessage player, TCRequestMessage tcMessage, TCRequestRegister registerData) {
632 		TCTeam team = null;
633 		TCInfoStatus data = null;
634 		
635 		synchronized(mutex) {			
636 			if (!running.getFlag()) return false;
637 			
638 			log.warning(source.getStringId() + ": Registering this bot for team " + player.getTeam());
639 			
640 			team = teams.get(player.getTeam());
641 		
642 			synchronized(team) {
643 				synchronized(team.getConnectedBots()) {
644 					team.getConnectedBots().add(source);
645 				}
646 			}
647 			
648 			synchronized(registeredBots) {
649 				registeredBots.put(source, player);
650 			}
651 			
652 			// BOT JOINED
653 			{
654 				log.info(source.getStringId() + ": Bot joined the TC, notifying connected bots.");
655 				
656 				TCInfoBotJoined dataJoined = new TCInfoBotJoined(-1, owner.getSimTime());
657 				dataJoined.setBotId(source);
658 				dataJoined.setTeam(player.getTeam());
659 				
660 				TCInfoMessage messageJoined = new TCInfoMessage(dataJoined);
661 				sendGlobalExcept(messageJoined, source);
662 			}
663 			
664 			// SEND INITIAL STATUS OF TC SERVER
665 			log.info(source.getStringId() + ": Sending initial TCInfoStatus to this newly joined bot.");
666 			data = new TCInfoStatus(-1, owner.getSimTime());
667 			synchronized(registeredBots) {
668 				data.setAllBots(new ArrayList<UnrealId>(registeredBots.keySet()));
669 			}
670 			synchronized(team) {
671 				data.setTeam(team.clone());
672 			}
673 			
674 			TCInfoMessage message = new TCInfoMessage(data);
675 			
676 			sendInfo(message, session);
677 			
678 			return true;
679 		}
680 	}
681 
682 	private void unregisterBot(UnrealId botId) {
683 		if (botId == null) return;
684 		
685 		synchronized(mutex) {
686 			
687 			if (!running.getFlag()) return;
688 			if (!registeredBots.containsKey(botId)) return;
689 			
690 			log.warning(botId.getStringId() + ": Unregistering bot.");
691 			
692 			PlayerMessage player;
693 			synchronized(registeredBots) {
694 				player = registeredBots.remove(botId);
695 			}
696 		
697 			if (player == null) {
698 				log.severe(botId.getStringId() + ": Could not FULLY unregister bot as we do not have PlayerMessage for it! Data structures corrupted.");
699 			} else {
700 				TCTeam team = teams.get(player.getTeam());
701 				if (team == null) {
702 					log.severe(botId.getStringId() + ": Could not FULLY unregister bot as we do not have TCTeam[" + player.getTeam() + "] for it!");
703 				} else {
704 					synchronized(team) {					
705 						team.getConnectedBots().remove(botId);
706 						Set<TCChannel> toRemove = new HashSet<TCChannel>();
707 						synchronized(team.getChannels()) {
708 							for (TCChannel channel : team.getChannels().values()) {
709 								if (channel.getCreator().equals(botId)) {
710 									toRemove.add(channel);
711 								} else {
712 									synchronized(channel) {
713 										channel.getConnectedBots().remove(botId);
714 									}
715 								}
716 							}	
717 							// DESTROY CHANNELS OWNED BY THE BOT
718 							for (TCChannel channel : toRemove) {
719 								team.getChannels().remove(channel.getChannelId());
720 								
721 								// INFORM THE REST OF THE TEAM
722 								TCInfoTeamChannelDestroyed infoDataTeam = new TCInfoTeamChannelDestroyed(-1, owner.getSimTime());
723 								infoDataTeam.setChannelId(channel.getChannelId());
724 								infoDataTeam.setDestroyer(botId);
725 								TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
726 								sendTeamExcept(infoMessageTeam, player.getTeam(), botId);
727 							}
728 						}
729 					}
730 				}
731 			}
732 			
733 			synchronized(botSessions) {
734 				IoSession session = botSessions.remove(botId);				
735 				if (session == null) {
736 					log.severe(botId.getStringId() + ": Could not FULLY unregister bot as we do not have IoSession for it! Data structures corrupted?");
737 				} else {
738 					session.close(true);
739 				}
740 			}	
741 			
742 			TCInfoBotLeft infoData = new TCInfoBotLeft(-1, owner.getSimTime());
743 			infoData.setBotId(botId);
744 			infoData.setTeam(player == null ? -1 : player.getTeam());
745 			TCInfoMessage infoMessage = new TCInfoMessage(infoData);
746 			sendGlobalExcept(infoMessage, botId);			
747 		}		
748 	}
749 
750 	// =================================
751 	// INVALID REQUEST & ERROR REPORTING
752 	// =================================
753 	
754 	/**
755 	 * @param session
756 	 * @param failureType may be null
757 	 * @param reason may be null
758 	 * @param requestId
759 	 * @param source may be null
760 	 */
761 	private void invalidRequest(IoSession session, TCInfoRequestFailureType failureType, String reason, long requestId, UnrealId source) {
762 		if (session == null) return;
763 		
764 		if (failureType == null) failureType = TCInfoRequestFailureType.GENERIC_FAILURE;
765 		if (reason == null || reason.isEmpty()) reason = "No info.";
766 				
767 		log.warning((source == null ? "" : source.getStringId()) + ": InvalidRequest[" + failureType + "] " + reason);
768 		
769 		TCInfoRequestFailed data = new TCInfoRequestFailed(requestId, owner.getSimTime());
770 		data.setFailureType(failureType);
771 		
772 		TCInfoMessage message = new TCInfoMessage(data);
773 		
774 		try {
775 			synchronized(session) {
776 				session.write(message);
777 			}
778 		} catch (Exception e) { // we might have been stopped mean-while...
779 			if (running.getFlag()) {
780 				log.warning(ExceptionToString.process(source.getStringId() + ": Failed to send message to the bot: " + message, e));
781 			}
782 		}
783 	}
784 	
785 	private void sessionError(IoSession session, String reason) {
786 		if (session == null) return;		
787 		
788 		synchronized(mutex) {
789 			
790 			if (!running.getFlag()) return;
791 			
792 			log.warning("SessionError: " + reason);		
793 			
794 			UnrealId source = null;
795 			
796 			synchronized(botSessions) {
797 				for (Entry<UnrealId, IoSession> entry : botSessions.entrySet()) {
798 					if (entry.getValue() == session) {
799 						source = entry.getKey();
800 						break;
801 					}
802 				}
803 			}	
804 			
805 			if (source != null) {
806 				botLeft(source); // will close the session as well!
807 			} else {			
808 				synchronized(session) {
809 					try {
810 						session.close(true);
811 					} catch (Exception e) {
812 						log.warning(ExceptionToString.process("Could not close the session... ???", e));
813 					}
814 				}
815 			}
816 			
817 		}
818 		
819 	}
820 	
821 }