/**
* 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.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.services.GroupService;
import org.apereo.portal.spring.locator.ApplicationContextLocator;
import org.apereo.portal.spring.locator.EntityTypesLocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
/**
* This is the base class for every node in the graph.
*
* @see IGroupMember
*/
public abstract class GroupMemberImpl implements IGroupMember {
/*
* The <code>EntityIdentifier</code> that uniquely identifies the entity,
* e.g., the <code>IPerson</code>, <code>ChannelDefinition</code>, etc.,
* that underlies the <code>IGroupMember</code>.
*/
private EntityIdentifier underlyingEntityIdentifier;
private final Cache parentGroupsCache;
private final Logger logger = LoggerFactory.getLogger(getClass());
/** GroupMemberImpl constructor */
public GroupMemberImpl(EntityIdentifier newEntityIdentifier) throws GroupsException {
super();
if (isKnownEntityType(newEntityIdentifier.getType())) {
underlyingEntityIdentifier = newEntityIdentifier;
} else {
throw new GroupsException("Unknown entity type: " + newEntityIdentifier.getType());
}
ApplicationContext context = ApplicationContextLocator.getApplicationContext();
CacheManager cacheManager = context.getBean("cacheManager", CacheManager.class);
this.parentGroupsCache =
cacheManager.getCache("org.apereo.portal.groups.GroupMemberImpl.parentGroups");
}
/**
* Returns an <code>Iterator</code> over the <code>Set</code> of this <code>IGroupMember's
* </code> recursively-retrieved parent groups.
*
* @return Iterator
*/
@Override
public Set<IEntityGroup> getAncestorGroups() throws GroupsException {
return primGetAncestorGroups(this, new HashSet<IEntityGroup>());
}
/**
* Returns an <code>Iterator</code> over this <code>IGroupMember's</code> parent groups.
* Synchronize the collection of keys with adds and removes.
*
* @return Iterator
*/
@Override
public Set<IEntityGroup> getParentGroups() throws GroupsException {
final EntityIdentifier cacheKey = getUnderlyingEntityIdentifier();
Element element = parentGroupsCache.get(cacheKey);
if (element == null) {
final Set<IEntityGroup> groups = buildParentGroupsSet();
element = new Element(cacheKey, groups);
parentGroupsCache.put(element);
}
@SuppressWarnings("unchecked")
final Set<IEntityGroup> rslt = (Set<IEntityGroup>) element.getObjectValue();
return rslt;
}
private synchronized Set<IEntityGroup> buildParentGroupsSet() throws GroupsException {
logger.debug(
"Constructing containingGroups for member='{}'", getUnderlyingEntityIdentifier());
final Set<IEntityGroup> rslt = new HashSet<>();
for (Iterator it = GroupService.getCompositeGroupService().findParentGroups(this);
it.hasNext();
) {
final IEntityGroup eg = (IEntityGroup) it.next();
rslt.add(eg);
}
return Collections.unmodifiableSet(rslt);
}
/** @return String */
@Override
public String getKey() {
return getUnderlyingEntityIdentifier().getKey();
}
/** @return Class */
@Override
public Class getType() {
return getUnderlyingEntityIdentifier().getType();
}
/** @return EntityIdentifier */
@Override
public EntityIdentifier getUnderlyingEntityIdentifier() {
return underlyingEntityIdentifier;
}
/**
* Answers if this <code>IGroupMember</code> is, recursively, a member of <code>IGroupMember
* </code> gm.
*
* @return boolean
* @param gm org.apereo.portal.groups.IGroupMember
*/
@Override
public boolean isDeepMemberOf(IEntityGroup group) throws GroupsException {
return isMemberOf(group) ? true : group.deepContains(this);
}
/** @return boolean */
@Override
public boolean isGroup() {
return false;
}
/** @return boolean. */
protected boolean isKnownEntityType(Class anEntityType) throws GroupsException {
return (EntityTypesLocator.getEntityTypes().getEntityIDFromType(anEntityType) != null);
}
/**
* Answers if this <code>IGroupMember</code> is a member of <code>IGroupMember</code> gm.
*
* @param gm org.apereo.portal.groups.IGroupMember
* @return boolean
*/
@Override
public boolean isMemberOf(IEntityGroup group) throws GroupsException {
return getParentGroups().contains(group);
}
/** @throws UnsupportedOperationException */
@Override
public IEntityGroup asGroup() {
throw new UnsupportedOperationException("This member is not a group: " + this.getKey());
}
/**
* Returns the <code>Set</code> of groups in our member <code>Collection</code> and,
* recursively, in the <code>Collections</code> of our members.
*
* @param member org.apereo.portal.groups.IGroupMember - The current group member in the
* recursive execution.
* @param rslt Set - A Set that groups are added to.
* @return Set
*/
protected Set<IEntityGroup> primGetAncestorGroups(IGroupMember member, Set<IEntityGroup> rslt)
throws GroupsException {
for (IEntityGroup group : member.getParentGroups()) {
// avoid stack overflow in case of circular group dependencies
if (!rslt.contains(group)) {
rslt.add(group);
primGetAncestorGroups(group, rslt);
}
}
return rslt;
}
@Override
public final int hashCode() {
final int prime = 31;
int result = 1;
result =
prime * result
+ ((underlyingEntityIdentifier == null)
? 0
: underlyingEntityIdentifier.hashCode());
return result;
}
@Override
public final boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
GroupMemberImpl other = (GroupMemberImpl) obj;
if (underlyingEntityIdentifier == null) {
if (other.underlyingEntityIdentifier != null) return false;
} else if (!underlyingEntityIdentifier.equals(other.underlyingEntityIdentifier))
return false;
return true;
}
protected void invalidateInParentGroupsCache(Set<IGroupMember> members) {
for (IGroupMember member : members) {
parentGroupsCache.remove(member.getEntityIdentifier());
}
}
}