Composite Group Service Guide

Developers and Deployers Guide to the Composite Group Service

Contents

Introduction
What's In This Document?
Goals and Rationale of the Composite Service
The Service Design
Groups, Keys and Service Names
Assembling the Composite
Configuring the Composite
Caching of Group Members
Alternate Group Stores
Next Steps

Introduction

Early in the uPortal project, it became clear that some mechanism was needed for grouping portal users, chiefly for the purpose of authorization.  The org.jasig.portal.groups package evolved in response.  It defines an api for managing groups of portal entities such as IPersons and ChannelDefinitions.  The groups framework does not actually operate on IPersons or ChannelDefinitions; it manipulates stub objects whose keys and types point to the underlying entities.  The stubs are implementations of org.jasig.portal.groups.IEntity, and their only concern is their group memberships.  A stub knows nothing about its underlying entity except its key and type.  The groups it belongs to are implementations of org.jasig.portal.groups.IEntityGroup.  Groups are recursive (groups can contain other groups) and homogeneous (their IEntities have only one type of underlying entity.)  

Prior to version 2.1, groups came from a group service with a single store.  As of version 2.1, uPortal ships with a composite group service made up of multiple component services, each with its own group store.  This document describes the composite design and the steps involved in configuring a composite group service.  It is principally aimed at implementors and developers, and to a lesser extent, at planners evaluating uPortal's support for native sources of group information.
 

What's In This Document?

This document is organized as follows:  Goals and Rationale of the Composite Service sets out the argument for a composite service.  The next section (The Service Design) describes the service api and the service class hierarchy.  This is followed by a discussion of group keys and their relationship to the composite design (Groups, Keys and Service Names) .  These first 3 sections are aimed at developers.  If you are interested only in deploying a composite service, you can skim them or skip them entirely.  The next 4 sections are aimed at deployers.  They describe the process of composite service assembly (Assembling the Composite), the configuration file that controls the assembly process (Configuring the Composite), configuration issues involving caching (Caching of Group Members) and the design of the 3 alternate group stores that ship with uPortal 2.3 (Alternate Groups Stores).  The last section (Next Steps) presents a very general outline for getting started with groups.   

Unless otherwise noted, all referenced classes are in the org.jasig.portal.groups package.  I'm assuming some familiarity with the basic groups types, IGroupMember, IEntity and IEntityGroup, and with the service façade, org.jasig.portal.services.GroupService, which have changed little since uPortal 2.0.  For more information on these types, see The Developers Guide to Groups or javadoc for org.jasig.portal.groups.  The following terms are used interchangeably: IEntityGroup and group; IGroupMember and group member; composite group service and service.  Depending on the context, entity may refer to an IEntity or to the underlying entity that is referred to by the IEntity. 

Goals and Rationale of the Composite Service

Many institutions have group information that is not under the control of the portal, for example, in an LDAP server.   In some organizations, this information is spread over a number of external sources.   In order to use it, the portal must be able to combine group data from multiple sources and adapt it to the portal groups design.  For example, if LDAP is one of the sources, the portal needs an LDAP adaptor that makes LDAP attributes look like portal group memberships.  These memberships, supplemented by memberships stored in the reference portal database, can be associated with Permissions.  In this way, portal authorization can be driven from LDAP and managed from within the portal.  

The composite groups system is a framework for creating and managing group adaptors.  Its job is to aggregate group information from a variety of sources and present it in a consistent form to clients who can remain unaware of its origins.  In fact, a group service client never interacts directly even with the composite group service.  A client makes a request to the service façade to obtain a group member, and the group member acts as an entry point into the composite group system.   Once the client has a reference to a new or pre-existing group member, it makes subsequent requests to the group member itself, and from then on it can ignore the service of origin of any group member it navigates to.  

The Service Design

The Service Hierarchy.  The composite group service api is divided among 3 service types, IComponentGroupService,ICompositeGroupService and IIndividualGroupService, each of which defines responsibilities for a specific service role.  An IComponentGroupService is a composite component.  It is concerned with composite service assembly and with identifying components in the composite.  The ICompositeGroupService represents the composition as a whole, encapsulating service components and delegating requests for group services.  The IIndividualGroupService defines responsibilities for a specific group service that reads and possibly writes groups.  It is the leaf component in the composite.  Together, they form the following class hierarchy:

