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 		if (message.isExcludeMyselfIfApplicable()) {
320 			sendGlobalExcept(message, source);
321 		} else {
322 			sendGlobalExcept(message, null);
323 		}
324 	}
325 
326 	private void teamMessage(TCMessage message, IoSession session, UnrealId source, PlayerMessage player) {
327 		if (message.isExcludeMyselfIfApplicable()) {
328 			sendTeamExcept(message, player.getTeam(), source);
329 		} else {
330 			sendTeamExcept(message, player.getTeam(), null);
331 		}
332 	}
333 	
334 	private void channelMessage(TCMessage message, IoSession session, UnrealId source, PlayerMessage player) {
335 		if (message.isExcludeMyselfIfApplicable()) {
336 			sendChannelExcept(message, player.getTeam(), message.getChannelId(), source);
337 		} else {
338 			sendChannelExcept(message, player.getTeam(), message.getChannelId(), null);
339 		}
340 	}
341 	
342 	private void privateMessage(TCMessage message, IoSession session, UnrealId source, PlayerMessage player) {
343 		sendPrivate(message, message.getTargetId());
344 	}
345 
346 	private void requestMessage(TCRequestMessage message, TCRequestData data, IoSession session, UnrealId source, PlayerMessage player) {
347 		if (data == null) {
348 			invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_DESERIALIZE_REQUEST_DATA, "Request data are NULL", -1, source);
349 			return;
350 		}
351 		
352 		if (data instanceof TCRequestCreateChannel) {
353 			requestCreateChannel(message, (TCRequestCreateChannel)data, session, source, player);
354 		} else
355 		if (data instanceof TCRequestDestroyChannel) {
356 			requestDestroyChannel(message, (TCRequestDestroyChannel)data, session, source, player);
357 		} else
358 		if (data instanceof TCRequestGetStatus) {
359 			requestGetStatus(message, (TCRequestGetStatus)data, session, source, player);
360 		} else
361 		if (data instanceof TCRequestJoinChannel) {
362 			requestJoinChannel(message, (TCRequestJoinChannel)data, session, source, player);
363 		} else
364 		if (data instanceof TCRequestLeaveChannel) {
365 			requestLeaveChannel(message, (TCRequestLeaveChannel)data, session, source, player);
366 		} else
367 		if (data instanceof TCRequestRegister) {
368 			throw new RuntimeException("Should not reach here!");
369 		} else {
370 			invalidRequest(session, TCInfoRequestFailureType.FAILED_TO_PROCESS_REQUEST, "Unknown request.", data.getRequestId(), source);
371 		}
372 	}
373 	
374 	// ================
375 	// REQUEST HANDLERS
376 	// ================
377 
378 	private void requestCreateChannel(TCRequestMessage message, TCRequestCreateChannel data, IoSession session, UnrealId source, PlayerMessage player) {		
379 		synchronized(mutex) {
380 			if (!running.getFlag()) return;
381 			TCTeam team = teams.get(player.getTeam());
382 			synchronized(team) {
383 				Map<Integer, TCChannel> channels = team.getChannels();
384 				synchronized(channels) {
385 					int newChannelId = team.getNextChannelId();
386 					TCChannel newChannel = new TCChannel();
387 					newChannel.setChannelId(newChannelId);
388 					newChannel.setCreator(source);
389 					newChannel.getConnectedBots().add(source);
390 					channels.put(newChannelId, newChannel);
391 									
392 					TCInfoTeamChannelCreated infoDataTeam = new TCInfoTeamChannelCreated(-1, owner.getSimTime());
393 					infoDataTeam.setChannel(newChannel.clone());
394 					TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
395 					sendTeamExcept(infoMessageTeam, player.getTeam(), source);
396 					
397 					TCInfoTeamChannelCreated infoDataRequestee = new TCInfoTeamChannelCreated(data.getRequestId(), owner.getSimTime());
398 					infoDataRequestee.setChannel(newChannel.clone());
399 					TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
400 					sendPrivate(infoMessageRequestee, source);				
401 				}
402 			}
403 		}
404 	}
405 	
406 	private void requestDestroyChannel(TCRequestMessage message, TCRequestDestroyChannel data, IoSession session, UnrealId source, PlayerMessage player) {
407 		synchronized(mutex) {
408 			if (!running.getFlag()) return;
409 			TCTeam team = teams.get(player.getTeam());
410 			synchronized(team) {
411 				Map<Integer, TCChannel> channels = team.getChannels();
412 				synchronized(channels) {
413 					TCChannel channel = channels.get(data.getChannelId());
414 					if (channel == null) {
415 						invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "Cannot destroy team " + player.getTeam() + " channel " + data.getChannelId() + " as it does not exist.", data.getRequestId(), source);
416 						return;
417 					}
418 					if (channel.getCreator() != source) {
419 						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);
420 						return;
421 					}					
422 					
423 					channels.remove(data.getChannelId());
424 					
425 					TCInfoTeamChannelDestroyed infoDataTeam = new TCInfoTeamChannelDestroyed(-1, owner.getSimTime());
426 					infoDataTeam.setChannelId(data.getChannelId());
427 					infoDataTeam.setDestroyer(source);
428 					TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
429 					sendTeamExcept(infoMessageTeam, player.getTeam(), source);
430 					
431 					TCInfoTeamChannelDestroyed infoDataRequestee = new TCInfoTeamChannelDestroyed(data.getRequestId(), owner.getSimTime());
432 					infoDataRequestee.setChannelId(data.getChannelId());
433 					infoDataRequestee.setDestroyer(source);
434 					TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
435 					sendPrivate(infoMessageRequestee, source);
436 				}
437 			}
438 		}
439 	}
440 	
441 	private void requestGetStatus(TCRequestMessage message, TCRequestGetStatus data, IoSession session, UnrealId source, PlayerMessage player) {
442 		TCInfoStatus infoData = new TCInfoStatus(data.getRequestId(), owner.getSimTime());		
443 		
444 		synchronized(registeredBots) {
445 			infoData.setAllBots(new ArrayList<UnrealId>(registeredBots.keySet()));
446 		}
447 		
448 		TCTeam team = teams.get(player.getTeam());
449 		synchronized(team) {
450 			infoData.setTeam(team.clone());
451 		}
452 		
453 		TCInfoMessage infoMessage = new TCInfoMessage(infoData);
454 		
455 		sendPrivate(infoMessage, source);
456 	}
457 	
458 	private void requestJoinChannel(TCRequestMessage message, TCRequestJoinChannel data, IoSession session, UnrealId source, PlayerMessage player) {
459 		synchronized(mutex) {
460 			if (!running.getFlag()) return;
461 			TCTeam team = teams.get(player.getTeam());
462 			
463 			TCChannel channel = team.getChannels().get(data.getChannelId());
464 			
465 			if (channel == null) {
466 				invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "You cannot join team " + player.getTeam() + " channel " + data.getChannelId() + " as it does not exist.", data.getRequestId(), source);
467 				return;
468 			}
469 			
470 			if (channel.getConnectedBots().contains(source)) {
471 				invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "You are already coonnected to team " + player.getTeam() + " channel " + data.getChannelId() + ".", data.getRequestId(), source);
472 				return;
473 			}
474 			
475 			synchronized(channel) {
476 				channel.getConnectedBots().add(source);
477 				
478 				TCInfoTeamChannelBotJoined infoDataTeam = new TCInfoTeamChannelBotJoined(-1, owner.getSimTime());
479 				infoDataTeam.setChannelId(data.getChannelId());
480 				infoDataTeam.setBotId(source);
481 				TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
482 				sendTeamExcept(infoMessageTeam, player.getTeam(), source);
483 				
484 				TCInfoTeamChannelBotJoined infoDataRequestee = new TCInfoTeamChannelBotJoined(data.getRequestId(), owner.getSimTime());
485 				infoDataRequestee.setChannelId(data.getChannelId());
486 				infoDataRequestee.setBotId(source);
487 				TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
488 				sendPrivate(infoMessageRequestee, source);			
489 			}
490 		}
491 	}
492 	
493 	private void requestLeaveChannel(TCRequestMessage message, TCRequestLeaveChannel data, IoSession session, UnrealId source, PlayerMessage player) {
494 		synchronized(mutex) {
495 			if (!running.getFlag()) return;
496 			TCTeam team = teams.get(player.getTeam());
497 			
498 			TCChannel channel = team.getChannels().get(data.getChannelId());
499 			
500 			if (channel == null) {
501 				invalidRequest(session, TCInfoRequestFailureType.CHANNEL_DOES_NOT_EXIST, "You cannot leave team " + player.getTeam() + " channel " + data.getChannelId() + " as it does not exist.", data.getRequestId(), source);
502 				return;
503 			}
504 			
505 			if (!channel.getConnectedBots().contains(source)) {
506 				invalidRequest(session, TCInfoRequestFailureType.NOT_CONNECTED_TO_CHANNEL, "You are not coonnected to team " + player.getTeam() + " channel " + data.getChannelId() + ".", data.getRequestId(), source);
507 				return;
508 			}
509 			
510 			synchronized(channel) {
511 				channel.getConnectedBots().remove(source);
512 				
513 				TCInfoTeamChannelBotLeft infoDataTeam = new TCInfoTeamChannelBotLeft(-1, owner.getSimTime());
514 				infoDataTeam.setChannelId(data.getChannelId());
515 				infoDataTeam.setBotId(source);
516 				TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
517 				sendTeamExcept(infoMessageTeam, player.getTeam(), source);
518 				
519 				TCInfoTeamChannelBotLeft infoDataRequestee = new TCInfoTeamChannelBotLeft(data.getRequestId(), owner.getSimTime());
520 				infoDataRequestee.setChannelId(data.getChannelId());
521 				infoDataRequestee.setBotId(source);
522 				TCInfoMessage infoMessageRequestee = new TCInfoMessage(infoDataRequestee);
523 				sendPrivate(infoMessageRequestee, source);			
524 			}
525 		}
526 	}
527 	
528 	// ============
529 	// BROADCASTING
530 	// ============
531 
532 	private void sendGlobalExcept(TCMessage message, UnrealId except) {
533 		synchronized(botSessions) {
534 			for (Entry<UnrealId, IoSession> botSessionEntry : botSessions.entrySet()) {
535 				if (botSessionEntry.getKey() == except) continue;
536 				synchronized(botSessionEntry.getValue()) {
537 					botSessionEntry.getValue().write(message);
538 				}
539 			}
540 		}
541 	}
542 	
543 	private void sendTeamExcept(TCMessage message, int teamNum, UnrealId except) {
544 		TCTeam team = teams.get(teamNum);
545 		if (team == null) return;
546 		synchronized(team) {
547 			synchronized(team.getConnectedBots()) {
548 				for (UnrealId target : team.getConnectedBots()) {
549 					if (target == except) continue;
550 					IoSession targetSession = botSessions.get(target);
551 					synchronized(targetSession) {
552 						targetSession.write(message);
553 					}
554 				}
555 			}
556 		}
557 	}
558 	
559 	private void sendChannelExcept(TCMessage message, int teamNum, int channelId, UnrealId except) {
560 		TCTeam team = teams.get(teamNum);
561 		if (team == null) return;
562 		TCChannel channel = team.getChannels().get(channelId);
563 		if (channel == null) return;
564 		synchronized(channel) {
565 			synchronized(channel.getConnectedBots()) {
566 				for (UnrealId target : channel.getConnectedBots()) {
567 					if (target == except) continue;
568 					IoSession targetSession = botSessions.get(target);
569 					synchronized(targetSession) {
570 						targetSession.write(message);
571 					}
572 				}
573 			}
574 		}
575 	}
576 	
577 	private void sendPrivate(TCMessage message, UnrealId target) {
578 		IoSession targetSession = botSessions.get(target);
579 		if (targetSession != null) {
580 			synchronized(targetSession) {
581 				targetSession.write(message);
582 			}
583 		}
584 	}
585 	
586 	private void sendInfo(TCInfoMessage message, IoSession session) {
587 		synchronized(session) {
588 			session.write(message);
589 		}
590 	}
591 	
592 	// ======================================
593 	// ENSURE SESSION / UN/REGISTER BOT UTILS
594 	// ======================================
595 	
596 	/**
597 	 * Returns whether the session is OK.
598 	 *
599 	 * @param source
600 	 * @param session
601 	 * @return
602 	 */
603 	private boolean ensureSession(UnrealId source, IoSession session) {
604 		if (source == null) {
605 			return false;
606 		}
607 		if (session == null) {
608 			return false;
609 		}
610 		IoSession existing = botSessions.get(source);
611 		if (existing == null) {
612 			// BIND NEW SESSION
613 			synchronized(mutex) {
614 				if (!running.getFlag()) return false;
615 				existing = botSessions.get(source);
616 				if (existing == null) {
617 					log.warning(source.getStringId() + ": Binding new session for this bot.");
618 					synchronized(botSessions) {
619 						botSessions.put(source, session);
620 					}
621 					return true;
622 				}
623 			}
624 		}
625 		// SESSION ALREADY EXIST FOR THE SOURCE!
626 		if (existing != session) {
627 			return false;
628 		} else {
629 			return true;
630 		}
631 	}
632 	
633 	/**
634 	 * Registers the bot into all internal data structures.
635 	 *  
636 	 * @param session
637 	 * @param source
638 	 * @param player
639 	 * @param tcMessage
640 	 * @param registerData
641 	 * @return whether the bot has been successfully registered
642 	 */
643 	private boolean registerBot(IoSession session, UnrealId source, PlayerMessage player, TCRequestMessage tcMessage, TCRequestRegister registerData) {
644 		TCTeam team = null;
645 		TCInfoStatus data = null;
646 		
647 		synchronized(mutex) {			
648 			if (!running.getFlag()) return false;
649 			
650 			log.warning(source.getStringId() + ": Registering this bot for team " + player.getTeam());
651 			
652 			team = teams.get(player.getTeam());
653 		
654 			synchronized(team) {
655 				synchronized(team.getConnectedBots()) {
656 					team.getConnectedBots().add(source);
657 				}
658 			}
659 			
660 			synchronized(registeredBots) {
661 				registeredBots.put(source, player);
662 			}
663 			
664 			// BOT JOINED
665 			{
666 				log.info(source.getStringId() + ": Bot joined the TC, notifying connected bots.");
667 				
668 				TCInfoBotJoined dataJoined = new TCInfoBotJoined(-1, owner.getSimTime());
669 				dataJoined.setBotId(source);
670 				dataJoined.setTeam(player.getTeam());
671 				
672 				TCInfoMessage messageJoined = new TCInfoMessage(dataJoined);
673 				sendGlobalExcept(messageJoined, source);
674 			}
675 			
676 			// SEND INITIAL STATUS OF TC SERVER
677 			log.info(source.getStringId() + ": Sending initial TCInfoStatus to this newly joined bot.");
678 			data = new TCInfoStatus(-1, owner.getSimTime());
679 			synchronized(registeredBots) {
680 				data.setAllBots(new ArrayList<UnrealId>(registeredBots.keySet()));
681 			}
682 			synchronized(team) {
683 				data.setTeam(team.clone());
684 			}
685 			
686 			TCInfoMessage message = new TCInfoMessage(data);
687 			
688 			sendInfo(message, session);
689 			
690 			return true;
691 		}
692 	}
693 
694 	private void unregisterBot(UnrealId botId) {
695 		if (botId == null) return;
696 		
697 		synchronized(mutex) {
698 			
699 			if (!running.getFlag()) return;
700 			if (!registeredBots.containsKey(botId)) return;
701 			
702 			log.warning(botId.getStringId() + ": Unregistering bot.");
703 			
704 			PlayerMessage player;
705 			synchronized(registeredBots) {
706 				player = registeredBots.remove(botId);
707 			}
708 		
709 			if (player == null) {
710 				log.severe(botId.getStringId() + ": Could not FULLY unregister bot as we do not have PlayerMessage for it! Data structures corrupted.");
711 			} else {
712 				TCTeam team = teams.get(player.getTeam());
713 				if (team == null) {
714 					log.severe(botId.getStringId() + ": Could not FULLY unregister bot as we do not have TCTeam[" + player.getTeam() + "] for it!");
715 				} else {
716 					synchronized(team) {					
717 						team.getConnectedBots().remove(botId);
718 						Set<TCChannel> toRemove = new HashSet<TCChannel>();
719 						synchronized(team.getChannels()) {
720 							for (TCChannel channel : team.getChannels().values()) {
721 								if (channel.getCreator().equals(botId)) {
722 									toRemove.add(channel);
723 								} else {
724 									synchronized(channel) {
725 										channel.getConnectedBots().remove(botId);
726 									}
727 								}
728 							}	
729 							// DESTROY CHANNELS OWNED BY THE BOT
730 							for (TCChannel channel : toRemove) {
731 								team.getChannels().remove(channel.getChannelId());
732 								
733 								// INFORM THE REST OF THE TEAM
734 								TCInfoTeamChannelDestroyed infoDataTeam = new TCInfoTeamChannelDestroyed(-1, owner.getSimTime());
735 								infoDataTeam.setChannelId(channel.getChannelId());
736 								infoDataTeam.setDestroyer(botId);
737 								TCInfoMessage infoMessageTeam = new TCInfoMessage(infoDataTeam);
738 								sendTeamExcept(infoMessageTeam, player.getTeam(), botId);
739 							}
740 						}
741 					}
742 				}
743 			}
744 			
745 			synchronized(botSessions) {
746 				IoSession session = botSessions.remove(botId);				
747 				if (session == null) {
748 					log.severe(botId.getStringId() + ": Could not FULLY unregister bot as we do not have IoSession for it! Data structures corrupted?");
749 				} else {
750 					session.close(true);
751 				}
752 			}	
753 			
754 			TCInfoBotLeft infoData = new TCInfoBotLeft(-1, owner.getSimTime());
755 			infoData.setBotId(botId);
756 			infoData.setTeam(player == null ? -1 : player.getTeam());
757 			TCInfoMessage infoMessage = new TCInfoMessage(infoData);
758 			sendGlobalExcept(infoMessage, botId);			
759 		}		
760 	}
761 
762 	// =================================
763 	// INVALID REQUEST & ERROR REPORTING
764 	// =================================
765 	
766 	/**
767 	 * @param session
768 	 * @param failureType may be null
769 	 * @param reason may be null
770 	 * @param requestId
771 	 * @param source may be null
772 	 */
773 	private void invalidRequest(IoSession session, TCInfoRequestFailureType failureType, String reason, long requestId, UnrealId source) {
774 		if (session == null) return;
775 		
776 		if (failureType == null) failureType = TCInfoRequestFailureType.GENERIC_FAILURE;
777 		if (reason == null || reason.isEmpty()) reason = "No info.";
778 				
779 		log.warning((source == null ? "" : source.getStringId()) + ": InvalidRequest[" + failureType + "] " + reason);
780 		
781 		TCInfoRequestFailed data = new TCInfoRequestFailed(requestId, owner.getSimTime());
782 		data.setFailureType(failureType);
783 		
784 		TCInfoMessage message = new TCInfoMessage(data);
785 		
786 		try {
787 			synchronized(session) {
788 				session.write(message);
789 			}
790 		} catch (Exception e) { // we might have been stopped mean-while...
791 			if (running.getFlag()) {
792 				log.warning(ExceptionToString.process(source.getStringId() + ": Failed to send message to the bot: " + message, e));
793 			}
794 		}
795 	}
796 	
797 	private void sessionError(IoSession session, String reason) {
798 		if (session == null) return;		
799 		
800 		synchronized(mutex) {
801 			
802 			if (!running.getFlag()) return;
803 			
804 			log.warning("SessionError: " + reason);		
805 			
806 			UnrealId source = null;
807 			
808 			synchronized(botSessions) {
809 				for (Entry<UnrealId, IoSession> entry : botSessions.entrySet()) {
810 					if (entry.getValue() == session) {
811 						source = entry.getKey();
812 						break;
813 					}
814 				}
815 			}	
816 			
817 			if (source != null) {
818 				botLeft(source); // will close the session as well!
819 			} else {			
820 				synchronized(session) {
821 					try {
822 						session.close(true);
823 					} catch (Exception e) {
824 						log.warning(ExceptionToString.process("Could not close the session... ???", e));
825 					}
826 				}
827 			}
828 			
829 		}
830 		
831 	}
832 	
833 }