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