Grouper:Integration Grouper uPortal implémentation
Un article de Wiki ESCO-Portail.
Sommaire |
Implémentation du gestionnaire de groupes uPortal pour Grouper
L'intégration des groupes issus de Grouper dans uPortal concerne deux points.
- Rendre accessibles par uPortal les groupes définis dans Grouper.
- Raccorder dynamiquement ces groupes à ceux de uPortal, pour pouvoir les utiliser via le canal de gestion de groupes (par exemple lors de l'attribution d'un canal à un groupe).
Visibilité des groupes définis dans Grouper
Cette opération se fait en ajoutant un nouveau gestionnaire de groupes dans le service composite de gestion des groupes du portail :
<service> <name>ESCO-GROUPER</name> <service_factory>org.esco.groups.ESCOReferenceIndividualGroupServiceFactory</service_factory> <entity_store_factory>org.esco.groups.EntityStoreFactory</entity_store_factory> <group_store_factory>org.esco.groups.EntityGroupStoreFactory</group_store_factory> <entity_searcher_factory>org.esco.groups.EntitySearcherFactory</entity_searcher_factory> <internally_managed>false</internally_managed> <caching_enabled>false</caching_enabled> </service>
Extrait du fichier properties /groups/compositeGroupServices.xml.
- La classe EntityStoreFactory est indépendante de Grouper. Son rôle est de créer des instances de la classe EntityStore. Ces instances sont elles-mêmes utilisées pour créer des objets de type EntityImpl.
- La classe EntityGroupStoreFactory créé des instances de EntityGroupStore :
Pour chacune de ces méthodes le GroupStore s'appuie sur le web service exposant l'API de Grouper pour retrouver les groupes définis dans Grouper. Le groupes retournés sont du type ESCOEntityGroupImpl :

