From a09e50d8d41c0f50c486742f4cc2343083c635e3 Mon Sep 17 00:00:00 2001
From: ludovicp <ludovicp@localhost>
Date: Fri, 25 Jun 2010 09:25:49 +0000
Subject: [PATCH] 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

---
 opends/tests/unit-tests-testng/src/server/org/opends/server/api/plugin/DirectoryServerPluginTestCase.java   |   11 
 opends/src/server/org/opends/server/api/plugin/PluginResult.java                                            |  194 +++
 opends/src/server/org/opends/server/api/plugin/PluginType.java                                              |   11 
 opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java                        |   13 
 opends/src/admin/messages/PasswordPolicyImportPluginCfgDefn.properties                                      |    1 
 opends/src/server/org/opends/server/core/SubentryManager.java                                               |  302 +++-
 opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java                                 |  113 +
 opends/src/admin/messages/ReferentialIntegrityPluginCfgDefn.properties                                      |    1 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java                  |  222 +++
 opends/src/admin/defn/org/opends/server/admin/std/PluginRootConfiguration.xml                               |   32 
 opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java |   16 
 opends/src/admin/messages/PluginRootCfgDefn.properties                                                      |    3 
 opends/src/admin/messages/UniqueAttributePluginCfgDefn.properties                                           |    1 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java                 |  172 ++
 opends/src/messages/messages/jeb.properties                                                                 |    2 
 opends/src/server/org/opends/server/extensions/DynamicGroup.java                                            |   13 
 opends/src/server/org/opends/server/extensions/StaticGroup.java                                             |   13 
 opends/src/admin/messages/PluginCfgDefn.properties                                                          |    1 
 opends/src/server/org/opends/server/core/AuthenticatedUsers.java                                            |  245 +++
 opends/src/server/org/opends/server/core/GroupManager.java                                                  |  220 ++
 opends/src/admin/messages/ProfilerPluginCfgDefn.properties                                                  |    1 
 opends/resource/schema/02-config.ldif                                                                       |    6 
 opends/src/admin/defn/org/opends/server/admin/std/PluginConfiguration.xml                                   |    8 
 opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java                                   |   25 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java               |  265 ++-
 opends/src/admin/messages/LastModPluginCfgDefn.properties                                                   |    1 
 opends/src/admin/messages/ChangeNumberControlPluginCfgDefn.properties                                       |    1 
 opends/src/server/org/opends/server/core/PluginConfigManager.java                                           |   92 +
 opends/resource/config/config.ldif                                                                          |    1 
 opends/src/admin/messages/EntryUUIDPluginCfgDefn.properties                                                 |    1 
 opends/src/admin/messages/LDAPAttributeDescriptionListPluginCfgDefn.properties                              |    1 
 opends/src/admin/messages/FractionalLDIFImportPluginCfgDefn.properties                                      |    1 
 opends/src/admin/messages/NetworkGroupPluginCfgDefn.properties                                              |    1 
 opends/src/admin/messages/SevenBitCleanPluginCfgDefn.properties                                             |    1 
 opends/src/server/org/opends/server/authorization/dseecompat/AciList.java                                   |  348 +++--
 opends/src/server/org/opends/server/backends/jeb/EntryContainer.java                                        |   22 
 opends/src/server/org/opends/server/extensions/VirtualStaticGroup.java                                      |   13 
 opends/tests/unit-tests-testng/src/server/org/opends/server/api/DITCacheMapTestCase.java                    |  496 +++++++
 opends/src/server/org/opends/server/crypto/CryptoManagerSync.java                                           |    6 
 opends/src/server/org/opends/server/types/AuthenticationInfo.java                                           |   44 
 opends/src/server/org/opends/server/api/DITCacheMap.java                                                    |  791 ++++++++++++
 opends/src/server/org/opends/server/api/Group.java                                                          |   13 
 opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml               |    3 
 opends/src/messages/messages/plugin.properties                                                              |   12 
 44 files changed, 3,232 insertions(+), 507 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index c370f27..ada1f35 100644
