From 44e65a90731cfd0dbaff36aaa3993fde064f744d Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Mon, 03 Sep 2007 02:33:57 +0000
Subject: [PATCH] Commit plugin for maintaining referential integrity. Issue 257.

---
 opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java                                 |  829 +++++++++++++++++++++++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java                 |   44 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java |  997 +++++++++++++++++++++++++++
 opendj-sdk/opends/resource/config/config.ldif                                                                          |   13 
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml               |  185 +++++
 opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif                                                 |    8 
 opendj-sdk/opends/src/messages/messages/plugin.properties                                                              |   42 +
 opendj-sdk/opends/resource/schema/02-config.ldif                                                                       |   23 
 8 files changed, 2,131 insertions(+), 10 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index e37b617..e042b40 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -1480,6 +1480,19 @@
 ds-cfg-plugin-type: preOperationModifyDN
 ds-cfg-unique-attribute-type: uid
 
+dn: cn=Referential Integrity,cn=Plugins,cn=config
+objectClass: top
+objectClass: ds-cfg-plugin
+objectClass: ds-cfg-referential-integrity-plugin
+cn: Referential Integrity
+ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin
+ds-cfg-plugin-enabled: false
+ds-cfg-plugin-type: postOperationDelete
+ds-cfg-plugin-type: postOperationModifyDN
+ds-cfg-plugin-type: subordinateModifyDN
+ds-cfg-referential-integrity-attribute-type: member
+ds-cfg-referential-integrity-attribute-type: uniqueMember
+
 dn: cn=Root DNs,cn=config
 objectClass: top
 objectClass: ds-cfg-root-dn-base
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index a7834a5..a4aa90b 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/opends/resource/schema/02-config.ldif
@@ -1626,6 +1626,22 @@
   NAME 'ds-cfg-save-config-on-successful-startup'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
   X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.474
+  NAME 'ds-cfg-referential-integrity-log-file'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.475
+  NAME 'ds-cfg-referential-integrity-update-interval'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.472
+  NAME 'ds-cfg-referential-integrity-attribute-type'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.473
+   NAME 'ds-cfg-referential-integrity-base-dn'
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+   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 )
@@ -2431,4 +2447,11 @@
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.162
   NAME 'ds-cfg-subject-equals-dn-certificate-mapper'
   SUP ds-cfg-certificate-mapper STRUCTURAL X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.122
+  NAME 'ds-cfg-referential-integrity-plugin' SUP ds-cfg-plugin
+  STRUCTURAL MUST ds-cfg-referential-integrity-attribute-type
+  MAY ( ds-cfg-referential-integrity-base-dn
+  $ ds-cfg-referential-integrity-update-interval
+  $ ds-cfg-referential-integrity-log-file )
+  X-ORIGIN 'OpenDS Directory Server' )
 
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
new file mode 100644
index 0000000..e10d4f5
--- /dev/null
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ! 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.
+ ! -->
+
+<adm:managed-object
+        name="referential-integrity-plugin"
+        plural-name="referential-integrity-plugins"
+        package="org.opends.server.admin.std"
+        extends="plugin"
+        xmlns:adm="http://www.opends.org/admin"
+        xmlns:ldap="http://www.opends.org/admin-ldap">
+  <adm:synopsis>
+    The <adm:user-friendly-name />
+    is used to enforce referential integrity on DN attribute types
+    specified in the plugin configuration. The values of these attribute types,
+    may reference entries that have been deleted by a delete operation or
+    renamed by a modify DN operation. The plugin will either remove stale
+    references to deleted entries or remove an old references and add a new
+    references to a renamed entries. The plugin allows the scope of this
+    referential check to be limited to a set of base DNs if desired.
+    It also can be configured to perform the referential
+    checking in background mode at specified intervals.
+  </adm:synopsis>
+
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:oid>1.3.6.1.4.1.26027.1.2.122</ldap:oid>
+      <ldap:name>ds-cfg-referential-integrity-plugin</ldap:name>
+      <ldap:superior>ds-cfg-plugin</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+
+  <adm:property-override name="plugin-class">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.plugins.ReferentialIntegrityPlugin
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+
+  <adm:property name="referential-integrity-attribute-type" mandatory="true"
+                multi-valued="true">
+    <adm:synopsis>
+      Specifies an attribute type to  process referential integrity checking on.
+      There must be at least one of these specified in the plugin configuration
+      and the syntax of the type must either be distinguished name
+      (1.3.6.1.4.1.1466.115.121.1.12) or name and optional uid
+      (1.3.6.1.4.1.1466.115.121.1.34).
+    </adm:synopsis>
+    <adm:description>
+      Specifies an attribute type to process referential integrity.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>T
+          The ds-cfg-referential-integrity attribute must be specified at least
+          once.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:attribute-type />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.472</ldap:oid>
+        <ldap:name>ds-cfg-referential-integrity-attribute-type</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+
+  <adm:property name="referential-integrity-base-dn" mandatory="false"
+                multi-valued="true">
+    <adm:synopsis>
+      Specifies a base DN to restrict the referential integrity
+      processing scope. If none of these are specified in the plugin
+      configuration, then the server's public naming contexts are used.
+    </adm:synopsis>
+    <adm:description>
+       Specifies the a base DN to restrict the referential integrity
+       processing scope.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          The scope will be to use all of the public naming contexts.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:dn />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.473</ldap:oid>
+        <ldap:name>ds-cfg-referential-integrity-base-dn</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+  <adm:property name="referential-integrity-log-file" mandatory="false"
+                multi-valued="false">
+    <adm:synopsis>
+      Specifies the log file location where the update records will be written
+      when the plugin is in background mode processing. The default location is
+      in the logs directory of the server instance, using the file name
+      "referint".
+    </adm:synopsis>
+    <adm:description>
+      Specifies the log file location where the update records will be written.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>logs/referint</adm:value>
+      </adm:defined>
+    </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.474</ldap:oid>
+        <ldap:name>ds-cfg-referential-integrity-log-file</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+  <adm:property name="referential-integrity-update-interval" mandatory="false"
+                multi-valued="false">
+    <adm:synopsis>
+      Specifies the interval, in seconds, when the referential integrity
+      background thread will wakeup and process new update records. If this
+      value is 0, then the updates are processed in foreground.
+    </adm:synopsis>
+    <adm:description>
+      Specifies the interval, in seconds, when the referential integrity update
+      thread will wakeup and process new update records.
+    </adm:description>
+     <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          0 seconds
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+   <adm:syntax>
+      <adm:duration base-unit="s" allow-unlimited="false" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.475</ldap:oid>
+        <ldap:name>ds-cfg-referential-integrity-update-interval</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+</adm:managed-object>
+
diff --git a/opendj-sdk/opends/src/messages/messages/plugin.properties b/opendj-sdk/opends/src/messages/messages/plugin.properties
index 4a76304..747d78b 100644
--- a/opendj-sdk/opends/src/messages/messages/plugin.properties
+++ b/opendj-sdk/opends/src/messages/messages/plugin.properties
@@ -317,4 +317,44 @@
  changes failed the attribute value uniqueness check
 SEVERE_ERR_PLUGIN_REFERINT_INVALID_PLUGIN_TYPE_81=An attempt was made to \
  register the Referential Integrity plugin to be invoked as a %s plugin.  This \