IComponentGroupService
   ICompositeGroupService extends IComponentGroupService
     IIndividualGroupService extends ICompositeGroupService

IComponentGroupService.

An IComponentGroupService can get and set its name and, as a component in a composite, answer its component group services.  A component service can either contain other component services or be a leaf service and serve groups.  While it may seem unlikely that services with groups of IPersons, like LDAP or ERP-based services would be nested inside of other service components, services with groups of ChannelDefinitions actually might, particularly those representing groups of channels running on remote portals.   

public interfaceIComponentGroupService{
   public Map getComponentServices();
   public Name getServiceName();
   public boolean isLeafService();
   public void setServiceName(Name newServiceName);
 }

The reference implementation is a true composite only at service start-up, when each IComponentGroupService performs a recursive retrieval of its components.  Once the elements of this composite have been retrieved, the composite service keeps its components in a one-dimensional collection.  Since it does not contain nested group services, the reference composite group service does not have a direct implementation of IComponentGroupService but only implementations of its subtypes, ICompositeGroupService and IIndividualGroupService.  

ICompositeGroupService. 

An ICompositeGroupService represents the entire composition.  It is responsible for delegating requests to the appropriate component service(s) and for aggregating results.  Requests come to the composite from either the outside, (the service façade), or the inside, (a component service).   Some requests can be handled by a single group service, for example, a request to find a specific group (findGroup(),newGroup(), etc.)  Other requests may span some or all of the component services, for example, a request to find groups that contain a particular group member (findContainingGroups()) or a request to find groups whose names contain a particular String (searchForGroups()).  

public interfaceICompositeGroupServiceextends IComponentGroupService
 {
   public Iterator findContainingGroups(IGroupMember gm)
     throws GroupsException;
   public IEntityGroup findGroup(String key) throws GroupsException;
   public ILockableEntityGroup findGroupWithLock(String key, String owner)
     throws GroupsException;
   public IEntity getEntity(String key, Class type)
     throws GroupsException;
   public IGroupMember getGroupMember(String key, Class type)
     throws GroupsException;
   public IGroupMember getGroupMember(EntityIdentifier
     underlyingEntityIdentifier) throws GroupsException;
   public IEntityGroup newGroup(Class type, Name serviceName)
     throws GroupsException;
   public EntityIdentifier[] searchForEntities
     (String query, int method, Class type)
      throws GroupsException;
   public EntityIdentifier[] searchForEntities
     (String query, int method, Class type, IEntityGroup ancestor)
     throws GroupsException;
   public EntityIdentifier[] searchForGroups
     (String query, int method, Class leaftype)
     throws GroupsException;
   public EntityIdentifier[] searchForGroups
     (String query, int method, Class leaftype, IEntityGroup ancestor)
     throws GroupsException;
 }

IIndividualGroupService. 

The third type, IIndividualGroupService defines the methods that a specific or leaf group service uses to read and write groups.  IIndividualGroupService inherits find() methods from ICompositeGroupService, but whereas an ICompositeGroupService would probably delegate these requests, an IIndividualGroupService would most likely perform them itself.  In addition, an IIndividualGroupService must answer if one of its groups contains a particular member (contains()), if the service can be updated (isEditable()), and more specifically, if it is possible to edit a particular group (isEditable(IEntityGroup group)).  An attempt to update a group that is not editable should throw a GroupsException.  

public interfaceIIndividualGroupServiceextends ICompositeGroupService
 {
  public boolean contains(IEntityGroup group, IGroupMember member)
    throws GroupsException;
   public void deleteGroup(IEntityGroup group)
     throws GroupsException;
   public IEntityGroup findGroup(CompositeEntityIdentifier ent)
     throws GroupsException;
   public Iterator findMembers(IEntityGroup group)
     throws GroupsException;
   public boolean isEditable();
   public boolean isEditable(IEntityGroup group)
     throws GroupsException;
   public IEntityGroup newGroup(Class type)
     throws GroupsException;
   public void updateGroup(IEntityGroup group)
     throws GroupsException;
   public void updateGroupMembers(IEntityGroup group)
     throws GroupsException;
 }

