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

neil_a_wilson
10.08.2007 94bbd5bb7c20a490558d8ec97d1be7e3dc492a42
opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
@@ -26,47 +26,104 @@
 */
package org.opends.server.plugins;
import org.opends.server.admin.std.server.UniqueAttributePluginCfg;
import org.opends.server.admin.std.meta.PluginCfgDefn;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.PluginCfgDefn;
import org.opends.server.admin.std.server.PluginCfg;
import org.opends.server.admin.std.server.UniqueAttributePluginCfg;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.plugin.DirectoryServerPlugin;
import org.opends.server.api.plugin.PluginType;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.config.ConfigException;
import org.opends.server.types.*;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
import org.opends.server.types.operation.PostSynchronizationAddOperation;
import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
import org.opends.server.types.operation.PostSynchronizationModifyOperation;
import org.opends.server.types.operation.PreOperationAddOperation;
import org.opends.server.types.operation.PreOperationModifyDNOperation;
import org.opends.server.types.operation.PreOperationModifyOperation;
import org.opends.server.types.operation.PreOperationOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.messages.Message;
import static org.opends.messages.PluginMessages.*;
import java.util.*;
import static org.opends.messages.PluginMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class implements a Directory Server plugin that performs attribute
 * uniqueness checking on the add, modify and modifyDN operations. If the
 * operation is eligible for checking based on a set of configuration criteria,
 * then the operation's attribute values will be checked, using that
 * configuration criteria, for uniqueness against the server's values to
 * determine if the operation can proceed.
 * This class implements a Directory Server plugin that can be used to ensure
 * that all values for a given attribute or set of attributes are unique within
 * the server (or optionally, below a specified set of base DNs).  It will
 * examine all add, modify, and modify DN operations to determine whether any
 * new conflicts are introduced.  If a conflict is detected then the operation
 * will be rejected, unless that operation is being applied through
 * synchronization in which case an alert will be generated to notify
 * administrators of the problem.
 */