--- a/opends/resource/config/config.ldif
+++ b/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
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 2bd9523..e95dd17 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/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
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/PluginConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/PluginConfiguration.xml
index de91736..d26a54e 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/PluginConfiguration.xml
+++ b/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
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/PluginRootConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/PluginRootConfiguration.xml
index 5eb8892..dea9a5c 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/PluginRootConfiguration.xml
+++ b/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
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
index 9f977ea..1cf8344 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
+++ b/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>
diff --git a/opends/src/admin/messages/ChangeNumberControlPluginCfgDefn.properties b/opends/src/admin/messages/ChangeNumberControlPluginCfgDefn.properties
index 0bee311..7fcf2f3 100644
--- a/opends/src/admin/messages/ChangeNumberControlPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/EntryUUIDPluginCfgDefn.properties b/opends/src/admin/messages/EntryUUIDPluginCfgDefn.properties
index 7dc5d3a..a9103ce 100644
--- a/opends/src/admin/messages/EntryUUIDPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/FractionalLDIFImportPluginCfgDefn.properties b/opends/src/admin/messages/FractionalLDIFImportPluginCfgDefn.properties
index dec2887..0ae518b 100644
--- a/opends/src/admin/messages/FractionalLDIFImportPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/LDAPAttributeDescriptionListPluginCfgDefn.properties b/opends/src/admin/messages/LDAPAttributeDescriptionListPluginCfgDefn.properties
index 44de0e4..99a39ad 100644
--- a/opends/src/admin/messages/LDAPAttributeDescriptionListPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/LastModPluginCfgDefn.properties b/opends/src/admin/messages/LastModPluginCfgDefn.properties
index 3080f98..d17d72a 100644
--- a/opends/src/admin/messages/LastModPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/NetworkGroupPluginCfgDefn.properties b/opends/src/admin/messages/NetworkGroupPluginCfgDefn.properties
index a9a8b5e..2a1c4e6 100644
--- a/opends/src/admin/messages/NetworkGroupPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/PasswordPolicyImportPluginCfgDefn.properties b/opends/src/admin/messages/PasswordPolicyImportPluginCfgDefn.properties
index d57f754..fe8fdc7 100644
--- a/opends/src/admin/messages/PasswordPolicyImportPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/PluginCfgDefn.properties b/opends/src/admin/messages/PluginCfgDefn.properties
index 12ef5b4..86d6384 100644
--- a/opends/src/admin/messages/PluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/PluginRootCfgDefn.properties b/opends/src/admin/messages/PluginRootCfgDefn.properties
index 95b0e64..a22fc82 100644
--- a/opends/src/admin/messages/PluginRootCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/ProfilerPluginCfgDefn.properties b/opends/src/admin/messages/ProfilerPluginCfgDefn.properties
index 7c55b1e..b091793 100644
--- a/opends/src/admin/messages/ProfilerPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/ReferentialIntegrityPluginCfgDefn.properties b/opends/src/admin/messages/ReferentialIntegrityPluginCfgDefn.properties
index 1c4a9fe..b263a86 100644
--- a/opends/src/admin/messages/ReferentialIntegrityPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/SevenBitCleanPluginCfgDefn.properties b/opends/src/admin/messages/SevenBitCleanPluginCfgDefn.properties
index 469cc2f..69e1a1d 100644
--- a/opends/src/admin/messages/SevenBitCleanPluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/admin/messages/UniqueAttributePluginCfgDefn.properties b/opends/src/admin/messages/UniqueAttributePluginCfgDefn.properties
index 97052f4..d4d27f5 100644
--- a/opends/src/admin/messages/UniqueAttributePluginCfgDefn.properties
+++ b/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.
diff --git a/opends/src/messages/messages/jeb.properties b/opends/src/messages/messages/jeb.properties
index f1f8cdf..2ba7d9d 100644
--- a/opends/src/messages/messages/jeb.properties
+++ b/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
diff --git a/opends/src/messages/messages/plugin.properties b/opends/src/messages/messages/plugin.properties
index aa03fb8..0ab9988 100644
--- a/opends/src/messages/messages/plugin.properties
+++ b/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.
 
 
 