The reference implementation, ReferenceIndividualGroupService, delegates most requests to one of three sub-components, an IEntityGroupStore, an IEntityStore and an IEntitySearcher.  It also may use the portal's Entity Locking and Entity Caching services.  

Groups, Keys and Service Names

Component Services and Their Names.  Once it has been assembled, the composite structure of the group service is reflected in the names given to component services, which are instances of javax.naming.Name with a node for each nested service.  Assuming a node separator of ".", a service named "columbia" contained by a service named "remoteChannels" would be named "remoteChannels.columbia".  Since a component cannot be expected to know in advance which components will contain it, the fully-qualified service name of a given component may not be known until the composite is fully assembled.  In the reference implementation, the service name is built up node by node as the composite is composed. 

Group Keys.  A group's composite service key is the concatenation of its fully-qualified service name and its key in the local service.  The nodes of the service name, and the final node of the name and the local key, are separated by a node separator.  For example, a group with a local key of "English_Department" in a service named "ldap" with a node separator of "!" would have a key of "ldap!English_Department". 

Node Separators.  The default node separator is a String containing a period, or ".", but it can be any String not found within the nodes of a group key.  However, group keys can find their way into portal content and urls and are subject to xsl transformations. Therefore, the separator should not contain a character like '%' that has a special meaning and must be escaped to preserve its literal value. If local group keys include "Latin.101.Section01" and "chefs@columbia.edu", valid separators would include "$", and "_x", but not "." or "@".  For instructions on changing the node separator see Configuring the Composite. 

Groups and their Service Names.  The significance of the service name in a group key is that it directs us to the specific service that can answer the request for the group and it further qualifies the local key, which may not be unique across different services.  Thus, a client wishing to find a group called "English_Department" in a component service named "ldap" needs to ask the composite service for "ldap.English101" rather than "sis.English101" or simply "English101".  The ldap service may know the group by its local key, but the client must know it by its fully-qualified, composite key.  

Conversely, for a service to support foreign entries, where an entry from one service participates in some way in another service, the foreign entry must keep a reference to its home service so that it can be retrieved and then navigate its service of origin.  As of version 2.1, IEntityGroup inherits from org.jasig.portal.IBasicEntity, therefore a group can already answer a key and a type, in the form of an org.jasig.portal.EntityIdentifier.  In the reference implementation, a group (an instance of EntityGroupImpl) answers a subclass of EntityIdentifier,  CompositeEntityIdentifier, whose key contains the fully-qualified service name in addition to the local service key.  

Entity Group Members and Their Keys.  While a group key is qualified by a service name, an entity key is not, and we make the assumption that an entity group member (an IEntity) is known across the composite service by a single key.  All component group services are obligated to know the entity by that key.  Thus, to get containing groups for IPerson "kweiner", the client would ask the service façade for an IGroupMember for IPerson "kweiner", not for "ldap.kweiner" or "local.kweiner", and then ask the group member for its containing groups.  The composite service would ask each of its components to get containing groups for group member IPerson kweiner, and each component service would be obligated to retrieve membership information for IPerson kweiner, rather than for IPerson ldap.kweiner, IPerson local.kweiner, etc.  If the source of entities for a component group service stores entity keys in a different format, e.g., "kw1" rather than "kweiner", the group service must translate these keys from their native format to the format understood by the other component services.   

Assembling the Composite

In the reference implementation, the composite service is an instance of org.jasig.portal.groups.ReferenceCompositeGroupService and is responsible for assembling the composite structure.  Each leaf component is an instance of ReferenceIndividualGroupService and is customized with an IEntityGroupStore, an IEntityStore and an IEntitySearcher.  A factory class for each of these types is specified in the configuration file.  

The configuration file. 
The composite configuration is stored in xml format in properties/groups/compositeGroupServices.xml.  The group service deployer edits this file to control the composition of the composite group service.  The root element of this document is a service list whose service elements describe group services that are top-level services, that is, not contained by another service.  In most installations, all services will be top-level services.  The configuration document is represented in Java as a GroupServiceConfiguration, essentially a parser with a Map of ComponentServiceDescriptors.  Each ComponentServiceDescriptor is itself a Map containing the elements and attributes of a single service element.  These elements are:

