/**
* 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.smartldap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.ConcurrentException;
import org.apache.commons.lang3.concurrent.LazyInitializer;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.groups.ComponentGroupServiceDescriptor;
import org.apereo.portal.groups.EntityGroupImpl;
import org.apereo.portal.groups.EntityTestingGroupImpl;
import org.apereo.portal.groups.GroupsException;
import org.apereo.portal.groups.IEntityGroup;
import org.apereo.portal.groups.IEntityGroupStore;
import org.apereo.portal.groups.IEntityGroupStoreFactory;
import org.apereo.portal.groups.IGroupConstants;
import org.apereo.portal.groups.IGroupMember;
import org.apereo.portal.groups.ILockableEntityGroup;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.PersonFactory;
import org.danann.cernunnos.Task;
import org.danann.cernunnos.runtime.RuntimeRequestResponse;
import org.danann.cernunnos.runtime.ScriptRunner;
import org.jasig.services.persondir.IPersonAttributeDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.ContextSource;
public final class SmartLdapGroupStore implements IEntityGroupStore {
// Instance Members.
private String memberOfAttributeName = "memberOf"; // default
public void setMemberOfAttributeName(String memberOfAttributeName) {
this.memberOfAttributeName = memberOfAttributeName;
}
private String baseGroupDn = null;
public void setBaseGroupDn(String baseGroupDn) {
this.baseGroupDn = baseGroupDn;
}
private String filter = "(objectCategory=group)"; // default
public void setFilter(String filter) {
this.filter = filter;
}
private ContextSource ldapContext =
null; // default; must be set if used -- validated in refreshTree()
public void setLdapContext(ContextSource ldapContext) {
this.ldapContext = ldapContext;
}
private boolean resolveMemberGroups = false; // default
public void setResolveMemberGroups(boolean resolveMemberGroups) {
this.resolveMemberGroups = resolveMemberGroups;
}
private List<String> resolveDnList =
Collections.emptyList(); // default; used with resolveMemberGroups
public void setResolveDn(String resolveDn) {
this.resolveDnList = Collections.singletonList(resolveDn);
}
public void setResolveDnList(List<String> resolveDnList) {
this.resolveDnList = Collections.unmodifiableList(resolveDnList);
}
private AttributesMapper attributesMapper;
@Required
public void setAttributesMapper(AttributesMapper attributesMapper) {
this.attributesMapper = attributesMapper;
}
/**
* Period after which SmartLdap will drop and rebuild the groups tree. May be overridden in
* SmartLdapGroupStoreConfix.xml. A value of zero or less (negative) disables this feature.
*/
private long groupsTreeRefreshIntervalSeconds = 900; // default
public void setGroupsTreeRefreshIntervalSeconds(long groupsTreeRefreshIntervalSeconds) {
this.groupsTreeRefreshIntervalSeconds = groupsTreeRefreshIntervalSeconds;
}
/** Timestamp (milliseconds) of the last tree refresh. */
private volatile long lastTreeRefreshTime = 0;
// Cernunnos tech...
private final ScriptRunner runner = new ScriptRunner();
private final Task initTask =
runner.compileTask(getClass().getResource("init.crn").toExternalForm());
@Resource(name = "personAttributeDao")
private IPersonAttributeDao personAttributeDao;
private final Logger log = LoggerFactory.getLogger(getClass());
/*
* Indexed Collections.
*/
/**
* Single-object abstraction that contains all knowledge of SmartLdap groups:
*
* <ul>
* <li>Map of all groups keyed by 'key' (DN). Includes ROOT_GROUP.
* <li>Map of all parent relationships keyed by the 'key' (DN) of the child; the values are
* lists of the 'keys' (DNs) of its parents. Includes ROOT_GROUP.
* <li>Map of all child relationships keyed by the 'key' (DN) of the parent; the values are
* lists of the 'keys' (DNs) of its children. Includes ROOT_GROUP.
* <li>Map of all 'keys' (DNs) of SmartLdap managed groups indexed by group name in upper
* case. Includes ROOT_GROUP.
* </ul>
*/
private GroupsTree groupsTree;
/*
* Public API.
*/
public static final String UNSUPPORTED_MESSAGE =
"The SmartLdap implementation of JA-SIG Groups and Permissions (GaP) "
+ "does not support this operation.";
public static final String ROOT_KEY = "SmartLdap ROOT";
public static final String ROOT_DESC = "A root group provided for the SmartLdapGroupStore.";
private static final LazyInitializer<IEntityGroup> rootGroupInitializer =
new LazyInitializer<IEntityGroup>() {
@Override
protected IEntityGroup initialize() {
IEntityGroup rslt = new EntityTestingGroupImpl(ROOT_KEY, IPerson.class);
rslt.setCreatorID("System");
rslt.setName(ROOT_KEY);
rslt.setDescription(ROOT_DESC);
return rslt;
}
};
public boolean contains(IEntityGroup group, IGroupMember member) throws GroupsException {
log.warn("Unsupported method accessed: SmartLdapGroupStore.contains");
throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE);
}
public void delete(IEntityGroup group) throws GroupsException {
log.warn("Unsupported method accessed: SmartLdapGroupStore.delete");
throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE);
}
/**
* Returns an instance of the <code>IEntityGroup</code> from the data store.
*
* @return org.apereo.portal.groups.IEntityGroup
* @param key java.lang.String
*/
public IEntityGroup find(String key) throws GroupsException {
if (isTreeRefreshRequired()) {
refreshTree();
}
log.debug("Invoking find() for key: {}", key);
// All of our groups (incl. ROOT_GROUP)
// are indexed in the 'groups' map by key...
return groupsTree.getGroups().get(key);
}
/**
* Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntityGroups
* </code> that the <code>IGroupMember</code> belongs to.
*
* @return java.util.Iterator
* @param gm org.apereo.portal.groups.IEntityGroup
*/
public Iterator findParentGroups(IGroupMember gm) throws GroupsException {
if (isTreeRefreshRequired()) {
refreshTree();
}
List<IEntityGroup> rslt = new LinkedList<>();
final IEntityGroup root = getRootGroup();
if (gm.isGroup()) {
// Check the local indeces...
IEntityGroup group = (IEntityGroup) gm;
List<String> list = groupsTree.getParents().get(group.getLocalKey());
if (list != null) {
// should only reach this code if its a SmartLdap managed group...
for (String s : list) {
rslt.add(groupsTree.getGroups().get(s));
}
}
} else if (!gm.isGroup() && gm.getLeafType().equals(root.getLeafType())) {
// Ask the individual...
EntityIdentifier ei = gm.getUnderlyingEntityIdentifier();
Map<String, List<Object>> seed = new HashMap<>();
List<Object> seedValue = new LinkedList<>();
seedValue.add(ei.getKey());
seed.put(IPerson.USERNAME, seedValue);
Map<String, List<Object>> attr = personAttributeDao.getMultivaluedUserAttributes(seed);
// avoid NPEs and unnecessary IPerson creation
if (attr != null && !attr.isEmpty()) {
IPerson p = PersonFactory.createPerson();
p.setAttributes(attr);
// Analyze its memberships...
Object[] groupKeys = p.getAttributeValues(memberOfAttributeName);
// IPerson returns null if no value is defined for this attribute...
if (groupKeys != null) {
List<String> list = new LinkedList<>();
for (Object o : groupKeys) {
list.add((String) o);
}
for (String s : list) {
if (groupsTree.getGroups().containsKey(s)) {
rslt.add(groupsTree.getGroups().get(s));
}
}
}
}
}
return rslt.iterator();
}
/**
* Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntities</code>
* that are members of this <code>IEntityGroup</code>.
*
* @return java.util.Iterator
* @param group org.apereo.portal.groups.IEntityGroup
*/
public Iterator findEntitiesForGroup(IEntityGroup group) throws GroupsException {
if (isTreeRefreshRequired()) {
refreshTree();
}
log.debug("Invoking findEntitiesForGroup() for group: {}", group.getLocalKey());
// We only deal w/ group-group relationships here...
return findMemberGroups(group);
}
public ILockableEntityGroup findLockable(String key) throws GroupsException {
log.warn("Unsupported method accessed: SmartLdapGroupStore.findLockable");
throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE);
}
/**
* Returns a <code>String[]</code> containing the keys of <code>IEntityGroups</code> that are
* members of this <code>IEntityGroup</code>. In a composite group system, a group may contain a
* member group from a different service. This is called a foreign membership, and is only
* possible in an internally-managed service. A group store in such a service can return the key
* of a foreign member group, but not the group itself, which can only be returned by its local
* store.
*
* @return String[]
* @param group org.apereo.portal.groups.IEntityGroup
*/
public String[] findMemberGroupKeys(IEntityGroup group) throws GroupsException {
if (isTreeRefreshRequired()) {
refreshTree();
}
log.debug("Invoking findMemberGroupKeys() for group: {}", group.getLocalKey());
List<String> rslt = new LinkedList<>();
for (Iterator it = findMemberGroups(group); it.hasNext(); ) {
IEntityGroup g = (IEntityGroup) it.next();
// Return composite keys here...
rslt.add(g.getKey());
}
return rslt.toArray(new String[rslt.size()]);
}
/**
* Returns an <code>Iterator</code> over the <code>Collection</code> of <code>IEntityGroups
* </code> that are members of this <code>IEntityGroup</code>.
*
* @return java.util.Iterator
* @param group org.apereo.portal.groups.IEntityGroup
*/
public Iterator findMemberGroups(IEntityGroup group) throws GroupsException {
if (isTreeRefreshRequired()) {
refreshTree();
}
log.debug("Invoking findMemberGroups() for group: {}", group.getLocalKey());
List<IEntityGroup> rslt = new LinkedList<>();
List<String> list = groupsTree.getChildren().get(group.getLocalKey());
if (list != null) {
// should only reach this code if its a SmartLdap managed group...
for (String s : list) {
rslt.add(groupsTree.getGroups().get(s));
}
}
return rslt.iterator();
}
public IEntityGroup newInstance(Class entityType) throws GroupsException {
log.warn("Unsupported method accessed: SmartLdapGroupStore.newInstance");
throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE);
}
public EntityIdentifier[] searchForGroups(String query, int method, Class leaftype)
throws GroupsException {
if (isTreeRefreshRequired()) {
refreshTree();
}
log.debug(
"Invoking searchForGroups(): query={}, method={}, leaftype=",
query,
method,
leaftype.getClass().getName());
// We only match the IPerson leaf type...
final IEntityGroup root = getRootGroup();
if (!leaftype.equals(root.getLeafType())) {
return new EntityIdentifier[0];
}
// We need to escape regex special characters that appear in the query string...
final String[][] specials =
new String[][] {
/* backslash must come first! */
new String[] {"\\", "\\\\"},
new String[] {"[", "\\["},
/* closing ']' isn't needed b/c it's a normal character w/o a preceding '[' */
new String[] {"{", "\\{"},
/* closing '}' isn't needed b/c it's a normal character w/o a preceding '{' */
new String[] {"^", "\\^"},
new String[] {"$", "\\$"},
new String[] {".", "\\."},
new String[] {"|", "\\|"},
new String[] {"?", "\\?"},
new String[] {"*", "\\*"},
new String[] {"+", "\\+"},
new String[] {"(", "\\("},
new String[] {")", "\\)"}
};
for (String[] s : specials) {
query = query.replace(s[0], s[1]);
}
// Establish the regex pattern to match on...
String regex;
switch (method) {
case IGroupConstants.IS:
regex = query.toUpperCase();
break;
case IGroupConstants.STARTS_WITH:
regex = query.toUpperCase() + ".*";
break;
case IGroupConstants.ENDS_WITH:
regex = ".*" + query.toUpperCase();
break;
case IGroupConstants.CONTAINS:
regex = ".*" + query.toUpperCase() + ".*";
break;
default:
String msg = "Unsupported search method: " + method;
throw new GroupsException(msg);
}
List<EntityIdentifier> rslt = new LinkedList<>();
for (Map.Entry<String, List<String>> y : groupsTree.getKeysByUpperCaseName().entrySet()) {
if (y.getKey().matches(regex)) {
List<String> keys = y.getValue();
for (String k : keys) {
rslt.add(new EntityIdentifier(k, IEntityGroup.class));
}
}
}
return rslt.toArray(new EntityIdentifier[rslt.size()]);
}
public void update(IEntityGroup group) throws GroupsException {
log.warn("Unsupported method accessed: SmartLdapGroupStore.update");
throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE);
}
public void updateMembers(IEntityGroup group) throws GroupsException {
log.warn("Unsupported method accessed: SmartLdapGroupStore.updateMembers");
throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE);
}
public LdapRecord detectAndEliminateGroupReferences(
LdapRecord record, List<String> groupChain) {
LdapRecord rslt = record; // default
List<String> keysOfChildren = record.getKeysOfChildren();
List<String> filteredChildren = new ArrayList<>();
for (String key : keysOfChildren) {
if (!groupChain.contains(key)) {
filteredChildren.add(key);
} else {
// Circular reference detected!
log.warn(
"Circular reference detected and removed for the following groups: '{}' and '{}'",
key,
record.getGroup().getLocalKey());
}
}
if (filteredChildren.size() < keysOfChildren.size()) {
rslt = new LdapRecord(record.getGroup(), filteredChildren);
}
return rslt;
}
public boolean hasUndiscoveredChildrenWithinDn(
LdapRecord record, String referenceDn, Set<LdapRecord> groupsSet) {
boolean rslt = false; // default
for (String childKey : record.getKeysOfChildren()) {
if (childKey.endsWith(referenceDn)) {
// Make sure the one we found isn't already in the groupsSet;
// NOTE!... this test takes advantage of the implementation of
// equals() on LdapRecord, which states that 2 records with the
// same group key are equal.
IEntityGroup group = new EntityGroupImpl(childKey, IPerson.class);
List<String> list = Collections.emptyList();
LdapRecord proxy = new LdapRecord(group, list);
if (!groupsSet.contains(proxy)) {
rslt = true;
break;
} else {
log.trace("Child group is already in collection: {}", childKey);
}
}
}
log.trace(
"Query for children of parent group '{}': {}",
record.getGroup().getLocalKey(),
rslt);
return rslt;
}
/*
* Implementation.
*/
@PostConstruct
private void postConstruct() {
Factory.setInstance(this);
}
private IEntityGroup getRootGroup() {
try {
return rootGroupInitializer.get();
} catch (ConcurrentException ce) {
throw new RuntimeException("Failed to obtain the SmartLdap root group", ce);
}
}
private boolean isTreeRefreshRequired() {
if (groupsTree == null) {
// Of course we need it
return true;
}
if (groupsTreeRefreshIntervalSeconds <= 0) {
// SmartLdap refresh feature may be disabled by setting
// groupsTreeRefreshIntervalSeconds to zero or negative.
return false;
}
// The 'lastTreeRefreshTime' member variable is volatile. As of JDK 5,
// this fact should make reads of this variable dependable in a multi-
// threaded environment.
final long treeExpiresTimestamp =
lastTreeRefreshTime + (groupsTreeRefreshIntervalSeconds * 1000L);
return System.currentTimeMillis() > treeExpiresTimestamp;
}
/**
* Verifies that the collection of groups needs rebuilding and, if so, spawns a new worker
* <code>Thread</code> for that purpose.
*/
private synchronized void refreshTree() {
if (!isTreeRefreshRequired()) {
// The groupsTree was already re-built while
// we were waiting to enter this method.
return;
}
log.info("Refreshing groups tree for SmartLdap");
// We must join the builder thread if
// we don't have an existing groupsTree.
final boolean doJoin = groupsTree == null;
// In most cases, re-build the tree in a separate thread; the current
// request can proceed with the newly-expired groupsTree.
Thread refresh =
new Thread("SmartLdap Refresh Worker") {
public void run() {
// Replace the old with the new...
try {
groupsTree = buildGroupsTree();
} catch (Throwable t) {
log.error("SmartLdapGroupStore failed to build the groups tree", t);
}
}
};
refresh.setDaemon(true);
refresh.start();
if (doJoin) {
try {
log.info("Joining the SmartLdap Refresh Worker Thread");
refresh.join();
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
// Even if the refresh thread failed, don't try
// again for another groupsTreeRefreshIntervalSeconds.
lastTreeRefreshTime = System.currentTimeMillis();
}
private GroupsTree buildGroupsTree() {
long timestamp = System.currentTimeMillis();
// Prepare the new local indeces...
Map<String, IEntityGroup> new_groups =
Collections.synchronizedMap(new HashMap<String, IEntityGroup>());
Map<String, List<String>> new_parents =
Collections.synchronizedMap(new HashMap<String, List<String>>());
Map<String, List<String>> new_children =
Collections.synchronizedMap(new HashMap<String, List<String>>());
Map<String, List<String>> new_keysByUpperCaseName =
Collections.synchronizedMap(new HashMap<String, List<String>>());
// Gather IEntityGroup objects from LDAP...
RuntimeRequestResponse req = new RuntimeRequestResponse();
Set<LdapRecord> set = new HashSet<>();
req.setAttribute("GROUPS", set);
req.setAttribute("smartLdapGroupStore", this);
SubQueryCounter queryCounter = new SubQueryCounter();
req.setAttribute("queryCounter", queryCounter);
req.setAttribute("filter", filter); // This one changes iteratively...
req.setAttribute("baseFilter", filter); // while this one stays the same.
if (StringUtils.isBlank(baseGroupDn)) {
throw new IllegalStateException("baseGroupDn property not set");
}
req.setAttribute("baseGroupDn", baseGroupDn);
if (ldapContext == null) {
throw new IllegalStateException("ldapContext property not set");
}
req.setAttribute("ldapContext", ldapContext);
req.setAttribute("resolveMemberGroups", resolveMemberGroups);
req.setAttribute("resolveDnList", resolveDnList);
req.setAttribute("memberOfAttributeName", memberOfAttributeName);
req.setAttribute("attributesMapper", attributesMapper);
runner.run(initTask, req);
log.info("init() found {} records", set.size());
// Do a first loop to build the main catalog (new_groups)...
for (LdapRecord r : set) {
// new_groups (me)...
IEntityGroup g = r.getGroup();
new_groups.put(g.getLocalKey(), g);
}
// Do a second loop to build local indeces...
for (LdapRecord r : set) {
IEntityGroup g = r.getGroup();
// new_parents (I am a parent for all my children)...
for (String childKey : r.getKeysOfChildren()) {
// NB: We're only interested in relationships between
// objects in the main catalog (i.e. new_groups);
// discard everything else...
if (!new_groups.containsKey(childKey)) {
break;
}
List<String> parentsList = new_parents.get(childKey);
if (parentsList == null) {
// first parent for this child...
parentsList = Collections.synchronizedList(new LinkedList<String>());
new_parents.put(childKey, parentsList);
}
parentsList.add(g.getLocalKey());
}
// new_children...
List<String> childrenList = Collections.synchronizedList(new LinkedList<String>());
for (String childKey : r.getKeysOfChildren()) {
// NB: We're only interested in relationships between
// objects in the main catalog (i.e. new_groups);
// discard everything else...
if (new_groups.containsKey(childKey)) {
childrenList.add(childKey);
}
}
new_children.put(g.getLocalKey(), childrenList);
// new_keysByUpperCaseName...
List<String> groupsWithMyName = new_keysByUpperCaseName.get(g.getName().toUpperCase());
if (groupsWithMyName == null) {
// I am the first group with my name (pretty likely)...
groupsWithMyName = Collections.synchronizedList(new LinkedList<String>());
new_keysByUpperCaseName.put(g.getName().toUpperCase(), groupsWithMyName);
}
groupsWithMyName.add(g.getLocalKey());
}
/*
* Now load the ROOT_GROUP into the collections...
*/
// new_groups (me)...
final IEntityGroup root = getRootGroup();
new_groups.put(root.getLocalKey(), root);
// new_parents (I am a parent for all groups that have no other parent)...
List<String> childrenOfRoot =
Collections.synchronizedList(new LinkedList<String>()); // for later...
for (String possibleChildKey : new_groups.keySet()) {
if (!possibleChildKey.equals(root.getLocalKey())
&& !new_parents.containsKey(possibleChildKey)) {
List<String> p = Collections.synchronizedList(new LinkedList<String>());
p.add(root.getLocalKey());
new_parents.put(possibleChildKey, p);
childrenOfRoot.add(possibleChildKey); // for later...
}
}
// new_children...
new_children.put(root.getLocalKey(), childrenOfRoot);
// new_keysByUpperCaseName...
List<String> groupsWithMyName = new_keysByUpperCaseName.get(root.getName().toUpperCase());
if (groupsWithMyName == null) {
// I am the first group with my name (pretty likely)...
groupsWithMyName = Collections.synchronizedList(new LinkedList<String>());
new_keysByUpperCaseName.put(root.getName().toUpperCase(), groupsWithMyName);
}
groupsWithMyName.add(root.getLocalKey());
final long benchmark = System.currentTimeMillis() - timestamp;
log.info("Refresh of groups tree completed in {} milliseconds", benchmark);
log.info("Total number of LDAP queries: {}", queryCounter.getCount() + 1);
final String msg =
"init() :: final size of each collection is as follows..."
+ "\n\tgroups={}"
+ "\n\tparents={}"
+ "\n\tchildren={}"
+ "\n\tkeysByUpperCaseName={}";
log.info(
msg,
new_groups.size(),
new_parents.size(),
new_children.size(),
new_keysByUpperCaseName.size());
if (log.isTraceEnabled()) {
StringBuilder sbuilder = new StringBuilder();
// new_groups...
sbuilder.setLength(0);
sbuilder.append("Here are the keys of the new_groups collection:");
for (String s : new_groups.keySet()) {
sbuilder.append("\n\t").append(s);
}
log.trace(sbuilder.toString());
// new_parents...
sbuilder.setLength(0);
sbuilder.append("Here are the parents of each child in the new_parents collection:");
for (Map.Entry<String, List<String>> y : new_parents.entrySet()) {
sbuilder.append("\n\tchild=").append(y.getKey());
for (String s : y.getValue()) {
sbuilder.append("\n\t\tparent=").append(s);
}
}
log.trace(sbuilder.toString());
// new_children...
sbuilder.setLength(0);
sbuilder.append("Here are the children of each parent in the new_children collection:");
for (Map.Entry<String, List<String>> y : new_children.entrySet()) {
sbuilder.append("\n\tparent=").append(y.getKey());
for (String s : y.getValue()) {
sbuilder.append("\n\t\tchild=").append(s);
}
}
log.trace(sbuilder.toString());
// new_keysByUpperCaseName...
sbuilder.append(
"Here are the groups that have each name in the new_keysByUpperCaseName collection:");
for (Map.Entry<String, List<String>> y : new_keysByUpperCaseName.entrySet()) {
sbuilder.append("\n\tname=").append(y.getKey());
for (String s : y.getValue()) {
sbuilder.append("\n\t\tgroup=").append(s);
}
}
log.trace(sbuilder.toString());
}
return new GroupsTree(new_groups, new_parents, new_children, new_keysByUpperCaseName);
}
/*
* Nested Types.
*/
public static final class Factory implements IEntityGroupStoreFactory {
private static IEntityGroupStore instance;
private static void setInstance(IEntityGroupStore smartLdapGroupStore) {
instance = smartLdapGroupStore;
}
/*
* Public API.
*/
public IEntityGroupStore newGroupStore() throws GroupsException {
return instance;
}
public IEntityGroupStore newGroupStore(ComponentGroupServiceDescriptor svcDescriptor)
throws GroupsException {
return instance;
}
}
private static final class GroupsTree {
// Instance Members.
private final Map<String, IEntityGroup> groups;
private final Map<String, List<String>> parents;
private final Map<String, List<String>> children;
private final Map<String, List<String>> keysByUpperCaseName;
/*
* Public API.
*/
public GroupsTree(
Map<String, IEntityGroup> groups,
Map<String, List<String>> parents,
Map<String, List<String>> children,
Map<String, List<String>> keysByUpperCaseName) {
// Assertions.
if (groups == null) {
String msg = "Argument 'groups' cannot be null.";
throw new IllegalArgumentException(msg);
}
if (parents == null) {
String msg = "Argument 'parents' cannot be null.";
throw new IllegalArgumentException(msg);
}
if (children == null) {
String msg = "Argument 'children' cannot be null.";
throw new IllegalArgumentException(msg);
}
if (keysByUpperCaseName == null) {
String msg = "Argument 'keysByUpperCaseName' cannot be null.";
throw new IllegalArgumentException(msg);
}
// Instance Members.
this.groups = groups;
this.parents = parents;
this.children = children;
this.keysByUpperCaseName = keysByUpperCaseName;
}
public Map<String, IEntityGroup> getGroups() {
return groups;
}
public Map<String, List<String>> getParents() {
return parents;
}
public Map<String, List<String>> getChildren() {
return children;
}
public Map<String, List<String>> getKeysByUpperCaseName() {
return keysByUpperCaseName;
}
}
private static final class SubQueryCounter {
private int count = 0;
@SuppressWarnings("unused")
public void increment() {
++count;
}
public int getCount() {
return count;
}
}
}