@@ -412,4 +412,12 @@
 SEVERE_ERR_PLUGIN_CHANGE_NUMBER_INVALID_PLUGIN_TYPE_LIST_114=An attempt was \
  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
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/opends/src/server/org/opends/server/api/DITCacheMap.java b/opends/src/server/org/opends/server/api/DITCacheMap.java
new file mode 100644
index 0000000..6f2d335
--- /dev/null
+++ b/opends/src/server/org/opends/server/api/DITCacheMap.java
@@ -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;
+    }
+  }
+}
diff --git a/opends/src/server/org/opends/server/api/Group.java b/opends/src/server/org/opends/server/api/Group.java
index 60d19fb..d3bf5d6 100644
--- a/opends/src/server/org/opends/server/api/Group.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java b/opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java
index 940ff5d..eaab22e 100644
--- a/opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/api/plugin/PluginResult.java b/opends/src/server/org/opends/server/api/plugin/PluginResult.java
index 8f16525..4a51e1e 100644
--- a/opends/src/server/org/opends/server/api/plugin/PluginResult.java
+++ b/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,
diff --git a/opends/src/server/org/opends/server/api/plugin/PluginType.java b/opends/src/server/org/opends/server/api/plugin/PluginType.java
index 76033a9..6f95bff 100644
--- a/opends/src/server/org/opends/server/api/plugin/PluginType.java
+++ b/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.
    */
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
index b069c07..0674f03 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
+++ b/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,39 +84,52 @@
   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();
-    //Save the baseDN in case we need to evaluate a global ACI.
-    DN entryDN=baseDN;
-    while(baseDN != null) {
-      List<Aci> acis = aciList.get(baseDN);
-      if (acis != null) {
-       //Check if there are global ACIs. Global ACI has a NULL DN.
-       if(baseDN.isNullDN()) {
-           for(Aci aci : acis) {
-               AciTargets targets=aci.getTargets();
-               //If there is a target, evaluate it to see if this ACI should
-               //be included in the candidate set.
-               if(targets != null) {
-                   boolean ret=AciTargets.isTargetApplicable(aci, targets,
-                                                             entryDN);
-                   if(ret)
-                      candidates.add(aci);  //Add this ACI to the candidates.
-               }
-           }
-       } else
-           candidates.addAll(acis);
-      }
-      if(baseDN.isNullDN())
-        break;
-      DN parentDN=baseDN.getParent();
-      if(parentDN == null)
-        baseDN=DN.nullDN();
-      else
-        baseDN=parentDN;
     }
