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
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.
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
.
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.
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 interface IComponentGroupService {
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 interface ICompositeGroupService extends 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 interface IIndividualGroupService extends 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.
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 the period, or ".", but it can be any String
not found within the nodes of a group key. For example, if local
group keys include "Latin.101.Section01" and "chefs@columbia.edu", valid
separators would include "$", "%", and "@@", 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.
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
<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.
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$ -->
<!--
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");
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
and
caching_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>Note that the value of
<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>
<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>
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_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. 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
). 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.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. 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 process of deploying a composite group service involves (at least) the following steps:
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!
last revised: 03/31/2004, d.e.