name

required

service_factory

required

entity_store_factory

required for reference implementation

group_store_factory

required for reference implementation

entity_searcher_factory

required for reference implementation

internally_managed

optional, defaults to false

caching_enabled

optional, defaults to false

Here is the service entry for the reference portal group service, which is named "local":

<service>
     <name>local</name>
     <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
     <entity_store_factory>org.jasig.portal.groups.ReferenceEntityStoreFactory</entity_store_factory>
     <group_store_factory>org.jasig.portal.groups.ReferenceEntityGroupStoreFactory</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>

Creating the component services. 
On service start-up, the composite service gets a GroupServiceConfiguration and asks it for its service descriptors.  The composite passes each description to the appropriate service factory and gets back a new service instance.  

If the component is an individual or leaf service, the factory creates an IIndividualGroupService, in the reference implementation, a ReferenceIndividualGroupService.  The service instance uses the descriptor to customize itself, for example, by getting its group store from the group store factory designated in the descriptor.  When the component has been initialized, the composite service adds the new service to its service Map.  

If the service is not a leaf but a component service, the composite service asks it for its component services, which starts a recursive retrieval of leaf services.  The composite service completes the naming of each leaf service by prepending the name of the top-level component to the service name, and then adds each leaf component to its service Map.  

At the end of the process, non-leaf components have been eliminated, and the composite service may have multiple instances of the same IIndividualGroupService implementation, each customized by its own service descriptor.  

Configuring the Composite

The configuration described in compositeGroupServices.xml is made available to group service classes via the utility class GroupServiceConfiguration.  This class exposes the servicelist attributes and service elements via:public Map getAttributes();
public List getServiceDescriptors()

uPortal 2.3 ships with the following configuration:

<?xml version="1.0"?>
<!-- $Revision: 1.15 $ -->
<!-- This list of component group services is processed by the composite, or "root" service as it -->
<!-- assembles itself.  Each service element has 2 required elements: name and service_factory.  -->
<!-- The values of all service elements are delivered to the service_factory. -->

<servicelist defaultService="local"
             compositeFactory="org.jasig.portal.groups.ReferenceCompositeGroupServiceFactory"
             nodeSeparator=".">
  <service>
    <name>local</name>
    <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
    <entity_store_factory>org.jasig.portal.groups.ReferenceEntityStoreFactory</entity_store_factory>
    <group_store_factory>org.jasig.portal.groups.ReferenceEntityGroupStoreFactory</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>

<!--  UNcomment to configure an LDAP group service component.  Be sure to edit LDAPGroupStoreConfig.xml 
with your values. -->
<!-- 
  <service>
    <name>ldap</name>
    <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
    <entity_store_factory>org.jasig.portal.groups.ldap.LDAPEntityStoreFactory</entity_store_factory>
    <group_store_factory>org.jasig.portal.groups.ldap.LDAPGroupStoreFactory</group_store_factory>
    <entity_searcher_factory>org.jasig.portal.groups.ldap.LDAPEntitySearcherFactory</entity_searcher_factory>
    <internally_managed>false</internally_managed>
    <caching_enabled>false</caching_enabled>
  </service>
-->

<!-- UNcomment to configure a file system group service component.  Change groupsRoot as appropriate.  -->
<!--
  <service groupsRoot="C:/groups">
    <name>filesystem</name>
    <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
    <entity_store_factory>org.jasig.portal.groups.filesystem.FileSystemGroupStoreFactory</entity_store_factory>
    <group_store_factory>org.jasig.portal.groups.filesystem.FileSystemGroupStoreFactory</group_store_factory>
    <entity_searcher_factory>org.jasig.portal.groups.filesystem.FileSystemEntitySearcherFactory</entity_searcher_factory>
    <internally_managed>false</internally_managed>
    <caching_enabled>false</caching_enabled>
  </service>
-->

<!--
  UNcomment to configure a Person Attributes group service component.  Configure your person attributes groups
  in PAGSGroupStoreConfig.xml. 