+
+    lock.readLock().lock();
+    try
+    {
+      //Save the baseDN in case we need to evaluate a global ACI.
+      DN entryDN=baseDN;
+      while (baseDN != null) {
+        List<Aci> acis = aciList.get(baseDN);
+        if (acis != null) {
+          //Check if there are global ACIs. Global ACI has a NULL DN.
+          if (baseDN.isNullDN()) {
+            for (Aci aci : acis) {
+              AciTargets targets = aci.getTargets();
+              //If there is a target, evaluate it to see if this ACI should
+              //be included in the candidate set.
+              if (targets != null) {
+                boolean ret = AciTargets.isTargetApplicable(aci, targets,
+                        entryDN);
+                if (ret) {
+                  candidates.add(aci);  //Add this ACI to the candidates.
+                }
+              }
+            }
+          } else {
+            candidates.addAll(acis);
+          }
+        }
+        if(baseDN.isNullDN()) {
+          break;
+        }
+        DN parentDN=baseDN.getParent();
+        if(parentDN == null) {
+          baseDN=DN.nullDN();
+        } 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;
-    for (Entry entry : entries) {
-      DN dn=entry.getDN();
-      List<Attribute> attributeList =
-           entry.getOperationalAttribute(AciHandler.aciType);
-      validAcis += addAciAttributeList(aciCopy, dn, configDN,
-                                       attributeList, failedACIMsgs);
+
+    lock.writeLock().lock();
+    try
+    {
+      for (Entry entry : entries) {
+        DN dn=entry.getDN();
+        List<Attribute> attributeList =
+             entry.getOperationalAttribute(AciHandler.aciType);
+        validAcis += addAciAttributeList(aciList, dn, configDN,
+                                         attributeList, failedACIMsgs);
+      }
+    }
+    finally
+    {
+      lock.writeLock().unlock();
     }
 
-    // Replace the ACI list with the copy.
-    aciList = aciCopy;
     return validAcis;
   }
 
@@ -167,8 +175,16 @@
    * @param acis A set of ACIs to add to the ACI list.
    *
    */
-  public synchronized void addAci(DN dn, SortedSet<Aci> acis) {
-    aciList.put(dn, new LinkedList<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();
+    }
   }
 
   /**
@@ -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();
-    //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,
-                                        attributeList, failedACIMsgs);
+    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(aciList, DN.nullDN(), configDN,
+                                          attributeList, failedACIMsgs);
+      }
+
+      if(hasAci) {
+          List<Attribute> attributeList = entry.getAttribute(aciType);
+          validAcis += addAciAttributeList(aciList, entry.getDN(), configDN,
+                                           attributeList, failedACIMsgs);
+      }
+    }
+    finally
+    {
+      lock.writeLock().unlock();
     }
 
-    if(hasAci) {
-        List<Attribute> attributeList = entry.getAttribute(aciType);
-        validAcis += addAciAttributeList(aciCopy, entry.getDN(), configDN,
-                                         attributeList, failedACIMsgs);
-    }
-    // Replace the ACI list with the copy.
-    aciList = aciCopy;
     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,19 +356,33 @@
    * 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)
-          return false;
-      if(hasAci && aciCopy.remove(entry.getDN()) == null)
-          return false;
-      // Replace the ACI list with the copy.
-      aciList = aciCopy;
-      return true;
+    lock.writeLock().lock();
+    try
+    {
+      if (hasGlobalAci && entryDN.equals(configDN) &&
+          aciList.remove(DN.nullDN()) == null)
+      {
+        return false;
+      }
+      if (hasAci && aciList.remove(entryDN) == null)
+      {
+        return false;
+      }
+      if (!hasGlobalAci && !hasAci)
+      {
+        return aciList.removeSubtree(entryDN, null);
+      }
+    }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
+
+    return true;
   }
 
   /**
@@ -351,22 +390,26 @@
    * @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();
-    while (iterator.hasNext())
+    lock.writeLock().lock();
+    try
     {
-      Map.Entry<DN,List<Aci>> mapEntry = iterator.next();
-      if (backend.handlesEntry(mapEntry.getKey()))
+      Iterator<Map.Entry<DN,List<Aci>>> iterator =
+              aciList.entrySet().iterator();
+      while (iterator.hasNext())
       {
-        iterator.remove();
+        Map.Entry<DN,List<Aci>> mapEntry = iterator.next();
+        if (backend.handlesEntry(mapEntry.getKey()))
+        {
+          iterator.remove();
+        }
       }
     }
-
-    // Replace the ACI list with the copy.
-    aciList = aciCopy;
+    finally
+    {
+      lock.writeLock().unlock();
+    }
   }
 
   /**
@@ -375,41 +418,54 @@
    * @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()) {
-      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++)
-          newRDNs[i] = hashEntry.getKey().getRDN(i);
-        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()) {
-          try {
-             Aci newAci =
-               Aci.decode(ByteString.valueOf(aci.toString()), relocateDN);
-             acis.add(newAci);
-          } catch (AciException ex) {
-            //This should never happen since only a copy of the
-            //ACI with a new DN is being made. Log a message if it does and
-            //keep going.
-            Message message = WARN_ACI_ADD_LIST_FAILED_DECODE.get(
-                aci.toString(), String.valueOf(relocateDN), ex.getMessage());
-            logError(message);
+
+    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++) {
+            newRDNs[i] = hashEntry.getKey().getRDN(i);
           }
+          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()) {
+            try {
+               Aci newAci =
+                 Aci.decode(ByteString.valueOf(aci.toString()), relocateDN);
+               acis.add(newAci);
+            } catch (AciException ex) {
+              //This should never happen since only a copy of the
+              //ACI with a new DN is being made. Log a message if it does and
+              //keep going.
+              Message message = WARN_ACI_ADD_LIST_FAILED_DECODE.get(
+                  aci.toString(), String.valueOf(relocateDN), ex.getMessage());
+              logError(message);
+            }
+          }
+          tempAciList.put(relocateDN, acis);
+          iterator.remove();
         }
-        newCopyList.put(relocateDN, acis);
-      }  else
-        newCopyList.put(hashEntry.getKey(), hashEntry.getValue());
+      }
+      aciList.putAll(tempAciList);
     }
-    // Replace the ACI list with the copy.
-    aciList = newCopyList;
+    finally
+    {
+      lock.writeLock().unlock();
+    }
   }
 }
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
index 09cf14b..c4035b4 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
+++ b/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
@@ -240,14 +240,11 @@
     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)))
-      {
-        aciList.removeAci(deletedEntry, hasAci, hasGlobalAci);
-      }
+      boolean hasAci = deletedEntry.hasOperationalAttribute(
+              AciHandler.aciType);
+      boolean hasGlobalAci = deletedEntry.hasAttribute(
+              AciHandler.globalAciType);
+      aciList.removeAci(deletedEntry, hasAci, hasGlobalAci);
     }
 
 
diff --git a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
index 8712bcb..f991b2c 100644
--- a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/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++;
 
diff --git a/opends/src/server/org/opends/server/core/AuthenticatedUsers.java b/opends/src/server/org/opends/server/core/AuthenticatedUsers.java
index 55e9b70..43226c2 100644
--- a/opends/src/server/org/opends/server/core/AuthenticatedUsers.java
+++ b/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,18 +97,27 @@
    * @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);
-    if (connectionSet == null)
+    lock.writeLock().lock();
+    try
     {
-      connectionSet = new CopyOnWriteArraySet<ClientConnection>();
-      connectionSet.add(clientConnection);
-      userMap.put(userDN, connectionSet);
+      CopyOnWriteArraySet<ClientConnection> connectionSet =
+              userMap.get(userDN);
+      if (connectionSet == null)
+      {
+        connectionSet = new CopyOnWriteArraySet<ClientConnection>();
+        connectionSet.add(clientConnection);
+        userMap.put(userDN, connectionSet);
+      }
+      else
+      {
+        connectionSet.add(clientConnection);
+      }
     }
-    else
+    finally
     {
-      connectionSet.add(clientConnection);
+      lock.writeLock().unlock();
     }
   }
 
@@ -108,17 +131,26 @@
    * @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);
-    if (connectionSet != null)
+    lock.writeLock().lock();
+    try
     {
-      connectionSet.remove(clientConnection);
-      if (connectionSet.isEmpty())
+      CopyOnWriteArraySet<ClientConnection> connectionSet =
+              userMap.get(userDN);
+      if (connectionSet != null)
       {
-        userMap.remove(userDN);
+        connectionSet.remove(clientConnection);
+        if (connectionSet.isEmpty())
+        {
+          userMap.remove(userDN);
+        }
       }
     }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
   }
 
 
@@ -136,7 +168,15 @@
    */
   synchronized CopyOnWriteArraySet<ClientConnection> get(DN userDN)
   {
-    return userMap.get(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,18 +254,26 @@
                    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.
-    CopyOnWriteArraySet<ClientConnection> connectionSet =
-         userMap.get(oldEntry.getDN());
-    if (connectionSet != null)
+    // 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
     {
-      for (ClientConnection conn : connectionSet)
+      CopyOnWriteArraySet<ClientConnection> connectionSet =
+           userMap.get(oldEntry.getDN());
+      if (connectionSet != null)
       {
-        conn.updateAuthenticationInfo(oldEntry, newEntry);
+        for (ClientConnection conn : connectionSet)
+        {
+          conn.updateAuthenticationInfo(oldEntry, newEntry);
+        }
       }
     }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
   }
 
 
@@ -232,32 +291,108 @@
                    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();
 
-      for (ClientConnection conn : connectionSet)
+    // 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)
       {
-        conn.updateAuthenticationInfo(oldEntry, newEntry);
+        DN authNDN = null;
+        DN authZDN = null;
+        DN newAuthNDN = null;
+        DN newAuthZDN = null;
+        CopyOnWriteArraySet<ClientConnection> newAuthNSet = null;
+        CopyOnWriteArraySet<ClientConnection> newAuthZSet = null;
+        for (ClientConnection conn : connectionSet)
+        {
+          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();
+    }
   }
 }
 
diff --git a/opends/src/server/org/opends/server/core/GroupManager.java b/opends/src/server/org/opends/server/core/GroupManager.java
index 5eddc18..1cf06c1 100644
--- a/opends/src/server/org/opends/server/core/GroupManager.java
+++ b/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,15 +297,23 @@
     Group group = groupImplementations.remove(configuration.dn());
     if (group != null)
     {
-      Iterator<Group> iterator = groupInstances.values().iterator();
-      while (iterator.hasNext())
+      lock.writeLock().lock();
+      try
       {
-        Group g = iterator.next();
-        if (g.getClass().getName().equals(group.getClass().getName()))
+        Iterator<Group> iterator = groupInstances.values().iterator();
+        while (iterator.hasNext())
         {
-          iterator.remove();
+          Group g = iterator.next();
+          if (g.getClass().getName().equals(group.getClass().getName()))
+          {
+            iterator.remove();
+          }
         }
       }
+      finally
+      {
+        lock.writeLock().unlock();
+      }
 
       group.finalizeGroupImplementation();
     }
@@ -360,15 +376,23 @@
         Group group = groupImplementations.remove(configuration.dn());
         if (group != null)
         {
-          Iterator<Group> iterator = groupInstances.values().iterator();
-          while (iterator.hasNext())
+          lock.writeLock().lock();
+          try
           {
-            Group g = iterator.next();
-            if (g.getClass().getName().equals(group.getClass().getName()))
+            Iterator<Group> iterator = groupInstances.values().iterator();
+            while (iterator.hasNext())
             {
-              iterator.remove();
+              Group g = iterator.next();
+              if (g.getClass().getName().equals(group.getClass().getName()))
+              {
+                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,25 +700,33 @@
           continue;
         }
 
-        for (SearchResultEntry entry : internalSearch.getSearchEntries())
+        lock.writeLock().lock();
+        try
         {
-          try
+          for (SearchResultEntry entry : internalSearch.getSearchEntries())
           {
-            Group groupInstance = groupImplementation.newInstance(entry);
-            groupInstances.put(entry.getDN(), groupInstance);
-            refreshToken++;
-          }
-          catch (Exception e)
-          {
-            if (debugEnabled())
+            try
             {
-              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              Group groupInstance = groupImplementation.newInstance(entry);
+              groupInstances.put(entry.getDN(), groupInstance);
+              refreshToken++;
             }
+            catch (Exception e)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              }
 
-            // FIXME -- Handle this.
-            continue;
+              // FIXME -- Handle this.
+              continue;
+            }
           }
         }
+        finally
+        {
+          lock.writeLock().unlock();
+        }
       }
     }
   }
