UnboundID Server SDK

Ping Identity
UnboundID Server SDK Documentation

JSONFieldUniquenessPlugin.java

/*
 * 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
 * docs/licenses/cddl.txt
 * or http://www.opensource.org/licenses/cddl1.php.
 * 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
 * docs/licenses/cddl.txt.  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
 *
 *
 *      Copyright 2017-2018 Ping Identity Corporation
 */
package com.unboundid.directory.sdk.examples;



import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.unboundid.directory.sdk.common.schema.AttributeType;
import com.unboundid.directory.sdk.common.operation.AddRequest;
import com.unboundid.directory.sdk.common.operation.AddResult;
import com.unboundid.directory.sdk.common.operation.ModifyRequest;
import com.unboundid.directory.sdk.common.operation.ModifyResult;
import com.unboundid.directory.sdk.common.operation.UpdatableAddRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableAddResult;
import com.unboundid.directory.sdk.common.operation.UpdatableModifyRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableModifyResult;
import com.unboundid.directory.sdk.common.types.ActiveOperationContext;
import com.unboundid.directory.sdk.common.types.AlertSeverity;
import com.unboundid.directory.sdk.common.types.CompletedOperationContext;
import com.unboundid.directory.sdk.common.types.Entry;
import com.unboundid.directory.sdk.common.types.InternalConnection;
import com.unboundid.directory.sdk.common.types.OperationContext;
import com.unboundid.directory.sdk.ds.api.Plugin;
import com.unboundid.directory.sdk.ds.config.PluginConfig;
import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
import com.unboundid.directory.sdk.ds.types.PostResponsePluginResult;
import com.unboundid.directory.sdk.ds.types.PreOperationPluginResult;
import com.unboundid.directory.sdk.ds.types.PreParsePluginResult;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.DereferencePolicy;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModificationType;
import com.unboundid.ldap.sdk.RDN;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.unboundidds.jsonfilter.ANDJSONObjectFilter;
import com.unboundid.ldap.sdk.unboundidds.jsonfilter.EqualsAnyJSONObjectFilter;
import com.unboundid.ldap.sdk.unboundidds.jsonfilter.EqualsJSONObjectFilter;
import com.unboundid.ldap.sdk.unboundidds.jsonfilter.JSONObjectFilter;
import com.unboundid.ldap.sdk.unboundidds.jsonfilter.NegateJSONObjectFilter;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanValueArgument;
import com.unboundid.util.args.DNArgument;
import com.unboundid.util.args.FilterArgument;
import com.unboundid.util.args.StringArgument;
import com.unboundid.util.json.JSONArray;
import com.unboundid.util.json.JSONObject;
import com.unboundid.util.json.JSONValue;



/**
 * The JSON Field Uniqueness Plugin can be used to enforce uniqueness
 * constraints for values of a specified JSON field stored in a given LDAP
 * attribute.  Uniqueness can be enforced across entries so that the same value
 * will not be allowed to appear in two different entries.  It can also be
 * enforced within the same entry, so that each value of the JSON field (whether
 * in separate values of the LDAP attribute or within a JSON array in the same
 * LDAP attribute value).
 * <BR><BR>
 * This plugin can be used in either the Directory Server or the Directory Proxy
 * Server, although it is not recommended to be configured in both types of
 * servers in the same topology.  In deployments in which each Directory Server
 * instance contains a complete copy of all the data (regardless of whether
 * requests to those instances pass through the Directory Proxy Server), the
 * plugin should be configured in all Directory Server instances as a
 * pre-operation and post-synchronization plugin for add and modify operations,
 * and it should not be configured in any Directory Proxy Server instances.  For
 * each add and modify request received from an external client, the plugin will
 * check to see if that operation includes any changes involving the target JSON
 * field within the appropriate LDAP attribute, and will reject any change that
 * would introduce a conflict with a value that already exists in another entry,
 * or that includes duplicate values within the same entry if that is not
 * allowed.  It will also examine each add and modify operation that is
 * replicated from another server, and if that operation introduced a conflict
 * (which may arise if two conflicting requests are processed by different
 * servers at the same time), the plugin will generate an alert to notify
 * administrators of the conflict so that it can be addressed by manual
 * interaction.
 * <BR><BR>
 * In deployments that use the Directory Proxy Server to separate data across
 * multiple servers, whether through entry balancing or by separating different
 * portions of the DIT across different sets of servers, the plugin should be
 * configured in all Directory Proxy Server instances as a pre-parse and
 * post-response plugin for add and modify operations, and it should not be
 * configured in any of the Directory Server instances.  For each add and modify
 * request received by the Directory Proxy Server, the plugin will reject any
 * request that attempts to introduce a conflicting value for the target JSON
 * field.  It will also check for conflicts after each of those operations have
 * completed (to ensure that no conflicts were introduced by two conflicting
 * requests processed by different servers at the same time), and the plugin
 * will generate an alert to notify administrators of each conflict identified
 * so that they can be addressed by manual interaction.
 */
