1  //!-- UTF8
     2  /*
     3   Oregano Multiuser Server - Version 1.2.0 - January 4th, 2005
     4   
     5  	Web:  www.oregano-server.org
     6  	Mail: info@oregano-server.org
     7   
     8  	Copyright 2003 - 2004 - 2004 Jens Halm / Cologne, Germany
     9   
    10   This library is free software; you can redistribute it and/or
    11   modify it under the terms of the GNU Lesser General Public
    12   License as published by the Free Software Foundation; either
    13   version 2.1 of the License, or (at your option) any later version.
    14   
    15   This library is distributed in the hope that it will be useful,
    16   but WITHOUT ANY WARRANTY; without even the implied warranty of
    17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    18   Lesser General Public License for more details.
    19   
    20   You should have received a copy of the GNU Lesser General Public
    21   License along with this library; if not, write to the Free Software
    22   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    23  */
    24  
    25  /*
    26   -------------------------------------------
    27  	Classe Group
    28   
    29  	@description :
    30  	Représentation d'un ensemble d'utilisateurs.
    31  	Singleton
    32   
    33   
    34  	@author Jens Halm copyright http://www.spicefactory.org/
    35  	@author erixtekila copyleft http://www.v-i-a.net  
    36   -------------------------------------------
    37  	version history :
    38  	1.2.0 : 29/12/04
    39  			- Portage en actionscript 2 pour le
    40  			compile time type checking
    41  			- Implémentation du modèle Singleton 
    42  			(abstraite auparavant)
    43   -------------------------------------------
    44   */
    45  
    46  import org.omus.util.EventDispatcher;
    47  import org.omus.util.iObservable;
    48  import org.omus.util._Error;
    49  import org.omus.util._Class;
    50  import org.omus.util.Log;
    51  
    52  import org.omus.msg.MessageHandler;
    53  import org.omus.msg.iMessageHandler;
    54  import org.omus.msg.Message;
    55  import org.omus.msg.Envelope;
    56  import org.omus.msg.EnvelopeFactory;
    57  
    58  import org.omus.data.PropertySet;
    59  import org.omus.data.Table;
    60  
    61  import org.omus.core.Session;
    62  import org.omus.core.User;
    63  
    64  /**
    65   *	Cette classe gère les groupes d'utilisateurs
    66   *	et plus particulièrement celui dont fait partie l'utilisateur client.
    67   *	Chaque groupe doit être déclaré dans le fichier config.xml.
    68   *	La création de groupe dynamique s'obtient en utilisant un configID valide avec un nouveau nom groupName.
    69   *	Un nombre illimité de groupe peut partagé la même valeur de configID définit dans le config.xml.
    70   *	<p>
    71   *	Evénements auxquels s'abonner :
    72   *	<ul>
    73   *	<li>onOpen							Invoqué lorsqu'un groupe est à nouveau ouvert.
    74   *	</li><li>onClose					Invoqué lorsqu'il groupe est vérouillé aux nouveaux arrivants.
    75   *	</li><li>onError(error:_Error)		Invoqué lorsqu'une erreur survient lors de le fermeture/ouverture ou le changement de groupe.
    76   *	</li><li>onChange(data:Object)		Invoqué lorsque le changement de groupe est opéré.
    77   *												_param data	Un objet vide qui nécéssite l'écriture d'une GroupExtension
    78   *	</li><li>onUserJoined(userProps:PropertySet)	Invoqué lorsqu'un nouveau membre accède au groupe. 
    79   *												_param userProps	Propriétés de l'utilisateur.
    80   *	</li><li>onUserLeft(userProps:PropertySet)	Invoqué lorsqu'un member qui le groupe. 
    81   *												_param userProps	Propriétés de l'utilisateur.
    82   *	</li></ul></p>
    83   *	Cette classe est implémentée en suivant le modèle Singleton.
    84   *	Un accès global à son instance est obtenu graçe à la méthode getInstance.
    85   *	Elle est aggrémentée par composition des méthodes des sources d'événements (EventDispatcher).
    86   *	Elle est aggrémentée par composition de la classe MessageHandler.
    87   *	@see org.omus.util.EventDispatcher
    88   *	@see org.omus.util.iObservable
    89   *	@see org.omus.msg.MessageHandler
    90   *	@see org.omus.msg.iMessageHandler
    91   *
    92   *	@author Jens Halm copyright http://www.spicefactory.org/
    93   *	@author erixtekila copyleft http://www.v-i-a.net 
    94   *	@version 1.2.0
    95   */
    96  class org.omus.core.Group implements iObservable, iMessageHandler
    97  {
    98  	//--------------------
    99  	// PROPRIETES
   100  	//--------------------
   101  	/**
   102  	 *	Un objet contenant les propriétés persistantes du groupe.
   103  	 */
   104  	private var propConfig:Object;
   105  	
   106  	/**
   107  	 *	Conserve les informations de login pour la session
   108  	 */
   109  	private var cache:Array;
   110  	
   111  	/**
   112  	 *	Nom littéral du groupe.
   113  	 */
   114  	private var name:String;
   115  	
   116  	/**
   117  	 *	Identifiant unique d'un groupe de groupe correspondant au fichier config.xml.
   118  	 */
   119  	private var configID:String;
   120  	
   121  	/**
   122  	 *	Nombre d'utilisateurs présents dans ce groupe.
   123  	 */
   124  	private var userCount:Number;
   125  	
   126  	/**
   127  	 *	Nombre d'utilisateurs admis dans ce groupe.
   128  	 */
   129  	private var userLimit:Number;
   130  	
   131  	/**
   132  	 *	Un set de propriétés persistantes supplémentaire.
   133  	 */
   134  	private var extraConfig:Object;
   135  	
   136  	/**
   137  	 *	Ouverture du groupe.
   138  	 */
   139  	private var closed:Boolean;
   140  	
   141  	/**
   142  	 *	Un objet contenant une liste de toutes les propriétés persistantes
   143  	 *	de chacun des les utilisateurs inscrits dans le groupe.
   144  	 */
   145  	private var userProps:Object;
   146  	
   147  	/**
   148  	 *	Propriétés persistantes.
   149  	 */
   150  	private var props:PropertySet;
   151  	
   152  	/**
   153  	 *	Référence au système de génération d'événements.
   154  	 */
   155  	private var _eventSource:EventDispatcher;
   156  	
   157  	/**
   158  	 *	Référence à la classe MessageHandler;
   159  	 */
   160  	private var _messageHandler:MessageHandler;
   161  	
   162  	/**
   163  	 *	Référence à l'unique instance de la classe permise.
   164  	 */
   165  	private static var _instance:Group;
   166  	
   167  	//--------------------
   168  	// CONSTRUCTEUR
   169  	//--------------------
   170  	/**
   171  	 *	L'objet Group dispose des méthodes d'EventDispatcher
   172  	 *	et de celles de MessageHandler par composition.
   173  	 *
   174  	 *	@param propConfig		Propriétés persistantes du groupe.
   175  	 *	@see org.omus.util.EventDispatcher
   176  	 *	@see org.omus.util.iObservable
   177  	 *	@see org.omus.msg.MessageHandler
   178  	 *	@see org.omus.msg.iMessageHandler
   179  	 */
   180  	private function Group(propConfig:Object)
   181  	{
   182  		// Composition avec EventDispatcher
   183  		_eventSource = new EventDispatcher();
   184  		// Composition avec MessageHandler
   185  		_messageHandler = new MessageHandler();
   186  		
   187  		// Initialisation des événements
   188  		_messageHandler.initMessageHandler(this);
   189  		
   190  		// Propriétés
   191  		this.propConfig = propConfig;
   192  		cache = new Array();
   193  
   194  		// Callbacks des envois de messages.
   195  		addHandler("open","handleOpen");
   196  		addHandler("close","handleClose");
   197  		addHandler("change","handleChange");
   198  		addHandler("userJoined","handleUserJoined");
   199  		addHandler("userLeft","handleUserLeft");
   200  		
   201  		// trace(this+ " installé.");
   202  	}
   203  	
   204  	
   205  	//--------------------
   206  	// METHODES PUBLIQUES
   207  	//--------------------
   208  	/**
   209  	 *	Utilisé dans un contexte littéral
   210  	 *	@return	Une chaine définissant l'objet
   211  	 */
   212  	public function toString():String
   213  	{
   214  		var s = "[Objet Group]"; 
   215  		s += "org.omus.group:";
   216  		s += "\nname = " + name;
   217  		s += "\nconfigID = " + configID;
   218  		s += "\nuserCount = " + userCount;
   219  		s += "\nuserLimit = " + userLimit;
   220  		s += "\nclosed = " + closed;
   221  		return s;
   222  	}
   223  	
   224  	/**
   225  	 *	Initialisation de toutes les propriétés d'un groupe.
   226  	 *	Doit impérativement être activé après instanciation du constructeur.
   227  	 *	"package"
   228  	 *
   229  	 *	@param groupName		Nom du groupe à créer.
   230  	 *							- Si le groupe existe déjà, configID peut être égal à null.
   231  	 *							- Si le groupe est généré dynamiquement, configID doit être valide.
   232  	 *	@param configID			Identifiant du groupe sur le serveur.
   233  	 *	@param groupID			Identifiant serveur du groupe (clé primaire de la table groupprops).
   234  	 *	@param userLimit		Nombre maximal d'utilisateurs permis.
   235  	 *	@param props			Un objet contenant toutes les propriétés persistantes du groupe.
   236  	 *	@param extraConfig		Un objet contenant toutes les propriétés supplémentaires persistantes du groupe.
   237  	 *	@param uProps			Les utilisateurs inscrits dans un groupe dans la base de données (TODO : à vérifier).
   238  	 */
   239  	private function init (groupName:String, configID:String, groupID:Number, userLimit:Number, props:Object, extraConfig:Object, uProps:Table):Void
   240  	{
   241  		name = groupName;
   242  		this.configID = configID;
   243  		userCount = 1;
   244  		this.userLimit = userLimit;
   245  		this.extraConfig = extraConfig;
   246  		closed = false;
   247  		
   248  		// group properties:
   249  		var ps = new PropertySet("group", this, groupID, null);
   250  		// Rajoute toutes les propriétés persistantes du groupe
   251  		ps.fill(propConfig, props);
   252  		ps.fill(extraConfig, props);
   253  		this.props = ps;
   254  		
   255  		// user properties:
   256  		userProps = new Object();
   257  		// TODO : Accès Singleton
   258  		var user = User.getInstance();
   259  		// Nom de l'utilisateur
   260  		userProps[user.getName()] = user.getProperties();
   261  		//
   262  		if (uProps != null) {
   263  			var len = uProps.size();
   264  			for (var i = 0; i < len; i++) {
   265  				// 
   266  				var usr = uProps.getRow(i);
   267  				addUserProps(usr);
   268  			}
   269  		}
   270  	}
   271  	
   272  	/**
   273  	 *	Renvoie le nom du groupe.
   274  	 *
   275  	 *	@return Le nom du groupe.
   276  	 */
   277  	public function getName ():String
   278  	{
   279  		return name;
   280  	}
   281  	
   282  	/**
   283  	 *	Renvoie l'identifiant statique d'un groupe de groupe.
   284  	 *
   285  	 *	@return Un identifiant correspondant au nœud <group> du fichier config.xml.
   286  	 */
   287  	public function getConfigID ():String
   288  	{
   289  		return configID;
   290  	}
   291  	
   292  	/**
   293  	 *	Renvoie le nombre d'inscrits dans ce groupe.
   294  	 *
   295  	 *	@return Le nombre d'inscript.
   296  	 */
   297  	public function getUserCount ():Number
   298  	{
   299  		return userCount;
   300  	}
   301  	
   302  	/**
   303  	 *	Renvoie la limite d'utilisateurs simultanés dans un groupe.
   304  	 *
   305  	 *	@return Nombre maximum d'inscrits.
   306  	 */
   307  	public function getUserLimit ():Number
   308  	{
   309  		return userLimit;
   310  	}
   311  	
   312  	/**
   313  	 *	Renvoie si un verrou a été posé sur le groupe de telle faço
   314  	 *	qu'aucun nouveau utilisateur puissse s'inscrire.
   315  	 *
   316  	 *	@return true si le verrou est posé, sinon false.
   317  	 */
   318  	public function isClosed ():Boolean
   319  	{
   320  		return closed;
   321  	}
   322  	
   323  	/**
   324  	 *	Avertissement quant au seuil maximal de membres inscrits dans un groupe.
   325  	 *
   326  	 *	@return true si le seuil est atteint, sinon false.
   327  	 */
   328  	public function isFull ():Boolean
   329  	{
   330  		return (userCount >= userLimit);
   331  	}
   332  	
   333  	/**
   334  	 *	Renvoie les propriétés persistantes rattachées à un groupe.
   335  	 *
   336  	 *	@return Un objet PropertySet correspondant au groupe actuel.
   337  	 */
   338  	public function getProperties ():PropertySet
   339  	{
   340  		return props;
   341  	}
   342  	
   343  	/**
   344  	 *	Renvoie les propriétés persistantes en fonction dun nom d'un utilisateur inscript.
   345  	 *
   346  	 *	@return Un objet PropertySet définissant un membre du groupe.
   347  	 */
   348  	public function getUserProperties (username:String):PropertySet
   349  	{
   350  		return userProps[username];
   351  	}
   352  	
   353  	/**
   354  	 *	Renvoie une liste de toutes les propriétés persistantes 
   355  	 *	de chaque membre du groupe.
   356  	 *
   357  	 *	@return Une liste d'utilisateurs et leurs propriétés.
   358  	 */
   359  	public function getAllUserProperties ():Array
   360  	{
   361  		// Tous les membres
   362  		var up = userProps;
   363  		var l = new Array();
   364  		for (var each:String in up) {
   365  			l.push(up[each]);
   366  		}
   367  		
   368  		// Fonction de classement alphabétique
   369  		var func = function (a,b) {
   370  			if (a == b) return 0;
   371  			if (a.owner.toLowerCase() < b.owner.toLowerCase()) return -1;
   372  			return 1;
   373  		};
   374  		l.sort(func);
   375  		return l;
   376  	}
   377  	
   378  	/**
   379  	 *	Renvoie si un membre appartient à ce groupe.
   380  	 *
   381  	 *	@param username		Le nom d'utilisateur.
   382  	 *	@return true si l'utilisateur est membre du groupe.
   383  	 */
   384  	public function containsUser (username:String):Boolean
   385  	{
   386  		return (userProps[username] != undefined);
   387  	}
   388  	
   389  	/**
   390  	 *	Ouvre le groupe pour que s'y joignent d'autres utilisateurs.
   391  	 */
   392  	public function open ():Void
   393  	{
   394  		// TODO : Accès Singleton
   395  		var env = EnvelopeFactory.getInstance();
   396  		var session = Session.getInstance();
   397  		session.sendMessage(env.getOutgoing(new Message("open"), "group"));
   398  	}
   399  	
   400  	/**
   401  	 *	Cloture un groupe pour que personne ne puisse y pénétrer à nouveau.
   402  	 */
   403  	public function close ():Void
   404  	{
   405  		// TODO : Accès Singleton
   406  		var env = EnvelopeFactory.getInstance();
   407  		var session = Session.getInstance();
   408  
   409  		session.sendMessage(env.getOutgoing(new Message("close"), "group"));
   410  	}
   411  
   412  	/**
   413  	 *	Permet de quitter un groupe pour en rejoindre un nouveau.
   414  	 *	Cette méthode a double emploi :
   415  	 *	- Elle fait entrer un membre dans un groupe déjà enregistré.
   416  	 *	Auquel cas, il est possible de passer null en second argument.
   417  	 *	- Elle crée un nouveau groupe si l'identifiant de configID est valide,
   418  	 *	c'est à dire qu'il est contenu dans le nœud <group> du fichier config.xml
   419  	 *
   420  	 *	@param groupName		Nom du groupe à rejoindre ou à créer dynamiquement.
   421  	 *	@param configID			Identifiant du groupe de groupe, [null] si groupName existe déjà.
   422  	 *	@param syncProps		Le souhait de synchroniser les propriétés persistantes automatiquement, sans invoquer PropertySet.synchronize()
   423  	 *							Cela aura aussi effet de générer les événements PropertySet.onSynchronise() et Group.onChange().
   424  	 *							En cas d'échec, toutes les propriétés affectées sont supprimées.
   425  	 */
   426  	public function change (groupName:String, configID:String, syncProps:Boolean):Void
   427  	{
   428  		// TODO : Accès Singleton
   429  		var clazz = _Class.getInstance();
   430  		var argCheck = [
   431  						[groupName, "string", true],
   432  						[configID, "string", false],
   433  						[syncProps, "boolean", true]
   434  						];
   435  		if (! clazz.checkArguments("org.omus.group.change", argCheck)) return;
   436  		
   437  		// Mauvais groupe ?
   438  		if (name == groupName)
   439  		{
   440  			_global.oregano.iLog.error("clj-004","group name = " + groupName);
   441  			return;
   442  		}
   443  		// TODO : Accès Singleton
   444  		var session = Session.getInstance();
   445  		if (session.changeGroup)
   446  		{
   447  			// Logs internes
   448  			_global.oregano.iLog.error("clj-005","group name = " + groupName);
   449  			return;
   450  		}
   451  		
   452  		// Avertit le serveur
   453  		var msg = new Message("");
   454  		var attach = msg.getAttachment();
   455  		attach.groupName = groupName;
   456  		
   457  		// Gestion des nouveaux groupes
   458  		if (configID != null) attach.configID = configID;
   459  		
   460  		// Synchro des propriétés activées 
   461  		if (syncProps) 
   462  		{
   463  			// TODO : Accès Singleton
   464  			var user = User.getInstance();
   465  			attach.newProps = user.props.getChanged();
   466  		}else
   467  		{
   468  			attach.newProps = new Object();
   469  		}
   470  		cache.push({
   471  					args:arguments,
   472  					props:attach.newProps,
   473  					syncProps:syncProps
   474  					});
   475  		// Soumet le message au seveur
   476  		// TODO : Accès Singleton
   477  		var env = EnvelopeFactory.getInstance();
   478  		
   479  		session.sendMessage(env.getOutgoing(msg, "changeGroup"));
   480  		session.changeGroup = true;
   481  	}
   482  	
   483  	//*** Implémentation de iObservable ***\
   484  	/**
   485  	 *	Notifie les observateurs d'un événement.
   486  	 *
   487  	 *	@param logLevel Une chaine caractérisant le niveau de logging de l'information.
   488  	 *	@param eventName Nom de l'événement.
   489  	 *	@param arg1		[option] Autant de paramêtres de voulu.
   490  	 */
   491  	private function fireEvent (logLevel:String, eventName:String):Void
   492  	{
   493  		_eventSource.fireEvent.apply (_eventSource, arguments);
   494  	}
   495  	
   496  	/**
   497  	 *	Ajoute un nouvel observateur.
   498  	 * 
   499  	 *	@param		listener Référence de l'observateur.
   500  	 *	@return Un booléen indiquant la réussite de l'opération.
   501  	 */
   502  	public function addListener (listener:Object):Boolean
   503  	{
   504  		return _eventSource.addListener(listener);
   505  	}
   506  	
   507  	/**
   508  	 *	Supprime un observateur.
   509  	 *
   510  	 *	@param		listener Référence de l'observateur.
   511  	 *	@return Un booléen indiquant la réussite de l'opération.
   512  	 */
   513  	public function removeListener (listener:Object):Boolean
   514  	{
   515  		return _eventSource.removeListener(listener);
   516  	}
   517  	
   518  	/**
   519  	 *	Supprime tous les abonnés.
   520  	 */
   521  	public function removeAllListeners ():Void
   522  	{
   523  		_eventSource.removeAllListeners();
   524  	}
   525  	
   526  	/**
   527  	 *	Retourne le nombre d'observateurs.
   528  	 *
   529  	 *	@return Le nombre d'observateurs enregistrés.
   530  	 */
   531  	public function countListeners ():Number
   532  	{
   533  		return _eventSource.countListeners();
   534  	}
   535  	
   536  	
   537  	//*** Implémentation de iMessageHandler ***\\
   538  	/**
   539  	 *	Active la gestion d'un type de message par accusé de réception
   540  	 *	en fonction du contenu de son enveloppe.
   541  	 *
   542  	 * @param env		Une référence à l'enveloppe.
   543  	 */
   544  	public function handleMessage (env:Envelope):Void
   545  	{
   546  		_messageHandler.handleMessage(env);
   547  	}
   548  	
   549  	/**
   550  	 *	Rajoute un gestionnaire chargé d'intercepter 
   551  	 *	la réponse du serveur suite à un message soumis.
   552  	 *	Forme d'accusé de réception (callback).
   553  	 *
   554  	 *	@param		subject Le type de message.
   555  	 *	@param		methodName Le nom de l'événement gérant un type de message. 
   556  	 */
   557  	public function addHandler (subject:String, methodName:String):Void
   558  	{
   559  		_messageHandler.addHandler (subject, methodName);
   560  	}
   561  
   562  		
   563  	// Callbacks des messges Message avec accusés de réception
   564  	/**
   565  	 *	Gestionnaire de l'accusé de réception du message d'ouverture d'inscription dans le groupe.
   566  	 *	Génère un événement onOpen ou onError aux observateurs.
   567  	 *
   568  	 *	@param env		Enveloppe du message retourné.
   569  	 */
   570  	private function handleOpen (env:Envelope):Void
   571  	{
   572  		var errCode = env.getMessage().getAttachment().error;
   573  		if (errCode == "ok")
   574  		{
   575  			closed = false;
   576  			// Broadcast
   577  			fireEvent("info", "onOpen");
   578  		} else
   579  		{
   580  			// Broadcast
   581  			fireEvent("error", "onError", new _Error(errCode, "open", new Array()));
   582  		}
   583  	}
   584  	
   585  	/**
   586  	 *	Gestionnaire de l'accusé de réception du message de cloture d'un groupe.
   587  	 *	Génère un événement onClose ou onError aux observateurs.
   588  	 *
   589  	 *	@param env		Enveloppe du message retourné.
   590  	 */
   591  	private function handleClose (env:Envelope):Void
   592  	{
   593  		var errCode = env.getMessage().getAttachment().error;
   594  		if (errCode == "ok")
   595  		{
   596  			closed = true;
   597  			// Broadcast
   598  			fireEvent("info", "onClose");
   599  		} else
   600  		{
   601  			// Broadcast
   602  			fireEvent("error", "onError", new _Error(errCode,"close",new Array()));
   603  		}
   604  	}
   605  	
   606  	/**
   607  	 *	Gestionnaire de l'accusé de réception du mesage de changement de groupe.
   608  	 *	Génère un événement onChange ou onError aux observateurs.
   609  	 *
   610  	 *	@param env		Enveloppe du message retourné.
   611  	 */
   612  	private function handleChange (env:Envelope):Void
   613  	{
   614  		// TODO : Accès Singleton
   615  		var session = Session.getInstance();
   616  		session.handleChangeGroup();
   617  		
   618  		var at = env.getMessage().getAttachment();
   619  		var errCode = at.error;
   620  		var ch = cache.shift();
   621  		
   622  		if (errCode == "ok")
   623  		{
   624  			// Réinitialise l'appartenance au groupe
   625  			init(at.groupName, 
   626  				 at.configID, 
   627  				 at.grpID,
   628  				 at.userLimit,
   629  				 at.groupProps,
   630  				 at.extraPropConfig,
   631  				 at.userProps);
   632  			// TODO : Accès Singleton
   633  			var user = User.getInstance();
   634  			user.getLocks().reset();
   635  			// Synchronisation automatique des propriétés.
   636  			var propErr = at.syncProps.error;
   637  			if (propErr != "empty")
   638  			{
   639  				if (propErr == "ok")
   640  				{
   641  					user.getProperties().synchronizeOK(at.syncProps.newProps, true, ch.props.props);
   642  				}else
   643  				{
   644  					user.getProperties().synchronizeFailed(ch.props, propErr, "change");
   645  				}
   646  			}
   647  			// Broadcast
   648  			fireEvent("info", "onChange", at.extra);
   649  		} else
   650  		{
   651  			if (ch.syncProps)
   652  			{
   653  				// TODO : Accès Singleton
   654  				var user = User.getInstance();
   655  				user.getProperties().synchronizeFailed(ch.props.props, "prp-005", "change", ch.args);
   656  			}
   657  			// Broadcast
   658  			fireEvent("error", "onError", new _Error(errCode, "change", ch.args));
   659  		}
   660  		// Logs internes
   661  		_global.oregano.iLog.sendCache();
   662  		// Logs
   663  		Log.getInstance().sendCache();
   664  	}
   665  	
   666  	/**
   667  	 *	Gestionnaire de l'accusé de réception du message de membre nouvellement inscrit.
   668  	 *	Génère un événement onUserJoined aux observateurs.
   669  	 *
   670  	 *	@param env		Enveloppe du message retourné.
   671  	 */
   672  	private function handleUserJoined (env:Envelope):Void
   673  	{
   674  		var usr = env.getMessage().getAttachment();
   675  		addUserProps(usr);
   676  		//!! TODO : Vérifier le nombre de membres inscrits (cf ligne 716)
   677  		userCount++;
   678  		// Broadcast
   679  		fireEvent("info", "onUserJoined", userProps[usr.username]);
   680  	}
   681  	
   682  	/**
   683  	 *	Gestionnaire d'accusé de réeption du message de membre ayant quitté le groupe.
   684  	 *	Génère un événement onUserLeft aux observateurs.
   685  	 *
   686  	 *	@param env		Enveloppe du message retourné.
   687  	 */
   688  	private function handleUserLeft (env:Envelope):Void
   689  	{
   690  		var username = env.getMessage().getAttachment().username;
   691  		var props = userProps[username];
   692  		delete userProps[username];
   693  		userCount--;
   694  		// Broadcast
   695  		fireEvent("info", "onUserLeft", props);
   696  	}
   697  	// Fin des callbacks des messges Message avec accusés de réception
   698  
   699  	
   700  	//--------------------
   701  	// METHODES PRIVEES
   702  	//--------------------
   703  	/**
   704  	 *	Rajoute les propriétés persistantes d'un nouveau membre du groupe. 
   705  	 *	TODO : à vérifier.
   706  	 *	
   707  	 *	@param usr		Un utilisateur.
   708  	 */
   709  	private function addUserProps (usr:Object):Void
   710  	{
   711  		var username = usr.username;
   712  		var ps = new PropertySet("user", username, usr.usrID, null);
   713  		// TODO : Accès Singleton
   714  		var user = User.getInstance();
   715  		// Propriétés persistantes des utilisateurs
   716  		ps.fill(user.propConfig, usr.userProps);
   717  		userProps[username] = ps;
   718  		//!! TODO : Vérifier le nombre de membres inscrits (cf ligne 675)
   719  		//userCount++;
   720  	}
   721  	
   722  	//--------------------
   723  	// METHODES STATIQUES
   724  	//--------------------
   725  	/**
   726  	 *	Utilisé dans un contexte littéral
   727  	 *
   728  	 *	@return Une chaine définissant l'objet.
   729  	 */
   730  	public static function toLog():String
   731  	{
   732  		return "[Objet Group]";
   733  	}
   734  	
   735  	/**
   736  	 *	Accès global à la référence du Singleton
   737  	 *	@return	Une référence à la classe
   738  	 */
   739  	public static function getInstance(propConfig:Object):Group
   740  	{
   741  		if(_instance == undefined) _instance = new Group(propConfig);
   742  		return _instance;
   743  	}
   744  }
   745