-->
<!--
  <service>
    <name>pags</name>
    <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
    <entity_store_factory>org.jasig.portal.groups.pags.PersonAttributesEntityStoreFactory</entity_store_factory>
    <group_store_factory>org.jasig.portal.groups.pags.PersonAttributesGroupStoreFactory</group_store_factory>
    <entity_searcher_factory>org.jasig.portal.groups.pags.PersonAttributesEntitySearcherFactory</entity_searcher_factory>
    <internally_managed>false</internally_managed>
    <caching_enabled>true</caching_enabled>
  </service>
-->

</servicelist

Note that the servicelist element has 3 attributes, defaultService, compositeFactory and nodeSeparator, and that 3 of the 4 service entries are commented out.  

Required servicelist Attributes.  The attributes defaultService and compositeFactory are both required.  

<servicelist defaultService="local"
  compositeFactory="org.jasig.portal.groups.ReferenceCompositeGroupServiceFactory"
 nodeSeparator=".">

The defaultService is the service that responds to requests for new group members when the request does not include a service name, e.g., GroupService.newGroup(Class type).  The entity factory in the default service supplies those IEntities that are entry points into the composite group service (group members not obtained from other group members.)  One way to substitute an alternate IEntity implementation for these entry points would be to change the default service.  (Another would be to change the entity_store_factory element in the default service.)  The compositeFactory attribute designates the class that creates the composite service instance.  You would change its value if you wanted to substitute your own composite service implementation (and still use the configuration file.) 

The nodeSeparator attribute is optional and defaults to ".".  For a description of its use and why you might want to change it, see Group Keys and Service Names.  Changing the nodeSeparator changes the format of the composite group key.  If you have any pre-existing data in the portal database when you change the nodeSeparator, you must also change the format of any hard-coded composite group keys, replacing the old nodeSeparator with the new.  In the reference portal database, composite group keys are found in the following tables:

UP_PERMISSION.PRINCIPAL_KEY
UP_GROUP_FRAGMENT.GROUP_KEY
UP_GROUP_MEMBERSHIP.MEMBER_SERVICE

They are also found in the following properties files:

portal.properties (distinguished and root groups)
chanpub/categories.properties
chanpub/groups.properties

Individual channels may also store composite group keys, so review your channels and their data stores. 

Additional servicelist Attributes.  The GroupServiceConfiguration stores all servicelist attributes.  If you wish to make additional composite service attributes available to one or more of your component services, you can add them to the configuration document and retrieve them via:

String myAttribute = (String)
   GroupServiceConfiguration.getAttributes().get("myAttribute");

The servicelist elements.  The elements of the servicelist describe top-level component services.  The default configuration contains a single service element named "local".  This is the default service whose group store is the reference portal database.  The service elements"ldap" and "filesystem" are commented out.  All 4 elements define leaf services.  The service named "local" has the following entry:

<service>
     <name>local</name>
     <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
     <entity_store_factory>org.jasig.portal.groups.ReferenceEntityStoreFactory</entity_store_factory>
     <group_store_factory>org.jasig.portal.groups.ReferenceEntityGroupStoreFactory</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>

Child Elements of the service element.  Within the service element, the name and service_factory child elements are required.  In addition, the reference implementation of IIndividualGroupService requires all child elements except internally_managed andcaching_enabled for a fully-functioning leaf service.  The elements are as follows:

service_factory designates the class name of the factory that creates the service implementation.  You only need to change this value if you are substituting your own implementation for the reference service implementation, ReferenceIndividualGroupService.  

name of the service is significant if you need to use a group from the service as a composite service entry point, since you find such a group using a key that contains both the native key and the service name, e.g.,

 String nativeGroupKey = "100";
 String serviceName = "local";
 String nodeSep = GroupServiceConfiguration.getConfiguration().getNodeSeparator();
 String groupKey = serviceName + nodeSep + nativeKey;  // "local.100"
 IGroupMember myGroup = GroupService.findGroup(groupKey);

Warning:  The service name is part of the member key in membership entries for member groups (groups that are members of other groups.)  If you change the name of a service, you must change the keys of all membership entries for member groups originating from that service.  

entity_store_factory is the factory class name for the entity store, the factory for IEntities.  Since the keys of IEntities do not contain service names, an entity store can be shared by multiple group services.  You would change this value only if you were substituting your own implementation of IEntityStore for the reference implementation.  