@@ -685,17 +739,25 @@
    */
   public void performBackendFinalizationProcessing(Backend backend)
   {
-    Iterator<Map.Entry<DN,Group>> iterator =
-         groupInstances.entrySet().iterator();
-    while (iterator.hasNext())
+    lock.writeLock().lock();
+    try
     {
-      Map.Entry<DN,Group> mapEntry = iterator.next();
-      DN groupEntryDN = mapEntry.getKey();
-      if (backend.handlesEntry(groupEntryDN))
+      Iterator<Map.Entry<DN,Group>> iterator =
+           groupInstances.entrySet().iterator();
+      while (iterator.hasNext())
       {
-        iterator.remove();
+        Map.Entry<DN,Group> mapEntry = iterator.next();
+        DN groupEntryDN = mapEntry.getKey();
+        if (backend.handlesEntry(groupEntryDN))
+        {
+          iterator.remove();
+        }
       }
     }
+    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());
-        refreshToken++;
+        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,7 +950,16 @@
         if (groupImplementation.isGroupDefinition(entry))
         {
           Group groupInstance = groupImplementation.newInstance(entry);
-          groupInstances.put(entry.getDN(), groupInstance);
+
+          lock.writeLock().lock();
+          try
+          {
+            groupInstances.put(entry.getDN(), groupInstance);
+          }
+          finally
+          {
+            lock.writeLock().unlock();
+          }
         }
       }
       catch (Exception e)