Cette implementation de IEntityGroup prend en compte l'aspect dynamique de définition des groupes et des appartenances. Elle utilise malgré tout un cache (un simple ArrayList) avec une durée de validité très courte (propriété grouper.requests.cache.duration dans le fichier esco-GroupLoad.xml). L'objectif est de faire en sorte que l'accès à un groupe se fasse en une seule requête au web service.
- La classe EntitySearcherFactory crée des instances de EntitySearcher :
Cette classe utilise également le web service d'accès à Grouper pour réaliser la recherche d'entités.
Raccordement des groupes définis dans Grouper aux groupes de uPortal
Ajout des groupes issus de Grouper en tant que sous-groupes uPortal
L'idée est d'associer à un groupe uPortal un ou plusieurs groupes ou espaces de noms définis dans Grouper. Cette association se fait via le fichier de properties esco-GroupLoad.xml :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <entry key="grouper.requests.cache.duration">25</entry> <entry key="Etablissements">esco:ENT_Groupes:etb1; esco:ENT_Groupes:etb2</entry> </properties>
- Dans cet exemple, on raccorde au groupe uPortal dont le nom est Etablissement les 2 espaces de noms esco:ENT_Groupes:etb1 et esco:ENT_Groupes:etb2 définis dans Grouper.
- La propriété grouper.requests.cache.duration permet de spécifier une durée de validité des requêtes en secondes.
Pour réaliser effectivement cette association groupe uPortal <=> espaces de noms/groupes Grouper, une version modifiée du gestionnaire de groupe local uPortal doit être utilisée :
<service> <name>local</name> <service_factory>org.esco.groups.ESCOReferenceIndividualGroupServiceFactory</service_factory> <entity_store_factory>org.jasig.portal.groups.ReferenceEntityStoreFactory</entity_store_factory> <group_store_factory>org.esco.groups.ESCOReferenceEntityGroupStoreFactory</group_store_factory> <entity_searcher_factory>org.jasig.portal.groups.ReferenceEntitySearcherFactory</entity_searcher_factory> <internally_managed>true</internally_managed> <caching_enabled>true</caching_enabled> </service>
Extrait du fichier properties /groups/compositeGroupServices.xml
La modification essentielle est l'utilisation de la classe ESCOReferenceEntityGroupStoreFactory qui crée des instances de ESCORDBMEntityGroupStore :
Ce GroupStore contient le mapping permettant de déterminer quels groupes uPortal doivent être associés à des groupes espaces de noms définis dans Grouper. Lorsqu'il retourne un groupe uPortal, celui-ci est "décoré" en retournant un objet de type ESCODynEntityGroupDecorator :
Cette décoration a pour objectif de fusionner, si besoin, les groupes définis dans Grouper aux sous-groupes natifs uPortal. La methode getMemberGroups permet de réaliser cette fusion :
protected Iterator getMemberGroups() throws GroupsException { if (!hasGrouperGroups()) { return super.getMemberGroups(); } return new MergedIterators(super.getMemberGroups(), getGrouperMemberGroups().iterator()); }
Les autres méthodes s'appuient dessus pour prendre en compte les groupes issus de Grouper :
protected java.util.Set primGetAllMembers(final Set s) throws GroupsException { final Iterator groups = getMemberGroups(); final Iterator uPortalEntities = getMemberEntities(); while (groups.hasNext()) { final IEntityGroup group = (IEntityGroup) groups.next(); s.add(group); final Iterator members = group.getAllMembers(); while (members.hasNext()) { s.add(members.next()); } } while (uPortalEntities.hasNext()) { s.add(uPortalEntities.next()); } return s; }
Cohérence des informations d'appartenance pour les groupes de Grouper
Dans uPortal les groupes issus de Grouper et raccordés à un groupe uPortal doivent conserver cette information d'appartenance. Cela est nécessaire par exemple pour le calcul des autorisations qui s'appuie sur la méthode :
public Iterator<IEntityGroup> getContainingGroups() throws GroupsException
Cela se fait au niveau de la fabrique ESCOEntityGroupFactory qui ajoute si nécessaire les groupes uPortal à intégrer systématiquement aux appartenances :
public IEntityGroup createEntityGroup(final GrouperDTO groupInfo) { final ESCOEntityGroupImpl group = new ESCOEntityGroupImpl(groupInfo); // Loads the uPortal groups if needed. if (groupInfo.isRoot()) { final String parentStem = groupInfo.getParentStem(); for (String stem : uPortalGroupByStem.keySet()) { if (parentStem.equals(stem)) { IEntityGroup uPortalGroup = uPortalGroupByStem.get(stem); group.loadDinamicallyIntoUportalGroup(uPortalGroup); } } } return group; }
Une instance ainsi créée retournera comme information d'appartenance les groupes issus de grouper associés aux groupes uPortal ajoutés par la fabrique.
Modification du canal groupsmanager
Deux classes ont été modifiées pour répercuter dynamiquement les modifications de groupes effectuées dans Grouper.
- Classe org.jasig.portal.channels.groupsmanager.GroupManagerXML :
public static void expandGroupElementXML(Element expandedElem, CGroupsManagerUnrestrictedSessionData sd){ //Utility.printElement(expandElem,"Group to be expanded was found (not null): \n" ); boolean hasMembers = (expandedElem.getAttribute("hasMembers").equals("true")); Utility.logMessage("DEBUG", "ExpandGroup::execute(): Expanded element has Members = " + hasMembers); // ========= Modification ESCO ============== // Used to handle Grouper groups. // if (hasMembers) { // ========================================= expandedElem.setAttribute("expanded", "true"); Utility.logMessage("DEBUG", "ExpandGroup::execute(): About to retrieve children"); // Have to check for non persistent search element before doing retrieval IGroupMember entGrp = (!isPersistentGroup(expandedElem) ? null : (IGroupMember)retrieveGroup(expandedElem.getAttribute("key"))); GroupsManagerXML.getGroupMemberXml(entGrp, true, expandedElem, sd); //Utility.printDoc(xmlDoc, "renderXML: +++++++++ After children are retrieved +++++++++"); // } // Modification ESCO. }
- Classe org.jasig.portal.channels.groupsmanager.wrappers.GroupWrapper.java :
public Element getXml (IGroupMember gm, Element anElem, CGroupsManagerUnrestrictedSessionData sessionData) { Document aDoc = sessionData.model; String nextID; IEntityGroup entGrp = (IEntityGroup)gm; Element rootElem = (anElem != null ? anElem : GroupsManagerXML.createElement(ELEMENT_TAGNAME, aDoc, false)); Utility.logMessage("DEBUG", "GroupWrapper::getXml(): START, Element Expanded: " + rootElem.getAttribute("expanded")); try { String uid = rootElem.getAttribute("id"); if (Utility.areEqual(uid, "")) { nextID = GroupsManagerXML.getNextUid(); rootElem.setAttribute("id", nextID); } rootElem.setAttribute("key", gm.getKey()); rootElem.setAttribute("entityType",gm.getLeafType().getName()); rootElem.setAttribute("type", gm.getType().getName()); rootElem.setAttribute("editable", String.valueOf(entGrp.isEditable())); boolean hasMems = gm.hasMembers(); if (!hasMems) { rootElem.setAttribute("expanded", "false"); } boolean isGroupExpanded = (Boolean.valueOf(rootElem.getAttribute("expanded")).booleanValue()); if (!Utility.areEqual(rootElem.getAttribute("selected"), "true")) { rootElem.setAttribute("selected", "false"); } rootElem.setAttribute("hasMembers", String.valueOf(hasMems)); // set user permissions for group IGroupsManagerPermissions gmp = sessionData.gmPermissions; IAuthorizationPrincipal ap = sessionData.authPrincipal; applyPermissions (rootElem, gm, gmp, ap); // If no rdf element, create it, otherwise refresh the element NodeList nList = rootElem.getElementsByTagName("rdf:RDF"); if (nList.getLength() == 0) { Element rdf = GroupsManagerXML.createRdfElement(entGrp, aDoc); rootElem.appendChild(rdf); } else{ GroupsManagerXML.refreshAllNodesIfRequired(sessionData, rootElem); } if (isGroupExpanded) { expandElement(gm, rootElem, sessionData); } // ====== ESCO Modification ====== // Used to handle grouper groups //================================ NodeList children = rootElem.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Element child = (Element) children.item(i); if (Utility.notEmpty(child.getAttribute("type"))) { IGroupMember aChildGm = GroupsManagerXML.retrieveGroupMemberForElement(child); if (aChildGm == null || !gm.contains(aChildGm)) { rootElem.removeChild(child); } } } // ====== End of ESCO Modification. Utility.logMessage("DEBUG", "GroupWrapper::getXml(): FINISHED"); } catch (Exception e) { Utility.logMessage("ERROR", "GroupWrapper::getXml(): ERROR retrieving entity " + e, e); } return rootElem; }





