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

dugan
21.31.2007 26150e5cc7b18069cfabaa916fa19416d378de70
Add attribute uniqueness plugin implementation that provides single-server
attribute uniqueness. The plugin has the following features:

- provides ability to specify a group of attribute types that must have
unique values; if no attribute types are specified then the plugin allows
the operations to proceed with no checking

- provides ability to specify a set of base DNs that limit the scope of the
uniqueness checking; if no base DNs are specified the server's public
naming contexts are used

- allow changing of these configuration options without server restart

- allows the uniqueness checking to span multiple base DNs; if the server's
public naming contexts are used, then the specified attribute type values must
be globally unique within the server


Two configuration attributes have been added:

1. ds-cfg-unique-attribute-type used to specify the unique attribute type(s)
2. ds-cfg-unique-attribute-base-dn used specify the base DN(s) to limit the search scope

A disabled plugin configuration has been added to the config.ldif file for the uid attribute:

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

Issue 258.
3 files added
4 files modified
1570 ■■■■■ changed files
opends/resource/config/config.ldif 12 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 12 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml 118 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/plugin.properties 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java 485 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/resource/config-changes.ldif 19 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java 912 ●●●●● patch | view | raw | blame | history
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
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' )
opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
New file
@@ -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>
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
opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
New file
@@ -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;
  }
}
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
opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java
New file
@@ -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);
  }
}