- plugin type is not allowed for this plugin
\ No newline at end of file
+ plugin type is not allowed for this plugin
+SEVERE_ERR_PLUGIN_REFERENT_CREATE_LOGFILE_82=An error occured during \
+   Referential Integity plugin initialization because log file creation \
+   failed:  %s
+SEVERE_ERR_PLUGIN_REFERENT_CLOSE_LOGFILE_83=An error occured closing the \
+  Referential Integrity plugin update logile: %s
+SEVERE_ERR_PLUGIN_REFERENT_REPLACE_LOGFILE_84=An error occured replacing the \
+  Referential Integrity plugin update logile: %s
+INFO_PLUGIN_REFERENT_LOGFILE_CHANGE_REQUIRES_RESTART_85=The file name that \
+  the Referential Integrity plugin logs changes to during background \
+  processing has been changed from %s to %s, but this change will not take \
+  effect until the server is restarted
+INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_UPDATE_INTERVAL_CHANGED_86=The \
+  Referential Integrity plugin background processing update interval has \
+  been changed from %s to %s, the new value will now be during background \
+  processing
+INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STOPPING_87=The Referential \
+  Integrity plugin background processing has been stopped
+INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STARTING_88=The Referential \
+  Integrity plugin has started background processing using the update \
+  interval %s
+SEVERE_ERR_PLUGIN_REFERENT_SEARCH_FAILED_89=The Referential \
+  Integrity plugin failed when performaing an internal search: %s
+SEVERE_ERR_PLUGIN_REFERENT_MODIFY_FAILED_90=The Referential \
+  Integrity plugin failed when performing an internal modify on entry %s: %s
+MILD_ERR_PLUGIN_REFERENT_CANNOT_DECODE_STRING_AS_DN_91=The Referential \
+  Integrity plugin failed to decode a entry DN from the update log: %s
+INFO_PLUGIN_REFERENT_SEARCH_NO_SUCH_OBJECT_92=The Referential Integrity \
+  plugin failed when performing a search because the base DN %s does not exist
+SEVERE_ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX_93=An error occured \
+  in the Referential Integrity plugin while attempting to configure the \
+  attribute type %s which has a syntax OID of %s. A Referential Integrity \
+  attribute type must have a syntax OID of either \
+  1.3.6.1.4.1.1466.115.121.1.12 (for the distinguished name syntax) or \
+  1.3.6.1.4.1.1466.115.121.1.34 (for the name and optional uid syntax)
+SEVERE_ERR_PLUGIN_REFERENT_SKIP_DELETE_PROCESSING_94=The Referential Integrity \
+  plugin will not process a post delete operation on entry %s because the core \
+  operation failed
+SEVERE_ERR_PLUGIN_REFERENT_SKIP_MODIFY_DN_PROCESSING_95=The Referential \
+  Integrity plugin will not process a post modify DN operation on entry %s \
+  because the core operation failed
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java b/opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
new file mode 100644
index 0000000..b748629
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
@@ -0,0 +1,829 @@
+/*
+ * 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.plugins;
+
+import org.opends.server.admin.std.server.ReferentialIntegrityPluginCfg;
+import org.opends.server.admin.std.meta.PluginCfgDefn;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.api.plugin.*;
+import org.opends.server.api.DirectoryThread;
+import org.opends.server.api.ServerShutdownListener;
+import org.opends.server.config.ConfigException;
+import org.opends.server.types.*;
+import org.opends.server.types.operation.SubordinateModifyDNOperation;
+import org.opends.server.types.operation.PostOperationModifyDNOperation;
+import org.opends.server.types.operation.PostOperationDeleteOperation;
+import org.opends.messages.Message;
+import static org.opends.messages.PluginMessages.*;
+import static org.opends.server.util.StaticUtils.*;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.loggers.debug.DebugTracer;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.server.schema.SchemaConstants.*;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * This class implements a Directory Server post operation plugin that performs
+ * Referential Integrity processing on successful delete and modify DN
+ * operations. The plugin uses a set of configuration criteria to determine
+ * what attribute types to check referential integrity on, and, the set of
+ * base DNs to search for entries that might need referential integrity
+ * processing. If none of these base DNs are specified in the configuration,
+ * then the public naming contexts are used as the base DNs by default.
+ *
+ * The plugin also has an option to process changes in background using
+ * a thread that wakes up periodically looking for change records in a log
+ * file.
+ *
+ **/
+public class ReferentialIntegrityPlugin
+        extends DirectoryServerPlugin<ReferentialIntegrityPluginCfg>
+        implements ConfigurationChangeListener<ReferentialIntegrityPluginCfg>,
+        ServerShutdownListener {
+
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  //Current plugin configuration.
+  private ReferentialIntegrityPluginCfg currentConfiguration;
+
+  //List of attribute types that will be checked during referential integrity
+  //processing.
+  private LinkedHashSet<AttributeType>
+          attributeTypes = new LinkedHashSet<AttributeType>();
+
+  //List of base DNs that limit the scope of the referential integrity checking.
+  private Set<DN> baseDNs = new LinkedHashSet<DN>();
+
+  //The update interval the background thread uses. If it is 0, then
+  //the changes are processed in foreground.
+  private long interval;
+
+  //The flag used by the background thread to check if it should exit.
+  private boolean stopRequested=false;
+
+  //The thread name.
+  private final String name="Referential Integrity Background Update Thread";
+
+  //The name of the logfile that the update thread uses to process change
+  //records. Defaults to "logs/referint", but can be changed in the
+  //configuration.
+  private String logFileName;
+
+  //The File class that logfile corresponds to.
+  private File logFile;
+
+  //The Thread class that the background thread corresponds to.
+  private Thread backGroundThread=null;
+
+  /**
+   * Used to save a map in the modifyDN operation attachment map that holds
+   * the old entry DNs and the new entry DNs related to a modify DN rename to
+   * new superior operation.
+   */
+
+  public static final String MODIFYDN_DNS="modifyDNs";
+
+  //The buffered reader that is used to read the log file by the background
+  //thread.
+  private BufferedReader reader;
+
+  //The buffered writer that is used to write update records in the log
+  //when the plugin is in background processing mode.
+  private BufferedWriter writer;
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void initializePlugin(Set<PluginType> pluginTypes,
+                                     ReferentialIntegrityPluginCfg pluginCfg)
+          throws ConfigException {
+
+    pluginCfg.addReferentialIntegrityChangeListener(this);
+    currentConfiguration = pluginCfg;
+    for (PluginType t : pluginTypes)
+      switch (t)  {
+        case POST_OPERATION_DELETE:
+        case POST_OPERATION_MODIFY_DN:
+        case SUBORDINATE_MODIFY_DN:
+          // These are acceptable.
+          break;
+        default:
+          throw new
+             ConfigException(ERR_PLUGIN_REFERINT_INVALID_PLUGIN_TYPE.
+                             get(t.toString()));
+      }
+    for(DN baseDN : pluginCfg.getReferentialIntegrityBaseDN())
+      baseDNs.add(baseDN);
+    //Iterate through attributes and check that each has a valid syntax
+    //before adding it to the list.
+    for(AttributeType type : pluginCfg.getReferentialIntegrityAttributeType()) {
+      if(!isAttributeSyntaxValid(type))
+        throw new
+          ConfigException(ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.
+                          get(type.getNameOrOID(),
+                              type.getSyntax().getSyntaxName()));
+      attributeTypes.add(type);
+    }
+    //Set up log file. Note: t is not allowed to change once the plugin
+    //is active.
+    setUpLogFile(pluginCfg.getReferentialIntegrityLogFile());
+    interval=pluginCfg.getReferentialIntegrityUpdateInterval();
+    //Set up background processing if interval > 0.
+    if(interval > 0)
+      setUpBackGroundProcessing();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConfigChangeResult applyConfigurationChange(
+          ReferentialIntegrityPluginCfg newConfiguration) {
+    ResultCode resultCode          = ResultCode.SUCCESS;
+    boolean           adminActionRequired = false;
+    ArrayList<Message> messages            = new ArrayList<Message>();
+
+    LinkedHashSet<DN> newConfiguredBaseDNs = new LinkedHashSet<DN>();
+    //Load base DNs from new configuration.
+    for(DN baseDN : newConfiguration.getReferentialIntegrityBaseDN())
+      newConfiguredBaseDNs.add(baseDN);
+    LinkedHashSet<AttributeType> newAttributeTypes =
+            new LinkedHashSet<AttributeType>();
+    //Load attribute types from new configuration.
+    for(AttributeType type :
+            newConfiguration.getReferentialIntegrityAttributeType())
+      newAttributeTypes.add(type);
+    String newLogFileName=newConfiguration.getReferentialIntegrityLogFile();
+    //User is not allowed to change the logfile name, append a message that the
+    //server needs restarting for change to take effect.
+    if(!logFileName.equals(newLogFileName)) {
+      adminActionRequired=true;
+      messages.add(INFO_PLUGIN_REFERENT_LOGFILE_CHANGE_REQUIRES_RESTART.
+                   get(logFileName, newLogFileName));
+    }
+    //Switch to the new lists.
+    baseDNs = newConfiguredBaseDNs;
+    attributeTypes = newAttributeTypes;
+    long newInterval=newConfiguration.getReferentialIntegrityUpdateInterval();
+    //If the interval has changed, process that change. The change might start
+    //or stop the background processing thread.
+    if(newInterval != interval)
+      processIntervalChange(newInterval, messages);
+    currentConfiguration = newConfiguration;
+    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isConfigurationChangeAcceptable(
+          ReferentialIntegrityPluginCfg configuration,
+          List<Message> unacceptableReasons) {
+
+    boolean configAcceptable = true;
+    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType()) {
+      switch (pluginType)  {
+        case POSTOPERATIONDELETE:
+        case POSTOPERATIONMODIFYDN:
+        case SUBORDINATEMODIFYDN:
+          // These are acceptable.
+          break;
+        default:
+          unacceptableReasons.add(ERR_PLUGIN_REFERINT_INVALID_PLUGIN_TYPE.
+                                  get(pluginType.toString()));
+          configAcceptable = false;
+      }
+      if(!configAcceptable)
+           break;
+    }
+    //Iterate through attributes and check that each has a valid syntax
+    for(AttributeType type :
+            configuration.getReferentialIntegrityAttributeType())
+      if(!isAttributeSyntaxValid(type)) {
+        unacceptableReasons.add(ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.
+                                get(type.getNameOrOID(),
+                                    type.getSyntax().getSyntaxName()));
+        configAcceptable = false;
+        break;
+      }
+    return configAcceptable;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  public PostOperationPluginResult
+         doPostOperation(PostOperationModifyDNOperation
+          modifyDNOperation) {
+
+    //If the core operation failed, then append an error message and skip
+    //processing.
+    if(modifyDNOperation.getResultCode() != ResultCode.SUCCESS)
+         return  PostOperationPluginResult.SUCCESS;
+ //     modifyDNOperation.appendErrorMessage(
+ ////             ERR_PLUGIN_REFERENT_SKIP_MODIFY_DN_PROCESSING.
+  //                    get(modifyDNOperation.getEntryDN().toString()));
+    else if(modifyDNOperation.getNewSuperior() == null) {
+      //Core operation was a rename entry.
+      DN oldEntryDN=modifyDNOperation.getOriginalEntry().getDN();
+      DN newEntryDN=modifyDNOperation.getUpdatedEntry().getDN();
+      Map<DN,DN> modDNmap=new LinkedHashMap<DN,DN>();
+      modDNmap.put(oldEntryDN, newEntryDN);
+      processModifyDN(modDNmap,(interval != 0));
+    } else {
+      //Core operation is a move to a new superior, use the save map of
+      //old DNs and new DNs from the operation attachment.
+      //
+      //This cast causes an unchecked cast warning, suppress it since the
+      //cast is ok.
+      Map<DN,DN> modDNmap =
+              (Map<DN, DN>) modifyDNOperation.getAttachment(MODIFYDN_DNS);
+      processModifyDN(modDNmap, (interval != 0));
+    }
+    return  PostOperationPluginResult.SUCCESS;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public PostOperationPluginResult
+  doPostOperation(PostOperationDeleteOperation deleteOperation) {
+    //If the core operation failed, then append an error message and skip
+    //processing.
+    if(deleteOperation.getResultCode() != ResultCode.SUCCESS)
+       return  PostOperationPluginResult.SUCCESS;
+ //     deleteOperation.appendErrorMessage(
+ //             ERR_PLUGIN_REFERENT_SKIP_DELETE_PROCESSING.
+  //                    get(deleteOperation.getEntryDN().toString()));
+    else
+      processDelete(deleteOperation.getEntryDN(), (interval != 0));
+    return  PostOperationPluginResult.SUCCESS;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  public SubordinateModifyDNPluginResult processSubordinateModifyDN(
+          SubordinateModifyDNOperation modifyDNOperation, Entry oldEntry,
+          Entry newEntry, List<Modification> modifications) {
+    //This cast gives an unchecked cast warning, suppress it since the cast
+    //is ok.
+    Map<DN,DN>modDNmap=
+            (Map<DN, DN>) modifyDNOperation.getAttachment(MODIFYDN_DNS);
+    if(modDNmap == null) {
+      //First time through, create the map and set it in the operation
+      //attachment.
+      modDNmap=new LinkedHashMap<DN,DN>();
+      modifyDNOperation.setAttachment(MODIFYDN_DNS, modDNmap);
+    }
+    modDNmap.put(oldEntry.getDN(), newEntry.getDN());
+    return SubordinateModifyDNPluginResult.SUCCESS;
+  }
+
+
+  /**
+   * Verify that the specified attribute has either a distinguished name syntax
+   * or "name and optional UID" syntax.
+   *
+   * @param attribute The attribute to check the syntax of.
+   *
+   * @return  Returns <code>true</code> if the attribute has a valid syntax.
+   *
+   */
+  private boolean isAttributeSyntaxValid(AttributeType attribute) {
+    return (attribute.getSyntaxOID().equals(SYNTAX_DN_OID) ||
+            attribute.getSyntaxOID().equals(SYNTAX_NAME_AND_OPTIONAL_UID_OID));
+  }
+
+  /**
+   * Process the specifed new interval value. This processing depends on what
+   * the current interval value is and new value will be. The values have been
+   * checked for equality at this point and are not equal.
+   *
+   * If the old interval is 0, then the server is in foreground mode and
+   * the background thread needs to be started using the new interval value.
+   *
+   * If the new interval value is 0, the the server is in background mode
+   * and the the background thread needs to be stopped.
+   *
+   * If the user just wants to change the interval value, the background thread
+   * needs to be interrupted so that it can use the new interval value.
+   *
+   * @param newInterval The new interval value to use.
+   *
+   * @param msgs An array list of messages that thread stop and start messages
+   *             can be added to.
+   *
+   */
+  private void processIntervalChange(long newInterval,
+                                     ArrayList<Message> msgs) {
+    if(interval == 0) {
+      DirectoryServer.registerShutdownListener(this);
+      interval=newInterval;
+      msgs.add(INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STARTING.
+              get(Long.toString(interval)));
+      setUpBackGroundProcessing();
+    } else if(newInterval == 0) {
+      Message message=
+              INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STOPPING.get();
+      msgs.add(message);
+      processServerShutdown(message);
+      interval=newInterval;
+    } else {
+      interval=newInterval;
+      backGroundThread.interrupt();
+      msgs.add(
+             INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_UPDATE_INTERVAL_CHANGED.
+                      get(Long.toString(interval),Long.toString(newInterval)));
+    }
+  }
+
+  /**
+   * Process a modify DN post operation using the specified map of old and new
+   * entry DNs.  The boolean "log" is used to determine if the  map
+   * is written to the log file for the background thread to pick up. If the
+   * map is to be processed in foreground, than each base DN or public
+   * naming context (if the base DN configuration is empty) is processed.
+   *
+   * @param modDNMap  The map of old entry and new entry DNs from the modify
+   *                  DN operation.
+   *
+   * @param log Set to <code>true</code> if the map should be written to a log
+   *            file so that the background thread can process the changes at
+   *            a later time.
+   *
+   */
+  private void processModifyDN(Map<DN, DN> modDNMap, boolean log) {
+    if(modDNMap != null) {
+      if(log)
+        writeLog(modDNMap);
+      else
+        for(DN baseDN : getBaseDNsToSearch())
+          doBaseDN(baseDN, modDNMap);
+    }
+  }
+
+  /**
+   * Used by both the background thread and the delete post operation to
+   * process a delete operation on the specified entry DN.  The
+   * boolean "log" is used to determine if the DN is written to the log file
+   * for the background thread to pick up. This value is set to false if the
+   * background thread is processing changes. If this method is being called
+   * by a delete post operation, then setting the "log" value to false will
+   * cause the DN to be processed in foreground
+   *
+   * If the DN is to be processed, than each base DN or public naming
+   * context (if the base DN configuration is empty) is is checked to see if
+   * entries under it contain references to the deleted entry DN that need
+   * to be removed.
+   *
+   * @param entryDN  The DN of the deleted entry.
+   *
+   * @param log Set to <code>true</code> if the DN should be written to a log
+   *            file so that the background thread can process the change at
+   *            a later time.
+   *
+   */
+  private void processDelete(DN entryDN, boolean log) {
+    if(log)
+      writeLog(entryDN);
+    else
+      for(DN baseDN : getBaseDNsToSearch())
+        searchBaseDN(baseDN, entryDN, null);
+  }
+
+  /**
+   * Used by the background thread to process the specified old entry DN and
+   * new entry DN. Each base DN or public naming context (if the base DN
+   * configuration is empty) is checked to see  if they contain entries with
+   * references to the old entry DN that need to be changed to the new entry DN.
+   *
+   * @param oldEntryDN  The entry DN before the modify DN operation.
+   *
+   * @param newEntryDN The entry DN after the modify DN operation.
+   *
+   */
+  private void processModifyDN(DN oldEntryDN, DN newEntryDN) {
+    for(DN baseDN : getBaseDNsToSearch())
+      searchBaseDN(baseDN, oldEntryDN, newEntryDN);
+  }
+
+  /**
+   * Return a set of DNs that are used to search for references under. If the
+   * base DN configuration set is empty, then the public naming contexts
+   * are used.
+   *
+   * @return A set of DNs to use in the reference searches.
+   *
+   */
+  private Set<DN>
+  getBaseDNsToSearch() {
+    if(baseDNs.isEmpty())
+      return DirectoryServer.getPublicNamingContexts().keySet();
+    else
+      return baseDNs;
+  }
+
+  /**
+   * Search a base DN using a filter built from the configured attribute
+   * types and the specified old entry DN. For each entry that is found from
+   * the search, delete the old entry DN from the entry. If the new entry
+   * DN is not null, then add it to the entry.
+   *
+   * @param baseDN  The DN to base the search at.
+   *
+   * @param oldEntryDN The old entry DN that needs to be deleted or replaced.
+   *
+   * @param newEntryDN The new entry DN that needs to be added. May be null
+   *                   if the original operation was a delete.
+   *
+   */
+  private void
+  searchBaseDN(DN baseDN, DN oldEntryDN, DN newEntryDN) {
+    HashSet<SearchFilter> componentFilters=new HashSet<SearchFilter>();
+    //Build an equality search with all of the configured attribute types
+    //and the old entry DN.
+    for(AttributeType attributeType : attributeTypes)
+      componentFilters.add(SearchFilter.createEqualityFilter(attributeType,
+              new AttributeValue(attributeType, oldEntryDN.toString())));
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    InternalSearchOperation operation = conn.processSearch(baseDN,
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+            SearchFilter.createORFilter(componentFilters),
+            null);
+    switch (operation.getResultCode()) {
+      case SUCCESS:
+        break;
+      case NO_SUCH_OBJECT:
+        Message message=
+                INFO_PLUGIN_REFERENT_SEARCH_NO_SUCH_OBJECT.
+                                                         get(baseDN.toString());
+        logError(message);
+        return;
+      default:
+        Message message1 = ERR_PLUGIN_REFERENT_SEARCH_FAILED.
+                get(String.valueOf(operation.getErrorMessage()));
+        logError(message1);
+        return;
+    }
+    for (SearchResultEntry entry : operation.getSearchEntries())
+      deleteAddAttributesEntry(entry, oldEntryDN, newEntryDN);
+  }
+
+  /**
+   * This method is used in foreground processing of a modify DN operation.
+   * It uses the specified map to perform base DN searching for each map
+   * entry. The key is the old entry DN and the value is the
+   * new entry DN.
+   *
+   * @param baseDN The DN to base the search at.
+   *
+   * @param modifyDNmap The map containing the modify DN old and new entry DNs.
+   *
+   */
+  private void
+  doBaseDN(DN baseDN, Map<DN,DN> modifyDNmap) {
+    for(Map.Entry<DN,DN> mapEntry: modifyDNmap.entrySet())
+      searchBaseDN(baseDN, mapEntry.getKey(), mapEntry.getValue());
+  }
+
+  /**
+   * For each attribute type, delete the specified old entry DN and
+   * optionally add the specified new entry DN if the DN is not null.
+   * The specified entry is used to see if it contains each attribute type so
+   * those types that the entry contains can be modified. An internal modify
+   * is performed to change the entry.
+   *
+   * @param e The entry that contains the old references.
+   *
+   * @param oldEntryDN The old entry DN to remove references to.
+   *
+   * @param newEntryDN The new entry DN to add a reference to, if it is not
+   *                   null.
+   *
+   */
+  private void
+  deleteAddAttributesEntry(Entry e, DN oldEntryDN, DN newEntryDN) {
+    LinkedList<Modification> mods = new LinkedList<Modification>();
+    DN entryDN=e.getDN();
+    for(AttributeType type : attributeTypes) {
+      if(e.hasAttribute(type)) {
+              AttributeValue deleteValue=
+                                new AttributeValue(type, oldEntryDN.toString());
+        LinkedHashSet<AttributeValue> deleteValues=
+                                            new LinkedHashSet<AttributeValue>();
+        deleteValues.add(deleteValue);
+        mods.add(new Modification(ModificationType.DELETE,
+                new Attribute(type, type.getNameOrOID(), deleteValues)));
+        //If the new entry DN exists, create an ADD modification for it.
+        if(newEntryDN != null) {
+          LinkedHashSet<AttributeValue> addValues=
+                                            new LinkedHashSet<AttributeValue>();
+          AttributeValue addValue=
+                                new AttributeValue(type, newEntryDN.toString());
+          addValues.add(addValue);
+          mods.add(new Modification(ModificationType.ADD,
+                  new Attribute(type, type.getNameOrOID(), addValues)));
+        }
+      }
+    }
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    ModifyOperation modifyOperation =
+            conn.processModify(entryDN, mods);
+    if(modifyOperation.getResultCode() != ResultCode.SUCCESS)
+      logError(ERR_PLUGIN_REFERENT_MODIFY_FAILED.get(entryDN.toString(),
+                      String.valueOf(modifyOperation.getErrorMessage())));
+  }
+
+  /**
+   * Sets up the log file that the plugin can write update recored to and
+   * the background thread can use to read update records from. The specifed
+   * log file name is the name to use for the file. If the file exists from
+   * a previous run, use it.
+   *
+   * @param logFileName The name of the file to use, may be absolute.
+   *
+   * @throws ConfigException If a new file cannot be created if needed.
+   *
+   */
+  private void setUpLogFile(String logFileName)
+          throws ConfigException {
+
+    this.logFileName=logFileName;
+    logFile=getFileForPath(logFileName);
+    try {
+      if(!logFile.exists()) logFile.createNewFile();
+    } catch (IOException io) {
+      Message message=
+              ERR_PLUGIN_REFERENT_CREATE_LOGFILE.get(io.getMessage());
+      throw new ConfigException(message);
+    }
+  }
+
+  /**
+   * Sets up a buffered writer that the plugin can use to write update records
+   * with.
+   *
+   * @throws IOException If a new file writer cannot be created.
+   *
+   */
+  private void setupWriter() throws IOException {
+    writer=new BufferedWriter(new FileWriter(logFile, true));
+  }
+
+
+  /**
+   * Sets up a buffered reader that the background thread can use to read
+   * update records with.
+   *
+   * @throws IOException If a new file reader cannot be created.
+   *
+   */
+  private void setupReader() throws IOException {
+    reader=new BufferedReader(new FileReader(logFile));
+  }
+
+  /**
+   * Write the specified map of old entry and new entry DNs to the log
+   * file. Each entry of the map is a line in the file, the key is the old
+   * entry normalized DN and the value is the new entry normalized DN.
+   * The DNs are separated by the tab character. This map is related to a
+   * modify DN operation.
+   *
+   * @param modDNmap The map of old entry and new entry DNs.
+   *
+   */
+  private void writeLog(Map<DN,DN> modDNmap) {
+    synchronized(logFile) {
+      try  {
+        setupWriter();
+        for(Map.Entry<DN,DN> mapEntry : modDNmap.entrySet()) {
+          writer.write(mapEntry.getKey().toNormalizedString() + "\t" +
+                  mapEntry.getValue().toNormalizedString());
+          writer.newLine();
+        }
+        writer.flush();
+        writer.close();
+      } catch (IOException io) {
+        Message message =
+                ERR_PLUGIN_REFERENT_CLOSE_LOGFILE.get(io.getMessage());
+        logError(message);
+      }
+    }
+  }
+
+  /**
+   * Write the specified entry DN to the log file. This entry DN is related to
+   * a delete operation.
+   *
+   * @param deletedEntryDN The DN of the deleted entry.
+   *
+   */
+  private void writeLog(DN deletedEntryDN) {
+    synchronized(logFile) {
+      try {
+        setupWriter();
+        writer.write(deletedEntryDN.toNormalizedString());
+        writer.newLine();
+        writer.flush();
+        writer.close();
+      } catch (IOException io) {
+        Message message =
+                ERR_PLUGIN_REFERENT_CLOSE_LOGFILE.get(io.getMessage());
+        logError(message);
+      }
+    }
+  }
+
+  /**
+   * Process all of the records in the log file. Each line of the file is read
+   * and parsed to determine if it was a delete operation (a single normalized
+   * DN) or a modify DN operation (two normalized DNs separated by a tab). The
+   * corresponding operation method is called to perform the referential
+   * integrity processing as though the operation was just processed. After
+   * all of the records in log file have been processed, the log file is
+   * cleared so that new records can be added.
+   *
+   */
+  private void processLog() {
+    synchronized(logFile) {
+      try {
+        if(logFile.length() == 0)
+          return;
+        setupReader();
+        String line;
+        while((line=reader.readLine()) != null) {
+          try {
+            String[] a=line.split("[\t]");
+            DN origDn = DN.decode(a[0]);
+            //If there is only a single DN string than it must be a delete.
+            if(a.length == 1) {
+              processDelete(origDn, false);
+            } else {
+              DN movedDN=DN.decode(a[1]);
+              processModifyDN(origDn, movedDN);
+            }
+          } catch (DirectoryException ex) {
+            //This exception should rarely happen since the plugin wrote the DN
+            //strings originally.
+            Message message=
+                    ERR_PLUGIN_REFERENT_CANNOT_DECODE_STRING_AS_DN.
+                                                           get(ex.getMessage());
+            logError(message);
+          }
+        }
+        reader.close();
+        logFile.delete();
+        logFile.createNewFile();
+      } catch (IOException io) {
+        Message message =
+                ERR_PLUGIN_REFERENT_REPLACE_LOGFILE.get(io.getMessage());
+        logError(message);
+      }
+    }
+  }
+
+  /**
+   * Return the listener name.
+   *
+   * @return The name of the listener.
+   *
+   */
+  public String getShutdownListenerName() {
+    return name;
+  }
+
+  /**
+   * Process a server shutdown. If the background thread is running it needs
+   * to be interrupted so it can read the stop request variable and exit.
+   *
+   * @param reason The reason message for the shutdown.
+   *
+   */
+  public void processServerShutdown(Message reason)
+  {
+    stopRequested = true;
+
+    // Wait for back ground thread to terminate
+    while (backGroundThread != null && backGroundThread.isAlive()) {
+      try {
+        // Interrupt if its sleeping
+        backGroundThread.interrupt();
+        backGroundThread.join();
+      }
+      catch (InterruptedException ex) {
+        //Expected.
+      }
+    }
+    DirectoryServer.deregisterShutdownListener(this);
+    backGroundThread=null;
+  }
+
+
+  /**
+   * Returns the interval time converted to milliseconds.
+   *
+   * @return The interval time for the background thread.
+   */
+  private long getInterval() {
+    return interval * 1000;
+  }
+
+  /**
+   * Sets up background processing of referential integrity by creating a
+   * new background thread to process updates.
+   *
+   */
+  private void setUpBackGroundProcessing()  {
+    if(backGroundThread == null) {
+      DirectoryServer.registerShutdownListener(this);
+      stopRequested = false;
+      backGroundThread = new BackGroundThread();
+      backGroundThread.start();
+    }
+  }
+
+
+  /**
+   * Used by the background thread to determine if it should exit.
+   *
+   * @return Returns <code>true</code> if the background thread should exit.
+   *
+   */
+  private boolean isShuttingDown()  {
+    return stopRequested;
+  }
+
+  /**
+   * The background referential integrity processing thread. Wakes up after
+   * sleeping for a configurable interval and checks the log file for update
+   * records.
+   *
+   */
+  private class BackGroundThread extends DirectoryThread {
+
+    /**
+     * Constructor for the background thread.
+     */
+    public
+    BackGroundThread() {
+      super(name);
+    }
+
+    /**
+     * Run method for the background thread.
+     */
+    public void run() {
+      while(!isShuttingDown())  {
+        try {
+          sleep(getInterval());
+        } catch(InterruptedException e) {
+          continue;
+        } catch(Exception e) {
+          if (debugEnabled()) {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+        }
+        processLog();
+      }
+    }
+  }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
index e260e8c..960b055 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -309,6 +309,14 @@
 replace: ds-cfg-unique-attribute-type
 ds-cfg-unique-attribute-type: oncRpcNumber
 
+dn: cn=Referential Integrity,cn=Plugins,cn=config
+changeType: modify
+replace: ds-cfg-plugin-enabled
+ds-cfg-plugin-enabled: true
+-
+add: ds-cfg-referential-integrity-base-dn
+ds-cfg-referential-integrity-base-dn: dc=does,dc=not,dc=exist
+
 dn: cn=Test Unique Attribute,cn=Plugins,cn=config
 changeType: add
 objectClass: top
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
index 0556382..17af69e 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
@@ -157,13 +157,17 @@
          new InternalClientConnection(DN.decode("uid=proxy.user,o=test"));
   }
 
+
+
   /**
    * Invokes a number of operation methods on the provided modify operation
-   * for which all processing has been completed.
+   * for which all processing has been completed. This method is used for
+   * tests that bypass the referential integrity plugin for whatever reason.
    *
    * @param  modifyDNOperation  The operation to be tested.
    */
-  private void examineCompletedOperation(ModifyDNOperation modifyDNOperation)
+  private void
+  examineCompletedOPNoExtraPluginCounts(ModifyDNOperation modifyDNOperation)
   {
     assertTrue(modifyDNOperation.getProcessingStartTime() > 0);
     assertTrue(modifyDNOperation.getProcessingStopTime() > 0);
@@ -177,6 +181,29 @@
     assertEquals(InvocationCounterPlugin.getPostResponseCount(), 1);
   }
 
+
+  /**
+   * Invokes a number of operation methods on the provided modify operation
+   * for which all processing has been completed.  The counters
+   * postResponseCount and preParseCount are incremented twice when
+   * referential integrity plugin is enabled.
+   *
+   * @param  modifyDNOperation  The operation to be tested.
+   */
+  private void examineCompletedOperation(ModifyDNOperation modifyDNOperation)
+  {
+    assertTrue(modifyDNOperation.getProcessingStartTime() > 0);
+    assertTrue(modifyDNOperation.getProcessingStopTime() > 0);
+    assertTrue(modifyDNOperation.getProcessingTime() >= 0);
+    assertNotNull(modifyDNOperation.getResponseLogElements());
+
+    assertEquals(InvocationCounterPlugin.getPreParseCount(), 2);
+    assertEquals(InvocationCounterPlugin.getPreOperationCount(), 1);
+    assertEquals(InvocationCounterPlugin.getPostOperationCount(), 1);
+    ensurePostReponseHasRun();
+    assertEquals(InvocationCounterPlugin.getPostResponseCount(), 2);
+  }
+
   /**
    * Invokes a number of operation methods on the provided modify operation
    * for which the pre-operation plugin was not called.
@@ -512,7 +539,7 @@
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
 
-    examineCompletedOperation(modifyDNOperation);
+    examineCompletedOPNoExtraPluginCounts(modifyDNOperation);
     InvocationCounterPlugin.resetAllCounters();
 
     modifyDNOperation =
@@ -537,7 +564,7 @@
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
 
-    examineCompletedOperation(modifyDNOperation);
+    examineCompletedOPNoExtraPluginCounts(modifyDNOperation);
   }
 
   @Test
@@ -571,7 +598,7 @@
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
 
-    examineCompletedOperation(modifyDNOperation);
+    examineCompletedOPNoExtraPluginCounts(modifyDNOperation);
     InvocationCounterPlugin.resetAllCounters();
 
     modifyDNOperation =
@@ -596,7 +623,7 @@
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
 
-    examineCompletedOperation(modifyDNOperation);
+    examineCompletedOPNoExtraPluginCounts(modifyDNOperation);
   }
 
   @Test
@@ -631,7 +658,7 @@
       assertTrue(attribute.hasValue(new AttributeValue(attribute.getAttributeType(), "Aaccf Amar")));
     }
 
-    examineCompletedOperation(modifyDNOperation);
+    examineCompletedOPNoExtraPluginCounts(modifyDNOperation);
     InvocationCounterPlugin.resetAllCounters();
 
     modifyDNOperation =
@@ -658,8 +685,7 @@
     {
       assertTrue(attribute.hasValue(new AttributeValue(attribute.getAttributeType(), "user.0")));
     }
-
-    examineCompletedOperation(modifyDNOperation);
+    examineCompletedOPNoExtraPluginCounts(modifyDNOperation);
   }
 
   @Test
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
new file mode 100644
index 0000000..a7c1366
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
@@ -0,0 +1,997 @@
+/*
+ * 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.plugins;
+
+import org.testng.annotations.*;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.config.ConfigException;
+import org.opends.server.admin.std.server.ReferentialIntegrityPluginCfg;
+import org.opends.server.admin.std.meta.ReferentialIntegrityPluginCfgDefn;
+import org.opends.server.admin.server.AdminTestCaseUtils;
+import org.opends.server.api.plugin.PluginType;
+import org.opends.server.api.Group;
+import static org.testng.Assert.assertEquals;
+import org.opends.server.core.*;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.types.*;
+
+import java.util.LinkedList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.HashSet;
+
+/**
+ * Unit test to test Referential Integrity plugin.
+ */
+
+public class ReferentialIntegrityPluginTestCase extends PluginTestCase  {
+
+  //Config DNs and attributes.
+  private DN configDN;
+  private String dsConfigAttrType="ds-cfg-referential-integrity-attribute-type";
+  private String dsConfigBaseDN="ds-cfg-referential-integrity-base-dn";
+  private String dsConfigUpdateInterval=
+                               "ds-cfg-referential-integrity-update-interval";
+
+  //Suffixes to use for non-public naming context tests.
+  private String exSuffix="dc=example,dc=com";
+  private String testSuffix="o=test";
+
+  //dc=example,dc=com entries.
+  private String user1="uid=user.1, ou=People, ou=dept," + exSuffix;
+  private String user2="uid=user.2, ou=People, ou=dept," + exSuffix;
+  private String user3="uid=user.3, ou=People, ou=dept," + exSuffix;
+
+  //Test entry to use for rename tests.
+  private String tuser1="uid=user.1, ou=People, ou=dept," + testSuffix;
+
+  //Old superior, new superior and new RDN for move tree tests.
+  private String newSuperior="ou=moved dept," + exSuffix;
+  private String oldSuperior="ou=people, ou=dept," + exSuffix;
+  private String newRdn="ou=moved people";
+
+  //DNs to verfiy that the moved tree test worked.
+  private String user1_moved= "uid=user.1," + newRdn + ',' +newSuperior;
+  private String user2_moved= "uid=user.2," + newRdn + ',' + newSuperior;
+  private String user3_moved= "uid=user.3," + newRdn + ',' + newSuperior;
+
+  //DN to test that the rename test worked.
+  private String tuser1_rename=
+                              "cn=new user.1, ou=People, ou=dept," + testSuffix;
+  private String tuser1_rdn="cn=new user.1";
+
+  //Test DNs to add to various groups.
+  private String tuser2="uid=user.2, ou=People, ou=dept," + testSuffix;
+  private String tuser3="uid=user.3, ou=People, ou=dept," + testSuffix;
+
+  //Groups to use for member and uniquemember attrbutes in dc=example, dc=com
+  //suffix.
+  private String group = "cn=group, ou=groups," + exSuffix;
+  private String ugroup = "cn=group, ou=unique groups," + exSuffix;
+
+  //DN to use for seeAlso attrbutes.
+  private String spPerson = "cn=special person, ou=Special People," + exSuffix;
+
+  //Same as above but for o=test suffix.
+  private String tgroup = "cn=group, ou=groups," + testSuffix;
+  private String tugroup = "cn=group, ou=unique groups," + testSuffix;
+  private String tspPerson =
+                         "cn=special person, ou=Special People," + testSuffix;
+
+
+  /**
+   * Test that a move to a new superior changes the correct entries under
+   * the correct suffixes.
+   *
+   * @throws Exception If an unexpected result is returned.
+   *
+   */
+  @Test()
+  public void testModDNMoveTree() throws Exception {
+    //Add attributes interested in: member, uniquemember, seealso.
+    replaceAttrEntry(configDN, dsConfigAttrType,"member");
+    addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso");
+    //Add suffixes to make referential changes under:
+    //o=test, and o=group, ou=unique groups, dc=example, dc=com
+    replaceAttrEntry(configDN, dsConfigBaseDN, testSuffix);
+    addAttrEntry(configDN, dsConfigBaseDN, ugroup);
+    //Add DNs to groups and special entries
+    addAttrEntry(DN.decode(tgroup), "member", user1, user2, user3);
+    addAttrEntry(DN.decode(tugroup), "uniquemember", user1, user2, user3);
+    addAttrEntry(DN.decode(ugroup), "uniquemember", user1, user2, user3);
+    addAttrEntry(DN.decode(spPerson), "seealso", user1, user2, user3);
+    //Perform the move.
+    doModDN(oldSuperior, newRdn, newSuperior);
+    //This group under the suffix all DNs should be moved.
+    isMember(tgroup, true, user1_moved, user2_moved, user3_moved);
+    //This group under a suffix all DNs should be moved.
+    isAttributeValueEntry(tugroup, true, "uniquemember",
+                          user1_moved, user2_moved, user3_moved);
+     //This group under a suffix all DNs should be moved.
+    isAttributeValueEntry(ugroup, true, "uniquemember",
+                          user1_moved, user2_moved, user3_moved);
+     //This group  not under a suffix, old entries should exist.
+    isAttributeValueEntry(spPerson, true,"seealso",
+                          user1, user2, user3);
+  }
+
+   /**
+   * Test that a rename  changes the correct entries under
+   * the correct suffixes.
+   *
+   * @throws Exception If an unexpected result is returned.
+    *
+   */
+
+  @Test()
+  public void testModDNMoveEntry() throws Exception {
+    //Add attributes interested in: member, uniquemember, seealso.
+    replaceAttrEntry(configDN, dsConfigAttrType,"member");
+    addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso");
+    //Add suffixes to make referential changes under:
+    //dc=example,dc=com and o=group, ou=unique groups, o=test
+    replaceAttrEntry(configDN, dsConfigBaseDN, exSuffix);
+    addAttrEntry(configDN, dsConfigBaseDN, tugroup);
+    //Add DNs to groups and special entry
+    addAttrEntry(DN.decode(group), "member", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(ugroup), "uniquemember", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(tugroup), "uniquemember", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(tspPerson), "seealso", tuser1, tuser2, tuser3);
+    //Perform rename.
+    doModDN(tuser1,tuser1_rdn, null);
+    //Verify that the changes were made.
+    isMember(group, true, tuser1_rename, tuser2, tuser3);
+    isAttributeValueEntry(ugroup, true, "uniquemember",
+                          tuser1_rename, tuser2, tuser3);
+    isAttributeValueEntry(tugroup, true, "uniquemember",
+                          tuser1_rename, tuser2, tuser3);
+    isAttributeValueEntry(tspPerson, true,"seealso",
+                          tuser1, tuser2, tuser3);
+  }
+
+
+  /**
+   * Test a delete using public naming contexts as base DNs.
+   *
+   * @throws Exception If an unexpected result is returned.
+   *
+   */
+  @Test()
+  public void testReferentialDelete() throws Exception {
+   replaceAttrEntry(configDN, dsConfigAttrType,"member");
+   addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3);
+   deleteEntries(tuser1, tuser2, tuser3);
+   isMember(tgroup, false, tuser1, tuser2, tuser3);
+  }
+
+
+  /**
+   * Test that delete using public naming context works in both background
+   * processing (set interval to 1 and wait 2 seconds) and forground. The
+   * changes are made without restarting the server.
+   *
+   * @throws Exception If an unexpected result happens.
+   *
+   */
+  @Test()
+   public void testReferentialDeleteBackGround() throws Exception {
+    replaceAttrEntry(configDN, dsConfigAttrType,"member");
+    //Set interval to 1 second, this should start the background thread
+    //and put the plugin in background mode.
+    replaceAttrEntry(configDN, dsConfigUpdateInterval,"1 seconds");
+    addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3);
+    deleteEntries(tuser1, tuser2, tuser3);
+    //Wait two seconds and then check the group.
+    Thread.sleep(2000);
+    isMember(tgroup, false, tuser1, tuser2, tuser3);
+    //Change the interval to zero seconds, this should stop the background
+    //thread.
+    replaceAttrEntry(configDN, dsConfigUpdateInterval,"0 seconds");
+    addEntries(tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3);
+    deleteEntries(tuser1, tuser2, tuser3);
+    //Don't wait, the changes should be there.
+    isMember(tgroup, false, tuser1, tuser2, tuser3);
+   }
+
+  /**
+   * Test delete using multiple attribute types and public naming contexts.
+   *
+   * @throws Exception If an unexpected result happened.
+   *
+   */
+  @Test()
+   public void testReferentialDeleteAttrs() throws Exception {
+    replaceAttrEntry(configDN, dsConfigAttrType,"member");
+    addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso");
+    addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(tugroup), "uniquemember", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(tspPerson), "seealso", tuser1, tuser2, tuser3);
+    deleteEntries(tuser1, tuser2, tuser3);
+    isMember(tgroup, false, tuser1, tuser2, tuser3);
+    isAttributeValueEntry(tugroup, false, "uniquemember",
+                          tuser1, tuser2, tuser3);
+    isAttributeValueEntry(tspPerson, false,"seealso",
+                          tuser1, tuser2, tuser3);
+   }
+
+  /**
+   * Check delete with multiple attribute types and multiple suffixes.
+   *
+   * @throws Exception If an unexpected result happened.
+   *
+   */
+  @Test()
+   public void testReferentialDeleteAttrsSuffix() throws Exception {
+    replaceAttrEntry(configDN, dsConfigAttrType,"member");
+    addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso");
+    replaceAttrEntry(configDN, dsConfigBaseDN, exSuffix);
+    addAttrEntry(configDN, dsConfigBaseDN, tugroup);
+    addAttrEntry(DN.decode(group), "member", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(ugroup), "uniquemember", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(tugroup), "uniquemember", tuser1, tuser2, tuser3);
+    addAttrEntry(DN.decode(tspPerson), "seealso", tuser1, tuser2, tuser3);
+    deleteEntries(tuser1, tuser2, tuser3);
+    isMember(group, false, tuser1, tuser2, tuser3);
+    isAttributeValueEntry(ugroup, true, "uniquemember",
+                          tuser1, tuser2, tuser3);
+    isAttributeValueEntry(tugroup, false, "uniquemember",
+                          tuser1, tuser2, tuser3);
+    isAttributeValueEntry(tspPerson, true,"seealso",
+                          tuser1, tuser2, tuser3);
+   }
+
+  /**
+   * Retrieves a set of valid configuration entries that may be used to
+   * initialize the plugin.
+   *
+   * @return An array of config entries.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   *
+   */
+  @DataProvider(name = "validConfigs")
+  public Object[][] getValidConfigs()
+          throws Exception
+  {
+    List<Entry> entries = TestCaseUtils.makeEntries(
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=people, o=test",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=people, o=test",
+            "ds-cfg-referential-integrity-update-interval: 300 seconds",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=people, o=test",
+            "ds-cfg-referential-integrity-update-interval: 300 seconds",
+            "ds-cfg-referential-integrity-log-file: logs/test",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=people, o=test",
+            "ds-cfg-referential-integrity-update-interval: 300 seconds",
+            "ds-cfg-referential-integrity-log-file: logs/test"
+    );
+    Object[][] array = new Object[entries.size()][1];
+    for (int i=0; i < array.length; i++)
+    {
+      array[i] = new Object[] { entries.get(i) };
+    }
+
+    return array;
+  }
+
+
+  /**
+   * Tests the process of initializing the server with valid configurations.
+   *
+   * @param  e  The configuration entry to use for the initialization.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   *
+   */
+  @Test(dataProvider = "validConfigs")
+  public void testInitializeWithValidConfigs(Entry e)
+          throws Exception
+  {
+    HashSet<PluginType> pluginTypes = new HashSet<PluginType>();
+    List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type");
+    for (Attribute a : attrList){
+      for (AttributeValue v : a.getValues())
+        pluginTypes.add(PluginType.forName(v.getStringValue().toLowerCase()));
+    }
+    ReferentialIntegrityPluginCfg configuration =
+            AdminTestCaseUtils.getConfiguration(
+                    ReferentialIntegrityPluginCfgDefn.getInstance(), e);
+    ReferentialIntegrityPlugin plugin = new ReferentialIntegrityPlugin();
+    plugin.initializePlugin(pluginTypes, configuration);
+    plugin.finalizePlugin();
+  }
+
+  /**
+   * Retrieves a set of invalid configuration entries that may be used to
+   * initialize the plugin.
+   *
+   * @return An array of config entries.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   *
+   */
+  @DataProvider(name = "invalidConfigs")
+  public Object[][] getInValidConfigs()
+          throws Exception
+  {
+    List<Entry> entries = TestCaseUtils.makeEntries(
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: cn",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: sn",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: preOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: baddn",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=people, o=test",
+            "ds-cfg-referential-integrity-update-interval: -5 seconds",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationDelete",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: notanattribute",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=people, o=test",
+            "ds-cfg-referential-integrity-update-interval: 300 seconds",
+            "ds-cfg-referential-integrity-log-file: logs/test",
+            "",
+            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
+            "objectClass: top",
+            "objectClass: ds-cfg-plugin",
+            "objectClass: ds-cfg-referential-integrity-plugin",
+            "cn: Referential Integrity",
+            "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
+            "ds-cfg-plugin-enabled: true",
+            "ds-cfg-plugin-type: postOperationModifyDN",
+            "ds-cfg-plugin-type: subordinateModifyDN",
+            "ds-cfg-referential-integrity-attribute-type: member",
+            "ds-cfg-referential-integrity-attribute-type: uniqueMember",
+            "ds-cfg-referential-integrity-attribute-type: seeAlso",
+            "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com",
+            "ds-cfg-referential-integrity-base-dn: ou=people, o=test",
+            "ds-cfg-referential-integrity-update-interval: 300 seconds",
+            "ds-cfg-referential-integrity-log-file: /hopefully/doesn't/file/exist"
+    );
+    Object[][] array = new Object[entries.size()][1];
+    for (int i=0; i < array.length; i++)
+    {
+      array[i] = new Object[] { entries.get(i) };
+    }
+
+    return array;
+  }
+
+
+  /**
+   * Tests the process of initializing the server with inValid configurations.
+   *
+   * @param  e  The configuration entry to use for the initialization.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   *
+   */
+  @Test(dataProvider = "invalidConfigs",
+        expectedExceptions = { ConfigException.class })
+  public void testInitializeWithInValidConfigs(Entry e)
+          throws Exception
+  {
+    HashSet<PluginType> pluginTypes = new HashSet<PluginType>();
+    List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type");
+    for (Attribute a : attrList){
+      for (AttributeValue v : a.getValues())
+        pluginTypes.add(PluginType.forName(v.getStringValue().toLowerCase()));
+    }
+    ReferentialIntegrityPluginCfg configuration =
+            AdminTestCaseUtils.getConfiguration(
+                    ReferentialIntegrityPluginCfgDefn.getInstance(), e);
+    ReferentialIntegrityPlugin plugin = new ReferentialIntegrityPlugin();
+    plugin.initializePlugin(pluginTypes, configuration);
+    plugin.finalizePlugin();
+  }
+
+  /**
+   * Ensures that the Directory Server is running.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   *
+   */
+  @BeforeClass()
+  public void startServer()
+          throws Exception
+  {
+    TestCaseUtils.startServer();
+    configDN= DN.decode("cn=Referential Integrity ,cn=Plugins,cn=config");
+  }
+
+  /**
+   * Clears configuration information before each method run and re-adds
+   * entries.
+   *
+   * @throws Exception If an unexpected problem occurs.
+   *
+   */
+  @BeforeMethod
+  public void clearConfigEntries() throws Exception {
+    deleteAttrsEntry(configDN, dsConfigBaseDN);
+    //Hopefully put an attribute type there that won't impact the rest of the
+    //unit tests.
+    replaceAttrEntry(configDN, dsConfigAttrType,"seeAlso");
+    replaceAttrEntry(configDN, dsConfigUpdateInterval,"0 seconds");
+    TestCaseUtils.initializeTestBackend(true);
+    addTestEntries("o=test");
+    TestCaseUtils.clearJEBackend(true,"userRoot", "dc=example,dc=com");
+    addTestEntries("dc=example,dc=com");
+  }
+
+
+  /**
+   * Clears things up after the unit test is completed.
+   *
+   * @throws Exception If an unexpected problem occurs.
+   *
+   */
+  @AfterClass
+  public void tearDown() throws Exception {
+     deleteAttrsEntry(configDN, dsConfigBaseDN);
+    //Hopefully put an attribute type there that won't impact the rest of the
+    //unit tests.
+    replaceAttrEntry(configDN, dsConfigAttrType,"seeAlso");
+    replaceAttrEntry(configDN, dsConfigUpdateInterval,"0 seconds");
+    TestCaseUtils.clearJEBackend(false,"userRoot", "dc=example,dc=com");
+  }
+
+  /**
+   * Create entries under the specified suffix and add them to the server.
+   * The character argument is used to make the mail attribute unique.
+   *
+   * @param suffix  The suffix to use in building the entries.
+   *
+   * @throws Exception If a problem occurs.
+   *
+   */
+  private void addTestEntries(String suffix) throws Exception {
+    TestCaseUtils.addEntries(
+            "dn: ou=dept," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: dept",
+            "aci: (targetattr= \"*\")" +
+                    "(version 3.0; acl \"allow all\";" +
+                    "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: ou=moved dept," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: moved dept",
+            "aci: (targetattr= \"*\")" +
+                    "(version 3.0; acl \"allow all\";" +
+                    "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: ou=groups," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: groups",
+            "aci: (targetattr= \"*\")" +
+                    "(version 3.0; acl \"allow all\";" +
+                    "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: ou=unique Groups," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: unique Groups",
+            "aci: (targetattr= \"*\")" +
+                    "(version 3.0; acl \"allow all\";" +
+                    "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: ou=People, ou=dept," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: People",
+            "",
+            "dn: ou=Special People," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: Special People",
+            "aci: (targetattr= \"*\")" +
+                    "(version 3.0; acl \"allow all\";" +
+                    "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: cn=special person, ou=Special People," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 1",
+            "givenName: User",
+            "sn: 1",
+            "cn: special person",
+            "userPassword: password",
+            "mail: user1" +"@test",
+            "employeeNumber: 1",
+            "mobile: 1-111-1234",
+            "pager: 1-111-5678",
+            "description: Use for seeAlso attribute",
+            "",
+            "dn: cn=group, ou=groups," + suffix,
+            "objectClass: top",
+            "objectClass: groupOfNames",
+            "cn: group",
+            "aci: (targetattr= \"*\")" +
+                    "(version 3.0; acl \"allow all\";" +
+                    "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: cn=group, ou=unique groups," + suffix,
+            "objectClass: top",
+            "objectClass: groupOfUniqueNames",
+            "cn: group",
+            "aci: (targetattr= \"*\")" +
+                    "(version 3.0; acl \"allow all\";" +
+                    "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: uid=user.1, ou=People, ou=dept," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 1",
+            "givenName: User",
+            "sn: 1",
+            "cn: User 1",
+            "userPassword: password",
+            "mail: user1" +"@test",
+            "employeeNumber: 1",
+            "mobile: 1-111-1234",
+            "pager: 1-111-5678",
+            "telephoneNumber: 1-111-9012",
+            "",
+            "dn: uid=user.2, ou=People, ou=dept," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 2",
+            "givenName: User",
+            "sn: 2",
+            "cn: User 2",
+            "mail: user2" + "@test",
+            "userPassword: password",
+            "employeeNumber: 2",
+            "mobile: 1-222-1234",
+            "pager: 1-222-5678",
+            "telephoneNumber: 1-222-9012",
+            "",
+            "dn: uid=user.3, ou=People, ou=dept," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 3",
+            "givenName: User",
+            "sn: 3",
+            "cn: User 3",
+            "mail: user3" + "@test",
+            "userPassword: password",
+            "employeeNumber: 3",
+            "mobile: 1-333-1234",
+            "pager: 1-333-5678",
+            "telephoneNumber: 1-333-9012",
+            "",
+            "dn: uid=user.4, ou=People, ou=dept," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 4",
+            "givenName: User",
+            "sn: 4",
+            "cn: User 4",
+            "mail: user4" + "@test",
+            "userPassword: password",
+            "employeeNumber: 4",
+            "mobile: 1-444-1234",
+            "pager: 1-444-5678",
+            "telephoneNumber: 1-444-9012",
+            "",
+            "dn: uid=user.5, ou=People, ou=dept," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 5",
+            "givenName: User",
+            "sn: 5",
+            "cn: User 5",
+            "mail: user5" + "@test",
+            "userPassword: password",
+            "employeeNumber: 5",
+            "mobile: 1-555-1234",
+            "pager: 1-555-5678",
+            "telephoneNumber: 1-555-9012"
+    );
+  }
+
+  /**
+   * Add specified attr type and type values to the entry specified by dn.
+   *
+   * @param dn The dn of the entry to add the attribute and values to.
+   *
+   * @param attrTypeString The attribute type to add the values to.
+   *
+   * @param attrValStrings The values to add to the entry.
+   *
+   */
+  private void
+  addAttrEntry(DN dn, String attrTypeString, String... attrValStrings) {
+    LinkedList<Modification> mods = new LinkedList<Modification>();
+    LinkedHashSet<AttributeValue> attrValues =
+                                            new LinkedHashSet<AttributeValue>();
+    AttributeType attrType = getAttrType(attrTypeString);
+    for(String valString : attrValStrings)
+      attrValues.add(new AttributeValue(attrType, valString));
+    Attribute attr = new Attribute(attrType, attrTypeString, attrValues);
+    mods.add(new Modification(ModificationType.ADD, attr));
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    conn.processModify(dn, mods);
+  }
+
+/**
+   * Replace specified attr type and type values to the entry specified by dn.
+   *
+   * @param dn The dn of the entry to replace the attribute and values to.
+   *
+   * @param attrTypeString The attribute type to replace the values in.
+   *
+   * @param attrValStrings The values to replace in the the entry.
+ *
+   */
+  private void
+  replaceAttrEntry(DN dn, String attrTypeString, String... attrValStrings) {
+    LinkedList<Modification> mods = new LinkedList<Modification>();
+    LinkedHashSet<AttributeValue> attrValues =
+            new LinkedHashSet<AttributeValue>();
+    AttributeType attrType = getAttrType(attrTypeString);
+    for(String valString : attrValStrings)
+      attrValues.add(new AttributeValue(attrType, valString));
+    Attribute attr = new Attribute(attrType, attrTypeString, attrValues);
+    mods.add(new Modification(ModificationType.REPLACE, attr));
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    conn.processModify(dn, mods);
+  }
+
+
+  /**
+   * Remove the attributes specified by the attribute type strings from the
+   * entry corresponding to the dn argument.
+   *
+   * @param dn The entry to remove the attributes from.
+   *
+   * @param attrTypeStrings The attribute type string list to remove from the
+   *                        entry.
+   *
+   * @throws Exception  If an error occurs.
+   *
+   */
+  private void
+  deleteAttrsEntry(DN dn, String... attrTypeStrings) throws Exception {
+    LinkedList<Modification> mods = new LinkedList<Modification>();
+    for(String attrTypeString : attrTypeStrings) {
+      AttributeType attrType = getAttrType(attrTypeString);
+      mods.add(new Modification(ModificationType.DELETE,
+              new Attribute(attrType)));
+    }
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    conn.processModify(dn, mods);
+  }
+
+  /**
+   * Return the attribute type corresponding to the attribute type string.
+   *
+   * @param attrTypeString  The attribute type string name.
+   *
+   * @return  An attribute type object pertaining to the string.
+   *
+   */
+  private AttributeType getAttrType(String attrTypeString) {
+    AttributeType attrType =
+            DirectoryServer.getAttributeType(attrTypeString);
+    if (attrType == null)
+      attrType = DirectoryServer.getDefaultAttributeType(attrTypeString);
+    return attrType;
+  }
+
+  private void deleteEntries(String... dns) throws Exception{
+    InternalClientConnection conn =
+                                 InternalClientConnection.getRootConnection();
+    for(String dn : dns) {
+         DeleteOperation op=conn.processDelete(DN.decode(dn));
+       assertEquals(op.getResultCode(), ResultCode.SUCCESS);
+    }
+  }
+
+  /**
+   * Check membership in a static group of the specified dns. The expected
+   * boolean is used to check if the dns are expected or not expected in the
+   * groups.
+   *
+   * @param group The group to check membership in.
+   *
+   * @param expected Set to <code>true</code> if the dns are expected in the
+   *                 groups.
+   *
+   * @param dns The dns to check membership for.
+   *
+   * @throws Exception If an unexpected membership occurs.
+   *
+   */
+  private void isMember(String group, boolean expected, String... dns)
+  throws Exception {
+   GroupManager groupManager=DirectoryServer.getGroupManager();
+   Group instance=groupManager.getGroupInstance(DN.decode(group));
+   for(String dn : dns)
+     assertEquals(instance.isMember(DN.decode(dn)), expected);
+  }
+
+  private void isAttributeValueEntry(String entryDN, boolean expected,
+                                     String attr,
+                                     String... dns)
+          throws Exception {
+    AttributeType type= getAttrType(attr);
+    String filterStr="(" + attr + "=*)";
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    InternalSearchOperation operation = conn.processSearch(DN.decode(entryDN),
+            SearchScope.BASE_OBJECT,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+            SearchFilter.createFilterFromString(filterStr),
+            null);
+    for (SearchResultEntry entry : operation.getSearchEntries()) {
+      for(String dn : dns) {
+        AttributeValue value = new AttributeValue(type, dn);
+        assertEquals(entry.hasValue(type, null, value), expected);
+      }
+    }
+  }
+
+  /**
+   * Add the entries created using the specified DNs to the server.
+   *
+   * @param dns The dns to use in entry creation.
+   *
+   * @throws Exception If an unexpected result happens.
+   *
+   */
+  private void addEntries(String... dns) throws Exception {
+        InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    for(String dn : dns) {
+      Entry e=makeEntry(dn);
+      AddOperation op=conn.processAdd(e);
+      assertEquals(op.getResultCode(), ResultCode.SUCCESS);
+    }
+  }
+
+  /**
+   * Make a entry with the specified dn.
+   *
+   * @param dn The dn of the entry.
+   * @return The created entry.
+   * @throws Exception  If the entry can't be created.
+   */
+  private Entry makeEntry(String dn) throws Exception {
+      return TestCaseUtils.makeEntry(
+            "dn: " + dn,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 1",
+            "givenName: test",
+            "sn: 1",
+            "cn: test"
+    );
+  }
+
+  /**
+   * Perform modify DN operation.
+   *
+   * @param dn  The DN to renmame or move.
+   *
+   * @param rdn RDN value.
+   *
+   * @param newSuperior New superior to move to.
+   *
+   * @throws Exception If the operation can't be performed.
+   *
+   */
+  private void
+  doModDN(String dn, String rdn, String newSuperior) throws Exception {
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    ModifyDNOperation modDNop;
+    if(newSuperior != null)
+        modDNop = conn.processModifyDN(DN.decode(dn), RDN.decode(rdn), true,
+                                       DN.decode(newSuperior));
+    else
+        modDNop = conn.processModifyDN(DN.decode(dn), RDN.decode(rdn),
+                                       false, null);
+    assertEquals(modDNop.getResultCode(), ResultCode.SUCCESS);
+  }
+}

--
Gitblit v1.10.0