From 94bbd5bb7c20a490558d8ec97d1be7e3dc492a42 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Sun, 09 Sep 2007 23:08:09 +0000
Subject: [PATCH] Update the server to provide a basic framework for controlling when plugins will be invoked.  There are two basic changes:

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java         |    4 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java         |   44 
 opends/tests/unit-tests-testng/src/server/org/opends/server/api/plugin/DirectoryServerPluginTestCase.java |  176 +++
 opends/src/server/org/opends/server/api/plugin/PluginType.java                                            |   90 +
 opends/src/server/org/opends/server/types/operation/PostSynchronizationOperation.java                     |  144 ++
 opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyOperation.java               |  148 ++
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java            |    4 
 opends/src/server/org/opends/server/util/ServerConstants.java                                             |   44 
 opends/tests/unit-tests-testng/resource/config-changes.ldif                                               |   20 
 opends/src/admin/defn/org/opends/server/admin/std/PluginRootConfiguration.xml                             |  117 ++
 opends/src/server/org/opends/server/types/operation/PostSynchronizationAddOperation.java                  |  136 ++
 opends/resource/schema/02-config.ldif                                                                     |   27 
 opends/src/admin/defn/org/opends/server/admin/std/PluginConfiguration.xml                                 |   54 
 opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java                                 |  139 ++
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java       |    4 
 opends/src/server/org/opends/server/core/PluginConfigManager.java                                         |  606 +++++++++-
 opends/resource/config/config.ldif                                                                        |   11 
 opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml                  |    3 
 opends/src/server/org/opends/server/types/operation/PostSynchronizationDeleteOperation.java               |   78 +
 opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java                                    |   16 
 opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyDNOperation.java             |  189 +++
 opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java                                    | 1137 ++++++++++++++------
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java         |    4 
 opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/InvocationCounterPlugin.java          |   66 +
 opends/src/messages/messages/plugin.properties                                                            |   16 
 25 files changed, 2,833 insertions(+), 444 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 6b9862b..240ee9e 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -1451,6 +1451,7 @@
 ds-cfg-7-bit-clean-attribute-type: uid
 ds-cfg-7-bit-clean-attribute-type: mail
 ds-cfg-7-bit-clean-attribute-type: userPassword
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=Entry UUID,cn=Plugins,cn=config
 objectClass: top
@@ -1461,6 +1462,7 @@
 ds-cfg-plugin-enabled: true
 ds-cfg-plugin-type: ldifImport
 ds-cfg-plugin-type: preOperationAdd
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=LastMod,cn=Plugins,cn=config
 objectClass: top
@@ -1472,6 +1474,7 @@
 ds-cfg-plugin-type: preOperationAdd
 ds-cfg-plugin-type: preOperationModify
 ds-cfg-plugin-type: preOperationModifyDN
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=LDAP Attribute Description List,cn=Plugins,cn=config
 objectClass: top
@@ -1481,6 +1484,7 @@
 ds-cfg-plugin-class: org.opends.server.plugins.LDAPADListPlugin
 ds-cfg-plugin-enabled: true
 ds-cfg-plugin-type: preParseSearch
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=Password Policy Import,cn=Plugins,cn=config
 objectClass: top
@@ -1492,6 +1496,7 @@
 ds-cfg-plugin-type: ldifImport
 ds-cfg-default-user-password-storage-scheme-dn: cn=Salted SHA-1,cn=Password Storage Schemes,cn=config
 ds-cfg-default-auth-password-storage-scheme-dn: cn=Salted SHA-1,cn=Password Storage Schemes,cn=config
+ds-cfg-invoke-for-internal-operations: false
 
 dn: cn=Profiler,cn=Plugins,cn=config
 objectClass: top
@@ -1504,6 +1509,7 @@
 ds-cfg-enable-profiling-on-startup: false
 ds-cfg-profile-directory: logs
 ds-cfg-profile-sample-interval: 10 milliseconds
+ds-cfg-invoke-for-internal-operations: false
 
 dn: cn=Referential Integrity,cn=Plugins,cn=config
 objectClass: top
@@ -1517,6 +1523,7 @@
 ds-cfg-plugin-type: subordinateModifyDN
 ds-cfg-referential-integrity-attribute-type: member
 ds-cfg-referential-integrity-attribute-type: uniqueMember
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=UID Unique Attribute,cn=Plugins,cn=config
 objectClass: top
@@ -1528,7 +1535,11 @@
 ds-cfg-plugin-type: preOperationAdd
 ds-cfg-plugin-type: preOperationModify
 ds-cfg-plugin-type: preOperationModifyDN
+ds-cfg-plugin-type: postSynchronizationAdd
+ds-cfg-plugin-type: postSynchronizationModify
+ds-cfg-plugin-type: postSynchronizationModifyDN
 ds-cfg-unique-attribute-type: uid
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=Root DNs,cn=config
 objectClass: top
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 144ad47..145aeae 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -1658,6 +1658,26 @@
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.490 NAME 'ds-cfg-poll-interval'
   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.491
