From 26150e5cc7b18069cfabaa916fa19416d378de70 Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Tue, 21 Aug 2007 13:31:07 +0000
Subject: [PATCH] Add attribute uniqueness plugin implementation that provides single-server attribute uniqueness. The plugin has the following features:

---
 opends/resource/schema/02-config.ldif                                                                  |   12 
 opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java |  912 +++++++++++++++++++++++++++++++++
 opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java                                 |  485 +++++++++++++++++
 opends/resource/config/config.ldif                                                                     |   12 
 opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml               |  118 ++++
 opends/src/messages/messages/plugin.properties                                                         |   12 
 opends/tests/unit-tests-testng/resource/config-changes.ldif                                            |   19 
 7 files changed, 1,569 insertions(+), 1 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 6315958..978d667 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -1375,6 +1375,18 @@
 ds-cfg-profile-directory: logs
 ds-cfg-profile-sample-interval: 10 milliseconds
 
+dn: cn=UID Unique Attribute ,cn=Plugins,cn=config
+objectClass: top
+objectClass: ds-cfg-plugin
+objectClass: ds-cfg-unique-attribute-plugin
+cn: UID Unique Attribute
+ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin
+ds-cfg-plugin-enabled: false
+ds-cfg-plugin-type: preOperationAdd
+ds-cfg-plugin-type: preOperationModify
+ds-cfg-plugin-type: preOperationModifyDN
+ds-cfg-unique-attribute-type: uid
+
 dn: cn=Root DNs,cn=config
 objectClass: top
 objectClass: ds-cfg-root-dn-base
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 87d0a8e..af1d21d 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -1567,6 +1567,12 @@
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.470 NAME 'ds-cfg-replace-pattern'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
   X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.467
+  NAME 'ds-cfg-unique-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.468
+  NAME 'ds-cfg-unique-attribute-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 )
@@ -2201,4 +2207,8 @@
   STRUCTURAL MUST ( ds-cfg-match-attribute $ ds-cfg-match-pattern )
   MAY ( ds-cfg-match-base-dn $ ds-cfg-replace-pattern )
   X-ORIGIN 'OpenDS Directory Server' )
-
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.120
+  NAME 'ds-cfg-unique-attribute-plugin' SUP ds-cfg-plugin
+  STRUCTURAL
+  MAY ( ds-cfg-unique-attribute-type $ ds-cfg-unique-attribute-base-dn )
+  X-ORIGIN 'OpenDS Directory Server' )
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
new file mode 100644
index 0000000..58c453c
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
@@ -0,0 +1,118 @@
+<?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="unique-attribute-plugin"
+        plural-name="unique-attribute-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 enforce constraints on the value of an attribute within a portion
+    of the directory.
+  </adm:synopsis>
+
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:oid>1.3.6.1.4.1.26027.1.2.120</ldap:oid>
+      <ldap:name>ds-cfg-unique-attribute-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.UniqueAttributePlugin
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+
+    <adm:property name="unique-attribute-type" mandatory="false"
+                multi-valued="true">
+    <adm:synopsis>
+      Specifies the attribute type to check for value uniqueness.
+    </adm:synopsis>
+    <adm:description>
+      Specifies the attribute type to check for value uniqueness. The
+      values for each ds-cfg-unique-attribute-type attribute must be unique
+      within each base DN specified in the configuration's
+      ds-cfg-unique-attribute-base-dn attribute or within all of the server's
+      public naming contexts if no base DNs were specified in the configuration.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          The plugin will bypass unique attribute checking.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.467</ldap:oid>
+        <ldap:name>ds-cfg-unique-attribute-type</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+  <adm:property name="unique-attribute-base-dn" mandatory="false"
+                 multi-valued="true">
+    <adm:synopsis>
+      Specifies a base DN that the attribute must be unique within.
+    </adm:synopsis>
+    <adm:description>
+      Specifies a base DN that the attribute must be unique within.
+    </adm:description>
+        <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          The plugin will use the server's public naming contexts in the
+          searches.
+        </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.468</ldap:oid>
+        <ldap:name>ds-cfg-unique-attribute-base-dn</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+</adm:managed-object>
+
diff --git a/opends/src/messages/messages/plugin.properties b/opends/src/messages/messages/plugin.properties
index 8b7e6d2..a669f5c 100644
--- a/opends/src/messages/messages/plugin.properties
+++ b/opends/src/messages/messages/plugin.properties
@@ -303,3 +303,15 @@
  subordinate modify DN plugin defined in configuration entry %s returned null \
  when invoked for connection %d operation %s.  This is an illegal response, \
  and processing on this operation will be terminated