@@ -869,7 +983,15 @@
    */
   void deregisterAllGroups()
   {
-    groupInstances.clear();
+    lock.writeLock().lock();
+    try
+    {
+      groupInstances.clear();
+    }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
   }
 
 
diff --git a/opends/src/server/org/opends/server/core/PluginConfigManager.java b/opends/src/server/org/opends/server/core/PluginConfigManager.java
index 3f0bf15..9a74cd8 100644
--- a/opends/src/server/org/opends/server/core/PluginConfigManager.java
+++ b/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.
    *
diff --git a/opends/src/server/org/opends/server/core/SubentryManager.java b/opends/src/server/org/opends/server/core/SubentryManager.java
index 60a8c4e..b95f626 100644
--- a/opends/src/server/org/opends/server/core/SubentryManager.java
+++ b/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,17 +741,63 @@
   {
     if (entry.isSubentry() || entry.isLDAPSubentry())
     {
+      lock.writeLock().lock();
       try
       {
-        addSubEntry(entry);
+        try
+        {
+          addSubEntry(entry);
+
+          // Notify change listeners.
+          for (SubentryChangeListener changeListener :
+            changeListeners)
+          {
+            try
+            {
+              changeListener.handleSubentryAdd(entry);
+            }
+            catch (Exception e)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              }
+            }
+          }
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          // FIXME -- Handle this.
+        }
+      }
+      finally
+      {
+        lock.writeLock().unlock();
+      }
+    }
+  }
+
+  private void doPostDelete(Entry entry)
+  {
+    lock.writeLock().lock();
+    try
+    {
+      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getDN()))
+      {
+        removeSubEntry(subEntry.getEntry());
 
         // Notify change listeners.
         for (SubentryChangeListener changeListener :
-          changeListeners)
+                changeListeners)
         {
           try
           {
-            changeListener.handleSubentryAdd(entry);
+            changeListener.handleSubentryDelete(subEntry.getEntry());
           }
           catch (Exception e)
           {
@@ -752,40 +808,10 @@
           }
         }
       }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        // FIXME -- Handle this.
