mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
12.39.2007 80523e58ccb629f3e4bde4366641e9befba24a36
Implement support for virtual static groups, which are entries which appear to
be static groups but get their membership information from another group and
present it through a virtual attribute. This can make it possible to use a
dynamic group to actually define the set of membership, but still support
applications which can only interact with static groups.

OpenDS Issue Number: 425
3 files added
6 files modified
1853 ■■■■■ changed files
opends/resource/config/config.ldif 27 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/MemberVirtualAttributeProvider.java 349 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/StaticGroup.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/VirtualStaticGroup.java 484 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 96 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 17 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java 2 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/VirtualStaticGroupTestCase.java 859 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -441,6 +441,13 @@
ds-cfg-group-implementation-class: org.opends.server.extensions.StaticGroup
ds-cfg-group-implementation-enabled: true
dn: cn=Virtual Static,cn=Group Implementations,cn=config
objectClass: top
objectClass: ds-cfg-group-implementation
cn: Virtual Static
ds-cfg-group-implementation-class: org.opends.server.extensions.VirtualStaticGroup
ds-cfg-group-implementation-enabled: true
dn: cn=Identity Mappers,cn=config
objectClass: top
objectClass: ds-cfg-branch
@@ -1731,6 +1738,26 @@
ds-cfg-virtual-attribute-type: subschemaSubentry
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
dn: cn=Virtual Static member,cn=Virtual Attributes,cn=config
objectClass: top
objectClass: ds-cfg-virtual-attribute
cn: Virtual Static member
ds-cfg-virtual-attribute-class: org.opends.server.extensions.MemberVirtualAttributeProvider
ds-cfg-virtual-attribute-enabled: true
ds-cfg-virtual-attribute-type: member
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
ds-cfg-virtual-attribute-filter: (&(objectClass=groupOfNames)(objectClass=ds-virtual-static-group))
dn: cn=Virtual Static uniqueMember,cn=Virtual Attributes,cn=config
objectClass: top
objectClass: ds-cfg-virtual-attribute
cn: Virtual Static uniqueMember
ds-cfg-virtual-attribute-class: org.opends.server.extensions.MemberVirtualAttributeProvider
ds-cfg-virtual-attribute-enabled: true
ds-cfg-virtual-attribute-type: uniqueMember
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
ds-cfg-virtual-attribute-filter: (&(objectClass=groupOfUniqueNames)(objectClass=ds-virtual-static-group))
dn: cn=Work Queue,cn=config
objectClass: top
objectClass: ds-cfg-work-queue
opends/resource/schema/02-config.ldif
@@ -1150,6 +1150,9 @@
  NAME 'ds-task-rebuild-max-threads'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.343 NAME 'ds-target-group-dn'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1602,5 +1605,8 @@
  MUST ( ds-task-rebuild-base-dn $ ds-task-rebuild-index )
  MAY ( ds-task-rebuild-max-threads )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.99
  NAME 'ds-virtual-static-group' SUP top AUXILIARY MUST ds-target-group-dn
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/server/org/opends/server/extensions/MemberVirtualAttributeProvider.java
New file
@@ -0,0 +1,349 @@
/*
 * 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.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.Group;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.MemberList;
import org.opends.server.types.MembershipException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.VirtualAttributeRule;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class implements a virtual attribute provider that works in conjunction
 * with virtual static groups to generate the values for the member or
 * uniqueMember attribute.
 */