+  NAME 'ds-cfg-plugin-order-synchronization-add'
+  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.492
+  NAME 'ds-cfg-plugin-order-synchronization-delete'
+  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.493
+  NAME 'ds-cfg-plugin-order-synchronization-modify'
+  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.494
+  NAME 'ds-cfg-plugin-order-synchronization-modify-dn'
+  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.495
+  NAME 'ds-cfg-invoke-for-internal-operations'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
+  X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
   NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
   MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1847,7 +1867,8 @@
   ds-cfg-password-validator-enabled ) X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.37 NAME 'ds-cfg-plugin' SUP top
   STRUCTURAL MUST ( cn $ ds-cfg-plugin-class $ ds-cfg-plugin-enabled $
-  ds-cfg-plugin-type ) X-ORIGIN 'OpenDS Directory Server' )
+  ds-cfg-plugin-type ) MAY ds-cfg-invoke-for-internal-operations
+  X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.38 NAME 'ds-cfg-profiler-plugin'
   SUP ds-cfg-plugin STRUCTURAL MAY ( ds-cfg-enable-profiling-on-startup $
   ds-cfg-profile-directory $ ds-cfg-profile-sample-interval $
@@ -2254,6 +2275,10 @@
   ds-cfg-plugin-order-post-response-modify $
   ds-cfg-plugin-order-post-response-modify-dn $
   ds-cfg-plugin-order-post-response-search $
+  ds-cfg-plugin-order-synchronization-add $
+  ds-cfg-plugin-order-synchronization-delete $
+  ds-cfg-plugin-order-synchronization-modify $
+  ds-cfg-plugin-order-synchronization-modify-dn $
   ds-cfg-plugin-order-search-result-entry $
   ds-cfg-plugin-order-search-result-reference $
   ds-cfg-plugin-order-subordinate-modify-dn $
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 1194f54..cc13da3 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
@@ -31,6 +31,7 @@
   package="org.opends.server.admin.std" abstract="false"
   xmlns:adm="http://www.opends.org/admin"
   xmlns:ldap="http://www.opends.org/admin-ldap">
+
   <adm:synopsis>
     <adm:user-friendly-plural-name />
     provide a mechanism for executing custom code at specified points in
@@ -38,7 +39,9 @@
     establishment and termination, server startup and shutdown, and LDIF import
     and export.
   </adm:synopsis>
+
   <adm:tag name="core"/>
+
   <adm:profile name="ldap">
     <ldap:object-class>
       <ldap:oid>1.3.6.1.4.1.26027.1.2.37</ldap:oid>
@@ -46,6 +49,7 @@
       <ldap:superior>top</ldap:superior>
     </ldap:object-class>
   </adm:profile>
+
   <adm:property name="enabled" mandatory="true">
     <adm:synopsis>
       Indicate whether the
@@ -62,6 +66,7 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+
   <adm:property name="plugin-class" mandatory="true">
     <adm:synopsis>
       The fully-qualified name of the Java class that provides the
@@ -82,6 +87,7 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+
   <adm:property name="plugin-type" mandatory="true" multi-valued="true">
     <adm:synopsis>
       The plugin types, which define the conditions under which this plugin
@@ -308,6 +314,30 @@
             Invoked after sending the search result done message to the client.
           </adm:synopsis>
         </adm:value>
+        <adm:value name="postsynchronizationadd">
+          <adm:synopsis>
+            Invoked after completing post-synchronization processing for an add
+            operation.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="postsynchronizationdelete">
+          <adm:synopsis>
+            Invoked after completing post-synchronization processing for a
+            delete operation.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="postsynchronizationmodify">
+          <adm:synopsis>
+            Invoked after completing post-synchronization processing for a
+            modify operation.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="postsynchronizationmodifydn">
+          <adm:synopsis>
+            Invoked after completing post-synchronization processing for a
+            modify DN operation.
+          </adm:synopsis>
+        </adm:value>
         <adm:value name="searchresultentry">
           <adm:synopsis>
             Invoked before sending a search result entry to the client.
@@ -339,5 +369,29 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+
+  <adm:property name="invoke-for-internal-operations" mandatory="false">
+    <adm:synopsis>
+      Indicates whether the plugin should be invoked for internal operations.
+      Note that any plugin which may be invoked for internal operations should
+      be careful to ensure that they do not create any new internal operatons
+      that can cause the same plugin to be re-invoked.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>true</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.495</ldap:oid>
+        <ldap:name>ds-cfg-invoke-for-internal-operations</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
 </adm:managed-object>
 
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 f35b605..c0bce10 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
@@ -1212,6 +1212,123 @@
     </adm:profile>
   </adm:property>
 
+  <adm:property name="plugin-order-post-synchronization-add" mandatory="false">
+    <adm:synopsis>
+      Specifies the order in which post-synchronization add plugins should be
+      loaded and invoked.  The value should be a comma-delimited list of plugin
+      names (where the plugin name is the RDN value from the plugin
+      configuration entry DN).  The list may include at most one asterisk to
+      indicate the position of any unspecified plugin (and the relative order of
+      those unspecified plugins will be undefined).
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          The order in which post-synchronization add plugins are loaded and
+          invoked will be undefined.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.491</ldap:oid>
+        <ldap:name>ds-cfg-plugin-order-post-synchronization-add</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+  <adm:property name="plugin-order-post-synchronization-delete"
+    mandatory="false">
+    <adm:synopsis>
+      Specifies the order in which post-synchronization delete plugins should be
+      loaded and invoked.  The value should be a comma-delimited list of plugin
+      names (where the plugin name is the RDN value from the plugin
+      configuration entry DN).  The list may include at most one asterisk to
+      indicate the position of any unspecified plugin (and the relative order of
+      those unspecified plugins will be undefined).
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          The order in which post-synchronization delete plugins are loaded and
+          invoked will be undefined.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.492</ldap:oid>
+        <ldap:name>ds-cfg-plugin-order-post-synchronization-delete</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+  <adm:property name="plugin-order-post-synchronization-modify"
+    mandatory="false">
+    <adm:synopsis>
+      Specifies the order in which post-synchronization modify plugins should be
+      loaded and invoked.  The value should be a comma-delimited list of plugin
+      names (where the plugin name is the RDN value from the plugin
+      configuration entry DN).  The list may include at most one asterisk to
+      indicate the position of any unspecified plugin (and the relative order of
+      those unspecified plugins will be undefined).
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          The order in which post-synchronization modify plugins are loaded and
+          invoked will be undefined.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.493</ldap:oid>
+        <ldap:name>ds-cfg-plugin-order-post-synchronization-modify</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+  <adm:property name="plugin-order-post-synchronization-modify-dn"
+    mandatory="false">
+    <adm:synopsis>
+      Specifies the order in which post-synchronization modify DN plugins should
+      be loaded and invoked.  The value should be a comma-delimited list of
+      plugin names (where the plugin name is the RDN value from the plugin
+      configuration entry DN).  The list may include at most one asterisk to
+      indicate the position of any unspecified plugin (and the relative order of
+      those unspecified plugins will be undefined).
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          The order in which post-synchronization modify DN plugins are loaded
+          and invoked will be undefined.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.494</ldap:oid>
+        <ldap:name>
+          ds-cfg-plugin-order-post-synchronization-modify-dn
+        </ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
   <adm:property name="plugin-order-post-response-search" mandatory="false">
     <adm:synopsis>
       Specifies the order in which post-response search plugins should be
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
index 3c87817..e2054c5 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
+++ b/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
@@ -63,6 +63,9 @@
         <adm:value>preoperationadd</adm:value>
         <adm:value>preoperationmodify</adm:value>
         <adm:value>preoperationmodifydn</adm:value>
+        <adm:value>postsynchronizationadd</adm:value>
+        <adm:value>postsynchronizationmodify</adm:value>
+        <adm:value>postsynchronizationmodifydn</adm:value>
       </adm:defined>
     </adm:default-behavior>
   </adm:property-override>
diff --git a/opends/src/messages/messages/plugin.properties b/opends/src/messages/messages/plugin.properties
index 8b0a93c..2dfd85e 100644
--- a/opends/src/messages/messages/plugin.properties
+++ b/opends/src/messages/messages/plugin.properties
@@ -379,3 +379,19 @@
 SEVERE_ERR_PLUGIN_PWIMPORT_NO_SUCH_DEFAULT_AUTH_SCHEME_104=The password \
  policy import plugin references default auth password storage scheme %s \
  which is not available for use in the server
+SEVERE_ERR_PLUGIN_POST_SYNCHRONIZATION_PLUGIN_EXCEPTION_105=The \
+ post-synchronization %s plugin defined in configuration entry %s threw an \
+ exception when it was invoked for connection %d operation %d:  %s
+SEVERE_ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE_106=A unique attribute conflict \
+ was detected for attribute %s:  value %s already exists in entry %s
+SEVERE_ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE_107=A unique attribute conflict \
+ was detected for attribute %s during synchronization (connID=%d, opID=%d):  \
+ value %s in entry %s conflicts with an existing value in entry %s.  Manual \
+ interaction is required to eliminate the conflict
+SEVERE_ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_108=An internal error occurred \
+ while attempting to determine whether the operation would have resulted in a \
+ unique attribute conflict (result %s, message %s)
+SEVERE_ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC_109=An internal error \
+ occurred while attempting to determine whether the synchronization operation \
+ (connID=%d, opID=%d) for entry %s would have resulted in a unique attribute \
+ conflict (result %s, message %s)
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 f99b60d..558978f 100644
--- a/opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java
+++ b/opends/src/server/org/opends/server/api/plugin/DirectoryServerPlugin.java
@@ -67,6 +67,10 @@
 public abstract class DirectoryServerPlugin
        <T extends PluginCfg>
 {
+  // Indicates whether this plugin should be invoked for internal
+  // operations.
+  private boolean invokeForInternalOps;
+
   // The DN of the configuration entry for this plugin.
   private DN pluginDN;
 
@@ -119,20 +123,23 @@
    * plugins regardless of type.  This should only be called by the
    * core Directory Server code during the course of loading a plugin.
    *
-   * @param  pluginDN     The DN of the plugin configuration entry.
-   * @param  pluginTypes  The set of plugin types for which this
-   *                      plugin is registered.
+   * @param  configuration  The configuration for this plugin.
+   * @param  pluginTypes    The set of plugin types for which this
+   *                        plugin is registered.
    */
- @org.opends.server.types.PublicAPI(
-      stability=org.opends.server.types.StabilityLevel.PRIVATE,
-      mayInstantiate=false,
-      mayExtend=false,
-      mayInvoke=false)
-  public final void initializeInternal(DN pluginDN,
+  @org.opends.server.types.PublicAPI(
+       stability=org.opends.server.types.StabilityLevel.PRIVATE,
+       mayInstantiate=false,
+       mayExtend=false,
+       mayInvoke=false)
+  public final void initializeInternal(PluginCfg configuration,
                                        Set<PluginType> pluginTypes)
   {
-    this.pluginDN    = pluginDN;
     this.pluginTypes = pluginTypes;
+
+    pluginDN = configuration.dn();
+    invokeForInternalOps =
+         configuration.isInvokeForInternalOperations();
   }
 
 
@@ -198,6 +205,41 @@
 
 
   /**
+   * Indicates whether this plugin should be invoked for internal
+   * operations.
+   *
+   * @return  {@code true} if this plugin should be invoked for
+   *          internal operations, or {@code false} if not.
+   */
+  public final boolean invokeForInternalOperations()
+  {
+    return invokeForInternalOps;
+  }
+
+
+
+  /**
+   * Specifies whether this plugin should be invoked for internal
+   * operations.
+   *
+   * @param  invokeForInternalOps  Indicates whether this plugin
+   *                               should be invoked for internal
+   *                               operations.
+   */
+  @org.opends.server.types.PublicAPI(
+       stability=org.opends.server.types.StabilityLevel.PRIVATE,
+       mayInstantiate=false,
+       mayExtend=false,
+       mayInvoke=false)
+  public final void setInvokeForInternalOperations(
+                         boolean invokeForInternalOps)
+  {
+    this.invokeForInternalOps = invokeForInternalOps;
+  }
+
+
+
+  /**
    * Performs any processing that should be done when the Directory
    * Server is in the process of starting.  This method will be called
    * after virtually all other initialization has been performed but
@@ -448,6 +490,25 @@
 
 
   /**
+   * Performs any necessary processing that should be done after the
+   * Directory Server has completed processing for an add operation
+   * performed via synchronization.
+   *
+   * @param  addOperation  The synchronized add operation for which
+   *                       processing has been completed.
+   */
+  public void doPostSynchronization(
+                   PostSynchronizationAddOperation addOperation)
+  {
+    Message message = ERR_PLUGIN_TYPE_NOT_SUPPORTED.
+        get(String.valueOf(pluginDN),
+            PluginType.POST_SYNCHRONIZATION_ADD.getName());
+    throw new UnsupportedOperationException(message.toString());
+  }
+
+
+
+  /**
    * Performs any necessary processing that should be done before the
    * Directory Server parses the elements of a bind request.
    *
@@ -702,6 +763,25 @@
 
 
   /**
+   * Performs any necessary processing that should be done after the
+   * Directory Server has completed processing for a delete operation
+   * performed via synchronization.
+   *
+   * @param  deleteOperation  The synchronized delete operation for
+   *                          which processing has been completed.
+   */
+  public void doPostSynchronization(
+                   PostSynchronizationDeleteOperation deleteOperation)
+  {
+    Message message = ERR_PLUGIN_TYPE_NOT_SUPPORTED.
+        get(String.valueOf(pluginDN),
+            PluginType.POST_SYNCHRONIZATION_DELETE.getName());
+    throw new UnsupportedOperationException(message.toString());
+  }
+
+
+
+  /**
    * Performs any necessary processing that should be done before the
    * Directory Server parses the elements of an extended request.
    *
@@ -875,6 +955,25 @@
 
 
   /**
+   * Performs any necessary processing that should be done after the
+   * Directory Server has completed processing for a modify operation
+   * performed via synchronization.
+   *
+   * @param  modifyOperation  The synchronized modify operation for
+   *                          which processing has been completed.
+   */
+  public void doPostSynchronization(
+                   PostSynchronizationModifyOperation modifyOperation)
+  {
+    Message message = ERR_PLUGIN_TYPE_NOT_SUPPORTED.
+        get(String.valueOf(pluginDN),
+            PluginType.POST_SYNCHRONIZATION_MODIFY.getName());
+    throw new UnsupportedOperationException(message.toString());
+  }
+
+
+
+  /**
    * Performs any necessary processing that should be done before the
    * Directory Server parses the elements of a modify DN request.
    *
@@ -1003,6 +1102,26 @@
 
 
   /**
+   * Performs any necessary processing that should be done after the
+   * Directory Server has completed processing for a modify DN
+   * operation performed via synchronization.
+   *
+   * @param  modifyDNOperation  The synchronized modify DN operation
+   *                            for which processing has been
+   *                            completed.
+   */
+  public void doPostSynchronization(
+              PostSynchronizationModifyDNOperation modifyDNOperation)
+  {
+    Message message = ERR_PLUGIN_TYPE_NOT_SUPPORTED.
+        get(String.valueOf(pluginDN),
+            PluginType.POST_SYNCHRONIZATION_MODIFY_DN.getName());
+    throw new UnsupportedOperationException(message.toString());
+  }
+
+
+
+  /**
    * Performs any necessary processing that should be done before the
    * Directory Server parses the elements of a search request.
    *
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 79388df..07b67a6 100644
--- a/opends/src/server/org/opends/server/api/plugin/PluginType.java
+++ b/opends/src/server/org/opends/server/api/plugin/PluginType.java
@@ -383,6 +383,41 @@
 
 
   /**
+   * The plugin type for plugins that are to be invoked just after an
+   * add operation has been completed via synchronization.
+   */
+  POST_SYNCHRONIZATION_ADD(PluginType.NAME_POST_SYNCHRONIZATION_ADD),
+
+
+
+  /**
+   * The plugin type for plugins that are to be invoked just after a
+   * delete operation has been completed via synchronization.
+   */
+  POST_SYNCHRONIZATION_DELETE(
+       PluginType.NAME_POST_SYNCHRONIZATION_DELETE),
+
+
+
+  /**
+   * The plugin type for plugins that are to be invoked just after a
+   * modify operation has been completed via synchronization.
+   */
+  POST_SYNCHRONIZATION_MODIFY(
+       PluginType.NAME_POST_SYNCHRONIZATION_MODIFY),
+
+
+
+  /**
+   * The plugin type for plugins that are to be invoked just after a
+   * modify DN operation has been completed via synchronization.
+   */
+  POST_SYNCHRONIZATION_MODIFY_DN(
+       PluginType.NAME_POST_SYNCHRONIZATION_MODIFY_DN),
+
+
+
+  /**
    * The plugin type for plugins that are to be invoked before each
    * search result entry is sent to a client.
    */
@@ -744,6 +779,41 @@
 
 
   /**
+   * The name that will be used for post-synchronization add plugins.
+   */
+  private static final String NAME_POST_SYNCHRONIZATION_ADD =
+       "postsynchronizationadd";
+
+
+
+  /**
+   * The name that will be used for post-synchronization delete
+   * plugins.
+   */
+  private static final String NAME_POST_SYNCHRONIZATION_DELETE =
+       "postsynchronizationdelete";
+
+
+
+  /**
+   * The name that will be used for post-synchronization modify
+   * plugins.
+   */
+  private static final String NAME_POST_SYNCHRONIZATION_MODIFY =
+       "postsynchronizationmodify";
+
+
+
+  /**
+   * The name that will be used for post-synchronization modify DN
+   * plugins.
+   */
+  private static final String NAME_POST_SYNCHRONIZATION_MODIFY_DN =
+       "postsynchronizationmodifydn";
+
+
+
+  /**
    * The name that will be used for search result entry plugins.
    */
   private static final String NAME_SEARCH_ENTRY = "searchresultentry";
@@ -779,7 +849,7 @@
    * types.
    */
   private static final Set<String> PLUGIN_TYPE_NAMES =
-       new HashSet<String>(46);
+       new HashSet<String>(50);
 
 
 
@@ -788,7 +858,7 @@
    * corresponding plugin type.
    */
   private static final Map<String,PluginType> PLUGIN_TYPE_MAP =
-       new HashMap<String,PluginType>(46);
+       new HashMap<String,PluginType>(50);
 
 
 
@@ -836,6 +906,13 @@
     PLUGIN_TYPE_NAMES.add(PluginType.NAME_POST_RESPONSE_MODIFY);
     PLUGIN_TYPE_NAMES.add(PluginType.NAME_POST_RESPONSE_MODIFY_DN);
     PLUGIN_TYPE_NAMES.add(PluginType.NAME_POST_RESPONSE_SEARCH);
+    PLUGIN_TYPE_NAMES.add(PluginType.NAME_POST_SYNCHRONIZATION_ADD);
+    PLUGIN_TYPE_NAMES.add(
+         PluginType.NAME_POST_SYNCHRONIZATION_DELETE);
+    PLUGIN_TYPE_NAMES.add(
+         PluginType.NAME_POST_SYNCHRONIZATION_MODIFY);
+    PLUGIN_TYPE_NAMES.add(
+         PluginType.NAME_POST_SYNCHRONIZATION_MODIFY_DN);
     PLUGIN_TYPE_NAMES.add(PluginType.NAME_SEARCH_ENTRY);
     PLUGIN_TYPE_NAMES.add(PluginType.NAME_SEARCH_REFERENCE);
     PLUGIN_TYPE_NAMES.add(PluginType.NAME_SUBORDINATE_MODIFY_DN);
@@ -924,6 +1001,15 @@
                         PluginType.POST_RESPONSE_MODIFY_DN);
     PLUGIN_TYPE_MAP.put(PluginType.NAME_POST_RESPONSE_SEARCH,
                         PluginType.POST_RESPONSE_SEARCH);
+    PLUGIN_TYPE_MAP.put(PluginType.NAME_POST_SYNCHRONIZATION_ADD,
+                        PluginType.POST_SYNCHRONIZATION_ADD);
+    PLUGIN_TYPE_MAP.put(PluginType.NAME_POST_SYNCHRONIZATION_DELETE,
+                        PluginType.POST_SYNCHRONIZATION_DELETE);
+    PLUGIN_TYPE_MAP.put(PluginType.NAME_POST_SYNCHRONIZATION_MODIFY,
+                        PluginType.POST_SYNCHRONIZATION_MODIFY);
+    PLUGIN_TYPE_MAP.put(
+         PluginType.NAME_POST_SYNCHRONIZATION_MODIFY_DN,
+         PluginType.POST_SYNCHRONIZATION_MODIFY_DN);
     PLUGIN_TYPE_MAP.put(PluginType.NAME_SEARCH_ENTRY,
                         PluginType.SEARCH_RESULT_ENTRY);
     PLUGIN_TYPE_MAP.put(PluginType.NAME_SEARCH_REFERENCE,
diff --git a/opends/src/server/org/opends/server/core/PluginConfigManager.java b/opends/src/server/org/opends/server/core/PluginConfigManager.java
index 1552f9f..e65bf24 100644
--- a/opends/src/server/org/opends/server/core/PluginConfigManager.java
+++ b/opends/src/server/org/opends/server/core/PluginConfigManager.java
@@ -25,7 +25,6 @@
  *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.core;
-import org.opends.messages.Message;
 
 
 
@@ -41,6 +40,8 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.ClassPropertyDefinition;
 import org.opends.server.admin.server.ConfigurationAddListener;
 import org.opends.server.admin.server.ConfigurationChangeListener;
@@ -66,12 +67,11 @@
 import org.opends.server.api.plugin.StartupPluginResult;
 import org.opends.server.api.plugin.SubordinateModifyDNPluginResult;
 import org.opends.server.config.ConfigException;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
-
-
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.IntermediateResponse;
 import org.opends.server.types.LDIFExportConfig;
@@ -80,7 +80,6 @@
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
-
 import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.Modification;
 import org.opends.server.types.operation.PostOperationAbandonOperation;
@@ -101,6 +100,10 @@
 import org.opends.server.types.operation.PostResponseModifyDNOperation;
 import org.opends.server.types.operation.PostResponseModifyOperation;
 import org.opends.server.types.operation.PostResponseSearchOperation;
+import org.opends.server.types.operation.PostSynchronizationAddOperation;
+import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
+import org.opends.server.types.operation.PostSynchronizationModifyOperation;
+import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
 import org.opends.server.types.operation.PreOperationAddOperation;
 import org.opends.server.types.operation.PreOperationBindOperation;
 import org.opends.server.types.operation.PreOperationCompareOperation;
@@ -124,13 +127,10 @@
 import org.opends.server.types.operation.SubordinateModifyDNOperation;
 import org.opends.server.workflowelement.localbackend.*;
 
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import static org.opends.server.loggers.ErrorLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.messages.PluginMessages.*;
-
-import org.opends.messages.MessageBuilder;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.util.StaticUtils.*;
 
 
@@ -195,6 +195,10 @@
   private DirectoryServerPlugin[] postResponseModifyPlugins;
   private DirectoryServerPlugin[] postResponseModifyDNPlugins;
   private DirectoryServerPlugin[] postResponseSearchPlugins;
+  private DirectoryServerPlugin[] postSynchronizationAddPlugins;
+  private DirectoryServerPlugin[] postSynchronizationDeletePlugins;
+  private DirectoryServerPlugin[] postSynchronizationModifyPlugins;
+  private DirectoryServerPlugin[] postSynchronizationModifyDNPlugins;
   private DirectoryServerPlugin[] searchResultEntryPlugins;
   private DirectoryServerPlugin[] searchResultReferencePlugins;
   private DirectoryServerPlugin[] subordinateModifyDNPlugins;
@@ -223,53 +227,57 @@
   {
     pluginLock = new ReentrantLock();
 
-    startupPlugins               = new DirectoryServerPlugin[0];
-    shutdownPlugins              = new DirectoryServerPlugin[0];
-    postConnectPlugins           = new DirectoryServerPlugin[0];
-    postDisconnectPlugins        = new DirectoryServerPlugin[0];
-    ldifImportPlugins            = new DirectoryServerPlugin[0];
-    ldifExportPlugins            = new DirectoryServerPlugin[0];
-    preParseAbandonPlugins       = new DirectoryServerPlugin[0];
-    preParseAddPlugins           = new DirectoryServerPlugin[0];
-    preParseBindPlugins          = new DirectoryServerPlugin[0];
-    preParseComparePlugins       = new DirectoryServerPlugin[0];
-    preParseDeletePlugins        = new DirectoryServerPlugin[0];
-    preParseExtendedPlugins      = new DirectoryServerPlugin[0];
-    preParseModifyPlugins        = new DirectoryServerPlugin[0];
-    preParseModifyDNPlugins      = new DirectoryServerPlugin[0];
-    preParseSearchPlugins        = new DirectoryServerPlugin[0];
-    preParseUnbindPlugins        = new DirectoryServerPlugin[0];
-    preOperationAddPlugins       = new DirectoryServerPlugin[0];
-    preOperationBindPlugins      = new DirectoryServerPlugin[0];
-    preOperationComparePlugins   = new DirectoryServerPlugin[0];
-    preOperationDeletePlugins    = new DirectoryServerPlugin[0];
-    preOperationExtendedPlugins  = new DirectoryServerPlugin[0];
-    preOperationModifyPlugins    = new DirectoryServerPlugin[0];
-    preOperationModifyDNPlugins  = new DirectoryServerPlugin[0];
-    preOperationSearchPlugins    = new DirectoryServerPlugin[0];
-    postOperationAbandonPlugins  = new DirectoryServerPlugin[0];
-    postOperationAddPlugins      = new DirectoryServerPlugin[0];
-    postOperationBindPlugins     = new DirectoryServerPlugin[0];
-    postOperationComparePlugins  = new DirectoryServerPlugin[0];
-    postOperationDeletePlugins   = new DirectoryServerPlugin[0];
-    postOperationExtendedPlugins = new DirectoryServerPlugin[0];
-    postOperationModifyPlugins   = new DirectoryServerPlugin[0];
-    postOperationModifyDNPlugins = new DirectoryServerPlugin[0];
-    postOperationSearchPlugins   = new DirectoryServerPlugin[0];
-    postOperationUnbindPlugins   = new DirectoryServerPlugin[0];
-    postResponseAddPlugins       = new DirectoryServerPlugin[0];
-    postResponseBindPlugins      = new DirectoryServerPlugin[0];
-    postResponseComparePlugins   = new DirectoryServerPlugin[0];
-    postResponseDeletePlugins    = new DirectoryServerPlugin[0];
-    postResponseExtendedPlugins  = new DirectoryServerPlugin[0];
-    postResponseModifyPlugins    = new DirectoryServerPlugin[0];
-    postResponseModifyDNPlugins  = new DirectoryServerPlugin[0];
-    postResponseSearchPlugins    = new DirectoryServerPlugin[0];
-    searchResultEntryPlugins     = new DirectoryServerPlugin[0];
-    searchResultReferencePlugins = new DirectoryServerPlugin[0];
-    subordinateModifyDNPlugins   = new DirectoryServerPlugin[0];
-    intermediateResponsePlugins  = new DirectoryServerPlugin[0];
-    registeredPlugins            =
+    startupPlugins                     = new DirectoryServerPlugin[0];
+    shutdownPlugins                    = new DirectoryServerPlugin[0];
+    postConnectPlugins                 = new DirectoryServerPlugin[0];
+    postDisconnectPlugins              = new DirectoryServerPlugin[0];
+    ldifImportPlugins                  = new DirectoryServerPlugin[0];
+    ldifExportPlugins                  = new DirectoryServerPlugin[0];
+    preParseAbandonPlugins             = new DirectoryServerPlugin[0];
+    preParseAddPlugins                 = new DirectoryServerPlugin[0];
+    preParseBindPlugins                = new DirectoryServerPlugin[0];
+    preParseComparePlugins             = new DirectoryServerPlugin[0];
+    preParseDeletePlugins              = new DirectoryServerPlugin[0];
+    preParseExtendedPlugins            = new DirectoryServerPlugin[0];
+    preParseModifyPlugins              = new DirectoryServerPlugin[0];
+    preParseModifyDNPlugins            = new DirectoryServerPlugin[0];
+    preParseSearchPlugins              = new DirectoryServerPlugin[0];
+    preParseUnbindPlugins              = new DirectoryServerPlugin[0];
+    preOperationAddPlugins             = new DirectoryServerPlugin[0];
+    preOperationBindPlugins            = new DirectoryServerPlugin[0];
+    preOperationComparePlugins         = new DirectoryServerPlugin[0];
+    preOperationDeletePlugins          = new DirectoryServerPlugin[0];
+    preOperationExtendedPlugins        = new DirectoryServerPlugin[0];
+    preOperationModifyPlugins          = new DirectoryServerPlugin[0];
+    preOperationModifyDNPlugins        = new DirectoryServerPlugin[0];
+    preOperationSearchPlugins          = new DirectoryServerPlugin[0];
+    postOperationAbandonPlugins        = new DirectoryServerPlugin[0];
+    postOperationAddPlugins            = new DirectoryServerPlugin[0];
+    postOperationBindPlugins           = new DirectoryServerPlugin[0];
+    postOperationComparePlugins        = new DirectoryServerPlugin[0];
+    postOperationDeletePlugins         = new DirectoryServerPlugin[0];
+    postOperationExtendedPlugins       = new DirectoryServerPlugin[0];
+    postOperationModifyPlugins         = new DirectoryServerPlugin[0];
+    postOperationModifyDNPlugins       = new DirectoryServerPlugin[0];
+    postOperationSearchPlugins         = new DirectoryServerPlugin[0];
+    postOperationUnbindPlugins         = new DirectoryServerPlugin[0];
+    postResponseAddPlugins             = new DirectoryServerPlugin[0];
+    postResponseBindPlugins            = new DirectoryServerPlugin[0];
+    postResponseComparePlugins         = new DirectoryServerPlugin[0];
+    postResponseDeletePlugins          = new DirectoryServerPlugin[0];
+    postResponseExtendedPlugins        = new DirectoryServerPlugin[0];
+    postResponseModifyPlugins          = new DirectoryServerPlugin[0];
+    postResponseModifyDNPlugins        = new DirectoryServerPlugin[0];
+    postResponseSearchPlugins          = new DirectoryServerPlugin[0];
+    postSynchronizationAddPlugins      = new DirectoryServerPlugin[0];
+    postSynchronizationDeletePlugins   = new DirectoryServerPlugin[0];
+    postSynchronizationModifyPlugins   = new DirectoryServerPlugin[0];
+    postSynchronizationModifyDNPlugins = new DirectoryServerPlugin[0];
+    searchResultEntryPlugins           = new DirectoryServerPlugin[0];
+    searchResultReferencePlugins       = new DirectoryServerPlugin[0];
+    subordinateModifyDNPlugins         = new DirectoryServerPlugin[0];
+    intermediateResponsePlugins        = new DirectoryServerPlugin[0];
+    registeredPlugins                  =
          new ConcurrentHashMap<DN,
                   DirectoryServerPlugin<? extends PluginCfg>>();
   }
@@ -404,8 +412,8 @@
       if (initialize)
       {
         Method method = plugin.getClass().getMethod("initializeInternal",
-                                                    DN.class, Set.class);
-        method.invoke(plugin, configuration.dn(), pluginTypes);
+                                                    PluginCfg.class, Set.class);
+        method.invoke(plugin, configuration, pluginTypes);
 
         method = plugin.getClass().getMethod("initializePlugin", Set.class,
                       configuration.definition().getServerConfigurationClass());
@@ -511,6 +519,14 @@
       case SEARCHRESULTREFERENCE:  return PluginType.SEARCH_RESULT_REFERENCE;
       case SUBORDINATEMODIFYDN:    return PluginType.SUBORDINATE_MODIFY_DN;
       case INTERMEDIATERESPONSE:   return PluginType.INTERMEDIATE_RESPONSE;
+      case POSTSYNCHRONIZATIONADD:
+                return PluginType.POST_SYNCHRONIZATION_ADD;
+      case POSTSYNCHRONIZATIONDELETE:
+                return PluginType.POST_SYNCHRONIZATION_DELETE;
+      case POSTSYNCHRONIZATIONMODIFY:
+                return PluginType.POST_SYNCHRONIZATION_MODIFY;
+      case POSTSYNCHRONIZATIONMODIFYDN:
+                return PluginType.POST_SYNCHRONIZATION_MODIFY_DN;
       default:                     return null;
     }
   }
@@ -819,6 +835,30 @@
                  addPlugin(postResponseSearchPlugins, plugin, t,
                            pluginRootConfig.getPluginOrderPostResponseSearch());
             break;
+          case POST_SYNCHRONIZATION_ADD:
+            postSynchronizationAddPlugins =
+                 addPlugin(postSynchronizationAddPlugins, plugin, t,
+                           pluginRootConfig.
+                                getPluginOrderPostSynchronizationAdd());
+            break;
+          case POST_SYNCHRONIZATION_DELETE:
+            postSynchronizationDeletePlugins =
+                 addPlugin(postSynchronizationDeletePlugins, plugin, t,
+                           pluginRootConfig.
+                                getPluginOrderPostSynchronizationDelete());
+            break;
+          case POST_SYNCHRONIZATION_MODIFY:
+            postSynchronizationModifyPlugins =
+                 addPlugin(postSynchronizationModifyPlugins, plugin, t,
+                           pluginRootConfig.
+                                getPluginOrderPostSynchronizationModify());
+            break;
+          case POST_SYNCHRONIZATION_MODIFY_DN:
+            postSynchronizationModifyDNPlugins =
+                 addPlugin(postSynchronizationModifyDNPlugins, plugin, t,
+                           pluginRootConfig.
+                                getPluginOrderPostSynchronizationModifyDN());
+            break;
           case SEARCH_RESULT_ENTRY:
             searchResultEntryPlugins =
                  addPlugin(searchResultEntryPlugins, plugin, t,
@@ -1244,6 +1284,22 @@
             postResponseSearchPlugins = removePlugin(postResponseSearchPlugins,
                                                      plugin);
             break;
+          case POST_SYNCHRONIZATION_ADD:
+            postSynchronizationAddPlugins =
+                 removePlugin(postSynchronizationAddPlugins, plugin);
+            break;
+          case POST_SYNCHRONIZATION_DELETE:
+            postSynchronizationDeletePlugins =
+                 removePlugin(postSynchronizationDeletePlugins, plugin);
+            break;
+          case POST_SYNCHRONIZATION_MODIFY:
+            postSynchronizationModifyPlugins =
+                 removePlugin(postSynchronizationModifyPlugins, plugin);
+            break;
+          case POST_SYNCHRONIZATION_MODIFY_DN:
+            postSynchronizationModifyDNPlugins =
+                 removePlugin(postSynchronizationModifyDNPlugins, plugin);
+            break;
           case SEARCH_RESULT_ENTRY:
             searchResultEntryPlugins = removePlugin(searchResultEntryPlugins,
                                                     plugin);
@@ -1740,6 +1796,12 @@
 
     for (DirectoryServerPlugin p : preParseAbandonPlugins)
     {
+      if (abandonOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(abandonOperation);
@@ -1816,6 +1878,12 @@
 
     for (DirectoryServerPlugin p : preParseAddPlugins)
     {
+      if (addOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(addOperation);
@@ -1889,6 +1957,12 @@
 
     for (DirectoryServerPlugin p : preParseBindPlugins)
     {
+      if (bindOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(bindOperation);
@@ -1962,6 +2036,12 @@
 
     for (DirectoryServerPlugin p : preParseComparePlugins)
     {
+      if (compareOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(compareOperation);
@@ -2038,6 +2118,12 @@
 
     for (DirectoryServerPlugin p : preParseDeletePlugins)
     {
+      if (deleteOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(deleteOperation);
@@ -2114,6 +2200,12 @@
 
     for (DirectoryServerPlugin p : preParseExtendedPlugins)
     {
+      if (extendedOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(extendedOperation);
@@ -2178,21 +2270,27 @@
    * Invokes the set of pre-parse modify plugins that have been configured in
    * the Directory Server.
    *
-   * @param  operation  The modify operation for which to invoke the
+   * @param  modifyOperation  The modify operation for which to invoke the
    *                          pre-parse plugins.
    *
    * @return  The result of processing the pre-parse modify plugins.
    */
   public PreParsePluginResult invokePreParseModifyPlugins(
-                                   PreParseModifyOperation operation)
+                                   PreParseModifyOperation modifyOperation)
   {
     PreParsePluginResult result = null;
 
     for (DirectoryServerPlugin p : preParseModifyPlugins)
     {
+      if (modifyOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
-        result = p.doPreParse(operation);
+        result = p.doPreParse(modifyOperation);
       }
       catch (Exception e)
       {
@@ -2202,15 +2300,16 @@
         }
 
         Message message = ERR_PLUGIN_PRE_PARSE_PLUGIN_EXCEPTION.
-            get(operation.getOperationType().getOperationName(),
+            get(modifyOperation.getOperationType().getOperationName(),
                 String.valueOf(p.getPluginEntryDN()),
-                operation.getConnectionID(), operation.getOperationID(),
+                modifyOperation.getConnectionID(),
+                modifyOperation.getOperationID(),
                 stackTraceToSingleLineString(e));
         logError(message);
 
-        operation.setResultCode(
+        modifyOperation.setResultCode(
                              DirectoryServer.getServerErrorResultCode());
-        operation.appendErrorMessage(message);
+        modifyOperation.appendErrorMessage(message);
 
         return new PreParsePluginResult(false, false, true);
       }
@@ -2218,15 +2317,15 @@
       if (result == null)
       {
         Message message = ERR_PLUGIN_PRE_PARSE_PLUGIN_RETURNED_NULL.
-            get(operation.getOperationType().getOperationName(),
+            get(modifyOperation.getOperationType().getOperationName(),
                 String.valueOf(p.getPluginEntryDN()),
-                operation.getConnectionID(),
-                String.valueOf(operation.getOperationID()));
+                modifyOperation.getConnectionID(),
+                String.valueOf(modifyOperation.getOperationID()));
         logError(message);
 
-        operation.setResultCode(
+        modifyOperation.setResultCode(
                              DirectoryServer.getServerErrorResultCode());
-        operation.appendErrorMessage(message);
+        modifyOperation.appendErrorMessage(message);
 
         return new PreParsePluginResult(false, false, true);
       }
@@ -2265,6 +2364,12 @@
 
     for (DirectoryServerPlugin p : preParseModifyDNPlugins)
     {
+      if (modifyDNOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(modifyDNOperation);
@@ -2341,6 +2446,12 @@
 
     for (DirectoryServerPlugin p : preParseSearchPlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(searchOperation);
@@ -2417,6 +2528,12 @@
 
     for (DirectoryServerPlugin p : preParseUnbindPlugins)
     {
+      if (unbindOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreParse(unbindOperation);
@@ -2493,6 +2610,12 @@
 
     for (DirectoryServerPlugin p : preOperationAddPlugins)
     {
+      if (addOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(addOperation);
@@ -2565,6 +2688,12 @@
 
     for (DirectoryServerPlugin p : preOperationBindPlugins)
     {
+      if (bindOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(bindOperation);
@@ -2638,6 +2767,12 @@
 
     for (DirectoryServerPlugin p : preOperationComparePlugins)
     {
+      if (compareOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(compareOperation);
@@ -2714,6 +2849,12 @@
 
     for (DirectoryServerPlugin p : preOperationDeletePlugins)
     {
+      if (deleteOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(deleteOperation);
@@ -2790,6 +2931,12 @@
 
     for (DirectoryServerPlugin p : preOperationExtendedPlugins)
     {
+      if (extendedOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(extendedOperation);
@@ -2866,6 +3013,12 @@
 
     for (DirectoryServerPlugin p : preOperationModifyPlugins)
     {
+      if (modifyOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(modifyOperation);
@@ -2942,6 +3095,12 @@
 
     for (DirectoryServerPlugin p : preOperationModifyDNPlugins)
     {
+      if (modifyDNOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(modifyDNOperation);
@@ -3018,6 +3177,12 @@
 
     for (DirectoryServerPlugin p : preOperationSearchPlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPreOperation(searchOperation);
@@ -3094,6 +3259,12 @@
 
     for (DirectoryServerPlugin p : postOperationAbandonPlugins)
     {
+      if (abandonOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(abandonOperation);
@@ -3170,6 +3341,12 @@
 
     for (DirectoryServerPlugin p : postOperationAddPlugins)
     {
+      if (addOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(addOperation);
@@ -3242,6 +3419,12 @@
 
     for (DirectoryServerPlugin p : postOperationBindPlugins)
     {
+      if (bindOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(bindOperation);
@@ -3315,6 +3498,12 @@
 
     for (DirectoryServerPlugin p : postOperationComparePlugins)
     {
+      if (compareOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(compareOperation);
@@ -3391,6 +3580,12 @@
 
     for (DirectoryServerPlugin p : postOperationDeletePlugins)
     {
+      if (deleteOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(deleteOperation);
@@ -3467,6 +3662,12 @@
 
     for (DirectoryServerPlugin p : postOperationExtendedPlugins)
     {
+      if (extendedOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(extendedOperation);
@@ -3543,6 +3744,12 @@
 
     for (DirectoryServerPlugin p : postOperationModifyPlugins)
     {
+      if (modifyOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(modifyOperation);
@@ -3619,6 +3826,12 @@
 
     for (DirectoryServerPlugin p : postOperationModifyDNPlugins)
     {
+      if (modifyDNOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(modifyDNOperation);
@@ -3695,6 +3908,12 @@
 
     for (DirectoryServerPlugin p : postOperationSearchPlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(searchOperation);
@@ -3771,6 +3990,12 @@
 
     for (DirectoryServerPlugin p : postOperationUnbindPlugins)
     {
+      if (unbindOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostOperation(unbindOperation);
@@ -3847,6 +4072,12 @@
 
     for (DirectoryServerPlugin p : postResponseAddPlugins)
     {
+      if (addOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(addOperation);
@@ -3913,6 +4144,12 @@
 
     for (DirectoryServerPlugin p : postResponseBindPlugins)
     {
+      if (bindOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(bindOperation);
@@ -3980,6 +4217,12 @@
 
     for (DirectoryServerPlugin p : postResponseComparePlugins)
     {
+      if (compareOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(compareOperation);
@@ -4048,6 +4291,12 @@
 
     for (DirectoryServerPlugin p : postResponseDeletePlugins)
     {
+      if (deleteOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(deleteOperation);
@@ -4116,6 +4365,12 @@
 
     for (DirectoryServerPlugin p : postResponseExtendedPlugins)
     {
+      if (extendedOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(extendedOperation);
@@ -4184,6 +4439,12 @@
 
     for (DirectoryServerPlugin p : postResponseModifyPlugins)
     {
+      if (modifyOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(modifyOperation);
@@ -4252,6 +4513,12 @@
 
     for (DirectoryServerPlugin p : postResponseModifyDNPlugins)
     {
+      if (modifyDNOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(modifyDNOperation);
@@ -4320,6 +4587,12 @@
 
     for (DirectoryServerPlugin p : postResponseSearchPlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.doPostResponse(searchOperation);
@@ -4370,6 +4643,151 @@
     return result;
   }
 
+
+
+  /**
+   * Invokes the set of post-synchronization add plugins that have been
+   * configured in the Directory Server.
+   *
+   * @param  addOperation  The add operation for which to invoke the
+   *                       post-synchronization plugins.
+   */
+  public void invokePostSynchronizationAddPlugins(
+                   PostSynchronizationAddOperation addOperation)
+  {
+    for (DirectoryServerPlugin p : postSynchronizationAddPlugins)
+    {
+      try
+      {
+        p.doPostSynchronization(addOperation);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        Message message = ERR_PLUGIN_POST_SYNCHRONIZATION_PLUGIN_EXCEPTION.
+            get(addOperation.getOperationType().getOperationName(),
+                String.valueOf(p.getPluginEntryDN()),
+                addOperation.getConnectionID(), addOperation.getOperationID(),
+                stackTraceToSingleLineString(e));
+        logError(message);
+      }
+    }
+  }
+
+
+
+  /**
+   * Invokes the set of post-synchronization delete plugins that have been
+   * configured in the Directory Server.
+   *
+   * @param  deleteOperation  The delete operation for which to invoke the
+   *                          post-synchronization plugins.
+   */
+  public void invokePostSynchronizationDeletePlugins(
+                   PostSynchronizationDeleteOperation deleteOperation)
+  {
+    for (DirectoryServerPlugin p : postSynchronizationDeletePlugins)
+    {
+      try
+      {
+        p.doPostSynchronization(deleteOperation);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        Message message = ERR_PLUGIN_POST_SYNCHRONIZATION_PLUGIN_EXCEPTION.
+            get(deleteOperation.getOperationType().getOperationName(),
+                String.valueOf(p.getPluginEntryDN()),
+                deleteOperation.getConnectionID(),
+                deleteOperation.getOperationID(),
+                stackTraceToSingleLineString(e));
+        logError(message);
+      }
+    }
+  }
+
+
+
+  /**
+   * Invokes the set of post-synchronization modify plugins that have been
+   * configured in the Directory Server.
+   *
+   * @param  modifyOperation  The modify operation for which to invoke the
+   *                          post-synchronization plugins.
+   */
+  public void invokePostSynchronizationModifyPlugins(
+                   PostSynchronizationModifyOperation modifyOperation)
+  {
+    for (DirectoryServerPlugin p : postSynchronizationModifyPlugins)
+    {
+      try
+      {
+        p.doPostSynchronization(modifyOperation);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        Message message = ERR_PLUGIN_POST_SYNCHRONIZATION_PLUGIN_EXCEPTION.
+            get(modifyOperation.getOperationType().getOperationName(),
+                String.valueOf(p.getPluginEntryDN()),
+                modifyOperation.getConnectionID(),
+                modifyOperation.getOperationID(),
+                stackTraceToSingleLineString(e));
+        logError(message);
+      }
+    }
+  }
+
+
+
+  /**
+   * Invokes the set of post-synchronization modify DN plugins that have been
+   * configured in the Directory Server.
+   *
+   * @param  modifyDNOperation  The modify DN operation for which to invoke the
+   *                            post-synchronization plugins.
+   */
+  public void invokePostSynchronizationModifyDNPlugins(
+                   PostSynchronizationModifyDNOperation modifyDNOperation)
+  {
+    for (DirectoryServerPlugin p : postSynchronizationModifyDNPlugins)
+    {
+      try
+      {
+        p.doPostSynchronization(modifyDNOperation);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        Message message = ERR_PLUGIN_POST_SYNCHRONIZATION_PLUGIN_EXCEPTION.
+            get(modifyDNOperation.getOperationType().getOperationName(),
+                String.valueOf(p.getPluginEntryDN()),
+                modifyDNOperation.getConnectionID(),
+                modifyDNOperation.getOperationID(),
+                stackTraceToSingleLineString(e));
+        logError(message);
+      }
+    }
+  }
+
+
+
   /**
    * Invokes the set of search result entry plugins that have been configured
    * in the Directory Server.
@@ -4388,6 +4806,12 @@
 
     for (DirectoryServerPlugin p : searchResultEntryPlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.processSearchEntry(searchOperation, searchEntry);
@@ -4438,6 +4862,8 @@
     return result;
   }
 
+
+
   /**
    * Invokes the set of search result entry plugins that have been configured
    * in the Directory Server.
@@ -4456,6 +4882,18 @@
 
     for (DirectoryServerPlugin p : searchResultEntryPlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.processSearchEntry(searchOperation, searchEntry);
@@ -4506,6 +4944,8 @@
     return result;
   }
 
+
+
   /**
    * Invokes the set of search result reference plugins that have been
    * configured in the Directory Server.
@@ -4524,6 +4964,12 @@
 
     for (DirectoryServerPlugin p : searchResultReferencePlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.processSearchReference(searchOperation, searchReference);
@@ -4574,6 +5020,8 @@
     return result;
   }
 
+
+
   /**
    * Invokes the set of search result reference plugins that have been
    * configured in the Directory Server.
@@ -4592,6 +5040,12 @@
 
     for (DirectoryServerPlugin p : searchResultReferencePlugins)
     {
+      if (searchOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         result = p.processSearchReference(searchOperation, searchReference);
@@ -4667,6 +5121,12 @@
 
     for (DirectoryServerPlugin p : subordinateModifyDNPlugins)
     {
+      if (modifyDNOperation.isInternalOperation() &&
+          (! p.invokeForInternalOperations()))
+      {
+        continue;
+      }
+
       try
       {
         DirectoryServerPlugin<? extends PluginCfg> gp =
@@ -4972,7 +5432,8 @@
     // then we shouldn't do anything with it although if the class has changed
     // then we'll at least need to indicate that administrative action is
     // required.  If the mapper is disabled, then instantiate the class and
-    // initialize and register it as an identity mapper.
+    // initialize and register it as an identity mapper.  Also, update the
+    // plugin to indicate whether it should be invoked for internal operations.
     String className = configuration.getPluginClass();
     if (existingPlugin != null)
     {
@@ -4981,6 +5442,9 @@
         adminActionRequired = true;
       }
 
+      existingPlugin.setInvokeForInternalOperations(
+                          configuration.isInvokeForInternalOperations());
+
       return new ConfigChangeResult(resultCode, adminActionRequired, messages);
     }
 
diff --git a/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java b/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
index a7a7e16..33b767d 100644
--- a/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
+++ b/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
@@ -465,6 +465,10 @@
       StringBuilder buffer = new StringBuilder(50);
       buffer.append("# ");
       buffer.append(TimeThread.getLocalTime());
+      buffer.append("; conn=");
+      buffer.append(addOperation.getConnectionID());
+      buffer.append("; op=");
+      buffer.append(addOperation.getOperationID());
       buffer.append(EOL);
 
       buffer.append("dn:");
@@ -598,6 +602,10 @@
       StringBuilder buffer = new StringBuilder(50);
       buffer.append("# ");
       buffer.append(TimeThread.getLocalTime());
+      buffer.append("; conn=");
+      buffer.append(deleteOperation.getConnectionID());
+      buffer.append("; op=");
+      buffer.append(deleteOperation.getOperationID());
       buffer.append(EOL);
 
       buffer.append("dn:");
@@ -676,6 +684,10 @@
       StringBuilder buffer = new StringBuilder(50);
       buffer.append("# ");
       buffer.append(TimeThread.getLocalTime());
+      buffer.append("; conn=");
+      buffer.append(modifyOperation.getConnectionID());
+      buffer.append("; op=");
+      buffer.append(modifyOperation.getOperationID());
       buffer.append(EOL);
 
       buffer.append("dn:");
@@ -777,6 +789,10 @@
       StringBuilder buffer = new StringBuilder(50);
       buffer.append("# ");
       buffer.append(TimeThread.getLocalTime());
+      buffer.append("; conn=");
+      buffer.append(modifyDNOperation.getConnectionID());
+      buffer.append("; op=");
+      buffer.append(modifyDNOperation.getOperationID());
       buffer.append(EOL);
 
       buffer.append("dn:");
diff --git a/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java b/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
index 9c9a8d8..279164f 100644
--- a/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
+++ b/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
@@ -26,47 +26,104 @@
  */
 package org.opends.server.plugins;
 
-import org.opends.server.admin.std.server.UniqueAttributePluginCfg;
-import org.opends.server.admin.std.meta.PluginCfgDefn;
+
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.messages.Message;
 import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.meta.PluginCfgDefn;
+import org.opends.server.admin.std.server.PluginCfg;
+import org.opends.server.admin.std.server.UniqueAttributePluginCfg;
+import org.opends.server.api.AlertGenerator;
 import org.opends.server.api.plugin.DirectoryServerPlugin;
 import org.opends.server.api.plugin.PluginType;
 import org.opends.server.api.plugin.PreOperationPluginResult;
 import org.opends.server.config.ConfigException;
-import org.opends.server.types.*;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.RDN;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchScope;
+import org.opends.server.types.operation.PostSynchronizationAddOperation;
+import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
+import org.opends.server.types.operation.PostSynchronizationModifyOperation;
 import org.opends.server.types.operation.PreOperationAddOperation;
 import org.opends.server.types.operation.PreOperationModifyDNOperation;
 import org.opends.server.types.operation.PreOperationModifyOperation;
-import org.opends.server.types.operation.PreOperationOperation;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.protocols.internal.InternalClientConnection;
-import org.opends.server.protocols.internal.InternalSearchOperation;
-import org.opends.messages.Message;
-import static org.opends.messages.PluginMessages.*;
 
-import java.util.*;
+import static org.opends.messages.PluginMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.ServerConstants.*;
+
+
 
 /**
- * This class implements a Directory Server plugin that performs attribute
- * uniqueness checking on the add, modify and modifyDN operations. If the
- * operation is eligible for checking based on a set of configuration criteria,
- * then the operation's attribute values will be checked, using that
- * configuration criteria, for uniqueness against the server's values to
- * determine if the operation can proceed.
+ * This class implements a Directory Server plugin that can be used to ensure
+ * that all values for a given attribute or set of attributes are unique within
+ * the server (or optionally, below a specified set of base DNs).  It will
+ * examine all add, modify, and modify DN operations to determine whether any
+ * new conflicts are introduced.  If a conflict is detected then the operation
+ * will be rejected, unless that operation is being applied through
+ * synchronization in which case an alert will be generated to notify
+ * administrators of the problem.
  */
 public class UniqueAttributePlugin
         extends DirectoryServerPlugin<UniqueAttributePluginCfg>
-        implements ConfigurationChangeListener<UniqueAttributePluginCfg> {
+        implements ConfigurationChangeListener<UniqueAttributePluginCfg>,
+                   AlertGenerator
+{
+  /**
+   * The debug log tracer that will be used for this plugin.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+
+
+  /**
+   * The pre-operation plugin result that should be returned if an operation
+   * would have resulted in a unique attribute conflict.
+   */
+  private static final PreOperationPluginResult FAILED_PREOP_RESULT =
+       new PreOperationPluginResult(false, false, false, true);
+
+
+
+  /**
+   * The set of attributes that will be requested when performing internal
+   * search operations.  This indicates that no attributes should be returned.
+   */
+  private static final LinkedHashSet<String> SEARCH_ATTRS =
+       new LinkedHashSet<String>(1);
+  static
+  {
+    SEARCH_ATTRS.add("1.1");
+  }
+
+
 
   //Current plugin configuration.
   private UniqueAttributePluginCfg currentConfiguration;
 
-  //List of attribute types that must be unique.
-  private LinkedHashSet<AttributeType> uniqueAttributeTypes =
-          new LinkedHashSet<AttributeType>();
 
-//List of base DNs that limit the scope of the uniqueness checking.
- private LinkedHashSet<DN> baseDNs = new LinkedHashSet<DN>();
 
   /**
    * {@inheritDoc}
@@ -74,37 +131,710 @@
   @Override()
   public final void initializePlugin(Set<PluginType> pluginTypes,
                                      UniqueAttributePluginCfg configuration)
-          throws ConfigException {
+          throws ConfigException
+  {
     configuration.addUniqueAttributeChangeListener(this);
     currentConfiguration = configuration;
+    DirectoryServer.registerAlertGenerator(this);
+
     for (PluginType t : pluginTypes)
-      switch (t)  {
+    {
+      switch (t)
+      {
         case PRE_OPERATION_ADD:
         case PRE_OPERATION_MODIFY:
         case PRE_OPERATION_MODIFY_DN:
+        case POST_SYNCHRONIZATION_ADD:
+        case POST_SYNCHRONIZATION_MODIFY:
+        case POST_SYNCHRONIZATION_MODIFY_DN:
           // These are acceptable.
           break;
+
         default:
           Message message =
                   ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(t.toString());
           throw new ConfigException(message);
 
       }
-    //Load base DNs if any.
-    for(DN baseDN : configuration.getUniqueAttributeBaseDN())
-      baseDNs.add(baseDN);
-    //Load attribute types if any.
-    for(AttributeType attributeType : configuration.getUniqueAttributeType())
-      uniqueAttributeTypes.add(attributeType);
+    }
   }
 
 
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void finalizePlugin()
+  {
+    currentConfiguration.removeUniqueAttributeChangeListener(this);
+    DirectoryServer.deregisterAlertGenerator(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final PreOperationPluginResult
+               doPreOperation(PreOperationAddOperation addOperation)
+  {
+    UniqueAttributePluginCfg config = currentConfiguration;
+    Entry entry = addOperation.getEntryToAdd();
+
+    Set<DN> baseDNs = getBaseDNs(config, entry.getDN());
+    if (baseDNs == null)
+    {
+      // The entry is outside the scope of this plugin.
+      return PreOperationPluginResult.SUCCESS;
+    }
+
+    for (AttributeType t : config.getUniqueAttributeType())
+    {
+      List<Attribute> attrList = entry.getAttribute(t);
+      if (attrList != null)
+      {
+        for (Attribute a : attrList)
+        {
+          for (AttributeValue v : a.getValues())
+          {
+            try
+            {
+              DN conflictDN = getConflictingEntryDN(baseDNs, entry.getDN(),
+                                                    config, v);
+              if (conflictDN != null)
+              {
+                addOperation.appendErrorMessage(
+                     ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(),
+                          v.getStringValue(), conflictDN.toString()));
+                addOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return FAILED_PREOP_RESULT;
+              }
+            }
+            catch (DirectoryException de)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, de);
+              }
+
+              Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
+                               de.getResultCode().toString(),
+                               de.getMessageObject());
+
+              addOperation.setResultCode(
+                   DirectoryServer.getServerErrorResultCode());
+              addOperation.appendErrorMessage(m);
+              return FAILED_PREOP_RESULT;
+            }
+          }
+        }
+      }
+    }
+
+    return PreOperationPluginResult.SUCCESS;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final PreOperationPluginResult
+               doPreOperation(PreOperationModifyOperation modifyOperation)
+  {
+    UniqueAttributePluginCfg config = currentConfiguration;
+    DN entryDN = modifyOperation.getEntryDN();
+
+    Set<DN> baseDNs = getBaseDNs(config, entryDN);
+    if (baseDNs == null)
+    {
+      // The entry is outside the scope of this plugin.
+      return PreOperationPluginResult.SUCCESS;
+    }
+
+    for (Modification m : modifyOperation.getModifications())
+    {
+      Attribute a = m.getAttribute();
+      AttributeType t = a.getAttributeType();
+      if (! config.getUniqueAttributeType().contains(t))
+      {
+        // This modification isn't for a unique attribute.
+        continue;
+      }
+
+      switch (m.getModificationType())
+      {
+        case ADD:
+        case REPLACE:
+          for (AttributeValue v : a.getValues())
+          {
+            try
+            {
+              DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, config,
+                                                    v);
+              if (conflictDN != null)
+              {
+                modifyOperation.appendErrorMessage(
+                     ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(),
+                          v.getStringValue(), conflictDN.toString()));
+                modifyOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return FAILED_PREOP_RESULT;
+              }
+            }
+            catch (DirectoryException de)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, de);
+              }
+
+              Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
+                                     de.getResultCode().toString(),
+                                     de.getMessageObject());
+
+              modifyOperation.setResultCode(
+                   DirectoryServer.getServerErrorResultCode());
+              modifyOperation.appendErrorMessage(message);
+              return FAILED_PREOP_RESULT;
+            }
+          }
+          break;
+
+        case INCREMENT:
+          // We could calculate the new value, but we'll just take it from the
+          // updated entry.
+          List<Attribute> attrList =
+               modifyOperation.getModifiedEntry().getAttribute(t,
+                                                               a.getOptions());
+          if (attrList != null)
+          {
+            for (Attribute updatedAttr : attrList)
+            {
+              if (! updatedAttr.optionsEqual(a.getOptions()))
+              {
+                continue;
+              }
+
+              for (AttributeValue v : updatedAttr.getValues())
+              {
+                try
+                {
+                  DN conflictDN = getConflictingEntryDN(baseDNs, entryDN,
+                                                        config, v);
+                  if (conflictDN != null)
+                  {
+                    modifyOperation.appendErrorMessage(
+                         ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(
+                              t.getNameOrOID(), v.getStringValue(),
+                              conflictDN.toString()));
+                    modifyOperation.setResultCode(
+                         ResultCode.CONSTRAINT_VIOLATION);
+                    return FAILED_PREOP_RESULT;
+                  }
+                }
+                catch (DirectoryException de)
+                {
+                  if (debugEnabled())
+                  {
+                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
+                  }
+
+                  Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
+                                         de.getResultCode().toString(),
+                                         de.getMessageObject());
+
+                  modifyOperation.setResultCode(
+                       DirectoryServer.getServerErrorResultCode());
+                  modifyOperation.appendErrorMessage(message);
+                  return FAILED_PREOP_RESULT;
+                }
+              }
+            }
+          }
+          break;
+
+        default:
+          // We don't need to look at this modification because it's not a
+          // modification type of interest.
+          continue;
+      }
+    }
+
+    return PreOperationPluginResult.SUCCESS;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final PreOperationPluginResult doPreOperation(
+                    PreOperationModifyDNOperation modifyDNOperation)
+  {
+    UniqueAttributePluginCfg config = currentConfiguration;
+
+    Set<DN> baseDNs = getBaseDNs(config,
+                                 modifyDNOperation.getUpdatedEntry().getDN());
+    if (baseDNs == null)
+    {
+      // The entry is outside the scope of this plugin.
+      return PreOperationPluginResult.SUCCESS;
+    }
+
+    RDN newRDN = modifyDNOperation.getNewRDN();
+    for (int i=0; i < newRDN.getNumValues(); i++)
+    {
+      AttributeType t = newRDN.getAttributeType(i);
+      if (! config.getUniqueAttributeType().contains(t))
+      {
+        // We aren't interested in this attribute type.
+        continue;
+      }
+
+      try
+      {
+        AttributeValue v = newRDN.getAttributeValue(i);
+        DN conflictDN = getConflictingEntryDN(baseDNs,
+                             modifyDNOperation.getEntryDN(), config, v);
+        if (conflictDN != null)
+        {
+          modifyDNOperation.appendErrorMessage(
+               ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(),
+                    v.getStringValue(), conflictDN.toString()));
+          modifyDNOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+          return FAILED_PREOP_RESULT;
+        }
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
+                         de.getResultCode().toString(),
+                         de.getMessageObject());
+
+        modifyDNOperation.setResultCode(
+             DirectoryServer.getServerErrorResultCode());
+        modifyDNOperation.appendErrorMessage(m);
+        return FAILED_PREOP_RESULT;
+      }
+    }
+
+    return PreOperationPluginResult.SUCCESS;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void doPostSynchronization(
+                         PostSynchronizationAddOperation addOperation)
+  {
+    UniqueAttributePluginCfg config = currentConfiguration;
+    Entry entry = addOperation.getEntryToAdd();
+
+    Set<DN> baseDNs = getBaseDNs(config, entry.getDN());
+    if (baseDNs == null)
+    {
+      // The entry is outside the scope of this plugin.
+      return;
+    }
+
+    for (AttributeType t : config.getUniqueAttributeType())
+    {
+      List<Attribute> attrList = entry.getAttribute(t);
+      if (attrList != null)
+      {
+        for (Attribute a : attrList)
+        {
+          for (AttributeValue v : a.getValues())
+          {
+            try
+            {
+              DN conflictDN = getConflictingEntryDN(baseDNs, entry.getDN(),
+                                                    config, v);
+              if (conflictDN != null)
+              {
+                Message m = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
+                                 t.getNameOrOID(),
+                                 addOperation.getConnectionID(),
+                                 addOperation.getOperationID(),
+                                 v.getStringValue(),
+                                 entry.getDN().toString(),
+                                 conflictDN.toString());
+                DirectoryServer.sendAlertNotification(this,
+                                     ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, m);
+              }
+            }
+            catch (DirectoryException de)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, de);
+              }
+
+              Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
+                               addOperation.getConnectionID(),
+                               addOperation.getOperationID(),
+                               entry.getDN().toString(),
+                               de.getResultCode().toString(),
+                               de.getMessageObject());
+              DirectoryServer.sendAlertNotification(this,
+                                   ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, m);
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void doPostSynchronization(
+                         PostSynchronizationModifyOperation modifyOperation)
+  {
+    UniqueAttributePluginCfg config = currentConfiguration;
+    DN entryDN = modifyOperation.getEntryDN();
+
+    Set<DN> baseDNs = getBaseDNs(config, entryDN);
+    if (baseDNs == null)
+    {
+      // The entry is outside the scope of this plugin.
+      return;
+    }
+
+    for (Modification m : modifyOperation.getModifications())
+    {
+      Attribute a = m.getAttribute();
+      AttributeType t = a.getAttributeType();
+      if (! config.getUniqueAttributeType().contains(t))
+      {
+        // This modification isn't for a unique attribute.
+        continue;
+      }
+
+      switch (m.getModificationType())
+      {
+        case ADD:
+        case REPLACE:
+          for (AttributeValue v : a.getValues())
+          {
+            try
+            {
+              DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, config,
+                                                    v);
+              if (conflictDN != null)
+              {
+                Message message = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
+                                       t.getNameOrOID(),
+                                       modifyOperation.getConnectionID(),
+                                       modifyOperation.getOperationID(),
+                                       v.getStringValue(),
+                                       entryDN.toString(),
+                                       conflictDN.toString());
+                DirectoryServer.sendAlertNotification(this,
+                                     ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
+                                     message);
+              }
+            }
+            catch (DirectoryException de)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, de);
+              }
+
+              Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
+                                    modifyOperation.getConnectionID(),
+                                    modifyOperation.getOperationID(),
+                                    entryDN.toString(),
+                                    de.getResultCode().toString(),
+                                    de.getMessageObject());
+              DirectoryServer.sendAlertNotification(this,
+                                   ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, message);
+            }
+          }
+          break;
+
+        case INCREMENT:
+          // We could calculate the new value, but we'll just take it from the
+          // updated entry.
+          List<Attribute> attrList =
+               modifyOperation.getModifiedEntry().getAttribute(t,
+                                                               a.getOptions());
+          if (attrList != null)
+          {
+            for (Attribute updatedAttr : attrList)
+            {
+              if (! updatedAttr.optionsEqual(a.getOptions()))
+              {
+                continue;
+              }
+
+              for (AttributeValue v : updatedAttr.getValues())
+              {
+                try
+                {
+                  DN conflictDN = getConflictingEntryDN(baseDNs, entryDN,
+                                                        config, v);
+                  if (conflictDN != null)
+                  {
+                    Message message = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
+                                           t.getNameOrOID(),
+                                           modifyOperation.getConnectionID(),
+                                           modifyOperation.getOperationID(),
+                                           v.getStringValue(),
+                                           entryDN.toString(),
+                                           conflictDN.toString());
+                    DirectoryServer.sendAlertNotification(this,
+                                         ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
+                                         message);
+                  }
+                }
+                catch (DirectoryException de)
+                {
+                  if (debugEnabled())
+                  {
+                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
+                  }
+
+                  Message message =
+                       ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
+                            modifyOperation.getConnectionID(),
+                            modifyOperation.getOperationID(),
+                            entryDN.toString(),
+                            de.getResultCode().toString(),
+                            de.getMessageObject());
+                  DirectoryServer.sendAlertNotification(this,
+                                       ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR,
+                                       message);
+                }
+              }
+            }
+          }
+          break;
+
+        default:
+          // We don't need to look at this modification because it's not a
+          // modification type of interest.
+          continue;
+      }
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void doPostSynchronization(
+                         PostSynchronizationModifyDNOperation modifyDNOperation)
+  {
+    UniqueAttributePluginCfg config = currentConfiguration;
+
+    Set<DN> baseDNs = getBaseDNs(config,
+                                 modifyDNOperation.getUpdatedEntry().getDN());
+    if (baseDNs == null)
+    {
+      // The entry is outside the scope of this plugin.
+      return;
+    }
+
+    RDN newRDN = modifyDNOperation.getNewRDN();
+    for (int i=0; i < newRDN.getNumValues(); i++)
+    {
+      AttributeType t = newRDN.getAttributeType(i);
+      if (! config.getUniqueAttributeType().contains(t))
+      {
+        // We aren't interested in this attribute type.
+        continue;
+      }
+
+      try
+      {
+        AttributeValue v = newRDN.getAttributeValue(i);
+        DN conflictDN = getConflictingEntryDN(baseDNs,
+                             modifyDNOperation.getEntryDN(), config, v);
+        if (conflictDN != null)
+        {
+          Message m =
+               ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
+                    t.getNameOrOID(),
+                    modifyDNOperation.getConnectionID(),
+                    modifyDNOperation.getOperationID(),
+                    v.getStringValue(),
+                    modifyDNOperation.getUpdatedEntry().getDN().toString(),
+                    conflictDN.toString());
+          DirectoryServer.sendAlertNotification(this,
+                               ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, m);
+        }
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
+                         modifyDNOperation.getConnectionID(),
+                         modifyDNOperation.getOperationID(),
+                         modifyDNOperation.getUpdatedEntry().getDN().toString(),
+                         de.getResultCode().toString(),
+                         de.getMessageObject());
+        DirectoryServer.sendAlertNotification(this,
+                             ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, m);
+      }
+    }
+  }
+
+
+
+  /**
+   * Retrieves the set of base DNs below which uniqueness checks should be
+   * performed.  If no uniqueness checks should be performed for the specified
+   * entry, then {@code null} will be returned.
+   *
+   * @param  config   The plugin configuration to use to make the determination.
+   * @param  entryDN  The DN of the entry for which the checks will be
+   *                  performed.
+   */
+  private Set<DN> getBaseDNs(UniqueAttributePluginCfg config, DN entryDN)
+  {
+    Set<DN> baseDNs = config.getUniqueAttributeBaseDN();
+    if ((baseDNs == null) || baseDNs.isEmpty())
+    {
+      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
+    }
+
+    for (DN baseDN : baseDNs)
+    {
+      if (entryDN.isDescendantOf(baseDN))
+      {
+        return baseDNs;
+      }
+    }
+
+    return null;
+  }
+
+
+
+  /**
+   * Retrieves the DN of the first entry identified that conflicts with the
+   * provided value.
+   *
+   * @param  baseDNs   The set of base DNs below which the search is to be
+   *                   performed.
+   * @param  targetDN  The DN of the entry at which the change is targeted.  If
+   *                   a conflict is found in that entry, then it will be
+   *                   ignored.
+   * @param  config    The plugin configuration to use when making the
+   *                   determination.
+   * @param  value     The value for which to identify any conflicting entries.
+   *
+   * @return  The DN of the first entry identified that contains a conflicting
+   *          value.
+   *
+   * @throws  DirectoryException  If a problem occurred while attempting to
+   *                              make the determination.
+   */
+  private DN getConflictingEntryDN(Set<DN> baseDNs, DN targetDN,
+                                   UniqueAttributePluginCfg config,
+                                   AttributeValue value)
+          throws DirectoryException
+  {
+    SearchFilter filter;
+    Set<AttributeType> attrTypes = config.getUniqueAttributeType();
+    if (attrTypes.size() == 1)
+    {
+      filter = SearchFilter.createEqualityFilter(attrTypes.iterator().next(),
+                                                 value);
+    }
+    else
+    {
+      ArrayList<SearchFilter> equalityFilters =
+           new ArrayList<SearchFilter>(attrTypes.size());
+      for (AttributeType t : attrTypes)
+      {
+        equalityFilters.add(SearchFilter.createEqualityFilter(t, value));
+      }
+      filter = SearchFilter.createORFilter(equalityFilters);
+    }
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    for (DN baseDN : baseDNs)
+    {
+      InternalSearchOperation searchOperation =
+           conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE,
+                              DereferencePolicy.NEVER_DEREF_ALIASES, 2, 0,
+                              false, filter, SEARCH_ATTRS);
+      for (SearchResultEntry e : searchOperation.getSearchEntries())
+      {
+        if (! e.getDN().equals(targetDN))
+        {
+          return e.getDN();
+        }
+      }
+
+      switch (searchOperation.getResultCode())
+      {
+        case SUCCESS:
+        case NO_SUCH_OBJECT:
+          // These are fine.  Either the search was successful or the base DN
+          // didn't exist.
+          break;
+
+        default:
+          // An error occurred that prevented the search from completing
+          // successfully.
+          throw new DirectoryException(searchOperation.getResultCode(),
+                         searchOperation.getErrorMessage().toMessage());
+      }
+    }
+
+    // If we've gotten here, then no conflict was found.
+    return null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isConfigurationAcceptable(PluginCfg configuration,
+                                           List<Message> unacceptableReasons)
+  {
+    UniqueAttributePluginCfg cfg = (UniqueAttributePluginCfg) configuration;
+    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
+  }
+
+
+
   /**
    * {@inheritDoc}
    */
   public boolean isConfigurationChangeAcceptable(
-          UniqueAttributePluginCfg configuration,
-          List<Message> unacceptableReasons) {
+                      UniqueAttributePluginCfg configuration,
+                      List<Message> unacceptableReasons)
+  {
     boolean configAcceptable = true;
     for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
     {
@@ -113,12 +843,15 @@
         case PREOPERATIONADD:
         case PREOPERATIONMODIFY:
         case PREOPERATIONMODIFYDN:
+        case POSTSYNCHRONIZATIONADD:
+        case POSTSYNCHRONIZATIONMODIFY:
+        case POSTSYNCHRONIZATIONMODIFYDN:
           // These are acceptable.
           break;
 
         default:
-          Message message =
-           ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(pluginType.toString());
+          Message message = ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(
+                                 pluginType.toString());
           unacceptableReasons.add(message);
           configAcceptable = false;
       }
@@ -126,337 +859,53 @@
     return configAcceptable;
   }
 
+
+
   /**
    * {@inheritDoc}
    */
   public ConfigChangeResult applyConfigurationChange(
-          UniqueAttributePluginCfg newConfiguration) {
-    ResultCode resultCode          = ResultCode.SUCCESS;
-    boolean           adminActionRequired = false;
-    ArrayList<Message> messages            = new ArrayList<Message>();
-    LinkedHashSet<AttributeType> newUniqueattributeTypes=
-                                             new LinkedHashSet<AttributeType>();
-    LinkedHashSet<DN> newConfiguredBaseDNs = new LinkedHashSet<DN>();
-    //Load base DNs from new configuration.
-    for(DN baseDN : newConfiguration.getUniqueAttributeBaseDN())
-      newConfiguredBaseDNs.add(baseDN);
-    //Load attribute types from new configuration.
-    for(AttributeType attributeType : newConfiguration.getUniqueAttributeType())
-      newUniqueattributeTypes.add(attributeType);
-    //Switch to the new lists and configurations.
-    baseDNs = newConfiguredBaseDNs;
-    uniqueAttributeTypes = newUniqueattributeTypes;
+                                 UniqueAttributePluginCfg newConfiguration)
+  {
     currentConfiguration = newConfiguration;
-    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+    return new ConfigChangeResult(ResultCode.SUCCESS, false);
   }
 
+
+
   /**
    * {@inheritDoc}
    */
-  @Override()
-  public final PreOperationPluginResult
-               doPreOperation(PreOperationAddOperation addOperation) {
-    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
-    DN entryDN=addOperation.getEntryDN();
-    if(isEntryUniquenessCandidate(entryDN)) {
-      List<AttributeValue> valueList =
-                         getEntryAttributeValues(addOperation.getEntryToAdd());
-      if(!searchAllBaseDNs(valueList, entryDN))
-        pluginResult =  getPluginErrorResult(addOperation,
-                ERR_PLUGIN_UNIQUEATTR_ADD_NOT_UNIQUE.get(entryDN.toString()));
-    }
-    return pluginResult;
+  public DN getComponentEntryDN()
+  {
+    return currentConfiguration.dn();
   }
 
+
+
   /**
    * {@inheritDoc}
    */
-  @Override()
-  public final PreOperationPluginResult
-  doPreOperation(PreOperationModifyOperation modifyOperation) {
-    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
-    DN entryDN = modifyOperation.getEntryDN();
-    if(isEntryUniquenessCandidate(entryDN)) {
-      List<AttributeValue> valueList =
-              getModificationAttributeValues(modifyOperation.getModifications(),
-                                        modifyOperation.getModifiedEntry());
-      if(!searchAllBaseDNs(valueList, entryDN))
-        pluginResult =  getPluginErrorResult(modifyOperation,
-                  ERR_PLUGIN_UNIQUEATTR_MOD_NOT_UNIQUE.get(entryDN.toString()));
-    }
-    return pluginResult;
+  public String getClassName()
+  {
+    return UniqueAttributePlugin.class.getName();
   }
 
+
+
   /**
    * {@inheritDoc}
    */
-  @Override()
-  public final PreOperationPluginResult
-               doPreOperation(PreOperationModifyDNOperation modifyDNOperation) {
-    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
-    DN entryDN=modifyDNOperation.getOriginalEntry().getDN();
-    //If the operation has a new superior DN then use that, since any moves
-    //need to make sure there are no conflicts in the new superior base DN.
-    if(modifyDNOperation.getNewSuperior() != null)
-        entryDN = modifyDNOperation.getNewSuperior();
-    if(isEntryUniquenessCandidate(entryDN)) {
-      List<AttributeValue> valueList =
-              getRDNAttributeValues(modifyDNOperation.getNewRDN());
-      if(!searchAllBaseDNs(valueList, entryDN))
-        pluginResult =  getPluginErrorResult(modifyDNOperation,
-                ERR_PLUGIN_UNIQUEATTR_MODDN_NOT_UNIQUE.get(entryDN.toString()));
-    }
-    return pluginResult;
-  }
+  public LinkedHashMap<String,String> getAlerts()
+  {
+    LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>(2);
 
-  /**
-   * Determine if the specified DN is a candidate for attribute uniqueness
-   * checking. Checking is skipped if the the unique attribute type list is
-   * empty or if there are base DNS configured and the specified DN is not a
-   * descendant of any of them. Checking is performed for all other cases.
-   *
-   * @param dn The DN to check.
-   *
-   * @return Returns <code>true</code> if the operation needs uniqueness
-   *         checking performed.
-   */
-  private boolean
-  isEntryUniquenessCandidate(DN dn) {
-    if(uniqueAttributeTypes.isEmpty())
-      return false;
-    else if(baseDNs.isEmpty())
-      return true;
-    else {
-      for(DN baseDN : baseDNs)
-        if(baseDN.isAncestorOf(dn))
-          return true;
-    }
-    return false;
-  }
+    alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
+               ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_CONFLICT);
+    alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR,
+               ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_ERROR);
 
-  /**
-   * Returns a plugin result instance indicating that the operation should be
-   * terminated; that no further pre-operation processing should be performed
-   * and that the server should send the response immediately. It also adds
-   * a CONSTRAINT_VIOLATION result code and the specified error message to
-   * the specified operation.
-   *
-   * @param operation   The operation to add the result code and message to.
-   *
-   * @param message The message to add to the operation.
-   *
-   * @return Returns a plugin result instance that halts further processing
-   *         on this operation.
-   */
-  private PreOperationPluginResult
-  getPluginErrorResult(PreOperationOperation operation, Message message) {
-        operation.appendErrorMessage(message);
-        operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-        return new PreOperationPluginResult(false, false, true);
-  }
-
-  /**
-   * Searches all of the the attribute types of the specified RDN for matches
-   * in the unique attribute type list. If matches are found, then the
-   * corresponding values are added to a list of values that will be eventually
-   * searched for uniqueness.
-
-   * @param rdn  The RDN to examine.
-   *
-   * @return Returns a list of attribute values from the RDN that matches the
-   *         unique attribute type list.
-   */
-  private List<AttributeValue> getRDNAttributeValues(RDN rdn) {
-    LinkedList<AttributeValue> valueList=
-                                            new LinkedList<AttributeValue>();
-    int numAVAs = rdn.getNumValues();
-    for (int i = 0; i < numAVAs; i++) {
-      if(uniqueAttributeTypes.contains(rdn.getAttributeType(i)))
-        valueList.add(rdn.getAttributeValue(i));
-    }
-    return valueList;
-  }
-
-  /**
-   * Searches all of the attribute types of the specified entry for matches
-   * in the unique attribute type list. Ff matches are found, then the
-   * corresponding values are added to a list of values that will eventually
-   * be searched for uniqueness.
-   *
-   * @param entry The entry to examine.
-   *
-   * @return Returns a list of attribute values from the entry that matches the
-   *         unique attribute type list.
-   */
-  private List<AttributeValue> getEntryAttributeValues(Entry entry) {
-    LinkedList<AttributeValue> valueList=new LinkedList<AttributeValue>();
-    for(AttributeType attributeType : uniqueAttributeTypes) {
-      if(entry.hasAttribute(attributeType))  {
-        List<Attribute> attrList=entry.getAttribute(attributeType);
-        for (Attribute a : attrList)
-          valueList.addAll(a.getValues());
-      }
-    }
-    return valueList;
-  }
-
-  /**
-   * Iterate over the unique attribute type list calling a method that will
-   * search the specified modification list for each attribute type and add
-   * the corresponding values to a list of values.
-   *
-   * @param modificationList  The modification list to search over.
-   *
-   * @param modifedEntry The copy of the entry with modifications applied.
-   *
-   * @return Returns a list of attribute values from the modification list
-   *         that matches the unique attribute type list.
-   */
-  private List<AttributeValue>
-  getModificationAttributeValues(List<Modification>  modificationList,
-                            Entry modifedEntry)  {
-    LinkedList<AttributeValue> valueList =
-                                            new LinkedList<AttributeValue>();
-    for(AttributeType attributeType : uniqueAttributeTypes)
-      getModValuesForAttribute(modificationList, attributeType, valueList,
-                               modifedEntry);
-    return valueList;
-  }
-
-  /**
-   * Searches the specified modification list for the provided attribute type.
-   * If a match is found than the attribute value is added to a list of
-   * attribute values that will be eventually searched for uniqueness.
-   *
-   * @param modificationList The modification list to search over.
-   *
-   * @param attributeType The attribute type to search for.
-   *
-   * @param valueList A list of attribute values to put the values in.
-   *
-   * @param modifiedEntry A copy of the entry with modifications applied.
-   */
-  private void
-  getModValuesForAttribute(List<Modification> modificationList,
-                           AttributeType attributeType,
-                           LinkedList<AttributeValue> valueList,
-                           Entry modifiedEntry) {
-
-    for(Modification modification : modificationList) {
-      ModificationType modType=modification.getModificationType();
-      //Skip delete modifications or modifications on attribute types not
-      //matching the specified type.
-      if(modType == ModificationType.DELETE ||
-         !modification.getAttribute().getAttributeType().equals(attributeType))
-          continue;
-      //Increment uses modified entry to get value for the attribute type.
-      if(modType == ModificationType.INCREMENT) {
-        List<Attribute> modifiedAttrs =
-           modifiedEntry.getAttribute(attributeType,
-                                      modification.getAttribute().getOptions());
-        if (modifiedAttrs != null)  {
-          for (Attribute a : modifiedAttrs)
-            valueList.addAll(a.getValues());
-        }
-      } else {
-        Attribute modifiedAttribute=modification.getAttribute();
-        if(modifiedAttribute.hasValue())
-          valueList.addAll(modifiedAttribute.getValues());
-      }
-    }
-  }
-
-
-  /**
-   * Iterates over the base DNs configured by the plugin entry searching for
-   * value matches. If the base DN list is empty then the public naming
-   * contexts are used instead.
-   *
-   * @param valueList The list of values to search for.
-   *
-   * @param entryDN  The DN of the entry related to the operation.
-   *
-   * @return  Returns <code>true</code> if a value is unique.
-   */
-  private boolean
-  searchAllBaseDNs(List<AttributeValue> valueList, DN entryDN) {
-    if(valueList.isEmpty())
-      return true;
-    if(baseDNs.isEmpty()) {
-      for(DN baseDN : DirectoryServer.getPublicNamingContexts().keySet()) {
-        if(searchBaseDN(valueList, baseDN, entryDN))
-          return false;
-      }
-    } else {
-      for(DN baseDN : baseDNs)  {
-        if(searchBaseDN(valueList, baseDN, entryDN))
-          return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Search a single base DN for all the values in a specified value list.
-   * A filter is created to search all the attribute at once for each
-   * value in the list.
-   *
-   * @param valueList The list of values to search for.
-   *
-   * @param baseDN  The base DN to base the search at.
-   *
-   * @param entryDN  The DN of the entry related to the operation.
-   *
-   * @return Returns <code>true</code> if the values are not unique under the
-   *         under the base DN.
-   */
-  private boolean
-  searchBaseDN(List<AttributeValue> valueList, DN baseDN,
-                    DN entryDN) {
-    //Filter set to hold component filters.
-    HashSet<SearchFilter> componentFilters=new HashSet<SearchFilter>();
-    for(AttributeValue value : valueList) {
-      //Iterate over the unique attribute list and build a equality filter
-      //using each attribute type in the list and the current value being
-      //matched.
-      for(AttributeType attributeType : uniqueAttributeTypes)
-        componentFilters.add(SearchFilter.createEqualityFilter(attributeType,
-                value));
-      //Perform the search using the OR filter created from the filter
-      //components created above.
-      InternalClientConnection conn =
-              InternalClientConnection.getRootConnection();
-      InternalSearchOperation operation = conn.processSearch(baseDN,
-              SearchScope.WHOLE_SUBTREE,
-              DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, true,
-              SearchFilter.createORFilter(componentFilters),
-              null);
-      switch (operation.getResultCode()) {
-        case SUCCESS:
-          break;
-
-        case NO_SUCH_OBJECT:
-          //This base DN doesn't exist, return false.
-          return false;
-
-        case SIZE_LIMIT_EXCEEDED:
-        case TIME_LIMIT_EXCEEDED:
-        case ADMIN_LIMIT_EXCEEDED:
-        default:
-          //Couldn't determine if the attribute is unique because an
-          //administrative limit was reached during the search. Fail the
-          //operation by returning true. Possibly log an error here?
-          return true;
-      }
-      for (SearchResultEntry entry : operation.getSearchEntries()) {
-        //Only allow the entry DN to exist. The user might be modifying
-        //the attribute values and putting the same value back. Any other entry
-        //means the value is not unique.
-        if(!entry.getDN().equals(entryDN))
-          return true;
-      }
-      componentFilters.clear();
-    }
-    return false;
+    return alerts;
   }
 }
+
diff --git a/opends/src/server/org/opends/server/types/operation/PostSynchronizationAddOperation.java b/opends/src/server/org/opends/server/types/operation/PostSynchronizationAddOperation.java
new file mode 100644
index 0000000..3a7fb5a
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/operation/PostSynchronizationAddOperation.java
@@ -0,0 +1,136 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types.operation;
+
+
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.RawAttribute;
+
+
+
+/**
+ * This class defines a set of methods that are available for use by
+ * post-synchronization plugins for add operations.  Note that this
+ * interface is intended only to define an API for use by plugins and
+ * is not intended to be implemented by any custom classes.
+ */
+@org.opends.server.types.PublicAPI(
+     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+     mayInstantiate=false,
+     mayExtend=false,
+     mayInvoke=true)
+public interface PostSynchronizationAddOperation
+       extends PostSynchronizationOperation
+{
+  /**
+   * Retrieves the DN of the entry to add in a raw, unparsed form as
+   * it was included in the request.  This may or may not actually
+   * contain a valid DN, since no validation will have been performed
+   * on it.
+   *
+   * @return  The DN of the entry in a raw, unparsed form.
+   */
+  public ByteString getRawEntryDN();
+
+
+
+  /**
+   * Retrieves the set of attributes in their raw, unparsed form as
+   * read from the client request.  Some of these attributes may be
+   * invalid as no validation will have been performed on them.  The
+   * returned list must not be altered by the caller.
+   *
+   * @return  The set of attributes in their raw, unparsed form as
+   *          read from the client request.
+   */
+  public List<RawAttribute> getRawAttributes();
+
+
+
+  /**
+   * Retrieves the DN of the entry to add.
+   *
+   * @return  The DN of the entry to add.
+   */
+  public DN getEntryDN();
+
+
+
+  /**
+   * Retrieves the set of processed objectclasses for the entry to
+   * add.  The contents of the returned map must not be altered by the
+   * caller.
+   *
+   * @return  The set of processed objectclasses for the entry to add.
+   */
+  public Map<ObjectClass,String> getObjectClasses();
+
+
+
+  /**
+   * Retrieves the set of processed user attributes for the entry to
+   * add.  The contents of the returned map must not be altered by the
+   * caller.
+   *
+   * @return  The set of processed user attributes for the entry to
+   *          add.
+   */
+  public Map<AttributeType,List<Attribute>> getUserAttributes();
+
+
+
+  /**
+   * Retrieves the set of processed operational attributes for the
+   * entry to add.  The contents of the returned map must not be
+   * altered by the caller.
+   *
+   * @return  The set of processed operational attributes for the
+   *          entry to add.
+   */
+  public Map<AttributeType,List<Attribute>>
+              getOperationalAttributes();
+
+
+
+  /**
+   * Retrieves the entry to be added to the server.  The contents of
+   * the returned entry must not be altered by the caller.
+   *
+   * @return  The entry to be added to the server.
+   */
+  public Entry getEntryToAdd();
+}
+
diff --git a/opends/src/server/org/opends/server/types/operation/PostSynchronizationDeleteOperation.java b/opends/src/server/org/opends/server/types/operation/PostSynchronizationDeleteOperation.java
new file mode 100644
index 0000000..e40837d
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/operation/PostSynchronizationDeleteOperation.java
@@ -0,0 +1,78 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types.operation;
+
+
+
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+
+
+
+/**
+ * This class defines a set of methods that are available for use by
+ * post-synchronization plugins for delete operations.  Note that this
+ * interface is intended only to define an API for use by plugins and
+ * is not intended to be implemented by any custom classes.
+ */
+@org.opends.server.types.PublicAPI(
+     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+     mayInstantiate=false,
+     mayExtend=false,
+     mayInvoke=true)
+public interface PostSynchronizationDeleteOperation
+       extends PostSynchronizationOperation
+{
+  /**
+   * Retrieves the raw, unprocessed entry DN as included in the client
+   * request.
+   *
+   * @return  The raw, unprocessed entry DN as included in the client
+   *          request.
+   */
+  public ByteString getRawEntryDN();
+
+
+
+  /**
+   * Retrieves the DN of the entry to delete.
+   *
+   * @return  The DN of the entry to delete.
+   */
+  public DN getEntryDN();
+
+
+
+  /**
+   * Retrieves the entry to be deleted.
+   *
+   * @return  The entry to be deleted.
+   */
+  public Entry getEntryToDelete();
+}
+
diff --git a/opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyDNOperation.java b/opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyDNOperation.java
new file mode 100644
index 0000000..eb8225e
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyDNOperation.java
@@ -0,0 +1,189 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types.operation;
+
+
+
+import java.util.List;
+
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.RDN;
+
+
+
+/**
+ * This class defines a set of methods that are available for use by
+ * post-synchronization plugins for modify DN operations.  Note that
+ * this interface is intended only to define an API for use by plugins
+ * and is not intended to be implemented by any custom classes.
+ */
+@org.opends.server.types.PublicAPI(
+     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+     mayInstantiate=false,
+     mayExtend=false,
+     mayInvoke=true)
+public interface PostSynchronizationModifyDNOperation
+       extends PostSynchronizationOperation
+{
+  /**
+   * Retrieves the raw, unprocessed entry DN as included in the client
+   * request.  The DN that is returned may or may not be a valid DN,
+   * since no validation will have been performed upon it.
+   *
+   * @return  The raw, unprocessed entry DN as included in the client
+   *          request.
+   */
+  public ByteString getRawEntryDN();
+
+
+
+  /**
+   * Retrieves the DN of the entry to rename.  This should not be
+   * called by pre-parse plugins because the processed DN will not be
+   * available yet.  Instead, they should call the
+   * <CODE>getRawEntryDN</CODE> method.
+   *
+   * @return  The DN of the entry to rename, or <CODE>null</CODE> if
+   *          the raw entry DN has not yet been processed.
+   */
+  public DN getEntryDN();
+
+
+
+  /**
+   * Retrieves the raw, unprocessed newRDN as included in the request
+   * from the client.  This may or may not contain a valid RDN, as no
+   * validation will have been performed on it.
+   *
+   * @return  The raw, unprocessed newRDN as included in the request
+   *          from the client.
+   */
+  public ByteString getRawNewRDN();
+
+
+
+  /**
+   * Retrieves the new RDN to use for the entry.  This should not be
+   * called by pre-parse plugins, because the processed newRDN will
+   * not yet be available.  Pre-parse plugins should instead use the
+   * <CODE>getRawNewRDN</CODE> method.
+   *
+   * @return  The new RDN to use for the entry, or <CODE>null</CODE>
+   *          if the raw newRDN has not yet been processed.
+   */
+  public RDN getNewRDN();
+
+
+
+  /**
+   * Indicates whether the current RDN value should be removed from
+   * the entry.
+   *
+   * @return  <CODE>true</CODE> if the current RDN value should be
+   *          removed from the entry, or <CODE>false</CODE> if not.
+   */
+  public boolean deleteOldRDN();
+
+
+
+  /**
+   * Retrieves the raw, unprocessed newSuperior from the client
+   * request.  This may or may not contain a valid DN, as no
+   * validation will have been performed on it.
+   *
+   * @return  The raw, unprocessed newSuperior from the client
+   *          request, or <CODE>null</CODE> if there is none.
+   */
+  public ByteString getRawNewSuperior();
+
+
+
+  /**
+   * Retrieves the newSuperior DN for the entry.  This should not be
+   * called by pre-parse plugins, because the processed DN will not
+   * yet be available at that time.  Instead, they should use the
+   * <CODE>getRawNewSuperior</CODE> method.
+   *
+   * @return  The newSuperior DN for the entry, or <CODE>null</CODE>
+   *          if there is no newSuperior DN for this request or if the
+   *          raw newSuperior has not yet been processed.
+   */
+  public DN getNewSuperior();
+
+
+
+  /**
+   * Retrieves the set of modifications applied to attributes of the
+   * target entry in the course of processing this modify DN
+   * operation.  This will include attribute-level changes from the
+   * modify DN itself (e.g., removing old RDN values if deleteOldRDN
+   * is set, or adding new RDN values that don't already exist), but
+   * it may also be used by pre-operation plugins to cause additional
+   * changes in the entry.  In this case, those plugins may add
+   * modifications to this list through the
+   * <CODE>addModification</CODE> method (the list returned from this
+   * method should not be modified directly) if any changes should be
+   * processed in addition to the core modify DN processing.  Backends
+   * may read this list to identify which attribute-level changes were
+   * applied in order to more easily apply updates to attribute
+   * indexes.
+   *
+   * @return  The set of modifications applied to attributes during
+   *          the course of the modify DN processing, or
+   *          <CODE>null</CODE> if that information is not yet
+   *          available (e.g., during pre-parse plugins).
+   */
+  public List<Modification> getModifications();
+
+
+
+  /**
+   * Retrieves the current entry, before it is renamed.  This will not
+   * be available to pre-parse plugins or during the conflict
+   * resolution portion of the synchronization processing.
+   *
+   * @return  The current entry, or <CODE>null</CODE> if it is not yet
+   *           available.
+   */
+  public Entry getOriginalEntry();
+
+
+
+  /**
+   * Retrieves the new entry, as it will appear after it is renamed.
+   * This will not be  available to pre-parse plugins or during the
+   * conflict resolution portion of the synchronization processing.
+   *
+   * @return  The updated entry, or <CODE>null</CODE> if it is not yet
+   *           available.
+   */
+  public Entry getUpdatedEntry();
+}
+
diff --git a/opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyOperation.java b/opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyOperation.java
new file mode 100644
index 0000000..45c6f72
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/operation/PostSynchronizationModifyOperation.java
@@ -0,0 +1,148 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types.operation;
+
+
+
+import java.util.List;
+
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.RawModification;
+
+
+
+/**
+ * This class defines a set of methods that are available for use by
+ * post-synchronization plugins for modify operations.  Note that this
+ * interface is intended only to define an API for use by plugins and
+ * is not intended to be implemented by any custom classes.
+ */
+@org.opends.server.types.PublicAPI(
+     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+     mayInstantiate=false,
+     mayExtend=false,
+     mayInvoke=true)
+public interface PostSynchronizationModifyOperation
+       extends PostSynchronizationOperation
+{
+  /**
+   * Retrieves the raw, unprocessed entry DN as included in the client
+   * request.  The DN that is returned may or may not be a valid DN,
+   * since no validation will have been performed upon it.
+   *
+   * @return  The raw, unprocessed entry DN as included in the client
+   *          request.
+   */
+  public ByteString getRawEntryDN();
+
+
+
+  /**
+   * Retrieves the DN of the entry to modify.
+   *
+   * @return  The DN of the entry to modify.
+   */
+  public DN getEntryDN();
+
+
+
+  /**
+   * Retrieves the set of raw, unprocessed modifications as included
+   * in the client request.  Note that this may contain one or more
+   * invalid modifications, as no validation will have been performed
+   * on this information.  The list returned must not be altered by
+   * the caller.
+   *
+   * @return  The set of raw, unprocessed modifications as included
+   *          in the client request.
+   */
+  public List<RawModification> getRawModifications();
+
+
+
+  /**
+   * Retrieves the set of modifications for this modify operation.
+   Its contents should not be altered.
+   *
+   * @return  The set of modifications for this modify operation.
+   */
+  public List<Modification> getModifications();
+
+
+
+  /**
+   * Retrieves the current entry before any modifications are applied.
+   * It should not be modified by the caller.
+   *
+   * @return  The current entry before any modifications are applied.
+   */
+  public Entry getCurrentEntry();
+
+
+
+  /**
+   * Retrieves the modified entry that is to be written to the
+   * backend.  It should not be modified by the caller.
+   *
+   * @return  The modified entry that is to be written to the backend.
+   */
+  public Entry getModifiedEntry();
+
+
+
+  /**
+   * Retrieves the set of clear-text current passwords for the user,
+   * if available.  This will only be available if the modify
+   * operation contains one or more delete elements that target the
+   * password attribute and provide the values to delete in the clear.
+   * This list should not be altered by the caller.
+   *
+   * @return  The set of clear-text current password values as
+   *          provided in the modify request, or <CODE>null</CODE> if
+   *          there were none.
+   */
+  public List<AttributeValue> getCurrentPasswords();
+
+
+
+  /**
+   * Retrieves the set of clear-text new passwords for the user, if
+   * available.  This will only be available if the modify operation
+   * contains one or more add or replace elements that target the
+   * password attribute and provide the values in the clear.  This
+   * list should not be altered by the caller.
+   *
+   * @return  The set of clear-text new passwords as provided in the
+   *          modify request, or <CODE>null</CODE> if there were none.
+   */
+  public List<AttributeValue> getNewPasswords();
+}
+
diff --git a/opends/src/server/org/opends/server/types/operation/PostSynchronizationOperation.java b/opends/src/server/org/opends/server/types/operation/PostSynchronizationOperation.java
new file mode 100644
index 0000000..ed8e0e9
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/operation/PostSynchronizationOperation.java
@@ -0,0 +1,144 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types.operation;
+import org.opends.messages.MessageBuilder;
+
+
+import java.util.List;
+
+import org.opends.server.types.DN;
+import org.opends.server.types.ResultCode;
+
+
+
+/**
+ * This class defines a set of methods that are available for use by
+ * post-synchronization plugins for all types of operations.  Note
+ * that this interface is intended only to define an API for use by
+ * plugins and is not intended to be implemented by any custom
+ * classes.
+ */
+@org.opends.server.types.PublicAPI(
+     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+     mayInstantiate=false,
+     mayExtend=false,
+     mayInvoke=true)
+public interface PostSynchronizationOperation
+       extends PluginOperation
+{
+  /**
+   * Retrieves the result code for this operation.
+   *
+   * @return  The result code associated for this operation, or
+   *          <CODE>UNDEFINED</CODE> if the operation has not yet
+   *          completed.
+   */
+  public ResultCode getResultCode();
+
+
+
+  /**
+   * Retrieves the error message for this operation.  Its contents may
+   * be altered by the caller.
+   *
+   * @return  The error message for this operation.
+   */
+  public MessageBuilder getErrorMessage();
+
+
+
+  /**
+   * Retrieves the additional log message for this operation, which
+   * should be written to the log but not included in the response to
+   * the client.  The contents of this buffer may be altered by the
+   * caller.
+   *
+   * @return  The additional log message for this operation.
+   */
+  public MessageBuilder getAdditionalLogMessage();
+
+
+
+  /**
+   * Retrieves the matched DN for this operation.
+   *
+   * @return  The matched DN for this operation, or <CODE>null</CODE>
+   *          if the operation has not yet completed or does not have
+   *          a matched DN.
+   */
+  public DN getMatchedDN();
+
+
+
+  /**
+   * Retrieves the set of referral URLs for this operation.  Its
+   * contents must not be altered by the caller.
+   *
+   * @return  The set of referral URLs for this operation, or
+   *          <CODE>null</CODE> if the operation is not yet complete
+   *          or does not have a set of referral URLs.
+   */
+  public List<String> getReferralURLs();
+
+
+
+  /**
+   * Retrieves the authorization DN for this operation.  In many
+   * cases, it will be the same as the DN of the authenticated user
+   * for the underlying connection, or the null DN if no
+   * authentication has been performed on that connection.  However,
+   * it may be some other value if special processing has been
+   * requested (e.g., the operation included a proxied authorization
+   * control).
+   *
+   * @return  The authorization DN for this operation.
+   */
+  public DN getAuthorizationDN();
+
+
+
+  /**
+   * Retrieves the time that processing stopped for this operation.
+   * This will actually hold a time immediately before the response
+   * was sent to the client.
+   *
+   * @return  The time that processing stopped for this operation.
+   */
+  public long getProcessingStopTime();
+
+
+
+  /**
+   * Retrieves the length of time in milliseconds that the server
+   * spent processing this operation.
+   *
+   * @return  The length of time in milliseconds that the server spent
+   *          processing this operation.
+   */
+  public long getProcessingTime();
+}
+
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index 2d54cca..704801e 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1703,6 +1703,50 @@
 
 
   /**
+   * The description for the alert type that will be used for the alert
+   * notification generated if a unique attribute conflict is detected during
+   * synchronization processing.
+   */
+  public static final String ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_CONFLICT =
+       "This alert type will be used to provide notification that a unique " +
+       "attribute conflict has been detected during synchronization " +
+       "processing.";
+
+
+
+  /**
+   * The alert type string that will be used for the alert notification
+   * generated if a unique attribute conflict is detected during synchronization
+   * processing.
+   */
+  public static final String ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT =
+       "org.opends.server.UniqueAttributeSynchronizationConflict";
+
+
+
+  /**
+   * The description for the alert type that will be used for the alert
+   * notification generated if an error occurs while attempting to perform
+   * unique attribute conflict detection during synchronization processing.
+   */
+  public static final String ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_ERROR =
+       "This alert type will be used to provide notification that an error " +
+       "occurred while attempting to perform unique attribute conflict " +
+       "detection during synchronization processing.";
+
+
+
+  /**
+   * The alert type string that will be used for the alert notification
+   * generated if an error occurs while attempting to perform unique attribute
+   * conflict detection during synchronization processing.
+   */
+  public static final String ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR =
+       "org.opends.server.UniqueAttributeSynchronizationError";
+
+
+
+  /**
    * The name of the default password storage scheme that will be used for new
    * passwords.
    */
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index 9d06f82..5d0df11 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -63,6 +63,7 @@
 import org.opends.server.types.operation.PostOperationAddOperation;
 import org.opends.server.types.operation.PostResponseAddOperation;
 import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.PostSynchronizationAddOperation;
 import org.opends.server.util.TimeThread;
 
 /**
@@ -72,7 +73,8 @@
 public class LocalBackendAddOperation extends AddOperationWrapper
   implements PreOperationAddOperation,
              PostOperationAddOperation,
-             PostResponseAddOperation
+             PostResponseAddOperation,
+             PostSynchronizationAddOperation
 {
 
   // The entry being added to the server.
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
index b2d68ad..ebd4182 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -33,6 +33,7 @@
 import org.opends.server.types.operation.PostOperationDeleteOperation;
 import org.opends.server.types.operation.PostResponseDeleteOperation;
 import org.opends.server.types.operation.PreOperationDeleteOperation;
+import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
 
 /**
  * This class defines an operation used to delete an entry in a local backend
@@ -41,7 +42,8 @@
 public class LocalBackendDeleteOperation extends DeleteOperationWrapper
   implements PreOperationDeleteOperation,
              PostOperationDeleteOperation,
-             PostResponseDeleteOperation
+             PostResponseDeleteOperation,
+             PostSynchronizationDeleteOperation
 {
   // The entry to be deleted.
   private Entry entry;
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
index 02db5e9..b1e1b32 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -32,6 +32,7 @@
 import org.opends.server.types.operation.PostOperationModifyDNOperation;
 import org.opends.server.types.operation.PostResponseModifyDNOperation;
 import org.opends.server.types.operation.PreOperationModifyDNOperation;
+import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
 
 /**
  * This class defines an operation used to move an entry in a local backend
@@ -41,7 +42,8 @@
   extends ModifyDNOperationWrapper
   implements PreOperationModifyDNOperation,
              PostOperationModifyDNOperation,
-             PostResponseModifyDNOperation
+             PostResponseModifyDNOperation,
+             PostSynchronizationModifyDNOperation
 {
   // The current entry, before it is renamed.
   private Entry currentEntry;
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 28449d3..2339824 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -37,6 +37,7 @@
 import org.opends.server.types.operation.PostOperationModifyOperation;
 import org.opends.server.types.operation.PostResponseModifyOperation;
 import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.opends.server.types.operation.PostSynchronizationModifyOperation;
 
 /**
  * This class defines an operation used to modify an entry in a local backend
@@ -45,7 +46,8 @@
 public class LocalBackendModifyOperation extends ModifyOperationWrapper
   implements PreOperationModifyOperation,
              PostOperationModifyOperation,
-             PostResponseModifyOperation
+             PostResponseModifyOperation,
+             PostSynchronizationModifyOperation
 {
   // The current entry, before any changes are applied.
   private Entry currentEntry = null;
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
index 903ae3c..255cd3f 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -2416,8 +2416,15 @@
     // Indicate that it is now too late to attempt to cancel the operation.
     localOp.setCancelResult(CancelResult.TOO_LATE);
 
-    // Invoke the post-operation modify plugins.
-    if (! skipPostOperation)
+    // Invoke the post-operation or post-synchronization modify plugins.
+    if (localOp.isSynchronizationOperation())
+    {
+      if (localOp.getResultCode() == ResultCode.SUCCESS)
+      {
+        pluginConfigManager.invokePostSynchronizationModifyPlugins(localOp);
+      }
+    }
+    else if (! skipPostOperation)
     {
       // FIXME -- Should this also be done while holding the locks?
       PostOperationPluginResult postOpResult =
@@ -5893,8 +5900,15 @@
     localOp.setCancelResult(CancelResult.TOO_LATE);
 
 
-    // Invoke the post-operation add plugins.
-    if (! skipPostOperation)
+    // Invoke the post-operation or post-synchronization add plugins.
+    if (localOp.isSynchronizationOperation())
+    {
+      if (localOp.getResultCode() == ResultCode.SUCCESS)
+      {
+        pluginConfigManager.invokePostSynchronizationAddPlugins(localOp);
+      }
+    }
+    else if (! skipPostOperation)
     {
       // FIXME -- Should this also be done while holding the locks?
       PostOperationPluginResult postOpResult =
@@ -6672,8 +6686,15 @@
     localOp.setCancelResult(CancelResult.TOO_LATE);
 
 
-    // Invoke the post-operation delete plugins.
-    if (! skipPostOperation)
+    // Invoke the post-operation or post-synchronization delete plugins.
+    if (localOp.isSynchronizationOperation())
+    {
+      if (localOp.getResultCode() == ResultCode.SUCCESS)
+      {
+        pluginConfigManager.invokePostSynchronizationDeletePlugins(localOp);
+      }
+    }
+    else if (! skipPostOperation)
     {
       PostOperationPluginResult postOperationResult =
            pluginConfigManager.invokePostOperationDeletePlugins(localOp);
@@ -8498,8 +8519,15 @@
     op.setCancelResult(CancelResult.TOO_LATE);
 
 
-    // Invoke the post-operation modify DN plugins.
-    if (! skipPostOperation)
+    // Invoke the post-operation or post-synchronization modify DN plugins.
+    if (op.isSynchronizationOperation())
+    {
+      if (op.getResultCode() == ResultCode.SUCCESS)
+      {
+        pluginConfigManager.invokePostSynchronizationModifyDNPlugins(op);
+      }
+    }
+    else if (! skipPostOperation)
     {
       PostOperationPluginResult postOperationResult =
            pluginConfigManager.invokePostOperationModifyDNPlugins(op);
diff --git a/opends/tests/unit-tests-testng/resource/config-changes.ldif b/opends/tests/unit-tests-testng/resource/config-changes.ldif
index b26cc6f..ac3a9bb 100644
--- a/opends/tests/unit-tests-testng/resource/config-changes.ldif
+++ b/opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -48,6 +48,14 @@
 replace: ds-cfg-suppress-internal-operations
 ds-cfg-suppress-internal-operations: false
 
+dn: cn=File-Based Audit Logger,cn=Loggers,cn=config
+changetype: modify
+replace: ds-cfg-logger-enabled
+ds-cfg-logger-enabled: true
+-
+replace: ds-cfg-suppress-internal-operations
+ds-cfg-suppress-internal-operations: false
+
 dn: cn=Test Password Validator,cn=Password Validators,cn=config
 changetype: add
 objectClass: top
@@ -166,6 +174,7 @@
 ds-cfg-plugin-type: preOperationModify
 ds-cfg-plugin-type: preOperationModifyDN
 ds-cfg-plugin-type: preOperationSearch
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=Disconnect Client Plugin,cn=Plugins,cn=config
 changetype: add
@@ -210,6 +219,7 @@
 ds-cfg-plugin-type: postResponseModify
 ds-cfg-plugin-type: postResponseModifyDN
 ds-cfg-plugin-type: postResponseSearch
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=Invocation Counter Plugin,cn=Plugins,cn=config
 changetype: add
@@ -254,6 +264,9 @@
 ds-cfg-plugin-type: postResponseModify
 ds-cfg-plugin-type: postResponseModifyDN
 ds-cfg-plugin-type: postResponseSearch
+ds-cfg-plugin-type: postSynchronizationAdd
+ds-cfg-plugin-type: postSynchronizationModify
+ds-cfg-plugin-type: postSynchronizationModifyDN
 ds-cfg-plugin-type: searchResultEntry
 ds-cfg-plugin-type: searchResultReference
 ds-cfg-plugin-type: subordinateModifyDN
@@ -264,6 +277,7 @@
 ds-cfg-plugin-type: ldifExport
 ds-cfg-plugin-type: startup
 ds-cfg-plugin-type: shutdown
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=Short Circuit Plugin,cn=Plugins,cn=config
 changetype: add
@@ -290,6 +304,7 @@
 ds-cfg-plugin-type: preOperationModify
 ds-cfg-plugin-type: preOperationModifyDN
 ds-cfg-plugin-type: preOperationSearch
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=Update PreOperation Plugin,cn=Plugins,cn=config
 changetype: add
@@ -300,6 +315,7 @@
 ds-cfg-plugin-enabled: true
 ds-cfg-plugin-type: preOperationAdd
 ds-cfg-plugin-type: preOperationModify
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=UID Unique Attribute ,cn=Plugins,cn=config
 changeType: modify
@@ -328,7 +344,11 @@
 ds-cfg-plugin-type: preOperationAdd
 ds-cfg-plugin-type: preOperationModify
 ds-cfg-plugin-type: preOperationModifyDN
+ds-cfg-plugin-type: postSynchronizationAdd
+ds-cfg-plugin-type: postSynchronizationModify
+ds-cfg-plugin-type: postSynchronizationModifyDN
 ds-cfg-unique-attribute-type: bootParameter
+ds-cfg-invoke-for-internal-operations: true
 
 dn: cn=JKS,cn=Key Manager Providers,cn=config
 changetype: modify
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 86afa40..96e387c 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
@@ -37,9 +37,13 @@
 import org.testng.annotations.Test;
 import org.testng.annotations.BeforeClass;
 
+import org.opends.server.admin.server.AdminTestCaseUtils;
+import org.opends.server.admin.std.meta.PluginCfgDefn;
+import org.opends.server.admin.std.server.PluginCfg;
 import org.opends.server.plugins.NullPlugin;
 import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
 import org.opends.server.types.operation.*;
 import org.opends.server.TestCaseUtils;
 import org.opends.messages.Message;
@@ -464,6 +468,34 @@
     expectedPublicMethods.add(sigList);
 
     sigList = new LinkedList<String>();
+    sigList.add("doPostSynchronization");
+    sigList.add("void");
+    sigList.add("org.opends.server.types.operation." +
+                "PostSynchronizationAddOperation");
+    expectedPublicMethods.add(sigList);
+
+    sigList = new LinkedList<String>();
+    sigList.add("doPostSynchronization");
+    sigList.add("void");
+    sigList.add("org.opends.server.types.operation." +
+                "PostSynchronizationDeleteOperation");
+    expectedPublicMethods.add(sigList);
+
+    sigList = new LinkedList<String>();
+    sigList.add("doPostSynchronization");
+    sigList.add("void");
+    sigList.add("org.opends.server.types.operation." +
+                "PostSynchronizationModifyOperation");
+    expectedPublicMethods.add(sigList);
+
+    sigList = new LinkedList<String>();
+    sigList.add("doPostSynchronization");
+    sigList.add("void");
+    sigList.add("org.opends.server.types.operation." +
+                "PostSynchronizationModifyDNOperation");
+    expectedPublicMethods.add(sigList);
+
+    sigList = new LinkedList<String>();
     sigList.add("processSearchEntry");
     sigList.add("org.opends.server.api.plugin.SearchEntryPluginResult");
     sigList.add("org.opends.server.types.operation.SearchEntrySearchOperation");
@@ -498,7 +530,7 @@
     sigList = new LinkedList<String>();
     sigList.add("initializeInternal");
     sigList.add("void");
-    sigList.add("org.opends.server.types.DN");
+    sigList.add("org.opends.server.admin.std.server.PluginCfg");
     sigList.add("java.util.Set");
     expectedPublicMethods.add(sigList);
 
@@ -513,6 +545,17 @@
     expectedPublicMethods.add(sigList);
 
     sigList = new LinkedList<String>();
+    sigList.add("invokeForInternalOperations");
+    sigList.add("boolean");
+    expectedPublicMethods.add(sigList);
+
+    sigList = new LinkedList<String>();
+    sigList.add("setInvokeForInternalOperations");
+    sigList.add("void");
+    sigList.add("boolean");
+    expectedPublicMethods.add(sigList);
+
+    sigList = new LinkedList<String>();
     sigList.add("getClass");
     sigList.add("java.lang.Class");
     expectedPublicMethods.add(sigList);
@@ -676,6 +719,69 @@
   public void testGetPluginEntryDN()
          throws Exception
   {
+    Entry pluginEntry = TestCaseUtils.makeEntry(
+      "dn: cn=Null Plugin,cn=Plugins,cn=config",
+      "objectClass: top",
+      "objectClass: ds-cfg-plugin",
+      "cn: Null Plugin",
+      "ds-cfg-plugin-class: org.opends.server.plugins.NullPlugin",
+      "ds-cfg-plugin-enabled: true",
+      "ds-cfg-plugin-type: startup",
+      "ds-cfg-plugin-type: shutdown",
+      "ds-cfg-plugin-type: postConnect",
+      "ds-cfg-plugin-type: postDisconnect",
+      "ds-cfg-plugin-type: ldifImport",
+      "ds-cfg-plugin-type: ldifExport",
+      "ds-cfg-plugin-type: preParseAbandon",
+      "ds-cfg-plugin-type: preParseAdd",
+      "ds-cfg-plugin-type: preParsebind",
+      "ds-cfg-plugin-type: preParseCompare",
+      "ds-cfg-plugin-type: preParseDelete",
+      "ds-cfg-plugin-type: preParseExtended",
+      "ds-cfg-plugin-type: preParseModify",
+      "ds-cfg-plugin-type: preParseModifyDN",
+      "ds-cfg-plugin-type: preParseSearch",
+      "ds-cfg-plugin-type: preParseUnbind",
+      "ds-cfg-plugin-type: preOperationAdd",
+      "ds-cfg-plugin-type: preOperationbind",
+      "ds-cfg-plugin-type: preOperationCompare",
+      "ds-cfg-plugin-type: preOperationDelete",
+      "ds-cfg-plugin-type: preOperationExtended",
+      "ds-cfg-plugin-type: preOperationModify",
+      "ds-cfg-plugin-type: preOperationModifyDN",
+      "ds-cfg-plugin-type: preOperationSearch",
+      "ds-cfg-plugin-type: postOperationAbandon",
+      "ds-cfg-plugin-type: postOperationAdd",
+      "ds-cfg-plugin-type: postOperationbind",
+      "ds-cfg-plugin-type: postOperationCompare",
+      "ds-cfg-plugin-type: postOperationDelete",
+      "ds-cfg-plugin-type: postOperationExtended",
+      "ds-cfg-plugin-type: postOperationModify",
+      "ds-cfg-plugin-type: postOperationModifyDN",
+      "ds-cfg-plugin-type: postOperationSearch",
+      "ds-cfg-plugin-type: postOperationUnbind",
+      "ds-cfg-plugin-type: postResponseAdd",
+      "ds-cfg-plugin-type: postResponsebind",
+      "ds-cfg-plugin-type: postResponseCompare",
+      "ds-cfg-plugin-type: postResponseDelete",
+      "ds-cfg-plugin-type: postResponseExtended",
+      "ds-cfg-plugin-type: postResponseModify",
+      "ds-cfg-plugin-type: postResponseModifyDN",
+      "ds-cfg-plugin-type: postResponseSearch",
+      "ds-cfg-plugin-type: postsynchronizationAdd",
+      "ds-cfg-plugin-type: postsynchronizationDelete",
+      "ds-cfg-plugin-type: postsynchronizationModify",
+      "ds-cfg-plugin-type: postsynchronizationModifyDN",
+      "ds-cfg-plugin-type: searchResultEntry",
+      "ds-cfg-plugin-type: searchResultReference",
+      "ds-cfg-plugin-type: subordinateModifyDN",
+      "ds-cfg-plugin-type: intermediateResponse");
+
+    PluginCfg configuration =
+         AdminTestCaseUtils.getConfiguration(PluginCfgDefn.getInstance(),
+                                             pluginEntry);
+
+
     NullPlugin nullPlugin = new NullPlugin();
     DN pluginEntryDN = DN.decode("cn=Null Plugin,cn=Plugins,cn=config");
 
@@ -685,7 +791,7 @@
       pluginTypes.add(t);
     }
 
-    nullPlugin.initializeInternal(pluginEntryDN, pluginTypes);
+    nullPlugin.initializeInternal(configuration, pluginTypes);
     assertEquals(nullPlugin.getPluginEntryDN(), pluginEntryDN);
   }
 
@@ -700,8 +806,70 @@
   public void testGetPluginTypes()
          throws Exception
   {
+    Entry pluginEntry = TestCaseUtils.makeEntry(
+      "dn: cn=Null Plugin,cn=Plugins,cn=config",
+      "objectClass: top",
+      "objectClass: ds-cfg-plugin",
+      "cn: Null Plugin",
+      "ds-cfg-plugin-class: org.opends.server.plugins.NullPlugin",
+      "ds-cfg-plugin-enabled: true",
+      "ds-cfg-plugin-type: startup",
+      "ds-cfg-plugin-type: shutdown",
+      "ds-cfg-plugin-type: postConnect",
+      "ds-cfg-plugin-type: postDisconnect",
+      "ds-cfg-plugin-type: ldifImport",
+      "ds-cfg-plugin-type: ldifExport",
+      "ds-cfg-plugin-type: preParseAbandon",
+      "ds-cfg-plugin-type: preParseAdd",
+      "ds-cfg-plugin-type: preParsebind",
+      "ds-cfg-plugin-type: preParseCompare",
+      "ds-cfg-plugin-type: preParseDelete",
+      "ds-cfg-plugin-type: preParseExtended",
+      "ds-cfg-plugin-type: preParseModify",
+      "ds-cfg-plugin-type: preParseModifyDN",
+      "ds-cfg-plugin-type: preParseSearch",
+      "ds-cfg-plugin-type: preParseUnbind",
+      "ds-cfg-plugin-type: preOperationAdd",
+      "ds-cfg-plugin-type: preOperationbind",
+      "ds-cfg-plugin-type: preOperationCompare",
+      "ds-cfg-plugin-type: preOperationDelete",
+      "ds-cfg-plugin-type: preOperationExtended",
+      "ds-cfg-plugin-type: preOperationModify",
+      "ds-cfg-plugin-type: preOperationModifyDN",
+      "ds-cfg-plugin-type: preOperationSearch",
+      "ds-cfg-plugin-type: postOperationAbandon",
+      "ds-cfg-plugin-type: postOperationAdd",
+      "ds-cfg-plugin-type: postOperationbind",
+      "ds-cfg-plugin-type: postOperationCompare",
+      "ds-cfg-plugin-type: postOperationDelete",
+      "ds-cfg-plugin-type: postOperationExtended",
+      "ds-cfg-plugin-type: postOperationModify",
+      "ds-cfg-plugin-type: postOperationModifyDN",
+      "ds-cfg-plugin-type: postOperationSearch",
+      "ds-cfg-plugin-type: postOperationUnbind",
+      "ds-cfg-plugin-type: postResponseAdd",
+      "ds-cfg-plugin-type: postResponsebind",
+      "ds-cfg-plugin-type: postResponseCompare",
+      "ds-cfg-plugin-type: postResponseDelete",
+      "ds-cfg-plugin-type: postResponseExtended",
+      "ds-cfg-plugin-type: postResponseModify",
+      "ds-cfg-plugin-type: postResponseModifyDN",
+      "ds-cfg-plugin-type: postResponseSearch",
+      "ds-cfg-plugin-type: postsynchronizationAdd",
+      "ds-cfg-plugin-type: postsynchronizationDelete",
+      "ds-cfg-plugin-type: postsynchronizationModify",
+      "ds-cfg-plugin-type: postsynchronizationModifyDN",
+      "ds-cfg-plugin-type: searchResultEntry",
+      "ds-cfg-plugin-type: searchResultReference",
+      "ds-cfg-plugin-type: subordinateModifyDN",
+      "ds-cfg-plugin-type: intermediateResponse");
+
+    PluginCfg configuration =
+         AdminTestCaseUtils.getConfiguration(PluginCfgDefn.getInstance(),
+                                             pluginEntry);
+
+
     NullPlugin nullPlugin = new NullPlugin();
-    DN pluginEntryDN = DN.decode("cn=Null Plugin,cn=Plugins,cn=config");
 
     HashSet<PluginType> pluginTypes = new HashSet<PluginType>();
     for (PluginType t : PluginType.values())
@@ -709,7 +877,7 @@
       pluginTypes.add(t);
     }
 
-    nullPlugin.initializeInternal(pluginEntryDN, pluginTypes);
+    nullPlugin.initializeInternal(configuration, pluginTypes);
     assertEquals(nullPlugin.getPluginTypes(), pluginTypes);
   }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/InvocationCounterPlugin.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/InvocationCounterPlugin.java
index fa77cfa..735636e 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/InvocationCounterPlugin.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/InvocationCounterPlugin.java
@@ -89,6 +89,8 @@
   private static AtomicInteger preOperationCounter    = new AtomicInteger(0);
   private static AtomicInteger postOperationCounter   = new AtomicInteger(0);
   private static AtomicInteger postResponseCounter    = new AtomicInteger(0);
+  private static AtomicInteger postSynchronizationCounter =
+                                    new AtomicInteger(0);
   private static AtomicInteger searchEntryCounter     = new AtomicInteger(0);
   private static AtomicInteger searchReferenceCounter = new AtomicInteger(0);
   private static AtomicInteger subordinateModifyDNCounter =
@@ -704,6 +706,69 @@
    * {@inheritDoc}
    */
   @Override()
+  public void doPostSynchronization(PostSynchronizationAddOperation
+                                         addOperation)
+  {
+    postSynchronizationCounter.incrementAndGet();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void doPostSynchronization(PostSynchronizationModifyOperation
+                                         modifyOperation)
+  {
+    postSynchronizationCounter.incrementAndGet();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void doPostSynchronization(PostSynchronizationModifyDNOperation
+                                         modifyDNOperation)
+  {
+    postSynchronizationCounter.incrementAndGet();
+  }
+
+
+
+  /**
+   * Retrieves the number of times that the post-synchronization plugins have
+   * been called since the last reset.
+   *
+   * @return  The number of times that the post-synchronization plugins have
+   *          been called since the last reset.
+   */
+  public static int getPostSynchronizationCount()
+  {
+    return postSynchronizationCounter.get();
+  }
+
+
+
+  /**
+   * Resets the post-synchronization plugin invocation count to zero.
+   *
+   * @return  The post-synchronization plugin invocation count before it was
+   *          reset.
+   */
+  public static int resetPostSynchronizationCount()
+  {
+    return postSynchronizationCounter.getAndSet(0);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
   public SearchEntryPluginResult
        processSearchEntry(SearchEntrySearchOperation searchOperation,
                           SearchResultEntry searchEntry)
@@ -1031,6 +1096,7 @@
     resetPreOperationCount();
     resetPostOperationCount();
     resetPostResponseCount();
+    resetPostSynchronizationCount();
     resetSearchEntryCount();
     resetSearchReferenceCount();
     resetSubordinateModifyDNCount();

--
Gitblit v1.10.0