-      }
     }
-  }
-
-  private void doPostDelete(Entry entry)
-  {
-    if (entry.isSubentry() || entry.isLDAPSubentry())
+    finally
     {
-      removeSubEntry(entry);
-
-      // Notify change listeners.
-      for (SubentryChangeListener changeListener :
-        changeListeners)
-      {
-        try
-        {
-          changeListener.handleSubentryDelete(entry);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-        }
-      }
+      lock.writeLock().unlock();
     }
   }
 
@@ -793,39 +819,20 @@
   {
     boolean notify = false;
 
-    if (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
+    lock.writeLock().lock();
+    try
     {
-      removeSubEntry(oldEntry);
-      notify = true;
-    }
-    if (newEntry.isSubentry() || newEntry.isLDAPSubentry())
-    {
-      try
+      if (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
       {
-        addSubEntry(newEntry);
+        removeSubEntry(oldEntry);
         notify = true;
       }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        // FIXME -- Handle this.
-      }
-    }
-
-    if (notify)
-    {
-      // Notify change listeners.
-      for (SubentryChangeListener changeListener :
-        changeListeners)
+      if (newEntry.isSubentry() || newEntry.isLDAPSubentry())
       {
         try
         {
-          changeListener.handleSubentryModify(
-                  oldEntry, newEntry);
+          addSubEntry(newEntry);
+          notify = true;
         }
         catch (Exception e)
         {
@@ -833,48 +840,96 @@
           {
             TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
+
+          // FIXME -- Handle this.
         }
       }
+
+      if (notify)
+      {
+        // Notify change listeners.
+        for (SubentryChangeListener changeListener :
+          changeListeners)
+        {
+          try
+          {
+            changeListener.handleSubentryModify(
+                    oldEntry, newEntry);
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+          }
+        }
+      }
+    }
+    finally
+    {
+      lock.writeLock().unlock();
     }
   }
 
   private void doPostModifyDN(Entry oldEntry, Entry newEntry)
   {
-    if (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
+    String oldDNString = oldEntry.getDN().toNormalizedString();
+    String newDNString = newEntry.getDN().toNormalizedString();
+
+    lock.writeLock().lock();
+    try
     {
-      removeSubEntry(oldEntry);
-      try
+      Collection<SubEntry> setToDelete =
+              dit2SubEntry.getSubtree(oldEntry.getDN());
+      for (SubEntry subentry : setToDelete)
       {
-        addSubEntry(newEntry);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        // FIXME -- Handle this.
-      }
-
-      // Notify change listeners.
-      for (SubentryChangeListener changeListener :
-        changeListeners)
-      {
+        removeSubEntry(subentry.getEntry());
+        oldEntry = subentry.getEntry();
         try
         {
-          changeListener.handleSubentryModify(
-                  oldEntry, newEntry);
+          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);
           }
         }
+
+        // Notify change listeners.
+        for (SubentryChangeListener changeListener :
+          changeListeners)
+        {
+          try
+          {
+            changeListener.handleSubentryModify(
+                    oldEntry, newEntry);
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+          }
+        }
       }
     }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
   }
 
   /**
@@ -920,27 +975,36 @@
   {
     Entry entry = deleteOperation.getEntryToDelete();
 
-    if (entry.isSubentry() || entry.isLDAPSubentry())
+    lock.readLock().lock();
+    try
     {
-      for (SubentryChangeListener changeListener :
-              changeListeners)
+      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getDN()))
       {
-        try
+        for (SubentryChangeListener changeListener :
+                changeListeners)
         {
-          changeListener.checkSubentryDeleteAcceptable(entry);
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
+          try
           {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            changeListener.checkSubentryDeleteAcceptable(
+                    subEntry.getEntry());
           }
+          catch (DirectoryException de)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
 
-          return PluginResult.PreOperation.stopProcessing(
-                  de.getResultCode(), de.getMessageObject());
+            return PluginResult.PreOperation.stopProcessing(
+                    de.getResultCode(), de.getMessageObject());
+          }
         }
       }
     }
+    finally
+    {
+      lock.readLock().unlock();
+    }
 
     return PluginResult.PreOperation.continueOperationProcessing();
   }
@@ -991,29 +1055,61 @@
   {
     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
     {
-      for (SubentryChangeListener changeListener :
-              changeListeners)
+      Collection<SubEntry> setToDelete =
+              dit2SubEntry.getSubtree(oldEntry.getDN());
+      for (SubEntry subentry : setToDelete)
       {
+        oldEntry = subentry.getEntry();
         try
         {
-          changeListener.checkSubentryModifyAcceptable(
-                  oldEntry, newEntry);
+          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 (DirectoryException de)
+        catch (Exception e)
         {
+          // Shouldnt happen.
           if (debugEnabled())
           {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
+        }
+        for (SubentryChangeListener changeListener :
+                changeListeners)
+        {
+          try
+          {
+            changeListener.checkSubentryModifyAcceptable(
+                    oldEntry, newEntry);
+          }
+          catch (DirectoryException de)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
 
-          return PluginResult.PreOperation.stopProcessing(
-                  de.getResultCode(), de.getMessageObject());
+            return PluginResult.PreOperation.stopProcessing(
+                    de.getResultCode(), de.getMessageObject());
+          }
         }
       }
     }
+    finally
+    {
+      lock.readLock().unlock();
+    }
 
     return PluginResult.PreOperation.continueOperationProcessing();
   }
diff --git a/opends/src/server/org/opends/server/crypto/CryptoManagerSync.java b/opends/src/server/org/opends/server/crypto/CryptoManagerSync.java
index e183408..61e08e9 100644
--- a/opends/src/server/org/opends/server/crypto/CryptoManagerSync.java
+++ b/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.
   }
 }
diff --git a/opends/src/server/org/opends/server/extensions/DynamicGroup.java b/opends/src/server/org/opends/server/extensions/DynamicGroup.java
index dc81701..6c4c594 100644
--- a/opends/src/server/org/opends/server/extensions/DynamicGroup.java
+++ b/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.
    *
diff --git a/opends/src/server/org/opends/server/extensions/StaticGroup.java b/opends/src/server/org/opends/server/extensions/StaticGroup.java
index 6804905..49ed4bc 100644
--- a/opends/src/server/org/opends/server/extensions/StaticGroup.java
+++ b/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()
   {
diff --git a/opends/src/server/org/opends/server/extensions/VirtualStaticGroup.java b/opends/src/server/org/opends/server/extensions/VirtualStaticGroup.java
index 4d8a1a0..af0a76a 100644
--- a/opends/src/server/org/opends/server/extensions/VirtualStaticGroup.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java b/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
index e279194..5dab099 100644
--- a/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
+++ b/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)
+    Map<DN,DN>modDNmap=
+         (Map<DN, DN>) modifyDNOperation.getAttachment(MODIFYDN_DNS);
+    if(modDNmap == 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));
+      modDNmap=new LinkedHashMap<DN,DN>();
+      modifyDNOperation.setAttachment(MODIFYDN_DNS, modDNmap);
     }
-    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));
-    }
+    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();
-        writer.write(deletedEntryDN.toNormalizedString());
-        writer.newLine();
+        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);
diff --git a/opends/src/server/org/opends/server/types/AuthenticationInfo.java b/opends/src/server/org/opends/server/types/AuthenticationInfo.java
index b43adc3..f9d3875 100644
--- a/opends/src/server/org/opends/server/types/AuthenticationInfo.java
+++ b/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();
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/api/DITCacheMapTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/api/DITCacheMapTestCase.java
new file mode 100644
index 0000000..75c8d50
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/api/DITCacheMapTestCase.java
@@ -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));
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/api/plugin/DirectoryServerPluginTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/api/plugin/DirectoryServerPluginTestCase.java
index 90d7f9a..271e8c4 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/api/plugin/DirectoryServerPluginTestCase.java
+++ b/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");
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
index c8e9610..976e0d9 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
+++ b/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.
    *
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
index 971b761..b8856bd 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/GroupManagerTestCase.java
+++ b/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"
+    );
+  }
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java
index 068ab7f..e357678 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java
+++ b/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()));
+  }
 }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
index 90453af..b5e1e49 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
+++ b/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);

--
Gitblit v1.10.0