/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the License at the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apereo.portal.groups;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.concurrency.CachingException;
import org.apereo.portal.concurrency.IEntityLock;
import org.apereo.portal.concurrency.LockingException;
import org.apereo.portal.services.EntityCachingService;
import org.apereo.portal.services.EntityLockService;
import org.apereo.portal.services.GroupService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Reference individual, or leaf, group service.
*/
public class ReferenceIndividualGroupService extends ReferenceCompositeGroupService
implements IIndividualGroupService, ILockableGroupService {
private static final Logger log =
LoggerFactory.getLogger(ReferenceIndividualGroupService.class);
// Describes the attributes of this service. See compositeGroupServices.xml.
protected ComponentGroupServiceDescriptor serviceDescriptor;
protected IEntityGroupStore groupFactory;
// Entity searcher
protected IEntitySearcher entitySearcher;
/** ReferenceGroupsService constructor. */
public ReferenceIndividualGroupService() throws GroupsException {
this(new ComponentGroupServiceDescriptor());
}
/** ReferenceGroupsService constructor. */
public ReferenceIndividualGroupService(ComponentGroupServiceDescriptor svcDescriptor)
throws GroupsException {
super();
serviceDescriptor = svcDescriptor;
initialize();
}
/** Answers if <code>IGroupMembers</code> are being cached. */
protected boolean cacheInUse() {
return getServiceDescriptor().isCachingEnabled();
}
/**
* Removes the <code>IEntityGroup</code> from the cache and the store.
*
* @param group IEntityGroup
*/
public void deleteGroup(IEntityGroup group) throws GroupsException {
throwExceptionIfNotInternallyManaged();
synchronizeGroupMembersOnDelete(group);
getGroupStore().delete(group);
if (cacheInUse()) {
cacheRemove(group);
}
}
/**
* Removes the <code>ILockableEntityGroup</code> from its containing groups. The <code>finally
* </code> block tries to release any groups that are still locked, which can occur if an
* attempt to remove the group from one of its containing groups fails and throws a
* GroupsException. In this event, we do not try to roll back any successful removes, since that
* would probably fail anyway.
*
* @param group ILockableEntityGroup
*/
private void removeDeletedGroupFromParentGroups(ILockableEntityGroup group)
throws GroupsException {
// IEntityLock lock = null;
List<ILockableEntityGroup> lockableGroups = new ArrayList<>();
try {
String lockOwner = group.getLock().getLockOwner();
for (IEntityGroup containingGroup : group.getParentGroups()) {
ILockableEntityGroup lockableGroup =
GroupService.findLockableGroup(containingGroup.getKey(), lockOwner);
if (lockableGroup != null) {
lockableGroups.add(lockableGroup);
}
}
for (ILockableEntityGroup lockableGroup : lockableGroups) {
lockableGroup.removeChild(group);
lockableGroup.updateMembers();
}
} catch (GroupsException ge) {
throw new GroupsException(
"Could not remove deleted group " + group.getKey() + " from parent", ge);
} finally {
for (ILockableEntityGroup lockableGroup : lockableGroups) {
IEntityLock lock = lockableGroup.getLock();
try {
if (lock.isValid()) {
lock.release();
}
} catch (LockingException le) {
log.error(
"ReferenceIndividualGroupService.removeDeletedGroupFromParentGroups(): "
+ "Problem unlocking parent group",
le);
}
}
}
}
/**
* Removes the <code>ILockableEntityGroup</code> from the cache and the store, including both
* parent and child memberships.
*
* @param group ILockableEntityGroup
*/
public void deleteGroup(ILockableEntityGroup group) throws GroupsException {
throwExceptionIfNotInternallyManaged();
try {
if (group.getLock().isValid()) {
removeDeletedGroupFromParentGroups(group);
deleteGroup((IEntityGroup) group);
} else {
throw new GroupsException(
"Could not delete group " + group.getKey() + " has invalid lock.");
}
} catch (LockingException le) {
throw new GroupsException("Could not delete group " + group.getKey(), le);
} finally {
try {
group.getLock().release();
} catch (LockingException le) {
}
}
}
private EntityIdentifier[] filterEntities(EntityIdentifier[] entities, IEntityGroup ancestor)
throws GroupsException {
ArrayList ar = new ArrayList(entities.length);
for (int i = 0; i < entities.length; i++) {
IGroupMember gm = this.getGroupMember(entities[i]);
if (ancestor.deepContains(gm)) {
ar.add(entities[i]);
}
}
return (EntityIdentifier[]) ar.toArray(new EntityIdentifier[0]);
}
/**
* Returns and caches the containing groups for the <code>IGroupMember</code>
*
* @param gm IGroupMember
*/
@Override
public Iterator findParentGroups(IGroupMember gm) throws GroupsException {
log.debug("Finding containing groups for member {}", gm.getKey());
Collection groups = new ArrayList(10);
IEntityGroup group = null;
for (Iterator it = getGroupStore().findParentGroups(gm); it.hasNext(); ) {
group = (IEntityGroup) it.next();
group.setLocalGroupService(this);
groups.add(group);
if (cacheInUse()) {
try {
if (getGroupFromCache(group.getEntityIdentifier().getKey()) == null) {
cacheAdd(group);
}
} catch (CachingException ce) {
throw new GroupsException("Problem finding containing groups", ce);
}
}
}
return groups.iterator();
}
/** Returns a pre-existing <code>IEntityGroup</code> or null if it does not exist. */
public IEntityGroup findGroup(String key) throws GroupsException {
return findGroup(newCompositeEntityIdentifier(key));
}
/** Returns a pre-existing <code>IEntityGroup</code> or null if it does not exist. */
public IEntityGroup findGroup(CompositeEntityIdentifier ent) throws GroupsException {
return (cacheInUse()) ? findGroupWithCache(ent) : primFindGroup(ent.getLocalKey());
}
/** Returns a pre-existing <code>IEntityGroup</code> or null if it does not exist. */
protected IEntityGroup findGroupWithCache(CompositeEntityIdentifier ent)
throws GroupsException {
try {
IEntityGroup group = getGroupFromCache(ent.getKey());
if (group == null) {
group = primFindGroup(ent.getLocalKey());
if (group != null) {
cacheAdd(group);
}
}
return group;
} catch (CachingException ce) {
throw new GroupsException("Problem retrieving group " + ent.getKey(), ce);
}
}
/**
* Returns a pre-existing <code>ILockableEntityGroup</code> or null if the group is not found.
*/
public ILockableEntityGroup findGroupWithLock(String key, String owner) throws GroupsException {
return findGroupWithLock(key, owner, 0);
}
/**
* Returns a pre-existing <code>ILockableEntityGroup</code> or null if the group is not found.
*/
public ILockableEntityGroup findGroupWithLock(String key, String owner, int secs)
throws GroupsException {
throwExceptionIfNotInternallyManaged();
Class groupType = ICompositeGroupService.GROUP_ENTITY_TYPE;
try {
IEntityLock lock =
(secs == 0)
? EntityLockService.instance().newWriteLock(groupType, key, owner)
: EntityLockService.instance()
.newWriteLock(groupType, key, owner, secs);
ILockableEntityGroup group = groupFactory.findLockable(key);
if (group == null) {
lock.release();
} else {
group.setLock(lock);
group.setLocalGroupService(this);
}
return group;
} catch (LockingException le) {
throw new GroupsException("Problem getting lock for group " + key, le);
}
}
/**
* Returns and caches the member groups for the <code>IEntityGroup</code>
*
* @param eg IEntityGroup
*/
protected Iterator findLocalMemberGroups(IEntityGroup eg) throws GroupsException {
Collection groups = new ArrayList(10);
IEntityGroup group = null;
for (Iterator it = getGroupStore().findMemberGroups(eg); it.hasNext(); ) {
group = (IEntityGroup) it.next();
if (group == null) {
log.warn(
"A null IEntityGroup object was part of a list groupStore.findMemberGroups");
continue;
}
group.setLocalGroupService(this);
groups.add(group);
if (cacheInUse()) {
try {
if (getGroupFromCache(group.getEntityIdentifier().getKey()) == null) {
cacheAdd(group);
}
} catch (CachingException ce) {
throw new GroupsException("Problem finding member groups", ce);
}
}
}
return groups.iterator();
}
/** Finds the <code>IEntities</code> that are members of <code>group</code>. */
public Iterator findMemberEntities(IEntityGroup group) throws GroupsException {
return getGroupStore().findEntitiesForGroup(group);
}
/**
* Returns member groups for the <code>IEntityGroup</code>. First get the member groups that are
* local to this service. Then retrieve the keys of all of the member groups and ask the
* GroupService to find the groups we do not yet have.
*
* @param eg IEntityGroup
*/
public Iterator findMemberGroups(IEntityGroup eg) throws GroupsException {
Map groups = new HashMap();
IEntityGroup group = null;
for (Iterator itr = findLocalMemberGroups(eg); itr.hasNext(); ) {
group = (IEntityGroup) itr.next();
groups.put(group.getKey(), group);
}
String[] memberGroupKeys = getGroupStore().findMemberGroupKeys(eg);
for (int i = 0; i < memberGroupKeys.length; i++) {
if (!groups.containsKey(memberGroupKeys[i])) {
group = GroupService.findGroup(memberGroupKeys[i]);
if (group != null) {
groups.put(group.getKey(), group);
}
}
}
return groups.values().iterator();
}
/**
* Returns and members for the <code>IEntityGroup</code>.
*
* @param eg IEntityGroup
*/
public Iterator findMembers(IEntityGroup eg) throws GroupsException {
Collection members = new ArrayList(10);
Iterator it = null;
for (it = findMemberGroups(eg); it.hasNext(); ) {
members.add(it.next());
}
for (it = findMemberEntities(eg); it.hasNext(); ) {
members.add(it.next());
}
return members.iterator();
}
/**
* Returns an <code>IEntity</code> representing a portal entity. This does not guarantee that
* the underlying entity actually exists.
*/
public IEntity getEntity(String key, Class type) throws GroupsException {
IEntity ent = primGetEntity(key, type);
if (cacheInUse()) {
try {
IEntity cachedEnt = getEntityFromCache(ent.getEntityIdentifier().getKey());
if (cachedEnt == null) {
cacheAdd(ent);
} else {
ent = cachedEnt;
}
} catch (CachingException ce) {
throw new GroupsException(
"Problem retrieving group member " + type + "(" + key + ")", ce);
}
}
return ent;
}
/** Returns a cached <code>IEntityGroup</code> or null if it has not been cached. */
protected IEntityGroup getGroupFromCache(String key) throws CachingException {
return (IEntityGroup)
EntityCachingService.instance().get(ICompositeGroupService.GROUP_ENTITY_TYPE, key);
}
/**
* Returns an <code>IGroupMember</code> representing either a group or a portal entity. If the
* parm <code>type</code> is the group type, the <code>IGroupMember</code> is an <code>
* IEntityGroup</code> else it is an <code>IEntity</code>.
*/
public IGroupMember getGroupMember(String key, Class type) throws GroupsException {
IGroupMember gm = null;
if (type == ICompositeGroupService.GROUP_ENTITY_TYPE) gm = findGroup(key);
else gm = getEntity(key, type);
return gm;
}
/**
* Returns an <code>IGroupMember</code> representing either a group or a portal entity, based on
* the <code>EntityIdentifier</code>, which refers to the UNDERLYING entity for the <code>
* IGroupMember</code>.
*/
public IGroupMember getGroupMember(EntityIdentifier underlyingEntityIdentifier)
throws GroupsException {
return getGroupMember(
underlyingEntityIdentifier.getKey(), underlyingEntityIdentifier.getType());
}
/**
* Returns the implementation of <code>IEntityGroupStore</code> whose class name was retrieved
* by the PropertiesManager (see initialize()).
*/
public IEntityGroupStore getGroupStore() throws GroupsException {
return groupFactory;
}
protected ComponentGroupServiceDescriptor getServiceDescriptor() {
return serviceDescriptor;
}
/** @exception GroupsException */
private void initialize() throws GroupsException {
String eMsg = null;
String svcName = getServiceDescriptor().getName();
if (log.isDebugEnabled()) log.debug("Service descriptor attributes: " + svcName);
// print service descriptor attributes:
for (Iterator i = getServiceDescriptor().keySet().iterator(); i.hasNext(); ) {
String descriptorKey = (String) i.next();
Object descriptorValue = getServiceDescriptor().get(descriptorKey);
if (descriptorValue != null) {
if (log.isDebugEnabled()) log.debug(" " + descriptorKey + " : " + descriptorValue);
}
}
String groupStoreFactoryName = getServiceDescriptor().getGroupStoreFactoryName();
String entityStoreFactoryName = getServiceDescriptor().getEntityStoreFactoryName();
String entitySearcherFactoryName = getServiceDescriptor().getEntitySearcherFactoryName();
if (groupStoreFactoryName == null) {
if (log.isInfoEnabled()) {
log.info(
"ReferenceGroupService.initialize(): ("
+ svcName
+ ") No Group Store factory specified in service descriptor.");
}
} else {
try {
IEntityGroupStoreFactory groupStoreFactory =
(IEntityGroupStoreFactory)
Class.forName(groupStoreFactoryName).newInstance();
groupFactory = groupStoreFactory.newGroupStore(getServiceDescriptor());
} catch (Exception e) {
eMsg =
"ReferenceIndividualGroupService.initialize(): Failed to instantiate group store ("
+ svcName
+ "): "
+ e;
log.error(eMsg);
throw new GroupsException(eMsg, e);
}
}
if (entityStoreFactoryName == null) {
if (log.isInfoEnabled())
log.info(
"ReferenceIndividualGroupService.initialize(): "
+ "No Entity Store Factory specified in service descriptor ("
+ svcName
+ ")");
} else {
try {
IEntityStoreFactory entityStoreFactory =
(IEntityStoreFactory) Class.forName(entityStoreFactoryName).newInstance();
entityFactory = entityStoreFactory.newEntityStore();
} catch (Exception e) {
eMsg =
"ReferenceIndividualGroupService.initialize(): Failed to instantiate entity store "
+ e;
log.error(eMsg);
throw new GroupsException(eMsg, e);
}
}
if (entitySearcherFactoryName == null) {
if (log.isInfoEnabled())
log.info(
"ReferenceIndividualGroupService.initialize(): "
+ "No Entity Searcher Factory specified in service descriptor.");
} else {
try {
IEntitySearcherFactory entitySearcherFactory =
(IEntitySearcherFactory)
Class.forName(entitySearcherFactoryName).newInstance();
entitySearcher = entitySearcherFactory.newEntitySearcher();
} catch (Exception e) {
eMsg =
"ReferenceIndividualGroupService.initialize(): Failed to instantiate entity searcher "
+ e;
log.error(eMsg);
throw new GroupsException(eMsg, e);
}
}
}
/** Answers if the group can be updated or deleted in the store. */
public boolean isEditable(IEntityGroup group) throws GroupsException {
return isInternallyManaged();
}
/** Answers if this service is managed by the portal and is therefore updatable. */
protected boolean isInternallyManaged() {
return getServiceDescriptor().isInternallyManaged();
}
/**
* Answers if this service is a leaf in the composite; a service that actually operates on
* groups.
*/
public boolean isLeafService() {
return true;
}
/** Answers if this service is updateable by the portal. */
public boolean isEditable() {
return isInternallyManaged();
}
/** Returns a new <code>IEntityGroup</code> for the given Class with an unused key. */
public IEntityGroup newGroup(Class type) throws GroupsException {
throwExceptionIfNotInternallyManaged();
IEntityGroup group = groupFactory.newInstance(type);
group.setLocalGroupService(this);
if (cacheInUse()) {
cacheAdd(group);
}
return group;
}
/** Returns a pre-existing <code>IEntityGroup</code> or null if it does not exist. */
protected IEntityGroup primFindGroup(String localKey) throws GroupsException {
IEntityGroup group = groupFactory.find(localKey);
if (group != null) {
group.setLocalGroupService(this);
}
return group;
}
private EntityIdentifier[] removeDuplicates(EntityIdentifier[] entities) {
ArrayList ar = new ArrayList(entities.length);
for (int i = 0; i < entities.length; i++) {
if (!ar.contains(entities[i])) {
ar.add(entities[i]);
}
}
return (EntityIdentifier[]) ar.toArray(new EntityIdentifier[0]);
}
public EntityIdentifier[] searchForEntities(String query, int method, Class type)
throws GroupsException {
return removeDuplicates(entitySearcher.searchForEntities(query, method, type));
}
public EntityIdentifier[] searchForEntities(
String query, int method, Class type, IEntityGroup ancestor) throws GroupsException {
return filterEntities(searchForEntities(query, method, type), ancestor);
}
public EntityIdentifier[] searchForGroups(String query, int method, Class leaftype)
throws GroupsException {
return removeDuplicates(groupFactory.searchForGroups(query, method, leaftype));
}
public EntityIdentifier[] searchForGroups(
String query, int method, Class leaftype, IEntityGroup ancestor)
throws GroupsException {
return filterEntities(searchForGroups(query, method, leaftype), ancestor);
}
protected void throwExceptionIfNotInternallyManaged() throws GroupsException {
if (!isInternallyManaged()) {
throw new GroupsException("Group Service " + getServiceName() + " is not updatable.");
}
}
/**
* Update the store and the updated members.
*
* @param group IEntityGroup
*/
public void updateGroup(IEntityGroup group) throws GroupsException {
throwExceptionIfNotInternallyManaged();
getGroupStore().update(group);
if (cacheInUse()) {
cacheUpdate(group);
}
synchronizeGroupMembersOnUpdate(group);
}
/**
* Updates the <code>ILockableEntityGroup</code> in the store and removes it from the cache.
*
* @param group ILockableEntityGroup
*/
public void updateGroup(ILockableEntityGroup group, boolean renewLock) throws GroupsException {
throwExceptionIfNotInternallyManaged();
try {
if (!group.getLock().isValid()) {
throw new GroupsException(
"Could not update group " + group.getKey() + " has invalid lock.");
}
// updateGroup((IEntityGroup)group);
getGroupStore().update(group);
if (cacheInUse()) {
cacheRemove(group);
}
synchronizeGroupMembersOnUpdate(group);
if (renewLock) {
group.getLock().renew();
} else {
group.getLock().release();
}
} catch (LockingException le) {
throw new GroupsException("Problem updating group " + group.getKey(), le);
}
}
/**
* Update the store and the updated members.
*
* @param group IEntityGroup
*/
public void updateGroupMembers(IEntityGroup group) throws GroupsException {
throwExceptionIfNotInternallyManaged();
getGroupStore().updateMembers(group);
if (cacheInUse()) {
cacheUpdate(group);
}
synchronizeGroupMembersOnUpdate(group);
}
/**
* Updates the <code>ILockableEntityGroup</code> in the store and removes it from the cache.
*
* @param group ILockableEntityGroup
*/
public void updateGroupMembers(ILockableEntityGroup group, boolean renewLock)
throws GroupsException {
throwExceptionIfNotInternallyManaged();
try {
if (!group.getLock().isValid()) {
throw new GroupsException(
"Could not update group " + group.getKey() + " has invalid lock.");
}
getGroupStore().updateMembers(group);
if (cacheInUse()) {
cacheRemove(group);
}
synchronizeGroupMembersOnUpdate(group);
if (renewLock) {
group.getLock().renew();
} else {
group.getLock().release();
}
} catch (LockingException le) {
throw new GroupsException("Problem updating group " + group.getKey(), le);
}
}
/**
* Returns an <code>IEntity</code> representing a portal entity. This does not guarantee that
* the underlying entity actually exists.
*/
protected IEntity primGetEntity(String key, Class type) throws GroupsException {
return entityFactory.newInstance(key, type);
}
/**
* Remove the back pointers of the group members of the deleted group. Then update the cache to
* invalidate copies on peer servers.
*
* @param group ILockableEntityGroup
*/
protected void synchronizeGroupMembersOnDelete(IEntityGroup group) throws GroupsException {
GroupMemberImpl gmi = null;
for (Iterator it = group.getChildren().iterator(); it.hasNext(); ) {
gmi = (GroupMemberImpl) it.next();
gmi.invalidateInParentGroupsCache(Collections.singleton((IGroupMember) gmi));
if (cacheInUse()) {
cacheUpdate(gmi);
}
}
}
/**
* Adjust the back pointers of the updated group members to either add or remove the parent
* group. Then update the cache to invalidate copies on peer servers.
*
* @param group ILockableEntityGroup
*/
protected void synchronizeGroupMembersOnUpdate(IEntityGroup group) throws GroupsException {
EntityGroupImpl egi = (EntityGroupImpl) group;
GroupMemberImpl gmi = null;
for (Iterator it = egi.getAddedMembers().values().iterator(); it.hasNext(); ) {
gmi = (GroupMemberImpl) it.next();
gmi.invalidateInParentGroupsCache(Collections.singleton((IGroupMember) gmi));
if (cacheInUse()) {
cacheUpdate(gmi);
}
}
for (Iterator it = egi.getRemovedMembers().values().iterator(); it.hasNext(); ) {
gmi = (GroupMemberImpl) it.next();
gmi.invalidateInParentGroupsCache(Collections.singleton((IGroupMember) gmi));
if (cacheInUse()) {
cacheUpdate(gmi);
}
}
}
}