public class MemberVirtualAttributeProvider
       extends VirtualAttributeProvider<VirtualAttributeCfg>
{
  // The attribute type used to indicate which target group should be used to
  // obtain the member list.
  private AttributeType targetGroupType;
  /**
   * Creates a new instance of this member virtual attribute provider.
   */
  public MemberVirtualAttributeProvider()
  {
    super();
    // All initialization should be performed in the
    // initializeVirtualAttributeProvider method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeVirtualAttributeProvider(
                            VirtualAttributeCfg configuration)
         throws ConfigException, InitializationException
  {
    targetGroupType =
         DirectoryServer.getAttributeType(ATTR_TARGET_GROUP_DN, true);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMultiValued()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashSet<AttributeValue> getValues(Entry entry,
                                                 VirtualAttributeRule rule)
  {
    Group g = DirectoryServer.getGroupManager().getGroupInstance(entry.getDN());
    if (g == null)
    {
      return new LinkedHashSet<AttributeValue>(0);
    }
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    try
    {
      MemberList memberList = g.getMembers();
      while (memberList.hasMoreMembers())
      {
        try
        {
          DN memberDN = memberList.nextMemberDN();
          if (memberDN != null)
          {
            values.add(new AttributeValue(rule.getAttributeType(),
                                          memberDN.toString()));
          }
        }
        catch (MembershipException me)
        {
          if (! me.continueIterating())
          {
            break;
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    return values;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
  {
    Group g = DirectoryServer.getGroupManager().getGroupInstance(entry.getDN());
    if (g == null)
    {
      return false;
    }
    try
    {
      MemberList memberList = g.getMembers();
      while (memberList.hasMoreMembers())
      {
        try
        {
          DN memberDN = memberList.nextMemberDN();
          if (memberDN != null)
          {
            memberList.close();
            return true;
          }
        }
        catch (MembershipException me)
        {
          if (! me.continueIterating())
          {
            break;
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule,
                          AttributeValue value)
  {
    Group g = DirectoryServer.getGroupManager().getGroupInstance(entry.getDN());
    if (g == null)
    {
      return false;
    }
    try
    {
      return g.isMember(DN.decode(value.getValue()));
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasAnyValue(Entry entry, VirtualAttributeRule rule,
                             Collection<AttributeValue> values)
  {
    for (AttributeValue v : values)
    {
      if (hasValue(entry, rule, v))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult matchesSubstring(Entry entry,
                                          VirtualAttributeRule rule,
                                          ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    // DNs cannot be used in substring matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult greaterThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult lessThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult approximatelyEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in approximate matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}.  This virtual attribute will support search operations only
   * if one of the following is true about the search filter:
   * <UL>
   *   <LI>It is an equality filter targeting the associated attribute
   *       type.</LI>
   *   <LI>It is an AND filter in which at least one of the components is an
   *       equality filter targeting the associated attribute type.</LI>
   *   <LI>It is an OR filter in which all of the components are equality
   *       filters targeting the associated attribute type.</LI>
   * </UL>
   */
  @Override()
  public boolean isSearchable(VirtualAttributeRule rule,
                              SearchOperation searchOperation)
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSearch(VirtualAttributeRule rule,
                            SearchOperation searchOperation)
  {
    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
    return;
  }
}
opends/src/server/org/opends/server/extensions/StaticGroup.java
@@ -79,9 +79,6 @@
public class StaticGroup
       extends Group
{
  // The attribute type used to hold the membership list for this group.
  private AttributeType memberAttributeType;
@@ -241,7 +238,8 @@
    // FIXME -- This needs to exclude enhanced groups once we have support for
    // them.
    String filterString =
         "(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames))";
         "(&(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames))" +
            "(!(objectClass=ds-virtual-static-group))";
    return SearchFilter.createFilterFromString(filterString);
  }
@@ -257,6 +255,13 @@
    // FIXME -- This needs to exclude enhanced groups once we have support for
    //them.
    ObjectClass virtualStaticGroupClass =
         DirectoryConfig.getObjectClass(OC_VIRTUAL_STATIC_GROUP, true);
    if (entry.hasObjectClass(virtualStaticGroupClass))
    {
      return false;
    }
    ObjectClass groupOfNamesClass =
         DirectoryConfig.getObjectClass(OC_GROUP_OF_NAMES_LC, true);
    ObjectClass groupOfUniqueNamesClass =
opends/src/server/org/opends/server/extensions/VirtualStaticGroup.java
New file
@@ -0,0 +1,484 @@
/*
 * 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.List;
import org.opends.server.api.Group;
import org.opends.server.core.DirectoryServer;
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.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.MemberList;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ResultCode;
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.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 virtual static group implementation, in which
 * membership is based on membership of another group.
 */
public class VirtualStaticGroup
       extends Group
{
  // The DN of the entry that holds the definition for this group.
  private DN groupEntryDN;
  // The DN of the target group that will provide membership information.
  private DN targetGroupDN;
  /**
   * Creates a new, uninitialized virtual static group instance.  This is
   * intended for internal use only.
   */
  public VirtualStaticGroup()
  {
    super();
    // No initialization is required here.
  }
  /**
   * Creates a new virtual static 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  targetGroupDN  The DN of the target group that will provide
   *                        membership information.  It must not be
   *                        {@code null}.
   */
  public VirtualStaticGroup(DN groupEntryDN, DN targetGroupDN)
  {
    super();
    ensureNotNull(groupEntryDN, targetGroupDN);
    this.groupEntryDN  = groupEntryDN;
    this.targetGroupDN = targetGroupDN;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeGroupImplementation(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  {
    // No additional initialization is required.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public VirtualStaticGroup newInstance(Entry groupEntry)
         throws DirectoryException
  {
    ensureNotNull(groupEntry);
    // Get the target group DN attribute from the entry, if there is one.
    DN targetDN = null;
    AttributeType targetType =
         DirectoryServer.getAttributeType(ATTR_TARGET_GROUP_DN, true);
    List<Attribute> attrList = groupEntry.getAttribute(targetType);
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        for (AttributeValue v : a.getValues())
        {
          if (targetDN != null)
          {
            int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_MULTIPLE_TARGETS;
            String message = getMessage(msgID,
                                        String.valueOf(groupEntry.getDN()));
            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
                                         message, msgID);
          }
          try
          {
            targetDN = DN.decode(v.getValue());
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              debugCaught(DebugLogLevel.ERROR, de);
            }
            int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_CANNOT_DECODE_TARGET;
            String message = getMessage(msgID, v.getStringValue(),
                                        String.valueOf(groupEntry.getDN()),
                                        de.getErrorMessage());
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                         message, msgID, de);
          }
        }
      }
    }
    if (targetDN == null)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET;
      String message = getMessage(msgID, String.valueOf(groupEntry.getDN()));
      throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message,
                                   msgID);
    }
    return new VirtualStaticGroup(groupEntry.getDN(), targetDN);
  }
  /**
   * {@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_VIRTUAL_STATIC_GROUP + ")");
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isGroupDefinition(Entry entry)
  {
    ensureNotNull(entry);
    // FIXME -- This needs to exclude enhanced groups once we have support for
    //them.
    ObjectClass virtualStaticGroupClass =
         DirectoryServer.getObjectClass(OC_VIRTUAL_STATIC_GROUP, true);
    return entry.hasObjectClass(virtualStaticGroupClass);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public DN getGroupDN()
  {
    return groupEntryDN;
  }
  /**
   * Retrieves the DN of the target group for this virtual static group.
   *
   * @return  The DN of the target group for this virtual static group.
   */
  public DN getTargetGroupDN()
  {
    return targetGroupDN;
  }
  /**
   * {@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_VIRTUAL_STATIC_GROUP_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_VIRTUAL_STATIC_GROUP_NESTING_NOT_SUPPORTED;
    String message = getMessage(msgID);
    throw new UnsupportedOperationException(message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMember(DN userDN)
         throws DirectoryException
  {
    Group targetGroup =
         DirectoryServer.getGroupManager().getGroupInstance(targetGroupDN);
    if (targetGroup == null)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET_GROUP;
      String message = getMessage(msgID, String.valueOf(targetGroupDN),
                                  String.valueOf(groupEntryDN));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, msgID);
    }
    else if (targetGroup instanceof VirtualStaticGroup)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_TARGET_CANNOT_BE_VIRTUAL;
      String message = getMessage(msgID, String.valueOf(groupEntryDN),
                                  String.valueOf(targetGroupDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
                                   msgID);
    }
    else
    {
      return targetGroup.isMember(userDN);
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMember(Entry userEntry)
         throws DirectoryException
  {
    Group targetGroup =
         DirectoryServer.getGroupManager().getGroupInstance(targetGroupDN);
    if (targetGroup == null)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET_GROUP;
      String message = getMessage(msgID, String.valueOf(targetGroupDN),
                                  String.valueOf(groupEntryDN));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, msgID);
    }
    else if (targetGroup instanceof VirtualStaticGroup)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_TARGET_CANNOT_BE_VIRTUAL;
      String message = getMessage(msgID, String.valueOf(groupEntryDN),
                                  String.valueOf(targetGroupDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
                                   msgID);
    }
    else
    {
      return targetGroup.isMember(userEntry);
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public MemberList getMembers()
         throws DirectoryException
  {
    Group targetGroup =
         DirectoryServer.getGroupManager().getGroupInstance(targetGroupDN);
    if (targetGroup == null)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET_GROUP;
      String message = getMessage(msgID, String.valueOf(targetGroupDN),
                                  String.valueOf(groupEntryDN));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, msgID);
    }
    else if (targetGroup instanceof VirtualStaticGroup)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_TARGET_CANNOT_BE_VIRTUAL;
      String message = getMessage(msgID, String.valueOf(groupEntryDN),
                                  String.valueOf(targetGroupDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
                                   msgID);
    }
    else
    {
      return targetGroup.getMembers();
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public MemberList getMembers(DN baseDN, SearchScope scope,
                               SearchFilter filter)
         throws DirectoryException
  {
    Group targetGroup =
         DirectoryServer.getGroupManager().getGroupInstance(targetGroupDN);
    if (targetGroup == null)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET_GROUP;
      String message = getMessage(msgID, String.valueOf(targetGroupDN),
                                  String.valueOf(groupEntryDN));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, msgID);
    }
    else if (targetGroup instanceof VirtualStaticGroup)
    {
      int    msgID   = MSGID_VIRTUAL_STATIC_GROUP_TARGET_CANNOT_BE_VIRTUAL;
      String message = getMessage(msgID, String.valueOf(groupEntryDN),
                                  String.valueOf(targetGroupDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
                                   msgID);
    }
    else
    {
      return targetGroup.getMembers(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_VIRTUAL_STATIC_GROUP_ALTERING_MEMBERS_NOT_SUPPORTED;
    String message = getMessage(msgID, String.valueOf(groupEntryDN));
    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_VIRTUAL_STATIC_GROUP_ALTERING_MEMBERS_NOT_SUPPORTED;
    String message = getMessage(msgID, String.valueOf(groupEntryDN));
    throw new UnsupportedOperationException(message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void toString(StringBuilder buffer)
  {
    buffer.append("VirtualStaticGroup(dn=");
    buffer.append(groupEntryDN);
    buffer.append(",targetGroupDN=");
    buffer.append(targetGroupDN);
    buffer.append(")");
  }
}
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -4941,6 +4941,78 @@
  /**
   * The message ID for the message that will be used if the virtual static
   * group has multiple targets.  This takes a single argument, which is the DN
   * of the group.
   */
  public static final int MSGID_VIRTUAL_STATIC_GROUP_MULTIPLE_TARGETS =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 470;
  /**
   * The message ID for the message that will be used if the virtual static
   * group has a target that can't be decoded as a DN.  This takes three
   * arguments, which are the target group value, the group DN, and a message
   * explaining the problem that occurred.
   */
  public static final int MSGID_VIRTUAL_STATIC_GROUP_CANNOT_DECODE_TARGET =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 471;
  /**
   * The message ID for the message that will be used if the virtual static
   * group does not have a target group DN.  This takes a single argument, which
   * is the DN of the group.
   */
  public static final int MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 472;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * nest a virtual static group.  This takes a single argument, which is the
   * DN of the group.
   */
  public static final int MSGID_VIRTUAL_STATIC_GROUP_NESTING_NOT_SUPPORTED =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 473;
  /**
   * The message ID for the message that will be used if the target group does
   * not exist.  This takes two arguments, which is the target group DN and the
   * virtual static group DN.
   */
  public static final int MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET_GROUP =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 474;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * alter the membership for a virtual static group.  This takes a single
   * argument, which is the DN of the group.
   */
  public static final int
       MSGID_VIRTUAL_STATIC_GROUP_ALTERING_MEMBERS_NOT_SUPPORTED =
            CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 475;
  /**
   * The message ID for the message that will be used if a virtual static group
   * target is also a virtual static group.  This takes two arguments, which are
   * the object group DN and the target group DN.
   */
  public static final int MSGID_VIRTUAL_STATIC_GROUP_TARGET_CANNOT_BE_VIRTUAL=
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 476;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -7109,6 +7181,30 @@
                    "The provided character set definition '%s' is invalid " +
                    "because it contains character '%s' which has already " +
                    "been used.");
    registerMessage(MSGID_VIRTUAL_STATIC_GROUP_MULTIPLE_TARGETS,
                    "The virtual static group defined in entry %s contains " +
                    "multiple target group DNs, but only one is allowed.");
    registerMessage(MSGID_VIRTUAL_STATIC_GROUP_CANNOT_DECODE_TARGET,
                    "Unable to decode \"%s\" as the target DN for group %s:  " +
                    "%s.");
    registerMessage(MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET,
                    "The virtual static group defined in entry %s does not " +
                    "contain a target group definition.");
    registerMessage(MSGID_VIRTUAL_STATIC_GROUP_NESTING_NOT_SUPPORTED,
                    "Virtual static groups do not support nesting.");
    registerMessage(MSGID_VIRTUAL_STATIC_GROUP_NO_TARGET_GROUP,
                    "Target group %s referenced by virtual static group %s " +
                    "does not exist.");
    registerMessage(MSGID_VIRTUAL_STATIC_GROUP_ALTERING_MEMBERS_NOT_SUPPORTED,
                    "Altering membership for virtual static group %s is not " +
                    "allowed.");
    registerMessage(MSGID_VIRTUAL_STATIC_GROUP_TARGET_CANNOT_BE_VIRTUAL,
                    "Virtual static group %s references target group %s " +
                    "which is itself a virtual static group.  One " +
                    "virtual static group is not allowed to reference " +
                    "another as its target group.");
  }
}
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -490,6 +490,14 @@
  /**
   * The name of the attribute that is used to specify the DN of the target
   * group for a virtual static group.
   */
  public static final String ATTR_TARGET_GROUP_DN = "ds-target-group-dn";
  /**
   * The name of the attribute that is used to specify the total number of
   * connections established since startup, formatted in camel case.
   */
@@ -824,6 +832,15 @@
  /**
   * The name of the ds-virtual-static-group objectclass in all lowercase
   * characters.
   */
  public static final String OC_VIRTUAL_STATIC_GROUP =
       "ds-virtual-static-group";
  /**
   * The English name for the basic disabled log severity used for all
   * log severities.
   */
opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
@@ -43,6 +43,7 @@
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.extensions.DynamicGroup;
import org.opends.server.extensions.StaticGroup;
import org.opends.server.extensions.VirtualStaticGroup;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Attribute;
@@ -100,6 +101,7 @@
    LinkedHashSet<Class> groupClasses = new LinkedHashSet<Class>();
    groupClasses.add(StaticGroup.class);
    groupClasses.add(DynamicGroup.class);
    groupClasses.add(VirtualStaticGroup.class);
    for (Group g : groupManager.getGroupImplementations())
    {
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/VirtualStaticGroupTestCase.java
New file
@@ -0,0 +1,859 @@
/*
 * 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.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.GroupManager;
import org.opends.server.core.ModifyOperation;
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.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConditionResult;
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;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.VirtualAttributeRule;
import static org.testng.Assert.*;
/**
 * A set of test cases for the virtual static group implementation and the
 * member virtual attribute provider.
 */
public class VirtualStaticGroupTestCase
       extends ExtensionsTestCase
{
  /**
   * The lines comprising the LDIF test data.
   */
  private static final String[] LDIF_LINES =
  {
    "dn: ou=People,o=test",
    "objectClass: top",
    "objectClass: organizationalUnit",
    "ou: People",
    "",
    "dn: uid=test.1,ou=People,o=test",
    "objectClass: top",
    "objectClass: person",
    "objectClass: organizationalPerson",
    "objectClass: inetOrgPerson",
    "uid: test.1",
    "givenName: Test",
    "sn: 1",
    "cn: Test 1",
    "userPassword: password",
    "",
    "dn: uid=test.2,ou=People,o=test",
    "objectClass: top",
    "objectClass: person",
    "objectClass: organizationalPerson",
    "objectClass: inetOrgPerson",
    "uid: test.2",
    "givenName: Test",
    "sn: 2",
    "cn: Test 2",
    "userPassword: password",
    "",
    "dn: uid=test.3,ou=People,o=test",
    "objectClass: top",
    "objectClass: person",
    "objectClass: organizationalPerson",
    "objectClass: inetOrgPerson",
    "uid: test.3",
    "givenName: Test",
    "sn: 3",
    "cn: Test 3",
    "userPassword: password",
    "",
    "dn: uid=test.4,ou=People,o=test",
    "objectClass: top",
    "objectClass: person",
    "objectClass: organizationalPerson",
    "objectClass: inetOrgPerson",
    "uid: test.4",
    "givenName: Test",
    "sn: 4",
    "cn: Test 4",
    "userPassword: password",
    "",
    "dn: ou=Groups,o=test",
    "objectClass: top",
    "objectClass: organizationalUnit",
    "ou: Groups",
    "",
    "dn: cn=Dynamic All Users,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfURLs",
    "cn: Dynamic All Users",
    "memberURL: ldap:///ou=People,o=test??sub?(objectClass=person)",
    "",
    "dn: cn=Dynamic One User,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfURLs",
    "cn: Dynamic One User",
    "memberURL: ldap:///ou=People,o=test??sub?(&(objectClass=person)(sn=4))",
    "",
    "dn: cn=Static member List,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfNames",
    "cn: Static member List",
    "member: uid=test.1,ou=People,o=test",
    "member: uid=test.3,ou=People,o=test",
    "",
    "dn: cn=Static uniqueMember List,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfUniqueNames",
    "cn: Static uniqueMember List",
    "uniqueMember: uid=test.2,ou=People,o=test",
    "uniqueMember: uid=test.3,ou=People,o=test",
    "uniqueMember: uid=no-such-user,ou=People,o=test",
    "",
    "dn: cn=Virtual member All Users,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfNames",
    "objectClass: ds-virtual-static-group",
    "cn: Virtual member All Users",
    "ds-target-group-dn: cn=Dynamic All Users,ou=Groups,o=test",
    "",
    "dn: cn=Virtual uniqueMember All Users,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfUniqueNames",
    "objectClass: ds-virtual-static-group",
    "cn: Virtual uniqueMember All Users",
    "ds-target-group-dn: cn=Dynamic All Users,ou=Groups,o=test",
    "",
    "dn: cn=Virtual member One User,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfNames",
    "objectClass: ds-virtual-static-group",
    "cn: Virtual member One User",
    "ds-target-group-dn: cn=Dynamic One User,ou=Groups,o=test",
    "",
    "dn: cn=Virtual uniqueMember One User,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfUniqueNames",
    "objectClass: ds-virtual-static-group",
    "cn: Virtual uniqueMember One User",
    "ds-target-group-dn: cn=Dynamic One User,ou=Groups,o=test",
    "",
    "dn: cn=Virtual Static member List,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfNames",
    "objectClass: ds-virtual-static-group",
    "cn: Virtual Static member List",
    "ds-target-group-dn: cn=Static member List,ou=Groups,o=test",
    "",
    "dn: cn=Virtual Static uniqueMember List,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfUniqueNames",
    "objectClass: ds-virtual-static-group",
    "cn: Virtual Static uniqueMember List",
    "ds-target-group-dn: cn=Static uniqueMember List,ou=Groups,o=test",
    "",
    "dn: cn=Crossover member Static Group,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfUniqueNames",
    "objectClass: ds-virtual-static-group",
    "cn: Crossover member Static Group",
    "ds-target-group-dn: cn=Static member List,ou=Groups,o=test",
    "",
    "dn: cn=Crossover uniqueMember Static Group,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfNames",
    "objectClass: ds-virtual-static-group",
    "cn: Crossover uniqueMember Static Group",
    "ds-target-group-dn: cn=Static uniqueMember List,ou=Groups,o=test",
    "",
    "dn: cn=Virtual Nonexistent,ou=Groups,o=test",
    "objectClass: top",
    "objectClass: groupOfNames",
    "objectClass: ds-virtual-static-group",
    "cn: Virtual Nonexistent",
    "ds-target-group-dn: cn=Nonexistent,ou=Groups,o=test"
  };
  // The attribute type for the member attribute.
  private AttributeType memberType;
  // The attribute type for the uniqueMember attribute.
  private AttributeType uniqueMemberType;
  // The server group manager.
  private GroupManager groupManager;
  // The DNs of the various entries in the data set.
  private DN u1;
  private DN u2;
  private DN u3;
  private DN u4;
  private DN da;
  private DN d1;
  private DN sm;
  private DN su;
  private DN vmda;
  private DN vuda;
  private DN vmd1;
  private DN vud1;
  private DN vsm;
  private DN vsu;
  private DN vcm;
  private DN vcu;
  private DN vn;
  private DN ne;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    memberType = DirectoryServer.getAttributeType("member", false);
    assertNotNull(memberType);
    uniqueMemberType = DirectoryServer.getAttributeType("uniquemember", false);
    assertNotNull(uniqueMemberType);
    groupManager = DirectoryServer.getGroupManager();
    u1 = DN.decode("uid=test.1,ou=People,o=test");
    u2 = DN.decode("uid=test.2,ou=People,o=test");
    u3 = DN.decode("uid=test.3,ou=People,o=test");
    u4 = DN.decode("uid=test.4,ou=People,o=test");
    da = DN.decode("cn=Dynamic All Users,ou=Groups,o=test");
    d1 = DN.decode("cn=Dynamic One User,ou=Groups,o=test");
    sm = DN.decode("cn=Static member List,ou=Groups,o=test");
    su = DN.decode("cn=Static uniqueMember List,ou=Groups,o=test");
    vmda = DN.decode("cn=Virtual member All Users,ou=Groups,o=test");
    vuda = DN.decode("cn=Virtual uniqueMember All Users,ou=Groups,o=test");
    vmd1 = DN.decode("cn=Virtual member One User,ou=Groups,o=test");
    vud1 = DN.decode("cn=Virtual uniqueMember One User,ou=Groups,o=test");
    vsm = DN.decode("cn=Virtual Static member List,ou=Groups,o=test");
    vsu = DN.decode("cn=Virtual Static uniqueMember List,ou=Groups,o=test");
    vcm = DN.decode("cn=Crossover member Static Group,ou=Groups,o=test");
    vcu = DN.decode("cn=Crossover uniqueMember Static Group,ou=Groups,o=test");
    vn = DN.decode("cn=Virtual Nonexistent,ou=Groups,o=test");
    ne = DN.decode("cn=Nonexistent,ou=Groups,o=test");
  }
  /**
   * Tests creating a new instance of a virtual static group from a valid entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testCreateValidGroup()
         throws Exception
  {
    Entry entry = TestCaseUtils.makeEntry(
      "dn: cn=Valid Virtual Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "objectClass: ds-virtual-static-group",
      "cn: Valid Virtual Static Group",
      "ds-target-group-dn: cn=Static member List,ou=Groups,o=test");
    VirtualStaticGroup groupImplementation = new VirtualStaticGroup();
    VirtualStaticGroup groupInstance = groupImplementation.newInstance(entry);
    assertNotNull(groupInstance);
    groupImplementation.finalizeGroupImplementation();
  }
  /**
   * Retrieves a set of invalid vittual static group definition entries.
   *
   * @return  A set of invalid virtul static group definition entries.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "invalidGroups")
  public Object[][] getInvalidGroupDefinitions()
         throws Exception
  {
    List<Entry> groupEntries = TestCaseUtils.makeEntries(
      "dn: cn=Not a Virtual Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Not a Virtual Static Group",
      "member: uid=test.1,ou=People,o=test",
      "",
      "dn: cn=No Target,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "objectClass: ds-virtual-static-group",
      "cn: No Target",
      "",
      "dn: cn=Invalid Target,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "objectClass: ds-virtual-static-group",
      "cn: Invalid Target",
      "ds-target-group-dn: invalid");
    Object[][] entryArray = new Object[groupEntries.size()][1];
    for (int i=0; i < entryArray.length; i++)
    {
      entryArray[i][0] = groupEntries.get(i);
    }
    return entryArray;
  }
  /**
   * Tests creating a new instance of a virtual static group from an invalid
   * entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "invalidGroups",
        expectedExceptions = { DirectoryException.class })
  public void testCreateInvalidGroup(Entry entry)
         throws Exception
  {
    VirtualStaticGroup groupImplementation = new VirtualStaticGroup();
    try
    {
      VirtualStaticGroup groupInstance = groupImplementation.newInstance(entry);
    }
    finally
    {
      groupImplementation.finalizeGroupImplementation();
    }
  }
  /**
   * Performs general tests of the group API for virtual static groups with a
   * group that has a real target group.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testGroupAPI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    VirtualStaticGroup g =
         (VirtualStaticGroup) groupManager.getGroupInstance(vmda);
    assertNotNull(g);
    assertTrue(g.isMember(u1));
    assertNotNull(g.getGroupDefinitionFilter());
    assertEquals(g.getGroupDN(), vmda);
    assertEquals(g.getTargetGroupDN(), da);
    assertFalse(g.supportsNestedGroups());
    assertTrue(g.getNestedGroupDNs().isEmpty());
    assertFalse(g.mayAlterMemberList());
    Entry entry = DirectoryServer.getEntry(u1);
    assertTrue(g.isMember(entry));
    MemberList memberList = g.getMembers();
    assertTrue(memberList.hasMoreMembers());
    assertNotNull(memberList.nextMemberDN());
    assertNotNull(memberList.nextMemberEntry());
    assertNotNull(memberList.nextMemberDN());
    assertNotNull(memberList.nextMemberDN());
    assertFalse(memberList.hasMoreMembers());
    SearchFilter filter = SearchFilter.createFilterFromString("(sn=1)");
    memberList = g.getMembers(DN.decode("o=test"), SearchScope.WHOLE_SUBTREE,
                              filter);
    assertTrue(memberList.hasMoreMembers());
    assertNotNull(memberList.nextMemberDN());
    assertFalse(memberList.hasMoreMembers());
    try
    {
      g.addNestedGroup(d1);
      fail("Expected an exception from addNestedGroupDN");
    } catch (Exception e) {}
    try
    {
      g.removeNestedGroup(d1);
      fail("Expected an exception from removeNestedGroupDN");
    } catch (Exception e) {}
    try
    {
      g.addMember(entry);
      fail("Expected an exception from addMember");
    } catch (Exception e) {}
    try
    {
      g.removeMember(u1);
      fail("Expected an exception from removeMember");
    } catch (Exception e) {}
    assertNotNull(g.toString());
    cleanUp();
  }
  /**
   * Performs general tests of the group API for virtual static groups with a
   * group that has a nonexistent target group.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testGroupAPINonexistent()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    VirtualStaticGroup g =
         (VirtualStaticGroup) groupManager.getGroupInstance(vn);
    assertNotNull(g);
    assertNotNull(g.getGroupDefinitionFilter());
    assertEquals(g.getGroupDN(), vn);
    assertEquals(g.getTargetGroupDN(), ne);
    assertFalse(g.supportsNestedGroups());
    assertTrue(g.getNestedGroupDNs().isEmpty());
    assertFalse(g.mayAlterMemberList());
    Entry entry = DirectoryServer.getEntry(u1);
    try
    {
      g.isMember(u1);
      fail("Expected an exception from isMember(DN)");
    } catch (Exception e) {}
    try
    {
      g.isMember(entry);
      fail("Expected an exception from isMember(Entry)");
    } catch (Exception e) {}
    try
    {
      g.getMembers();
      fail("Expected an exception from getMembers()");
    } catch (Exception e) {}
    try
    {
      SearchFilter filter = SearchFilter.createFilterFromString("(sn=1)");
      g.getMembers(DN.decode("o=test"), SearchScope.WHOLE_SUBTREE, filter);
      fail("Expected an exception from getMembers(base, scope, filter)");
    } catch (Exception e) {}
    try
    {
      g.addNestedGroup(d1);
      fail("Expected an exception from addNestedGroupDN");
    } catch (Exception e) {}
    try
    {
      g.removeNestedGroup(d1);
      fail("Expected an exception from removeNestedGroupDN");
    } catch (Exception e) {}
    try
    {
      g.addMember(entry);
      fail("Expected an exception from addMember");
    } catch (Exception e) {}
    try
    {
      g.removeMember(u1);
      fail("Expected an exception from removeMember");
    } catch (Exception e) {}
    assertNotNull(g.toString());
    cleanUp();
  }
  /**
   * Tests the behavior of the virtual static group with a dynamic group.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVirtualGroupDynamicGroupWithMember()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    VirtualStaticGroup g =
         (VirtualStaticGroup) groupManager.getGroupInstance(vmda);
    assertNotNull(g);
    assertTrue(g.isMember(u1));
    assertTrue(g.isMember(u2));
    assertTrue(g.isMember(u3));
    assertTrue(g.isMember(u4));
    cleanUp();
  }
  /**
   * Tests the behavior of the virtual static group with a static group based on
   * the member attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVirtualGroupStaticGroupWithMember()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    VirtualStaticGroup g =
         (VirtualStaticGroup) groupManager.getGroupInstance(vsm);
    assertNotNull(g);
    assertTrue(g.isMember(u1));
    assertFalse(g.isMember(u2));
    assertTrue(g.isMember(u3));
    assertFalse(g.isMember(u4));
    cleanUp();
  }
  /**
   * Tests the behavior of the virtual static group with a static group based on
   * the uniqueMember attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVirtualGroupStaticGroupWithUniqueMember()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    VirtualStaticGroup g =
         (VirtualStaticGroup) groupManager.getGroupInstance(vsu);
    assertNotNull(g);
    assertFalse(g.isMember(u1));
    assertTrue(g.isMember(u2));
    assertTrue(g.isMember(u3));
    assertFalse(g.isMember(u4));
    cleanUp();
  }
  /**
   * Performs general tests of the virtual attribute provider API for the member
   * virtual attribute with a target group that exists.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVirtualAttributeAPI()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    VirtualAttributeRule rule = null;
    for (VirtualAttributeRule r : DirectoryServer.getVirtualAttributes())
    {
      if (r.getAttributeType().equals(memberType))
      {
        rule = r;
        break;
      }
    }
    assertNotNull(rule);
    MemberVirtualAttributeProvider provider =
         (MemberVirtualAttributeProvider) rule.getProvider();
    assertNotNull(provider);
    Entry entry = DirectoryServer.getEntry(vsm);
    assertNotNull(entry);
    assertTrue(provider.isMultiValued());
    LinkedHashSet<AttributeValue> values = provider.getValues(entry, rule);
    assertNotNull(values);
    assertFalse(values.isEmpty());
    assertTrue(provider.hasValue(entry, rule));
    assertTrue(provider.hasValue(entry, rule,
                    new AttributeValue(memberType, u1.toString())));
    assertFalse(provider.hasValue(entry, rule,
                    new AttributeValue(memberType, ne.toString())));
    assertTrue(provider.hasAnyValue(entry, rule, values));
    assertFalse(provider.hasAnyValue(entry, rule,
                                     Collections.<AttributeValue>emptySet()));
    assertEquals(provider.matchesSubstring(entry, rule, null, null, null),
                 ConditionResult.UNDEFINED);
    assertEquals(provider.greaterThanOrEqualTo(entry, rule, null),
                 ConditionResult.UNDEFINED);
    assertEquals(provider.lessThanOrEqualTo(entry, rule, null),
                 ConditionResult.UNDEFINED);
    assertEquals(provider.approximatelyEqualTo(entry, rule, null),
                 ConditionResult.UNDEFINED);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), null, DN.decode("o=test"),
                  SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString(
                       "(member=" + u1.toString() + ")"),
                  null, null);
    assertFalse(provider.isSearchable(rule, searchOperation));
    provider.processSearch(rule, searchOperation);
    assertFalse(searchOperation.getResultCode() == ResultCode.SUCCESS);
    cleanUp();
  }
  /**
   * Performs general tests of the virtual attribute provider API for the member
   * virtual attribute with a target group that does not exist.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVirtualAttributeAPINonexistent()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    VirtualAttributeRule rule = null;
    for (VirtualAttributeRule r : DirectoryServer.getVirtualAttributes())
    {
      if (r.getAttributeType().equals(memberType))
      {
        rule = r;
        break;
      }
    }
    assertNotNull(rule);
    MemberVirtualAttributeProvider provider =
         (MemberVirtualAttributeProvider) rule.getProvider();
    assertNotNull(provider);
    Entry entry = DirectoryServer.getEntry(vn);
    assertNotNull(entry);
    assertTrue(provider.isMultiValued());
    LinkedHashSet<AttributeValue> values = provider.getValues(entry, rule);
    assertNotNull(values);
    assertTrue(values.isEmpty());
    assertFalse(provider.hasValue(entry, rule));
    assertFalse(provider.hasValue(entry, rule,
                    new AttributeValue(memberType, u1.toString())));
    assertFalse(provider.hasValue(entry, rule,
                    new AttributeValue(memberType, ne.toString())));
    assertFalse(provider.hasAnyValue(entry, rule, values));
    assertFalse(provider.hasAnyValue(entry, rule,
                                     Collections.<AttributeValue>emptySet()));
    assertEquals(provider.matchesSubstring(entry, rule, null, null, null),
                 ConditionResult.UNDEFINED);
    assertEquals(provider.greaterThanOrEqualTo(entry, rule, null),
                 ConditionResult.UNDEFINED);
    assertEquals(provider.lessThanOrEqualTo(entry, rule, null),
                 ConditionResult.UNDEFINED);
    assertEquals(provider.approximatelyEqualTo(entry, rule, null),
                 ConditionResult.UNDEFINED);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), null, DN.decode("o=test"),
                  SearchScope.WHOLE_SUBTREE,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString(
                       "(member=" + u1.toString() + ")"),
                  null, null);
    assertFalse(provider.isSearchable(rule, searchOperation));
    provider.processSearch(rule, searchOperation);
    assertFalse(searchOperation.getResultCode() == ResultCode.SUCCESS);
    cleanUp();
  }
  /**
   * Tests the behavior of the member virtual attribute with a dynamic group.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVirtualAttrDynamicGroupWithMember()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    Entry e = DirectoryServer.getEntry(vmda);
    assertNotNull(e);
    assertTrue(e.hasAttribute(memberType));
    Attribute a = e.getAttribute(memberType).get(0);
    assertEquals(a.getValues().size(), 4);
    AttributeValue v = new AttributeValue(memberType, u1.toString());
    assertTrue(a.hasValue(v));
    cleanUp();
  }
  /**
   * Tests the behavior of the member virtual attribute with a dynamic group.
   * The target dynamic group will initially have only one memberURL which
   * matches only one user, but will then be updated on the fly to contain a
   * second URL that matches all users.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testVirtualAttrDynamicGroupWithUpdatedMemberURLs()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(LDIF_LINES);
    Entry e = DirectoryServer.getEntry(vmd1);
    assertNotNull(e);
    assertTrue(e.hasAttribute(memberType));
    Attribute a = e.getAttribute(memberType).get(0);
    assertEquals(a.getValues().size(), 1);
    AttributeValue v = new AttributeValue(memberType, u4.toString());
    assertTrue(a.hasValue(v));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    LinkedList<Modification> mods = new LinkedList<Modification>();
    mods.add(new Modification(ModificationType.ADD,
         new Attribute("memberurl",
                       "ldap:///o=test??sub?(objectClass=person)")));
    ModifyOperation modifyOperation = conn.processModify(d1, mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
    a = e.getAttribute(memberType).get(0);
    assertEquals(a.getValues().size(), 4);
    assertTrue(a.hasValue(v));
    cleanUp();
  }
  /**
   * Removes all of the groups that have been added to the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void cleanUp()
          throws Exception
  {
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(DN.decode("ou=Groups,dc=example,dc=com"),
              SearchScope.SINGLE_LEVEL,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    for (Entry e : searchOperation.getSearchEntries())
    {
      conn.processDelete(e.getDN());
    }
  }
}