public class JSONFieldUniquenessPlugin
       extends Plugin
{
  /**
   * The parent DN for all JSON attribute constraints definitions contained in
   * the server configuration.
   */
  private static final DN JSON_ATTRIBUTE_CONSTRAINTS_PARENT_DN = new DN(
       new RDN("cn", "JSON Attribute Constraints"),
       new RDN("cn", "Cluster"),
       new RDN("cn", "config"));



  /**
   * The set of expected plugin types for a plugin that is running in the
   * Directory Server.
   */
  private final Set<String> EXPECTED_DIRECTORY_PLUGIN_TYPES =
       Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
            "preOperationAdd".toLowerCase(),
            "preOperationModify".toLowerCase(),
            "postSynchronizationAdd".toLowerCase(),
            "postSynchronizationModify".toLowerCase())));



  /**
   * The set of expected plugin types for a plugin that is running in the
   * Directory Proxy Server.
   */
  private final Set<String> EXPECTED_PROXY_PLUGIN_TYPES =
       Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
            "preParseAdd".toLowerCase(),
            "preParseModify".toLowerCase(),
            "postResponseAdd".toLowerCase(),
            "postResponseModify".toLowerCase())));



  /**
   * The name of the required argument used to specify the name or OID of the
   * LDAP attribute for which uniqueness is to be maintained.
   */
  private static final String ARG_ATTR_NAME = "ldap-attribute-name";



  /**
   * The name of the required argument used to specify the path to the JSON
   * field for which uniqueness is to be maintained.
   */
  private static final String ARG_JSON_FIELD_PATH = "json-field-path";



  /**
   * The name of the optional argument used to indicate whether the plugin
   * should ensure that values of the target field must not conflict with values
   * for the same field across other entries within the branches indicated by
   * the configured base DN(s).
   */
  private static final String ARG_REQUIRE_UNIQUENESS_ACROSS_ENTRIES =
       "require-uniqueness-across-entries";



  /**
   * The name of the optional argument used to indicate whether the plugin
   * should ensure that multiple values of the target field within the same
   * entry will be required to be unique.
   */
  private static final String ARG_REQUIRE_UNIQUENESS_WITHIN_AN_ENTRY =
       "require-uniqueness-within-an-entry";



  /**
   * The name of the optional argument used to specify the base DN(s) that will
   * be used for searches to find conflicting entries.
   */
  private static final String ARG_BASE_DN = "entry-base-dn";



  /**
   * The name of the optional argument used to provide an LDAP search filter
   * that indicates the set of entries for which uniqueness must be enforced.
   */
  private static final String ARG_UNIQUE_ENTRY_LDAP_FILTER =
       "unique-entry-ldap-filter";



  /**
   * The name of the optional argument used to provide a JSON object filter that
   * a JSON object contained in a value of the specified LDAP attribute will
   * be required to match before applying the uniqueness constraint to it.
   */
  private static final String ARG_UNIQUE_VALUE_JSON_OBJECT_FILTER =
       "unique-value-json-object-filter";



  /**
   * The name of the attribute in JSON field constraints definitions that
   * indicates whether the associated JSON field is indexed.
   */
  private static final String ATTR_JSON_FIELD_CONSTRAINTS_INDEX_VALUES =
       "ds-cfg-index-values";



  /**
   * The OID for the JSONObject syntax, which is the required syntax for the
   * configured LDAP attribute type.
   */
  private static final String OID_JSON_OBJECT_SYNTAX =
       "1.3.6.1.4.1.30221.2.3.4";



  // The attribute type for which to enforce uniqueness.
  private volatile AttributeType ldapAttributeType;

  // Indicates whether to require uniqueness across entries.
  private volatile boolean requireUniquenessAcrossEntries;

  // Indicates whether to require uniqueness across multiple values in the same
  // entry.
  private volatile boolean requireUniquenessWithinAnEntry;

  // The server context to use during processing.
  private volatile DirectoryServerContext serverContext;

  // An LDAP filter that identifies which entries should be subject to
  // uniqueness constraints.
  private volatile Filter uniqueEntryLDAPFilter;

  // An connection that may be used to perform internal operations within the
  // server.
  private volatile InternalConnection internalConnection;

  // A JSON object filter that identifies which attribute values should be
  // subject to uniqueness constraints.
  private volatile JSONObjectFilter uniqueValueJSONObjectFilter;

  // The base DNs for subtrees in which to enforce uniqueness.
  private volatile List<DN> baseDNs;

  // The path to the JSON field for which to enforce uniqueness.
  private volatile List<String> jsonFieldPath;

  // The configuration for this plugin.
  private volatile PluginConfig config;

  // The string representation of the JSON field path.
  private volatile String jsonFieldPathString;



  /**
   * Creates a new instance of this plugin.
   */
  public JSONFieldUniquenessPlugin()
  {
    ldapAttributeType = null;
    jsonFieldPath = null;
    jsonFieldPathString = null;
    requireUniquenessWithinAnEntry = true;
    requireUniquenessAcrossEntries = true;
    baseDNs = null;
    uniqueEntryLDAPFilter = null;
    uniqueValueJSONObjectFilter = null;

    serverContext = null;
    internalConnection = null;
    config = null;
  }



  /**
   * Retrieves the name for this extension.
   *
   * @return  The name for this extension.
   */
  @Override()
  public String getExtensionName()
  {
    return "JSON Field Uniqueness Plugin";
  }



  /**
   * Retrieves a description for this extension.
   *
   * @return  A description for this extension.
   */
  @Override()
  public String[] getExtensionDescription()
  {
    return new String[]
    {
      "The JSON Field Uniqueness Plugin can be used to enforce uniqueness " +
           "constraints for values of a specified JSON field stored in a " +
           "given LDAP attribute.  Uniqueness can be enforced across entries " +
           "so that the same value will not be allowed to appear in two " +
           "different entries.  It can also be enforced within the same " +
           "entry, so that each value of the JSON field (whether in separate " +
           "values of the LDAP attribute or within a JSON array in the same " +
           "LDAP attribute value).",

      "This plugin can be used in either the Directory Server or the " +
           "Directory Proxy Server, although it is not recommended to be " +
           "configured in both types of servers in the same topology.  In " +
           "deployments in which each Directory Server instance contains a " +
           "complete copy of all the data (regardless of whether requests to " +
           "those instances pass through the Directory Proxy Server), the " +
           "plugin should be configured in all Directory Server instances as " +
           "a pre-operation and post-synchronization plugin for add and " +
           "modify operations, and it should not be configured in any " +
           "Directory Proxy Server instances.  For each add and modify " +
           "request received from an external client, the plugin will check " +
           "to see if that operation includes any changes involving the " +
           "target JSON field within the appropriate LDAP attribute, and " +
           "will reject any change that would introduce a conflict with a " +
           "value that already exists in another entry, or that includes " +
           "duplicate values within the same entry if that is not allowed.  " +
           "It will also examine each add and modify operation that is " +
           "replicated from another server, and if that operation introduced " +
           "a conflict (which may arise if two conflicting requests are " +
           "processed by different servers at the same time), the plugin " +
           "will generate an alert to notify administrators of the conflict " +
           "so that it can be addressed by manual interaction.",

      "In deployments that use the Directory Proxy Server to separate data " +
           "across multiple servers, whether through entry balancing or by " +
           "separating different portions of the DIT across different sets " +
           "of servers, the plugin should be configured in all Directory " +
           "Proxy Server instances as a pre-parse and post-response plugin " +
           "for add and modify operations, and it should not be configured " +
           "in any of the Directory Server instances.  For each add and " +
           "modify request received by the Directory Proxy Server, the " +
           "plugin will reject any request that attempts to introduce a " +
           "conflicting value for the target JSON field.  It will also check " +
           "for conflicts after each of those operations have completed (to " +
           "ensure that no conflicts were introduced by two conflicting " +
           "requests processed by different servers at the same time), and " +
           "the plugin will generate an alert to notify administrators of " +
           "each conflict identified so that they can be addressed by " +
           "manual interaction."
    };
  }



  /**
   * Updates the provided argument parser to include the arguments for this
   * extension.
   *
   * @param  parser  The argument parser to update.
   */
  @Override()
  public void defineConfigArguments(final ArgumentParser parser)
         throws ArgumentException
  {
    parser.addArgument(new StringArgument(null, ARG_ATTR_NAME, true, 1,
         "{attribute}",
         "The name or OID of the LDAP attribute type whose values will be " +
              "examined for uniqueness conflicts.  This attribute type must " +
              "be defined with a JSON Object syntax (OID " +
              "1.3.6.1.4.1.30221.2.3.4) in the schema for all Directory " +
              "Server and Directory Proxy Server instances in the topology.  " +
              "All Directory Server instances must be configured with a JSON " +
              "object constraints definition for this attribute type.  This " +
              "is a required argument."));

    parser.addArgument(new StringArgument(null, ARG_JSON_FIELD_PATH, true, 1,
         "{path}",
         "The path to the JSON field for which uniqueness will be enforced.  " +
              "For a top-level field, the path should simply be the name of " +
              "that field.  For a nested field (a field contained in a JSON " +
              "object that is itself the value of a field in an outer JSON " +
              "object), the path to the field should be constructed by " +
              "concatenating the names of the fields in that path, in order " +
              "from outermost to innermost, and separating them with " +
              "periods.  For example, if a JSON object has a top-level field " +
              "named \"contact-info\" whose value is a JSON object that may " +
              "contain an \"email-address\" field, then the json-field-path " +
              "value for that field would be " +
              "\"contact-info.email-address\".  If the name of any field " +
              "in the path contains one or more periods, those periods " +
              "should be escaped by preceding them with a backslash so that " +
              "the only unescaped periods are those used as delimiters " +
              "between field names.  All Directory Server instances must be " +
              "configured with a JSON field constraints definition for this " +
              "field, and that definition must have the index-values " +
              "property set to true.  JSON field names will be treated in " +
              "a case-sensitive manner.  This is a required argument."));

    parser.addArgument(new BooleanValueArgument(null,
         ARG_REQUIRE_UNIQUENESS_ACROSS_ENTRIES, false, "{true|false}",
         "Indicates whether the plugin should check for uniqueness conflicts " +
              "across separate entries.  If this is true, or if it is " +
              "omitted from the configuration, then the plugin will reject " +
              "any add or modify request that attempts to set one or more " +
              "values for the target JSON field if any of those values is " +
              "already in use in any other entry, and it will raise an " +
              "administrative alert for each conflict discovered after the " +
              "change has already been applied.  If this is false, then the " +
              "plugin will not attempt to identify uniqueness conflicts in " +
              "separate entries.  At least one of the " +
              "require-uniqueness-across-entries and " +
              "require-uniqueness-within-an-entry properties must be set to " +
              "true.",
         Boolean.TRUE));

    parser.addArgument(new BooleanValueArgument(null,
         ARG_REQUIRE_UNIQUENESS_WITHIN_AN_ENTRY, false, "{true|false}",
         "Indicates whether the plugin should check for uniqueness conflicts " +
              "within the same entry.  If this is true, or if it is omitted " +
              "from the configuration, then the plugin will reject any add " +
              "or modify request that attempts to set one or more values for " +
              "the target JSON field if any of those values is already in " +
              "use in the same entry, whether in separate value for the " +
              "LDAP attribute, or in another value in a JSON array.  If this " +
              "is false, then the plugin will not attempt to identify " +
              "uniqueness conflicts within the same entry.  At least one of " +
              "the require-uniqueness-across-entries and " +
              "require-uniqueness-within-an-entry properties must be set to " +
              "true.",
         Boolean.TRUE));

    parser.addArgument(new DNArgument(null, ARG_BASE_DN, false, 0, "{dn}",
         "The base DN for a subtree in which the plugin will enforce " +
              "uniqueness constraints.  This can be provided multiple times " +
              "to specify multiple base DNs, and if it is not specified, " +
              "then the plugin will use a default set of base DNs that is " +
              "the set of public naming contexts for the server.  The " +
              "plugin will only examine add and modify requests that target " +
              "entries at or below one of these base DNs, and if uniqueness " +
              "is to be enforced across entries, it will ignore any " +
              "conflicts that may be identified outside any of the " +
              "configured base DNs."));

    parser.addArgument(new FilterArgument(null, ARG_UNIQUE_ENTRY_LDAP_FILTER,
         false, 1, "{ldap-filter}",
         "An optional LDAP search filter that may be used to identify the " +
              "set of entries for which uniqueness should be enforced.  If a " +
              "filter is configured, then the plugin will ignore any add or " +
              "modify requests that target entries that do not match this " +
              "filter, and if uniqueness is to be enforced across entries, " +
              "it will ignore conflicts that may be identified in entries " +
              "that do not match this filter."));

    parser.addArgument(new StringArgument(null,
         ARG_UNIQUE_VALUE_JSON_OBJECT_FILTER, false, 1, "{json-object-filter}",
         "An optional JSON object filter that may be used to identify which " +
              "values of a multivalued attribute should be subject to the " +
              "uniqueness constraints.  If a unique value JSON object " +
              "filter is configured, then the plugin will ignore field " +
              "values for JSON objects that do not match this filter."));
  }



  /**
   * Initializes this plugin.
   *
   * @param  serverContext  A handle to the server context for the server in
   *                        which this extension is running.
   * @param  config         The general configuration for this plugin.
   * @param  parser         The argument parser which has been initialized from
   *                        the configuration for this plugin.
   *
   * @throws  LDAPException  If a problem occurs while initializing this plugin.
   */
  @Override()
  public void initializePlugin(final DirectoryServerContext serverContext,
                               final PluginConfig config,
                               final ArgumentParser parser)
         throws LDAPException
  {
    this.serverContext = serverContext;
    this.config = config;


    // Make sure that we have an internal connection to use for processing
    // internal operations.
    if (internalConnection == null)
    {
      internalConnection = serverContext.getInternalRootConnection();
    }


    // Use the applyConfiguration method to set up the plugin.
    final ArrayList<String> adminActionsRequired = new ArrayList<String>(5);
    final ArrayList<String> messages = new ArrayList<String>(5);

    final ResultCode resultCode = applyConfiguration(config, parser,
         adminActionsRequired, messages);
    if (resultCode != ResultCode.SUCCESS)
    {
      throw new LDAPException(resultCode,
           "One or more errors occurred while trying to initialize the JSON " +
                "field uniqueness plugin using the configuration contained " +
                "in '" + config.getConfigObjectDN() + "':  " +
                StaticUtils.concatenateStrings(messages));
    }
  }



  /**
   * Performs any processing which may be necessary before the server starts
   * processing for an add request.  This will be invoked only for add
   * operations requested directly by clients, but not for add operations
   * received from another server via replication.
   *
   * @param  operationContext  The context for the add operation.
   * @param  request           The add request to be processed.  It may be
   *                           altered if desired.
   * @param  result            The result that will be returned to the client if
   *                           the plugin result indicates that processing on
   *                           the operation should be interrupted.  It may be
   *                           altered if desired.
   *
   * @return  Information about the result of the plugin processing.
   */
  @Override()
  public PreParsePluginResult doPreParse(
              final ActiveOperationContext operationContext,
              final UpdatableAddRequest request,
              final UpdatableAddResult result)
  {
    try
    {
      ensureUniqueness(request.getEntry().toLDAPSDKEntry(), serverContext,
           operationContext, config, internalConnection, ldapAttributeType,
           jsonFieldPathString, jsonFieldPath, requireUniquenessWithinAnEntry,
           requireUniquenessAcrossEntries, baseDNs, uniqueEntryLDAPFilter,
           uniqueValueJSONObjectFilter);

      return PreParsePluginResult.SUCCESS;
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);
      result.setResultData(le);

      return new PreParsePluginResult(false, false, true, true);
    }
  }



  /**
   * Performs any processing which may be necessary before the server actually
   * attempts to add an entry to the appropriate backend.  This will be invoked
   * only for add operations requested directly by clients, but not for add
   * operations received from another server via replication.
   *
   * @param  operationContext  The context for the add operation.
   * @param  request           The add request to be processed.
   * @param  result            The result that will be returned to the client if
   *                           the plugin result indicates that processing on
   *                           the operation should be interrupted.  It may be
   *                           altered if desired.
   *
   * @return  Information about the result of the plugin processing.
   */
  @Override()
  public PreOperationPluginResult doPreOperation(
              final ActiveOperationContext operationContext,
              final AddRequest request, final UpdatableAddResult result)
  {
    try
    {
      ensureUniqueness(request.getEntry().toLDAPSDKEntry(), serverContext,
           operationContext, config, internalConnection, ldapAttributeType,
           jsonFieldPathString, jsonFieldPath, requireUniquenessWithinAnEntry,
           requireUniquenessAcrossEntries, baseDNs, uniqueEntryLDAPFilter,
           uniqueValueJSONObjectFilter);

      return PreOperationPluginResult.SUCCESS;
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);
      result.setResultData(le);

      return new PreOperationPluginResult(false, false, true, true);
    }
  }



  /**
   * Performs any processing which may be necessary after all other processing
   * has been completed for an add operation and the response has been sent to
   * the client.  This will be invoked only for add operations requested
   * directly by clients, but not for add operations received from another
   * server via replication.
   *
   * @param  operationContext  The context for the add operation.
   * @param  request           The add request that was processed.
   * @param  result            The result that was returned to the client.
   *
   * @return  Information about the result of the plugin processing.
   */
  @Override()
  public PostResponsePluginResult doPostResponse(
              final CompletedOperationContext operationContext,
              final AddRequest request, final AddResult result)
  {
    try
    {
      ensureUniqueness(request.getEntry().toLDAPSDKEntry(), serverContext,
           operationContext, config, internalConnection, ldapAttributeType,
           jsonFieldPathString, jsonFieldPath, requireUniquenessWithinAnEntry,
           requireUniquenessAcrossEntries, baseDNs, uniqueEntryLDAPFilter,
           uniqueValueJSONObjectFilter);
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);

      // This means that a replicated operation generated a conflict.  We can't
      // do anything to prevent this, but we can generate an administrative
      // alert to notify administrators of the problem.
      serverContext.sendAlert(AlertSeverity.ERROR, le.getMessage());
    }

    return PostResponsePluginResult.SUCCESS;
  }



  /**
   * Performs any processing which may be necessary after all other processing
   * has been completed for an add operation which has been received from
   * another server via replication.
   *
   * @param  operationContext  The context for the add operation.
   * @param  request           The add request that was processed.
   * @param  result            The result that was returned to the client.
   */
  @Override()
  public void doPostReplication(
                   final CompletedOperationContext operationContext,
                   final AddRequest request, final AddResult result)
  {
    try
    {
      ensureUniqueness(request.getEntry().toLDAPSDKEntry(), serverContext,
           operationContext, config, internalConnection, ldapAttributeType,
           jsonFieldPathString, jsonFieldPath, requireUniquenessWithinAnEntry,
           requireUniquenessAcrossEntries, baseDNs, uniqueEntryLDAPFilter,
           uniqueValueJSONObjectFilter);
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);

      // This means that a replicated operation generated a conflict.  We can't
      // do anything to prevent this, but we can generate an administrative
      // alert to notify administrators of the problem.
      serverContext.sendAlert(AlertSeverity.ERROR, le.getMessage());
    }
  }



  /**
   * Performs any processing which may be necessary before the server starts
   * processing for a modify request.  This will be invoked only for modify
   * operations requested directly by clients, but not for modify operations
   * received from another server via replication.
   *
   * @param  operationContext  The context for the modify operation.
   * @param  request           The modify request to be processed.  It may be
   *                           altered if desired.
   * @param  result            The result that will be returned to the client if
   *                           the plugin result indicates that processing on
   *                           the operation should be interrupted.  It may be
   *                           altered if desired.
   *
   * @return  Information about the result of the plugin processing.
   */
  @Override()
  public PreParsePluginResult doPreParse(
              final ActiveOperationContext operationContext,
              final UpdatableModifyRequest request,
              final UpdatableModifyResult result)
  {
    // We aren't given access to the entry (either before or after the changes
    // are applied), so we'll need to fetch it from a backend server and apply
    // the changes to it so we'll have a representation of the updated entry for
    // examination.  But we'll only do that if the request could result in a
    // conflict, since otherwise retrieving the entry would be wasted effort.
    //
    // First, we want to make sure that we're dealing with a consistent
    // configuration, so get all of the configuration properties first.
    final AttributeType attributeType = ldapAttributeType;
    final List<String> fieldPath = jsonFieldPath;
    final String fieldPathString = jsonFieldPathString;
    final boolean uniqueWithinEntry = requireUniquenessWithinAnEntry;
    final boolean uniqueAcrossEntries = requireUniquenessAcrossEntries;
    final Filter uniqueEntryFilter = uniqueEntryLDAPFilter;
    final JSONObjectFilter uniqueValueFilter = uniqueValueJSONObjectFilter;

    if (! couldCreateConflict(request, attributeType, baseDNs, fieldPath))
    {
      return PreParsePluginResult.SUCCESS;
    }


    // Retrieve the entry from a backend server.
    final SearchResultEntry currentEntry;
    try
    {
      currentEntry = internalConnection.getEntry(request.getDN(), "*", "+");
      if (currentEntry == null)
      {
        result.setResultCode(ResultCode.NO_SUCH_OBJECT);
        result.setDiagnosticMessage("Could not find entry " + request.getDN());
        return new PreParsePluginResult(false, false, true, true);
      }
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);
      result.setResultData(le);
      return new PreParsePluginResult(false, false, true, true);
    }


    // Update the entry with the modifications from the request.
    final com.unboundid.ldap.sdk.Entry updatedEntry;
    try
    {
      updatedEntry = com.unboundid.ldap.sdk.Entry.applyModifications(
           currentEntry, false, request.getModifications());
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);
      result.setResultData(le);
      return new PreParsePluginResult(false, false, true, true);
    }


    try
    {
      ensureUniqueness(updatedEntry, serverContext, operationContext, config,
           internalConnection, attributeType, fieldPathString, fieldPath,
           uniqueWithinEntry, uniqueAcrossEntries, baseDNs, uniqueEntryFilter,
           uniqueValueFilter);

      return PreParsePluginResult.SUCCESS;
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);
      result.setResultData(le);
      return new PreParsePluginResult(false, false, true, true);
    }
  }



  /**
   * Performs any processing which may be necessary before the server actually
   * attempts to update the entry in the backend.  This will be invoked only for
   * modify operations requested directly by clients, but not for modify
   * operations received from another server via replication.
   *
   * @param  operationContext  The context for the modify operation.
   * @param  request           The modify request to be processed.
   * @param  result            The result that will be returned to the client if
   *                           the plugin result indicates that processing on
   *                           the operation should be interrupted.  It may be
   *                           altered if desired.
   * @param  oldEntry          The entry as it appeared before the modifications
   *                           were applied.
   * @param  newEntry          The updated entry as it will appear after the
   *                           modifications have been applied.
   *
   * @return  Information about the result of the plugin processing.
   */
  @Override()
  public PreOperationPluginResult doPreOperation(
              final ActiveOperationContext operationContext,
              final ModifyRequest request, final UpdatableModifyResult result,
              final Entry oldEntry, final Entry newEntry)
  {
    try
    {
      ensureUniqueness(newEntry.toLDAPSDKEntry(), serverContext,
           operationContext, config, internalConnection, ldapAttributeType,
           jsonFieldPathString, jsonFieldPath, requireUniquenessWithinAnEntry,
           requireUniquenessAcrossEntries, baseDNs, uniqueEntryLDAPFilter,
           uniqueValueJSONObjectFilter);

      return PreOperationPluginResult.SUCCESS;
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);
      result.setResultData(le);

      return new PreOperationPluginResult(false, false, true, true);
    }
  }



  /**
   * Performs any processing which may be necessary after all other processing
   * has been completed for a modify operation and the response has been sent
   * to the client.  This will be invoked only for modify operations requested
   * directly by clients, but not for modify operations received from another
   * server via replication.
   *
   * @param  operationContext  The context for the modify operation.
   * @param  request           The modify request that was processed.
   * @param  result            The result that was returned to the client.
   *
   * @return  Information about the result of the plugin processing.
   */
  @Override()
  public PostResponsePluginResult doPostResponse(
              final CompletedOperationContext operationContext,
              final ModifyRequest request, final ModifyResult result)
  {
    // We aren't given access to the updated entry, so we'll need to fetch it
    // from a backend server.  But we'll only do that if the request could
    // result in a conflict, since otherwise retrieving the entry would be
    // wasted effort.
    //
    // First, we want to make sure that we're dealing with a consistent
    // configuration, so get all of the configuration properties first.
    final AttributeType attributeType = ldapAttributeType;
    final List<String> fieldPath = jsonFieldPath;
    final String fieldPathString = jsonFieldPathString;
    final boolean uniqueWithinEntry = requireUniquenessWithinAnEntry;
    final boolean uniqueAcrossEntries = requireUniquenessAcrossEntries;
    final Filter uniqueEntryFilter = uniqueEntryLDAPFilter;
    final JSONObjectFilter uniqueValueFilter = uniqueValueJSONObjectFilter;

    if (! couldCreateConflict(request, attributeType, baseDNs, fieldPath))
    {
      return PostResponsePluginResult.SUCCESS;
    }


    // Retrieve the entry.
    final SearchResultEntry entry;
    try
    {
      entry = internalConnection.getEntry(request.getDN(), "*", "+");
      if (entry == null)
      {
        if (serverContext.debugEnabled())
        {
          serverContext.debugWarning("The JSON field uniqueness plugin " +
               "defined in configuration entry '" + config.getConfigObjectDN() +
               "' could not find entry '" + request.getDN() +
               "' targeted by proxied modify operation conn=" +
               operationContext.getConnectionID() + " op=" +
               operationContext.getOperationID() +
               ".  This likely indicates that the entry was deleted shortly " +
               "after the modification.  At any rate, no uniqueness conflict " +
               "processing can be performed for this entry.");
        }

        return PostResponsePluginResult.SUCCESS;
      }
    }
    catch (final Exception e)
    {
      if (serverContext.debugEnabled())
      {
        serverContext.debugCaught(e);
        serverContext.debugError("The JSON field uniqueness plugin " +
             "defined in configuration entry '" + config.getConfigObjectDN() +
             "' encountered an error while trying to retrieve entry '" +
             request.getDN() + "' targeted by proxied modify operation " +
             "conn=" + operationContext.getConnectionID() + " op=" +
             operationContext.getOperationID() +
             ":  " + StaticUtils.getExceptionMessage(e) +
             ".  No uniqueness conflict processing can be performed for this " +
             "entry.");
      }

      return PostResponsePluginResult.SUCCESS;
    }


    try
    {
      ensureUniqueness(entry, serverContext, operationContext, config,
           internalConnection, attributeType, fieldPathString, fieldPath,
           uniqueWithinEntry, uniqueAcrossEntries, baseDNs, uniqueEntryFilter,
           uniqueValueFilter);
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);

      // This means that a proxied operation generated a conflict.  We can't do
      // anything to prevent this, but we can generate an administrative alert
      // to notify administrators of the problem.
      serverContext.sendAlert(AlertSeverity.ERROR, le.getMessage());
    }

    return PostResponsePluginResult.SUCCESS;
  }



  /**
   * Performs any processing which may be necessary after all other processing
   * has been completed for a modify operation which has been received from
   * another server via replication.
   *
   * @param  operationContext  The context for the modify operation.
   * @param  request           The modify request that was processed.
   * @param  result            The result that was returned to the client.
   */
  @Override()
  public void doPostReplication(
                   final CompletedOperationContext operationContext,
                   final ModifyRequest request, final ModifyResult result)
  {
    // We aren't given access to the updated entry, so we'll need to fetch it
    // from the backend.  But we'll only do that if the request could result
    // in a conflict, since otherwise retrieving the entry would be wasted
    // effort.
    //
    // First, we want to make sure that we're dealing with a consistent
    // configuration, so get all of the configuration properties first.
    final AttributeType attributeType = ldapAttributeType;
    final List<String> fieldPath = jsonFieldPath;
    final String fieldPathString = jsonFieldPathString;
    final boolean uniqueWithinEntry = requireUniquenessWithinAnEntry;
    final boolean uniqueAcrossEntries = requireUniquenessAcrossEntries;
    final Filter uniqueEntryFilter = uniqueEntryLDAPFilter;
    final JSONObjectFilter uniqueValueFilter = uniqueValueJSONObjectFilter;

    if (! couldCreateConflict(request, attributeType, baseDNs, fieldPath))
    {
      return;
    }


    // Retrieve the entry.
    final SearchResultEntry entry;
    try
    {
      entry = internalConnection.getEntry(request.getDN(), "*", "+");
      if (entry == null)
      {
        if (serverContext.debugEnabled())
        {
          serverContext.debugWarning("The JSON field uniqueness plugin " +
               "defined in configuration entry '" + config.getConfigObjectDN() +
               "' could not find entry '" + request.getDN() +
               "' targeted by replicated modify operation conn=" +
               operationContext.getConnectionID() + " op=" +
               operationContext.getOperationID() +
               ".  This likely indicates that the entry was deleted shortly " +
               "after the modification.  At any rate, no uniqueness conflict " +
               "processing can be performed for this entry.");
        }
        return;
      }
    }
    catch (final Exception e)
    {
      if (serverContext.debugEnabled())
      {
        serverContext.debugCaught(e);
        serverContext.debugError("The JSON field uniqueness plugin " +
             "defined in configuration entry '" + config.getConfigObjectDN() +
             "' encountered an error while trying to retrieve entry '" +
             request.getDN() + "' targeted by replicated modify operation " +
             "conn=" + operationContext.getConnectionID() + " op=" +
             operationContext.getOperationID() +
             ":  " + StaticUtils.getExceptionMessage(e) +
             ".  No uniqueness conflict processing can be performed for this " +
             "entry.");
      }
      return;
    }


    try
    {
      ensureUniqueness(entry, serverContext, operationContext, config,
           internalConnection, attributeType, fieldPathString, fieldPath,
           uniqueWithinEntry, uniqueAcrossEntries, baseDNs, uniqueEntryFilter,
           uniqueValueFilter);
    }
    catch (final LDAPException le)
    {
      serverContext.debugCaught(le);

      // This means that a replicated operation generated a conflict.  We can't
      // do anything to prevent this, but we can generate an administrative
      // alert to notify administrators of the problem.
      serverContext.sendAlert(AlertSeverity.ERROR, le.getMessage());
    }
  }



  /**
   * Attempts to determine whether the provided modify request could create a
   * uniqueness conflict.
   *
   * @param  request        The modify request being processed.
   * @param  attributeType  The LDAP attribute type for which uniqueness will be
   *                        enforced.
   * @param  baseDNList     The list of base DNs for which uniqueness will be
   *                        enforced.
   * @param  fieldPath      The path to the JSON field for which uniqueness will
   *                        be enforced.
   *
   * @return  {@code true} if the provided modify request could create a
   *          uniqueness conflict, or {@code false} if not.
   */
  private boolean couldCreateConflict(final ModifyRequest request,
                                      final AttributeType attributeType,
                                      final List<DN> baseDNList,
                                      final List<String> fieldPath)
  {
    // See if the target entry is within any of the configured base DNs.  If
    // not, then we don't care about it.
    try
    {
      final DN parsedEntryDN = new DN(request.getDN());

      boolean withinBaseDN = false;
      for (final DN baseDN : baseDNList)
      {
        if (parsedEntryDN.isDescendantOf(baseDN, true))
        {
          withinBaseDN = true;
          break;
        }
      }

      if (! withinBaseDN)
      {
        return false;
      }
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
    }


    // Now, iterate through the modifications to see if any of them could
    // generate a conflict.
    for (final Modification m : request.getModifications())
    {
      // We only care about modification types of ADD or REPLACE.
      if (! ((m.getModificationType() == ModificationType.ADD) ||
             (m.getModificationType() == ModificationType.REPLACE)))
      {
        continue;
      }

      // We only care about modifications that have values.  A REPLACE
      // modification might not have any values if it's intended to remove
      // the attribute from the entry.
      final String[] values = m.getValues();
      if ((values == null) || (values.length == 0))
      {
        continue;
      }

      // See if the modification targets the right attribute type.
      //
      // NOTE:  It's possible that the modification targeted an attribute with
      // options, so we need to check the base name without any options.
      final String baseName = Attribute.getBaseName(m.getAttributeName());
      if (! attributeType.hasNameOrOID(baseName))
      {
        continue;
      }

      // Try to parse each of the values as a JSON object and see if any of
      // them contains the target field.  That'll be enough to convince us
      // that we need to get the entry.
      for (final String value : values)
      {
        try
        {
          final JSONObject o = new JSONObject(value);
          final List<JSONValue> fieldValues = getValues(o, fieldPath);
          if (fieldValues.isEmpty())
          {
            continue;
          }

          return true;
        }
        catch (final Exception e)
        {
          serverContext.debugCaught(e);
        }
      }
    }


    // If we've gotten here, then the modification couldn't have introduced a
    // conflict.
    return false;
  }



  /**
   * Performs all necessary uniqueness processing for the provided entry.
   *
   * @param  entry                The entry to examine.
   * @param  serverContext        The server context.
   * @param  operationContext     The operation context.
   * @param  config               The configuration for the plugin.
   * @param  internalConnection   The internal connection to use to perform
   *                              internal searches.
   * @param  attributeType        The target attribute type.
   * @param  fieldPathString      The string representation of the JSON field
   *                              path.
   * @param  fieldPath            The components that comprise the path to the
   *                              target JSON field.
   * @param  uniqueWithinEntry    Indicates whether to enforce uniqueness across
   *                              multiple values within the same entry.
   * @param  uniqueAcrossEntries  Indicates whether to enforce uniqueness across
   *                              entries within the specified set of base DNs.
   * @param  baseDNs              The set of base DNs below which to enforce
   *                              uniqueness.
   * @param  uniqueEntryFilter    An optional filter that entries must match.
   * @param  uniqueValueFilter    An optional filter that values must match.
   *
   * @throws  LDAPException  If a conflict is found.
   */
  private static void ensureUniqueness(final com.unboundid.ldap.sdk.Entry entry,
                           final DirectoryServerContext serverContext,
                           final OperationContext operationContext,
                           final PluginConfig config,
                           final InternalConnection internalConnection,
                           final AttributeType attributeType,
                           final String fieldPathString,
                           final List<String> fieldPath,
                           final boolean uniqueWithinEntry,
                           final boolean uniqueAcrossEntries,
                           final List<DN> baseDNs,
                           final Filter uniqueEntryFilter,
                           final JSONObjectFilter uniqueValueFilter)
          throws LDAPException
  {
    // See if the entry is below a base DN that we care about.
    try
    {
      final DN parsedEntryDN = entry.getParsedDN();
      boolean withinBaseDN = false;
      for (final DN baseDN : baseDNs)
      {
        if (parsedEntryDN.isDescendantOf(baseDN, true))
        {
          withinBaseDN = true;
          break;
        }
      }

      if (! withinBaseDN)
      {
        if (serverContext.debugEnabled())
        {
          serverContext.debugInfo("The JSON field uniqueness plugin " +
               "defined in configuration entry '" + config.getConfigObjectDN() +
               " is skipping uniqueness processing for entry '" +
               entry.getDN() + "' for conn=" +
               operationContext.getConnectionID() + " op=" +
               operationContext.getOperationID() + " because it is not " +
               "within any of the configured base DNs " + baseDNs);
        }
        return;
      }
    }
    catch (final Exception e)
    {
      if (serverContext.debugEnabled())
      {
        serverContext.debugCaught(e);
        serverContext.debugWarning("The JSON field uniqueness plugin " +
             "defined in configuration entry '" + config.getConfigObjectDN() +
             "' could not parse DN '" + entry.getDN() +
             "' encountered while processing operation conn=" +
             operationContext.getConnectionID() + " op=" +
             operationContext.getOperationID() + ":  " +
             StaticUtils.getExceptionMessage(e) + ".  Going to assume that " +
             "the entry is within a base DN for which uniqueness should be " +
             "enforced.");
      }
    }


    // If there is a unique entry filter, then see if the entry matches it.
    if (uniqueEntryFilter != null)
    {
      try
      {
        if (! uniqueEntryFilter.matchesEntry(entry))
        {
          if (serverContext.debugEnabled())
          {
            serverContext.debugInfo("The JSON field uniqueness plugin " +
                 "defined in configuration entry '" +
                 config.getConfigObjectDN() + " is skipping uniqueness " +
                 "processing for entry '" + entry.getDN() + "' for conn=" +
                 operationContext.getConnectionID() + " op=" +
                 operationContext.getOperationID() + " because it does not " +
                 "match " + ARG_UNIQUE_ENTRY_LDAP_FILTER + " value " +
                 uniqueEntryFilter);
          }
          return;
        }
      }
      catch (final Exception e)
      {
        if (serverContext.debugEnabled())
        {
          serverContext.debugCaught(e);
          serverContext.debugWarning("The JSON field uniqueness plugin " +
               "defined in configuration entry '" + config.getConfigObjectDN() +
               "' encountered an error while evaluating " +
               ARG_UNIQUE_ENTRY_LDAP_FILTER + " value " + uniqueEntryFilter +
               " against entry '" + entry.getDN() + "' for conn=" +
               operationContext.getConnectionID() + " op=" +
               operationContext.getOperationID() + ":  " +
               StaticUtils.getExceptionMessage(e) + ".  Going to assume that " +
               "the entry matches the filter.");
        }
      }
    }


    // Iterate through the attributes in the entry to get a list of all values
    // for the target field from the target attribute type.
    final ArrayList<JSONValue> fieldValues = new ArrayList<JSONValue>(10);
    for (final Attribute a : entry.getAttributes())
    {
      // See if the attribute has the appropriate attribute type.
      //
      // NOTE:  The attribute could have options, so make sure to check the
      // base name.
      if (! attributeType.hasNameOrOID(a.getBaseName()))
      {
        continue;
      }


      // Iterate through the values of the attribute and try to parse them as
      // JSON objects.
      for (final String value : a.getValues())
      {
        final JSONObject o;
        try
        {
          o = new JSONObject(value);
        }
        catch (final Exception e)
        {
          if (serverContext.debugEnabled())
          {
            serverContext.debugCaught(e);
            serverContext.debugWarning("The JSON field uniqueness plugin " +
                 "defined in configuration entry '" +
                 config.getConfigObjectDN() + "' encountered " + a.getName() +
                 " value '" + value + "' in entry '" + entry.getDN() +
                 "' that could not be parsed as a valid JSON object for conn=" +
                 operationContext.getConnectionID() + " op=" +
                 operationContext.getOperationID() + ":  " +
                 StaticUtils.getExceptionMessage(e));
          }
          continue;
        }

        // If we have a uniqueValueFilter, then see if the provided object
        // matches that filter.  If not, then skip this value.
        if ((uniqueValueFilter != null) &&
            (! uniqueValueFilter.matchesJSONObject(o)))
        {
          continue;
        }


        // Extract the values of the target field from the object.  Iterate
        // through them and see if any of them match a value that we've already
        // found.  At the very least, we only want to do the processing for each
        // value once, but we might also want to reject conflicts in the same
        // entry.
        for (final JSONValue jsonValue : getValues(o, fieldPath))
        {
          boolean conflictFound = false;
          for (final JSONValue v : fieldValues)
          {
            if (v.equals(jsonValue, false, true, false))
            {
              conflictFound = true;
              break;
            }
          }

          if (conflictFound)
          {
            if (uniqueWithinEntry)
            {
              throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
                   "Entry '" + entry.getDN() + "' has duplicate values for " +
                        "JSON field " + fieldPathString +
                        " but the server is configured to require all values " +
                        "for that field to be unique, even within the same " +
                        "entry.");
            }
          }
          else
          {
            fieldValues.add(jsonValue);
          }
        }
      }
    }


    // If we shouldn't check for conflicts across other entries, or if we didn't
    // find any field values, then we're done.
    if (fieldValues.isEmpty())
    {
      if (serverContext.debugEnabled())
      {
        serverContext.debugInfo("The JSON field uniqueness plugin " +
             "defined in configuration entry '" + config.getConfigObjectDN() +
             "' did not find any " + attributeType.getNameOrOID() +
             " values for field " + fieldPathString + " in entry '" +
             entry.getDN() + "' for conn=" +
             operationContext.getConnectionID() + " op=" +
             operationContext.getOperationID());
      }

      return;
    }

    if (! uniqueAcrossEntries)
    {
      if (serverContext.debugEnabled())
      {
        serverContext.debugInfo("The JSON field uniqueness plugin " +
             "defined in configuration entry '" + config.getConfigObjectDN() +
             "' is only configured to look for duplicate " + fieldPathString +
             " values in attribute " + attributeType.getNames() +
             " within the same entry, and entry '" + entry.getDN() +
             "' for conn=" + operationContext.getConnectionID() + " op=" +
             operationContext.getOperationID() +
             " did not have any such conflicts.");
      }

      return;
    }

    if (serverContext.debugEnabled())
    {
      serverContext.debugInfo("The JSON field uniqueness plugin defined in " +
           "configuration entry '" + config.getConfigObjectDN() +
           "' found one or more values for JSON field " + fieldPathString +
           " in attribute " + attributeType.getNameOrOID() +
           " in entry '" + entry.getDN() + " for operation conn=" +
           operationContext.getConnectionID() + " op=" +
           operationContext.getOperationID() + "':  " + fieldValues +
           ".  Going to search for entries with conflicting values.");
    }


    // We need to look for conflicts across other entries.  First, generate the
    // equals or equalsAny filter to use to identify entries with conflicting
    // values.
    JSONObjectFilter jsonObjectFilter;
    if (fieldValues.size() == 1)
    {
      jsonObjectFilter =
           new EqualsJSONObjectFilter(fieldPath, fieldValues.get(0));
    }
    else
    {
      jsonObjectFilter = new EqualsAnyJSONObjectFilter(fieldPath, fieldValues);
    }

    // If we have a unique value filter, then we'll need to AND the JSON object
    // filter we just created with a NOT of that value filter so that we exclude
    // objects that don't match that filter.
    if (uniqueValueFilter != null)
    {
      jsonObjectFilter = new ANDJSONObjectFilter(jsonObjectFilter,
           new NegateJSONObjectFilter(uniqueValueFilter));
    }


    // Get an LDAP filter that will allow us to process the JSON object filter.
    Filter ldapFilter =
         jsonObjectFilter.toLDAPFilter(attributeType.getNameOrOID());


    // If we have a unique entry filter, then we'll need to AND the LDAP filter
    // we just created with that unique entry filter so that we only check
    // entries that match that filter.
    if (uniqueEntryFilter != null)
    {
      ldapFilter = Filter.createANDFilter(ldapFilter, uniqueEntryFilter);
    }


    // Iterate through the configured base DNs and search beneath them for
    // conflicting entries.
    //
    // NOTE:  Use a size limit of 1 for the search request so that we don't
    // waste time finding all the matches once we know that there are at least
    // two matching entries.  One matching entry may be okay if it's the entry
    // that we're currently validating, and we should only report a conflict for
    // one match if it doesn't match the DN of the entry we're currently
    // validating.  But any more than one match (which should trigger a
    // SIZE_LIMIT_EXCEEDED result) means that there's definitely a conflict.
    final SearchRequest searchRequest = new SearchRequest("", SearchScope.SUB,
         DereferencePolicy.NEVER, 1, 0, false, ldapFilter, "1.1");
    for (final DN baseDN : baseDNs)
    {
      searchRequest.setBaseDN(baseDN);

      SearchResult searchResult;
      try
      {
        searchResult = internalConnection.search(searchRequest);
      }
      catch (final LDAPSearchException lse)
      {
        searchResult = lse.getSearchResult();
      }

      if (serverContext.debugEnabled())
      {
        serverContext.debugInfo("The JSON field uniqueness plugin " +
             "defined in configuration entry '" + config.getConfigObjectDN() +
             "' got search result " + searchResult + " for internal search " +
             searchRequest);
      }

      switch (searchResult.getResultCode().intValue())
      {
        case ResultCode.SUCCESS_INT_VALUE:
          // The search completed successfully, but we need to check the entries
          // that were returned to see if it matched any entry other than the
          // target entry.
          switch (searchResult.getEntryCount())
          {
            case 0:
              break;
            case 1:
              final DN parsedThisDN = entry.getParsedDN();
              final DN parsedFoundDN =
                   searchResult.getSearchEntries().get(0).getParsedDN();
              if (! parsedThisDN.equals(parsedFoundDN))
              {
                throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
                     "Entry '" + entry.getDN() + "' has one or more values " +
                          "for JSON field '" + fieldPathString + "' that " +
                          "already exist in at least one other entry in " +
                          "the server, but the server is configured to " +
                          "require all values for that field to be unique.");
              }
              break;
            default:
              throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
                   "Entry '" + entry.getDN() + "' has one or more values for " +
                        "JSON field '" + fieldPathString +
                        "' that already exist in at least one other entry in " +
                        "the server, but the server is configured to " +
                        "require all values for that field to be unique.");
          }
          break;

        case ResultCode.NO_SUCH_OBJECT_INT_VALUE:
          // This means that the search base didn't exist.  This definitely
          // means no conflict.
          break;

        case ResultCode.SIZE_LIMIT_EXCEEDED_INT_VALUE:
          // This means that there was definitely a conflict.
          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
               "Entry '" + entry.getDN() + "' has one or more values for " +
                    "JSON field '" + fieldPathString +
                    "' that already exist in at least one other entry in " +
                    "the server, but the server is configured to " +
                    "require all values for that field to be unique.");

        default:
          // This indicates that some other error occurred that prevented the
          // search from succeeding.  We'll treat this like a conflict in that
          // we'll throw an exception to reject the operation (or alert if it's
          // a replicated operation that's already been applied somewhere else)
          // but use a different message for the exception.
          throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
               "An error occurred while looking for uniqueness conflicts " +
                    "for values of the '" + fieldPathString +
                    "' field in attribute " + attributeType.getNameOrOID() +
                    " (result code " + searchResult.getResultCode() +
                    ", diagnostic message '" +
                    searchResult.getDiagnosticMessage() + "'.");
      }
    }


    // If we've gotten here, then there are no conflicts.
    if (serverContext.debugEnabled())
    {
      serverContext.debugInfo("The JSON field uniqueness plugin defined in " +
           "configuration entry '" + config.getConfigObjectDN() +
           "' did not find any conflicts for values of JSON field " +
           fieldPathString + " in attribute " + attributeType.getNameOrOID() +
           " in entry '" + entry.getDN() + " for operation conn=" +
           operationContext.getConnectionID() + " op=" +
           operationContext.getOperationID());
    }
  }



  /**
   * Retrieves a list of the values for the specified field from the provided
   * object.
   *
   * @param  o          The JSON object to examine.
   * @param  fieldPath  The path to the JSON field for which to retrieve the
   *                    values.
   *
   * @return  The set of values that match the provided field name specifier, or
   *          an empty list if the provided JSON object does not have any fields
   *          matching the provided specifier.
   */
  private static List<JSONValue> getValues(final JSONObject o,
                                           final List<String> fieldPath)
  {
    final ArrayList<JSONValue> values = new ArrayList<JSONValue>(10);
    getValues(o, fieldPath, 0, values);
    return values;
  }



  /**
   * Adds all values for the specified field to the provided list.
   *
   * @param  o               The JSON object to examine.
   * @param  fieldPath       The path to the JSON field for which to retrieve
   *                         the values.
   * @param  fieldNameIndex  The current index into the field name specifier.
   * @param  values          The list into which matching values should be
   *                         added.
   */
  private static void getValues(final JSONObject o,
                                final List<String> fieldPath,
                                final int fieldNameIndex,
                                final List<JSONValue> values)
  {
    final JSONValue v = o.getField(fieldPath.get(fieldNameIndex));
    if (v == null)
    {
      return;
    }

    final int nextIndex = fieldNameIndex + 1;
    if (nextIndex < fieldPath.size())
    {
      // This indicates that there are more elements in the field name
      // specifier.  The value must either be a JSON object that we can look
      // further into, or it must be an array containing one or more JSON
      // objects.
      if (v instanceof JSONObject)
      {
        getValues((JSONObject) v, fieldPath, nextIndex, values);
      }
      else if (v instanceof JSONArray)
      {
        getValuesFromArray((JSONArray) v, fieldPath, nextIndex, values);
      }

      return;
    }

    // If we've gotten here, then there is no more of the field specifier, so
    // the value we retrieved matches the specifier.  Add it to the list of
    // values.
    values.add(v);
  }



  /**
   * Calls {@code getValues} for any elements of the provided array that are
   * JSON objects, recursively descending into any nested arrays.
   *
   * @param  a               The array to process.
   * @param  fieldPath       The path to the JSON field for which to retrieve
   *                         the values.
   * @param  fieldNameIndex  The current index into the field name specifier.
   * @param  values          The list into which matching values should be
   *                         added.
   */
  private static void getValuesFromArray(final JSONArray a,
                                         final List<String> fieldPath,
                                         final int fieldNameIndex,
                                         final List<JSONValue> values)
  {
    for (final JSONValue v : a.getValues())
    {
      if (v instanceof JSONObject)
      {
        getValues((JSONObject) v, fieldPath, fieldNameIndex, values);
      }
      else if (v instanceof JSONArray)
      {
        getValuesFromArray((JSONArray) v, fieldPath, fieldNameIndex, values);
      }
    }
  }



  /**
   * Indicates whether the configuration represented by the provided argument
   * parser is acceptable for use by this extension.  The parser will have been
   * used to parse any configuration available for this extension, and any
   * automatic validation will have been performed.  This method may be used to
   * perform any more complex validation which cannot be performed automatically
   * by the argument parser.
   *
   * @param  config               The general configuration for this extension.
   * @param  parser               The argument parser that has been used to
   *                              parse the proposed configuration for this
   *                              extension.
   * @param  unacceptableReasons  A list to which messages may be added to
   *                              provide additional information about why the
   *                              provided configuration is not acceptable.
   *
   * @return  {@code true} if the configuration in the provided argument parser
   *          appears to be acceptable, or {@code false} if not.
   */
  @Override()
  public boolean isConfigurationAcceptable(final PluginConfig config,
                      final ArgumentParser parser,
                      final List<String> unacceptableReasons)
  {
    // If the server context is null, then get an instance from the provided
    // configuration.
    if (serverContext == null)
    {
      serverContext = config.getServerContext();
    }


    // Get an internal connection to use for processing internal operations.
    if (internalConnection == null)
    {
      internalConnection = serverContext.getInternalRootConnection();
    }


    // Make sure that the plugin is configured with the appropriate set of
    // plugin types based on the type of product in which it is running.
    boolean acceptable = true;
    final boolean isDirectoryServer;
    final Set<String> pluginTypes = config.getPluginTypes();
    if (serverContext.isDirectoryFunctionalityAvailable())
    {
      isDirectoryServer = true;
      if (! pluginTypes.equals(EXPECTED_DIRECTORY_PLUGIN_TYPES))
      {
        acceptable = false;
        unacceptableReasons.add("When the " + getExtensionName() +
             " is used in the Directory Server, it must be configured with " +
             "the following plugin types:  " + EXPECTED_DIRECTORY_PLUGIN_TYPES +
             '.');
      }
    }
    else if (serverContext.isDirectoryProxyFunctionalityAvailable())
    {
      isDirectoryServer = false;
      if (! pluginTypes.equals(EXPECTED_PROXY_PLUGIN_TYPES))
      {
        acceptable = false;
        unacceptableReasons.add("When the " + getExtensionName() +
             " is used in the Directory Proxy Server, it must be configured " +
             "with the following plugin types:  " +
             EXPECTED_PROXY_PLUGIN_TYPES + '.');
      }
    }
    else
    {
      isDirectoryServer = false;
      acceptable = false;
      unacceptableReasons.add("The " + getExtensionName() +
           " can only be used in the Directory Server or Directory Proxy " +
           "Server.");
    }


    // Get the configured LDAP attribute type.  It must be defined in the server
    // schema.
    final AttributeType attrType;
    final StringArgument attrNameArg = parser.getStringArgument(ARG_ATTR_NAME);
    if ((attrNameArg == null) || (! attrNameArg.isPresent()))
    {
      // The argument parser should ensure that this never happens.
      acceptable = false;
      unacceptableReasons.add("No value was provided for the required " +
           ARG_ATTR_NAME + " argument.");
      attrType = null;
    }
    else
    {
      attrType = serverContext.getSchema().getAttributeType(
           attrNameArg.getValue(), false);
      if (attrType == null)
      {
        acceptable = false;
        unacceptableReasons.add("Value '" + attrNameArg.getValue() +
             "' for argument " + ARG_ATTR_NAME + " does not represent the " +
             "name or OID for any attribute type defined in the server " +
             "schema.");
      }
    }


    DN jsonAttributeConstraintsDN = null;
    if (attrType != null)
    {
      // The attribute type must have a JSONObject syntax.
      if ((attrType.getSyntax() == null) ||
          (! attrType.getSyntax().getOID().equals(OID_JSON_OBJECT_SYNTAX)))
      {
        acceptable = false;
        unacceptableReasons.add("Attribute type " + attrNameArg.getValue() +
             " is not defined with a JSONObject syntax (OID " +
             OID_JSON_OBJECT_SYNTAX + ").");
      }


      // If the plugin is being run in the Directory Server, then make sure that
      // the server is configured with a JSON attribute constraints definition
      // for the specified attribute type.  Note that it's possible that the
      // definition was created with an alternate name for the attribute type,
      // so we'll see if the entry exists with any of the names (or OID) for
      // that attribute type.
      if (isDirectoryServer)
      {
        for (final String name : getNamesAndOID(attrType))
        {
          final DN dn = new DN(
               new RDN("ds-cfg-attribute-type", name),
               JSON_ATTRIBUTE_CONSTRAINTS_PARENT_DN);

          try
          {
            if (internalConnection.getEntry(dn.toString(), "1.1") != null)
            {
              jsonAttributeConstraintsDN = dn;
              break;
            }
          }
          catch (final Exception e)
          {
            serverContext.debugCaught(e);
            acceptable = false;
            unacceptableReasons.add("An error occurred while attempting to " +
                 "determine whether a JSON attribute constraints definition " +
                 "exists in entry '" + dn + "':  " +
                 StaticUtils.getExceptionMessage(e));
            break;
          }
        }

        if (jsonAttributeConstraintsDN == null)
        {
          acceptable = false;
          unacceptableReasons.add("The server is not configured with a JSON " +
               "attribute constraints definition for attribute " +
               attrNameArg.getValue() + '.');
        }
      }
    }


    // Retrieve and validate the configured JSON field path.
    final String fieldPathStr;
    final StringArgument fieldPathArg =
         parser.getStringArgument(ARG_JSON_FIELD_PATH);
    if ((fieldPathArg == null) || (! fieldPathArg.isPresent()))
    {
      // The argument parser should ensure that this never happens.
      acceptable = false;
      unacceptableReasons.add("No value was provided for the required " +
           ARG_JSON_FIELD_PATH + " argument.");
      fieldPathStr = null;
    }
    else
    {
      fieldPathStr = fieldPathArg.getValue();
      final List<String> fieldPath =
           parseFieldPath(fieldPathStr, unacceptableReasons);
      if (fieldPath == null)
      {
        // The unacceptable reasons will have already been updated by the
        // parseFieldPath method.
        acceptable = false;
      }
    }


    // If the plugin is being run in the Directory Server, then make sure that
    // the server is configured with a JSON field constraints definition for the
    // target field, and that it has a ds-cfg-index-values attribute with a
    // value of true.
    if (acceptable && isDirectoryServer)
    {
      final DN jsonFieldConstraintParentDN = new DN(
           new RDN("cn", "JSON Field Constraints"),
           jsonAttributeConstraintsDN);
      final DN jsonFieldConstraintsDN = new DN(
           new RDN("ds-cfg-json-field", fieldPathStr),
           jsonFieldConstraintParentDN);

      try
      {
        final SearchResultEntry jsonFieldConstraintsEntry =
             internalConnection.getEntry(jsonFieldConstraintsDN.toString(),
                  ATTR_JSON_FIELD_CONSTRAINTS_INDEX_VALUES);
        if (jsonFieldConstraintsEntry == null)
        {
          acceptable = false;
          unacceptableReasons.add("The server is not configured with a JSON " +
               "field constraints definition for field " + fieldPathStr +
               " for attribute " + attrNameArg.getValue() + '.');
        }
        else if (! jsonFieldConstraintsEntry.hasAttributeValue(
             ATTR_JSON_FIELD_CONSTRAINTS_INDEX_VALUES, "true"))
        {
          acceptable = false;
          unacceptableReasons.add("The JSON field constraints definition in " +
               "entry '" + jsonFieldConstraintsDN + "' does not have a " +
               ATTR_JSON_FIELD_CONSTRAINTS_INDEX_VALUES + " value of true.");
        }
      }
      catch (final Exception e)
      {
        serverContext.debugCaught(e);
        acceptable = false;
        unacceptableReasons.add("An error occurred while attempting to " +
             "retrieve the JSON field constraints definition contained in " +
             "entry '" + jsonFieldConstraintsDN + "':  " +
             StaticUtils.getExceptionMessage(e));
      }
    }


    // If a unique value JSON object filter was provided, then make sure that
    // it's a valid JSON object filter.
    final StringArgument jsonFilterArg =
         parser.getStringArgument(ARG_UNIQUE_VALUE_JSON_OBJECT_FILTER);
    if ((jsonFilterArg != null) && jsonFilterArg.isPresent())
    {
      final JSONObjectFilter f = parseJSONObjectFilter(jsonFilterArg.getValue(),
           unacceptableReasons, serverContext);
      if (f == null)
      {
        // The unacceptable reasons will have already been updated by the
        // parseJSONObjectFilter method.
        acceptable = false;
      }
    }


    // At least one of the require-uniqueness-within-an-entry and
    // require-uniqueness-across-entries properties must have a value of true.
    final BooleanValueArgument uniqueWithEntryArg =
         parser.getBooleanValueArgument(ARG_REQUIRE_UNIQUENESS_WITHIN_AN_ENTRY);
    if ((uniqueWithEntryArg != null) && uniqueWithEntryArg.isPresent() &&
        (uniqueWithEntryArg.getValue() == Boolean.FALSE))
    {
      final BooleanValueArgument uniqueAcrossEntriesArg =
           parser.getBooleanValueArgument(
                ARG_REQUIRE_UNIQUENESS_ACROSS_ENTRIES);
      if ((uniqueAcrossEntriesArg != null) &&
          uniqueAcrossEntriesArg.isPresent() &&
          (uniqueAcrossEntriesArg.getValue() == Boolean.FALSE))
      {
        acceptable = false;
        unacceptableReasons.add("The configuration cannot have both " +
             ARG_REQUIRE_UNIQUENESS_WITHIN_AN_ENTRY + " and " +
             ARG_REQUIRE_UNIQUENESS_ACROSS_ENTRIES +
             " arguments set to false.");
      }
    }


    return acceptable;
  }



  /**
   * Attempts to apply the configuration from the provided argument parser to
   * this extension.
   *
   * @param  config                The general configuration for this extension.
   * @param  parser                The argument parser that has been used to
   *                               parse the new configuration for this
   *                               extension.
   * @param  adminActionsRequired  A list to which messages may be added to
   *                               provide additional information about any
   *                               additional administrative actions that may
   *                               be required to apply some of the
   *                               configuration changes.
   * @param  messages              A list to which messages may be added to
   *                               provide additional information about the
   *                               processing performed by this method.
   *
   * @return  A result code providing information about the result of applying
   *          the configuration change.  A result of {@code SUCCESS} should be
   *          used to indicate that all processing completed successfully.  Any
   *          other result will indicate that a problem occurred during
   *          processing.
   */
  @Override()
  public ResultCode applyConfiguration(final PluginConfig config,
                                       final ArgumentParser parser,
                                       final List<String> adminActionsRequired,
                                       final List<String> messages)
  {
    // If the server context is null, then get an instance from the provided
    // configuration.
    if (serverContext == null)
    {
      serverContext = config.getServerContext();
    }


    // Get an internal connection to use for processing internal operations.
    if (internalConnection == null)
    {
      internalConnection = serverContext.getInternalRootConnection();
    }


    // Get the configured LDAP attribute type.
    final StringArgument attrNameArg = parser.getStringArgument(ARG_ATTR_NAME);
    final AttributeType attrType = serverContext.getSchema().getAttributeType(
         attrNameArg.getValue(), false);

    boolean valid = true;
    if (attrType == null)
    {
      valid = false;
      messages.add("Unable to retrieve attribute type " +
           attrNameArg.getValue() + " from the server schema.");
    }


    // Get the configured JSON field path.
    final StringArgument fieldPathArg =
         parser.getStringArgument(ARG_JSON_FIELD_PATH);
    final String fieldPathString = fieldPathArg.getValue();
    final List<String> fieldPath = parseFieldPath(fieldPathString, messages);
    if (fieldPath == null)
    {
      valid = false;
    }


    // Determine whether to require uniqueness within an entry.
    boolean uniqueWithinEntry = true;
    final BooleanValueArgument uniqueWithinEntryArg =
         parser.getBooleanValueArgument(ARG_REQUIRE_UNIQUENESS_WITHIN_AN_ENTRY);
    if (uniqueWithinEntryArg != null)
    {
      uniqueWithinEntry = uniqueWithinEntryArg.getValue();
    }


    // Determine whether to require uniqueness across entries.
    boolean uniqueAcrossEntries = true;
    final BooleanValueArgument uniqueAcrossEntriesArg =
         parser.getBooleanValueArgument(ARG_REQUIRE_UNIQUENESS_ACROSS_ENTRIES);
    if (uniqueAcrossEntriesArg != null)
    {
      uniqueAcrossEntries = uniqueAcrossEntriesArg.getValue();
    }


    // Get the set of base DNs.  If no values are provided in the configuration,
    // then get the set of naming contexts from the root DSE.
    List<DN> baseDNList = null;
    final DNArgument baseDNArg = parser.getDNArgument(ARG_BASE_DN);
    if ((baseDNArg != null) && baseDNArg.isPresent())
    {
      baseDNList = baseDNArg.getValues();
    }

    if ((baseDNList == null) || baseDNList.isEmpty())
    {
      try
      {
        final String[] namingContextDNs =
             internalConnection.getRootDSE().getNamingContextDNs();
        final ArrayList<DN> dnList = new ArrayList<DN>(namingContextDNs.length);
        for (final String dn : namingContextDNs)
        {
          dnList.add(new DN(dn));
        }
        baseDNList = Collections.unmodifiableList(dnList);
      }
      catch (final Exception e)
      {
        serverContext.debugCaught(e);
      }

      if ((baseDNList == null) || baseDNList.isEmpty())
      {
        valid = false;
        messages.add("No base DNs are configured, and the public naming " +
             "context DNs could not be obtained from the server root DSE.");
      }
    }


    // Get the unique entry filter.
    Filter entryFilter = null;
    final FilterArgument filterArg =
         parser.getFilterArgument(ARG_UNIQUE_ENTRY_LDAP_FILTER);
    if ((filterArg != null) && filterArg.isPresent())
    {
      entryFilter = filterArg.getValue();
    }


    // Get the unique object filter.
    JSONObjectFilter objectFilter = null;
    final StringArgument objectFilterArg =
         parser.getStringArgument(ARG_UNIQUE_VALUE_JSON_OBJECT_FILTER);
    if ((objectFilterArg != null) && objectFilterArg.isPresent())
    {
      objectFilter = parseJSONObjectFilter(objectFilterArg.getValue(), messages,
           serverContext);
      if (objectFilter == null)
      {
        valid = false;
      }
    }


    // If the configuration is valid, then apply it.
    if (valid)
    {
      ldapAttributeType              = attrType;
      jsonFieldPathString            = fieldPathString;
      jsonFieldPath                  = fieldPath;
      requireUniquenessWithinAnEntry = uniqueWithinEntry;
      requireUniquenessAcrossEntries = uniqueAcrossEntries;
      baseDNs                        = baseDNList;
      uniqueEntryLDAPFilter          = entryFilter;
      uniqueValueJSONObjectFilter    = objectFilter;

      this.config = config;

      return ResultCode.SUCCESS;
    }
    else
    {
      return ResultCode.OTHER;
    }
  }



  /**
   * Retrieves a list containing all of the names and the OID for the provided
   * attribute type.
   *
   * @param  attrType  The attribute type for which to obtain the names and OID.
   *
   * @return  A list containing all of the names and the OID for the provided
   *          attribute type.
   */
  private static List<String> getNamesAndOID(final AttributeType attrType)
  {
    final ArrayList<String> namesAndOID = new ArrayList<String>(5);
    namesAndOID.addAll(attrType.getNames());
    namesAndOID.add(attrType.getOID());
    return namesAndOID;
  }



  /**
   * Parses the provided string to extract the path to the target field as a
   * list.  Levels of hierarchy will be separated by periods.  Any periods
   * contained in field names must be escaped with backslashes.
   *
   * @param  fieldPathStr         The string representation of the field path.
   * @param  unacceptableReasons  A list that should be updated to include any
   *                              reason that the provided string is not a valid
   *                              path.
   *
   * @return  The list containing the path to the field name, or {@code null} if
   *          the provided field path string does not represent a valid path.
   */
  private static List<String> parseFieldPath(final String fieldPathStr,
                                   final List<String> unacceptableReasons)
  {
    boolean escaped = false;
    final ArrayList<String> fieldList = new ArrayList<String>(5);
    final StringBuilder buffer = new StringBuilder(fieldPathStr.length());
    for (final char c :  fieldPathStr.toCharArray())
    {
      if (escaped)
      {
        buffer.append(c);
        escaped = false;
      }
      else if (c == '\\')
      {
        escaped = true;
      }
      else if (c == '.')
      {
        if (buffer.length() > 0)
        {
          fieldList.add(buffer.toString());
          buffer.setLength(0);
        }
        else
        {
          unacceptableReasons.add("Value '" + fieldPathStr + "' is not a " +
                    "valid JSON field path because it includes an empty " +
               "field name component.");
          return null;
        }
      }
      else
      {
        buffer.append(c);
      }
    }

    if (buffer.length() == 0)
    {
      unacceptableReasons.add("Value '" + fieldPathStr + "' is not a valid " +
           "JSON field path because it includes an empty field name " +
           "component.");
      return null;
    }
    else
    {
      fieldList.add(buffer.toString());
    }

    return Collections.unmodifiableList(fieldList);
  }



  /**
   * Parses the provided string as a JSON object filter.
   *
   * @param  filterStr            The string to be parsed as a JSON object
   *                              filter.
   * @param  unacceptableReasons  A list that should be updated to include any
   *                              reason that the provided string is not a valid
   *                              JSON object filter.
   * @param  serverContext        The server context to use for debugging
   *                              purposes.
   *
   * @return  The parsed JSON object filter, or {@code null} if the provided
   *          string does not represent a valid JSON object filter.
   */
  private static JSONObjectFilter parseJSONObjectFilter(final String filterStr,
                      final List<String> unacceptableReasons,
                      final DirectoryServerContext serverContext)
  {
    final JSONObject filterObject;
    try
    {
      filterObject = new JSONObject(filterStr);
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      unacceptableReasons.add(ARG_UNIQUE_VALUE_JSON_OBJECT_FILTER +
           " value '" + filterStr + "' is not a valid JSON object:  " +
           StaticUtils.getExceptionMessage(e));
      return null;
    }

    try
    {
      return JSONObjectFilter.decode(filterObject);
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      unacceptableReasons.add(ARG_UNIQUE_VALUE_JSON_OBJECT_FILTER +
           " value '" + filterStr + "' does not represent a valid JSON " +
           "object filter:  " + StaticUtils.getExceptionMessage(e));
      return null;
    }
  }



  /**
   * Retrieves a map containing examples of configurations that may be used for
   * this extension.  The map key should be a list of sample arguments, and the
   * corresponding value should be a description of the behavior that will be
   * exhibited by the extension when used with that configuration.
   *
   * @return  A map containing examples of configurations that may be used for
   *          this extension.  It may be {@code null} or empty if there should
   *          not be any example argument sets.
   */
  @Override()
  public Map<List<String>,String> getExamplesArgumentSets()
  {
    final List<String> exampleArgs = Arrays.asList(
         ARG_ATTR_NAME + "=my-json-attribute",
         ARG_JSON_FIELD_PATH + "=my-json-field",
         ARG_REQUIRE_UNIQUENESS_WITHIN_AN_ENTRY + "=true",
         ARG_REQUIRE_UNIQUENESS_ACROSS_ENTRIES + "=true",
         ARG_BASE_DN + "=dc=example,dc=com");

    final String exampleDescription = "Configure the " + getExtensionName() +
         " to ensure that all values of the 'my-json-field' field in the " +
         "'my-json-attribute' attribute are unique across all entries below " +
         "'dc=example,dc=com', including across multiple values within the " +
         "same entry.";

    return Collections.singletonMap(exampleArgs, exampleDescription);
  }
}