group_store_factory is the factory class name for the group store, the adaptor that connects the group service with the native source of groups information.  Although entity stores can be shared, each service will almost certainly have its own group store.  The "local" group store, RDBMEntityGroupStore, refers to tables in the reference portal database, contains sql statements and retrieves group information via jdbc.  The "ldap" group store, org.jasig.portal.groups.ldap.LDAPGroupStore, refers to an LDAP database and submits LDAP queries over ldap://.  The "filesystem" group store refers to filesystem files and directories and gets its group information via java.io. 

entity_searcher_factory is the factory class name for the entity searcher implementation, a class that returns EntityIdentifiers for potential group members.  It is likely that each group service will have its own entity searcher implementation (although some implementations may be no-ops).  The entity searcher for "local" returns EntityIdentifiers for ChannelDefinitions and IPersons from the reference portal database, while the entity searcher for "ldap" returns EntityIdentifiers for IPersons (but no ChannelDefs) from LDAP.  The entity searcher for "filesystem" is a no-op.  Entity searchers have proven necessary for group service clients like Groups Manager, which allow interactive updates to the groups system.  Users need to be able to find the members they are looking for by searching by attributes like last name, rather than by browsing through large numbers of entries. 

internally_managed contains a boolean value, either true or false.  If a service is internally-managed, it is under the control of the portal and presumed to be capable of writing as well as reading groups.  In the reference implementation, if internally_managed is true, the service will attempt to satisfy a request to add, update or delete a group.  If it is not internally-managed, an attempt to update a group will throw a GroupsException.  An alternate implementation of IIndividualGroupService could be more selective and decide if updates are allowed on a group-by-group basis.  

caching_enabled has a boolean value, either true or false, which controls whether the component service uses the portal's entity caching service to cache groups.  The value for the "local" service is set to true to eliminate excess database calls.  It is set to false for the other services because they do their own caching.  

Additional services.  Comment in one or more of the other group service declarations if you want to supplement the local groups with groups from other sources, (or create your own service):  

<service>
     <name>ldap</name>
     <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
     <entity_store_factory>org.jasig.portal.groups.ldap.LDAPEntityStoreFactory</entity_store_factory>
     <group_store_factory>org.jasig.portal.groups.ldap.LDAPGroupStoreFactory</group_store_factory>
     <entity_searcher_factory>org.jasig.portal.groups.ldap.LDAPEntitySearcherFactory</entity_searcher_factory>
     <internally_managed>false</internally_managed>
     <caching_enabled>false</caching_enabled>
   </service>

 &nbsp; <service groupsRoot=C:/groups>
    <name>filesystem</name>
    <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
    <entity_store_factory>org.jasig.portal.groups.filesystem.FileSystemGroupStoreFactory</entity_store_factory>
    <group_store_factory>org.jasig.portal.groups.filesystem.FileSystemGroupStoreFactory</group_store_factory>
    <entity_searcher_factory>org.jasig.portal.groups.filesystem.FileSystemEntitySearcherFactory</entity_searcher_factory>
    <internally_managed>false</internally_managed>
    <caching_enabled>false</caching_enabled>
   </service>

     <service>
    <name>pags</name>
    <service_factory>org.jasig.portal.groups.ReferenceIndividualGroupServiceFactory</service_factory>
    <entity_store_factory>org.jasig.portal.groups.pags.PersonAttributesEntityStoreFactory</entity_store_factory>
    <group_store_factory>org.jasig.portal.groups.pags.PersonAttributesGroupStoreFactory</group_store_factory>
    <entity_searcher_factory>org.jasig.portal.groups.pags.PersonAttributesEntitySearcherFactory</entity_searcher_factory>
    <internally_managed>false</internally_managed>
    <caching_enabled>true</caching_enabled>
  </service>

Note that the value of service_factory is the same for all of the service elements.  All of the services are instances of ReferenceIndividualGroupService, customized by their respective service configurations.  In the case of the "ldap", "pags" and "filesystem" services, the entries for entity_store_factory,group_store_factory and entity_searcher_factory all designate different factory classes but return a single group store instance that implements  IEntityStore, IEntityGroupStore and IEntitySearcher.  These services have internally_managed set to false since updates to them occur outside the transactional control of the portal and, in any event, their stores do not support updates.  

