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

ludovicp
25.25.2010 a09e50d8d41c0f50c486742f4cc2343083c635e3
Fixes issues #4552 #4557, making sure plugins and internal services are properly handling subtree move or delete.
The changes particularly resolve problems raised by the community with the referential integrity and the isMemberOf plug-ins.
Unit-tests have been updated to cover those cases
2 files added
42 files modified
3313 ■■■■■ changed files
opends/resource/config/config.ldif 1 ●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 6 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/PluginConfiguration.xml 8 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/PluginRootConfiguration.xml 32 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml 3 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/ChangeNumberControlPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/EntryUUIDPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/FractionalLDIFImportPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/LDAPAttributeDescriptionListPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/LastModPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/NetworkGroupPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/PasswordPolicyImportPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/PluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/PluginRootCfgDefn.properties 3 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/ProfilerPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/ReferentialIntegrityPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/SevenBitCleanPluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/UniqueAttributePluginCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/jeb.properties 2 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/plugin.properties 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/DITCacheMap.java 791 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/Group.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java 25 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/plugin/PluginResult.java 194 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/plugin/PluginType.java 11 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciList.java 214 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java 11 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AuthenticatedUsers.java 211 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/GroupManager.java 156 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PluginConfigManager.java 92 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SubentryManager.java 118 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/crypto/CryptoManagerSync.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/DynamicGroup.java 13 ●●●●● 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 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java 107 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AuthenticationInfo.java 44 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/api/DITCacheMapTestCase.java 496 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/api/plugin/DirectoryServerPluginTestCase.java 11 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java 172 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java 222 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java 265 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java 16 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1786,6 +1786,7 @@
ds-cfg-plugin-type: postOperationDelete
ds-cfg-plugin-type: postOperationModifyDN
ds-cfg-plugin-type: subordinateModifyDN
ds-cfg-plugin-type: subordinateDelete
ds-cfg-attribute-type: member
ds-cfg-attribute-type: uniqueMember
ds-cfg-invoke-for-internal-operations: true
opends/resource/schema/02-config.ldif
@@ -2465,6 +2465,11 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.609
  NAME 'ds-cfg-plugin-order-subordinate-delete'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  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
@@ -3573,6 +3578,7 @@
        ds-cfg-plugin-order-search-result-entry $
        ds-cfg-plugin-order-search-result-reference $
        ds-cfg-plugin-order-subordinate-modify-dn $
        ds-cfg-plugin-order-subordinate-delete $
        ds-cfg-plugin-order-intermediate-response )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.112
opends/src/admin/defn/org/opends/server/admin/std/PluginConfiguration.xml
@@ -23,7 +23,7 @@
  ! CDDL HEADER END
  !
  !
  !      Copyright 2007-2009 Sun Microsystems, Inc.
  !      Copyright 2007-2010 Sun Microsystems, Inc.
  ! -->
<adm:managed-object name="plugin" plural-name="plugins"
  package="org.opends.server.admin.std"
@@ -362,6 +362,12 @@
            subordinate to the target of a modify DN operation.
          </adm:synopsis>
        </adm:value>
        <adm:value name="subordinatedelete">
          <adm:synopsis>
            Invoked in the course of deleting a subordinate
            entry of a delete operation.
          </adm:synopsis>
        </adm:value>
        <adm:value name="intermediateresponse">
          <adm:synopsis>
            Invoked before sending an intermediate repsonse message to
opends/src/admin/defn/org/opends/server/admin/std/PluginRootConfiguration.xml
@@ -23,7 +23,7 @@
  ! CDDL HEADER END
  !
  !
  !      Copyright 2007-2009 Sun Microsystems, Inc.
  !      Copyright 2007-2010 Sun Microsystems, Inc.
  ! -->
<adm:managed-object name="plugin-root" plural-name="plugin-roots"
  package="org.opends.server.admin.std"
@@ -1607,6 +1607,36 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="plugin-order-subordinate-delete">
    <adm:synopsis>
      Specifies the order in which subordinate delete plug-ins are to
      be loaded and invoked.
    </adm:synopsis>
    <adm:description>
      The value is a comma-delimited list
      of plug-in names (where the plug-in name is the RDN value from the
      plug-in configuration entry DN). The list can include at most one
      asterisk to indicate the position of any unspecified plug-in (and
      the relative order of those unspecified plug-ins is
      undefined).
    </adm:description>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          The order in which subordinate delete plug-ins are loaded
          and invoked is undefined.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-plugin-order-subordinate-delete</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="plugin-order-intermediate-response">
    <adm:synopsis>
      Specifies the order in which intermediate response plug-ins are to
opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
@@ -23,7 +23,7 @@
  ! CDDL HEADER END
  !
  !
  !      Copyright 2007-2008 Sun Microsystems, Inc.
  !      Copyright 2007-2010 Sun Microsystems, Inc.
  ! -->
<adm:managed-object name="referential-integrity-plugin"
  plural-name="referential-integrity-plugins"
@@ -67,6 +67,7 @@
        <adm:value>postoperationdelete</adm:value>
        <adm:value>postoperationmodifydn</adm:value>
        <adm:value>subordinatemodifydn</adm:value>
        <adm:value>subordinatedelete</adm:value>
      </adm:defined>
    </adm:default-behavior>
  </adm:property-override>
opends/src/admin/messages/ChangeNumberControlPluginCfgDefn.properties
@@ -58,4 +58,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/EntryUUIDPluginCfgDefn.properties
@@ -58,4 +58,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/FractionalLDIFImportPluginCfgDefn.properties
@@ -58,4 +58,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/LDAPAttributeDescriptionListPluginCfgDefn.properties
@@ -58,4 +58,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/LastModPluginCfgDefn.properties
@@ -58,4 +58,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/NetworkGroupPluginCfgDefn.properties
@@ -58,4 +58,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/PasswordPolicyImportPluginCfgDefn.properties
@@ -63,4 +63,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/PluginCfgDefn.properties
@@ -57,4 +57,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/PluginRootCfgDefn.properties
@@ -155,6 +155,9 @@
property.plugin-order-startup.synopsis=Specifies the order in which startup plug-ins are to be loaded and invoked.
property.plugin-order-startup.description=The value is a comma-delimited list of plug-in names (where the plug-in name is the RDN value from the plug-in configuration entry DN). The list can include at most one asterisk to indicate the position of any unspecified plug-in (and the relative order of those unspecified plug-ins is undefined).
property.plugin-order-startup.default-behavior.alias.synopsis=The order in which startup plug-ins are loaded and invoked is undefined.
property.plugin-order-subordinate-delete.synopsis=Specifies the order in which subordinate delete plug-ins are to be loaded and invoked.
property.plugin-order-subordinate-delete.description=The value is a comma-delimited list of plug-in names (where the plug-in name is the RDN value from the plug-in configuration entry DN). The list can include at most one asterisk to indicate the position of any unspecified plug-in (and the relative order of those unspecified plug-ins is undefined).
property.plugin-order-subordinate-delete.default-behavior.alias.synopsis=The order in which subordinate delete plug-ins are loaded and invoked is undefined.
property.plugin-order-subordinate-modify-dn.synopsis=Specifies the order in which subordinate modify DN plug-ins are to be loaded and invoked.
property.plugin-order-subordinate-modify-dn.description=The value is a comma-delimited list of plug-in names (where the plug-in name is the RDN value from the plug-in configuration entry DN). The list can include at most one asterisk to indicate the position of any unspecified plug-in (and the relative order of those unspecified plug-ins is undefined).
property.plugin-order-subordinate-modify-dn.default-behavior.alias.synopsis=The order in which subordinate modify DN plug-ins are loaded and invoked is undefined.
opends/src/admin/messages/ProfilerPluginCfgDefn.properties
@@ -59,6 +59,7 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
property.profile-action.synopsis=Specifies the action that should be taken by the profiler.
property.profile-action.description=A value of "start" causes the profiler thread to start collecting data if it is not already active. A value of "stop" causes the profiler thread to stop collecting data and write it to disk, and a value of "cancel" causes the profiler thread to stop collecting data and discard anything that has been captured. These operations occur immediately.
opends/src/admin/messages/ReferentialIntegrityPluginCfgDefn.properties
@@ -65,6 +65,7 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
property.update-interval.synopsis=Specifies the interval in seconds when referential integrity updates are made.
property.update-interval.description=If this value is 0, then the updates are made synchronously in the foreground.
opends/src/admin/messages/SevenBitCleanPluginCfgDefn.properties
@@ -62,4 +62,5 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
opends/src/admin/messages/UniqueAttributePluginCfgDefn.properties
@@ -60,5 +60,6 @@
property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client.
property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful Directory Server shutdown.
property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the Directory Server startup process.
property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation.
property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation.
property.type.synopsis=Specifies the type of attributes to check for value uniqueness.
opends/src/messages/messages/jeb.properties
@@ -365,6 +365,8 @@
SEVERE_ERR_CONFIG_JEB_CACHE_SIZE_TOO_SMALL_194=Configuration \
  attribute ds-cfg-db-cache-size has a value of %d which is less than \
  the minimum: %d
MILD_ERR_JEB_DELETE_ABORTED_BY_SUBORDINATE_PLUGIN_195=A plugin caused the \
 delete operation to be aborted while deleting a subordinate entry %s
NOTICE_JEB_IMPORT_LDIF_PHASE_TWO_MEM_REPORT_196=The available memory for phase \
two processing is %d bytes. The read ahead cache size is %d bytes calculated \
using %d buffers
opends/src/messages/messages/plugin.properties
@@ -20,7 +20,7 @@
#
# CDDL HEADER END
#
#      Copyright 2006-2009 Sun Microsystems, Inc.
#      Copyright 2006-2010 Sun Microsystems, Inc.
@@ -413,3 +413,11 @@
 made to register the Change Number Control plugin with the following plugin \
 types : %s. However this plugin must be configured with all of the following \
 plugin types : %s
SEVERE_ERR_PLUGIN_SUBORDINATE_DELETE_PLUGIN_EXCEPTION_115=The subordinate \
 delete plugin defined in configuration entry %s threw an exception when it \
 was invoked for connection %d operation %d:  %s.  Processing on this \
 operation will be terminated
SEVERE_ERR_PLUGIN_SUBORDINATE_DELETE_PLUGIN_RETURNED_NULL_116=The \
 subordinate delete plugin defined in configuration entry %s returned null \
 when invoked for connection %d operation %s.  This is an illegal response, \
 and processing on this operation will be terminated
opends/src/server/org/opends/server/api/DITCacheMap.java
New file
@@ -0,0 +1,791 @@
/*
 * 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
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 */
package org.opends.server.api;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import org.opends.server.types.DN;
/**
 * The DITCacheMap class implements custom Map for structural
 * storage of arbitrary objects in Directory Information Tree
 * (DIT) like structure.
 *
 * This Map intended usage is for caching various server
 * objects which can be subject to subtree operations
 * like retrieval or removal of all objects under a
 * specific DN. While using a regular Map it would
 * require the entire Map iteration to achieve, this Map
 * implementation maintains such internal structure that
 * subtree operations are more efficient and do not
 * require iterations over the entire map, instead
 * additional subtree operations methods are provided by
 * this Map to do just that.
 *
 * API wise it behaves exactly like a regular Map
 * implementation except for providing additional
 * subtree methods. All required linkage and
 * structuring is performed within this Map
 * implementation itself and not exposed via the
 * API in any way. For example, putting these
 * key/value pairs
 *
 * cn=Object1,ou=Objects,dc=example,dc=com : object1
 * cn=Object2,ou=Objects,dc=example,dc=com : object2
 * cn=Object3,ou=Objects,dc=example,dc=com : object3
 *
 * then invoking a subtree method on this Map with
 * any of these keys
 *
 * ou=Objects,dc=example,dc=com
 * dc=example,dc=com
 * dc=com
 *
 * would bring all three objects previously stored in
 * this map into subtree operation scope. Standard
 * Map API methods can only work with the objects
 * previously stored in this map explicitly.
 *
 * Note that this Map implementation is not
 * synchronized.
 *
 * @param <T> arbitrary object type.
 */
