Introduction
Portal Entities
The Groups Model
Using the Groups Service
Group and Entity Keys
Finding a Group Member by Key
Searching for Group Members by Name
Creating a New Group Member
Working With Group Members
The IGroupMember Interface
org.jasig.portal.security.IPerson
.
The key of an IPerson is usually something like "kweiner" or "ab123"
and is typically the user's handle in a central directory. An IBasicEntity
must be able to return an EntityIdentifier
which, in turn,
answers a key and a type. public interface IBasicEntity {
public EntityIdentifier getEntityIdentifier();
}
IBasicEntity
, a portal entity must have its
type added to org.jasig.portal.EntityTypes
. This is
accomplished by inserting a row into the UP_ENTITY_TYPE
table in the portal database, a reference table that maps the class name
of an IBasicEntity
to an integer. The EntityTypes
class has a convenience method to do this:// Add a new EntityType for Thing:
Class newType = Class.forName("org.jasig.portal.Thing");
String description = "An all-purpose object";
EntityTypes.addEntityType(newType, description);
IBasicEntities
,
so once an IBasicEntity
has been added to EntityTypes
,
it can be cached, locked and grouped. For more information on
caching and locking portal entities, see uPortal Concurrency Services.)
The org.jasig.portal.ChannelDefinition
and org.jasig.portal.security.IPerson
types
come pre-loaded in EntityTypes
. IBasicEntities
like IPersons or ChannelDefinitions, but manipulates stub objects whose
keys and types point to underlying
entities. The stubs are implementations of org.jasig.portal.groups.IEntity
,
and their only concern is their group memberships. The groups an IEntity
belongs to are implementations of org.jasig.portal.groups.IEntityGroup
.
Both IEntity
and IEntityGroup
are also IBasicEntities
,
and as a result, they can be cached and locked. IEntityGroups
are composites;
they can contain other IEntityGroups
as well as IEntities
.
This structure is represented by the following interfaces:IBasicEntity
IGroupMember extends IBasicEntity
IEntity extends IGroupMember
IEntityGroup extends
IGroupMember
IEntityGroups
are homogeneous;
an IEntityGroup's
IEntities
must have
underlying entities that are, implement or extend a single type.
An IEntityGroup
that contains IEntities
of
type of IPerson
can contain memberships for IPersons
,
subtypes of IPerson
, and other IPerson
groups
.
An IEntityGroup
that contains entities of type Object
can contain memberships for entities of any type, as long as their keys
do not collide. Clients of the groups service use its façade, org.jasig.portal.services.GroupService
to obtain an IGroupMember
, which acts as a starting point
in the groups system, much like a javax.naming.InitialContext
obtained from a jndi service acts as an entry point into a
directory. Once you have a reference to a new or pre-existing IGroupMember
,
you make subsequent requests to the group member itself, to navigate
the system, retrieve other group members, and update groups.
The groups service facade lets you get an IGroupMember
in 1 of 3 ways: by finding it
with a key, by searching for it
by name or by creating a new instance. Each of these techniques
has a different meaning for IEntities
and IEntityGroups
.
The groups service is itself a group, a group of group
services. Each individual service is a component, and the service as a
whole is a composite.
Most of the time -- except when you have to configure a composite
service or write a component -- the composite service and its facade
shield you from this knowledge. However you do need to be aware of
component services when you use a group key. The key is a compound key composed of a component
service name concatenated to the key of the group in the component
service, separated by a separator character, typically a period.
For example, the key of group "chem101" in the component service "ldap"
would be "ldap.chem101". The key of group "chem101" in the "local"
service would be "local.chem101".
An entity key is not compound. It has a single
node containing the key that all component group services must use to
identify the underlying entity. The assumption here is that in the
portal, an entity key like a userid will transcend an individual source,
service or application. The userid will be understood by ldap, ERP
systems, the portal database, etc. In many, perhaps most
installations, this will be the case. But suppose for a minute
that it isn't. Assume that a person, say Mick Jagger, is known in
ldap as "mjagger", in People Soft as "mj1" and in a third system as
"J.J.Flash". In this portal, the deployer would have to select one
system to be the base source of
person entities (perhaps ldap) and provide translations for the other
sources. If ldap were chosen, then a request to find groups
that contain "mjagger" would have to be understood by the People Soft
component as meaning "find groups that contain "mj1", and other systems
would have to perform their own translations. Regardless of
whether it is necessary to do these translations, a groups client should
see an entity as having a single, untranslated key.
In fact, an IEntity
has 2 keys, one
for its underlying entity and one for itself as an IBasicEntity
,
which allows it to be cached with other
IEntities
.
This duality is reflected in the IGroupMember
interface,
which has getters for the basic EntityIdentifier
(inherited
from IBasicEntity
) and the underlying EntityIdentifier
:
public EntityIdentifier
getUnderlyingEntityIdentifier();
public EntityIdentifier getEntityIdentifier();
In the case of an IEntityGroup
, the
underlying EntityIdentifier
will be the same as the basic EntityIdentifier
.
In the case of an IEntity
, it will be the EntityIdentifier
for the underlying entity, the IPerson
, ChannelDefinition
,
etc., as shown below:
IPerson person = new PersonImpl();
person.setID(1);
person.setAttribute(IPerson.USERNAME,"guest");
IGroupMember gm =
GroupSevice.getGroupMember(person.getEntityIdentifier());
EntityIdentifier basicEI =
gm.getEntityIdentifier();
EntityIdentifier underlyingEI = gm.getUnderlyingEntityIdentifier();
// Key and type used by clients to
get the IEntity:
System.out.println(underlyingEI.getKey()); // guest
System.out.println(underlyingEI.getType()); // interface
org.jasig.portal.security.IPerson
// Key and type used by groups system to cache the IEntity:
System.out.println(basicEI.getKey()); //
5.guest
System.out.println(basicEI.getType()); //
interface org.jasig.portal.Groups.IEntity
For more
information on keys in the group system, see the section on group and
entity keys in The
Composite Group Service Guide.
A key uniquely
identifies a group member, therefore an attempt to find one by key will
return at most 1 instance. The service attempts to find an IEntityGroup
by searching for an existing group. If it doesn't find the group,
it returns null:
// Find a group by key:
String key = "local.123";
IEntityGroup myGroup = GroupSevice.findGroup(key); // could be null
On the other hand, when you ask the group service to
get an IEntity
corresponding to a given key and type, the
service is not obligated to verify that the underlying entity actually
exists in some external system. The behavior of the local service, the default source of IEntities
is to return an IEntity
for any key so long as the type
exists in EntityTypes
. A non-null result does not
mean that the underlying entity actually exists or that it has
memberships.
// Get an entity by key:
String key = "dan";
Class personType = Class.forName("org.jasig.portal.security.IPerson");
IEntity myEntity = GroupService.getEntity(key, personType); // should
never be null
You can override this behavior by asking
for the
IEntity
from a non-default source by passing in the
name of the component group service, after you have reimplemented the
entity factory, (the IEntityStore
) for this service.
// Get an entity by key (could be null):
String service = "myService";
String key = "dan";
Class personType = Class.forName("org.jasig.portal.security.IPerson");
IEntity myEntity = GroupService.getEntity(key, personType,
service); // could be null
The service will use the appropriate
method if you call one of the
getGroupMember()
methods:
// Get a group member by key and type:
String key = "100";
Class channelDefType =
Class.forName("org.jasig.portal.ChannelDefinition");
IGroupMember chanGroupMember = GroupService.getGroupMember(key,
channelDefType);
or:
// Get a group member by EntityIdentifier:
String key = "local.101";
Class groupType = Class.forName("org.jasig.portal.groups.IEntityGroup");
EntityIdentifier eid = new EntityIdentifier(key, groupType);
IGroupMember chanGroupMember = GroupService.getGroupMember(eid);
There are 2 other kinds of groups that can be found by
key, albeit indirectly, via entries in portal.properties
.
A distinguished group has an
entry in portal.properties
that associates a name with a
group key. A root group
is a group that you can optionally designate as the group from which all
groups of the same entity type descend. The entry in portal.properties
for a root group associates a type name with a group key. This
designation is informal only and is not enforced by the group
system. It exists for the convenience of groups clients like the
Groups Manager channel that display the group system as a forest in
which all groups of the same type descend from a single tree.
// Find a distinguished group:
String distinguishedName = "administrators";
IEntityGroup administrators =
GroupService.findDistinguishedGroup(distinguishedName);
// Find a root group:
Class thingType = Class.forName("org.jasig.portal.Thing");
IEntityGroup rootOfAllThings = GroupService.findRootGroup(thingType);
There are
times when you do not know the key of the group member, but instead want
to search for it by name. A search can turn up 0 or more
instances, and the 4 search methods return an EntityIdentifier[]
,
each element of which can be turned into an IGroupMember
via GroupService.getGroupMember()
:
searchForEntities(String query, int method, Class
type)
searchForEntities(String query, int method, Class type, IEntityGroup
ancestor)
searchForGroups(String query, int method, Class leaftype)
searchForGroups(String query, int method, Class leaftype, IEntityGroup
ancestor)
Two of the search methods take 3 arguments, a search
String, (the name of the group member), a search method (see org.jasig.portal.groups.IGroupConstants
for a list of the search methods), and a Class (the entity type).
The other 2 methods take an additional argument, ancestor group, which confines the
search to (recursive) members of the group.
Each component group service is obligated to implement
the 4 search methods. In a search for groups, a component service
examines its group store and returns results that match the query.
The results are EntityIdentifiers
for groups that already
exist in the groups system. A search for entities, on the other
hand, may be conducted to locate an entity that is not yet represented
in the group system, for example, a new employee. Here, the search
is conducted on one or more stores that constitute entity sources for
the component service.
// search for IPersons whose names start with
"Khar"
String query = "Khar";
int method = IGroupConstants.STARTS_WITH;
Class type = Class.forName("org.jasig.portal.security.IPerson");
EntityIdentifier[] ents =
GroupService.searchForEntities(query,method,type);
Creating a
New Group MemberThere are 2
methods for creating a new group. In one, you designate the group
type and the component service name. In the other, you designate
only the type and the default service creates the group.
(The default service is designated in the composite group service
configuration, compositeGroupServices.xml
.)
IEntityGroup newGroup(Class type)
Once you have created a new group, you can set its name, add
members, make it a member of other groups, etc., but until you update
the group, it is not saved in the store.
IEntityGroup newGroup(Class type, String serviceName)
// Create a new IPerson group in the default
service
Class type = Class.forName("org.jasig.portal.security.IPerson");
IEntityGroup newGroup = GroupService.newGroup(type);
newGroup.setName("Test Group");
...
newGroup.update();
A
new IEntity
is created with the getEntity()
and getGroupMember()
methods. New instances of IEntity
are routinely created and destroyed, but they are not saved persistently
in the groups system. An IEntity
represents and
points to an underlying entity that may exist in some external
source. The only way to create the underlying entity is to
create it in the external source.
Working With Group Members
Once you get a group member from the service, you can use it to
retrieve related group members:
IGroupMember student =
GroupService.getEntity("student");
// Find groups that the entity belongs to:
Iterator studentGroups = student.getContainingGroups();
...
// (Recursively) find groups the entity belongs
to:
Iterator allStudentGroups = student.getAllContainingGroups();
...
// Find if an entity is a member of a group:
IGroupMember gradStudents = GroupService.findGroup("local.8");
if ( gradStudents != null && student.isMemberOf(gradStudents) )
{
...
}
IEntities
are not updatable. An IEntityGroup
may or may not be,
depending on whether its component group service implements update
methods and is declared to be updatable. (For information on
configuring component group services, see The
Composite Group Service Guide.) Changes to an IEntityGroup
are only committed to the store when the group is explicitly updated:
IEntityGroup faculty = GroupService.findGroup("local.2");
if ( faculty != null && faculty.hasMembers() )
{
// Find members of a group:
for (Iterator itr = faculty.getMembers();
itr.hasNext();)
{
IGroupMember facultyMember =
(IGroupMember) itr.next();
faculty.removeMember(facultyMember);
}
}
faculty.setDescription("Has no members");
faculty.update();
The Group Service also provides a lockable group, a group whose key
has been exclusively locked by the Entity Locking Service (see uPortal
Concurrency Services for locking service details.) Getting a
lockable group doesn't literally guarantee exclusive write access, it
guarantees that no other process can get a lockable group for the same
key, so as long as all group clients cooperate, lockable groups
work.
public static ILockableEntityGroup
findLockableGroup(String key, String lockOwner)
If the group is already locked, the group
service throws a
GroupsException
, so you must catch the
Exception and decide whether to abandon the attempt or perhaps wait and
try again. Lock management for the group is handled by the
component service, including checking that the lock is still valid and
releasing it after update.
String studentGroupKey = "local.1";
String owner = "dan";
ILockableEntityGroup leg = null;
try
{
leg =
GroupService.findLockableGroup(studentGroupKey, owner);
}
catch (GroupsException gre)
{
// group is already locked.
}
if ( leg == null )
{
// group could not be found.
}
else
{
leg.setDescription("Edited student group");
...
leg.update();
}
The complete IGroupMember
interface is
listed below.
IGroupMember
defines the common (component)
behavior for both its leaf (IEntity
)
and composite (IEntityGroup
)
sub-types. An IGroupMember
can answer both its parents and its children but has no api for adding
or removing them. These methods, along with
methods to update the persistent store, are defined on the group type
because you add a member to a group, not vice versa.
All
methods that maintain the groups structure or return IGroupMembers
throw GroupsExceptions
. These Exceptions are thrown for two reasons,
an attempt to violate the groups structure, for example by trying to add
a group with a duplicate name, or some error accessing the persistent
store. In the latter case, the GroupsException
wraps an Exception specific to the store, like a SQLException
or a NamingException
.
public boolean equals(Object o);
public Class getEntityType();
public String getKey();
public Class getLeafType();
public Class getType();
public EntityIdentifier getUnderlyingEntityIdentifier();
public int hashCode();
public boolean isEntity();
public boolean isGroup();
// shallow:
public boolean contains(IGroupMember gm) throws GroupsException;
public Iterator getContainingGroups() throws GroupsException;
public Iterator getEntities() throws GroupsException;
public IEntityGroup getMemberGroupNamed(String name) throws
GroupsException;
public Iterator getMembers() throws GroupsException;
public boolean isMemberOf(IGroupMember gm) throws
GroupsException;
public boolean hasMembers() throws GroupsException;
// deep:
public boolean deepContains(IGroupMember gm) throws
GroupsException;
public Iterator getAllContainingGroups() throws GroupsException;
public Iterator getAllEntities() throws GroupsException;
public Iterator getAllMembers() throws GroupsException;
public boolean isDeepMemberOf(IGroupMember gm) throws
GroupsException;
}
IEntity
is the leaf sub-type of IGroupMember
. It inherits
component and entity behavior from IGroupMember
.
At present this is just a marker interface.
public interface IEntity extends IGroupMember
{
// marker interface
}
public interface IEntityGroup extends IGroupMember
{
// getters and setters:
public String getCreatorID();
public String getDescription();
public String getLocalKey();
public String getName();
public Name getServiceName();
public void setCreatorID(String userID);
public void setDescription(String name);
public void setName(String name) throws GroupsException;
public void setLocalGroupService(IIndividualGroupService
groupService)
throws GroupsException;
// composite methods:
public void addMember(IGroupMember gm) throws GroupsException;
public void delete() throws GroupsException;
public boolean isEditable() throws GroupsException;
public void removeMember(IGroupMember gm);
public void update() throws GroupsException;
public void updateMembers() throws GroupsException;
}
ILockableEntityGroup
extends IEntityGroup
and defines a few methods that support
exclusive updates:public interface ILockableEntityGroup extends IEntityGroup {
public IEntityLock getLock();
public void setLock(IEntityLock lock);
public void updateAndRenewLock() throws GroupsException;
public void updateMembersAndRenewLock() throws GroupsException;
}