public class UniqueAttributePlugin
        extends DirectoryServerPlugin<UniqueAttributePluginCfg>
        implements ConfigurationChangeListener<UniqueAttributePluginCfg> {
        implements ConfigurationChangeListener<UniqueAttributePluginCfg>,
                   AlertGenerator
{
  /**
   * The debug log tracer that will be used for this plugin.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The pre-operation plugin result that should be returned if an operation
   * would have resulted in a unique attribute conflict.
   */
  private static final PreOperationPluginResult FAILED_PREOP_RESULT =
       new PreOperationPluginResult(false, false, false, true);
  /**
   * The set of attributes that will be requested when performing internal
   * search operations.  This indicates that no attributes should be returned.
   */
  private static final LinkedHashSet<String> SEARCH_ATTRS =
       new LinkedHashSet<String>(1);
  static
  {
    SEARCH_ATTRS.add("1.1");
  }
  //Current plugin configuration.
  private UniqueAttributePluginCfg currentConfiguration;
  //List of attribute types that must be unique.
  private LinkedHashSet<AttributeType> uniqueAttributeTypes =
          new LinkedHashSet<AttributeType>();
//List of base DNs that limit the scope of the uniqueness checking.
 private LinkedHashSet<DN> baseDNs = new LinkedHashSet<DN>();
  /**
   * {@inheritDoc}
@@ -74,37 +131,710 @@
  @Override()
  public final void initializePlugin(Set<PluginType> pluginTypes,
                                     UniqueAttributePluginCfg configuration)
          throws ConfigException {
          throws ConfigException
  {
    configuration.addUniqueAttributeChangeListener(this);
    currentConfiguration = configuration;
    DirectoryServer.registerAlertGenerator(this);
    for (PluginType t : pluginTypes)
      switch (t)  {
    {
      switch (t)
      {
        case PRE_OPERATION_ADD:
        case PRE_OPERATION_MODIFY:
        case PRE_OPERATION_MODIFY_DN:
        case POST_SYNCHRONIZATION_ADD:
        case POST_SYNCHRONIZATION_MODIFY:
        case POST_SYNCHRONIZATION_MODIFY_DN:
          // These are acceptable.
          break;
        default:
          Message message =
                  ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(t.toString());
          throw new ConfigException(message);
      }
    //Load base DNs if any.
    for(DN baseDN : configuration.getUniqueAttributeBaseDN())
      baseDNs.add(baseDN);
    //Load attribute types if any.
    for(AttributeType attributeType : configuration.getUniqueAttributeType())
      uniqueAttributeTypes.add(attributeType);
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void finalizePlugin()
  {
    currentConfiguration.removeUniqueAttributeChangeListener(this);
    DirectoryServer.deregisterAlertGenerator(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreOperationPluginResult
               doPreOperation(PreOperationAddOperation addOperation)
  {
    UniqueAttributePluginCfg config = currentConfiguration;
    Entry entry = addOperation.getEntryToAdd();
    Set<DN> baseDNs = getBaseDNs(config, entry.getDN());
    if (baseDNs == null)
    {
      // The entry is outside the scope of this plugin.
      return PreOperationPluginResult.SUCCESS;
    }
    for (AttributeType t : config.getUniqueAttributeType())
    {
      List<Attribute> attrList = entry.getAttribute(t);
      if (attrList != null)
      {
        for (Attribute a : attrList)
        {
          for (AttributeValue v : a.getValues())
          {
            try
            {
              DN conflictDN = getConflictingEntryDN(baseDNs, entry.getDN(),
                                                    config, v);
              if (conflictDN != null)
              {
                addOperation.appendErrorMessage(
                     ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(),
                          v.getStringValue(), conflictDN.toString()));
                addOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                return FAILED_PREOP_RESULT;
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
                               de.getResultCode().toString(),
                               de.getMessageObject());
              addOperation.setResultCode(
                   DirectoryServer.getServerErrorResultCode());
              addOperation.appendErrorMessage(m);
              return FAILED_PREOP_RESULT;
            }
          }
        }
      }
    }
    return PreOperationPluginResult.SUCCESS;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreOperationPluginResult
               doPreOperation(PreOperationModifyOperation modifyOperation)
  {
    UniqueAttributePluginCfg config = currentConfiguration;
    DN entryDN = modifyOperation.getEntryDN();
    Set<DN> baseDNs = getBaseDNs(config, entryDN);
    if (baseDNs == null)
    {
      // The entry is outside the scope of this plugin.
      return PreOperationPluginResult.SUCCESS;
    }
    for (Modification m : modifyOperation.getModifications())
    {
      Attribute a = m.getAttribute();
      AttributeType t = a.getAttributeType();
      if (! config.getUniqueAttributeType().contains(t))
      {
        // This modification isn't for a unique attribute.
        continue;
      }
      switch (m.getModificationType())
      {
        case ADD:
        case REPLACE:
          for (AttributeValue v : a.getValues())
          {
            try
            {
              DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, config,
                                                    v);
              if (conflictDN != null)
              {
                modifyOperation.appendErrorMessage(
                     ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(),
                          v.getStringValue(), conflictDN.toString()));
                modifyOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                return FAILED_PREOP_RESULT;
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
                                     de.getResultCode().toString(),
                                     de.getMessageObject());
              modifyOperation.setResultCode(
                   DirectoryServer.getServerErrorResultCode());
              modifyOperation.appendErrorMessage(message);
              return FAILED_PREOP_RESULT;
            }
          }
          break;
        case INCREMENT:
          // We could calculate the new value, but we'll just take it from the
          // updated entry.
          List<Attribute> attrList =
               modifyOperation.getModifiedEntry().getAttribute(t,
                                                               a.getOptions());
          if (attrList != null)
          {
            for (Attribute updatedAttr : attrList)
            {
              if (! updatedAttr.optionsEqual(a.getOptions()))
              {
                continue;
              }
              for (AttributeValue v : updatedAttr.getValues())
              {
                try
                {
                  DN conflictDN = getConflictingEntryDN(baseDNs, entryDN,
                                                        config, v);
                  if (conflictDN != null)
                  {
                    modifyOperation.appendErrorMessage(
                         ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(
                              t.getNameOrOID(), v.getStringValue(),
                              conflictDN.toString()));
                    modifyOperation.setResultCode(
                         ResultCode.CONSTRAINT_VIOLATION);
                    return FAILED_PREOP_RESULT;
                  }
                }
                catch (DirectoryException de)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
                  }
                  Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
                                         de.getResultCode().toString(),
                                         de.getMessageObject());
                  modifyOperation.setResultCode(
                       DirectoryServer.getServerErrorResultCode());
                  modifyOperation.appendErrorMessage(message);
                  return FAILED_PREOP_RESULT;
                }
              }
            }
          }
          break;
        default:
          // We don't need to look at this modification because it's not a
          // modification type of interest.
          continue;
      }
    }
    return PreOperationPluginResult.SUCCESS;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreOperationPluginResult doPreOperation(
                    PreOperationModifyDNOperation modifyDNOperation)
  {
    UniqueAttributePluginCfg config = currentConfiguration;
    Set<DN> baseDNs = getBaseDNs(config,
                                 modifyDNOperation.getUpdatedEntry().getDN());
    if (baseDNs == null)
    {
      // The entry is outside the scope of this plugin.
      return PreOperationPluginResult.SUCCESS;
    }
    RDN newRDN = modifyDNOperation.getNewRDN();
    for (int i=0; i < newRDN.getNumValues(); i++)
    {
      AttributeType t = newRDN.getAttributeType(i);
      if (! config.getUniqueAttributeType().contains(t))
      {
        // We aren't interested in this attribute type.
        continue;
      }
      try
      {
        AttributeValue v = newRDN.getAttributeValue(i);
        DN conflictDN = getConflictingEntryDN(baseDNs,
                             modifyDNOperation.getEntryDN(), config, v);
        if (conflictDN != null)
        {
          modifyDNOperation.appendErrorMessage(
               ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(),
                    v.getStringValue(), conflictDN.toString()));
          modifyDNOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
          return FAILED_PREOP_RESULT;
        }
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
                         de.getResultCode().toString(),
                         de.getMessageObject());
        modifyDNOperation.setResultCode(
             DirectoryServer.getServerErrorResultCode());
        modifyDNOperation.appendErrorMessage(m);
        return FAILED_PREOP_RESULT;
      }
    }
    return PreOperationPluginResult.SUCCESS;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void doPostSynchronization(
                         PostSynchronizationAddOperation addOperation)
  {
    UniqueAttributePluginCfg config = currentConfiguration;
    Entry entry = addOperation.getEntryToAdd();
    Set<DN> baseDNs = getBaseDNs(config, entry.getDN());
    if (baseDNs == null)
    {
      // The entry is outside the scope of this plugin.
      return;
    }
    for (AttributeType t : config.getUniqueAttributeType())
    {
      List<Attribute> attrList = entry.getAttribute(t);
      if (attrList != null)
      {
        for (Attribute a : attrList)
        {
          for (AttributeValue v : a.getValues())
          {
            try
            {
              DN conflictDN = getConflictingEntryDN(baseDNs, entry.getDN(),
                                                    config, v);
              if (conflictDN != null)
              {
                Message m = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
                                 t.getNameOrOID(),
                                 addOperation.getConnectionID(),
                                 addOperation.getOperationID(),
                                 v.getStringValue(),
                                 entry.getDN().toString(),
                                 conflictDN.toString());
                DirectoryServer.sendAlertNotification(this,
                                     ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, m);
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
                               addOperation.getConnectionID(),
                               addOperation.getOperationID(),
                               entry.getDN().toString(),
                               de.getResultCode().toString(),
                               de.getMessageObject());
              DirectoryServer.sendAlertNotification(this,
                                   ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, m);
            }
          }
        }
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void doPostSynchronization(
                         PostSynchronizationModifyOperation modifyOperation)
  {
    UniqueAttributePluginCfg config = currentConfiguration;
    DN entryDN = modifyOperation.getEntryDN();
    Set<DN> baseDNs = getBaseDNs(config, entryDN);
    if (baseDNs == null)
    {
      // The entry is outside the scope of this plugin.
      return;
    }
    for (Modification m : modifyOperation.getModifications())
    {
      Attribute a = m.getAttribute();
      AttributeType t = a.getAttributeType();
      if (! config.getUniqueAttributeType().contains(t))
      {
        // This modification isn't for a unique attribute.
        continue;
      }
      switch (m.getModificationType())
      {
        case ADD:
        case REPLACE:
          for (AttributeValue v : a.getValues())
          {
            try
            {
              DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, config,
                                                    v);
              if (conflictDN != null)
              {
                Message message = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
                                       t.getNameOrOID(),
                                       modifyOperation.getConnectionID(),
                                       modifyOperation.getOperationID(),
                                       v.getStringValue(),
                                       entryDN.toString(),
                                       conflictDN.toString());
                DirectoryServer.sendAlertNotification(this,
                                     ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
                                     message);
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
                                    modifyOperation.getConnectionID(),
                                    modifyOperation.getOperationID(),
                                    entryDN.toString(),
                                    de.getResultCode().toString(),
                                    de.getMessageObject());
              DirectoryServer.sendAlertNotification(this,
                                   ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, message);
            }
          }
          break;
        case INCREMENT:
          // We could calculate the new value, but we'll just take it from the
          // updated entry.
          List<Attribute> attrList =
               modifyOperation.getModifiedEntry().getAttribute(t,
                                                               a.getOptions());
          if (attrList != null)
          {
            for (Attribute updatedAttr : attrList)
            {
              if (! updatedAttr.optionsEqual(a.getOptions()))
              {
                continue;
              }
              for (AttributeValue v : updatedAttr.getValues())
              {
                try
                {
                  DN conflictDN = getConflictingEntryDN(baseDNs, entryDN,
                                                        config, v);
                  if (conflictDN != null)
                  {
                    Message message = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
                                           t.getNameOrOID(),
                                           modifyOperation.getConnectionID(),
                                           modifyOperation.getOperationID(),
                                           v.getStringValue(),
                                           entryDN.toString(),
                                           conflictDN.toString());
                    DirectoryServer.sendAlertNotification(this,
                                         ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
                                         message);
                  }
                }
                catch (DirectoryException de)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
                  }
                  Message message =
                       ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
                            modifyOperation.getConnectionID(),
                            modifyOperation.getOperationID(),
                            entryDN.toString(),
                            de.getResultCode().toString(),
                            de.getMessageObject());
                  DirectoryServer.sendAlertNotification(this,
                                       ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR,
                                       message);
                }
              }
            }
          }
          break;
        default:
          // We don't need to look at this modification because it's not a
          // modification type of interest.
          continue;
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void doPostSynchronization(
                         PostSynchronizationModifyDNOperation modifyDNOperation)
  {
    UniqueAttributePluginCfg config = currentConfiguration;
    Set<DN> baseDNs = getBaseDNs(config,
                                 modifyDNOperation.getUpdatedEntry().getDN());
    if (baseDNs == null)
    {
      // The entry is outside the scope of this plugin.
      return;
    }
    RDN newRDN = modifyDNOperation.getNewRDN();
    for (int i=0; i < newRDN.getNumValues(); i++)
    {
      AttributeType t = newRDN.getAttributeType(i);
      if (! config.getUniqueAttributeType().contains(t))
      {
        // We aren't interested in this attribute type.
        continue;
      }
      try
      {
        AttributeValue v = newRDN.getAttributeValue(i);
        DN conflictDN = getConflictingEntryDN(baseDNs,
                             modifyDNOperation.getEntryDN(), config, v);
        if (conflictDN != null)
        {
          Message m =
               ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
                    t.getNameOrOID(),
                    modifyDNOperation.getConnectionID(),
                    modifyDNOperation.getOperationID(),
                    v.getStringValue(),
                    modifyDNOperation.getUpdatedEntry().getDN().toString(),
                    conflictDN.toString());
          DirectoryServer.sendAlertNotification(this,
                               ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, m);
        }
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
                         modifyDNOperation.getConnectionID(),
                         modifyDNOperation.getOperationID(),
                         modifyDNOperation.getUpdatedEntry().getDN().toString(),
                         de.getResultCode().toString(),
                         de.getMessageObject());
        DirectoryServer.sendAlertNotification(this,
                             ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, m);
      }
    }
  }
  /**
   * Retrieves the set of base DNs below which uniqueness checks should be
   * performed.  If no uniqueness checks should be performed for the specified
   * entry, then {@code null} will be returned.
   *
   * @param  config   The plugin configuration to use to make the determination.
   * @param  entryDN  The DN of the entry for which the checks will be
   *                  performed.
   */
  private Set<DN> getBaseDNs(UniqueAttributePluginCfg config, DN entryDN)
  {
    Set<DN> baseDNs = config.getUniqueAttributeBaseDN();
    if ((baseDNs == null) || baseDNs.isEmpty())
    {
      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
    }
    for (DN baseDN : baseDNs)
    {
      if (entryDN.isDescendantOf(baseDN))
      {
        return baseDNs;
      }
    }
    return null;
  }
  /**
   * Retrieves the DN of the first entry identified that conflicts with the
   * provided value.
   *
   * @param  baseDNs   The set of base DNs below which the search is to be
   *                   performed.
   * @param  targetDN  The DN of the entry at which the change is targeted.  If
   *                   a conflict is found in that entry, then it will be
   *                   ignored.
   * @param  config    The plugin configuration to use when making the
   *                   determination.
   * @param  value     The value for which to identify any conflicting entries.
   *
   * @return  The DN of the first entry identified that contains a conflicting
   *          value.
   *
   * @throws  DirectoryException  If a problem occurred while attempting to
   *                              make the determination.
   */
  private DN getConflictingEntryDN(Set<DN> baseDNs, DN targetDN,
                                   UniqueAttributePluginCfg config,
                                   AttributeValue value)
          throws DirectoryException
  {
    SearchFilter filter;
    Set<AttributeType> attrTypes = config.getUniqueAttributeType();
    if (attrTypes.size() == 1)
    {
      filter = SearchFilter.createEqualityFilter(attrTypes.iterator().next(),
                                                 value);
    }
    else
    {
      ArrayList<SearchFilter> equalityFilters =
           new ArrayList<SearchFilter>(attrTypes.size());
      for (AttributeType t : attrTypes)
      {
        equalityFilters.add(SearchFilter.createEqualityFilter(t, value));
      }
      filter = SearchFilter.createORFilter(equalityFilters);
    }
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    for (DN baseDN : baseDNs)
    {
      InternalSearchOperation searchOperation =
           conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE,
                              DereferencePolicy.NEVER_DEREF_ALIASES, 2, 0,
                              false, filter, SEARCH_ATTRS);
      for (SearchResultEntry e : searchOperation.getSearchEntries())
      {
        if (! e.getDN().equals(targetDN))
        {
          return e.getDN();
        }
      }
      switch (searchOperation.getResultCode())
      {
        case SUCCESS:
        case NO_SUCH_OBJECT:
          // These are fine.  Either the search was successful or the base DN
          // didn't exist.
          break;
        default:
          // An error occurred that prevented the search from completing
          // successfully.
          throw new DirectoryException(searchOperation.getResultCode(),
                         searchOperation.getErrorMessage().toMessage());
      }
    }
    // If we've gotten here, then no conflict was found.
    return null;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isConfigurationAcceptable(PluginCfg configuration,
                                           List<Message> unacceptableReasons)
  {
    UniqueAttributePluginCfg cfg = (UniqueAttributePluginCfg) configuration;
    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
          UniqueAttributePluginCfg configuration,
          List<Message> unacceptableReasons) {
                      UniqueAttributePluginCfg configuration,
                      List<Message> unacceptableReasons)
  {
    boolean configAcceptable = true;
    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
    {
@@ -113,12 +843,15 @@
        case PREOPERATIONADD:
        case PREOPERATIONMODIFY:
        case PREOPERATIONMODIFYDN:
        case POSTSYNCHRONIZATIONADD:
        case POSTSYNCHRONIZATIONMODIFY:
        case POSTSYNCHRONIZATIONMODIFYDN:
          // These are acceptable.
          break;
        default:
          Message message =
           ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(pluginType.toString());
          Message message = ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(
                                 pluginType.toString());
          unacceptableReasons.add(message);
          configAcceptable = false;
      }
@@ -126,337 +859,53 @@
    return configAcceptable;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
          UniqueAttributePluginCfg newConfiguration) {
    ResultCode resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    LinkedHashSet<AttributeType> newUniqueattributeTypes=
                                             new LinkedHashSet<AttributeType>();
    LinkedHashSet<DN> newConfiguredBaseDNs = new LinkedHashSet<DN>();
    //Load base DNs from new configuration.
    for(DN baseDN : newConfiguration.getUniqueAttributeBaseDN())
      newConfiguredBaseDNs.add(baseDN);
    //Load attribute types from new configuration.
    for(AttributeType attributeType : newConfiguration.getUniqueAttributeType())
      newUniqueattributeTypes.add(attributeType);
    //Switch to the new lists and configurations.
    baseDNs = newConfiguredBaseDNs;
    uniqueAttributeTypes = newUniqueattributeTypes;
                                 UniqueAttributePluginCfg newConfiguration)
  {
    currentConfiguration = newConfiguration;
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return new ConfigChangeResult(ResultCode.SUCCESS, false);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreOperationPluginResult
               doPreOperation(PreOperationAddOperation addOperation) {
    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
    DN entryDN=addOperation.getEntryDN();
    if(isEntryUniquenessCandidate(entryDN)) {
      List<AttributeValue> valueList =
                         getEntryAttributeValues(addOperation.getEntryToAdd());
      if(!searchAllBaseDNs(valueList, entryDN))
        pluginResult =  getPluginErrorResult(addOperation,
                ERR_PLUGIN_UNIQUEATTR_ADD_NOT_UNIQUE.get(entryDN.toString()));
    }
    return pluginResult;
  public DN getComponentEntryDN()
  {
    return currentConfiguration.dn();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreOperationPluginResult
  doPreOperation(PreOperationModifyOperation modifyOperation) {
    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
    DN entryDN = modifyOperation.getEntryDN();
    if(isEntryUniquenessCandidate(entryDN)) {
      List<AttributeValue> valueList =
              getModificationAttributeValues(modifyOperation.getModifications(),
                                        modifyOperation.getModifiedEntry());
      if(!searchAllBaseDNs(valueList, entryDN))
        pluginResult =  getPluginErrorResult(modifyOperation,
                  ERR_PLUGIN_UNIQUEATTR_MOD_NOT_UNIQUE.get(entryDN.toString()));
    }
    return pluginResult;
  public String getClassName()
  {
    return UniqueAttributePlugin.class.getName();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreOperationPluginResult
               doPreOperation(PreOperationModifyDNOperation modifyDNOperation) {
    PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
    DN entryDN=modifyDNOperation.getOriginalEntry().getDN();
    //If the operation has a new superior DN then use that, since any moves
    //need to make sure there are no conflicts in the new superior base DN.
    if(modifyDNOperation.getNewSuperior() != null)
        entryDN = modifyDNOperation.getNewSuperior();
    if(isEntryUniquenessCandidate(entryDN)) {
      List<AttributeValue> valueList =
              getRDNAttributeValues(modifyDNOperation.getNewRDN());
      if(!searchAllBaseDNs(valueList, entryDN))
        pluginResult =  getPluginErrorResult(modifyDNOperation,
                ERR_PLUGIN_UNIQUEATTR_MODDN_NOT_UNIQUE.get(entryDN.toString()));
    }
    return pluginResult;
  }
  public LinkedHashMap<String,String> getAlerts()
  {
    LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>(2);
  /**
   * Determine if the specified DN is a candidate for attribute uniqueness
   * checking. Checking is skipped if the the unique attribute type list is
   * empty or if there are base DNS configured and the specified DN is not a
   * descendant of any of them. Checking is performed for all other cases.
   *
   * @param dn The DN to check.
   *
   * @return Returns <code>true</code> if the operation needs uniqueness
   *         checking performed.
   */
  private boolean
  isEntryUniquenessCandidate(DN dn) {
    if(uniqueAttributeTypes.isEmpty())
      return false;
    else if(baseDNs.isEmpty())
      return true;
    else {
      for(DN baseDN : baseDNs)
        if(baseDN.isAncestorOf(dn))
          return true;
    }
    return false;
  }
    alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
               ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_CONFLICT);
    alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR,
               ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_ERROR);
  /**
   * Returns a plugin result instance indicating that the operation should be
   * terminated; that no further pre-operation processing should be performed
   * and that the server should send the response immediately. It also adds
   * a CONSTRAINT_VIOLATION result code and the specified error message to
   * the specified operation.
   *
   * @param operation   The operation to add the result code and message to.
   *
   * @param message The message to add to the operation.
   *
   * @return Returns a plugin result instance that halts further processing
   *         on this operation.
   */
  private PreOperationPluginResult
  getPluginErrorResult(PreOperationOperation operation, Message message) {
        operation.appendErrorMessage(message);
        operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
        return new PreOperationPluginResult(false, false, true);
  }
  /**
   * Searches all of the the attribute types of the specified RDN for matches
   * in the unique attribute type list. If matches are found, then the
   * corresponding values are added to a list of values that will be eventually
   * searched for uniqueness.
   * @param rdn  The RDN to examine.
   *
   * @return Returns a list of attribute values from the RDN that matches the
   *         unique attribute type list.
   */
  private List<AttributeValue> getRDNAttributeValues(RDN rdn) {
    LinkedList<AttributeValue> valueList=
                                            new LinkedList<AttributeValue>();
    int numAVAs = rdn.getNumValues();
    for (int i = 0; i < numAVAs; i++) {
      if(uniqueAttributeTypes.contains(rdn.getAttributeType(i)))
        valueList.add(rdn.getAttributeValue(i));
    }
    return valueList;
  }
  /**
   * Searches all of the attribute types of the specified entry for matches
   * in the unique attribute type list. Ff matches are found, then the
   * corresponding values are added to a list of values that will eventually
   * be searched for uniqueness.
   *
   * @param entry The entry to examine.
   *
   * @return Returns a list of attribute values from the entry that matches the
   *         unique attribute type list.
   */
  private List<AttributeValue> getEntryAttributeValues(Entry entry) {
    LinkedList<AttributeValue> valueList=new LinkedList<AttributeValue>();
    for(AttributeType attributeType : uniqueAttributeTypes) {
      if(entry.hasAttribute(attributeType))  {
        List<Attribute> attrList=entry.getAttribute(attributeType);
        for (Attribute a : attrList)
          valueList.addAll(a.getValues());
      }
    }
    return valueList;
  }
  /**
   * Iterate over the unique attribute type list calling a method that will
   * search the specified modification list for each attribute type and add
   * the corresponding values to a list of values.
   *
   * @param modificationList  The modification list to search over.
   *
   * @param modifedEntry The copy of the entry with modifications applied.
   *
   * @return Returns a list of attribute values from the modification list
   *         that matches the unique attribute type list.
   */
  private List<AttributeValue>
  getModificationAttributeValues(List<Modification>  modificationList,
                            Entry modifedEntry)  {
    LinkedList<AttributeValue> valueList =
                                            new LinkedList<AttributeValue>();
    for(AttributeType attributeType : uniqueAttributeTypes)
      getModValuesForAttribute(modificationList, attributeType, valueList,
                               modifedEntry);
    return valueList;
  }
  /**
   * Searches the specified modification list for the provided attribute type.
   * If a match is found than the attribute value is added to a list of
   * attribute values that will be eventually searched for uniqueness.
   *
   * @param modificationList The modification list to search over.
   *
   * @param attributeType The attribute type to search for.
   *
   * @param valueList A list of attribute values to put the values in.
   *
   * @param modifiedEntry A copy of the entry with modifications applied.
   */
  private void
  getModValuesForAttribute(List<Modification> modificationList,
                           AttributeType attributeType,
                           LinkedList<AttributeValue> valueList,
                           Entry modifiedEntry) {
    for(Modification modification : modificationList) {
      ModificationType modType=modification.getModificationType();
      //Skip delete modifications or modifications on attribute types not
      //matching the specified type.
      if(modType == ModificationType.DELETE ||
         !modification.getAttribute().getAttributeType().equals(attributeType))
          continue;
      //Increment uses modified entry to get value for the attribute type.
      if(modType == ModificationType.INCREMENT) {
        List<Attribute> modifiedAttrs =
           modifiedEntry.getAttribute(attributeType,
                                      modification.getAttribute().getOptions());
        if (modifiedAttrs != null)  {
          for (Attribute a : modifiedAttrs)
            valueList.addAll(a.getValues());
        }
      } else {
        Attribute modifiedAttribute=modification.getAttribute();
        if(modifiedAttribute.hasValue())
          valueList.addAll(modifiedAttribute.getValues());
      }
    }
  }
  /**
   * Iterates over the base DNs configured by the plugin entry searching for
   * value matches. If the base DN list is empty then the public naming
   * contexts are used instead.
   *
   * @param valueList The list of values to search for.
   *
   * @param entryDN  The DN of the entry related to the operation.
   *
   * @return  Returns <code>true</code> if a value is unique.
   */
  private boolean
  searchAllBaseDNs(List<AttributeValue> valueList, DN entryDN) {
    if(valueList.isEmpty())
      return true;
    if(baseDNs.isEmpty()) {
      for(DN baseDN : DirectoryServer.getPublicNamingContexts().keySet()) {
        if(searchBaseDN(valueList, baseDN, entryDN))
          return false;
      }
    } else {
      for(DN baseDN : baseDNs)  {
        if(searchBaseDN(valueList, baseDN, entryDN))
          return false;
      }
    }
    return true;
  }
  /**
   * Search a single base DN for all the values in a specified value list.
   * A filter is created to search all the attribute at once for each
   * value in the list.
   *
   * @param valueList The list of values to search for.
   *
   * @param baseDN  The base DN to base the search at.
   *
   * @param entryDN  The DN of the entry related to the operation.
   *
   * @return Returns <code>true</code> if the values are not unique under the
   *         under the base DN.
   */
  private boolean
  searchBaseDN(List<AttributeValue> valueList, DN baseDN,
                    DN entryDN) {
    //Filter set to hold component filters.
    HashSet<SearchFilter> componentFilters=new HashSet<SearchFilter>();
    for(AttributeValue value : valueList) {
      //Iterate over the unique attribute list and build a equality filter
      //using each attribute type in the list and the current value being
      //matched.
      for(AttributeType attributeType : uniqueAttributeTypes)
        componentFilters.add(SearchFilter.createEqualityFilter(attributeType,
                value));
      //Perform the search using the OR filter created from the filter
      //components created above.
      InternalClientConnection conn =
              InternalClientConnection.getRootConnection();
      InternalSearchOperation operation = conn.processSearch(baseDN,
              SearchScope.WHOLE_SUBTREE,
              DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, true,
              SearchFilter.createORFilter(componentFilters),
              null);
      switch (operation.getResultCode()) {
        case SUCCESS:
          break;
        case NO_SUCH_OBJECT:
          //This base DN doesn't exist, return false.
          return false;
        case SIZE_LIMIT_EXCEEDED:
        case TIME_LIMIT_EXCEEDED:
        case ADMIN_LIMIT_EXCEEDED:
        default:
          //Couldn't determine if the attribute is unique because an
          //administrative limit was reached during the search. Fail the
          //operation by returning true. Possibly log an error here?
          return true;
      }
      for (SearchResultEntry entry : operation.getSearchEntries()) {
        //Only allow the entry DN to exist. The user might be modifying
        //the attribute values and putting the same value back. Any other entry
        //means the value is not unique.
        if(!entry.getDN().equals(entryDN))
          return true;
      }
      componentFilters.clear();
    }
    return false;
    return alerts;
  }
}