From c179022074f9136fc38367f31b9843a74c529334 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Tue, 20 Mar 2007 23:09:54 +0000
Subject: [PATCH] Add support for dynamic groups, which use the groupOfURLs object class and the memberURL attribute type to specify one or more LDAP URLs containing criteria for membership.
---
opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java | 11
opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java | 14
opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupSearchThread.java | 208 ++++++
opendj-sdk/opends/resource/schema/00-core.ldif | 2
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java | 15
opendj-sdk/opends/src/server/org/opends/server/types/LDAPURL.java | 35
opendj-sdk/opends/resource/config/config.ldif | 7
opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java | 86 ++
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchListener.java | 19
opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroup.java | 433 ++++++++++++
opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java | 10
opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java | 34
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java | 610 +++++++++++++++++
opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupMemberList.java | 480 ++++++++++++++
opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java | 7
opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java | 53 +
16 files changed, 1,988 insertions(+), 36 deletions(-)
diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index 1eedfcf..4788127 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -425,6 +425,13 @@
objectClass: ds-cfg-branch
cn: Group Implementations
+dn: cn=Dynamic,cn=Group Implementations,cn=config
+objectClass: top
+objectClass: ds-cfg-group-implementation
+cn: Dynamic
+ds-cfg-group-implementation-class: org.opends.server.extensions.DynamicGroup
+ds-cfg-group-implementation-enabled: true
+
dn: cn=Static,cn=Group Implementations,cn=config
objectClass: top
objectClass: ds-cfg-group-implementation
diff --git a/opendj-sdk/opends/resource/schema/00-core.ldif b/opendj-sdk/opends/resource/schema/00-core.ldif
index cd51e55..d70f875 100644
--- a/opendj-sdk/opends/resource/schema/00-core.ldif
+++ b/opendj-sdk/opends/resource/schema/00-core.ldif
@@ -524,7 +524,7 @@
STRUCTURAL MUST ( inheritable ) MAY ( blockInheritance )
X-ORIGIN 'draft-ietf-ldup-subentry' )
objectClasses: ( 2.16.840.1.113730.3.2.33 NAME 'groupOfURLs'
- DESC 'Sun-defined objectclass' SUP top MUST ( cn )
+ DESC 'Sun-defined objectclass' SUP top STRUCTURAL MUST ( cn )
MAY ( memberURL $ businessCategory $ description $ o $ ou $ owner $ seeAlso )
X-ORIGIN 'Sun Java System Directory Server' )
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
index d1ca3a3..b5dd809 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
@@ -412,10 +412,15 @@
* entry is associated.
* @param searchEntry The search result entry to be sent to
* the client.
+ *
+ * @throws DirectoryException If a problem occurs while attempting
+ * to send the entry to the client and
+ * the search should be terminated.
*/
public abstract void sendSearchEntry(
SearchOperation searchOperation,
- SearchResultEntry searchEntry);
+ SearchResultEntry searchEntry)
+ throws DirectoryException;
@@ -431,10 +436,15 @@
* referrals, or <CODE>false</CODE> if the client cannot
* handle referrals and no more attempts should be made to
* send them for the associated search operation.
+ *
+ * @throws DirectoryException If a problem occurs while attempting
+ * to send the reference to the client
+ * and the search should be terminated.
*/
public abstract boolean sendSearchReference(
SearchOperation searchOperation,
- SearchResultReference searchReference);
+ SearchResultReference searchReference)
+ throws DirectoryException;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
index bd01dcf..25e88cb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
@@ -1115,15 +1115,27 @@
// Send the entry to the client.
if (pluginResult.sendEntry())
{
- clientConnection.sendSearchEntry(this, searchEntry);
+ try
+ {
+ clientConnection.sendSearchEntry(this, searchEntry);
- // Log the entry sent to the client.
- logSearchResultEntry(this, searchEntry);
+ // Log the entry sent to the client.
+ logSearchResultEntry(this, searchEntry);
- entriesSent++;
+ entriesSent++;
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ return false;
+ }
}
-
return pluginResult.continueSearch();
}
@@ -1201,19 +1213,32 @@
// to send any more.
if (pluginResult.sendReference())
{
- if (clientConnection.sendSearchReference(this, reference))
+ try
{
- // Log the entry sent to the client.
- logSearchResultReference(this, reference);
- referencesSent++;
+ if (clientConnection.sendSearchReference(this, reference))
+ {
+ // Log the entry sent to the client.
+ logSearchResultReference(this, reference);
+ referencesSent++;
- // FIXME -- Should the size limit apply here?
+ // FIXME -- Should the size limit apply here?
+ }
+ else
+ {
+ // We know that the client can't handle referrals, so we won't try to
+ // send it any more.
+ clientAcceptsReferrals = false;
+ }
}
- else
+ catch (DirectoryException de)
{
- // We know that the client can't handle referrals, so we won't try to
- // send it any more.
- clientAcceptsReferrals = false;
+ if (debugEnabled())
+ {
+ debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ return false;
}
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroup.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroup.java
new file mode 100644
index 0000000..b348072
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroup.java
@@ -0,0 +1,433 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.server.api.Group;
+import org.opends.server.config.ConfigEntry;
+import org.opends.server.config.ConfigException;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryConfig;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.LDAPURL;
+import org.opends.server.types.MemberList;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchScope;
+
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.Error.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.messages.ExtensionsMessages.*;
+import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.Validator.*;
+
+
+
+/**
+ * This class provides a dynamic group implementation, in which
+ * membership is determined dynamically based on criteria provided
+ * in the form of one or more LDAP URLs. All dynamic groups should
+ * contain the groupOfURLs object class, with the memberURL attribute
+ * specifying the membership criteria.
+ */
+public class DynamicGroup
+ extends Group
+{
+ // The DN of the entry that holds the definition for this group.
+ private DN groupEntryDN;
+
+ // The set of the LDAP URLs that define the membership criteria.
+ private LinkedHashSet<LDAPURL> memberURLs;
+
+
+
+ /**
+ * Creates a new, uninitialized dynamic group instance. This is intended for
+ * internal use only.
+ */
+ public DynamicGroup()
+ {
+ super();
+
+ // No initialization is required here.
+ }
+
+
+
+ /**
+ * Creates a new dynamic group instance with the provided information.
+ *
+ * @param groupEntryDN The DN of the entry that holds the definition for
+ * this group. It must not be {@code null}.
+ * @param memberURLs The set of LDAP URLs that define the membership
+ * criteria for this group. It must not be
+ * {@code null}.
+ */
+ public DynamicGroup(DN groupEntryDN, LinkedHashSet<LDAPURL> memberURLs)
+ {
+ super();
+
+ ensureNotNull(groupEntryDN, memberURLs);
+
+ this.groupEntryDN = groupEntryDN;
+ this.memberURLs = memberURLs;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void initializeGroupImplementation(ConfigEntry configEntry)
+ throws ConfigException, InitializationException
+ {
+ // No additional initialization is required.
+ }
+
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public DynamicGroup newInstance(Entry groupEntry)
+ throws DirectoryException
+ {
+ ensureNotNull(groupEntry);
+
+
+ // Get the memberURL attribute from the entry, if there is one, and parse
+ // out the LDAP URLs that it contains.
+ LinkedHashSet<LDAPURL> memberURLs = new LinkedHashSet<LDAPURL>();
+ AttributeType memberURLType =
+ DirectoryConfig.getAttributeType(ATTR_MEMBER_URL_LC, true);
+ List<Attribute> attrList = groupEntry.getAttribute(memberURLType);
+ if (attrList != null)
+ {
+ for (Attribute a : attrList)
+ {
+ for (AttributeValue v : a.getValues())
+ {
+ try
+ {
+ memberURLs.add(LDAPURL.decode(v.getStringValue(), true));
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ int msgID = MSGID_DYNAMICGROUP_CANNOT_DECODE_MEMBERURL;
+ String message = getMessage(msgID, v.getStringValue(),
+ String.valueOf(groupEntry.getDN()),
+ de.getErrorMessage());
+ logError(ErrorLogCategory.EXTENSIONS, ErrorLogSeverity.MILD_ERROR,
+ message, msgID);
+ }
+ }
+ }
+ }
+
+ return new DynamicGroup(groupEntry.getDN(), memberURLs);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public SearchFilter getGroupDefinitionFilter()
+ throws DirectoryException
+ {
+ // FIXME -- This needs to exclude enhanced groups once we have support for
+ // them.
+ return SearchFilter.createFilterFromString("(" + ATTR_OBJECTCLASS + "=" +
+ OC_GROUP_OF_URLS + ")");
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean isGroupDefinition(Entry entry)
+ {
+ ensureNotNull(entry);
+
+ // FIXME -- This needs to exclude enhanced groups once we have support for
+ //them.
+ ObjectClass groupOfURLsClass =
+ DirectoryConfig.getObjectClass(OC_GROUP_OF_URLS_LC, true);
+ return entry.hasObjectClass(groupOfURLsClass);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public DN getGroupDN()
+ {
+ return groupEntryDN;
+ }
+
+
+
+ /**
+ * Retrieves the set of member URLs for this dynamic group. The returned set
+ * must not be altered by the caller.
+ *
+ * @return The set of member URLs for this dynamic group.
+ */
+ public Set<LDAPURL> getMemberURLs()
+ {
+ return memberURLs;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean supportsNestedGroups()
+ {
+ // Dynamic groups don't support nesting.
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public List<DN> getNestedGroupDNs()
+ {
+ // Dynamic groups don't support nesting.
+ return Collections.<DN>emptyList();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void addNestedGroup(DN nestedGroupDN)
+ throws UnsupportedOperationException, DirectoryException
+ {
+ // Dynamic groups don't support nesting.
+ int msgID = MSGID_DYNAMICGROUP_NESTING_NOT_SUPPORTED;
+ String message = getMessage(msgID);
+ throw new UnsupportedOperationException(message);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void removeNestedGroup(DN nestedGroupDN)
+ throws UnsupportedOperationException, DirectoryException
+ {
+ // Dynamic groups don't support nesting.
+ int msgID = MSGID_DYNAMICGROUP_NESTING_NOT_SUPPORTED;
+ String message = getMessage(msgID);
+ throw new UnsupportedOperationException(message);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean isMember(DN userDN)
+ throws DirectoryException
+ {
+ Entry entry = DirectoryConfig.getEntry(userDN);
+ if (entry == null)
+ {
+ return false;
+ }
+ else
+ {
+ return isMember(entry);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean isMember(Entry userEntry)
+ throws DirectoryException
+ {
+ for (LDAPURL memberURL : memberURLs)
+ {
+ if (memberURL.matchesEntry(userEntry))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public MemberList getMembers()
+ throws DirectoryException
+ {
+ return new DynamicGroupMemberList(groupEntryDN, memberURLs);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public MemberList getMembers(DN baseDN, SearchScope scope,
+ SearchFilter filter)
+ throws DirectoryException
+ {
+ if ((baseDN == null) && (filter == null))
+ {
+ return new DynamicGroupMemberList(groupEntryDN, memberURLs);
+ }
+ else
+ {
+ return new DynamicGroupMemberList(groupEntryDN, memberURLs, baseDN, scope,
+ filter);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean mayAlterMemberList()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void addMember(Entry userEntry)
+ throws UnsupportedOperationException, DirectoryException
+ {
+ // Dynamic groups don't support altering the member list.
+ int msgID = MSGID_DYNAMICGROUP_ALTERING_MEMBERS_NOT_SUPPORTED;
+ String message = getMessage(msgID);
+ throw new UnsupportedOperationException(message);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void removeMember(DN userDN)
+ throws UnsupportedOperationException, DirectoryException
+ {
+ // Dynamic groups don't support altering the member list.
+ int msgID = MSGID_DYNAMICGROUP_ALTERING_MEMBERS_NOT_SUPPORTED;
+ String message = getMessage(msgID);
+ throw new UnsupportedOperationException(message);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("DynamicGroup(dn=");
+ buffer.append(groupEntryDN);
+ buffer.append(",urls={");
+
+ if (! memberURLs.isEmpty())
+ {
+ Iterator<LDAPURL> iterator = memberURLs.iterator();
+ buffer.append("\"");
+ iterator.next().toString(buffer, false);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("\", ");
+ iterator.next().toString(buffer, false);
+ }
+
+ buffer.append("\"");
+ }
+
+ buffer.append("})");
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupMemberList.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupMemberList.java
new file mode 100644
index 0000000..92f5012
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupMemberList.java
@@ -0,0 +1,480 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.LDAPURL;
+import org.opends.server.types.MemberList;
+import org.opends.server.types.MembershipException;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchScope;
+
+
+
+/**
+ * This class defines a mechanism that may be used to iterate over the
+ * members of a dynamic group, optionally using an additional set of
+ * criteria to further filter the results.
+ */
+public class DynamicGroupMemberList
+ extends MemberList
+{
+ // Indicates whether the search thread has completed its processing.
+ private boolean searchesCompleted;
+
+ // The base DN to use when filtering the set of group members.
+ private final DN baseDN;
+
+ // The DN of the entry containing the group definition.
+ private final DN groupDN;
+
+ // The queue into which results will be placed while they are waiting to be
+ // returned. The types of objects that may be placed in this queue are Entry
+ // objects to return or MembershipException objects to throw.
+ private final LinkedBlockingQueue<Object> resultQueue;
+
+ // The search filter to use when filtering the set of group members.
+ private final SearchFilter filter;
+
+ // The search scope to use when filtering the set of group members.
+ private final SearchScope scope;
+
+ // The set of LDAP URLs that define the membership criteria.
+ private final Set<LDAPURL> memberURLs;
+
+
+
+ /**
+ * Creates a new dynamic group member list with the provided information.
+ *
+ * @param groupDN The DN of the entry containing the group definition.
+ * @param memberURLs The set of LDAP URLs that define the membership
+ * criteria for the associated group.
+ *
+ * @throws DirectoryException If a problem occurs while creating the member
+ * list.
+ */
+ public DynamicGroupMemberList(DN groupDN, Set<LDAPURL> memberURLs)
+ throws DirectoryException
+ {
+ this(groupDN, memberURLs, null, null, null);
+ }
+
+
+
+ /**
+ * Creates a new dynamic group member list with the provided information.
+ *
+ * @param groupDN The DN of the entry containing the group definition.
+ * @param memberURLs The set of LDAP URLs that define the membership
+ * criteria for the associated group.
+ * @param baseDN The base DN that should be enforced for all entries to
+ * return.
+ * @param scope The scope that should be enforced for all entries to
+ * return.
+ * @param filter The filter that should be enforced for all entries to
+ * return.
+ *
+ * @throws DirectoryException If a problem occurs while creating the member
+ * list.
+ */
+ public DynamicGroupMemberList(DN groupDN, Set<LDAPURL> memberURLs,
+ DN baseDN, SearchScope scope,
+ SearchFilter filter)
+ throws DirectoryException
+ {
+ this.groupDN = groupDN;
+ this.memberURLs = memberURLs;
+ this.baseDN = baseDN;
+ this.filter = filter;
+
+ if (scope == null)
+ {
+ this.scope = SearchScope.WHOLE_SUBTREE;
+ }
+ else
+ {
+ this.scope = scope;
+ }
+
+ searchesCompleted = false;
+ resultQueue = new LinkedBlockingQueue<Object>(10);
+
+
+ // We're going to have to perform one or more internal searches in order to
+ // get the results. We need to be careful about the way that we construct
+ // them in order to avoid the possibility of getting duplicate results, so
+ // searches with overlapping bases will need to be combined.
+ LinkedHashMap<DN,LinkedList<LDAPURL>> baseDNs =
+ new LinkedHashMap<DN,LinkedList<LDAPURL>>();
+ for (LDAPURL memberURL : memberURLs)
+ {
+ // First, determine the base DN for the search. It needs to be evaluated
+ // as relative to both the overall base DN specified in the set of
+ // criteria, as well as any other existing base DNs in the same hierarchy.
+ DN urlBaseDN = memberURL.getBaseDN();
+ if (baseDN != null)
+ {
+ if (baseDN.isDescendantOf(urlBaseDN))
+ {
+ // The base DN requested by the user is below the base DN for this
+ // URL, so we'll use the base DN requested by the user.
+ urlBaseDN = baseDN;
+ }
+ else if (! urlBaseDN.isDescendantOf(baseDN))
+ {
+ // The base DN from the URL is outside the base requested by the user,
+ // so we can skip this URL altogether.
+ continue;
+ }
+ }
+
+ // If this is the first URL, then we can just add it with the base DN.
+ // Otherwise, we need to see if it needs to be merged with other URLs in
+ // the same hierarchy.
+ if (baseDNs.isEmpty())
+ {
+ LinkedList<LDAPURL> urlList = new LinkedList<LDAPURL>();
+ urlList.add(memberURL);
+ baseDNs.put(urlBaseDN, urlList);
+ }
+ else
+ {
+ // See if the specified base DN is already in the map. If so, then
+ // just add the new URL to the existing list.
+ LinkedList<LDAPURL> urlList = baseDNs.get(urlBaseDN);
+ if (urlList == null)
+ {
+ // There's no existing list for the same base DN, but there might be
+ // DNs in an overlapping hierarchy. If so, then use the base DN that
+ // is closest to the naming context. If not, then add a new list with
+ // the current base DN.
+ boolean found = false;
+ Iterator<DN> iterator = baseDNs.keySet().iterator();
+ while (iterator.hasNext())
+ {
+ DN existingBaseDN = iterator.next();
+ if (urlBaseDN.isDescendantOf(existingBaseDN))
+ {
+ // The base DN for the current URL is below an existing base DN,
+ // so we can just add this URL to the existing list and be done.
+ urlList = baseDNs.get(existingBaseDN);
+ urlList.add(memberURL);
+ found = true;
+ break;
+ }
+ else if (existingBaseDN.isDescendantOf(urlBaseDN))
+ {
+ // The base DN for the current URL is above the existing base DN,
+ // so we should use the base DN for the current URL instead of the
+ // existing one.
+ urlList = baseDNs.get(existingBaseDN);
+ urlList.add(memberURL);
+ iterator.remove();
+ baseDNs.put(urlBaseDN, urlList);
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ urlList = new LinkedList<LDAPURL>();
+ urlList.add(memberURL);
+ baseDNs.put(urlBaseDN, urlList);
+ }
+ }
+ else
+ {
+ // There was already a list with the same base DN, so just add the
+ // URL.
+ urlList.add(memberURL);
+ }
+ }
+ }
+
+
+ // At this point, we should know what base DN(s) we need to use, so we can
+ // create the filter to use with that base DN. There are some special-case
+ // optimizations that we can do here, but in general the filter will look
+ // like "(&(filter)(|(urlFilters)))".
+ LinkedHashMap<DN,SearchFilter> searchMap =
+ new LinkedHashMap<DN,SearchFilter>();
+ for (DN urlBaseDN : baseDNs.keySet())
+ {
+ LinkedList<LDAPURL> urlList = baseDNs.get(urlBaseDN);
+ LinkedHashSet<SearchFilter> urlFilters =
+ new LinkedHashSet<SearchFilter>();
+ for (LDAPURL url : urlList)
+ {
+ urlFilters.add(url.getFilter());
+ }
+
+ SearchFilter combinedFilter;
+ if (filter == null)
+ {
+ if (urlFilters.size() == 1)
+ {
+ combinedFilter = urlFilters.iterator().next();
+ }
+ else
+ {
+ combinedFilter = SearchFilter.createORFilter(urlFilters);
+ }
+ }
+ else
+ {
+ if (urlFilters.size() == 1)
+ {
+ SearchFilter urlFilter = urlFilters.iterator().next();
+ if (urlFilter.equals(filter))
+ {
+ combinedFilter = filter;
+ }
+ else
+ {
+ LinkedHashSet<SearchFilter> filterSet =
+ new LinkedHashSet<SearchFilter>();
+ filterSet.add(filter);
+ filterSet.add(urlFilter);
+ combinedFilter = SearchFilter.createANDFilter(filterSet);
+ }
+ }
+ else
+ {
+ if (urlFilters.contains(filter))
+ {
+ combinedFilter = filter;
+ }
+ else
+ {
+ LinkedHashSet<SearchFilter> filterSet =
+ new LinkedHashSet<SearchFilter>();
+ filterSet.add(filter);
+ filterSet.add(SearchFilter.createORFilter(urlFilters));
+ combinedFilter = SearchFilter.createANDFilter(filterSet);
+ }
+ }
+ }
+
+ searchMap.put(urlBaseDN, combinedFilter);
+ }
+
+
+ // At this point, we should have all the information we need to perform the
+ // searches. Create arrays of the elements for each.
+ DN[] baseDNArray = new DN[baseDNs.size()];
+ SearchFilter[] filterArray = new SearchFilter[baseDNArray.length];
+ LDAPURL[][] urlArray = new LDAPURL[baseDNArray.length][];
+ Iterator<DN> iterator = baseDNs.keySet().iterator();
+ for (int i=0; i < baseDNArray.length; i++)
+ {
+ baseDNArray[i] = iterator.next();
+ filterArray[i] = searchMap.get(baseDNArray[i]);
+
+ LinkedList<LDAPURL> urlList = baseDNs.get(baseDNArray[i]);
+ urlArray[i] = new LDAPURL[urlList.size()];
+ int j=0;
+ for (LDAPURL url : urlList)
+ {
+ urlArray[i][j++] = url;
+ }
+ }
+
+
+ DynamicGroupSearchThread searchThread =
+ new DynamicGroupSearchThread(this, baseDNArray, filterArray, urlArray);
+ searchThread.start();
+ }
+
+
+
+ /**
+ * Retrieves the DN of the dynamic group with which this dynamic group member
+ * list is associated.
+ *
+ * @return The DN of the dynamic group with which this dynamic group member
+ * list is associated.
+ */
+ public final DN getDynamicGroupDN()
+ {
+ return groupDN;
+ }
+
+
+
+ /**
+ * Indicates that all of the searches needed to iterate across the member list
+ * have completed and there will not be any more results provided.
+ */
+ final void setSearchesCompleted()
+ {
+ searchesCompleted = true;
+ }
+
+
+
+ /**
+ * Adds the provided entry to the set of results that should be returned for
+ * this member list.
+ *
+ * @param entry The entry to add to the set of results that should be
+ * returned for this member list.
+ *
+ * @return {@code true} if the entry was added to the result set, or
+ * {@code false} if it was not (either because a timeout expired or
+ * the attempt was interrupted). If this method returns
+ * {@code false}, then the search thread should terminate
+ * immediately.
+ */
+ final boolean addResult(Entry entry)
+ {
+ try
+ {
+ return resultQueue.offer(entry, 10, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException ie)
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Adds the provided membership exception so that it will be thrown along with
+ * the set of results for this member list.
+ *
+ * @param membershipException The membership exception to be thrown.
+ *
+ * @return {@code true} if the exception was added to the result set, or
+ * {@code false} if it was not (either because a timeout expired or
+ * the attempt was interrupted). If this method returns
+ * {@code false}, then the search thread should terminate
+ * immediately.
+ */
+ final boolean addResult(MembershipException membershipException)
+ {
+ try
+ {
+ return resultQueue.offer(membershipException, 10, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException ie)
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean hasMoreMembers()
+ {
+ while (! searchesCompleted)
+ {
+ if (resultQueue.peek() != null)
+ {
+ return true;
+ }
+
+ try
+ {
+ Thread.sleep(0, 1000);
+ } catch (Exception e) {}
+ }
+
+ return (resultQueue.peek() != null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public Entry nextMemberEntry()
+ throws MembershipException
+ {
+ if (! hasMoreMembers())
+ {
+ return null;
+ }
+
+ Object result = resultQueue.poll();
+ if (result == null)
+ {
+ close();
+ return null;
+ }
+ else if (result instanceof Entry)
+ {
+ return (Entry) result;
+ }
+ else if (result instanceof MembershipException)
+ {
+ MembershipException me = (MembershipException) result;
+ if (! me.continueIterating())
+ {
+ close();
+ }
+
+ throw me;
+ }
+
+ // We should never get here.
+ close();
+ return null;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void close()
+ {
+ searchesCompleted = true;
+ resultQueue.clear();
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupSearchThread.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupSearchThread.java
new file mode 100644
index 0000000..c780fd6
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/DynamicGroupSearchThread.java
@@ -0,0 +1,208 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.LinkedHashSet;
+
+import org.opends.server.api.DirectoryThread;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchListener;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.LDAPURL;
+import org.opends.server.types.MembershipException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchResultReference;
+import org.opends.server.types.SearchScope;
+
+import static org.opends.server.loggers.Error.*;
+import static org.opends.server.messages.ExtensionsMessages.*;
+import static org.opends.server.messages.MessageHandler.*;
+
+
+
+/**
+ * This class implements a Directory Server thread that will be used to perform
+ * a background search to retrieve all of the members of a dynamic group.
+ * <BR><BR>
+ */
+public class DynamicGroupSearchThread
+// FIXME -- Would it be better to implement this class using an Executor
+// rather than always creating a custom thread?
+ extends DirectoryThread
+ implements InternalSearchListener
+{
+ // The set of base DNs for the search requests.
+ private final DN[] baseDNs;
+
+ // The member list with which this search thread is associated.
+ private final DynamicGroupMemberList memberList;
+
+ // A counter used to keep track of which search is currently in progress.
+ private int searchCounter;
+
+ // The set of member URLs for determining whether entries match the criteria.
+ private final LDAPURL[][] memberURLs;
+
+ // The set of search filters for the search requests.
+ private final SearchFilter[] searchFilters;
+
+
+
+ /**
+ * Creates a new dynamic group search thread that is associated with the
+ * provided member list and that will perform the search using the provided
+ * information.
+ *
+ * @param memberList The dynamic group member list with which this thread is
+ * associated.
+ * @param baseDNs The set of base DNs to use for the search requests.
+ * @param filters The set of search filters to use for the search
+ * requests.
+ * @param memberURLs The set of member URLs to use when determining if
+ * entries match the necessary group criteria.
+ */
+ public DynamicGroupSearchThread(DynamicGroupMemberList memberList,
+ DN[] baseDNs, SearchFilter[] filters,
+ LDAPURL[][] memberURLs)
+ {
+ super("Dynamic Group Search Thread " + memberList.getDynamicGroupDN());
+
+ this.memberList = memberList;
+ this.baseDNs = baseDNs;
+ this.searchFilters = filters;
+ this.memberURLs = memberURLs;
+
+ searchCounter = 0;
+ }
+
+
+
+ /**
+ * Performs the set of searches and provides the results to the associated
+ * member list.
+ */
+ public void run()
+ {
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ LinkedHashSet<String> attributes = new LinkedHashSet<String>(0);
+
+ for (searchCounter = 0; searchCounter < baseDNs.length; searchCounter++)
+ {
+ InternalSearchOperation searchOperation =
+ conn.processSearch(baseDNs[searchCounter], SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0,
+ false, searchFilters[searchCounter], attributes,
+ this);
+
+ ResultCode resultCode = searchOperation.getResultCode();
+ if (resultCode != ResultCode.SUCCESS)
+ {
+ if (resultCode == ResultCode.NO_SUCH_OBJECT)
+ {
+ int msgID = MSGID_DYNAMICGROUP_NONEXISTENT_BASE_DN;
+ String message =
+ getMessage(msgID, String.valueOf(baseDNs[searchCounter]),
+ String.valueOf(memberList.getDynamicGroupDN()));
+ logError(ErrorLogCategory.EXTENSIONS, ErrorLogSeverity.MILD_WARNING,
+ message, msgID);
+ continue;
+ }
+ else
+ {
+ int msgID = MSGID_DYNAMICGROUP_INTERNAL_SEARCH_FAILED;
+ String message =
+ getMessage(msgID, String.valueOf(baseDNs[searchCounter]),
+ String.valueOf(searchFilters[searchCounter]),
+ String.valueOf(memberList.getDynamicGroupDN()),
+ String.valueOf(resultCode),
+ String.valueOf(searchOperation.getErrorMessage()));
+ if (! memberList.addResult(
+ new MembershipException(msgID, message, true)))
+ {
+ memberList.setSearchesCompleted();
+ return;
+ }
+ }
+ }
+ }
+
+ memberList.setSearchesCompleted();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleInternalSearchEntry(InternalSearchOperation searchOperation,
+ SearchResultEntry searchEntry)
+ throws DirectoryException
+ {
+ for (LDAPURL url : memberURLs[searchCounter])
+ {
+ if (url.matchesEntry(searchEntry))
+ {
+ if (! memberList.addResult(searchEntry))
+ {
+ int msgID = MSGID_DYNAMICGROUP_CANNOT_RETURN_ENTRY;
+ String message = getMessage(msgID,
+ String.valueOf(searchEntry.getDN()),
+ String.valueOf(memberList.getDynamicGroupDN()));
+ throw new DirectoryException(
+ DirectoryServer.getServerErrorResultCode(), message,
+ msgID);
+ }
+
+ return;
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleInternalSearchReference(
+ InternalSearchOperation searchOperation,
+ SearchResultReference searchReference)
+ {
+ // No implementation required.
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
index dbb9802..a89e972 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -4704,6 +4704,71 @@
/**
+ * The message ID for the message that will be used if an error occurs while
+ * attempting to decode a memberURL value as an LDAP URL. This takes three
+ * arguments, which are the value that could not be decoded, the DN of the
+ * entry containing the value, and a message explaining the problem that
+ * occurred.
+ */
+ public static final int MSGID_DYNAMICGROUP_CANNOT_DECODE_MEMBERURL =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 447;
+
+
+
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * use nesting in conjunction with a dynamic group. This does not take any
+ * arguments.
+ */
+ public static final int MSGID_DYNAMICGROUP_NESTING_NOT_SUPPORTED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 448;
+
+
+
+ /**
+ * The message ID for the message that will be used if an attempt is made to
+ * alter the set of members in a dynamic group. This does not take any
+ * arguments.
+ */
+ public static final int MSGID_DYNAMICGROUP_ALTERING_MEMBERS_NOT_SUPPORTED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 449;
+
+
+
+ /**
+ * The message ID for the message that will be used if a dynamic group
+ * includes a member URL with a base DN that doesn't exist. This takes two
+ * arguments, which are the base DN and the DN of the dynamic group entry.
+ */
+ public static final int MSGID_DYNAMICGROUP_NONEXISTENT_BASE_DN =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_WARNING | 450;
+
+
+
+ /**
+ * The message ID for the message that will be used if an error occurs while
+ * processing an internal search to determine dynamic group membership. This
+ * takes five arguments, which are the search base DN, the search filter,
+ * the DN of the dynamic group entry, the result code for the search, and the
+ * error message for the search.
+ */
+ public static final int MSGID_DYNAMICGROUP_INTERNAL_SEARCH_FAILED =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 451;
+
+
+
+ /**
+ * The message ID for the message that will be used if an error occurs while
+ * trying to return an entry for a user that matches a set of dynamic group
+ * criteria. This takes two arguments, which are the DN of the entry that
+ * could not be returned and the DN of the dynamic group entry.
+ */
+ public static final int MSGID_DYNAMICGROUP_CANNOT_RETURN_ENTRY =
+ CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 452;
+
+
+
+ /**
* Associates a set of generic messages with the message IDs defined in this
* class.
*/
@@ -6791,6 +6856,27 @@
registerMessage(MSGID_FCM_MULTIPLE_MATCHING_ENTRIES,
"The certificate with fingerprint %s could not be mapped " +
"to exactly one user. It maps to both %s and %s.");
+
+
+ registerMessage(MSGID_DYNAMICGROUP_CANNOT_DECODE_MEMBERURL,
+ "Unable to decode value \"%s\" in entry \"%s\" as an " +
+ "LDAP URL: %s.");
+ registerMessage(MSGID_DYNAMICGROUP_NESTING_NOT_SUPPORTED,
+ "Dynamic groups do not support nested groups.");
+ registerMessage(MSGID_DYNAMICGROUP_ALTERING_MEMBERS_NOT_SUPPORTED,
+ "Dynamic groups do not support explicitly altering their " +
+ "membership.");
+ registerMessage(MSGID_DYNAMICGROUP_NONEXISTENT_BASE_DN,
+ "Base DN %s specified in dynamic group %s does not exist " +
+ "in the server.");
+ registerMessage(MSGID_DYNAMICGROUP_INTERNAL_SEARCH_FAILED,
+ "An error occurred while attempting perform an internal " +
+ "search with base DN %s and filter %s to resolve the " +
+ "member list for dynamic group %s: result code %s, " +
+ "error message %s.");
+ registerMessage(MSGID_DYNAMICGROUP_CANNOT_RETURN_ENTRY,
+ "The server encountered a timeout while attempting to " +
+ "add user %s to the member list for dynamic group %s.");
}
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
index 9c64150..cfdf825 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
@@ -1324,9 +1324,14 @@
* entry is associated.
* @param searchEntry The search result entry to be sent to
* the client.
+ *
+ * @throws DirectoryException If a problem occurs while processing
+ * the entry and the search should be
+ * terminated.
*/
public void sendSearchEntry(SearchOperation searchOperation,
SearchResultEntry searchEntry)
+ throws DirectoryException
{
((InternalSearchOperation) searchOperation).
addSearchEntry(searchEntry);
@@ -1346,9 +1351,14 @@
* referrals, or <CODE>false</CODE> if the client cannot
* handle referrals and no more attempts should be made to
* send them for the associated search operation.
+ *
+ * @throws DirectoryException If a problem occurs while processing
+ * the entry and the search should be
+ * terminated.
*/
public boolean sendSearchReference(SearchOperation searchOperation,
SearchResultReference searchReference)
+ throws DirectoryException
{
((InternalSearchOperation)
searchOperation).addSearchReference(searchReference);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchListener.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchListener.java
index e0d2326..172a4e2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchListener.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchListener.java
@@ -28,6 +28,7 @@
+import org.opends.server.types.DirectoryException;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
@@ -49,10 +50,17 @@
* processed.
* @param searchEntry The matching search result entry to be
* processed.
+ *
+ * @throws DirectoryException If a problem occurred while handling
+ * the provided entry. Search
+ * processing will be terminated, and
+ * the search operation will result
+ * will be set based on this exception.
*/
public void handleInternalSearchEntry(
InternalSearchOperation searchOperation,
- SearchResultEntry searchEntry);
+ SearchResultEntry searchEntry)
+ throws DirectoryException;
@@ -64,9 +72,16 @@
* processed.
* @param searchReference The search result reference to be
* processed.
+ *
+ * @throws DirectoryException If a problem occurred while handling
+ * the provided entry. Search
+ * processing will be terminated, and
+ * the search operation will result
+ * will be set based on this exception.
*/
public void handleInternalSearchReference(
InternalSearchOperation searchOperation,
- SearchResultReference searchReference);
+ SearchResultReference searchReference)
+ throws DirectoryException;
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java
index 31dd8d6..d2fd9fa 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java
@@ -39,6 +39,7 @@
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
@@ -46,7 +47,6 @@
-
/**
* This class defines a subclass of the core search operation that is
* to be used for internal searches. The primary difference between
@@ -57,9 +57,6 @@
public class InternalSearchOperation
extends SearchOperation
{
-
-
-
// The internal search listener for this search, if one was
// provided.
private InternalSearchListener searchListener;
@@ -226,8 +223,13 @@
*
* @param searchEntry The search result entry returned for this
* search.
+ *
+ * @throws DirectoryException If a problem occurs while processing
+ * the provided entry and the search
+ * should be terminated.
*/
public void addSearchEntry(SearchResultEntry searchEntry)
+ throws DirectoryException
{
if (searchListener == null)
{
@@ -263,9 +265,14 @@
*
* @param searchReference The search result reference returned for
* this search.
+ *
+ * @throws DirectoryException If a problem occurs while processing
+ * the provided reference and the
+ * search should be terminated.
*/
public void addSearchReference(
SearchResultReference searchReference)
+ throws DirectoryException
{
if (searchListener == null)
{
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
index 96c4391..1784efb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
@@ -52,6 +52,7 @@
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.IntermediateResponse;
import org.opends.server.types.SearchResultEntry;
@@ -720,9 +721,14 @@
* @param searchOperation The search operation with which the entry is
* associated.
* @param searchEntry The search result entry to be sent to the client.
+ *
+ * @throws DirectoryException If a problem occurs while attempting to send
+ * the entry to the client and the search should
+ * be terminated.
*/
public void sendSearchEntry(SearchOperation searchOperation,
SearchResultEntry searchEntry)
+ throws DirectoryException
{
((InternalSearchOperation) searchOperation).addSearchEntry(searchEntry);
}
@@ -741,9 +747,14 @@
* <CODE>false</CODE> if the client cannot handle referrals and no
* more attempts should be made to send them for the associated
* search operation.
+ *
+ * @throws DirectoryException If a problem occurs while attempting to send
+ * the reference to the client and the search
+ * should be terminated.
*/
public boolean sendSearchReference(SearchOperation searchOperation,
SearchResultReference searchReference)
+ throws DirectoryException
{
((InternalSearchOperation)
searchOperation).addSearchReference(searchReference);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/LDAPURL.java b/opendj-sdk/opends/src/server/org/opends/server/types/LDAPURL.java
index 4ea0ff9..21a80ab 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/LDAPURL.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/LDAPURL.java
@@ -35,9 +35,7 @@
import org.opends.server.core.DirectoryServer;
-import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
-import static
- org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.UtilityMessages.*;
import static org.opends.server.util.StaticUtils.*;
@@ -53,9 +51,6 @@
*/
public class LDAPURL
{
-
-
-
/**
* The default scheme that will be used if none is provided.
*/
@@ -1307,6 +1302,34 @@
/**
+ * Indicates whether the provided entry matches the criteria defined
+ * in this LDAP URL.
+ *
+ * @param entry The entry for which to make the determination.
+ *
+ * @return {@code true} if the provided entry does match the
+ * criteria specified in this LDAP URL, or {@code false} if
+ * it does not.
+ *
+ * @throws DirectoryException If a problem occurs while attempting
+ * to make the determination.
+ */
+ public boolean matchesEntry(Entry entry)
+ throws DirectoryException
+ {
+ SearchScope scope = getScope();
+ if (scope == null)
+ {
+ scope = SearchScope.BASE_OBJECT;
+ }
+
+ return (entry.matchesBaseAndScope(getBaseDN(), scope) &&
+ getFilter().matchesEntry(entry));
+ }
+
+
+
+ /**
* Indicates whether the provided object is equal to this LDAP URL.
*
* @param o The object for which to make the determination.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java b/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java
index 0059332..b7048ba 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java
@@ -63,9 +63,6 @@
*/
public class SearchFilter
{
-
-
-
// The attribute type for this filter.
private final AttributeType attributeType;
@@ -174,7 +171,7 @@
*
* @return The constructed search filter.
*/
- public static SearchFilter createANDFilter(List<SearchFilter>
+ public static SearchFilter createANDFilter(Collection<SearchFilter>
filterComponents)
{
return new SearchFilter(FilterType.AND, filterComponents, null,
@@ -192,7 +189,7 @@
*
* @return The constructed search filter.
*/
- public static SearchFilter createORFilter(List<SearchFilter>
+ public static SearchFilter createORFilter(Collection<SearchFilter>
filterComponents)
{
return new SearchFilter(FilterType.OR, filterComponents, null,
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
index 4ccf23d..e5c3468 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -195,7 +195,7 @@
/**
- * The name of the standard "member" attribute type, formatted in all '
+ * The name of the standard "member" attribute type, formatted in all
* lowercase characters.
*/
public static final String ATTR_MEMBER = "member";
@@ -203,6 +203,22 @@
/**
+ * The name of the standard "memberURL" attribute type, formatted in camel
+ * case.
+ */
+ public static final String ATTR_MEMBER_URL = "memberURL";
+
+
+
+ /**
+ * The name of the standard "memberURL" attribute type, formatted in all
+ * lowercase characters.
+ */
+ public static final String ATTR_MEMBER_URL_LC = "memberurl";
+
+
+
+ /**
* The name of the monitor attribute that is used to hold a backend ID.
*/
public static final String ATTR_MONITOR_BACKEND_ID = "ds-backend-id";
@@ -632,6 +648,22 @@
/**
+ * The name of the standard "groupOfURLs" object class, formatted in camel
+ * case.
+ */
+ public static final String OC_GROUP_OF_URLS = "groupOfURLs";
+
+
+
+ /**
+ * The name of the standard "groupOfURLs" object class, formatted in all
+ * lowercase characters.
+ */
+ public static final String OC_GROUP_OF_URLS_LC = "groupofurls";
+
+
+
+ /**
* The request OID for the cancel extended operation.
*/
public static final String OID_CANCEL_REQUEST = "1.3.6.1.1.8";
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
index 5df00df..cc4a76d 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
@@ -28,6 +28,7 @@
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -40,11 +41,14 @@
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.extensions.DynamicGroup;
+import org.opends.server.extensions.StaticGroup;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.MemberList;
@@ -93,7 +97,18 @@
{
GroupManager groupManager = DirectoryServer.getGroupManager();
- assertTrue(groupManager.getGroupImplementations().iterator().hasNext());
+ LinkedHashSet<Class> groupClasses = new LinkedHashSet<Class>();
+ groupClasses.add(StaticGroup.class);
+ groupClasses.add(DynamicGroup.class);
+
+ for (Group g : groupManager.getGroupImplementations())
+ {
+ assertTrue(groupClasses.remove(g.getClass()),
+ "Group class " + g.getClass() + " isn't registered");
+ }
+
+ assertTrue(groupClasses.isEmpty(),
+ "Unexpected group class(es) registered: " + groupClasses);
}
@@ -170,6 +185,7 @@
Group groupInstance = groupManager.getGroupInstance(groupDN);
assertNotNull(groupInstance);
+ assertEquals(groupInstance.getGroupDN(), groupDN);
assertTrue(groupInstance.isMember(user1DN));
assertTrue(groupInstance.isMember(user2DN));
assertFalse(groupInstance.isMember(user3DN));
@@ -1081,5 +1097,597 @@
assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
assertNull(groupManager.getGroupInstance(groupDN));
}
+
+
+
+ /**
+ * Invokes general group API methods on a dynamic group.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testGenericDynamicGroupAPI()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+
+ GroupManager groupManager = DirectoryServer.getGroupManager();
+ groupManager.deregisterAllGroups();
+
+ TestCaseUtils.addEntries(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "",
+ "dn: ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: Groups",
+ "",
+ "dn: uid=user.1,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.1",
+ "givenName: User",
+ "sn: 1",
+ "cn: User 1",
+ "userPassword: password",
+ "",
+ "dn: uid=user.2,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.2",
+ "givenName: User",
+ "sn: 2",
+ "cn: User 2",
+ "userPassword: password",
+ "",
+ "dn: uid=user.3,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.3",
+ "givenName: User",
+ "sn: 3",
+ "cn: User 3",
+ "userPassword: password",
+ "",
+ "dn: cn=Test Group of URLs,ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: groupOfURLs",
+ "cn: Test Group of URLs",
+ "memberURL: ldap:///o=test??sub?(sn<=2)");
+
+ DN groupDN = DN.decode("cn=Test Group of URLs,ou=Groups,o=test");
+ DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
+ DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
+ DN user3DN = DN.decode("uid=user.3,ou=People,o=test");
+ DN bogusDN = DN.decode("uid=bogus,ou=People,o=test");
+
+ Group groupInstance = groupManager.getGroupInstance(groupDN);
+ assertNotNull(groupInstance);
+ assertEquals(groupInstance.getGroupDN(), groupDN);
+ assertTrue(groupInstance.isMember(user1DN));
+ assertTrue(groupInstance.isMember(user2DN));
+ assertFalse(groupInstance.isMember(user3DN));
+ assertFalse(groupInstance.isMember(bogusDN));
+
+ assertFalse(groupInstance.supportsNestedGroups());
+ assertTrue(groupInstance.getNestedGroupDNs().isEmpty());
+
+ try
+ {
+ groupInstance.addNestedGroup(DN.decode("uid=test,ou=People,o=test"));
+ throw new AssertionError("Expected addNestedGroup to fail but it " +
+ "didn't");
+ } catch (UnsupportedOperationException uoe) {}
+
+ try
+ {
+ groupInstance.removeNestedGroup(
+ DN.decode("uid=test,ou=People,o=test"));
+ throw new AssertionError("Expected removeNestedGroup to fail but " +
+ "it didn't");
+ } catch (UnsupportedOperationException uoe) {}
+
+
+ assertFalse(groupInstance.mayAlterMemberList());
+
+ try
+ {
+ Entry user3Entry = DirectoryServer.getEntry(user3DN);
+ groupInstance.addMember(user3Entry);
+ throw new AssertionError("Expected addMember to fail but it didn't");
+ } catch (UnsupportedOperationException uoe) {}
+
+ try
+ {
+ groupInstance.removeMember(user2DN);
+ throw new AssertionError("Expected removeMember to fail but it didn't");
+ } catch (UnsupportedOperationException uoe) {}
+
+ groupInstance.toString(new StringBuilder());
+
+
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation deleteOperation = conn.processDelete(groupDN);
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ assertNull(groupManager.getGroupInstance(groupDN));
+ }
+
+
+
+ /**
+ * Tests to ensure that an attempt to add a dynamic group with a malformed URL
+ * will cause it to be decoded as a group but any operations attempted with it
+ * will fail with an exception.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testDynamicGroupMalformedURL()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+
+ GroupManager groupManager = DirectoryServer.getGroupManager();
+ groupManager.deregisterAllGroups();
+
+ TestCaseUtils.addEntries(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "",
+ "dn: ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: Groups",
+ "",
+ "dn: uid=user.1,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.1",
+ "givenName: User",
+ "sn: 1",
+ "cn: User 1",
+ "userPassword: password",
+ "",
+ "dn: cn=Test Malformed URL,ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: groupOfURLs",
+ "cn: Test Malformed URL",
+ "memberURL: ldap:///o=test??sub?(malformed)");
+
+ DN groupDN = DN.decode("cn=Test Malformed URL,ou=Groups,o=test");
+
+ Group groupInstance = groupManager.getGroupInstance(groupDN);
+ assertNotNull(groupInstance);
+
+ DynamicGroup dynamicGroup = (DynamicGroup) groupInstance;
+ assertTrue(dynamicGroup.getMemberURLs().isEmpty());
+
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation deleteOperation = conn.processDelete(groupDN);
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ assertNull(groupManager.getGroupInstance(groupDN));
+ }
+
+
+
+ /**
+ * Tests the {@code getMembers()} method for a dynamic group, using the
+ * variant that doesn't take any arguments.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testGetMembersSimple()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+
+ GroupManager groupManager = DirectoryServer.getGroupManager();
+ groupManager.deregisterAllGroups();
+
+ TestCaseUtils.addEntries(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "",
+ "dn: ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: Groups",
+ "",
+ "dn: uid=user.1,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.1",
+ "givenName: User",
+ "sn: 1",
+ "cn: User 1",
+ "userPassword: password",
+ "",
+ "dn: uid=user.2,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.2",
+ "givenName: User",
+ "sn: 2",
+ "cn: User 2",
+ "userPassword: password",
+ "",
+ "dn: uid=user.3,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.3",
+ "givenName: User",
+ "sn: 3",
+ "cn: User 3",
+ "userPassword: password",
+ "",
+ "dn: cn=Test Group of URLs,ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: groupOfURLs",
+ "cn: Test Group of URLs",
+ "memberURL: ldap:///o=test??sub?(sn<=2)");
+
+ DN groupDN = DN.decode("cn=Test Group of URLs,ou=Groups,o=test");
+ DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
+ DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
+
+ Group groupInstance = groupManager.getGroupInstance(groupDN);
+ assertNotNull(groupInstance);
+
+
+ LinkedHashSet<DN> memberSet = new LinkedHashSet<DN>();
+ memberSet.add(user1DN);
+ memberSet.add(user2DN);
+
+ MemberList memberList = groupInstance.getMembers();
+ assertNotNull(memberList);
+ while (memberList.hasMoreMembers())
+ {
+ DN memberDN = memberList.nextMemberDN();
+ assertTrue(memberSet.remove(memberDN),
+ "Returned unexpected member " + memberDN.toString());
+ }
+ memberList.close();
+ assertTrue(memberSet.isEmpty(),
+ "Expected member set to be empty but it was not: " + memberSet);
+
+
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation deleteOperation = conn.processDelete(groupDN);
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ assertNull(groupManager.getGroupInstance(groupDN));
+ }
+
+
+
+ /**
+ * Tests the {@code getMembers()} method for a dynamic group, using the
+ * variant that takes base, scope, and filter arguments.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testGetMembersComplex()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+
+ GroupManager groupManager = DirectoryServer.getGroupManager();
+ groupManager.deregisterAllGroups();
+
+ TestCaseUtils.addEntries(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "",
+ "dn: ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: Groups",
+ "",
+ "dn: uid=user.1,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.1",
+ "givenName: User",
+ "sn: 1",
+ "cn: User 1",
+ "userPassword: password",
+ "",
+ "dn: uid=user.2,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.2",
+ "givenName: User",
+ "sn: 2",
+ "cn: User 2",
+ "userPassword: password",
+ "",
+ "dn: uid=user.3,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.3",
+ "givenName: User",
+ "sn: 3",
+ "cn: User 3",
+ "userPassword: password",
+ "",
+ "dn: cn=Test Group of URLs,ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: groupOfURLs",
+ "cn: Test Group of URLs",
+ "memberURL: ldap:///o=test??sub?(sn<=2)");
+
+ DN groupDN = DN.decode("cn=Test Group of URLs,ou=Groups,o=test");
+ DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
+ DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
+
+ Group groupInstance = groupManager.getGroupInstance(groupDN);
+ assertNotNull(groupInstance);
+
+
+ LinkedHashSet<DN> memberSet = new LinkedHashSet<DN>();
+ memberSet.add(user1DN);
+
+ MemberList memberList = groupInstance.getMembers(
+ DN.decode("ou=people,o=test"),
+ SearchScope.SINGLE_LEVEL,
+ SearchFilter.createFilterFromString("(sn=1)"));
+ assertNotNull(memberList);
+ while (memberList.hasMoreMembers())
+ {
+ DN memberDN = memberList.nextMemberDN();
+ assertTrue(memberSet.remove(memberDN),
+ "Returned unexpected member " + memberDN.toString());
+ }
+ memberList.close();
+ assertTrue(memberSet.isEmpty(),
+ "Expected member set to be empty but it was not: " + memberSet);
+
+
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation deleteOperation = conn.processDelete(groupDN);
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ assertNull(groupManager.getGroupInstance(groupDN));
+ }
+
+
+
+ /**
+ * Tests the {@code getMembers()} method for a dynamic group that contains
+ * multiple member URLs containing non-overlapping criteria.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testGetMembersMultipleDistinctURLs()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+
+ GroupManager groupManager = DirectoryServer.getGroupManager();
+ groupManager.deregisterAllGroups();
+
+ TestCaseUtils.addEntries(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "",
+ "dn: ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: Groups",
+ "",
+ "dn: uid=user.1,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.1",
+ "givenName: User",
+ "sn: 1",
+ "cn: User 1",
+ "userPassword: password",
+ "",
+ "dn: uid=user.2,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.2",
+ "givenName: User",
+ "sn: 2",
+ "cn: User 2",
+ "userPassword: password",
+ "",
+ "dn: uid=user.3,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.3",
+ "givenName: User",
+ "sn: 3",
+ "cn: User 3",
+ "userPassword: password",
+ "",
+ "dn: cn=Test Group of URLs,ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: groupOfURLs",
+ "cn: Test Group of URLs",
+ "memberURL: ldap:///o=test??sub?(sn=1)",
+ "memberURL: ldap:///o=test??sub?(sn=2)");
+
+ DN groupDN = DN.decode("cn=Test Group of URLs,ou=Groups,o=test");
+ DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
+ DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
+
+ Group groupInstance = groupManager.getGroupInstance(groupDN);
+ assertNotNull(groupInstance);
+ groupInstance.toString();
+
+
+ LinkedHashSet<DN> memberSet = new LinkedHashSet<DN>();
+ memberSet.add(user1DN);
+ memberSet.add(user2DN);
+
+ MemberList memberList = groupInstance.getMembers();
+ assertNotNull(memberList);
+ while (memberList.hasMoreMembers())
+ {
+ DN memberDN = memberList.nextMemberDN();
+ assertTrue(memberSet.remove(memberDN),
+ "Returned unexpected member " + memberDN.toString());
+ }
+ memberList.close();
+ assertTrue(memberSet.isEmpty(),
+ "Expected member set to be empty but it was not: " + memberSet);
+
+
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation deleteOperation = conn.processDelete(groupDN);
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ assertNull(groupManager.getGroupInstance(groupDN));
+ }
+
+
+
+ /**
+ * Tests the {@code getMembers()} method for a dynamic group that contains
+ * multiple member URLs containing overlapping criteria.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testGetMembersMultipleOverlappingURLs()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
+
+ GroupManager groupManager = DirectoryServer.getGroupManager();
+ groupManager.deregisterAllGroups();
+
+ TestCaseUtils.addEntries(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "",
+ "dn: ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: Groups",
+ "",
+ "dn: uid=user.1,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.1",
+ "givenName: User",
+ "sn: 1",
+ "cn: User 1",
+ "userPassword: password",
+ "",
+ "dn: uid=user.2,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.2",
+ "givenName: User",
+ "sn: 2",
+ "cn: User 2",
+ "userPassword: password",
+ "",
+ "dn: uid=user.3,ou=People,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: user.3",
+ "givenName: User",
+ "sn: 3",
+ "cn: User 3",
+ "userPassword: password",
+ "",
+ "dn: cn=Test Group of URLs,ou=Groups,o=test",
+ "objectClass: top",
+ "objectClass: groupOfURLs",
+ "cn: Test Group of URLs",
+ "memberURL: ldap:///dc=example,dc=com??sub?(cn=nonexistent)",
+ "memberURL: ldap:///uid=user.2,ou=People,o=test??sub?(sn=2)",
+ "memberURL: ldap:///o=test??sub?(sn=1)",
+ "memberURL: ldap:///ou=People,o=test??subordinate?(!(sn=3))");
+
+ DN groupDN = DN.decode("cn=Test Group of URLs,ou=Groups,o=test");
+ DN user1DN = DN.decode("uid=user.1,ou=People,o=test");
+ DN user2DN = DN.decode("uid=user.2,ou=People,o=test");
+
+ Group groupInstance = groupManager.getGroupInstance(groupDN);
+ assertNotNull(groupInstance);
+ groupInstance.toString();
+
+
+ LinkedHashSet<DN> memberSet = new LinkedHashSet<DN>();
+ memberSet.add(user1DN);
+ memberSet.add(user2DN);
+
+ MemberList memberList =
+ groupInstance.getMembers(DN.nullDN(), SearchScope.WHOLE_SUBTREE,
+ SearchFilter.createFilterFromString("(objectClass=*)"));
+ assertNotNull(memberList);
+ while (memberList.hasMoreMembers())
+ {
+ DN memberDN = memberList.nextMemberDN();
+ assertTrue(memberSet.remove(memberDN),
+ "Returned unexpected member " + memberDN.toString());
+ }
+ memberList.close();
+ assertTrue(memberSet.isEmpty(),
+ "Expected member set to be empty but it was not: " + memberSet);
+
+
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ DeleteOperation deleteOperation = conn.processDelete(groupDN);
+ assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+ assertNull(groupManager.getGroupInstance(groupDN));
+ }
}
--
Gitblit v1.10.0