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 Messenger
    28   
    29  	@description :
    30  	
    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 : 04/02/05
    39  			- Portage en actionscript 2 pour le
    40  			compile time type checking
    41  			- Implémentation du modèle Singleton 
    42  			(abstraite auparavant)
    43  			- Accès méthodes aux classes Singleton
    44   -------------------------------------------
    45   */
    46  
    47  import org.omus.util.EventDispatcher;
    48  import org.omus.util.iObservable;
    49  import org.omus.util._Error;
    50  import org.omus.util._Class;
    51  
    52  import org.omus.msg.Message;
    53  import org.omus.msg.MessageFilter;
    54  import org.omus.msg.EnvelopeFactory;
    55  import org.omus.msg.Envelope;
    56  
    57  import org.omus.core.Session;
    58  import org.omus.core.Group;
    59  
    60  /**
    61   *	Cette classe gère l'envoi, la réception et le filtrage des messages.
    62   *	Contrairement à Mail, elle ne gère que les messages non persistants tels ceux d'un chat.
    63   *	Un mécasnisme de publication/abonnement est inclus pour la distribution des messages.
    64   *
    65   *	<p>
    66   *	Evénements auxquels s'abonner : 
    67   *	<ul>
    68   *	<li>onSubscribe				Généré lorsqu'un nouveau email est enregistré côté serevur.
    69   *	</li><li>onUnsubscribe			Généré lorsqu'un nouveau mot de passe est enregistré côté serveur.
    70   *	</li><li>onUnsubscribeAll			Généré lorsque de nouvelles permissions sont enregistrées côté serveur.
    71   *	</li><li>onAdminMessage
    72   *	</li><li>onMessage
    73   *	</li><li>onError(error:_Error)			Généré en cas d'erreur à cause d'une mauvaise modification de propriété persistante.
    74   *	</li></ul></p>
    75   *	Cette classe est implémentée en suivant le modèle Singleton.
    76   *	Un accès global à son instance est obtenu grâce à la méthode getInstance.
    77   *	Elle est aggrémentée par composition des méthodes des sources d'événements (EventDispatcher).
    78   *	@see org.omus.util.EventDispatcher
    79   *	@see org.omus.util.iObservable
    80   *
    81   *	@author Jens Halm copyright http://www.spicefactory.org/
    82   *	@author erixtekila copyleft http://www.v-i-a.net 
    83   *	@version 1.2.0
    84   */
    85  class org.omus.msg.Messenger implements iObservable
    86  {
    87  	//--------------------
    88  	// PROPRIETES
    89  	//--------------------
    90  	/**
    91  	 *	Liste des filtres.
    92  	 */
    93  	private var filters:Array;
    94  	
    95  	/**
    96  	 *	Liste des sujets avec abonnements.
    97  	 */
    98  	private var subscr:Array;
    99  	
   100  	/**
   101  	 *	Liste des gestionnaires d'accusé de réception.
   102  	 */
   103  	private var handlers:Object;
   104  	
   105  	/**
   106  	 *	Référence au système de génération d'événements.
   107  	 */
   108  	private var _eventSource:EventDispatcher;
   109  
   110  	/**
   111  	 *	Référence à l'unique instance de la classe permise.
   112  	 */
   113  	private static var _instance:Messenger;
   114  	
   115  	//--------------------
   116  	// CONSTRUCTEUR
   117  	//--------------------
   118  	/**
   119  	 *	L'objet Messenger dispose des méthodes d'EventDispatcher par composition.
   120  	 *	TODO : Implémenter MessageHandler ?
   121  	 */
   122  	private function Messenger()
   123  	{
   124  		// Composition avec EventDispatcher
   125  		_eventSource = new EventDispatcher();
   126  		
   127  		// Propriétés
   128  		filters = new Array();
   129  		subscr = new Array();
   130  		
   131  		// Gestionnaires
   132  		// Redirige les messages vers une méthode de gestion de l'accusé de réception ?
   133  		// TODO : Implémenter MessageHandler ?
   134  		var hd = new Object();
   135  		hd["msg.toServer"] = "handleError";
   136  		hd["customMsg"] = "handleCustomMsg";
   137  		hd["adminMsg"] = "handleAdminMsg";
   138  		hd["msg.subscr"] = "handleSubscribe";
   139  		hd["msg.unsubscr"] = "handleUnsubscribe";
   140  		hd["msg.unsubAll"] = "handleUnsubscribeAll";
   141  		handlers = hd;
   142  		
   143  		// trace(this+ " installé.");
   144  	}
   145  	
   146  	
   147  	//--------------------
   148  	// METHODES PUBLIQUES
   149  	//--------------------
   150  	/**
   151  	 *	Utilisé dans un contexte littéral
   152  	 *	@return	Une chaine définissant l'objet
   153  	 */
   154  	public function toString():String
   155  	{
   156  		return "[Objet Messenger]"; 
   157  	}
   158  	
   159  	/**
   160  	 *	N'envoie un message qu'à un seul utilisateur.
   161  	 *
   162  	 *	@param msg			Message à pfaire parvenir.
   163  	 *	@param username		Nom de l'utilisateur.
   164  	 *	@param sameGroup	true si l'utilisateur doit obligatoirement appartenir au même groupe pour recevoir le message.
   165  	 */
   166  	public function sendToUser (msg:Message, username:String, sameGroup:Boolean):Void
   167  	{
   168  		// TODO : Accès Singleton
   169  		var session = Session.getInstance();
   170  		var envFactory = EnvelopeFactory.getInstance();
   171  		var clazz = _Class.getInstance();
   172  		
   173  		var argCheck = [
   174  						[msg, Message, true],
   175  						[username, "string", true],
   176  						[sameGroup, "boolean", true]
   177  						];
   178  		if (! clazz.checkArguments("org.omus.messenger.sendToUser", argCheck)) return;
   179  		// Redirige le message en cas d'obligation d'appartenir au même groupe.
   180  		var type = (sameGroup) ? "grp.toUser" : "msg.toUser" ;
   181  		
   182  		session.sendMessage(envFactory.getOutgoing(msg, type, username));
   183  	}
   184  
   185  	/**
   186  	 *	Envoie un message à tous les membres d'un seul groupe.
   187  	 *
   188  	 *	@param msg			Le message à envoyer.
   189  	 *	@param groupName	Optionnel. Le groupe devant recevoir le message.
   190  	 *						Si le nom du groupe n'est pas précisé, le groupe actuel reçoit le message.
   191  	 */
   192  	public function sendToGroup (msg:Message, groupName:String):Void
   193  	{
   194  		// TODO : Accès Singleton
   195  		var session = Session.getInstance();
   196  		var envFactory = EnvelopeFactory.getInstance();
   197  		var clazz = _Class.getInstance();
   198  		var group = Group.getInstance();
   199  		
   200  		var argCheck = [
   201  						[msg, Message, true],
   202  						[groupName, "string", false]
   203  						];
   204  		if (! clazz.checkArguments("org.omus.messenger.sendToGroup", argCheck)) return;
   205  		// groupe différent ou actuel ?
   206  		var type = (groupName == undefined || groupName == group.getName()) ? "grp.toGroup" : "msg.toGroup" ;
   207  		
   208  		session.sendMessage(envFactory.getOutgoing(msg, type, groupName));
   209  	}
   210  	
   211  	/**
   212  	 *	Envoie un message à tous les utilisateurs loogés au serveur.
   213  	 *
   214  	 *	@param msg		Message à envoyer à tous.
   215  	 */
   216  	public function sendToAll (msg:Message):Void
   217  	{
   218  		// TODO : Accès Singleton
   219  		var session = Session.getInstance();
   220  		var envFactory = EnvelopeFactory.getInstance();
   221  		var clazz = _Class.getInstance();
   222  		
   223  		var argCheck = [[Message, true]];
   224  		
   225  		if (! clazz.checkArguments("org.omus.messenger.sendToAll", argCheck)) return;
   226  		
   227  		session.sendMessage(envFactory.getOutgoing(msg, "msg.toAll"));
   228  	}
   229  	
   230  	/**
   231  	 *	N'envoie un message qu'au serveur.
   232  	 *	Il est nécessaire de créer une extension pour recevoir ce message.
   233  	 *
   234  	 *	@param msg		Message à envoyer au serveur.
   235  	 */
   236  	public function sendToServer (msg:Message):Void
   237  	{
   238  		// TODO : Accès Singleton
   239  		var session = Session.getInstance();
   240  		var envFactory = EnvelopeFactory.getInstance();
   241  		var clazz = _Class.getInstance();
   242  		
   243  		var argCheck = [[Message, true]];
   244  		
   245  		if (! clazz.checkArguments("org.omus.messenger.sendToServer", argCheck)) return;
   246  		
   247  		session.sendMessage(envFactory.getOutgoing(msg, "msg.toServer"));
   248  	}
   249  	
   250  	/**
   251  	 *	Publie un message à tous les membres abonnés au sujet.
   252  	 *
   253  	 *	@param msg		Le message à envoyer.
   254  	 */
   255  	public function publish (msg:Message):Void
   256  	{
   257  		// TODO : Accès Singleton
   258  		var session = Session.getInstance();
   259  		var envFactory = EnvelopeFactory.getInstance();
   260  		var clazz = _Class.getInstance();
   261  		
   262  		var argCheck = [[Message, true]];
   263  		
   264  		if (! clazz.checkArguments("org.omus.messenger.publish", argCheck)) return;
   265  		
   266  		session.sendMessage(envFactory.getOutgoing(msg, "msg.publish"));
   267  	}
   268  	
   269  	/**
   270  	 *	S'abonne à tous les messages du sujet spécifié.
   271  	 *	Génère un événement onSubscribe aux abonnés.
   272  	 *
   273  	 *	@param subject		Sujet du message auquel s'abonner.
   274  	 */
   275  	public function subscribe (subject:String):Void
   276  	{
   277  		// TODO : Accès Singleton
   278  		var session = Session.getInstance();
   279  		var envFactory = EnvelopeFactory.getInstance();
   280  		var clazz = _Class.getInstance();
   281  		
   282  		if (! clazz.checkArguments("org.omus.messenger.subscribe", [[subject, "string", true]])) return;
   283  		if (isSubscribed(subject)) 
   284  		{
   285  			// Broadcast
   286  			fireEvent("info", "onSubscribe", subject);
   287  			return;
   288  		}
   289  		
   290  		// Envoie un message au serveur qui sera récupéré ensuite par gestionnaire d'accusé
   291  		session.sendMessage(envFactory.getOutgoing(new Message(subject), "msg.subscr"));
   292  	}
   293  	
   294  	/**
   295  	 *	Se désabonne à un sujet.
   296  	 *	Génère un événement onUnsubscribe aux abonnés.
   297  	 *
   298  	 *	@param subject		Sujet auquel se désabonner.
   299  	 */
   300  	public function unsubscribe (subject:String):Void
   301  	{
   302  		// TODO : Accès Singleton
   303  		var session = Session.getInstance();
   304  		var envFactory = EnvelopeFactory.getInstance();
   305  		var clazz = _Class.getInstance();
   306  		
   307  		if (! clazz.checkArguments("org.omus.messenger.unsubscribe", [[subject, "string", true]])) return;
   308  		if (! isSubscribed(subject))
   309  		{
   310  			fireEvent("info","onUnsubscribe",subject);
   311  			return;
   312  		}
   313  		
   314  		// Envoie un message au serveur qui sera récupéré par accusé de réception.
   315  		session.sendMessage(envFactory.getOutgoing(new Message(subject), "msg.unsubscr"));
   316  	}
   317  	
   318  	/**
   319  	 *	Se désabonne de tous les messages.
   320  	 *	Génère un événement onUnsubscribeAll aux abonnés.
   321  	 */
   322  	 public function unsubscribeAll () {
   323  		if (subscr.length == 0)
   324  		{
   325  			fireEvent("info", "onUnsubscribeAll");
   326  		} else {
   327  			// TODO : Accès Singleton
   328  			var session = Session.getInstance();
   329  			var envFactory = EnvelopeFactory.getInstance();
   330  			
   331  			// Message serveur
   332  			session.sendMessage(envFactory.getOutgoing(new Message(""), "msg.unsubAll"));
   333  		}
   334  	}
   335  	
   336  	/**
   337  	 *	Renvoie une liste de tous les sujets auquel le poste client s'est abonné.
   338  	 *
   339  	 *	@return		Une liste de tous les abonnements.
   340  	 */
   341  	public function getSubscriptions ():Array
   342  	{
   343  		return subscr.slice(0,this.subscr.length);
   344  	}
   345  	
   346  	/**
   347  	 *	Renvoie si cet utilisateur a souscrit au sujet d'un message.
   348  	 *
   349  	 *	@param subject		Le sujet souhaité.
   350  	 *	@return				true si le client est abonné à ce sujet.  
   351  	 */
   352  	public function isSubscribed (subject:String):Boolean
   353  	{
   354  		var l = this.subscr;
   355  		var n = l.length;
   356  		for (var i = 0; i < n; i++) 
   357  		{
   358  			if (l[i] == subject) return true;
   359  		}
   360  		return false;
   361  	}
   362  	
   363  	/**
   364  	 *	Rajoute un filtre pour ne recevoir qu'une partie des messages.
   365  	 *
   366  	 *	@param filter		Un nouveau filtre.
   367  	 */
   368  	public function addFilter (filter:MessageFilter):Void
   369  	{
   370  		// TODO : Accès Singleton
   371  		var clazz = _Class.getInstance();
   372  		if (! clazz.checkArguments("org.omus.messenger.addFilter", [[filter, MessageFilter, true]])) return;
   373  		// Doublon du filtre ?
   374  		if (indexOf(filter) == -1) filters.push(filter);
   375  	}
   376  	
   377  	/**
   378  	 *	Supprime un filtre.
   379  	 *
   380  	 *	@param filter		Le filtre à supprimer.
   381  	 */
   382  	public function removeFilter (filter:MessageFilter):Void
   383  	{
   384  		// TODO : Accès Singleton
   385  		var clazz = _Class.getInstance();
   386  		if (! clazz.checkArguments("org.omus.messenger.removeFilter", [[filter, MessageFilter, true]])) return;
   387  		var idx = indexOf(filter);
   388  		if (idx != -1) filters.splice(idx,1);
   389  	}	
   390  	
   391  	//*** Implémentation de iObservable ***\
   392  	/**
   393  	 *	Notifie les observateurs d'un événement.
   394  	 *
   395  	 *	@param logLevel Une chaine caractérisant le niveau de logging de l'information.
   396  	 *	@param eventName Nom de l'événement.
   397  	 *	@param arg1		[option] Autant de paramêtres de voulu.
   398  	 */
   399  	private function fireEvent (logLevel:String, eventName:String):Void
   400  	{
   401  		_eventSource.fireEvent.apply (_eventSource, arguments);
   402  	}
   403  	
   404  	/**
   405  	 *	Ajoute un nouvel observateur.
   406  	 * 
   407  	 *	@param		listener Référence de l'observateur.
   408  	 *	@return Un booléen indiquant la réussite de l'opération.
   409  	 */
   410  	public function addListener (listener:Object):Boolean
   411  	{
   412  		return _eventSource.addListener(listener);
   413  	}
   414  	
   415  	/**
   416  	 *	Supprime un observateur.
   417  	 *
   418  	 *	@param		listener Référence de l'observateur.
   419  	 *	@return Un booléen indiquant la réussite de l'opération.
   420  	 */
   421  	public function removeListener (listener:Object):Boolean
   422  	{
   423  		return _eventSource.removeListener(listener);
   424  	}
   425  	
   426  	/**
   427  	 *	Supprime tous les abonnés.
   428  	 */
   429  	public function removeAllListeners ():Void
   430  	{
   431  		_eventSource.removeAllListeners();
   432  	}
   433  	
   434  	/**
   435  	 *	Retourne le nombre d'observateurs.
   436  	 *
   437  	 *	@return Le nombre d'observateurs enregistrés.
   438  	 */
   439  	public function countListeners ():Number
   440  	{
   441  		return _eventSource.countListeners();
   442  	}
   443  
   444  	
   445  	// Gestionnaires d'accusé de réception des messages serveur.
   446  	/**
   447  	 *	Gestionnaire d'accusé de réception de tout les messages serveur.
   448  	 *	Dirige le message vers le bon gestionnaire.
   449  	 *	TODO : Par l'air d'être utilisé ?!
   450  	 *	Voir si ce n'est pas un oubli d'implémenation de iMessageHandler.
   451  	 *
   452  	 *	@param env		Enveloppe renvoyée par le serveur.
   453  	 */
   454  	public function handleMessage (env:Envelope):Void
   455  	{
   456  		var mName = handlers[env.getType()];
   457  		if (mName == undefined) {
   458  			// Logs internes
   459  			_global.oregano.iLog.error("clj-050", "envelope = " + env);
   460  			return;
   461  		}
   462  		// Active le gestionnaire.
   463  		this[mName](env);
   464  	}
   465  	
   466  	/**
   467  	 *	Gestionnaire d'accusé de réception de message d'erreur.
   468  	 *	Génère un événement onError aux abonnés.
   469  	 *
   470  	 *	@param env		Enveloppe retournée par le serveur.
   471  	 */
   472  	private function handleError (env:Envelope):Void
   473  	{
   474  		// Broadcast
   475  		//!! TODO : Syntaxe. env.getMessage() should be Array.
   476  		fireEvent("error", "onError", new _Error("msg-003", "sendToServer", [env.getMessage()]));
   477  	}
   478  
   479  	/**
   480  	 *	Gestionnaire d'accusé de réception de message Administrateur.
   481  	 *	Génère un événement onAdminMessage aux abonnés.
   482  	 *
   483  	 *	@param env		Enveloppe retournée par le serveur.
   484   	 */
   485  	private function handleAdminMsg (env:Envelope):Void
   486  	{
   487  		var text = env.getMessage().getAttachment().text;
   488  		// Broadcast un événement (non renseigné dans la documentation)
   489  		fireEvent("info", "onAdminMessage", text);
   490  	}
   491  
   492  	/**
   493  	 *	Gestionnaire d'accusé de réception de message avec ou sans filtrage.
   494  	 *	Génère un événement onMessage aux abonnés ou à ceux de messageFilter.
   495  	 *
   496  	 *	@param env		Enveloppe du message retourné.
   497  	 */
   498  	private function handleCustomMsg (env:Envelope):Void
   499  	{
   500  		var msg = env.getMessage();
   501  		var subj = msg.getSubject();
   502  		var fil = filters;
   503  		var len = fil.length;
   504  		var filtered = false;
   505  		// Applique le filtre sur le message 
   506  		for (var i = 0; i < len; i++)
   507  		{
   508  			var f = fil[i];
   509  			if (f.subjects[subj])
   510  			{
   511  				filtered = true;
   512  				// Broadcast
   513  				f.fireEvent("info","onMessage",msg);
   514  			} 
   515  		}
   516  		// Broadcast
   517  		if (! filtered) fireEvent("info", "onMessage", msg);
   518  		// Logs internes
   519  		if (_global.oregano.iLog.infoEnabled()) _global.oregano.iLog.logLocal("INFO", "clj-051", "subject = " + subj);
   520  	}
   521  	
   522  	/**
   523  	 *	Gestionnaire d'accusé de réception d'abonnement à un sujet.
   524  	 *	Génère un événement onSubscribe aux abonnés.
   525  	 *
   526  	 *	@param env		Enveloppe du message retourné.
   527  	 */
   528  	private function handleSubscribe (env:Envelope):Void
   529  	{
   530  		var subj = env.getMessage().getSubject();
   531  		subscr.push(subj);
   532  		// Broadcast
   533  		fireEvent("info", "onSubscribe", subj);
   534  	}
   535  	
   536  	/**
   537  	 *	Gestionnaire d'accusé de réception de désabonnement à un sujet.
   538  	 *	Génère un événement onUnsubscribe aux abonnés.
   539  	 *
   540  	 *	@param env		Enveloppe du message retourné.
   541  	 */
   542  	private function handleUnsubscribe (env:Envelope):Void
   543  	{
   544  		var subj = env.getMessage().getSubject();
   545  		// Supprime l'abonnement.
   546  		removeArrElem(subscr, subj);
   547  		// Broadcast
   548  		fireEvent("info", "onUnsubscribe", subj);
   549  	}
   550  	
   551  	/**
   552  	 *	Gestionnaire d'accusé de réception du désabonnement de tous les sujet.
   553  	 *	Génère un événement onSubscribeAll aux abonnés.
   554  	 *
   555  	 *	@param env		Enveloppe du message retourné.
   556  	 */
   557  	private function handleUnsubscribeAll (env:Envelope):Void
   558  	{
   559  		subscr = new Array();
   560  		fireEvent("info", "onUnsubscribeAll");
   561  	}
   562  	// Fin des gestionnaires d'accusé de réception.
   563  	
   564  	//--------------------
   565  	// METHODES PRIVEES
   566  	//--------------------
   567  	/**
   568  	 *	Supprime un élément d'une liste.
   569  	 *
   570  	 *	@param arr		La liste originale.
   571  	 *	@param elem		L'élément à enlever.
   572  	 */
   573  	private function removeArrElem (arr:Array, elem:String) {
   574  		for (var i = arr.length; i >= 0; i--) 
   575  		{
   576  			if (arr[i] == elem) 
   577  			{
   578  				arr.splice(i,1);
   579  				return;
   580  			}
   581  		}
   582  	}
   583  	
   584  	/**
   585  	 *	Renvoie l'index d'un élément de la liste des filtres.
   586  	 *
   587  	 *	@param filter		Le fitre à trouver.
   588  	 *	@return				L'index numérique de l'élément ou -1 s'il n'est pas référencé.
   589  	 */
   590  	private function indexOf (filter:MessageFilter):Number
   591  	{
   592  		var ls = filters;
   593  		var len = ls.length;
   594  		for (var i = 0; i < len; i++) 
   595  		{
   596  			if (ls[i] == filter) return i;
   597  		}
   598  		return -1;
   599  	}
   600  	
   601  	//--------------------
   602  	// METHODES STATIQUES
   603  	//--------------------
   604  	/**
   605  	 *	Utilisé dans un contexte littéral
   606  	 *
   607  	 *	@return Une chaine définissant l'objet.
   608  	 */
   609  	public static function toLog():String
   610  	{
   611  		return "[Objet Messenger]";
   612  	}
   613  	
   614  	/**
   615  	 *	Accès global à la référence du Singleton
   616  	 *	@return	Une référence à la classe
   617  	 */
   618  	public static function getInstance():Messenger
   619  	{
   620  		if(_instance == undefined) _instance = new Messenger();
   621  		return _instance;
   622  	}
   623  }
   624