Caching of Group Members

Caching of Groups.  When caching is set on for a component service (caching_enabled=true), the composite group service uses the portal's Entity Caching Service to cache group members that come from the component service.  On update, a cached group is either removed from the cache or replaced, and cached copies of the group on other servers are invalidated.  This saves the component service from having to check if a group is up-to-date each time it is requested and is appropriate for a group service that manages group updates and is therefore in a position to know when they happen. 

On the other hand, an externally-managed group service (internally_managed=false) might not benefit at all from group caching by the caching service.  If the group source is dynamic, the service has to check for updates anyway, and if the source is static, the service doesn't have to check at all.  Either way, a simple cache maintained by the service itself is probably more efficient (caching_enabled=false). 

Membership Changes for Cached Entities.  Unless a service name is specified, the composite group service methods getGroupMember() and getEntity() delegate the creation of IEntities to the default component group service, typically the local group service.  Since this service is always internally-managed, IEntities are by default cached in the caching service.  This is a big optimization, but it can lead to a couple of apparent anomolies.  If an IEntity is added or removed from an internally-managed group, the IEntity's group memberships are updated in real time in the same portal JVM, and within a configurable interval in caches on linked portal JVMs.  But if the IEntity is added or removed from an externally-managed group, the group service will not detect the change directly, and cached copies of the IEntity will not be updated.  If the group is then re-retrieved, it will have the update, but the cached entities will not.  In the case of IEntities for IPersons (users), we remove the cache entry when the user's portal session is destroyed, but this still requires the user to log off and log on again to see the effect of a concurrent membership change in an external group source.

Key Caches.  Groups cache the keys of their members so they do not have to keep re-retrieving membership information.  These caches are initialized when a group is first asked for its members.  If a group has a large number of members, this process is expensive, so it is not performed unless getMembers() is actually called.  For example, if contains() is called on a group that has not yet been initialized, the group will delegate this method to its store to spare it the expense of initialization just to answer if it contains a single group member. 

Alternate Group Stores

The composite design anticipates that a group system will have multiple sources of groups.  The group information that ships with the basic uPortal distribution comes from the local group service, which uses RDBMEntityGroupStore as its store.  As of version 2.3, there are 3 alternate stores that come with uPortal, described at the links below.  If none of them meets your needs, consider writing a custom store. 

  • The LDAP Group Store converts LDAP attributes into group memberships.
  • The Person Attributes Group Store makes IPerson attributes retrieved by the PersonDirectory class appear to be group memberships. 
  • The Filesystem Group Store lets you create a group system from lists of user IDs in file system files. 

 

Next Steps

The process of deploying a composite group service involves (at least) the following steps:

  • analyze why you need group information
  • identify the necessary sources of group information
  • find or create adaptors for these sources
  • configure the composite service

Most portals will at least use groups to manage authorization, so this is a common starting point.  Many institutions will rely on an LDAP service as the primary source of group information and supplement it with information from the portal database.  Others may require additional information from human resources, student information or other systems.  uPortal currently ships with 4 adaptors, 1 for the reference portal database (RDBMEntityGroupStore) and 3 alternate stores (see above.)  If you only intend to use these 4 sources, you do not have to write a custom group store.  If you do need to draw groups from another source, you will have to implement the IEntityGroupStore and IEntitySearcher interfaces.  You should not have to write a custom group serivce (an IIndividualGroupService) unless you need to change the transactional rules of the service.  Of course, you can always re-implement or sublcass the reference implementations for reasons of efficiency, correctness or to add new functions.  Please contribute your code back to the project so that the entire uPortal community can benefit from your improvements.  

The final step is to represent your composite group service in the configuration file.  Describe each top-level service in a service element and designate the default service.  Unless you have a specific reason for changing it, keep the initial default value of "local".  If a custom service supports updates, set internally_managed to true. Note that you must implement the update methods for such a service in a custom group store class.  Of the 4 group store implementations that ship with uPortal, only the local store supports updates.  Caching of groups is a must in a production system.  Either use the portal Entity Caching Service or implement your own caching mechanism.    

Please post your questions, comments, suggestions and ideas to the JA-SIG Portal Discussion List.  Good luck!