+SEVERE_ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE_77=An attempt was made to \
+ register the Unique Attribute plugin to be invoked as a %s plugin.  This \
+ plugin type is not allowed for this plugin
+SEVERE_ERR_PLUGIN_UNIQUEATTR_MOD_NOT_UNIQUE_78=An error occurred while \
+ attempting to modify an attribute value of entry %s because the proposed \
+ changes failed the attribute value uniqueness check
+SEVERE_ERR_PLUGIN_UNIQUEATTR_ADD_NOT_UNIQUE_79=An error occurred while \
+ attempting to add the entry %s because one of the entry's attribute values \
+ failed the attribute value uniqueness check
+SEVERE_ERR_PLUGIN_UNIQUEATTR_MODDN_NOT_UNIQUE_80=An error occurred while \
+ attempting to perform a modify DN operation on entry %s because the proposed \
+ changes failed the attribute value uniqueness check
diff --git a/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java b/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
new file mode 100644
index 0000000..d9b169c
--- /dev/null
+++ b/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
@@ -0,0 +1,485 @@
+/*
+ * 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.UniqueAttributePluginCfg;
+import org.opends.server.admin.std.meta.PluginCfgDefn;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.api.plugin.DirectoryServerPlugin;
+import org.opends.server.api.plugin.PluginType;
+import org.opends.server.api.plugin.PreOperationPluginResult;
+import org.opends.server.config.ConfigException;
+import org.opends.server.types.*;
+import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.PreOperationModifyDNOperation;
+import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.opends.server.types.operation.PreOperationOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.messages.Message;
+import static org.opends.messages.PluginMessages.*;
+
+import java.util.*;
+
+/**
+ * This class implements a Directory Server plugin that performs attribute
+ * uniqueness checking on the add, modify and modifyDN operations. If the
+ * operation is eligible for checking based on a set of configuration criteria,
+ * then the operation's attribute values will be checked, using that
+ * configuration criteria, for uniqueness against the server's values to
+ * determine if the operation can proceed.
+ */
+public class UniqueAttributePlugin
+        extends DirectoryServerPlugin<UniqueAttributePluginCfg>
+        implements ConfigurationChangeListener<UniqueAttributePluginCfg> {
+
+  //Current plugin configuration.
+  private UniqueAttributePluginCfg currentConfiguration;
+
+  //List of attribute types that must be unique.
+  private LinkedHashSet<AttributeType> uniqueAttributeTypes =
+          new LinkedHashSet<AttributeType>();
+
+//List of base DNs that limit the scope of the uniqueness checking.
+ private LinkedHashSet<DN> baseDNs = new LinkedHashSet<DN>();
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void initializePlugin(Set<PluginType> pluginTypes,
+                                     UniqueAttributePluginCfg configuration)
+          throws ConfigException {
+    configuration.addUniqueAttributeChangeListener(this);
+    currentConfiguration = configuration;
+    for (PluginType t : pluginTypes)
+      switch (t)  {
+        case PRE_OPERATION_ADD:
+        case PRE_OPERATION_MODIFY:
+        case PRE_OPERATION_MODIFY_DN:
+          // These are acceptable.
+          break;
+        default:
+          Message message =
+                  ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(t.toString());
+          throw new ConfigException(message);
+
+      }
+    //Load base DNs if any.
+    for(DN baseDN : configuration.getUniqueAttributeBaseDN())
+      baseDNs.add(baseDN);
+    //Load attribute types if any.
+    for(String attributeType : configuration.getUniqueAttributeType()) {
+      AttributeType type =
+              DirectoryServer.getAttributeType(attributeType.toLowerCase());
+      if(type == null)
+        type =
+           DirectoryServer.getDefaultAttributeType(attributeType.toLowerCase());
+      uniqueAttributeTypes.add(type);
+    }
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+//  @Override()
+  public boolean
+  isConfigurationAcceptable(UniqueAttributePluginCfg configuration,
+                            List<Message> unacceptableReasons) {
+    return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isConfigurationChangeAcceptable(
+          UniqueAttributePluginCfg configuration,
+          List<Message> unacceptableReasons) {
+    boolean configAcceptable = true;
+    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
+    {
+      switch (pluginType)
+      {
+        case PREOPERATIONADD:
+        case PREOPERATIONMODIFY:
+        case PREOPERATIONMODIFYDN:
+          // These are acceptable.
+          break;
+
+        default:
+          Message message =
+           ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(pluginType.toString());
+          unacceptableReasons.add(message);
+          configAcceptable = false;
+      }
+    }
+    return configAcceptable;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConfigChangeResult applyConfigurationChange(
+          UniqueAttributePluginCfg newConfiguration) {
+    ResultCode resultCode          = ResultCode.SUCCESS;
+    boolean           adminActionRequired = false;
+    ArrayList<Message> messages            = new ArrayList<Message>();
+    LinkedHashSet<AttributeType> newUniqueattributeTypes=
+                                             new LinkedHashSet<AttributeType>();
+    LinkedHashSet<DN> newConfiguredBaseDNs = new LinkedHashSet<DN>();
+    //Load base DNs from new configuration.
+    for(DN baseDN : newConfiguration.getUniqueAttributeBaseDN())
+      newConfiguredBaseDNs.add(baseDN);
+    //Load attribute types from new configuration.
+    for(String attributeType : newConfiguration.getUniqueAttributeType()) {
+      AttributeType type =
+              DirectoryServer.getAttributeType(attributeType.toLowerCase());
+      if(type == null)
+        type =
+           DirectoryServer.getDefaultAttributeType(attributeType.toLowerCase());
+      newUniqueattributeTypes.add(type);
+    }
+    //Switch to the new lists and configurations.
+    baseDNs = newConfiguredBaseDNs;
+    uniqueAttributeTypes = newUniqueattributeTypes;
+    currentConfiguration = newConfiguration;
+    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final PreOperationPluginResult
+               doPreOperation(PreOperationAddOperation addOperation) {
+    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
+    DN entryDN=addOperation.getEntryDN();
+    if(isEntryUniquenessCandidate(entryDN)) {
+      List<AttributeValue> valueList =
+                         getEntryAttributeValues(addOperation.getEntryToAdd());
+      if(!searchAllBaseDNs(valueList, entryDN))
+        pluginResult =  getPluginErrorResult(addOperation,
+                ERR_PLUGIN_UNIQUEATTR_ADD_NOT_UNIQUE.get(entryDN.toString()));
+    }
+    return pluginResult;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final PreOperationPluginResult
+  doPreOperation(PreOperationModifyOperation modifyOperation) {
+    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
+    DN entryDN = modifyOperation.getEntryDN();
+    if(isEntryUniquenessCandidate(entryDN)) {
+      List<AttributeValue> valueList =
+              getModificationAttributeValues(modifyOperation.getModifications(),
+                                        modifyOperation.getModifiedEntry());
+      if(!searchAllBaseDNs(valueList, entryDN))
+        pluginResult =  getPluginErrorResult(modifyOperation,
+                  ERR_PLUGIN_UNIQUEATTR_MOD_NOT_UNIQUE.get(entryDN.toString()));
+    }
+    return pluginResult;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final PreOperationPluginResult
+               doPreOperation(PreOperationModifyDNOperation modifyDNOperation) {
+    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
+    DN entryDN=modifyDNOperation.getOriginalEntry().getDN();
+    //If the operation has a new superior DN then use that, since any moves
+    //need to make sure there are no conflicts in the new superior base DN.
+    if(modifyDNOperation.getNewSuperior() != null)
+        entryDN = modifyDNOperation.getNewSuperior();
+    if(isEntryUniquenessCandidate(entryDN)) {
+      List<AttributeValue> valueList =
+              getRDNAttributeValues(modifyDNOperation.getNewRDN());
+      if(!searchAllBaseDNs(valueList, entryDN))
+        pluginResult =  getPluginErrorResult(modifyDNOperation,
+                ERR_PLUGIN_UNIQUEATTR_MODDN_NOT_UNIQUE.get(entryDN.toString()));
+    }
+    return pluginResult;
+  }
+
+  /**
+   * Determine if the specified DN is a candidate for attribute uniqueness
+   * checking. Checking is skipped if the the unique attribute type list is
+   * empty or if there are base DNS configured and the specified DN is not a
+   * descendant of any of them. Checking is performed for all other cases.
+   *
+   * @param dn The DN to check.
+   *
+   * @return Returns <code>true</code> if the operation needs uniqueness
+   *         checking performed.
+   */
+  private boolean
+  isEntryUniquenessCandidate(DN dn) {
+    if(uniqueAttributeTypes.isEmpty())
+      return false;
+    else if(baseDNs.isEmpty())
+      return true;
+    else {
+      for(DN baseDN : baseDNs)
+        if(baseDN.isAncestorOf(dn))
+          return true;
+    }
+    return false;
+  }
+
+  /**
+   * Returns a plugin result instance indicating that the operation should be
+   * terminated; that no further pre-operation processing should be performed
+   * and that the server should send the response immediately. It also adds
+   * a CONSTRAINT_VIOLATION result code and the specified error message to
+   * the specified operation.
+   *
+   * @param operation   The operation to add the result code and message to.
+   *
+   * @param message The message to add to the operation.
+   *
+   * @return Returns a plugin result instance that halts further processing
+   *         on this operation.
+   */
+  private PreOperationPluginResult
+  getPluginErrorResult(PreOperationOperation operation, Message message) {
+        operation.appendErrorMessage(message);
+        operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+        return new PreOperationPluginResult(false, false, true);
+  }
+
+  /**
+   * Searches all of the the attribute types of the specified RDN for matches
+   * in the unique attribute type list. If matches are found, then the
+   * corresponding values are added to a list of values that will be eventually
+   * searched for uniqueness.
+
+   * @param rdn  The RDN to examine.
+   *
+   * @return Returns a list of attribute values from the RDN that matches the
+   *         unique attribute type list.
+   */
+  private List<AttributeValue> getRDNAttributeValues(RDN rdn) {
+    LinkedList<AttributeValue> valueList=
+                                            new LinkedList<AttributeValue>();
+    int numAVAs = rdn.getNumValues();
+    for (int i = 0; i < numAVAs; i++) {
+      if(uniqueAttributeTypes.contains(rdn.getAttributeType(i)))
+        valueList.add(rdn.getAttributeValue(i));
+    }
+    return valueList;
+  }
+
+  /**
+   * Searches all of the attribute types of the specified entry for matches
+   * in the unique attribute type list. Ff matches are found, then the
+   * corresponding values are added to a list of values that will eventually
+   * be searched for uniqueness.
+   *
+   * @param entry The entry to examine.
+   *
+   * @return Returns a list of attribute values from the entry that matches the
+   *         unique attribute type list.
+   */
+  private List<AttributeValue> getEntryAttributeValues(Entry entry) {
+    LinkedList<AttributeValue> valueList=new LinkedList<AttributeValue>();
+    for(AttributeType attributeType : uniqueAttributeTypes) {
+      if(entry.hasAttribute(attributeType))  {
+        List<Attribute> attrList=entry.getAttribute(attributeType);
+        for (Attribute a : attrList)
+          valueList.addAll(a.getValues());
+      }
+    }
+    return valueList;
+  }
+
+  /**
+   * Iterate over the unique attribute type list calling a method that will
+   * search the specified modification list for each attribute type and add
+   * the corresponding values to a list of values.
+   *
+   * @param modificationList  The modification list to search over.
+   *
+   * @param modifedEntry The copy of the entry with modifications applied.
+   *
+   * @return Returns a list of attribute values from the modification list
+   *         that matches the unique attribute type list.
+   */
+  private List<AttributeValue>
+  getModificationAttributeValues(List<Modification>  modificationList,
+                            Entry modifedEntry)  {
+    LinkedList<AttributeValue> valueList =
+                                            new LinkedList<AttributeValue>();
+    for(AttributeType attributeType : uniqueAttributeTypes)
+      getModValuesForAttribute(modificationList, attributeType, valueList,
+                               modifedEntry);
+    return valueList;
+  }
+
+  /**
+   * Searches the specified modification list for the provided attribute type.
+   * If a match is found than the attribute value is added to a list of
+   * attribute values that will be eventually searched for uniqueness.
+   *
+   * @param modificationList The modification list to search over.
+   *
+   * @param attributeType The attribute type to search for.
+   *
+   * @param valueList A list of attribute values to put the values in.
+   *
+   * @param modifiedEntry A copy of the entry with modifications applied.
+   */
+  private void
+  getModValuesForAttribute(List<Modification> modificationList,
+                           AttributeType attributeType,
+                           LinkedList<AttributeValue> valueList,
+                           Entry modifiedEntry) {
+
+    for(Modification modification : modificationList) {
+      ModificationType modType=modification.getModificationType();
+      //Skip delete modifications or modifications on attribute types not
+      //matching the specified type.
+      if(modType == ModificationType.DELETE ||
+         !modification.getAttribute().getAttributeType().equals(attributeType))
+          continue;
+      //Increment uses modified entry to get value for the attribute type.
+      if(modType == ModificationType.INCREMENT) {
+        List<Attribute> modifiedAttrs =
+           modifiedEntry.getAttribute(attributeType,
+                                      modification.getAttribute().getOptions());
+        if (modifiedAttrs != null)  {
+          for (Attribute a : modifiedAttrs)
+            valueList.addAll(a.getValues());
+        }
+      } else {
+        Attribute modifiedAttribute=modification.getAttribute();
+        if(modifiedAttribute.hasValue())
+          valueList.addAll(modifiedAttribute.getValues());
+      }
+    }
+  }
+
+
+  /**
+   * Iterates over the base DNs configured by the plugin entry searching for
+   * value matches. If the base DN list is empty then the public naming
+   * contexts are used instead.
+   *
+   * @param valueList The list of values to search for.
+   *
+   * @param entryDN  The DN of the entry related to the operation.
+   *
+   * @return  Returns <code>true</code> if a value is unique.
+   */
+  private boolean
+  searchAllBaseDNs(List<AttributeValue> valueList, DN entryDN) {
+    if(valueList.isEmpty())
+      return true;
+    if(baseDNs.isEmpty()) {
+      for(DN baseDN : DirectoryServer.getPublicNamingContexts().keySet()) {
+        if(searchBaseDN(valueList, baseDN, entryDN))
+          return false;
+      }
+    } else {
+      for(DN baseDN : baseDNs)  {
+        if(searchBaseDN(valueList, baseDN, entryDN))
+          return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Search a single base DN for all the values in a specified value list.
+   * A filter is created to search all the attribute at once for each
+   * value in the list.
+   *
+   * @param valueList The list of values to search for.
+   *
+   * @param baseDN  The base DN to base the search at.
+   *
+   * @param entryDN  The DN of the entry related to the operation.
+   *
+   * @return Returns <code>true</code> if the values are not unique under the
+   *         under the base DN.
+   */
+  private boolean
+  searchBaseDN(List<AttributeValue> valueList, DN baseDN,
+                    DN entryDN) {
+    //Filter set to hold component filters.
+    HashSet<SearchFilter> componentFilters=new HashSet<SearchFilter>();
+    for(AttributeValue value : valueList) {
+      //Iterate over the unique attribute list and build a equality filter
+      //using each attribute type in the list and the current value being
+      //matched.
+      for(AttributeType attributeType : uniqueAttributeTypes)
+        componentFilters.add(SearchFilter.createEqualityFilter(attributeType,
+                value));
+      //Perform the search using the OR filter created from the filter
+      //components created above.
+      InternalClientConnection conn =
+              InternalClientConnection.getRootConnection();
+      InternalSearchOperation operation = conn.processSearch(baseDN,
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, true,
+              SearchFilter.createORFilter(componentFilters),
+              null);
+      switch (operation.getResultCode()) {
+        case SUCCESS:
+          break;
+
+        case NO_SUCH_OBJECT:
+          //This base DN doesn't exist, return false.
+          return false;
+
+        case SIZE_LIMIT_EXCEEDED:
+        case TIME_LIMIT_EXCEEDED:
+        case ADMIN_LIMIT_EXCEEDED:
+        default:
+          //Couldn't determine if the attribute is unique because an
+          //administrative limit was reached during the search. Fail the
+          //operation by returning true. Possibly log an error here?
+          return true;
+      }
+      for (SearchResultEntry entry : operation.getSearchEntries()) {
+        //Only allow the entry DN to exist. The user might be modifying
+        //the attribute values and putting the same value back. Any other entry
+        //means the value is not unique.
+        if(!entry.getDN().equals(entryDN))
+          return true;
+      }
+      componentFilters.clear();
+    }
+    return false;
+  }
+}
diff --git a/opends/tests/unit-tests-testng/resource/config-changes.ldif b/opends/tests/unit-tests-testng/resource/config-changes.ldif
index d7dda3e..1ffd43f 100644
--- a/opends/tests/unit-tests-testng/resource/config-changes.ldif
+++ b/opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -301,6 +301,25 @@
 ds-cfg-plugin-type: preOperationAdd
 ds-cfg-plugin-type: preOperationModify
 
+dn: cn=UID Unique Attribute ,cn=Plugins,cn=config
+changeType: modify
+replace: ds-cfg-plugin-enabled
+ds-cfg-plugin-enabled: true
+-
+replace: ds-cfg-unique-attribute-type
+
+dn: cn=Test Unique Attribute,cn=Plugins,cn=config
+changeType: add
+objectClass: top
+objectClass: ds-cfg-plugin
+objectClass: ds-cfg-unique-attribute-plugin
+cn: Test Unique Attribute
+ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin
+ds-cfg-plugin-enabled: true
+ds-cfg-plugin-type: preOperationAdd
+ds-cfg-plugin-type: preOperationModify
+ds-cfg-plugin-type: preOperationModifyDN
+
 dn: cn=JKS,cn=Key Manager Providers,cn=config
 changetype: modify
 replace: ds-cfg-key-manager-provider-enabled
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java
new file mode 100644
index 0000000..28d401a
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java
@@ -0,0 +1,912 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import org.opends.server.types.*;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.config.ConfigException;
+import org.opends.server.admin.std.server.UniqueAttributePluginCfg;
+import org.opends.server.admin.std.meta.UniqueAttributePluginCfgDefn;
+import org.opends.server.admin.server.AdminTestCaseUtils;
+import org.opends.server.api.plugin.PluginType;
+
+import java.util.List;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.LinkedHashSet;
+
+
+/**
+ * Unit test to test the unique attribute plugin.
+ */
+
+public class UniqueAttributePluginTestCase extends PluginTestCase {
+
+  private DN uidConfigDN;
+  private DN testConfigDN;
+  private String dsConfigAttrType="ds-cfg-unique-attribute-type";
+  private String dsConfigBaseDN="ds-cfg-unique-attribute-base-dn";
+
+  /**
+   * Ensures that the Directory Server is running.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @BeforeClass()
+  public void startServer()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+    //Add entries to two backends to test public naming context.
+    addTestEntries("o=test", 't');
+    TestCaseUtils.clearJEBackend(true,"userRoot", "dc=example,dc=com");
+    addTestEntries("dc=example,dc=com", 'x');
+    uidConfigDN=DN.decode("cn=UID Unique Attribute ,cn=Plugins,cn=config");
+    testConfigDN=DN.decode("cn=Test Unique Attribute,cn=Plugins,cn=config");
+  }
+
+
+  /**
+   * Clears configuration information before each method run.
+   *
+   * @throws Exception If an unexpected problem occurs.
+   */
+  @BeforeMethod
+  public void clearConfigEntries() throws Exception {
+    deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType, dsConfigBaseDN);
+    deleteAttrsFromEntry(testConfigDN,dsConfigAttrType, dsConfigBaseDN);
+  }
+
+
+  /**
+   * Clears things up after the unit test is completed.
+   *
+   * @throws Exception If an unexpected problem occurs.
+   */
+  @AfterClass
+  public void tearDown() throws Exception {
+    clearConfigEntries();
+    TestCaseUtils.clearJEBackend(false,"userRoot", "dc=example,dc=com");
+  }
+
+
+  /**
+   * 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=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "",
+        "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "",
+        "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "ds-cfg-unique-attribute-type: uid",
+        "",
+        "dn: cn=mail Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: mail Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "ds-cfg-unique-attribute-type: mail",
+        "ds-cfg-unique-attribute-type: othermail",
+        "",
+        "dn: cn=phone Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: phone Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "ds-cfg-unique-attribute-type: telephone",
+        "ds-cfg-unique-attribute-type: mobile",
+        "ds-cfg-unique-attribute-type: fax",
+        "ds-cfg-unique-attribute-base-dn: dc=example,dc=com",
+        "ds-cfg-unique-attribute-base-dn: o=test",
+        "",
+        "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "ds-cfg-unique-attribute-type: uid",
+        "",
+        "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UUID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "",
+        "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UUID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationModify",
+        "",
+        "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UUID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationModifyDN");
+  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()));
+    }
+    UniqueAttributePluginCfg configuration =
+            AdminTestCaseUtils.getConfiguration(
+                    UniqueAttributePluginCfgDefn.getInstance(), e);
+
+    UniqueAttributePlugin plugin = new UniqueAttributePlugin();
+    plugin.initializePlugin(pluginTypes, configuration);
+    plugin.finalizePlugin();
+  }
+
+  /**
+   * 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 = "invalidConfigs")
+  public Object[][] getInValidConfigs()
+          throws Exception
+  {
+    List<Entry> entries = TestCaseUtils.makeEntries(
+        "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: UID Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: ldifImport",
+        "",
+        "dn: cn=phone Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "cn: phone Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "ds-cfg-unique-attribute-type: telephone",
+        "ds-cfg-unique-attribute-type: mobile",
+        "ds-cfg-unique-attribute-type: fax",
+        "ds-cfg-unique-attribute-base-dn: dc=example,dc=com",
+        "",
+        "dn: cn=phone Unique Attribute,cn=Plugins,cn=config",
+        "objectClass: top",
+        "objectClass: ds-cfg-plugin",
+        "objectClass: ds-cfg-unique-attribute-plugin",
+        "cn: phone Unique Attribute",
+        "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+        "ds-cfg-plugin-enabled: true",
+        "ds-cfg-plugin-type: preOperationAdd",
+        "ds-cfg-plugin-type: preOperationModify",
+        "ds-cfg-plugin-type: preOperationModifyDN",
+        "ds-cfg-unique-attribute-type: telephone",
+        "ds-cfg-unique-attribute-type: mobile",
+        "ds-cfg-unique-attribute-type: fax",
+        "ds-cfg-unique-attribute-base-dn: dc=example,dc=com",
+        "ds-cfg-unique-attribute-base-dn: badDN");
+
+    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()));
+    }
+    UniqueAttributePluginCfg configuration =
+         AdminTestCaseUtils.getConfiguration(
+              UniqueAttributePluginCfgDefn.getInstance(), e);
+    UniqueAttributePlugin plugin = new UniqueAttributePlugin();
+    plugin.initializePlugin(pluginTypes, configuration);
+    plugin.finalizePlugin();
+  }
+
+  /**
+   * Test modify DN operation with various scenerios. See method comments.
+   *
+   * @throws Exception If an unexpected result occurs.
+   */
+  @Test()
+  public void testModDNOperation() throws Exception {
+    //Add an entry under the new superior DN that has a value for uid
+    //that will be tested for.
+     Entry e = makeEntry("cn=test user, ou=new people,o=test");
+     addAttribute(e, "uid", "3user.3");
+     addEntry(e, ResultCode.SUCCESS);
+    //Setup uid attribute to be unique. Test using public naming contexts
+    //for base DNs.
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"uid");
+    //Rename with new rdn, should fail, there is an entry already with that
+    //uid value.
+    doModDN(DN.decode("uid=3user.3, ou=people, o=test"), RDN.decode("uid=4"),
+                      false, null, ResultCode.CONSTRAINT_VIOLATION);
+    //Rename with multi-valued RDN, should fail there is an entry already with
+    //that uid value.
+    doModDN(DN.decode("uid=3user.3, ou=people, o=test"),
+                      RDN.decode("sn=xx+uid=4"),
+                      false, null, ResultCode.CONSTRAINT_VIOLATION);
+    //Now add a base dn to be unique under, so new superior move can be tested.
+    addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=new people,o=test");
+
+
+    //Try to move the entry to a new superior.
+    //Should fail, there is an entry under the new superior already with
+    //that uid value.
+    doModDN(DN.decode("uid=3user.3, ou=people, o=test"),
+                      RDN.decode("uid=3user.3"), false,
+                       DN.decode("ou=new people, o=test"),
+                       ResultCode.CONSTRAINT_VIOLATION);
+   //Test again with different superior, should succeed, new superior DN is
+   //not in base DN scope.
+   doModDN(DN.decode("uid=3user.3, ou=people, o=test"),
+                      RDN.decode("uid=3user.3"), false,
+                       DN.decode("ou=new people1, o=test"),
+                       ResultCode.SUCCESS);
+  }
+
+  /**
+   *  Test various modifcation scenerios using a configuration with no base
+   * DNs defined. Use default of public naming contexts for base DNs.
+   *
+   * @throws Exception If an unexpected result occurs.
+   */
+  @Test()
+  public void testModOperationNameContexts() throws Exception {
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+    LinkedList<Modification> mods = new LinkedList<Modification>();
+    addMods(mods,"mail",ModificationType.REPLACE,"userx@test","userxx@test",
+           "user1t@test");
+    //Fail because user1t@test already exists under "o=people,o=test".
+    doMods(mods, DN.decode("uid=5user.5,ou=People,o=test"),
+           ResultCode.CONSTRAINT_VIOLATION);
+    mods.clear();
+    addMods(mods,"pager",ModificationType.ADD,"2-999-1234","1-999-5678");
+    addMods(mods,"mail",ModificationType.ADD,"userx@test","userxx@test",
+           "user1t@test");
+    //Fail because user1t@test already exists under "o=people,o=test".
+    doMods(mods, DN.decode("uid=5user.5,ou=People,o=test"),
+           ResultCode.CONSTRAINT_VIOLATION);
+    mods.clear();
+    addMods(mods,"pager",ModificationType.ADD,"2-999-1234","1-999-5678");
+    addMods(mods,"mail",ModificationType.REPLACE,"userx@test","userxx@test",
+           "user1t@test");
+    //Ok because adding mail value user1t@test to entry that already
+    //contains mail value user1t@test.
+    doMods(mods, DN.decode("uid=1user.1,ou=People,o=test"),
+           ResultCode.SUCCESS);
+    mods.clear();
+    //Remove mail as the unique attribute.
+    deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType);
+    //Add employeenumber as the unique attribute.
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"employeenumber");
+    addMods(mods,"employeenumber",ModificationType.INCREMENT,"1");
+    //Test modify increment extension.
+    //Fail because incremented value of employeenumber (2) already exists.
+    doMods(mods, DN.decode("uid=1user.1,ou=People,o=test"),
+           ResultCode.CONSTRAINT_VIOLATION);
+  }
+
+
+  /**
+   * Test setting the plugins up to get DSEE behavior. Basically two or more
+   * base DNs can have the same value, but not within the trees. This uses two
+   * plugins to accomplish this.
+   *
+   * @throws Exception If an unexpected result occurs.
+   */
+  @Test()
+  public void testDseeCompatAdd() throws Exception {
+    //Set up one plugin with mail attribute and a suffix.
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+    addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=People,o=test");
+    //Set up another plugin with the mail attribute and a different suffix.
+    addAttrToEntry(testConfigDN,dsConfigAttrType,"mail");
+    addAttrToEntry(testConfigDN,dsConfigBaseDN,"ou=People1,o=test");
+    //Add two entries with same mail attribute value into the different
+    //base DNs.
+    Entry e1 = makeEntry("cn=test user1, ou=People,o=test");
+    addAttribute(e1, "mail", "mailtest@test");
+    addEntry(e1, ResultCode.SUCCESS);
+    Entry e2 = makeEntry("cn=test user2, ou=People1,o=test");
+    addAttribute(e2, "mail", "mailtest@test");
+    addEntry(e2, ResultCode.SUCCESS);
+    //Now try to add two more entries with the same mail attribute value.
+    Entry e3 = makeEntry("cn=test user3, ou=People,o=test");
+    addAttribute(e3, "mail", "mailtest@test");
+    addEntry(e3, ResultCode.CONSTRAINT_VIOLATION);
+    Entry e4 = makeEntry("cn=test user4, ou=People1,o=test");
+    addAttribute(e4, "mail", "mailtest@test");
+    addEntry(e4, ResultCode.CONSTRAINT_VIOLATION);
+  }
+
+  /**
+   * Test various add operation scenerios using defined base DNs.
+   * See comments in method.
+   *
+   * @throws Exception If an unexpected result occurs.
+   */
+  @Test()
+  public void testAddOperation() throws Exception {
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+    addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=People1,o=test");
+    addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=People,o=test");
+    Entry e = makeEntry("cn=test user, ou=People,o=test");
+    addAttribute(e, "mail", "user1t@test");
+    //Fail because mail attribute already exists under "ou=people,o=test".
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+    delAttribute(e, "mail");
+    //Remove mail attribute type from config.
+    deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType);
+    //Add mobile, pager, telephonenumber to config.
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"mobile",
+                  "pager","telephonenumber");
+    addAttribute(e, "mobile", "1-999-1234","1-999-5678","1-444-9012");
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+    e.setDN(DN.decode("cn=test user, ou=People,o=test"));
+    //Fail because "2-333-9012" already exists in "ou=people,o=test" in
+    //telephonenumber attribute.
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+    delAttribute(e, "mobile");
+    addAttribute(e, "pager", "2-111-1234","1-999-5678","1-999-9012");
+    //Fail because "2-111-9012" already exists in "ou=people1,o=test" in
+    //mobile attribute.
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+    //Test two plugin configuration. Add mail attribute to second plugin
+    //instance, leave the first instance as it is.
+    addAttrToEntry(testConfigDN,dsConfigAttrType,"mail");
+    //Add suffix to second plugin.
+    addAttrToEntry(testConfigDN,dsConfigBaseDN,"ou=People,o=test");
+    delAttribute(e, "pager");
+    //Add some values that will pass the first plugin.
+    addAttribute(e, "telephonenumber", "2-999-1234","1-999-5678","1-999-9012");
+    //Add a value that will fail the second plugin.
+    addAttribute(e, "mail", "user1t@test");
+    //Should pass frirail through second plugin configuration.
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+  }
+
+
+  /**
+   * Test attempting to add entries using a configuration with no base
+   * DNs defined. Use default of public naming contexts for base DNs.
+   *
+   * @throws Exception If an unexpected result occurs.
+   */
+  @Test()
+  public void testAddOperationNameContext() throws Exception {
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+    Entry e = makeEntry("cn=test user, ou=People,o=test");
+    addAttribute(e, "mail", "user77x@test");
+    //Fail because mail value "user77x@test" is a value under the
+    //"dc=example,dc=com" naming context.
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+    delAttribute(e, "mail");
+    deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType);
+    addAttrToEntry(uidConfigDN,dsConfigAttrType,"mobile",
+                  "pager","telephonenumber");
+    addAttribute(e, "mobile", "1-999-1234","1-999-5678","2-777-9012");
+    //Fail because "2-777-9012"  is a telephone value under the
+    //"dc=example,dc=com" naming context.
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+    e.setDN(DN.decode("cn=test user, ou=People,o=test"));
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+    delAttribute(e, "mobile");
+    addAttribute(e, "pager", "2-777-1234","1-999-5678","1-999-9012");
+    //Fail because "2-777-9012"  is a telephone value under the
+    //"dc=example,dc=com" naming context.
+    addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+  }
+
+
+  /**
+   * 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.
+   * @param c Character used to make the mail attribute unique.
+   * @throws Exception If a problem occurs.
+   */
+  private void addTestEntries(String suffix, char c) throws Exception {
+    TestCaseUtils.addEntries(
+            "dn: ou=People," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: People",
+            "aci: (targetattr= \"*\")" +
+                  "(version 3.0; acl \"allow all\";" +
+                  "allow(all) userdn=\"ldap:///anyone\";)",
+            "",
+            "dn: ou=People1," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: People1",
+            "aci: (targetattr= \"*\")" +
+                  "(version 3.0; acl \"allow all\";" +
+                  "allow(all) userdn=\"ldap:///anyone\";)",
+             "",
+            "dn: ou=New People1," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: New People",
+            "",
+            "",
+            "dn: ou=New People," + suffix,
+            "objectClass: top",
+            "objectClass: organizationalUnit",
+            "ou: New People",
+            "",
+            "dn: uid=1user.1,ou=People," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 1",
+            "givenName: 1User",
+            "sn: 1",
+            "cn: 1User 1",
+            "userPassword: password",
+            "mail: user1" + c +"@test",
+            "employeeNumber: 1",
+            "mobile: 1-111-1234",
+            "pager: 1-111-5678",
+            "telephoneNumber: 1-111-9012",
+            "",
+            "dn: uid=2user.2,ou=People," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 2",
+            "givenName: 2User",
+            "sn: 2",
+            "cn: User 2",
+            "mail: user2" + c + "@test",
+            "userPassword: password",
+            "employeeNumber: 2",
+            "mobile: 1-222-1234",
+            "pager: 1-222-5678",
+            "telephoneNumber: 1-222-9012",
+            "",
+            "dn: uid=3user.3,ou=People," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 3",
+            "givenName: 3User",
+            "sn: 3",
+            "cn: User 3",
+            "mail: user3" + c + "@test",
+            "userPassword: password",
+            "employeeNumber: 3",
+            "mobile: 1-333-1234",
+            "pager: 1-333-5678",
+            "telephoneNumber: 1-333-9012",
+            "",
+            "dn: uid=4user.4,ou=People," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 4",
+            "givenName: 4User",
+            "sn: 4",
+            "cn: User 4",
+            "mail: user4" + c + "@test",
+            "userPassword: password",
+            "employeeNumber: 4",
+            "mobile: 1-444-1234",
+            "pager: 1-444-5678",
+            "telephoneNumber: 1-444-9012",
+            "",
+            "dn: uid=5user.5,ou=People," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 5",
+            "givenName: 5User",
+            "sn: 5",
+            "cn: User 5",
+            "mail: user5" + c + "@test",
+            "userPassword: password",
+            "employeeNumber: 5",
+            "mobile: 1-555-1234",
+            "pager: 1-555-5678",
+            "telephoneNumber: 1-555-9012",
+             "",
+            "dn: uid=1user.1,ou=People1," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 1",
+            "givenName: 1User",
+            "sn: 11",
+            "cn: 1User 11",
+            "userPassword: password",
+            "mail: user11" + c + "@test",
+            "employeeNumber: 111",
+            "mobile: 2-111-1234",
+            "pager: 2-111-5678",
+            "telephoneNumber: 2-111-9012",
+            "",
+            "dn: uid=2user.22,ou=People1," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 2",
+            "givenName: 2User",
+            "sn: 22",
+            "cn: User 22",
+            "mail: user22" + c + "@test",
+            "userPassword: password",
+            "employeeNumber: 222",
+            "mobile: 2-222-1234",
+            "pager: 2-222-5678",
+            "telephoneNumber: 2-222-9012",
+            "",
+            "dn: uid=3user.33,ou=People1," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 33",
+            "givenName: 3User",
+            "sn: 3",
+            "cn: User 33",
+            "mail: user33" + c + "@test",
+            "userPassword: password",
+            "employeeNumber: 333",
+            "mobile: 2-333-1234",
+            "pager: 2-333-5678",
+            "telephoneNumber: 2-333-9012"
+    );
+    //Add an additional entry if the suffix is "dc=example,dc=com".
+    if(suffix.equals("dc=example,dc=com")) {
+      TestCaseUtils.addEntries(
+            "dn: uid=2user.77,ou=People," + suffix,
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: organizationalPerson",
+            "objectClass: inetOrgPerson",
+            "uid: 2",
+            "givenName: 2User",
+            "sn: 22",
+            "cn: User 22",
+            "mail: user77" + c + "@test",
+            "userPassword: password",
+            "employeeNumber: 777",
+            "mobile: 2-777-1234",
+            "pager: 2-777-5678",
+            "telephoneNumber: 2-777-9012"
+      );
+    }
+  }
+
+  /**
+   * 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
+  deleteAttrsFromEntry(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);
+  }
+
+  /**
+   * Attempt to add an attribute of attribute type string to the entry
+   * specified by the dn argument. The values to use in the attribute creation
+   * is specified by the variable argument list.
+   *
+   * @param dn  The dn of the entry to add the attribute.
+   * @param attrTypeString  The attribute type string.
+   * @param attrValStrings  The values of the attribute.
+   */
+  private void
+  addAttrToEntry(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);
+  }
+
+  /**
+   * Try to add an entry to the server checking for the expected return
+   * code.
+   *
+   * @param e  The entry to add.
+   * @param rc The expected return code.
+   * @throws Exception If an error occurs.
+   */
+  private void addEntry(Entry e, ResultCode rc) throws Exception {
+        InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    AddOperation addOperation = conn.processAdd(e);
+    assertEquals(addOperation.getResultCode(), rc);
+  }
+
+  /**
+   * 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: 1User",
+            "sn: 1",
+            "cn: 1User 1"
+    );
+  }
+
+  /**
+   * Remove an attribute from the specified entry.
+   *
+   * @param entry  The entry to remove the attribute from.
+   * @param attrTypeString The attribute type string to remove.
+   */
+  private void delAttribute(Entry entry, String attrTypeString) {
+    entry.removeAttribute(getAttrType(attrTypeString));
+  }
+
+  /**
+   * Add an attribute to an entry with specified values.
+   *
+   * @param entry  The entry to add the attribute to.
+   * @param attrTypeString The attribute type string name.
+   * @param attrValues The values use in building the attribute.
+   */
+  private void
+  addAttribute(Entry entry, String attrTypeString, String... attrValues) {
+    LinkedHashSet<AttributeValue> values=new LinkedHashSet<AttributeValue>();
+    AttributeType attrType=getAttrType(attrTypeString);
+    for(String attrValue : attrValues) {
+      AttributeValue value = new AttributeValue(attrType, attrValue);
+      values.add(value);
+    }
+    entry.addAttribute(new Attribute(attrType, attrTypeString, values), null);
+  }
+
+  /**
+   * Add a new modification for attribute type string and values of modification
+   * type to a list of modifications.
+   *
+   * @param mods The modification list to add to.
+   * @param attrTypeString The attribute type string name.
+   * @param modificationType The modification type.
+   * @param attrValues The values to build the modification from.
+   */
+  private void
+  addMods(LinkedList<Modification> mods, String attrTypeString,
+          ModificationType modificationType, String... attrValues) {
+    LinkedHashSet<AttributeValue> values=new LinkedHashSet<AttributeValue>();
+    AttributeType attrType=getAttrType(attrTypeString);
+    for(String attrValue : attrValues) {
+      AttributeValue value = new AttributeValue(attrType, attrValue);
+      values.add(value);
+    }
+    mods.add(new Modification(modificationType,
+             new Attribute(attrType, attrTypeString, values)));
+  }
+
+  /**
+   * 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;
+  }
+
+  /**
+   * Perform modify operation with list of modifications. Expect return code
+   * of value rc.
+   *
+   * @param mods  The modification list to use.
+   * @param dn The DN of the entry to modify.
+   * @param rc The expected return code.
+   */
+  private void
+  doMods(LinkedList<Modification> mods, DN dn, ResultCode rc ) {
+    InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    ModifyOperation modifyOperation =
+            conn.processModify(dn, mods);
+    assertEquals(modifyOperation.getResultCode(),  rc);
+  }
+
+  /**
+   *  Perform modify DN operation. Expect return value of rc.
+   *
+   * @param dn  The DN to renmame or move.
+   * @param rdn RDN value.
+   * @param delOld Delete old flag.
+   * @param newSuperior New superior to move to.
+   * @param rc Expected return code from operation.
+   */
+  private void
+  doModDN(DN dn, RDN rdn, boolean delOld, DN newSuperior, ResultCode rc) {
+        InternalClientConnection conn =
+            InternalClientConnection.getRootConnection();
+    ModifyDNOperation modifyDNOperation =
+            conn.processModifyDN(dn, rdn, delOld, newSuperior);
+    assertEquals(modifyDNOperation.getResultCode(),  rc);
+  }
+}

--
Gitblit v1.10.0