public class DITCacheMap<T> extends AbstractMap<DN,T>
{
  /**
   * Node class for object storage and
   * linking to any subordinate nodes.
   * @param <T> arbitrary storage object.
   */
  private static final class Node<T>
  {
    // Node DN.
    DN dn;
    // Storage object or null if this node exist
    // only to support the DIT like structuring.
    T element;
    // Parent.
    Node<T> parent;
    // First child.
    Node<T> child;
    // Next sibling.
    Node<T> next;
    // Previous sibling.
    Node<T> previous;
  }
  // Map size reflecting only nodes
  // containing non empty elements.
  private int size = 0;
  // Backing Map implementation.
  private Map<DN,Node<T>> ditCacheMap;
  /**
   * Default contructor.
   */
  public DITCacheMap()
  {
    ditCacheMap = new HashMap<DN,Node<T>>();
  }
  /**
   * Contructs a new DITCacheMap from a given Map.
   * @param m existing Map to construct new
   *          DITCacheMap from.
   */
  public DITCacheMap(Map<? extends DN, ? extends T> m)
  {
    ditCacheMap = new HashMap<DN,Node<T>>();
    this.putAll(m);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int size()
  {
    return size;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isEmpty()
  {
    return ditCacheMap.isEmpty();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean containsKey(Object key)
  {
    if (get((DN) key) != null)
    {
      return true;
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean containsValue(Object value)
  {
    for (Node<T> node : ditCacheMap.values())
    {
      if ((node.element != null) &&
           node.element.equals(value))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public T get(Object key)
  {
    Node<T> node = ditCacheMap.get((DN)key);
    if (node != null)
    {
      return node.element;
    }
    return null;
  }
  /**
   * Returns a set of stored objects
   * subordinate to subtree DN.
   * @param key subtree DN.
   * @return collection of stored objects
   *         subordinate to subtree DN.
   */
  public Collection<T> getSubtree(DN key)
  {
    return new DITSubtreeSet(key);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public T put(DN key, T value)
  {
    T returnValue = null;
    Node<T> existingNode = ditCacheMap.get(key);
    if (existingNode != null)
    {
      returnValue = existingNode.element;
      existingNode.element = value;
    }
    else
    {
      Node<T> node = new Node<T>();
      node.dn = key;
      node.element = value;
      node.parent = null;
      node.child = null;
      node.next = null;
      node.previous = null;
      ditCacheMap.put(key, node);
      size++;
      for (DN parentDN = key.getParent();
        parentDN != null;
        parentDN = parentDN.getParent())
      {
        Node<T> parentNode = ditCacheMap.get(parentDN);
        if (parentNode != null)
        {
          if (parentNode.child != null)
          {
            Node<T> lastNode = parentNode.child;
            while (lastNode.next != null)
            {
              lastNode = lastNode.next;
            }
            node.previous = lastNode;
            lastNode.next = node;
          }
          else
          {
            parentNode.child = node;
          }
          node.parent = parentNode;
          break;
        }
        else
        {
          parentNode = new Node<T>();
          parentNode.dn = parentDN;
          parentNode.element = null;
          parentNode.parent = null;
          parentNode.child = node;
          parentNode.next = null;
          parentNode.previous = null;
          ditCacheMap.put(parentDN, parentNode);
          node.parent = parentNode;
          node = parentNode;
        }
      }
    }
    return returnValue;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public T remove(Object key)
  {
    T returnValue = null;
    Node<T> existingNode = ditCacheMap.get((DN)key);
    if ((existingNode != null) &&
        (existingNode.element != null))
    {
      returnValue = existingNode.element;
      try
      {
        if (existingNode.child == null)
        {
          ditCacheMap.remove((DN)key);
        }
        else
        {
          existingNode.element = null;
          return returnValue;
        }
      }
      finally
      {
        size--;
      }
      for (DN parentDN = existingNode.dn.getParent();
        parentDN != null;
        parentDN = parentDN.getParent())
      {
        Node<T> parentNode = ditCacheMap.get(parentDN);
        if (parentNode.child == existingNode)
        {
          parentNode.child = existingNode.next;
        }
        else
        {
          if (existingNode.next != null)
          {
            existingNode.next.previous = existingNode.previous;
          }
          if (existingNode.previous != null)
          {
            existingNode.previous.next = existingNode.next;
          }
        }
        if ((parentNode.child == null) &&
            (parentNode.element == null))
        {
          existingNode = ditCacheMap.remove(parentDN);
        }
        else
        {
          break;
        }
      }
    }
    return returnValue;
  }
  /**
   * Removes a set of stored objects subordinate to subtree DN.
   * @param key subtree DN.
   * @param values collection for removed objects subordinate
   *               to subtree DN or <code>null</code>.
   * @return <code>true</code> on success or
   *         <code>false</code> otherwise.
   */
  public boolean removeSubtree(DN key, Collection<? super T> values)
  {
    Node<T> rootNode = ditCacheMap.get(key);
    if (rootNode != null)
    {
      if (rootNode.element != null)
      {
        remove(key);
        if (values != null)
        {
          values.add(rootNode.element);
        }
      }
      Node<T> node = rootNode.child;
      while (node != null)
      {
        if (node.element != null)
        {
          remove(node.dn);
          if (values != null)
          {
            values.add(node.element);
          }
        }
        if (node.child != null)
        {
          node = node.child;
        }
        else
        {
          while ((node.next == null) &&
                 (node.parent != rootNode))
          {
            node = node.parent;
          }
          node = node.next;
        }
      }
      return true;
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void putAll(Map<? extends DN, ? extends T> m)
  {
    for (Entry<? extends DN, ? extends T> entry : m.entrySet())
    {
      put(entry.getKey(), entry.getValue());
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void clear()
  {
    ditCacheMap.clear();
    size = 0;
  }
  /**
   * {@inheritDoc}
   */
  public Set<Entry<DN, T>> entrySet()
  {
    return new DITCacheEntrySet();
  }
  /**
   * EntrySet class implementation for the DITCacheMap.
   */
  private class DITCacheEntrySet extends AbstractSet<Entry<DN, T>>
  {
    /**
     * Iterator class implementation for the DITCacheEntrySet.
     */
    private class EntryIterator implements Iterator<Entry<DN, T>>
    {
      private Iterator<Entry<DN, Node<T>>> ditCacheMapIterator;
      private Entry<DN, Node<T>> currentEntry;
      private Entry<DN, Node<T>> nextEntry;
      private boolean hasNext;
      /**
       * Default constructor.
       */
      public EntryIterator()
      {
        ditCacheMapIterator = ditCacheMap.entrySet().iterator();
        currentEntry = null;
        nextEntry = null;
        hasNext = false;
      }
      /**
       * {@inheritDoc}
       */
      public boolean hasNext()
      {
        if (hasNext)
        {
          return true;
        }
        while (ditCacheMapIterator.hasNext())
        {
          Entry<DN, Node<T>> entry = ditCacheMapIterator.next();
          Node<T> node = entry.getValue();
          if ((node != null) && (node.element != null))
          {
            nextEntry = entry;
            hasNext = true;
            return true;
          }
        }
        nextEntry = null;
        return false;
      }
      /**
       * {@inheritDoc}
       */
      public Entry<DN, T> next()
      {
        if (nextEntry != null)
        {
          Node<T> node = nextEntry.getValue();
          currentEntry = nextEntry;
          nextEntry = null;
          hasNext = false;
          return new DITCacheMapEntry(node.dn, node.element);
        }
        while (ditCacheMapIterator.hasNext())
        {
          Entry<DN, Node<T>> entry = ditCacheMapIterator.next();
          Node<T> node = entry.getValue();
          if ((node != null) && (node.element != null))
          {
            currentEntry = entry;
            hasNext = false;
            return new DITCacheMapEntry(node.dn, node.element);
          }
        }
        throw new NoSuchElementException();
      }
      /**
       * {@inheritDoc}
       */
      public void remove()
      {
        if (currentEntry != null)
        {
          Entry<DN, Node<T>> oldIteratorEntry = null;
          if (hasNext())
          {
            oldIteratorEntry = nextEntry;
          }
          if (DITCacheMap.this.remove(currentEntry.getKey()) != null)
          {
            ditCacheMapIterator = ditCacheMap.entrySet().iterator();
            currentEntry = null;
            nextEntry = null;
            hasNext = false;
            while (hasNext())
            {
              Entry<DN, T> newIteratorEntry = next();
              if ((oldIteratorEntry != null) &&
                   oldIteratorEntry.getKey().equals(
                   newIteratorEntry.getKey()) &&
                   oldIteratorEntry.getValue().element.equals(
                   newIteratorEntry.getValue()))
              {
                nextEntry = currentEntry;
                hasNext = true;
                return;
              }
            }
            currentEntry = null;
            nextEntry = null;
            hasNext = false;
            return;
          }
        }
        throw new IllegalStateException();
      }
    }
    /**
     * {@inheritDoc}
     */
    public int size()
    {
      return DITCacheMap.this.size();
    }
    /**
     * {@inheritDoc}
     */
    public Iterator<Entry<DN, T>> iterator()
    {
      return new EntryIterator();
    }
  }
  /**
   * Map.Entry class implementation for the DITCacheMap.
   */
  private class DITCacheMapEntry implements Map.Entry<DN, T>
  {
    private DN key;
    private T  value;
    /**
     * Constructs a new DITCacheMapEntry
     * with given key and value.
     * @param key Map.Entry key.
     * @param value Map.Entry value.
     */
    public DITCacheMapEntry(DN key, T value)
    {
      this.key = key;
      this.value = value;
    }
    /**
     * {@inheritDoc}
     */
    public DN getKey()
    {
      return key;
    }
    /**
     * {@inheritDoc}
     */
    public T getValue()
    {
      return value;
    }
    /**
     * {@inheritDoc}
     */
    public T setValue(T value)
    {
      Node<T> node = ditCacheMap.get(key);
      T oldValue = this.value;
      node.element = value;
      this.value = value;
      return oldValue;
    }
  }
  /**
   * SubtreeSet class implementation.
   */
  private class DITSubtreeSet extends AbstractSet<T>
  {
    // Set key.
    private DN key;
    /**
     * Default constructor.
     */
    public DITSubtreeSet()
    {
      this.key = null;
    }
    /**
     * Keyed constructor.
     * @param key to construct
     *        this set from.
     */
    public DITSubtreeSet(DN key)
    {
      this.key = key;
    }
    /**
     * Iterator class implementation for SubtreeSet.
     */
    private class SubtreeSetIterator implements Iterator<T>
    {
      // Iterator key.
      private DN key;
      // Iterator root node.
      private Node<T> rootNode;
      // Iterator current node.
      private Node<T> node;
      /**
       * Default constructor.
       */
      public SubtreeSetIterator()
      {
        this.key = DITSubtreeSet.this.key;
        rootNode = ditCacheMap.get(this.key);
        node = rootNode;
      }
      /**
       * Keyed constructor.
       * @param key to cue this
       *        iterator from.
       */
      public SubtreeSetIterator(DN key)
      {
        this.key = key;
        rootNode = ditCacheMap.get(this.key);
        node = rootNode;
      }
      /**
       * {@inheritDoc}
       */
      public boolean hasNext()
      {
        if (rootNode != null)
        {
          while (node != null)
          {
            if (node.element != null)
            {
              return true;
            }
            if (node.child != null)
            {
              node = node.child;
            }
            else
            {
              if (node != rootNode)
              {
                while ((node.next == null) &&
                       (node.parent != rootNode))
                {
                  node = node.parent;
                }
              }
              node = node.next;
            }
          }
        }
        return false;
      }
      /**
       * {@inheritDoc}
       */
      public T next()
      {
        T element = null;
        if (rootNode != null)
        {
          while (node != null)
          {
            if (node.element != null)
            {
              element = node.element;
            }
            else
            {
              element = null;
            }
            if (node.child != null)
            {
              node = node.child;
            }
            else
            {
              if (node != rootNode)
              {
                while ((node.next == null) &&
                       (node.parent != rootNode))
                {
                  node = node.parent;
                }
              }
              node = node.next;
            }
            if (element != null)
            {
              return element;
            }
          }
        }
        throw new NoSuchElementException();
      }
      /**
       * {@inheritDoc}
       */
      public void remove()
      {
        throw new UnsupportedOperationException();
      }
    }
    /**
     * {@inheritDoc}
     */
    public Iterator<T> iterator()
    {
      return new SubtreeSetIterator();
    }
    /**
     * {@inheritDoc}
     */
    public int size()
    {
      int size = 0;
      Iterator<T> iterator = new SubtreeSetIterator(this.key);
      while (iterator.hasNext())
      {
        iterator.next();
        size++;
      }
      return size;
    }
  }
}
opends/src/server/org/opends/server/api/Group.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.api;
import org.opends.messages.Message;
@@ -209,6 +209,17 @@
  /**
   * Sets the DN of the entry that contains the definition for
   * this group.
   *
   * @param  groupDN  The DN of the entry that contains the
   *                  definition for this group.
   */
  public abstract void setGroupDN(DN groupDN);
  /**
   * Indicates whether this group supports nesting other groups, such
   * that the members of the nested groups will also be considered
   * members of this group.
opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.api.plugin;
import org.opends.messages.Message;
@@ -35,6 +35,7 @@
import org.opends.server.admin.std.server.PluginCfg;
import org.opends.server.api.ClientConnection;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DeleteOperation;
import org.opends.server.types.*;
import org.opends.server.types.operation.*;
@@ -1115,6 +1116,28 @@
  /**
   * Performs any necessary processing that should be done whenever a
   * subordinate entry is deleted as part of subtree delete operation.
   *
   * @param  deleteOperation  The delete operation with which the
   *                          subordinate entry is associated.
   * @param  entry            The subordinate entry being deleted.
   *
   * @return Information about the result of the plugin processing.
   */
  public PluginResult.SubordinateDelete
       processSubordinateDelete(DeleteOperation
         deleteOperation, Entry entry)
  {
    Message message = ERR_PLUGIN_TYPE_NOT_SUPPORTED.get(
            String.valueOf(pluginDN),
            PluginType.SUBORDINATE_MODIFY_DN.getName());
    throw new UnsupportedOperationException(message.toString());
  }
  /**
   * Performs any necessary processing that should be done after the
   * Directory Server has completed the core processing for a modify
   * DN operation but before the response has been sent to the client.
opends/src/server/org/opends/server/api/plugin/PluginResult.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.api.plugin;
@@ -1035,6 +1035,198 @@
  }
  /**
   * Defines a subordinate delete plugin result for core server
   * operation processing consisting of either continue, skip
   * further plugins, or stop operation processing with a result
   * code, matched DN, referral URLs, and error message.
   */
  public static final class SubordinateDelete
  {
    // Whether to continue operation processing.
    private final boolean continueProcessing;
    // Whether to invoke the rest of the plugins.
    private final boolean continuePluginProcessing;
    // An message explaining why processing should stop.
    private final Message errorMessage;
    // The matched DN for this result.
    private final DN matchedDN;
    // The set of referral URLs for this result.
    private final List<String> referralURLs;
    // The result code for this result.
    private final ResultCode resultCode;
    private static SubordinateDelete DEFAULT_RESULT =
        new SubordinateDelete(true, true, null, null, null, null);
    /**
     * Construct a new subordinate delete plugin result.
     *
     * @param continueProcessing Whether to continue startup.
     * @param continuePluginProcessing Whether to invoke the rest
     * of the plugins.
     * @param errorMessage An message explaining why processing
     * should stop.
     * @param resultCode The result code for this result.
     * @param matchedDN The matched DN for this result.
     * @param referralURLs The set of referral URLs for this result.
     * stop.
     */
    private SubordinateDelete(boolean continueProcessing,
                              boolean continuePluginProcessing,
                              Message errorMessage,
                              ResultCode resultCode, DN matchedDN,
                              List<String> referralURLs)
    {
      this.continueProcessing = continueProcessing;
      this.errorMessage = errorMessage;
      this.continuePluginProcessing = continuePluginProcessing;
      this.resultCode = resultCode;
      this.matchedDN = matchedDN;
      this.referralURLs = referralURLs;
    }
    /**
     * Defines a continue processing subordinate delete plugin
     *  result.
     *
     * @return a continue processing subordinate delete plugin
     *  result.
     */
    public static SubordinateDelete continueOperationProcessing()
    {
      return DEFAULT_RESULT;
    }
    /**
     * Defines a skip further plugin processing subordinate delete
     * plugin result.
     *
     * @return  a skip further plugin processing subordinate delete
     * plugin result.
     */
    public static SubordinateDelete skipFurtherPluginProcesssing()
    {
      return new SubordinateDelete(true, false, null, null, null,
          null);
    }
    /**
     * Defines a new stop processing subordinate delete plugin
     * result.
     *
     * @param resultCode The result code for this result.
     * @param errorMessage An message explaining why processing
     * should stop.
     * @param matchedDN The matched DN for this result.
     * @param referralURLs The set of referral URLs for this result.
     *
     * @return a new stop processing subordinate delete plugin
     * result.
     */
    public static SubordinateDelete stopProcessing(
        ResultCode resultCode, Message errorMessage, DN matchedDN,
        List<String> referralURLs)
    {
      return new SubordinateDelete(false, false, errorMessage,
          resultCode, matchedDN, referralURLs);
    }
    /**
     * Contrust a new stop processing subordinate delete plugin
     * result.
     *
     * @param resultCode The result code for this result.
     * @param errorMessage An message explaining why processing
     * should stop.
     *
     * @return a new stop processing subordinate delete plugin
     * result.
     */
    public static SubordinateDelete stopProcessing(
        ResultCode resultCode, Message errorMessage)
    {
      return new SubordinateDelete(false, false, errorMessage,
          resultCode, null, null);
    }
    /**
     * Whether to continue operation processing.
     *
     * @return <code>true</code> if processing should continue
     * or <code>false</code> otherwise.
     */
    public boolean continueProcessing()
    {
      return continueProcessing;
    }
    /**
     * Whether to invoke the rest of the plugins.
     *
     * @return <code>true</code> if the rest of the plugins should
     * be invoked for <code>false</code> to skip the rest of the
     * plugins.
     */
    public boolean continuePluginProcessing()
    {
      return continuePluginProcessing;
    }
    /**
     * Retrieves the error message if <code>continueProcessing</code>
     * returned <code>false</code>.
     *
     * @return An error message explaining why processing should
     * stop or <code>null</code> if none is provided.
     */
    public Message getErrorMessage()
    {
      return errorMessage;
    }
    /**
     * Retrieves the result code for the operation
     * if <code>continueProcessing</code> returned <code>false</code>.
     *
     * @return the result code for the operation or <code>null</code>
     * if none is provided.
     */
    public ResultCode getResultCode()
    {
      return resultCode;
    }
    /**
     * Retrieves the matched DN for the operation
     * if <code>continueProcessing</code> returned <code>false</code>.
     *
     * @return the matched DN for the operation or <code>null</code>
     * if none is provided.
     */
    public DN getMatchedDN()
    {
      return matchedDN;
    }
    /**
     * Retrieves the referral URLs for the operation
     * if <code>continueProcessing</code> returned <code>false</code>.
     *
     * @return the refferal URLs for the operation or
     * <code>null</code> if none is provided.
     */
    public List<String> getReferralURLs()
    {
      return referralURLs;
    }
  }
  /**
   * Defines an intermediate response plugin result for core server
   *  operation processing consisting of either continue, skip further
   * plugins, or stop operation processing with a result code,
opends/src/server/org/opends/server/api/plugin/PluginType.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.api.plugin;
@@ -458,6 +458,15 @@
  /**
   * The plugin type for plugins that are to be invoked on each
   * subordinate entry that is deleted as part of a subtree
   * delete operation.
   */
  SUBORDINATE_DELETE("subordinatedelete"),
  /**
   * The plugin type for plugins that are to be invoked before each
   * intermediate response message is sent to a client.
   */
opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
@@ -32,9 +32,11 @@
import static org.opends.server.authorization.dseecompat.AciHandler.*;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.messages.AccessControlMessages.*;
import org.opends.server.api.DITCacheMap;
import org.opends.server.types.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * The AciList class performs caching of the ACI attribute values
@@ -46,8 +48,14 @@
   * A map containing all the ACIs.
   * We use the copy-on-write technique to avoid locking when reading.
   */
  private volatile LinkedHashMap<DN, List<Aci>> aciList =
       new LinkedHashMap<DN, List<Aci>>();
  private volatile DITCacheMap<List<Aci>> aciList =
       new DITCacheMap<List<Aci>>();
  /*
   * Lock to protect internal data structures.
   */
  private final ReentrantReadWriteLock lock =
          new ReentrantReadWriteLock();
  /*
  * The configuration DN used to compare against the global ACI entry DN.
@@ -63,23 +71,6 @@
  }
  /**
   * Accessor to the ACI list intended to be called from within unsynchronized
   * read-only methods.
   * @return   The current ACI list.
   */
  private LinkedHashMap<DN, List<Aci>> getList() {
    return aciList;
  }
  /**
   * Used by synchronized write methods to make a copy of the ACI list.
   * @return A copy of the ACI list.
   */
  private LinkedHashMap<DN,List<Aci>> copyList() {
    return new LinkedHashMap<DN, List<Aci>>(aciList);
  }
  /**
   * Using the base DN, return a list of ACIs that are candidates for
   * evaluation by walking up from the base DN towards the root of the
   * DIT gathering ACIs on parents. Global ACIs use the NULL DN as the key
@@ -93,10 +84,13 @@
  public LinkedList<Aci> getCandidateAcis(DN baseDN) {
    LinkedList<Aci> candidates = new LinkedList<Aci>();
    if(baseDN == null)
    {
      return candidates;
    }
    // Save a reference to the current ACI list, in case it gets changed.
    LinkedHashMap<DN, List<Aci>> aciList = getList();
    lock.readLock().lock();
    try
    {
    //Save the baseDN in case we need to evaluate a global ACI.
    DN entryDN=baseDN;
    while(baseDN != null) {
@@ -111,21 +105,31 @@
               if(targets != null) {
                   boolean ret=AciTargets.isTargetApplicable(aci, targets,
                                                             entryDN);
                   if(ret)
                if (ret) {
                      candidates.add(aci);  //Add this ACI to the candidates.
               }
           }
       } else
            }
          } else {
           candidates.addAll(acis);
      }
      if(baseDN.isNullDN())
        }
        if(baseDN.isNullDN()) {
        break;
        }
      DN parentDN=baseDN.getParent();
      if(parentDN == null)
        if(parentDN == null) {
        baseDN=DN.nullDN();
      else
        } else {
        baseDN=parentDN;
    }
      }
    }
    finally
    {
      lock.readLock().unlock();
    }
    return candidates;
  }
@@ -138,23 +142,27 @@
   *                      exceptions.
   * @return The number of valid ACI attribute values added to the ACI list.
   */
  public synchronized int addAci(List<? extends Entry> entries,
  public int addAci(List<? extends Entry> entries,
                                 LinkedList<Message> failedACIMsgs)
  {
    // Copy the ACI list.
    LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
    int validAcis=0;
    lock.writeLock().lock();
    try
    {
    for (Entry entry : entries) {
      DN dn=entry.getDN();
      List<Attribute> attributeList =
           entry.getOperationalAttribute(AciHandler.aciType);
      validAcis += addAciAttributeList(aciCopy, dn, configDN,
        validAcis += addAciAttributeList(aciList, dn, configDN,
                                       attributeList, failedACIMsgs);
    }
    }
    finally
    {
      lock.writeLock().unlock();
    }
    // Replace the ACI list with the copy.
    aciList = aciCopy;
    return validAcis;
  }
@@ -167,9 +175,17 @@
   * @param acis A set of ACIs to add to the ACI list.
   *
   */
  public synchronized void addAci(DN dn, SortedSet<Aci> acis) {
  public void addAci(DN dn, SortedSet<Aci> acis) {
    lock.writeLock().lock();
    try
    {
    aciList.put(dn, new LinkedList<Aci>(acis));
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
  /**
   * Add all of an entry's ACI (global or regular) attribute values to the
@@ -182,29 +198,34 @@
   *                      exceptions.
   * @return The number of valid ACI attribute values added to the ACI list.
   */
  public synchronized int addAci(Entry entry,  boolean hasAci,
  public int addAci(Entry entry,  boolean hasAci,
                                 boolean hasGlobalAci,
                                 LinkedList<Message> failedACIMsgs) {
    int validAcis=0;
    // Copy the ACI list.
    LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
    lock.writeLock().lock();
    try
    {
    //Process global "ds-cfg-global-aci" attribute type. The oldentry
    //DN is checked to verify it is equal to the config DN. If not those
    //attributes are skipped.
    if(hasGlobalAci && entry.getDN().equals(configDN)) {
        List<Attribute> attributeList = entry.getAttribute(globalAciType);
        validAcis = addAciAttributeList(aciCopy, DN.nullDN(), configDN,
          validAcis = addAciAttributeList(aciList, DN.nullDN(), configDN,
                                        attributeList, failedACIMsgs);
    }
    if(hasAci) {
        List<Attribute> attributeList = entry.getAttribute(aciType);
        validAcis += addAciAttributeList(aciCopy, entry.getDN(), configDN,
          validAcis += addAciAttributeList(aciList, entry.getDN(), configDN,
                                         attributeList, failedACIMsgs);
    }
    // Replace the ACI list with the copy.
    aciList = aciCopy;
    }
    finally
    {
      lock.writeLock().unlock();
    }
    return validAcis;
  }
@@ -223,7 +244,7 @@
   *                      exceptions.
   * @return The number of valid attribute values added to the ACI list.
   */
  private static int addAciAttributeList(LinkedHashMap<DN,List<Aci>> aciList,
  private static int addAciAttributeList(DITCacheMap<List<Aci>> aciList,
                                         DN dn, DN configDN,
                                         List<Attribute> attributeList,
                                         LinkedList<Message> failedACIMsgs) {
@@ -271,33 +292,37 @@
   * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was
   * seen in the entry.
   */
  public synchronized void modAciOldNewEntry(Entry oldEntry, Entry newEntry,
  public void modAciOldNewEntry(Entry oldEntry, Entry newEntry,
                                             boolean hasAci,
                                             boolean hasGlobalAci) {
      // Copy the ACI list.
      LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
    lock.writeLock().lock();
    try
    {
      LinkedList<Message>failedACIMsgs=new LinkedList<Message>();
      //Process "aci" attribute types.
      if(hasAci) {
          aciCopy.remove(oldEntry.getDN());
          aciList.remove(oldEntry.getDN());
          List<Attribute> attributeList =
                  newEntry.getOperationalAttribute(aciType);
          addAciAttributeList(aciCopy,newEntry.getDN(), configDN,
          addAciAttributeList(aciList,newEntry.getDN(), configDN,
                              attributeList, failedACIMsgs);
      }
      //Process global "ds-cfg-global-aci" attribute type. The oldentry
      //DN is checked to verify it is equal to the config DN. If not those
      //attributes are skipped.
      if(hasGlobalAci && oldEntry.getDN().equals(configDN)) {
          aciCopy.remove(DN.nullDN());
          aciList.remove(DN.nullDN());
          List<Attribute> attributeList =
                  newEntry.getAttribute(globalAciType);
          addAciAttributeList(aciCopy, DN.nullDN(), configDN,
          addAciAttributeList(aciList, DN.nullDN(), configDN,
                              attributeList, failedACIMsgs);
      }
      // Replace the ACI list with the copy.
      aciList = aciCopy;
    }
    finally
    {
      lock.writeLock().unlock();
    }
  }
  /**
@@ -308,7 +333,7 @@
   * @param dn The DN to use as the key.
   * @param acis The ACI to be added.
   */
  private static void addAci(LinkedHashMap<DN,List<Aci>> aciList, DN dn,
  private static void addAci(DITCacheMap<List<Aci>> aciList, DN dn,
                             List<Aci> acis)
  {
    if(aciList.containsKey(dn)) {
@@ -331,18 +356,32 @@
   * seen in the entry.
   * @return  True if the ACI set was deleted.
   */
  public synchronized boolean removeAci(Entry entry,  boolean hasAci,
  public boolean removeAci(Entry entry,  boolean hasAci,
                                                      boolean hasGlobalAci) {
      // Copy the ACI list.
      LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
    DN entryDN = entry.getDN();
      if(hasGlobalAci && entry.getDN().equals(configDN) &&
         aciCopy.remove(DN.nullDN()) == null)
    lock.writeLock().lock();
    try
    {
      if (hasGlobalAci && entryDN.equals(configDN) &&
          aciList.remove(DN.nullDN()) == null)
      {
          return false;
      if(hasAci && aciCopy.remove(entry.getDN()) == null)
      }
      if (hasAci && aciList.remove(entryDN) == null)
      {
          return false;
      // Replace the ACI list with the copy.
      aciList = aciCopy;
      }
      if (!hasGlobalAci && !hasAci)
      {
        return aciList.removeSubtree(entryDN, null);
      }
    }
    finally
    {
      lock.writeLock().unlock();
    }
      return true;
  }
@@ -351,11 +390,13 @@
   * @param backend  The backend to check if each DN is handled by that
   * backend.
   */
  public synchronized void removeAci(Backend backend) {
    // Copy the ACI list.
    LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
  public void removeAci(Backend backend) {
    Iterator<Map.Entry<DN,List<Aci>>> iterator = aciCopy.entrySet().iterator();
    lock.writeLock().lock();
    try
    {
      Iterator<Map.Entry<DN,List<Aci>>> iterator =
              aciList.entrySet().iterator();
    while (iterator.hasNext())
    {
      Map.Entry<DN,List<Aci>> mapEntry = iterator.next();
@@ -364,9 +405,11 @@
        iterator.remove();
      }
    }
    // Replace the ACI list with the copy.
    aciList = aciCopy;
    }
    finally
    {
      lock.writeLock().unlock();
    }
  }
  /**
@@ -375,20 +418,29 @@
   * @param oldDN The DN of the original entry that was moved.
   * @param newDN The DN of the new entry.
   */
  public synchronized void renameAci(DN oldDN, DN newDN ) {
    LinkedHashMap<DN, List<Aci>> newCopyList =
            new LinkedHashMap<DN, List<Aci>>();
  public void renameAci(DN oldDN, DN newDN ) {
    int oldRDNCount=oldDN.getNumComponents();
    int newRDNCount=newDN.getNumComponents();
    for (Map.Entry<DN,List<Aci>> hashEntry : aciList.entrySet()) {
    lock.writeLock().lock();
    try
    {
      Map<DN,List<Aci>> tempAciList = new HashMap<DN,List<Aci>>();
      Iterator<Map.Entry<DN,List<Aci>>> iterator =
              aciList.entrySet().iterator();
      while (iterator.hasNext()) {
        Map.Entry<DN,List<Aci>> hashEntry = iterator.next();
      if(hashEntry.getKey().isDescendantOf(oldDN)) {
        int keyRDNCount=hashEntry.getKey().getNumComponents();
        int keepRDNCount=keyRDNCount - oldRDNCount;
        RDN[] newRDNs = new RDN[keepRDNCount + newRDNCount];
        for (int i=0; i < keepRDNCount; i++)
          for (int i=0; i < keepRDNCount; i++) {
          newRDNs[i] = hashEntry.getKey().getRDN(i);
        for (int i=keepRDNCount, j=0; j < newRDNCount; i++,j++)
          }
          for (int i=keepRDNCount, j=0; j < newRDNCount; i++,j++) {
          newRDNs[i] = newDN.getRDN(j);
          }
        DN relocateDN=new DN(newRDNs);
        List<Aci> acis = new LinkedList<Aci>();
        for(Aci aci : hashEntry.getValue()) {
@@ -405,11 +457,15 @@
            logError(message);
          }
        }
        newCopyList.put(relocateDN, acis);
      }  else
        newCopyList.put(hashEntry.getKey(), hashEntry.getValue());
          tempAciList.put(relocateDN, acis);
          iterator.remove();
    }
    // Replace the ACI list with the copy.
    aciList = newCopyList;
      }
      aciList.putAll(tempAciList);
    }
    finally
    {
      lock.writeLock().unlock();
    }
  }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
@@ -240,15 +240,12 @@
    private void doPostDelete(Entry deletedEntry)
    {
      // This entry might have both global and aci attribute types.
      boolean hasAci, hasGlobalAci = false;
      if ((hasAci = deletedEntry
          .hasOperationalAttribute(AciHandler.aciType))
          || (hasGlobalAci = deletedEntry
              .hasAttribute(AciHandler.globalAciType)))
      {
      boolean hasAci = deletedEntry.hasOperationalAttribute(
              AciHandler.aciType);
      boolean hasGlobalAci = deletedEntry.hasAttribute(
              AciHandler.globalAciType);
        aciList.removeAci(deletedEntry, hasAci, hasGlobalAci);
      }
    }
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -1977,6 +1977,28 @@
           */
          EntryID entryID = new EntryID(data);
          DN subordinateDN = DN.decode(ByteString.wrap(key.getData()));
          // Invoke any subordinate delete plugins on the entry.
          if (!deleteOperation.isSynchronizationOperation())
          {
            Entry subordinateEntry = id2entry.get(
                    txn, entryID, LockMode.DEFAULT);
            PluginConfigManager pluginManager =
              DirectoryServer.getPluginConfigManager();
            PluginResult.SubordinateDelete pluginResult =
              pluginManager.invokeSubordinateDeletePlugins(
                  deleteOperation, subordinateEntry);
            if (!pluginResult.continueProcessing())
            {
              Message message =
                      ERR_JEB_DELETE_ABORTED_BY_SUBORDINATE_PLUGIN.get(
                      subordinateDN.toString());
              throw new DirectoryException(
                  DirectoryServer.getServerErrorResultCode(), message);
            }
          }
          deleteEntry(txn, indexBuffer, true, entryDN, subordinateDN, entryID);
          subordinateEntriesDeleted++;
opends/src/server/org/opends/server/core/AuthenticatedUsers.java
@@ -22,20 +22,25 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import java.util.HashSet;
import java.util.Set;
import org.opends.messages.Message;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.DITCacheMap;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.Entry;
import org.opends.server.types.operation.PostResponseAddOperation;
import org.opends.server.types.operation.PostResponseDeleteOperation;
@@ -43,6 +48,8 @@
import org.opends.server.types.operation.PostResponseModifyDNOperation;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
/**
 * This class provides a data structure which maps an authenticated user DN to
 * the set of client connections authenticated as that user.  Note that a single
@@ -56,11 +63,17 @@
public class AuthenticatedUsers
       implements ChangeNotificationListener
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The mapping between authenticated user DNs and the associated client
  // connection objects.
  private ConcurrentHashMap<DN,CopyOnWriteArraySet<ClientConnection>>
               userMap;
  private DITCacheMap<CopyOnWriteArraySet<ClientConnection>> userMap;
  // Lock to protect internal data structures.
  private final ReentrantReadWriteLock lock;
  /**
@@ -68,7 +81,8 @@
   */
  public AuthenticatedUsers()
  {
    userMap = new ConcurrentHashMap<DN,CopyOnWriteArraySet<ClientConnection>>();
    userMap = new DITCacheMap<CopyOnWriteArraySet<ClientConnection>>();
    lock = new ReentrantReadWriteLock();
    DirectoryServer.registerChangeNotificationListener(this);
  }
@@ -83,9 +97,13 @@
   * @param  clientConnection  The client connection over which the user is
   *                           authenticated.
   */
  public synchronized void put(DN userDN, ClientConnection clientConnection)
  public void put(DN userDN, ClientConnection clientConnection)
  {
    CopyOnWriteArraySet<ClientConnection> connectionSet = userMap.get(userDN);
    lock.writeLock().lock();
    try
    {
      CopyOnWriteArraySet<ClientConnection> connectionSet =
              userMap.get(userDN);
    if (connectionSet == null)
    {
      connectionSet = new CopyOnWriteArraySet<ClientConnection>();
@@ -97,6 +115,11 @@
      connectionSet.add(clientConnection);
    }
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -108,9 +131,13 @@
   * @param  clientConnection  The client connection over which the user is
   *                           authenticated.
   */
  public synchronized void remove(DN userDN, ClientConnection clientConnection)
  public void remove(DN userDN, ClientConnection clientConnection)
  {
    CopyOnWriteArraySet<ClientConnection> connectionSet = userMap.get(userDN);
    lock.writeLock().lock();
    try
    {
      CopyOnWriteArraySet<ClientConnection> connectionSet =
              userMap.get(userDN);
    if (connectionSet != null)
    {
      connectionSet.remove(clientConnection);
@@ -120,6 +147,11 @@
      }
    }
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -136,8 +168,16 @@
   */
  synchronized CopyOnWriteArraySet<ClientConnection> get(DN userDN)
  {
    lock.readLock().lock();
    try
    {
    return userMap.get(userDN);
  }
    finally
    {
      lock.readLock().unlock();
    }
  }
@@ -172,11 +212,22 @@
                   PostResponseDeleteOperation deleteOperation,
                   Entry entry)
  {
    // Identify any client connections that may be authenticated or
    // authorized as the user whose entry has been deleted and terminate them.
    CopyOnWriteArraySet<ClientConnection> connectionSet =
         userMap.remove(entry.getDN());
    if (connectionSet != null)
    // Identify any client connections that may be authenticated
    // or authorized as the user whose entry has been deleted and
    // terminate them.
    Set<CopyOnWriteArraySet<ClientConnection>> arraySet =
            new HashSet<CopyOnWriteArraySet<ClientConnection>>();
    lock.writeLock().lock();
    try
    {
      userMap.removeSubtree(entry.getDN(), arraySet);
    }
    finally
    {
      lock.writeLock().unlock();
    }
    for (CopyOnWriteArraySet<ClientConnection>
            connectionSet : arraySet)
    {
      for (ClientConnection conn : connectionSet)
      {
@@ -203,9 +254,12 @@
                   PostResponseModifyOperation modifyOperation,
                   Entry oldEntry, Entry newEntry)
  {
    // Identify any client connections that may be authenticated or authorized
    // as the user whose entry has been modified and update them with the latest
    // version of the entry.
    // Identify any client connections that may be authenticated
    // or authorized as the user whose entry has been modified
    // and update them with the latest version of the entry.
    lock.writeLock().lock();
    try
    {
    CopyOnWriteArraySet<ClientConnection> connectionSet =
         userMap.get(oldEntry.getDN());
    if (connectionSet != null)
@@ -216,6 +270,11 @@
      }
    }
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -232,31 +291,107 @@
                   PostResponseModifyDNOperation modifyDNOperation,
                   Entry oldEntry, Entry newEntry)
  {
    // Identify any client connections that may be authenticated or authorized
    // as the user whose entry has been modified and update them with the latest
    // version of the entry.
    CopyOnWriteArraySet<ClientConnection> connectionSet =
         userMap.remove(oldEntry.getDN());
    if (connectionSet != null)
    {
      synchronized (this)
      {
        CopyOnWriteArraySet<ClientConnection> existingNewSet =
             userMap.get(newEntry.getDN());
        if (existingNewSet == null)
        {
          userMap.put(newEntry.getDN(), connectionSet);
        }
        else
        {
          existingNewSet.addAll(connectionSet);
        }
      }
    String oldDNString = oldEntry.getDN().toNormalizedString();
    String newDNString = newEntry.getDN().toNormalizedString();
    // Identify any client connections that may be authenticated
    // or authorized as the user whose entry has been modified
    // and update them with the latest version of the entry.
    lock.writeLock().lock();
    try
    {
      Set<CopyOnWriteArraySet<ClientConnection>> arraySet =
        new HashSet<CopyOnWriteArraySet<ClientConnection>>();
      userMap.removeSubtree(oldEntry.getDN(), arraySet);
      for (CopyOnWriteArraySet<ClientConnection>
              connectionSet : arraySet)
      {
        DN authNDN = null;
        DN authZDN = null;
        DN newAuthNDN = null;
        DN newAuthZDN = null;
        CopyOnWriteArraySet<ClientConnection> newAuthNSet = null;
        CopyOnWriteArraySet<ClientConnection> newAuthZSet = null;
      for (ClientConnection conn : connectionSet)
      {
        conn.updateAuthenticationInfo(oldEntry, newEntry);
          if (authNDN == null)
          {
            authNDN = conn.getAuthenticationInfo().getAuthenticationDN();
            try
            {
              StringBuilder builder = new StringBuilder(
                  authNDN.toNormalizedString());
              int oldDNIndex = builder.lastIndexOf(oldDNString);
              builder.replace(oldDNIndex, builder.length(),
                      newDNString);
              String newAuthNDNString = builder.toString();
              newAuthNDN = DN.decode(newAuthNDNString);
      }
            catch (Exception e)
            {
              // Shouldnt happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
          if (authZDN == null)
          {
            authZDN = conn.getAuthenticationInfo().getAuthorizationDN();
            try
            {
              StringBuilder builder = new StringBuilder(
                  authZDN.toNormalizedString());
              int oldDNIndex = builder.lastIndexOf(oldDNString);
              builder.replace(oldDNIndex, builder.length(),
                      newDNString);
              String newAuthZDNString = builder.toString();
              newAuthZDN = DN.decode(newAuthZDNString);
            }
            catch (Exception e)
            {
              // Shouldnt happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
          if ((newAuthNDN != null) && (authNDN != null) &&
               authNDN.isDescendantOf(oldEntry.getDN()))
          {
            if (newAuthNSet == null)
            {
              newAuthNSet = new CopyOnWriteArraySet<ClientConnection>();
            }
            conn.getAuthenticationInfo().setAuthenticationDN(newAuthNDN);
            newAuthNSet.add(conn);
          }
          if ((newAuthZDN != null) && (authZDN != null) &&
               authZDN.isDescendantOf(oldEntry.getDN()))
          {
            if (newAuthZSet == null)
            {
              newAuthZSet = new CopyOnWriteArraySet<ClientConnection>();
            }
            conn.getAuthenticationInfo().setAuthorizationDN(newAuthZDN);
            newAuthZSet.add(conn);
          }
        }
        if ((newAuthNDN != null) && (newAuthNSet != null))
        {
          userMap.put(newAuthNDN, newAuthNSet);
        }
        if ((newAuthZDN != null) && (newAuthZSet != null))
        {
          userMap.put(newAuthZDN, newAuthZSet);
        }
      }
    }
    finally
    {
      lock.writeLock().unlock();
    }
  }
}
opends/src/server/org/opends/server/core/GroupManager.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2007-2008 Sun Microsystems, Inc.
 *      Copyright 2007-2010 Sun Microsystems, Inc.
 */
package org.opends.server.core;
@@ -31,6 +31,7 @@
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.opends.messages.Message;
import org.opends.server.admin.ClassPropertyDefinition;
@@ -44,6 +45,7 @@
import org.opends.server.api.Backend;
import org.opends.server.api.BackendInitializationListener;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.DITCacheMap;
import org.opends.server.api.Group;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.debug.DebugTracer;
@@ -55,6 +57,7 @@
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
@@ -103,7 +106,7 @@
  //Used by group instances to determine if new groups have been
  //registered or groups deleted.
  private long refreshToken=0;
  private volatile long refreshToken=0;
  // A mapping between the DNs of the config entries and the associated
@@ -112,7 +115,10 @@
  // A mapping between the DNs of all group entries and the corresponding
  // group instances.
  private ConcurrentHashMap<DN,Group> groupInstances;
  private DITCacheMap<Group> groupInstances;
  // Lock to protect internal data structures.
  private final ReentrantReadWriteLock lock;
@@ -122,7 +128,9 @@
  public GroupManager()
  {
    groupImplementations = new ConcurrentHashMap<DN,Group>();
    groupInstances       = new ConcurrentHashMap<DN,Group>();
    groupInstances       = new DITCacheMap<Group>();
    lock = new ReentrantReadWriteLock();
    DirectoryServer.registerBackendInitializationListener(this);
    DirectoryServer.registerChangeNotificationListener(this);
@@ -289,6 +297,9 @@
    Group group = groupImplementations.remove(configuration.dn());
    if (group != null)
    {
      lock.writeLock().lock();
      try
      {
      Iterator<Group> iterator = groupInstances.values().iterator();
      while (iterator.hasNext())
      {
@@ -298,6 +309,11 @@
          iterator.remove();
        }
      }
      }
      finally
      {
        lock.writeLock().unlock();
      }
      group.finalizeGroupImplementation();
    }
@@ -360,6 +376,9 @@
        Group group = groupImplementations.remove(configuration.dn());
        if (group != null)
        {
          lock.writeLock().lock();
          try
          {
          Iterator<Group> iterator = groupInstances.values().iterator();
          while (iterator.hasNext())
          {
@@ -369,6 +388,11 @@
              iterator.remove();
            }
          }
          }
          finally
          {
            lock.writeLock().unlock();
          }
          group.finalizeGroupImplementation();
        }
@@ -543,7 +567,18 @@
   */
  public Iterable<Group> getGroupInstances()
  {
    return groupInstances.values();
    lock.readLock().lock();
    try
    {
      // Return a copy to protect from structural changes.
      ArrayList<Group> values = new ArrayList<Group>();
      values.addAll(groupInstances.values());
      return values;
    }
    finally
    {
      lock.readLock().unlock();
    }
  }
@@ -559,7 +594,18 @@
   */
  public Group getGroupInstance(DN entryDN)
  {
    Group group = groupInstances.get(entryDN);
    Group group = null;
    lock.readLock().lock();
    try
    {
      group = groupInstances.get(entryDN);
    }
    finally
    {
      lock.readLock().unlock();
    }
    if (group == null)
    {
      // FIXME -- Should we try to retrieve the corresponding entry and see if
@@ -654,6 +700,9 @@
          continue;
        }
        lock.writeLock().lock();
        try
        {
        for (SearchResultEntry entry : internalSearch.getSearchEntries())
        {
          try
@@ -674,6 +723,11 @@
          }
        }
      }
        finally
        {
          lock.writeLock().unlock();
        }
      }
    }
  }
@@ -685,6 +739,9 @@
   */
  public void performBackendFinalizationProcessing(Backend backend)
  {
    lock.writeLock().lock();
    try
    {
    Iterator<Map.Entry<DN,Group>> iterator =
         groupInstances.entrySet().iterator();
    while (iterator.hasNext())
@@ -697,6 +754,11 @@
      }
    }
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -719,11 +781,16 @@
        }
      }
    }
    synchronized (groupInstances)
    lock.writeLock().lock();
    try
    {
      createAndRegisterGroup(entry);
      refreshToken++;
    }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -746,11 +813,16 @@
        }
      }
    }
    synchronized (groupInstances)
    lock.writeLock().lock();
    try
    {
      groupInstances.remove(entry.getDN());
      groupInstances.removeSubtree(entry.getDN(), null);
      refreshToken++;
    }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -775,21 +847,24 @@
      }
    }
    if (groupInstances.containsKey(oldEntry.getDN()))
    lock.writeLock().lock();
    try
    {
      synchronized (groupInstances)
      if (groupInstances.containsKey(oldEntry.getDN()))
      {
        if (! oldEntry.getDN().equals(newEntry.getDN()))
        {
          // This should never happen, but check for it anyway.
          groupInstances.remove(oldEntry.getDN());
        }
        createAndRegisterGroup(newEntry);
        refreshToken++;
      }
    }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -816,14 +891,44 @@
      }
    }
    if (groupInstances.containsKey(oldEntry.getDN()))
    lock.writeLock().lock();
    try
    {
      synchronized (groupInstances)
      Set<Group> groupSet = new HashSet<Group>();
      groupInstances.removeSubtree(oldEntry.getDN(), groupSet);
      String oldDNString = oldEntry.getDN().toNormalizedString();
      String newDNString = newEntry.getDN().toNormalizedString();
      for (Group group : groupSet)
      {
        createAndRegisterGroup(newEntry);
        groupInstances.remove(oldEntry.getDN());
        StringBuilder builder = new StringBuilder(
                group.getGroupDN().toNormalizedString());
        int oldDNIndex = builder.lastIndexOf(oldDNString);
        builder.replace(oldDNIndex, builder.length(),
                newDNString);
        String groupDNString = builder.toString();
        DN groupDN = DN.NULL_DN;
        try
        {
          groupDN = DN.decode(groupDNString);
        }
        catch (DirectoryException de)
        {
          // Should not happen but if it does all we
          // can do here is debug log it and continue.
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          continue;
        }
        group.setGroupDN(groupDN);
        groupInstances.put(groupDN, group);
      }
        refreshToken++;
      }
    finally
    {
      lock.writeLock().unlock();
    }
  }
@@ -845,8 +950,17 @@
        if (groupImplementation.isGroupDefinition(entry))
        {
          Group groupInstance = groupImplementation.newInstance(entry);
          lock.writeLock().lock();
          try
          {
          groupInstances.put(entry.getDN(), groupInstance);
        }
          finally
          {
            lock.writeLock().unlock();
          }
        }
      }
      catch (Exception e)
      {
@@ -869,8 +983,16 @@
   */
  void deregisterAllGroups()
  {
    lock.writeLock().lock();
    try
    {
    groupInstances.clear();
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
  /**
opends/src/server/org/opends/server/core/PluginConfigManager.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.core;
@@ -136,6 +136,7 @@
  private DirectoryServerPlugin[] searchResultEntryPlugins;
  private DirectoryServerPlugin[] searchResultReferencePlugins;
  private DirectoryServerPlugin[] subordinateModifyDNPlugins;
  private DirectoryServerPlugin[] subordinateDeletePlugins;
  private DirectoryServerPlugin[] intermediateResponsePlugins;
@@ -222,6 +223,7 @@
    searchResultEntryPlugins           = new DirectoryServerPlugin[0];
    searchResultReferencePlugins       = new DirectoryServerPlugin[0];
    subordinateModifyDNPlugins         = new DirectoryServerPlugin[0];
    subordinateDeletePlugins           = new DirectoryServerPlugin[0];
    intermediateResponsePlugins        = new DirectoryServerPlugin[0];
    registeredPlugins                  =
         new ConcurrentHashMap<DN,
@@ -481,6 +483,7 @@
      case SEARCHRESULTENTRY:      return PluginType.SEARCH_RESULT_ENTRY;
      case SEARCHRESULTREFERENCE:  return PluginType.SEARCH_RESULT_REFERENCE;
      case SUBORDINATEMODIFYDN:    return PluginType.SUBORDINATE_MODIFY_DN;
      case SUBORDINATEDELETE:      return PluginType.SUBORDINATE_DELETE;
      case INTERMEDIATERESPONSE:   return PluginType.INTERMEDIATE_RESPONSE;
      case POSTSYNCHRONIZATIONADD:
                return PluginType.POST_SYNCHRONIZATION_ADD;
@@ -863,6 +866,11 @@
            addPlugin(subordinateModifyDNPlugins, plugin, t,
                pluginRootConfig.getPluginOrderSubordinateModifyDN());
        break;
      case SUBORDINATE_DELETE:
        subordinateDeletePlugins =
            addPlugin(subordinateDeletePlugins, plugin, t,
                pluginRootConfig.getPluginOrderSubordinateDelete());
        break;
      case INTERMEDIATE_RESPONSE:
        intermediateResponsePlugins =
            addPlugin(intermediateResponsePlugins, plugin, t,
@@ -1373,6 +1381,10 @@
          subordinateModifyDNPlugins =
               removePlugin(subordinateModifyDNPlugins, plugin);
          break;
        case SUBORDINATE_DELETE:
          subordinateDeletePlugins =
               removePlugin(subordinateDeletePlugins, plugin);
          break;
        case INTERMEDIATE_RESPONSE:
          intermediateResponsePlugins =
               removePlugin(intermediateResponsePlugins, plugin);
@@ -5289,6 +5301,84 @@
  /**
   * Invokes the set of subordinate delete plugins that have been configured
   * in the Directory Server.
   *
   * @param  deleteOperation  The delete operation with which the
   *                          subordinate entry is associated.
   * @param  entry            The subordinate entry being deleted.
   *
   * @return The result of processing the subordinate delete plugins.
   */
  public PluginResult.SubordinateDelete invokeSubordinateDeletePlugins(
              DeleteOperation deleteOperation, Entry entry)
  {
    PluginResult.SubordinateDelete result = null;
    for (DirectoryServerPlugin p : subordinateDeletePlugins)
    {
      if (deleteOperation.isInternalOperation() &&
          (! p.invokeForInternalOperations()))
      {
        continue;
      }
      try
      {
        DirectoryServerPlugin<? extends PluginCfg> gp =
             (DirectoryServerPlugin<? extends PluginCfg>) p;
        result = gp.processSubordinateDelete(deleteOperation, entry);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        Message message =
            ERR_PLUGIN_SUBORDINATE_DELETE_PLUGIN_EXCEPTION.get(
                String.valueOf(p.getPluginEntryDN()),
                deleteOperation.getConnectionID(),
                deleteOperation.getOperationID(),
                stackTraceToSingleLineString(e));
        logError(message);
        return PluginResult.SubordinateDelete.stopProcessing(
            DirectoryServer.getServerErrorResultCode(), message);
      }
      if (result == null)
      {
        Message message =
            ERR_PLUGIN_SUBORDINATE_DELETE_PLUGIN_RETURNED_NULL.get(
                        String.valueOf(p.getPluginEntryDN()),
                        deleteOperation.getConnectionID(),
                        String.valueOf(deleteOperation.getOperationID()));
        logError(message);
        return PluginResult.SubordinateDelete.stopProcessing(
            DirectoryServer.getServerErrorResultCode(), message);
      }
      else if (! result.continuePluginProcessing())
      {
        return result;
      }
    }
    if (result == null)
    {
      // This should only happen if there were no subordinate modify DN plugins
      // registered, which is fine.
      result = PluginResult.SubordinateDelete.continueOperationProcessing();
    }
    return result;
  }
  /**
   * Invokes the set of intermediate response plugins that have been configured
   * in the Directory Server.
   *
opends/src/server/org/opends/server/core/SubentryManager.java
@@ -34,6 +34,7 @@
import org.opends.server.api.Backend;
import org.opends.server.api.BackendInitializationListener;
import org.opends.server.api.DITCacheMap;
import org.opends.server.api.SubentryChangeListener;
import org.opends.server.api.plugin.InternalDirectoryServerPlugin;
import org.opends.server.api.plugin.PluginResult;
@@ -101,6 +102,9 @@
  // A mapping between the DNs and applicable collective subentries.
  private HashMap<DN,List<SubEntry>> dn2CollectiveSubEntry;
  // A mapping between subentry DNs and subentry objects.
  private DITCacheMap<SubEntry> dit2SubEntry;
  // Internal search all operational attributes.
  private LinkedHashSet<String> requestAttrs;
@@ -142,6 +146,7 @@
    dn2SubEntry = new HashMap<DN,List<SubEntry>>();
    dn2CollectiveSubEntry = new HashMap<DN,List<SubEntry>>();
    dit2SubEntry = new DITCacheMap<SubEntry>();
    changeListeners =
            new CopyOnWriteArrayList<SubentryChangeListener>();
@@ -228,6 +233,7 @@
          dn2SubEntry.put(subDN, subList);
        }
      }
      dit2SubEntry.put(entry.getDN(), subEntry);
      subList.add(subEntry);
    }
    finally
@@ -258,6 +264,7 @@
          SubEntry subEntry = listIterator.next();
          if (subEntry.getDN().equals(entry.getDN()))
          {
            dit2SubEntry.remove(entry.getDN());
            listIterator.remove();
            removed = true;
            break;
@@ -283,6 +290,7 @@
          SubEntry subEntry = listIterator.next();
          if (subEntry.getDN().equals(entry.getDN()))
          {
            dit2SubEntry.remove(entry.getDN());
            listIterator.remove();
            removed = true;
            break;
@@ -657,6 +665,7 @@
          SubEntry subEntry = listIterator.next();
          if (backend.handlesEntry(subEntry.getDN()))
          {
            dit2SubEntry.remove(subEntry.getDN());
            listIterator.remove();
            // Notify change listeners.
@@ -694,6 +703,7 @@
          SubEntry subEntry = listIterator.next();
          if (backend.handlesEntry(subEntry.getDN()))
          {
            dit2SubEntry.remove(subEntry.getDN());
            listIterator.remove();
            // Notify change listeners.
@@ -731,6 +741,9 @@
  {
    if (entry.isSubentry() || entry.isLDAPSubentry())
    {
      lock.writeLock().lock();
      try
      {
      try
      {
        addSubEntry(entry);
@@ -762,13 +775,21 @@
        // FIXME -- Handle this.
      }
    }
      finally
      {
        lock.writeLock().unlock();
      }
    }
  }
  private void doPostDelete(Entry entry)
  {
    if (entry.isSubentry() || entry.isLDAPSubentry())
    lock.writeLock().lock();
    try
    {
      removeSubEntry(entry);
      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getDN()))
      {
        removeSubEntry(subEntry.getEntry());
      // Notify change listeners.
      for (SubentryChangeListener changeListener :
@@ -776,7 +797,7 @@
      {
        try
        {
          changeListener.handleSubentryDelete(entry);
            changeListener.handleSubentryDelete(subEntry.getEntry());
        }
        catch (Exception e)
        {
@@ -788,11 +809,19 @@
      }
    }
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
  private void doPostModify(Entry oldEntry, Entry newEntry)
  {
    boolean notify = false;
    lock.writeLock().lock();
    try
    {
    if (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
    {
      removeSubEntry(oldEntry);
@@ -837,24 +866,45 @@
      }
    }
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
  private void doPostModifyDN(Entry oldEntry, Entry newEntry)
  {
    if (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
    {
      removeSubEntry(oldEntry);
    String oldDNString = oldEntry.getDN().toNormalizedString();
    String newDNString = newEntry.getDN().toNormalizedString();
    lock.writeLock().lock();
      try
      {
      Collection<SubEntry> setToDelete =
              dit2SubEntry.getSubtree(oldEntry.getDN());
      for (SubEntry subentry : setToDelete)
      {
        removeSubEntry(subentry.getEntry());
        oldEntry = subentry.getEntry();
        try
        {
          StringBuilder builder = new StringBuilder(
              subentry.getEntry().getDN().toNormalizedString());
          int oldDNIndex = builder.lastIndexOf(oldDNString);
          builder.replace(oldDNIndex, builder.length(),
                  newDNString);
          String subentryDNString = builder.toString();
          newEntry = subentry.getEntry().duplicate(false);
          newEntry.setDN(DN.decode(subentryDNString));
        addSubEntry(newEntry);
      }
      catch (Exception e)
      {
          // Shouldnt happen.
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        // FIXME -- Handle this.
      }
      // Notify change listeners.
@@ -876,6 +926,11 @@
      }
    }
  }
    finally
    {
      lock.writeLock().unlock();
    }
  }
  /**
   * {@inheritDoc}
@@ -920,14 +975,18 @@
  {
    Entry entry = deleteOperation.getEntryToDelete();
    if (entry.isSubentry() || entry.isLDAPSubentry())
    lock.readLock().lock();
    try
    {
      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getDN()))
    {
      for (SubentryChangeListener changeListener :
              changeListeners)
      {
        try
        {
          changeListener.checkSubentryDeleteAcceptable(entry);
            changeListener.checkSubentryDeleteAcceptable(
                    subEntry.getEntry());
        }
        catch (DirectoryException de)
        {
@@ -941,6 +1000,11 @@
        }
      }
    }
    }
    finally
    {
      lock.readLock().unlock();
    }
    return PluginResult.PreOperation.continueOperationProcessing();
  }
@@ -991,9 +1055,36 @@
  {
    Entry oldEntry = modifyDNOperation.getOriginalEntry();
    Entry newEntry = modifyDNOperation.getUpdatedEntry();
    String oldDNString = oldEntry.getDN().toNormalizedString();
    String newDNString = newEntry.getDN().toNormalizedString();
    if (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
    lock.readLock().lock();
    try
    {
      Collection<SubEntry> setToDelete =
              dit2SubEntry.getSubtree(oldEntry.getDN());
      for (SubEntry subentry : setToDelete)
      {
        oldEntry = subentry.getEntry();
        try
        {
          StringBuilder builder = new StringBuilder(
              subentry.getEntry().getDN().toNormalizedString());
          int oldDNIndex = builder.lastIndexOf(oldDNString);
          builder.replace(oldDNIndex, builder.length(),
                  newDNString);
          String subentryDNString = builder.toString();
          newEntry = subentry.getEntry().duplicate(false);
          newEntry.setDN(DN.decode(subentryDNString));
        }
        catch (Exception e)
        {
          // Shouldnt happen.
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      for (SubentryChangeListener changeListener :
              changeListeners)
      {
@@ -1014,6 +1105,11 @@
        }
      }
    }
    }
    finally
    {
      lock.readLock().unlock();
    }
    return PluginResult.PreOperation.continueOperationProcessing();
  }
opends/src/server/org/opends/server/crypto/CryptoManagerSync.java
@@ -551,6 +551,9 @@
    RDN srcRDN = entry.getDN().getRDN();
    // Only process the entry if it has the expected form of RDN.
    // FIXME: Technically it is possible to perform a subtree in
    // this case however such subtree delete would essentially be
    // removing configuration branches which should not happen.
    if (!srcRDN.isMultiValued() &&
         srcRDN.getAttributeType(0).equals(attrAlias))
    {
@@ -644,5 +647,8 @@
       Entry newEntry)
  {
    // No implementation required.
    // FIXME: Technically it is possible to perform a subtree modDN
    // in this case however such subtree modDN would essentially be
    // moving configuration branches which should not happen.
  }
}
opends/src/server/org/opends/server/extensions/DynamicGroup.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
@@ -225,6 +225,17 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void setGroupDN(DN groupDN)
  {
    groupEntryDN = groupDN;
  }
  /**
   * Retrieves the set of member URLs for this dynamic group.  The returned set
   * must not be altered by the caller.
   *
opends/src/server/org/opends/server/extensions/StaticGroup.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
@@ -346,6 +346,17 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void setGroupDN(DN groupDN)
  {
    groupEntryDN = groupDN;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean supportsNestedGroups()
  {
opends/src/server/org/opends/server/extensions/VirtualStaticGroup.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
@@ -234,6 +234,17 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void setGroupDN(DN groupDN)
  {
    groupEntryDN = groupDN;
  }
  /**
   * Retrieves the DN of the target group for this virtual static group.
   *
   * @return  The DN of the target group for this virtual static group.
opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
@@ -35,6 +35,7 @@
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -53,6 +54,7 @@
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.api.plugin.*;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.loggers.debug.DebugTracer;
@@ -136,6 +138,12 @@
   */
  public static final String MODIFYDN_DNS="modifyDNs";
  /**
   * Used to save a set in the delete operation attachment map that
   * holds the subordinate entry DNs related to a delete operation.
   */
  public static final String DELETE_DNS="deleteDNs";
  //The buffered reader that is used to read the log file by the background
  //thread.
  private BufferedReader reader;
@@ -163,6 +171,7 @@
        case POST_OPERATION_DELETE:
        case POST_OPERATION_MODIFY_DN:
        case SUBORDINATE_MODIFY_DN:
        case SUBORDINATE_DELETE:
          // These are acceptable.
          break;
@@ -305,6 +314,7 @@
        case POSTOPERATIONDELETE:
        case POSTOPERATIONMODIFYDN:
        case SUBORDINATEMODIFYDN:
        case SUBORDINATEDELETE:
          // These are acceptable.
          break;
        default:
@@ -370,23 +380,18 @@
      return PluginResult.PostOperation.continueOperationProcessing();
    }
    if (modifyDNOperation.getNewSuperior() == null)
    {
      // The entry was simply renamed below the same parent.
      DN oldEntryDN=modifyDNOperation.getOriginalEntry().getDN();
      DN newEntryDN=modifyDNOperation.getUpdatedEntry().getDN();
      Map<DN,DN> modDNmap=new LinkedHashMap<DN,DN>();
      modDNmap.put(oldEntryDN, newEntryDN);
      processModifyDN(modDNmap,(interval != 0));
    }
    else
    {
      // The entry was moved below a new parent.  Use the saved map of old DNs
      // and new DNs from the operation attachment.
      Map<DN,DN> modDNmap =
           (Map<DN, DN>) modifyDNOperation.getAttachment(MODIFYDN_DNS);
      processModifyDN(modDNmap, (interval != 0));
    if(modDNmap == null)
    {
      modDNmap=new LinkedHashMap<DN,DN>();
      modifyDNOperation.setAttachment(MODIFYDN_DNS, modDNmap);
    }
    DN oldEntryDN=modifyDNOperation.getOriginalEntry().getDN();
    DN newEntryDN=modifyDNOperation.getUpdatedEntry().getDN();
    modDNmap.put(oldEntryDN, newEntryDN);
    processModifyDN(modDNmap, (interval != 0));
    return PluginResult.PostOperation.continueOperationProcessing();
  }
@@ -396,6 +401,7 @@
  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  public PluginResult.PostOperation doPostOperation(
              PostOperationDeleteOperation deleteOperation)
  {
@@ -406,7 +412,16 @@
      return PluginResult.PostOperation.continueOperationProcessing();
    }
    processDelete(deleteOperation.getEntryDN(), (interval != 0));
    Set<DN> deleteDNset =
         (Set<DN>) deleteOperation.getAttachment(DELETE_DNS);
    if(deleteDNset == null)
    {
      deleteDNset = new HashSet<DN>();
      deleteOperation.setAttachment(MODIFYDN_DNS, deleteDNset);
    }
    deleteDNset.add(deleteOperation.getEntryDN());
    processDelete(deleteDNset, (interval != 0));
    return PluginResult.PostOperation.continueOperationProcessing();
  }
@@ -433,6 +448,27 @@
    return PluginResult.SubordinateModifyDN.continueOperationProcessing();
  }
  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  public PluginResult.SubordinateDelete processSubordinateDelete(
          DeleteOperation deleteOperation, Entry entry)
  {
    // This cast gives an unchecked cast warning, suppress it
    // since the cast is ok.
    Set<DN> deleteDNset =
         (Set<DN>) deleteOperation.getAttachment(DELETE_DNS);
    if(deleteDNset == null)
    {
      // First time through, create the set and set it in
      // the operation attachment.
      deleteDNset = new HashSet<DN>();
      deleteOperation.setAttachment(DELETE_DNS, deleteDNset);
    }
    deleteDNset.add(entry.getDN());
    return PluginResult.SubordinateDelete.continueOperationProcessing();
  }
  /**
   * Verify that the specified attribute has either a distinguished name syntax
@@ -546,17 +582,17 @@
   *            a later time.
   *
   */
  private void processDelete(DN entryDN, boolean log)
  private void processDelete(Set<DN> deleteDNset, boolean log)
  {
    if(log)
    {
      writeLog(entryDN);
      writeLog(deleteDNset);
    }
    else
    {
      for(DN baseDN : getBaseDNsToSearch())
      {
        searchBaseDN(baseDN, entryDN, null);
        doBaseDN(baseDN, deleteDNset);
      }
    }
  }
@@ -674,6 +710,24 @@
  }
  /**
   * This method is used in foreground processing of a delete operation.
   * It uses the specified set to perform base DN searching for each
   * element.
   *
   * @param baseDN The DN to base the search at.
   *
   * @param deleteDNset The set containing the delete DNs.
   *
   */
  private void doBaseDN(DN baseDN, Set<DN> deleteDNset)
  {
    for(DN deletedEntryDN : deleteDNset)
    {
      searchBaseDN(baseDN, deletedEntryDN, null);
    }
  }
  /**
   * For each attribute type, delete the specified old entry DN and
   * optionally add the specified new entry DN if the DN is not null.
   * The specified entry is used to see if it contains each attribute type so
@@ -812,18 +866,23 @@
  }
  /**
   * Write the specified entry DN to the log file. This entry DN is related to
   * a delete operation.
   * Write the specified entry DNs to the log file.
   * These entry DNs are related to a delete operation.
   *
   * @param deletedEntryDN The DN of the deleted entry.
   *
   */
  private void writeLog(DN deletedEntryDN) {
    synchronized(logFile) {
      try {
  private void writeLog(Set<DN> deleteDNset) {
    synchronized(logFile)
    {
      try
      {
        setupWriter();
        for (DN deletedEntryDN : deleteDNset)
        {
        writer.write(deletedEntryDN.toNormalizedString());
        writer.newLine();
        }
        writer.flush();
        writer.close();
      }
@@ -860,7 +919,7 @@
            DN origDn = DN.decode(a[0]);
            //If there is only a single DN string than it must be a delete.
            if(a.length == 1) {
              processDelete(origDn, false);
              processDelete(Collections.singleton(origDn), false);
            } else {
              DN movedDN=DN.decode(a[1]);
              processModifyDN(origDn, movedDN);
opends/src/server/org/opends/server/types/AuthenticationInfo.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.types;
@@ -362,6 +362,26 @@
  /**
   * Sets the DN of the user as whom the client is authenticated,
   * does nothing if the client is unauthenticated.
   *
   * @param dn authentication identity DN.
   */
  public void setAuthenticationDN(DN dn)
  {
    if (authenticationEntry == null)
    {
      return;
    }
    else
    {
      authenticationEntry.setDN(dn);
    }
  }
  /**
   * Retrieves the entry for the user that should be used as the
   * default authorization identity.
   *
@@ -401,6 +421,27 @@
  /**
   * Sets the DN for the user that should be used as the default
   * authorization identity, does nothing if the client is
   * unauthorized.
   *
   * @param dn authorization identity DN.
   */
  public void setAuthorizationDN(DN dn)
  {
    if (authorizationEntry == null)
    {
      return;
    }
    else
    {
      authorizationEntry.setDN(dn);
    }
  }
  /**
   * Retrieves the bind DN that the client used for simple
   * authentication.
   *
@@ -470,6 +511,7 @@
   * @return  A string representation of this authentication info
   *          structure.
   */
  @Override
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
opends/tests/unit-tests-testng/src/server/org/opends/server/api/DITCacheMapTestCase.java
New file
@@ -0,0 +1,496 @@
/*
 * 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
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 */
package org.opends.server.api;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.opends.server.TestCaseUtils;
import org.testng.annotations.Test;
import org.opends.server.types.DN;
import org.testng.annotations.BeforeClass;
import static org.testng.Assert.*;
/**
 * A set of basic test cases for DITCacheMap class.
 */
public class DITCacheMapTestCase extends APITestCase
{
  private static final DITCacheMap<String> ditMap =
          new DITCacheMap<String>();
  private static final String dn0String =
          "cn=Object0,dc=example,dc=com";
  private static final String dn1String =
          "cn=Object1,ou=Objects,dc=example,dc=com";
  private static final String dn2String =
          "cn=Object2,ou=Objects,dc=example,dc=com";
  private static final String dn3String =
          "cn=Object3,ou=Objects,dc=example,dc=com";
  private static final String dn4String =
          "cn=Object4,ou=Classes,dc=example,dc=com";
  private static final String dn5String =
          "cn=Object5,ou=Classes,dc=example,dc=com";
  private static final String dn6String =
          "cn=Object6,ou=More,ou=Objects,dc=example,dc=com";
  private static final String dn7String =
          "cn=Object7,ou=More,ou=Objects,dc=example,dc=com";
  private static final String dn8String =
          "cn=Object8,ou=More,ou=Objects,dc=example,dc=com";
  private static final String dn9String =
          "cn=Object9,ou=No,ou=More,ou=Objects,dc=example,dc=com";
  private static DN dn0;
  private static DN dn1;
  private static DN dn2;
  private static DN dn3;
  private static DN dn4;
  private static DN dn5;
  private static DN dn6;
  private static DN dn7;
  private static DN dn8;
  private static DN dn9;
  private void putAllAndVerify()
  {
    Map<DN,String> hashMap =
          new HashMap<DN,String>();
    hashMap.put(dn0, dn0String);
    hashMap.put(dn1, dn1String);
    hashMap.put(dn2, dn2String);
    hashMap.put(dn3, dn3String);
    hashMap.put(dn4, dn4String);
    hashMap.put(dn5, dn5String);
    hashMap.put(dn6, dn6String);
    hashMap.put(dn7, dn7String);
    hashMap.put(dn8, dn8String);
    hashMap.put(dn9, dn9String);
    ditMap.putAll(hashMap);
    assertFalse(ditMap.isEmpty());
    assertEquals(ditMap.size(), 10);
    assertTrue(ditMap.containsKey(dn0));
    assertTrue(ditMap.containsKey(dn1));
    assertTrue(ditMap.containsKey(dn2));
    assertTrue(ditMap.containsKey(dn3));
    assertTrue(ditMap.containsKey(dn4));
    assertTrue(ditMap.containsKey(dn5));
    assertTrue(ditMap.containsKey(dn6));
    assertTrue(ditMap.containsKey(dn7));
    assertTrue(ditMap.containsKey(dn8));
    assertTrue(ditMap.containsKey(dn9));
    assertTrue(ditMap.containsValue(dn0String));
    assertTrue(ditMap.containsValue(dn1String));
    assertTrue(ditMap.containsValue(dn2String));
    assertTrue(ditMap.containsValue(dn3String));
    assertTrue(ditMap.containsValue(dn4String));
    assertTrue(ditMap.containsValue(dn5String));
    assertTrue(ditMap.containsValue(dn6String));
    assertTrue(ditMap.containsValue(dn7String));
    assertTrue(ditMap.containsValue(dn8String));
    assertTrue(ditMap.containsValue(dn9String));
  }
  private void clearTestMap()
  {
    ditMap.clear();
    assertTrue(ditMap.isEmpty());
    assertEquals(ditMap.size(), 0);
  }
  @BeforeClass()
  public void beforeClass()
         throws Exception
  {
    TestCaseUtils.startServer();
    dn0 = DN.decode(dn0String);
    dn1 = DN.decode(dn1String);
    dn2 = DN.decode(dn2String);
    dn3 = DN.decode(dn3String);
    dn4 = DN.decode(dn4String);
    dn5 = DN.decode(dn5String);
    dn6 = DN.decode(dn6String);
    dn7 = DN.decode(dn7String);
    dn8 = DN.decode(dn8String);
    dn9 = DN.decode(dn9String);
  }
  @Test()
  public void testDITCacheMapBasicOps()
         throws Exception
  {
    clearTestMap();
    ditMap.put(dn0, dn0String);
    ditMap.put(dn1, dn1String);
    ditMap.put(dn2, dn2String);
    ditMap.put(dn3, dn3String);
    ditMap.put(dn4, dn4String);
    ditMap.put(dn5, dn5String);
    ditMap.put(dn6, dn6String);
    ditMap.put(dn7, dn7String);
    ditMap.put(dn8, dn8String);
    ditMap.put(dn9, dn9String);
    assertFalse(ditMap.isEmpty());
    assertEquals(ditMap.size(), 10);
    assertTrue(ditMap.containsKey(dn0));
    assertTrue(ditMap.containsKey(dn1));
    assertTrue(ditMap.containsKey(dn2));
    assertTrue(ditMap.containsKey(dn3));
    assertTrue(ditMap.containsKey(dn4));
    assertTrue(ditMap.containsKey(dn5));
    assertTrue(ditMap.containsKey(dn6));
    assertTrue(ditMap.containsKey(dn7));
    assertTrue(ditMap.containsKey(dn8));
    assertTrue(ditMap.containsKey(dn9));
    assertFalse(ditMap.containsKey(DN.decode(
            "ou=No,ou=More,ou=Objects,dc=example,dc=com")));
    assertFalse(ditMap.containsKey(DN.decode(
            "ou=More,ou=Objects,dc=example,dc=com")));
    assertFalse(ditMap.containsKey(DN.decode(
            "ou=Objects,dc=example,dc=com")));
    assertFalse(ditMap.containsKey(DN.decode(
            "ou=Classes,dc=example,dc=com")));
    assertFalse(ditMap.containsKey(DN.decode(
            "dc=example,dc=com")));
    assertFalse(ditMap.containsKey(DN.decode(
            "dc=com")));
    assertTrue(ditMap.containsValue(dn0String));
    assertTrue(ditMap.containsValue(dn1String));
    assertTrue(ditMap.containsValue(dn2String));
    assertTrue(ditMap.containsValue(dn3String));
    assertTrue(ditMap.containsValue(dn4String));
    assertTrue(ditMap.containsValue(dn5String));
    assertTrue(ditMap.containsValue(dn6String));
    assertTrue(ditMap.containsValue(dn7String));
    assertTrue(ditMap.containsValue(dn8String));
    assertTrue(ditMap.containsValue(dn9String));
    assertEquals(ditMap.get(dn0), dn0String);
    assertEquals(ditMap.get(dn1), dn1String);
    assertEquals(ditMap.get(dn2), dn2String);
    assertEquals(ditMap.get(dn3), dn3String);
    assertEquals(ditMap.get(dn4), dn4String);
    assertEquals(ditMap.get(dn5), dn5String);
    assertEquals(ditMap.get(dn6), dn6String);
    assertEquals(ditMap.get(dn7), dn7String);
    assertEquals(ditMap.get(dn8), dn8String);
    assertEquals(ditMap.get(dn9), dn9String);
    assertNull(ditMap.get(DN.decode(
            "ou=No,ou=More,ou=Objects,dc=example,dc=com")));
    assertNull(ditMap.get(DN.decode(
            "ou=More,ou=Objects,dc=example,dc=com")));
    assertNull(ditMap.get(DN.decode(
            "ou=Objects,dc=example,dc=com")));
    assertNull(ditMap.get(DN.decode(
            "ou=Classes,dc=example,dc=com")));
    assertNull(ditMap.get(DN.decode(
            "dc=example,dc=com")));
    assertNull(ditMap.get(DN.decode(
            "dc=com")));
  }
  @Test()
  public void testDITCacheMapGetSubTree()
         throws Exception
  {
    clearTestMap();
    putAllAndVerify();
    Collection<String> subtreeSet = ditMap.getSubtree(
            DN.decode("dc=example,dc=com"));
    assertFalse(subtreeSet.isEmpty());
    assertEquals(subtreeSet.size(), 10);
    assertTrue(subtreeSet.contains(dn0String));
    assertTrue(subtreeSet.contains(dn1String));
    assertTrue(subtreeSet.contains(dn2String));
    assertTrue(subtreeSet.contains(dn3String));
    assertTrue(subtreeSet.contains(dn4String));
    assertTrue(subtreeSet.contains(dn5String));
    assertTrue(subtreeSet.contains(dn6String));
    assertTrue(subtreeSet.contains(dn7String));
    assertTrue(subtreeSet.contains(dn8String));
    assertTrue(subtreeSet.contains(dn9String));
    subtreeSet = ditMap.getSubtree(
            DN.decode("ou=Objects,dc=example,dc=com"));
    assertFalse(subtreeSet.isEmpty());
    assertEquals(subtreeSet.size(), 7);
    assertTrue(subtreeSet.contains(dn1String));
    assertTrue(subtreeSet.contains(dn2String));
    assertTrue(subtreeSet.contains(dn3String));
    assertTrue(subtreeSet.contains(dn6String));
    assertTrue(subtreeSet.contains(dn7String));
    assertTrue(subtreeSet.contains(dn8String));
    assertTrue(subtreeSet.contains(dn9String));
    subtreeSet = ditMap.getSubtree(
            DN.decode("ou=Classes,dc=example,dc=com"));
    assertFalse(subtreeSet.isEmpty());
    assertEquals(subtreeSet.size(), 2);
    assertTrue(subtreeSet.contains(dn4String));
    assertTrue(subtreeSet.contains(dn5String));
    subtreeSet = ditMap.getSubtree(
            DN.decode("ou=More,ou=Objects,dc=example,dc=com"));
    assertFalse(subtreeSet.isEmpty());
    assertEquals(subtreeSet.size(), 4);
    assertTrue(subtreeSet.contains(dn6String));
    assertTrue(subtreeSet.contains(dn7String));
    assertTrue(subtreeSet.contains(dn8String));
    assertTrue(subtreeSet.contains(dn9String));
    subtreeSet = ditMap.getSubtree(
            DN.decode("ou=No,ou=More,ou=Objects,dc=example,dc=com"));
    assertFalse(subtreeSet.isEmpty());
    assertEquals(subtreeSet.size(), 1);
    assertTrue(subtreeSet.contains(dn9String));
    subtreeSet = ditMap.getSubtree(dn0);
    assertFalse(subtreeSet.isEmpty());
    assertEquals(subtreeSet.size(), 1);
    assertTrue(subtreeSet.contains(dn0String));
  }
  @Test()
  public void testDITCacheMapKeyAndEntrySet()
         throws Exception
  {
    clearTestMap();
    putAllAndVerify();
    Set<DN> dnSet = ditMap.keySet();
    assertFalse(dnSet.isEmpty());
    assertEquals(dnSet.size(), 10);
    assertTrue(dnSet.contains(dn0));
    assertTrue(dnSet.contains(dn1));
    assertTrue(dnSet.contains(dn2));
    assertTrue(dnSet.contains(dn3));
    assertTrue(dnSet.contains(dn4));
    assertTrue(dnSet.contains(dn5));
    assertTrue(dnSet.contains(dn6));
    assertTrue(dnSet.contains(dn7));
    assertTrue(dnSet.contains(dn8));
    assertTrue(dnSet.contains(dn9));
    Set<Entry<DN,String>> entrySet = ditMap.entrySet();
    assertFalse(entrySet.isEmpty());
    assertEquals(entrySet.size(), 10);
    Iterator<Entry<DN,String>> iterator = entrySet.iterator();
    Map<DN,String> tempMap = new HashMap<DN,String>();
    while (iterator.hasNext())
    {
      Entry<DN,String> entry = iterator.next();
      if ((entry.getKey().equals(dn0) &&
          entry.getValue().equals(dn0String)) ||
          (entry.getKey().equals(dn1) &&
          entry.getValue().equals(dn1String)) ||
          (entry.getKey().equals(dn2) &&
          entry.getValue().equals(dn2String)) ||
          (entry.getKey().equals(dn3) &&
          entry.getValue().equals(dn3String)) ||
          (entry.getKey().equals(dn4) &&
          entry.getValue().equals(dn4String)) ||
          (entry.getKey().equals(dn5) &&
          entry.getValue().equals(dn5String)) ||
          (entry.getKey().equals(dn6) &&
          entry.getValue().equals(dn6String)) ||
          (entry.getKey().equals(dn7) &&
          entry.getValue().equals(dn7String)) ||
          (entry.getKey().equals(dn8) &&
          entry.getValue().equals(dn8String)) ||
          (entry.getKey().equals(dn9) &&
          entry.getValue().equals(dn9String)))
      {
        assertFalse(tempMap.containsKey(entry.getKey()));
        assertFalse(tempMap.containsValue(entry.getValue()));
        assertNull(tempMap.put(entry.getKey(), entry.getValue()));
      }
      else
      {
        fail();
      }
      iterator.remove();
    }
    assertEquals(tempMap.size(), 10);
    assertEquals(ditMap.size(), 0);
    assertTrue(ditMap.isEmpty());
  }
  @Test()
  public void testDITCacheMapRemoveSubTree()
         throws Exception
  {
    clearTestMap();
    putAllAndVerify();
    Set<String> removeSet = new HashSet<String>();
    assertTrue(ditMap.removeSubtree(DN.decode(
            "dc=example,dc=com"),
            removeSet));
    assertFalse(removeSet.isEmpty());
    assertEquals(removeSet.size(), 10);
    assertTrue(removeSet.contains(dn0String));
    assertTrue(removeSet.contains(dn1String));
    assertTrue(removeSet.contains(dn2String));
    assertTrue(removeSet.contains(dn3String));
    assertTrue(removeSet.contains(dn4String));
    assertTrue(removeSet.contains(dn5String));
    assertTrue(removeSet.contains(dn6String));
    assertTrue(removeSet.contains(dn7String));
    assertTrue(removeSet.contains(dn8String));
    assertTrue(removeSet.contains(dn9String));
    assertTrue(ditMap.isEmpty());
    putAllAndVerify();
    removeSet.clear();
    assertTrue(ditMap.removeSubtree(DN.decode(
            "ou=Objects,dc=example,dc=com"),
            removeSet));
    assertFalse(removeSet.isEmpty());
    assertEquals(removeSet.size(), 7);
    assertTrue(removeSet.contains(dn1String));
    assertTrue(removeSet.contains(dn2String));
    assertTrue(removeSet.contains(dn3String));
    assertTrue(removeSet.contains(dn6String));
    assertTrue(removeSet.contains(dn7String));
    assertTrue(removeSet.contains(dn8String));
    assertTrue(removeSet.contains(dn9String));
    assertEquals(ditMap.size(), 3);
    assertTrue(ditMap.containsKey(dn0));
    assertTrue(ditMap.containsKey(dn4));
    assertTrue(ditMap.containsKey(dn5));
    clearTestMap();
    putAllAndVerify();
    removeSet.clear();
    assertTrue(ditMap.removeSubtree(DN.decode(
            "ou=Classes,dc=example,dc=com"),
            removeSet));
    assertFalse(removeSet.isEmpty());
    assertEquals(removeSet.size(), 2);
    assertTrue(removeSet.contains(dn4String));
    assertTrue(removeSet.contains(dn5String));
    assertEquals(ditMap.size(), 8);
    assertTrue(ditMap.containsKey(dn0));
    assertTrue(ditMap.containsKey(dn1));
    assertTrue(ditMap.containsKey(dn2));
    assertTrue(ditMap.containsKey(dn3));
    assertTrue(ditMap.containsKey(dn6));
    assertTrue(ditMap.containsKey(dn7));
    assertTrue(ditMap.containsKey(dn8));
    assertTrue(ditMap.containsKey(dn9));
    clearTestMap();
    putAllAndVerify();
    removeSet.clear();
    assertTrue(ditMap.removeSubtree(DN.decode(
            "ou=More,ou=Objects,dc=example,dc=com"),
            removeSet));
    assertFalse(removeSet.isEmpty());
    assertEquals(removeSet.size(), 4);
    assertTrue(removeSet.contains(dn6String));
    assertTrue(removeSet.contains(dn7String));
    assertTrue(removeSet.contains(dn8String));
    assertTrue(removeSet.contains(dn9String));
    assertEquals(ditMap.size(), 6);
    assertTrue(ditMap.containsKey(dn0));
    assertTrue(ditMap.containsKey(dn1));
    assertTrue(ditMap.containsKey(dn2));
    assertTrue(ditMap.containsKey(dn3));
    assertTrue(ditMap.containsKey(dn4));
    assertTrue(ditMap.containsKey(dn5));
    clearTestMap();
    putAllAndVerify();
    removeSet.clear();
    assertTrue(ditMap.removeSubtree(DN.decode(
            "ou=No,ou=More,ou=Objects,dc=example,dc=com"),
            removeSet));
    assertFalse(removeSet.isEmpty());
    assertEquals(removeSet.size(), 1);
    assertTrue(removeSet.contains(dn9String));
    assertEquals(ditMap.size(), 9);
    assertTrue(ditMap.containsKey(dn0));
    assertTrue(ditMap.containsKey(dn1));
    assertTrue(ditMap.containsKey(dn2));
    assertTrue(ditMap.containsKey(dn3));
    assertTrue(ditMap.containsKey(dn4));
    assertTrue(ditMap.containsKey(dn5));
    assertTrue(ditMap.containsKey(dn6));
    assertTrue(ditMap.containsKey(dn7));
    assertTrue(ditMap.containsKey(dn8));
    clearTestMap();
    putAllAndVerify();
    removeSet.clear();
    assertTrue(ditMap.removeSubtree(dn0,
            removeSet));
    assertFalse(removeSet.isEmpty());
    assertEquals(removeSet.size(), 1);
    assertTrue(removeSet.contains(dn0String));
    assertEquals(ditMap.size(), 9);
    assertTrue(ditMap.containsKey(dn1));
    assertTrue(ditMap.containsKey(dn2));
    assertTrue(ditMap.containsKey(dn3));
    assertTrue(ditMap.containsKey(dn4));
    assertTrue(ditMap.containsKey(dn5));
    assertTrue(ditMap.containsKey(dn6));
    assertTrue(ditMap.containsKey(dn7));
    assertTrue(ditMap.containsKey(dn8));
    assertTrue(ditMap.containsKey(dn9));
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/api/plugin/DirectoryServerPluginTestCase.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.api.plugin;
@@ -551,6 +551,15 @@
    expectedPublicMethods.add(sigList);
    sigList = new LinkedList<String>();
    sigList.add("processSubordinateDelete");
    sigList.add("org.opends.server.api.plugin." +
        "PluginResult$SubordinateDelete");
    sigList.add("org.opends.server.core." +
                "DeleteOperation");
    sigList.add("org.opends.server.types.Entry");
    expectedPublicMethods.add(sigList);
    sigList = new LinkedList<String>();
    sigList.add("processIntermediateResponse");
    sigList.add("org.opends.server.api.plugin." +
        "PluginResult$IntermediateResponse");
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.core;
@@ -45,11 +45,13 @@
import org.opends.server.tools.LDAPReader;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
import org.opends.messages.Message;
import org.opends.server.tools.LDAPDelete;
import org.opends.server.tools.LDAPModify;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import org.opends.messages.Message;
/**
@@ -2023,6 +2025,170 @@
  /**
   * Tests to ensure that performing subtree delete will
   * cause the connection to no longer be associated
   * with the previous identity.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(groups = "slow")
  public void testSubtreeDeleteClearsAuthInfo()
         throws Exception
  {
    TestCaseUtils.restartServer();
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: ou=people,dc=example,dc=com",
      "objectClass: organizationalUnit",
      "objectClass: top",
      "ou: people",
      "",
      "dn: uid=test,ou=people,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test",
      "givenName: test",
      "sn: Test",
      "cn: Test",
      "userPassword: password");
    String dnString = "uid=test,ou=people,dc=example,dc=com";
    DN userDN = DN.decode(dnString);
    Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
    s.setSoTimeout(6000);
    LDAPReader r = new LDAPReader(s);
    LDAPWriter w = new LDAPWriter(s);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(ByteString.valueOf(dnString),
                                   3, ByteString.valueOf("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    w.writeMessage(message);
    message = r.readMessage();
    BindResponseProtocolOp bindResponse =
            message.getBindResponseProtocolOp();
    assertEquals(bindResponse.getResultCode(), 0);
    assertNotNull(DirectoryServer.getAuthenticatedUsers().get(
            userDN));
    assertEquals(DirectoryServer.getAuthenticatedUsers().get(
            userDN).size(), 1);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-J", OID_SUBTREE_DELETE_CONTROL + ":true",
      "--noPropertiesFile",
      "ou=people,dc=example,dc=com"
    };
    assertEquals(LDAPDelete.mainDelete(args, false, null, System.err), 0);
    assertNull(DirectoryServer.getAuthenticatedUsers().get(userDN));
    s.close();
    // Cleanup.
    TestCaseUtils.clearJEBackend(false,
       "userRoot", "dc=example,dc=com");
  }
  /**
   * Tests to ensure that performing subtree modify will
   * cause the connection to be associated with new auth
   * identity instead of the previous identity.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(groups = "slow")
  public void testSubtreeModifyUpdatesAuthInfo()
         throws Exception
  {
    TestCaseUtils.restartServer();
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: ou=people,dc=example,dc=com",
      "objectClass: organizationalUnit",
      "objectClass: top",
      "ou: people",
      "",
      "dn: uid=test,ou=people,dc=example,dc=com",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test",
      "givenName: test",
      "sn: Test",
      "cn: Test",
      "userPassword: password");
    String dnString = "uid=test,ou=people,dc=example,dc=com";
    DN userDN = DN.decode(dnString);
    Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
    s.setSoTimeout(6000);
    LDAPReader r = new LDAPReader(s);
    LDAPWriter w = new LDAPWriter(s);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(ByteString.valueOf(dnString),
                                   3, ByteString.valueOf("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    w.writeMessage(message);
    message = r.readMessage();
    BindResponseProtocolOp bindResponse =
            message.getBindResponseProtocolOp();
    assertEquals(bindResponse.getResultCode(), 0);
    assertNotNull(DirectoryServer.getAuthenticatedUsers().get(
            userDN));
    assertEquals(DirectoryServer.getAuthenticatedUsers().get(
            userDN).size(), 1);
    String path = TestCaseUtils.createTempFile(
         "dn: ou=people,dc=example,dc=com",
         "changetype: moddn",
         "newRDN: ou=users",
         "deleteOldRDN: 1");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "--noPropertiesFile",
      "-f", path
    };
    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
    String newDNString = "uid=test,ou=users,dc=example,dc=com";
    DN newUserDN = DN.decode(newDNString);
    assertNotNull(DirectoryServer.getAuthenticatedUsers().get(
            newUserDN));
    assertEquals(DirectoryServer.getAuthenticatedUsers().get(
            newUserDN).size(), 1);
    s.close();
    // Cleanup.
    TestCaseUtils.clearJEBackend(false,
       "userRoot", "dc=example,dc=com");
  }
  /**
   * Tests to ensure that the "ignore" password policy state update policy
   * works as expected.
   *
opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */
package org.opends.server.core;
@@ -45,6 +45,8 @@
import org.opends.server.extensions.VirtualStaticGroup;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.tools.LDAPDelete;
import org.opends.server.tools.LDAPModify;
import org.opends.server.types.Attribute;
import org.opends.server.types.Attributes;
import org.opends.server.types.DereferencePolicy;
@@ -60,6 +62,7 @@
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import static org.opends.server.util.ServerConstants.*;
import static org.testng.Assert.*;
@@ -2281,6 +2284,170 @@
  }
  /**
   * Tests subtree delete operation on groups tree.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSubtreeDelete() throws Exception {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
    addSubtreeGroupTestEntries();
    Group group1 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=moregroups,dc=example,dc=com"));
    assertNotNull(group1);
    Group group2 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=groups,dc=example,dc=com"));
    assertNotNull(group2);
    Group group3 = groupManager.getGroupInstance(
            DN.decode("cn=group2,ou=groups,dc=example,dc=com"));
    assertNotNull(group3);
    // Get a client connection authenticated as user1 and make sure it handles
    // group operations correctly.
    DN userDN = DN.decode("uid=test1,ou=people,dc=example,dc=com");
    InternalClientConnection conn = new InternalClientConnection(userDN);
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), null, DN.nullDN(),
                  SearchScope.BASE_OBJECT,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
                  null);
    assertTrue(conn.isMemberOf(group1, null));
    assertTrue(conn.isMemberOf(group2, null));
    assertTrue(conn.isMemberOf(group3, null));
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-J", OID_SUBTREE_DELETE_CONTROL + ":true",
      "--noPropertiesFile",
      "ou=groups,dc=example,dc=com"
    };
    assertEquals(LDAPDelete.mainDelete(args, false, null, System.err), 0);
    InternalClientConnection conn1 =
            new InternalClientConnection(userDN);
    searchOperation =
         new InternalSearchOperation(conn1, conn1.nextOperationID(),
                  conn1.nextMessageID(), null, DN.nullDN(),
                  SearchScope.BASE_OBJECT,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
                  null);
    group1 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=moregroups,dc=example,dc=com"));
    assertNotNull(group1);
    assertTrue(conn1.isMemberOf(group1, null));
    group2 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=groups,dc=example,dc=com"));
    assertNull(group2);
    group3 = groupManager.getGroupInstance(
            DN.decode("cn=group2,ou=groups,dc=example,dc=com"));
    assertNull(group3);
    // Cleanup.
    TestCaseUtils.clearJEBackend(false,
       "userRoot", "dc=example,dc=com");
  }
  /**
   * Tests subtree modify operation on groups tree.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSubtreeModify() throws Exception {
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    GroupManager groupManager = DirectoryServer.getGroupManager();
    groupManager.deregisterAllGroups();
    addSubtreeGroupTestEntries();
    Group group1 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=moregroups,dc=example,dc=com"));
    assertNotNull(group1);
    Group group2 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=groups,dc=example,dc=com"));
    assertNotNull(group2);
    Group group3 = groupManager.getGroupInstance(
            DN.decode("cn=group2,ou=groups,dc=example,dc=com"));
    assertNotNull(group3);
    // Get a client connection authenticated as user1 and make sure it handles
    // group operations correctly.
    DN userDN = DN.decode("uid=test1,ou=people,dc=example,dc=com");
    InternalClientConnection conn = new InternalClientConnection(userDN);
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                  conn.nextMessageID(), null, DN.nullDN(),
                  SearchScope.BASE_OBJECT,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
                  null);
    assertTrue(conn.isMemberOf(group1, null));
    assertTrue(conn.isMemberOf(group2, null));
    assertTrue(conn.isMemberOf(group3, null));
    String path = TestCaseUtils.createTempFile(
         "dn: ou=groups,dc=example,dc=com",
         "changetype: moddn",
         "newRDN: ou=newgroups",
         "deleteOldRDN: 1");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "--noPropertiesFile",
      "-f", path
    };
    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
    InternalClientConnection conn1 =
            new InternalClientConnection(userDN);
    searchOperation =
         new InternalSearchOperation(conn1, conn1.nextOperationID(),
                  conn1.nextMessageID(), null, DN.nullDN(),
                  SearchScope.BASE_OBJECT,
                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
                  null);
    group1 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=moregroups,dc=example,dc=com"));
    assertNotNull(group1);
    assertTrue(conn1.isMemberOf(group1, null));
    group2 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=groups,dc=example,dc=com"));
    assertNull(group2);
    group3 = groupManager.getGroupInstance(
            DN.decode("cn=group2,ou=groups,dc=example,dc=com"));
    assertNull(group3);
    Group newGroup2 = groupManager.getGroupInstance(
            DN.decode("cn=group1,ou=newgroups,dc=example,dc=com"));
    assertNotNull(newGroup2);
    assertTrue(conn.isMemberOf(newGroup2, null));
    Group newGroup3 = groupManager.getGroupInstance(
            DN.decode("cn=group2,ou=newgroups,dc=example,dc=com"));
    assertNotNull(newGroup3);
    assertTrue(conn.isMemberOf(newGroup3, null));
    // Cleanup.
    TestCaseUtils.clearJEBackend(false,
       "userRoot", "dc=example,dc=com");
  }
  /**
   * Adds nested group entries.
   *
   * @throws Exception If a problem adding the entries occurs.
@@ -2374,5 +2541,58 @@
      "cn: User 5",
      "userPassword: password");
  }
  /**
   * Adds entries for subtree operations tests.
   *
   * @throws Exception If a problem adding the entries occurs.
   */
  private void addSubtreeGroupTestEntries() throws Exception {
    TestCaseUtils.addEntries(
      "dn: ou=people,dc=example,dc=com",
      "objectClass: organizationalUnit",
      "objectClass: top",
      "ou: people",
      "",
      "dn: uid=test1,ou=people,dc=example,dc=com",
      "objectClass: person",
      "objectClass: inetOrgPerson",
      "objectClass: organizationalPerson",
      "objectClass: top",
      "uid: test1",
      "cn: test",
      "sn: test",
      "userPassword: password",
      "",
      "dn: ou=groups,dc=example,dc=com",
      "objectClass: organizationalUnit",
      "objectClass: top",
      "ou: groups",
      "",
      "dn: cn=group1,ou=groups,dc=example,dc=com",
      "objectClass: groupOfNames",
      "objectClass: top",
      "member: uid=test1,ou=people,dc=example,dc=com",
      "cn: group1",
      "",
      "dn: cn=group2,ou=groups,dc=example,dc=com",
      "objectClass: groupOfNames",
      "objectClass: top",
      "member: uid=test1,ou=people,dc=example,dc=com",
      "cn: group2",
      "",
      "dn: ou=moregroups,dc=example,dc=com",
      "objectClass: organizationalUnit",
      "objectClass: top",
      "ou: moregroups",
      "",
      "dn: cn=group1,ou=moregroups,dc=example,dc=com",
      "objectClass: groupOfNames",
      "objectClass: top",
      "member: uid=test1,ou=people,dc=example,dc=com",
      "cn: group1"
    );
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.server.core;
@@ -37,6 +37,8 @@
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.tools.LDAPDelete;
import org.opends.server.tools.LDAPModify;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValues;
import org.opends.server.types.ByteString;
@@ -55,6 +57,7 @@
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.opends.server.util.ServerConstants.*;
import static org.testng.Assert.*;
public class SubentryManagerTestCase extends CoreTestCase
@@ -72,94 +75,7 @@
  {
    TestCaseUtils.startServer();
    TestCaseUtils.clearJEBackend(false, "userRoot", SUFFIX);
    InternalClientConnection connection =
         InternalClientConnection.getRootConnection();
    // Add suffix entry.
    DN suffixDN = DN.decode(SUFFIX);
    if (DirectoryServer.getEntry(suffixDN) == null)
    {
      Entry suffixEntry = StaticUtils.createEntry(suffixDN);
      AddOperation addOperation =
           connection.processAdd(suffixEntry.getDN(),
                                 suffixEntry.getObjectClasses(),
                                 suffixEntry.getUserAttributes(),
                                 suffixEntry.getOperationalAttributes());
      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
      assertNotNull(DirectoryServer.getEntry(suffixEntry.getDN()));
    }
    // Add base entry.
    DN baseDN = DN.decode(BASE);
    if (DirectoryServer.getEntry(baseDN) == null)
    {
      Entry baseEntry = StaticUtils.createEntry(baseDN);
      AddOperation addOperation =
           connection.processAdd(baseEntry.getDN(),
                                 baseEntry.getObjectClasses(),
                                 baseEntry.getUserAttributes(),
                                 baseEntry.getOperationalAttributes());
      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
      assertNotNull(DirectoryServer.getEntry(baseEntry.getDN()));
    }
    // Add test entry.
    testEntry = TestCaseUtils.makeEntry(
         "dn: uid=rogasawara," + BASE,
         "objectclass: top",
         "objectclass: person",
         "objectclass: organizationalPerson",
         "objectclass: inetOrgPerson",
         "uid: rogasawara",
         "userpassword: password",
         "mail: rogasawara@example.com",
         "givenname: Rodney",
         "sn: Ogasawara",
         "cn: Rodney Ogasawara",
         "title: Sales, Director"
    );
    AddOperation addOperation =
         connection.processAdd(testEntry.getDN(),
                               testEntry.getObjectClasses(),
                               testEntry.getUserAttributes(),
                               testEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getEntry(testEntry.getDN()));
    // Add test subentry.
    ldapSubentry = TestCaseUtils.makeEntry(
         "dn: cn=Subentry," + SUFFIX,
         "objectClass: top",
         "objectclass: subentry",
         "subtreeSpecification: {base \"ou=Test SubEntry Manager\"}",
         "cn: Subentry");
    addOperation =
         connection.processAdd(ldapSubentry.getDN(),
                               ldapSubentry.getObjectClasses(),
                               ldapSubentry.getUserAttributes(),
                               ldapSubentry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getEntry(ldapSubentry.getDN()));
    // Add test collective subentry.
    collectiveSubentry = TestCaseUtils.makeEntry(
         "dn: cn=Collective Subentry," + SUFFIX,
         "objectClass: top",
         "objectclass: subentry",
         "objectClass: collectiveAttributeSubentry",
         "objectClass: extensibleObject",
         "c-l: Savoie",
         "preferredLanguage;collective: fr",
         "subtreeSpecification: {base \"ou=Test SubEntry Manager\"}",
         "cn: Collective Subentry");
    addOperation =
         connection.processAdd(collectiveSubentry.getDN(),
                               collectiveSubentry.getObjectClasses(),
                               collectiveSubentry.getUserAttributes(),
                               collectiveSubentry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getEntry(collectiveSubentry.getDN()));
    addTestEntries();
  }
  @AfterClass
@@ -452,4 +368,175 @@
         testEntry.getDN().toNormalizedString()), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  @Test
  public void testSubtreeDelete() throws Exception
  {
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-J", OID_SUBTREE_DELETE_CONTROL + ":true",
      "--noPropertiesFile",
      SUFFIX
    };
    assertEquals(LDAPDelete.mainDelete(args, false, null, System.err), 0);
    assertTrue(DirectoryServer.getSubentryManager().getCollectiveSubentries(
            DN.decode("uid=rogasawara," + BASE)).isEmpty());
    assertTrue(DirectoryServer.getSubentryManager().getSubentries(
            DN.decode("uid=rogasawara," + BASE)).isEmpty());
    // Re-add entries.
    addTestEntries();
  }
  @Test
  public void testSubtreeModify() throws Exception
  {
    String OLDBASE = "ou=Test SubEntry Manager";
    String NEWBASE = "ou=New SubEntry Manager Base";
    String newPath = TestCaseUtils.createTempFile(
         "dn: " + BASE,
         "changetype: moddn",
         "newRDN: " + NEWBASE,
         "deleteOldRDN: 1");
    String[] newArgs =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "--noPropertiesFile",
      "-f", newPath
    };
    assertEquals(LDAPModify.mainModify(newArgs, false, null, System.err), 0);
    assertNotNull(DirectoryServer.getEntry(DN.decode(
            "uid=rogasawara," + NEWBASE + "," + SUFFIX)));
    assertTrue(DirectoryServer.getSubentryManager().getCollectiveSubentries(
          DN.decode("uid=rogasawara," + NEWBASE + "," + SUFFIX)).isEmpty());
    assertTrue(DirectoryServer.getSubentryManager().getSubentries(
          DN.decode("uid=rogasawara," + NEWBASE + "," + SUFFIX)).isEmpty());
    // Move it back.
    String oldPath = TestCaseUtils.createTempFile(
         "dn: " + NEWBASE + "," + SUFFIX,
         "changetype: moddn",
         "newRDN: " + OLDBASE,
         "deleteOldRDN: 1");
    String[] oldArgs =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "--noPropertiesFile",
      "-f", oldPath
    };
    assertEquals(LDAPModify.mainModify(oldArgs, false, null, System.err), 0);
    assertNotNull(DirectoryServer.getEntry(DN.decode(
            "uid=rogasawara," + OLDBASE + "," + SUFFIX)));
    assertFalse(DirectoryServer.getSubentryManager().getCollectiveSubentries(
          DN.decode("uid=rogasawara," + OLDBASE + "," + SUFFIX)).isEmpty());
    assertFalse(DirectoryServer.getSubentryManager().getSubentries(
          DN.decode("uid=rogasawara," + OLDBASE + "," + SUFFIX)).isEmpty());
  }
  private void addTestEntries() throws Exception
  {
    InternalClientConnection connection =
         InternalClientConnection.getRootConnection();
    // Add suffix entry.
    DN suffixDN = DN.decode(SUFFIX);
    if (DirectoryServer.getEntry(suffixDN) == null)
    {
      Entry suffixEntry = StaticUtils.createEntry(suffixDN);
      AddOperation addOperation =
           connection.processAdd(suffixEntry.getDN(),
                                 suffixEntry.getObjectClasses(),
                                 suffixEntry.getUserAttributes(),
                                 suffixEntry.getOperationalAttributes());
      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
      assertNotNull(DirectoryServer.getEntry(suffixEntry.getDN()));
    }
    // Add base entry.
    DN baseDN = DN.decode(BASE);
    if (DirectoryServer.getEntry(baseDN) == null)
    {
      Entry baseEntry = StaticUtils.createEntry(baseDN);
      AddOperation addOperation =
           connection.processAdd(baseEntry.getDN(),
                                 baseEntry.getObjectClasses(),
                                 baseEntry.getUserAttributes(),
                                 baseEntry.getOperationalAttributes());
      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
      assertNotNull(DirectoryServer.getEntry(baseEntry.getDN()));
    }
    // Add test entry.
    testEntry = TestCaseUtils.makeEntry(
         "dn: uid=rogasawara," + BASE,
         "objectclass: top",
         "objectclass: person",
         "objectclass: organizationalPerson",
         "objectclass: inetOrgPerson",
         "uid: rogasawara",
         "userpassword: password",
         "mail: rogasawara@example.com",
         "givenname: Rodney",
         "sn: Ogasawara",
         "cn: Rodney Ogasawara",
         "title: Sales, Director"
    );
    AddOperation addOperation =
         connection.processAdd(testEntry.getDN(),
                               testEntry.getObjectClasses(),
                               testEntry.getUserAttributes(),
                               testEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getEntry(testEntry.getDN()));
    // Add test subentry.
    ldapSubentry = TestCaseUtils.makeEntry(
         "dn: cn=Subentry," + SUFFIX,
         "objectClass: top",
         "objectclass: subentry",
         "subtreeSpecification: {base \"ou=Test SubEntry Manager\"}",
         "cn: Subentry");
    addOperation =
         connection.processAdd(ldapSubentry.getDN(),
                               ldapSubentry.getObjectClasses(),
                               ldapSubentry.getUserAttributes(),
                               ldapSubentry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getEntry(ldapSubentry.getDN()));
    // Add test collective subentry.
    collectiveSubentry = TestCaseUtils.makeEntry(
         "dn: cn=Collective Subentry," + SUFFIX,
         "objectClass: top",
         "objectclass: subentry",
         "objectClass: collectiveAttributeSubentry",
         "objectClass: extensibleObject",
         "c-l: Savoie",
         "preferredLanguage;collective: fr",
         "subtreeSpecification: {base \"ou=Test SubEntry Manager\"}",
         "cn: Collective Subentry");
    addOperation =
         connection.processAdd(collectiveSubentry.getDN(),
                               collectiveSubentry.getObjectClasses(),
                               collectiveSubentry.getUserAttributes(),
                               collectiveSubentry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getEntry(collectiveSubentry.getDN()));
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
@@ -144,15 +144,13 @@
  /**
   * Test that a delete subtree changes the correct entries under
   * the correct suffixes.
   *
   * FIXME: re-enable when CR 6959320 is fixed.
   * Test that a delete subtree changes the correct entries
   * under the correct suffixes.
   *
   * @throws Exception If an unexpected result is returned.
   *
   */
  @Test(enabled=false)
  @Test()
  public void testReferentialDeleteTree() throws Exception {
    // Add attributes interested in: member, uniquemember, seealso.
    replaceAttrEntry(configDN, dsConfigAttrType,"member");
@@ -181,12 +179,8 @@
    // Perform the subtree delete.
    deleteSubtree(oldSuperior);
    // Check group membership before delete.
    //
    // This simply checks that the group cache is updated
    // rather than RI plugin works (it fails at the moment).
    //
    // isMember(tgroup, false, user1, user2, user3);
    // Check that the group cache is updated.
    isMember(tgroup, false, user1, user2, user3);
    // Check values exist as before delete.
    isAttributeValueEntry(tgroup, false, "member", user1, user2, user3);