001/*
002 * Copyright 2017-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2017-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.controls;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.LinkedHashMap;
044import java.util.LinkedHashSet;
045import java.util.List;
046import java.util.Map;
047import java.util.Set;
048
049import com.unboundid.asn1.ASN1Boolean;
050import com.unboundid.asn1.ASN1Element;
051import com.unboundid.asn1.ASN1Enumerated;
052import com.unboundid.asn1.ASN1OctetString;
053import com.unboundid.asn1.ASN1Sequence;
054import com.unboundid.asn1.ASN1Set;
055import com.unboundid.ldap.sdk.Control;
056import com.unboundid.ldap.sdk.Filter;
057import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
058import com.unboundid.ldap.sdk.LDAPException;
059import com.unboundid.ldap.sdk.ResultCode;
060import com.unboundid.util.CryptoHelper;
061import com.unboundid.util.Debug;
062import com.unboundid.util.NotMutable;
063import com.unboundid.util.NotNull;
064import com.unboundid.util.Nullable;
065import com.unboundid.util.StaticUtils;
066import com.unboundid.util.ThreadSafety;
067import com.unboundid.util.ThreadSafetyLevel;
068import com.unboundid.util.Validator;
069import com.unboundid.util.json.JSONArray;
070import com.unboundid.util.json.JSONBoolean;
071import com.unboundid.util.json.JSONField;
072import com.unboundid.util.json.JSONObject;
073import com.unboundid.util.json.JSONString;
074import com.unboundid.util.json.JSONValue;
075
076import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
077
078
079
080/**
081 * This class provides a request control that may be included in an add, modify,
082 * or modify DN request to ensure that the contents of that request will not
083 * result in a uniqueness conflict with any other entry in the server.  Each
084 * instance of this control should define exactly one uniqueness constraint for
085 * the associated operation.  Multiple instances of this control can be included
086 * in the same request to define multiple independent uniqueness constraints
087 * that must all be satisfied.  If any of the uniqueness constraints is not
088 * satisfied, then the corresponding LDAP result should have a result code of
089 * {@link ResultCode#ASSERTION_FAILED} and a {@link UniquenessResponseControl}
090 * for each uniqueness constraint that was not satisfied.
091 * <BR>
092 * <BLOCKQUOTE>
093 *   <B>NOTE:</B>  This class, and other classes within the
094 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
095 *   supported for use against Ping Identity, UnboundID, and
096 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
097 *   for proprietary functionality or for external specifications that are not
098 *   considered stable or mature enough to be guaranteed to work in an
099 *   interoperable way with other types of LDAP servers.
100 * </BLOCKQUOTE>
101 * <BR>
102 * The request properties must contain either one or more attribute types, a
103 * filter, or both.  If only a filter is specified, then the server will use
104 * that filter to identify conflicts (for an add request, any matches at all
105 * will be considered a conflict; for a modify or modify DN request, any matches
106 * with any entry other than the one being updated will be considered a
107 * conflict).  If a single attribute type is specified with no filter, then any
108 * change that would result in multiple entries having the same value for that
109 * attribute will be considered a conflict.  If multiple attribute types are
110 * specified, then the multiple attribute behavior will be used to determine how
111 * to identify conflicts, as documented in the
112 * {@link UniquenessMultipleAttributeBehavior} enum.  If both a set of attribute
113 * types and a filter are provided, then only entries matching both sets of
114 * criteria will be considered a conflict.
115 * <BR><BR>
116 * The server can perform two different searches in an attempt to identify
117 * conflicts.  In the pre-commit phase, it will attempt to identify any
118 * conflicts that already exist, and will reject the associated change if there
119 * are any.  In the post-commit phase, it can see if there were any conflicts
120 * introduced by the change itself or by another change happening at the same
121 * time.  If a conflict is detected in the post-commit phase, then the server
122 * won't have prevented it, but at least the control can be used to provide
123 * notification about it.  The server may also raise an administrative alert to
124 * notify administrators about the conflict.
125 * <BR><BR>
126 * Although post-commit validation on its own should be able to detect conflicts
127 * that arise as a result of concurrent changes in other instances, it is also
128 * possible to take additional measures to help prevent conflicts from arising
129 * in the first place.  The control may indicate that the server should create
130 * a temporary conflict prevention details entry before beginning pre-commit
131 * validation processing.  This entry may be found during pre-commit validation
132 * performed for any conflicting concurrent updates so that the conflicting
133 * operation is rejected.  This temporary entry will be automatically removed
134 * after uniqueness processing has completed, regardless of its success or
135 * failure.
136 * <BR><BR>
137 * This request control may be sent either directly to a Directory Server
138 * instance, or it may be sent to a Directory Proxy Server with or without entry
139 * balancing.  If the request is sent directly to a Directory Server, then only
140 * that one server will be checked for uniqueness conflicts, and it is possible
141 * that concurrent conflicts may be introduced on other servers that have not
142 * yet been replicated by the time control processing has completed.  If the
143 * request is sent to a Directory Proxy Server instance, then search may be
144 * processed in one or more backend servers based on the pre-commit and
145 * post-commit validation levels, and at the most paranoid levels, it is highly
146 * unlikely that any conflicts will go unnoticed.
147 * <BR><BR>
148 * The request control has an OID of 1.3.6.1.4.1.30221.2.5.52, a criticality of
149 * either {@code true} or {@code false}, and a value with the following
150 * encoding:
151 * <PRE>
152 *   UniquenessRequestValue ::= SEQUENCE {
153 *     uniquenessID                            [0] OCTET STRING,
154 *     attributeTypes                          [1] SET OF OCTET STRING OPTIONAL,
155 *     multipleAttributeBehavior               [2] ENUMERATED {
156 *       uniqueWithinEachAttribute                      (0),
157 *       uniqueAcrossAllAttributesIncludingInSameEntry  (1),
158 *       uniqueAcrossAllAttributesExceptInSameEntry     (2),
159 *       uniqueInCombination                            (3),
160 *       ... } DEFAULT uniqueWithinEachAttribute,
161 *     baseDN                                  [3] LDAPDN OPTIONAL,
162 *     filter                                  [4] Filter OPTIONAL,
163 *     preventConflictsWithSoftDeletedEntries  [5] BOOLEAN DEFAULT FALSE,
164 *     preCommitValidationLevel                [6] ENUMERATED {
165 *       none                        (0),
166 *       allSubtreeViews             (1),
167 *       allBackendSets              (2),
168 *       allAvailableBackendServers  (3),
169 *       ... } DEFAULT allSubtreeViews,
170 *     postCommitValidationLevel               [7] ENUMERATED {
171 *       none                        (0),
172 *       allSubtreeViews             (1),
173 *       allBackendSets              (2),
174 *       allAvailableBackendServers  (3),
175 *       ... } DEFAULT allSubtreeViews,
176 *     alertOnPostCommitConflictDetection      [8] BOOLEAN DEFAULT TRUE,
177 *     createConflictPreventionDetailsEntry    [9] BOOLEAN DEFAULT FALSE,
178 *     ... }
179 * </PRE>
180 * <BR><BR>
181 * <H2>Example</H2>
182 * The following example demonstrates how to use the uniqueness request control
183 * to only process an add operation if it does not result in multiple entries
184 * that have the same uid value:
185 * <BR><BR>
186 * <PRE>
187 * // Create the properties to build a uniqueness request control that
188 * // will try to prevent an add operation from creating a new entry
189 * // that has the same uid as an existing entry in the server.  During
190 * // pre-commit processing (which happens before the server actually
191 * // processes the add), the server will check at least one server in
192 * // each entry-balancing backend set (or just one server in a
193 * // non-entry-balanced deployment).  During post-commit processing
194 * // (which happens if the add succeeds), the server will double-check
195 * // that no conflicting entry was added on any available server in the
196 * // topology.  Also ensure that the server will not allow conflicts
197 * // with soft-deleted entries.
198 * final UniquenessRequestControlProperties uniquenessProperties =
199 *      new UniquenessRequestControlProperties("uid");
200 * uniquenessProperties.setPreCommitValidationLevel(
201 *      UniquenessValidationLevel.ALL_BACKEND_SETS);
202 * uniquenessProperties.setPostCommitValidationLevel(
203 *      UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS);
204 * uniquenessProperties.setPreventConflictsWithSoftDeletedEntries(true);
205 *
206 * // Create the request control.  It will be critical so that the
207 * // server will not attempt to process the add if it can't honor the
208 * // uniqueness request.
209 * final boolean isCritical = true;
210 * final String uniquenessID = "uid-uniqueness";
211 * final UniquenessRequestControl uniquenessRequestControl =
212 *      new UniquenessRequestControl(isCritical, uniquenessID,
213 *           uniquenessProperties);
214 *
215 * // Attach the control to an add request.
216 * addRequest.addControl(uniquenessRequestControl);
217 *
218 * // Send the add request to the server and read the result.
219 * try
220 * {
221 *   final LDAPResult addResult = connection.add(addRequest);
222 *
223 *   // The add operation succeeded, so the entry should have been
224 *   // created, but there is still the possibility that a post-commit
225 *   // conflict was discovered, indicating that another request
226 *   // processed at about the same time as our add introduced a
227 *   // conflicting entry.
228 *   final Map&lt;String,UniquenessResponseControl&gt; uniquenessResponses;
229 *   try
230 *   {
231 *     uniquenessResponses = UniquenessResponseControl.get(addResult);
232 *   }
233 *   catch (final LDAPException e)
234 *   {
235 *     throw new RuntimeException(
236 *          "The add succeeded, but an error occurred while trying " +
237 *               "to decode a uniqueness response control in add " +
238 *               "result " + addResult + ":  " +
239 *               StaticUtils.getExceptionMessage(e),
240 *          e);
241 *   }
242 *
243 *   final UniquenessResponseControl uniquenessResponseControl =
244 *        uniquenessResponses.get(uniquenessID);
245 *   if ((uniquenessResponseControl != null) &amp;&amp;
246 *        uniquenessResponseControl.uniquenessConflictFound())
247 *   {
248 *     throw new RuntimeException(
249 *          "The add succeeded, but a uniqueness conflict was found  " +
250 *               "Uniqueness validation message:  " +
251 *               uniquenessResponseControl.getValidationMessage());
252 *   }
253 * }
254 * catch (final LDAPException e)
255 * {
256 *   // The add attempt failed.  It might have been because of a
257 *   // uniqueness problem, or it could have been for some other reason.
258 *   // To figure out which it was, look to see if there is an
259 *   // appropriate uniqueness response control.
260 *   final Map&lt;String, UniquenessResponseControl&gt; uniquenessResponses;
261 *   try
262 *   {
263 *     uniquenessResponses =
264 *          UniquenessResponseControl.get(e.toLDAPResult());
265 *   }
266 *   catch (final LDAPException e2)
267 *   {
268 *     throw new LDAPException(e.getResultCode(),
269 *          "The add attempt failed with result " + e.toLDAPResult() +
270 *               ", and an error occurred while trying to decode a " +
271 *               "uniqueness response control in the result:  " +
272 *               StaticUtils.getExceptionMessage(e2),
273 *          e);
274 *   }
275 *
276 *   final UniquenessResponseControl uniquenessResponseControl =
277 *        uniquenessResponses.get(uniquenessID);
278 *   if (uniquenessResponseControl == null)
279 *   {
280 *     // The add result didn't include a uniqueness response control,
281 *     // indicating that the failure was not because of a uniqueness
282 *     // conflict.
283 *     throw e;
284 *   }
285 *
286 *   if (uniquenessResponseControl.uniquenessConflictFound())
287 *   {
288 *     // The add failed, and the uniqueness response control indicates
289 *     // that the failure was because of a uniqueness conflict.
290 *
291 *     final UniquenessValidationResult preCommitResult =
292 *          uniquenessResponseControl.getPreCommitValidationResult();
293 *     final UniquenessValidationResult postCommitResult =
294 *          uniquenessResponseControl.getPreCommitValidationResult();
295 *     final String validationMessage =
296 *          uniquenessResponseControl.getValidationMessage();
297 *
298 *     throw e;
299 *   }
300 *   else
301 *   {
302 *     // The add failed, but the uniqueness response control indicates
303 *     // that the failure was not because of a uniqueness conflict.
304 *     throw e;
305 *   }
306 * }
307 * </PRE>
308 */
309@NotMutable()
310@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
311public final class UniquenessRequestControl
312       extends Control
313{
314  /**
315   * The OID (1.3.6.1.4.1.30221.2.5.52) for the uniqueness request control.
316   */
317  @NotNull public static final String UNIQUENESS_REQUEST_OID =
318       "1.3.6.1.4.1.30221.2.5.52";
319
320
321
322  /**
323   * The BER type for the uniqueness ID element in the value sequence.
324   */
325  private static final byte TYPE_UNIQUENESS_ID = (byte) 0x80;
326
327
328
329  /**
330   * The BER type for the attribute types element in the value sequence.
331   */
332  private static final byte TYPE_ATTRIBUTE_TYPES = (byte) 0xA1;
333
334
335
336  /**
337   * The BER type for the multiple attribute behavior element in the value
338   * sequence.
339   */
340  private static final byte TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR = (byte) 0x82;
341
342
343
344  /**
345   * The BER type for the base DN element in the value sequence.
346   */
347  private static final byte TYPE_BASE_DN = (byte) 0x83;
348
349
350
351  /**
352   * The BER type for the filter element in the value sequence.
353   */
354  private static final byte TYPE_FILTER = (byte) 0xA4;
355
356
357
358  /**
359   * The BER type for the prevent conflicts with soft-deleted entries element in
360   * the value sequence.
361   */
362  private static final byte TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES =
363       (byte) 0x85;
364
365
366
367  /**
368   * The BER type for the pre-commit validation element in the value sequence.
369   */
370  private static final byte TYPE_PRE_COMMIT_VALIDATION_LEVEL = (byte) 0x86;
371
372
373
374  /**
375   * The BER type for the post-commit validation element in the value sequence.
376   */
377  private static final byte TYPE_POST_COMMIT_VALIDATION_LEVEL = (byte) 0x87;
378
379
380
381  /**
382   * The BER type for the value sequence element that indicates whether to
383   * raise an administrative alert if a conflict is detected during post-commit
384   * validation.
385   */
386  private static final byte TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION =
387       (byte) 0x88;
388
389
390
391  /**
392   * The BER type for the value sequence element that indicates whether to
393   * create a conflict prevention details entry before pre-commit validation as
394   * a means of helping to avoid conflicts.
395   */
396  private static final byte TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY =
397       (byte) 0x89;
398
399
400
401  /**
402   * The name of the field used to hold the
403   * alert-on-post-commit-conflict-detection flag in the JSON representation of
404   * this control.
405   */
406  @NotNull private static final String
407       JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION =
408            "alert-on-post-commit-conflict-detection";
409
410
411
412  /**
413   * The name of the field used to hold the attribute types in the JSON
414   * representation of this control.
415   */
416  @NotNull private static final String JSON_FIELD_ATTRIBUTE_TYPES =
417       "attribute-types";
418
419
420
421  /**
422   * The name of the field used to hold the base DN in the JSON representation
423   * of this control.
424   */
425  @NotNull private static final String JSON_FIELD_BASE_DN = "base-dn";
426
427
428
429  /**
430   * The name of the field used to hold the
431   * create-conflict-prevention-details-entry flag in the JSON representation of
432   * this control.
433   */
434  @NotNull private static final String
435       JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY =
436            "create-conflict-prevention-details-entry";
437
438
439
440  /**
441   * The name of the field used to hold the filter in the JSON representation of
442   * this control.
443   */
444  @NotNull private static final String JSON_FIELD_FILTER = "filter";
445
446
447
448  /**
449   * The name of the field used to hold the multiple attribute behavior in the
450   * JSON representation of this control.
451   */
452  @NotNull private static final String JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR =
453       "multiple-attribute-behavior";
454
455
456
457  /**
458   * The name of the field used to hold the pre-commit validation level in the
459   * JSON representation of this control.
460   */
461  @NotNull private static final String  JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL =
462       "pre-commit-validation-level";
463
464
465
466  /**
467   * The name of the field used to hold the post-commit validation level in the
468   * JSON representation of this control.
469   */
470  @NotNull private static final String
471       JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL = "post-commit-validation-level";
472
473
474
475  /**
476   * The name of the field used to hold the
477   * prevent-conflicts-with-soft-deleted-entries flag in the JSON representation
478   * of this control.
479   */
480  @NotNull private static final String
481       JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES =
482            "prevent-conflicts-with-soft-deleted-entries";
483
484
485
486  /**
487   * The name of the field used to hold the uniqueness ID in the JSON
488   * representation of this control.
489   */
490  @NotNull private static final String JSON_FIELD_UNIQUENESS_ID =
491       "uniqueness-id";
492
493
494
495  /**
496   * The unique-within-each-attribute multiple-attribute-behavior value to use
497   * in the JSON representation of this control.
498   */
499  @NotNull private static final String JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE =
500       "unique-within-each-attribute";
501
502
503
504  /**
505   * The unique-across-all-attributes-including-in-the-same-entry
506   * multiple-attribute-behavior value to use in the JSON representation of this
507   * control.
508   */
509  @NotNull private static final String
510       JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY =
511            "unique-across-all-attributes-including-in-the-same-entry";
512
513
514
515  /**
516   * The unique-across-all-attributes-except-in-the-same-entry
517   * multiple-attribute-behavior value to use in the JSON representation of this
518   * control.
519   */
520  @NotNull private static final String
521       JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY =
522            "unique-across-all-attributes-except-in-the-same-entry";
523
524
525
526  /**
527   * The unique-in-combination multiple-attribute-behavior value to use in the
528   * JSON representation of this control.
529   */
530  @NotNull private static final String JSON_MAB_UNIQUE_IN_COMBINATION =
531       "unique-in-combination";
532
533
534
535  /**
536   * The none validation level value to use in the JSON representation of this
537   * control.
538   */
539  @NotNull private static final String JSON_VALIDATION_LEVEL_NONE = "none";
540
541
542
543  /**
544   * The all-subtree-views validation level value to use in the JSON
545   * representation of this control.
546   */
547  @NotNull private static final String JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS =
548       "all-subtree-views";
549
550
551
552  /**
553   * The all-backend-sets validation level value to use in the JSON
554   * representation of this control.
555   */
556  @NotNull private static final String JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS =
557       "all-backend-sets";
558
559
560
561  /**
562   * The all-available-backend-servers validation level value to use in the JSON
563   * representation of this control.
564   */
565  @NotNull private static final String
566       JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS =
567            "all-available-backend-servers";
568
569
570
571  /**
572   * The serial version UID for this serializable class.
573   */
574  private static final long serialVersionUID = 7976218379635922852L;
575
576
577
578  // Indicates whether the server should raise an administrative alert if a
579  // conflict is detected during post-commit validation.
580  private final boolean alertOnPostCommitConflictDetection;
581
582  // Indicates whether the server should create a conflict prevention details
583  // entry before pre-commit validation as a means of helping to avoid
584  // conflicts.
585  private final boolean createConflictPreventionDetailsEntry;
586
587  // Indicates whether to prevent conflicts with soft-deleted entries.
588  private final boolean preventConflictsWithSoftDeletedEntries;
589
590  // An optional filter that should be used in the course of identifying
591  // uniqueness conflicts.
592  @Nullable private final Filter filter;
593
594  // A potentially-empty set of attribute types that should be checked for
595  // uniqueness conflicts.
596  @NotNull private final Set<String> attributeTypes;
597
598  // An optional base DN to use when checking for conflicts.
599  @Nullable private final String baseDN;
600
601  // A value that will be used to correlate this request control with its
602  // corresponding response control.
603  @NotNull private final String uniquenessID;
604
605  // The behavior that the server should exhibit if multiple attribute types
606  // are configured.
607  @NotNull private final UniquenessMultipleAttributeBehavior
608       multipleAttributeBehavior;
609
610  // The level of validation that the server should perform before processing
611  // the associated change.
612  @NotNull private final UniquenessValidationLevel postCommitValidationLevel;
613
614  // The level of validation that the server should perform after processing the
615  // associated change.
616  @NotNull private final UniquenessValidationLevel preCommitValidationLevel;
617
618
619
620  /**
621   * Creates a new uniqueness request control with the provided information.
622   *
623   * @param  isCritical    Indicates whether the control should be considered
624   *                       critical.
625   * @param  uniquenessID  A value that will be used to correlate this request
626   *                       control with its corresponding response control.  If
627   *                       this is {@code null}, then a unique identifier will
628   *                       be automatically generated.
629   * @param  properties    The set of properties for this control.  It must not
630   *                       be {@code null}.
631   *
632   * @throws  LDAPException  If the provided properties cannot be used to create
633   *                         a valid uniqueness request control.
634   */
635  public UniquenessRequestControl(final boolean isCritical,
636              @Nullable final String uniquenessID,
637              @NotNull final UniquenessRequestControlProperties properties)
638         throws LDAPException
639  {
640    this((uniquenessID == null
641              ? CryptoHelper.getRandomUUID().toString()
642              : uniquenessID),
643         properties, isCritical);
644  }
645
646
647
648  /**
649   * Creates a new uniqueness request control with the provided information.
650   * Note that this version of the constructor takes the same set of arguments
651   * as the above constructor, but in a different order (to distinguish between
652   * the two versions), and with the additional constraint that the uniqueness
653   * ID must not be {@code null}.
654   *
655   * @param  uniquenessID  A value that will be used to correlate this request
656   *                       control with its corresponding response control.  It
657   *                       must not be {@code null}.
658   * @param  properties    The set of properties for this control.  It must not
659   *                       be {@code null}.
660   * @param  isCritical    Indicates whether the control should be considered
661   *                       critical.
662   *
663   * @throws  LDAPException  If the provided properties cannot be used to create
664   *                         a valid uniqueness request control.
665   */
666  private UniquenessRequestControl(@NotNull final String uniquenessID,
667               @NotNull final UniquenessRequestControlProperties properties,
668               final boolean isCritical)
669          throws LDAPException
670  {
671    super(UNIQUENESS_REQUEST_OID, isCritical,
672         encodeValue(uniquenessID, properties));
673
674    Validator.ensureNotNull(uniquenessID);
675    this.uniquenessID = uniquenessID;
676
677    attributeTypes = properties.getAttributeTypes();
678    multipleAttributeBehavior = properties.getMultipleAttributeBehavior();
679    baseDN = properties.getBaseDN();
680    filter = properties.getFilter();
681    preventConflictsWithSoftDeletedEntries =
682         properties.preventConflictsWithSoftDeletedEntries();
683    preCommitValidationLevel = properties.getPreCommitValidationLevel();
684    postCommitValidationLevel = properties.getPostCommitValidationLevel();
685    alertOnPostCommitConflictDetection =
686         properties.alertOnPostCommitConflictDetection();
687    createConflictPreventionDetailsEntry =
688         properties.createConflictPreventionDetailsEntry();
689
690    if (attributeTypes.isEmpty() && (filter == null))
691    {
692      throw new LDAPException(ResultCode.PARAM_ERROR,
693           ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get());
694    }
695  }
696
697
698
699  /**
700   * Encodes the provided information into an octet string that is suitable for
701   * use as the value of this control.
702   *
703   * @param  uniquenessID  A value that will be used to correlate this request
704   *                       control with its corresponding response control.  It
705   *                       must not be {@code null}.
706   * @param  properties    The set of properties for this control.  It must not
707   *                       be {@code null}.
708   *
709   * @return  The encoded value that was created.
710   */
711  @NotNull()
712  private static ASN1OctetString encodeValue(@NotNull final String uniquenessID,
713       @NotNull final UniquenessRequestControlProperties properties)
714  {
715    final ArrayList<ASN1Element> elements = new ArrayList<>(10);
716
717    elements.add(new ASN1OctetString(TYPE_UNIQUENESS_ID, uniquenessID));
718
719    final Set<String> attributeTypes = properties.getAttributeTypes();
720    if (!attributeTypes.isEmpty())
721    {
722      final ArrayList<ASN1Element> attributeTypeElements =
723           new ArrayList<>(attributeTypes.size());
724      for (final String attributeType : attributeTypes)
725      {
726        attributeTypeElements.add(new ASN1OctetString(attributeType));
727      }
728      elements.add(new ASN1Set(TYPE_ATTRIBUTE_TYPES, attributeTypeElements));
729    }
730
731    final UniquenessMultipleAttributeBehavior multipleAttributeBehavior =
732         properties.getMultipleAttributeBehavior();
733    if (multipleAttributeBehavior !=
734         UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE)
735    {
736      elements.add(new ASN1Enumerated(TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR,
737           multipleAttributeBehavior.intValue()));
738    }
739
740    final String baseDN = properties.getBaseDN();
741    if (baseDN != null)
742    {
743      elements.add(new ASN1OctetString(TYPE_BASE_DN, baseDN));
744    }
745
746    final Filter filter = properties.getFilter();
747    if (filter != null)
748    {
749      elements.add(new ASN1Element(TYPE_FILTER, filter.encode().encode()));
750    }
751
752    if (properties.preventConflictsWithSoftDeletedEntries())
753    {
754      elements.add(new ASN1Boolean(
755           TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES, true));
756    }
757
758    final UniquenessValidationLevel preCommitValidationLevel =
759         properties.getPreCommitValidationLevel();
760    if (preCommitValidationLevel != UniquenessValidationLevel.ALL_SUBTREE_VIEWS)
761    {
762      elements.add(new ASN1Enumerated(TYPE_PRE_COMMIT_VALIDATION_LEVEL,
763           preCommitValidationLevel.intValue()));
764    }
765
766    final UniquenessValidationLevel postCommitValidationLevel =
767         properties.getPostCommitValidationLevel();
768    if (postCommitValidationLevel !=
769         UniquenessValidationLevel.ALL_SUBTREE_VIEWS)
770    {
771      elements.add(new ASN1Enumerated(TYPE_POST_COMMIT_VALIDATION_LEVEL,
772           postCommitValidationLevel.intValue()));
773    }
774
775    if (! properties.alertOnPostCommitConflictDetection())
776    {
777      elements.add(new ASN1Boolean(
778           TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION, false));
779    }
780
781    if (properties.createConflictPreventionDetailsEntry())
782    {
783      elements.add(new ASN1Boolean(
784           TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY, true));
785    }
786
787    return new ASN1OctetString(new ASN1Sequence(elements).encode());
788  }
789
790
791
792  /**
793   * Creates a new uniqueness request control that is decoded from the provided
794   * generic control.
795   *
796   * @param  control  The control to be decoded as a uniqueness request control.
797   *                  It must not be {@code null}.
798   *
799   * @throws  LDAPException  If the provided control cannot be decoded as a
800   *                         valid uniqueness request control.
801   */
802  public UniquenessRequestControl(@NotNull final Control control)
803         throws LDAPException
804  {
805    super(control);
806
807    final ASN1OctetString value = control.getValue();
808    if (value == null)
809    {
810      throw new LDAPException(ResultCode.DECODING_ERROR,
811           ERR_UNIQUENESS_REQ_DECODE_NO_VALUE.get());
812    }
813
814    try
815    {
816      boolean decodedAlertOnPostCommitConflictDetection = true;
817      boolean decodedCreateConflictPreventionDetailsEntry = false;
818      boolean decodedPreventSoftDeletedConflicts = false;
819      Filter decodedFilter = null;
820      Set<String> decodedAttributeTypes = Collections.emptySet();
821      String decodedBaseDN = null;
822      String decodedUniquenessID = null;
823      UniquenessMultipleAttributeBehavior decodedMultipleAttributeBehavior =
824           UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE;
825      UniquenessValidationLevel decodedPreCommitLevel =
826           UniquenessValidationLevel.ALL_SUBTREE_VIEWS;
827      UniquenessValidationLevel decodedPostCommitLevel =
828           UniquenessValidationLevel.ALL_SUBTREE_VIEWS;
829
830      final ASN1Element[] elements =
831           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
832      for (final ASN1Element e : elements)
833      {
834        switch (e.getType())
835        {
836          case TYPE_UNIQUENESS_ID:
837            decodedUniquenessID =
838                 ASN1OctetString.decodeAsOctetString(e).stringValue();
839            break;
840          case TYPE_ATTRIBUTE_TYPES:
841            final ASN1Element[] atElements = ASN1Set.decodeAsSet(e).elements();
842            final LinkedHashSet<String> atNames = new LinkedHashSet<>(
843                 StaticUtils.computeMapCapacity(atElements.length));
844            for (final ASN1Element atElement : atElements)
845            {
846              atNames.add(ASN1OctetString.decodeAsOctetString(
847                   atElement).stringValue());
848            }
849            decodedAttributeTypes = Collections.unmodifiableSet(atNames);
850            break;
851          case TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR:
852            final int mabIntValue =
853                 ASN1Enumerated.decodeAsEnumerated(e).intValue();
854            decodedMultipleAttributeBehavior =
855                 UniquenessMultipleAttributeBehavior.valueOf(mabIntValue);
856            if (decodedMultipleAttributeBehavior == null)
857            {
858              throw new LDAPException(ResultCode.DECODING_ERROR,
859                   ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_MULTIPLE_ATTR_BEHAVIOR.get(
860                        mabIntValue));
861            }
862            break;
863          case TYPE_BASE_DN:
864            decodedBaseDN =
865                 ASN1OctetString.decodeAsOctetString(e).stringValue();
866            break;
867          case TYPE_FILTER:
868            decodedFilter = Filter.decode(ASN1Element.decode(e.getValue()));
869            break;
870          case TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES:
871            decodedPreventSoftDeletedConflicts =
872                 ASN1Boolean.decodeAsBoolean(e).booleanValue();
873            break;
874          case TYPE_PRE_COMMIT_VALIDATION_LEVEL:
875            final int preCommitIntValue =
876                 ASN1Enumerated.decodeAsEnumerated(e).intValue();
877            decodedPreCommitLevel =
878                 UniquenessValidationLevel.valueOf(preCommitIntValue);
879            if (decodedPreCommitLevel == null)
880            {
881              throw new LDAPException(ResultCode.DECODING_ERROR,
882                   ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_PRE_COMMIT_LEVEL.get(
883                        preCommitIntValue));
884            }
885            break;
886          case TYPE_POST_COMMIT_VALIDATION_LEVEL:
887            final int postCommitIntValue =
888                 ASN1Enumerated.decodeAsEnumerated(e).intValue();
889            decodedPostCommitLevel =
890                 UniquenessValidationLevel.valueOf(postCommitIntValue);
891            if (decodedPostCommitLevel == null)
892            {
893              throw new LDAPException(ResultCode.DECODING_ERROR,
894                   ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_POST_COMMIT_LEVEL.get(
895                        postCommitIntValue));
896            }
897            break;
898          case TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION:
899            decodedAlertOnPostCommitConflictDetection =
900                 ASN1Boolean.decodeAsBoolean(e).booleanValue();
901            break;
902          case TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY:
903            decodedCreateConflictPreventionDetailsEntry =
904                 ASN1Boolean.decodeAsBoolean(e).booleanValue();
905            break;
906          default:
907            throw new LDAPException(ResultCode.DECODING_ERROR,
908                 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_ELEMENT_TYPE.get(
909                      StaticUtils.toHex(e.getType())));
910        }
911      }
912
913      if (decodedUniquenessID == null)
914      {
915        throw new LDAPException(ResultCode.DECODING_ERROR,
916             ERR_UNIQUENESS_REQ_MISSING_UNIQUENESS_ID.get());
917      }
918
919      if (decodedAttributeTypes.isEmpty() && (decodedFilter == null))
920      {
921        throw new LDAPException(ResultCode.DECODING_ERROR,
922             ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get());
923      }
924
925      uniquenessID = decodedUniquenessID;
926      attributeTypes = decodedAttributeTypes;
927      multipleAttributeBehavior = decodedMultipleAttributeBehavior;
928      baseDN = decodedBaseDN;
929      filter = decodedFilter;
930      preventConflictsWithSoftDeletedEntries =
931           decodedPreventSoftDeletedConflicts;
932      preCommitValidationLevel = decodedPreCommitLevel;
933      postCommitValidationLevel = decodedPostCommitLevel;
934      alertOnPostCommitConflictDetection =
935           decodedAlertOnPostCommitConflictDetection;
936      createConflictPreventionDetailsEntry =
937           decodedCreateConflictPreventionDetailsEntry;
938    }
939    catch (final LDAPException le)
940    {
941      Debug.debugException(le);
942      throw le;
943    }
944    catch (final Exception e)
945    {
946      Debug.debugException(e);
947      throw new LDAPException(ResultCode.DECODING_ERROR,
948           ERR_UNIQUENESS_REQ_DECODE_ERROR_DECODING_VALUE.get(
949                StaticUtils.getExceptionMessage(e)),
950           e);
951    }
952  }
953
954
955
956  /**
957   * Retrieves the uniqueness identifier for this control, which may be used to
958   * identify the response control that corresponds to this request control.
959   * This is primarily useful for requests that contain multiple uniqueness
960   * controls, as there may be a separate response control for each.
961   *
962   * @return  The uniqueness identifier for this control.
963   */
964  @NotNull()
965  public String getUniquenessID()
966  {
967    return uniquenessID;
968  }
969
970
971
972  /**
973   * Retrieves the set of attribute types that the server will check for
974   * uniqueness conflicts.
975   *
976   * @return  The set of attribute types that the server will check for
977   *          uniqueness conflicts, or an empty set if only a filter should be
978   *          used to identify conflicts.
979   */
980  @NotNull()
981  public Set<String> getAttributeTypes()
982  {
983    return attributeTypes;
984  }
985
986
987
988  /**
989   * Retrieves the behavior that the server should exhibit if multiple attribute
990   * types are configured.
991   *
992   * @return  The behavior that the server should exhibit if multiple attribute
993   *          types are configured.
994   */
995  @NotNull()
996  public UniquenessMultipleAttributeBehavior getMultipleAttributeBehavior()
997  {
998    return multipleAttributeBehavior;
999  }
1000
1001
1002
1003  /**
1004   * Retrieves the base DN that will be used for searches used to identify
1005   * uniqueness conflicts, if defined.
1006   *
1007   * @return  The base DN that will be used for searches used to identify
1008   *          uniqueness conflicts, or {@code null} if the server should search
1009   *          below all public naming contexts.
1010   */
1011  @Nullable()
1012  public String getBaseDN()
1013  {
1014    return baseDN;
1015  }
1016
1017
1018
1019  /**
1020   * Retrieves a filter that will be used to identify uniqueness conflicts, if
1021   * defined.
1022   *
1023   * @return  A filter that will be used to identify uniqueness conflicts, or
1024   *          {@code null} if no filter has been defined.
1025   */
1026  @Nullable()
1027  public Filter getFilter()
1028  {
1029    return filter;
1030  }
1031
1032
1033
1034  /**
1035   * Indicates whether the server should attempt to identify conflicts with
1036   * soft-deleted entries.
1037   *
1038   * @return  {@code true} if the server should identify conflicts with both
1039   *          regular entries and soft-deleted entries, or {@code false} if the
1040   *          server should only identify conflicts with regular entries.
1041   */
1042  public boolean preventConflictsWithSoftDeletedEntries()
1043  {
1044    return preventConflictsWithSoftDeletedEntries;
1045  }
1046
1047
1048
1049  /**
1050   * Retrieves the pre-commit validation level, which will be used to identify
1051   * any conflicts before the associated request is processed.
1052   *
1053   * @return  The pre-commit validation level.
1054   */
1055  @NotNull()
1056  public UniquenessValidationLevel getPreCommitValidationLevel()
1057  {
1058    return preCommitValidationLevel;
1059  }
1060
1061
1062
1063  /**
1064   * Retrieves the post-commit validation level, which will be used to identify
1065   * any conflicts that were introduced by the request with which the control is
1066   * associated, or by some other concurrent changed processed in the server.
1067   *
1068   * @return  The post-commit validation level.
1069   */
1070  @NotNull()
1071  public UniquenessValidationLevel getPostCommitValidationLevel()
1072  {
1073    return postCommitValidationLevel;
1074  }
1075
1076
1077
1078  /**
1079   * Indicates whether the server should raise an administrative alert if a
1080   * conflict is detected during post-commit validation processing.
1081   *
1082   * @return  {@code true} if the server should raise an administrative alert if
1083   *          a conflict is detected during post-commit validation processing,
1084   *          or {@code false} if not.
1085   */
1086  public boolean alertOnPostCommitConflictDetection()
1087  {
1088    return alertOnPostCommitConflictDetection;
1089  }
1090
1091
1092
1093  /**
1094   * Indicates whether the server should create a temporary conflict prevention
1095   * details entry before beginning pre-commit validation to provide better
1096   * support for preventing conflicts.  If created, the entry will be removed
1097   * after post-commit validation processing has completed.
1098   *
1099   * @return  {@code true} if the server should create a temporary conflict
1100   *          prevention details entry before beginning pre-commit validation,
1101   *          or {@code false} if not.
1102   */
1103  public boolean createConflictPreventionDetailsEntry()
1104  {
1105    return createConflictPreventionDetailsEntry;
1106  }
1107
1108
1109
1110  /**
1111   * {@inheritDoc}
1112   */
1113  @Override()
1114  @NotNull()
1115  public String getControlName()
1116  {
1117    return INFO_UNIQUENESS_REQ_CONTROL_NAME.get();
1118  }
1119
1120
1121
1122  /**
1123   * Retrieves a representation of this uniqueness request control as a JSON
1124   * object.  The JSON object uses the following fields:
1125   * <UL>
1126   *   <LI>
1127   *     {@code oid} -- A mandatory string field whose value is the object
1128   *     identifier for this control.  For the uniqueness request control, the
1129   *     OID is "1.3.6.1.4.1.30221.2.5.52".
1130   *   </LI>
1131   *   <LI>
1132   *     {@code control-name} -- An optional string field whose value is a
1133   *     human-readable name for this control.  This field is only intended for
1134   *     descriptive purposes, and when decoding a control, the {@code oid}
1135   *     field should be used to identify the type of control.
1136   *   </LI>
1137   *   <LI>
1138   *     {@code criticality} -- A mandatory Boolean field used to indicate
1139   *     whether this control is considered critical.
1140   *   </LI>
1141   *   <LI>
1142   *     {@code value-base64} -- An optional string field whose value is a
1143   *     base64-encoded representation of the raw value for this uniqueness
1144   *     request control.  Exactly one of the {@code value-base64} and
1145   *     {@code value-json} fields must be present.
1146   *   </LI>
1147   *   <LI>
1148   *     {@code value-json} -- An optional JSON object field whose value is a
1149   *     user-friendly representation of the value for this uniqueness request
1150   *     control.  Exactly one of the {@code value-base64} and
1151   *     {@code value-json} fields must be present, and if the
1152   *     {@code value-json} field is used, then it will use the following
1153   *     fields:
1154   *     <UL>
1155   *       <LI>
1156   *         {@code uniqueness-id} -- An optional string field that holds a
1157   *         unique identifier for this uniqueness control instance.
1158   *       </LI>
1159   *       <LI>
1160   *         {@code attribute-types} -- An optional array field whose values are
1161   *         the names of the attribute types for which to impose uniqueness.
1162   *         It may be empty or absent if uniqueness should only be enforced
1163   *         using a filter.
1164   *       </LI>
1165   *       <LI>
1166   *         {@code multiple-attribute-behavior} -- An optional string field
1167   *         whose value indicates the behavior that should be used if multiple
1168   *         unique attribute types are requested.  If present, the value should
1169   *         be one of "{@code unique-within-each-attribute}",
1170   *         "{@code unique-across-all-attributes-including-in-the-same-entry}",
1171   *         "{@code unique-across-all-attributes-except-in-the-same-entry}", or
1172   *         "{@code unique-in-combination}".
1173   *       </LI>
1174   *       <LI>
1175   *         {@code base-dn} -- An optional string field whose value is the base
1176   *         DN that will be used for searches used to identify uniqueness
1177   *         conflicts.
1178   *       </LI>
1179   *       <LI>
1180   *         {@code filter} -- An optional string field whose value is the
1181   *         string representation of a search filter that will be used to
1182   *         identify uniqueness conflicts.
1183   *       </LI>
1184   *       <LI>
1185   *         {@code prevent-conflicts-with-soft-deleted-entries} -- An optional
1186   *         Boolean field that indicates whether the server should consider
1187   *         soft-deleted entries when looking for conflicts.
1188   *       </LI>
1189   *       <LI>
1190   *         {@code pre-commit-validation-level} -- A mandatory string field
1191   *         whose value specifies the level of validation that the server
1192   *         should perform before attempting to apply the change.  The value
1193   *         must be one of "{@code none}", "{@code all-subtree-views}",
1194   *         "{@code all-backend-sets}", or
1195   *         "{@code all-available-backend-servers}".
1196   *       </LI>
1197   *       <LI>
1198   *         {@code post-commit-validation-level} -- A mandatory string field
1199   *         whose value specifies the level of validation that the server
1200   *         should perform after applying the change.  The value must be one of
1201   *         "{@code none}", "{@code all-subtree-views}",
1202   *         "{@code all-backend-sets}", or
1203   *         "{@code all-available-backend-servers}".
1204   *       </LI>
1205   *       <LI>
1206   *         {@code alert-on-post-commit-conflict-detection} -- An optional
1207   *         Boolean field that indicates whether the server should raise an
1208   *         administrative alert if a conflict was detected after the change
1209   *         was applied.
1210   *       </LI>
1211   *       <LI>
1212   *         {@code create-conflict-prevention-details-entry} -- An optional
1213   *         Boolean field that indicates whether the server should create a
1214   *         temporary entry that can improve its ability to detect conflicts
1215   *         before they happen.
1216   *       </LI>
1217   *     </UL>
1218   *   </LI>
1219   * </UL>
1220   *
1221   * @return  A JSON object that contains a representation of this control.
1222   */
1223  @Override()
1224  @NotNull()
1225  public JSONObject toJSONControl()
1226  {
1227    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
1228    valueFields.put(JSON_FIELD_UNIQUENESS_ID, new JSONString(uniquenessID));
1229
1230    if (! attributeTypes.isEmpty())
1231    {
1232      final List<JSONValue> attributeTypesValues =
1233           new ArrayList<>(attributeTypes.size());
1234      for (final String attributeType : attributeTypes)
1235      {
1236        attributeTypesValues.add(new JSONString(attributeType));
1237      }
1238      valueFields.put(JSON_FIELD_ATTRIBUTE_TYPES,
1239           new JSONArray(attributeTypesValues));
1240    }
1241
1242    if ((attributeTypes.size() > 1) ||
1243         (multipleAttributeBehavior != UniquenessMultipleAttributeBehavior.
1244              UNIQUE_WITHIN_EACH_ATTRIBUTE))
1245    {
1246      switch (multipleAttributeBehavior)
1247      {
1248        case UNIQUE_WITHIN_EACH_ATTRIBUTE:
1249          valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR,
1250               new JSONString(JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE));
1251          break;
1252        case UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY:
1253          valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR,
1254               new JSONString(
1255               JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY));
1256          break;
1257        case UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY:
1258          valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR,
1259               new JSONString(
1260                    JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY));
1261          break;
1262        case UNIQUE_IN_COMBINATION:
1263          valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR,
1264               new JSONString(JSON_MAB_UNIQUE_IN_COMBINATION));
1265          break;
1266      }
1267    }
1268
1269    if (baseDN != null)
1270    {
1271      valueFields.put(JSON_FIELD_BASE_DN, new JSONString(baseDN));
1272    }
1273
1274    if (filter != null)
1275    {
1276      valueFields.put(JSON_FIELD_FILTER, new JSONString(filter.toString()));
1277    }
1278
1279    valueFields.put(JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES,
1280         new JSONBoolean(preventConflictsWithSoftDeletedEntries));
1281
1282    addJSONValidationLevel(valueFields, JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL,
1283         preCommitValidationLevel);
1284
1285    addJSONValidationLevel(valueFields, JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL,
1286         postCommitValidationLevel);
1287
1288    valueFields.put(JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION,
1289         new JSONBoolean(alertOnPostCommitConflictDetection));
1290
1291    valueFields.put(JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY,
1292         new JSONBoolean(createConflictPreventionDetailsEntry));
1293
1294    return new JSONObject(
1295         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
1296              UNIQUENESS_REQUEST_OID),
1297         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
1298              INFO_UNIQUENESS_REQ_CONTROL_NAME.get()),
1299         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
1300              isCritical()),
1301         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
1302              new JSONObject(valueFields)));
1303  }
1304
1305
1306
1307  /**
1308   * Updates the provided map with a JSON field for the given validation level.
1309   *
1310   * @param  valueFields      The map to which the field should be added.  It
1311   *                          must not be {@code null}./
1312   * @param  fieldName        The name to use for the field.  It must not be
1313   *                          {@code null}.
1314   * @param  validationLevel  The validation level value to use.  It must not be
1315   *                          {@code null}.
1316   */
1317  private static void addJSONValidationLevel(
1318               @NotNull final Map<String,JSONValue> valueFields,
1319               @NotNull final String fieldName,
1320               @NotNull final UniquenessValidationLevel validationLevel)
1321  {
1322    switch (validationLevel)
1323    {
1324      case NONE:
1325        valueFields.put(fieldName, new JSONString(JSON_VALIDATION_LEVEL_NONE));
1326        break;
1327      case ALL_SUBTREE_VIEWS:
1328        valueFields.put(fieldName,
1329             new JSONString(JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS));
1330        break;
1331      case ALL_BACKEND_SETS:
1332        valueFields.put(fieldName,
1333             new JSONString(JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS));
1334        break;
1335      case ALL_AVAILABLE_BACKEND_SERVERS:
1336        valueFields.put(fieldName, new JSONString(
1337             JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS));
1338        break;
1339    }
1340  }
1341
1342
1343
1344  /**
1345   * Attempts to decode the provided object as a JSON representation of a
1346   * uniqueness request control.
1347   *
1348   * @param  controlObject  The JSON object to be decoded.  It must not be
1349   *                        {@code null}.
1350   * @param  strict         Indicates whether to use strict mode when decoding
1351   *                        the provided JSON object.  If this is {@code true},
1352   *                        then this method will throw an exception if the
1353   *                        provided JSON object contains any unrecognized
1354   *                        fields.  If this is {@code false}, then unrecognized
1355   *                        fields will be ignored.
1356   *
1357   * @return  The uniqueness request control that was decoded from the provided
1358   *          JSON object.
1359   *
1360   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
1361   *                         valid uniqueness request control.
1362   */
1363  @NotNull()
1364  public static UniquenessRequestControl decodeJSONControl(
1365              @NotNull final JSONObject controlObject,
1366              final boolean strict)
1367         throws LDAPException
1368  {
1369    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
1370         controlObject, strict, true, true);
1371
1372    final ASN1OctetString rawValue = jsonControl.getRawValue();
1373    if (rawValue != null)
1374    {
1375      return new UniquenessRequestControl(new Control(
1376           jsonControl.getOID(), jsonControl.getCriticality(), rawValue));
1377    }
1378
1379
1380    final JSONObject valueObject = jsonControl.getValueObject();
1381
1382    final String uniquenessID =
1383         valueObject.getFieldAsString(JSON_FIELD_UNIQUENESS_ID);
1384
1385    final Set<String> attributeTypes;
1386    final List<JSONValue> attributeTypesValues =
1387         valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTE_TYPES);
1388    if ((attributeTypesValues == null) || attributeTypesValues.isEmpty())
1389    {
1390      attributeTypes = Collections.emptySet();
1391    }
1392    else
1393    {
1394      attributeTypes = new LinkedHashSet<>();
1395      for (final JSONValue v : attributeTypesValues)
1396      {
1397        if (v instanceof JSONString)
1398        {
1399          attributeTypes.add(((JSONString) v).stringValue());
1400        }
1401        else
1402        {
1403          throw new LDAPException(ResultCode.DECODING_ERROR,
1404               ERR_UNIQUENESS_REQ_JSON_ATTR_TYPE_NOT_STRING.get(
1405                    controlObject.toSingleLineString(),
1406                    JSON_FIELD_ATTRIBUTE_TYPES));
1407        }
1408      }
1409    }
1410
1411    final Filter filter;
1412    final String filterStr = valueObject.getFieldAsString(JSON_FIELD_FILTER);
1413    if (filterStr == null)
1414    {
1415      filter = null;
1416    }
1417    else
1418    {
1419      try
1420      {
1421        filter = Filter.create(filterStr);
1422      }
1423      catch (final LDAPException e)
1424      {
1425        Debug.debugException(e);
1426        throw new LDAPException(ResultCode.DECODING_ERROR,
1427             ERR_UNIQUENESS_REQ_JSON_INVALID_FILTER.get(
1428                  controlObject.toSingleLineString(), JSON_FIELD_FILTER,
1429                  filterStr));
1430      }
1431    }
1432
1433
1434    final UniquenessRequestControlProperties properties;
1435    if (attributeTypes.isEmpty())
1436    {
1437      if (filter == null)
1438      {
1439        throw new LDAPException(ResultCode.DECODING_ERROR,
1440             ERR_UNIQUENESS_REQ_JSON_NEITHER_ATTR_TYPES_NOR_FILTER.get(
1441                  controlObject.toSingleLineString(),
1442                  JSON_FIELD_ATTRIBUTE_TYPES, JSON_FIELD_FILTER));
1443      }
1444      else
1445      {
1446        properties = new UniquenessRequestControlProperties(filter);
1447      }
1448    }
1449    else
1450    {
1451      properties = new UniquenessRequestControlProperties(attributeTypes);
1452
1453      if (filter != null)
1454      {
1455        properties.setFilter(filter);
1456      }
1457    }
1458
1459
1460    final String multipleAttributeBehaviorStr =
1461         valueObject.getFieldAsString(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR);
1462    if (multipleAttributeBehaviorStr == null)
1463    {
1464      properties.setMultipleAttributeBehavior(
1465           UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE);
1466    }
1467    else
1468    {
1469      switch (multipleAttributeBehaviorStr)
1470      {
1471        case JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE:
1472          properties.setMultipleAttributeBehavior(
1473               UniquenessMultipleAttributeBehavior.
1474                    UNIQUE_WITHIN_EACH_ATTRIBUTE);
1475          break;
1476        case JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY:
1477          properties.setMultipleAttributeBehavior(
1478               UniquenessMultipleAttributeBehavior.
1479                    UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY);
1480          break;
1481        case JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY:
1482          properties.setMultipleAttributeBehavior(
1483               UniquenessMultipleAttributeBehavior.
1484                    UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY);
1485          break;
1486        case JSON_MAB_UNIQUE_IN_COMBINATION:
1487          properties.setMultipleAttributeBehavior(
1488               UniquenessMultipleAttributeBehavior.UNIQUE_IN_COMBINATION);
1489          break;
1490        default:
1491          throw new LDAPException(ResultCode.DECODING_ERROR,
1492               ERR_UNIQUENESS_REQ_JSON_UNRECOGNIZED_MULTIPLE_ATTR_BEHAVIOR.get(
1493                    controlObject.toSingleLineString(),
1494                    JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR,
1495                    multipleAttributeBehaviorStr,
1496                    JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE,
1497                    JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY,
1498                    JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY,
1499                    JSON_MAB_UNIQUE_IN_COMBINATION));
1500      }
1501    }
1502
1503
1504    final String baseDNStr = valueObject.getFieldAsString(JSON_FIELD_BASE_DN);
1505    if (baseDNStr != null)
1506    {
1507      properties.setBaseDN(baseDNStr);
1508    }
1509
1510
1511    final Boolean preventConflictsWithSoftDeletedEntries =
1512         valueObject.getFieldAsBoolean(
1513              JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES);
1514    if (preventConflictsWithSoftDeletedEntries == null)
1515    {
1516      properties.setPreventConflictsWithSoftDeletedEntries(false);
1517    }
1518    else
1519    {
1520      properties.setPreventConflictsWithSoftDeletedEntries(
1521           preventConflictsWithSoftDeletedEntries);
1522    }
1523
1524
1525    properties.setPreCommitValidationLevel(getValidationLevelJSON(
1526         controlObject, valueObject, JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL));
1527
1528
1529    properties.setPostCommitValidationLevel(getValidationLevelJSON(
1530         controlObject, valueObject, JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL));
1531
1532
1533    final Boolean alertOnPostCommitConflictDetection =
1534         valueObject.getFieldAsBoolean(
1535              JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION);
1536    if (alertOnPostCommitConflictDetection == null)
1537    {
1538      properties.setAlertOnPostCommitConflictDetection(true);
1539    }
1540    else
1541    {
1542      properties.setAlertOnPostCommitConflictDetection(
1543           alertOnPostCommitConflictDetection);
1544    }
1545
1546
1547    final Boolean createConflictPreventionDetailsEntry =
1548         valueObject.getFieldAsBoolean(
1549              JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY);
1550    if (createConflictPreventionDetailsEntry == null)
1551    {
1552      properties.setCreateConflictPreventionDetailsEntry(false);
1553    }
1554    else
1555    {
1556      properties.setCreateConflictPreventionDetailsEntry(
1557           createConflictPreventionDetailsEntry);
1558    }
1559
1560
1561    if (strict)
1562    {
1563      final List<String> unrecognizedFields =
1564           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1565                valueObject, JSON_FIELD_UNIQUENESS_ID,
1566                JSON_FIELD_ATTRIBUTE_TYPES,
1567                JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR,
1568                JSON_FIELD_BASE_DN, JSON_FIELD_FILTER,
1569                JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES,
1570                JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL,
1571                JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL,
1572                JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION,
1573                JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY);
1574      if (! unrecognizedFields.isEmpty())
1575      {
1576        throw new LDAPException(ResultCode.DECODING_ERROR,
1577             ERR_UNIQUENESS_REQ_JSON_UNRECOGNIZED_FIELD.get(
1578                  controlObject.toSingleLineString(),
1579                  unrecognizedFields.get(0)));
1580      }
1581    }
1582
1583
1584    return new UniquenessRequestControl(jsonControl.getCriticality(),
1585         uniquenessID, properties);
1586  }
1587
1588
1589
1590  /**
1591   * Retrieves the uniqueness validation level value contained in the specified
1592   * field of the given value object.
1593   *
1594   * @param  controlObject  The JSON object containing an encoded representation
1595   *                        of the control being decoded.  It must not be
1596   *                        {@code null}.
1597   * @param  valueObject    The JSON object from which the value is to be
1598   *                        retrieved.  It must not be {@code null}.
1599   * @param  fieldName      The name of the field that is expected to contain
1600   *                        the value.  It must not be {@code null}.
1601   *
1602   * @return  The uniqueness validation level value that was retrieved.
1603   *
1604   * @throws  LDAPException  If the value object does not have a valid
1605   *                         uniqueness validation level value in the specified
1606   *                         field.
1607   */
1608  @NotNull()
1609  private static UniquenessValidationLevel getValidationLevelJSON(
1610               @NotNull final JSONObject controlObject,
1611               @NotNull final JSONObject valueObject,
1612               @NotNull final String fieldName)
1613          throws LDAPException
1614  {
1615    final String valueStr = valueObject.getFieldAsString(fieldName);
1616    if (valueStr == null)
1617    {
1618      throw new LDAPException(ResultCode.DECODING_ERROR,
1619           ERR_UNIQUENESS_REQ_JSON_MISSING_FIELD.get(
1620                controlObject.toSingleLineString(), fieldName));
1621    }
1622
1623    switch (valueStr)
1624    {
1625      case JSON_VALIDATION_LEVEL_NONE:
1626        return UniquenessValidationLevel.NONE;
1627      case JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS:
1628        return UniquenessValidationLevel.ALL_SUBTREE_VIEWS;
1629      case JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS:
1630        return UniquenessValidationLevel.ALL_BACKEND_SETS;
1631      case JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS:
1632        return UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS;
1633      default:
1634        throw new LDAPException(ResultCode.DECODING_ERROR,
1635             ERR_UNIQUENESS_REQ_JSON_UNRECOGNIZED_VALIDATION_LEVEL.get(
1636                  controlObject.toSingleLineString(), fieldName, valueStr,
1637                  JSON_VALIDATION_LEVEL_NONE,
1638                  JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS,
1639                  JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS,
1640                  JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS));
1641    }
1642  }
1643
1644
1645
1646  /**
1647   * {@inheritDoc}
1648   */
1649  @Override()
1650  public void toString(@NotNull final StringBuilder buffer)
1651  {
1652    buffer.append("UniquenessRequestControl(isCritical=");
1653    buffer.append(isCritical());
1654    buffer.append(", uniquenessID='");
1655    buffer.append(uniquenessID);
1656    buffer.append("', attributeTypes={");
1657
1658    final Iterator<String> attributeTypesIterator = attributeTypes.iterator();
1659    while (attributeTypesIterator.hasNext())
1660    {
1661      buffer.append('\'');
1662      buffer.append(attributeTypesIterator.next());
1663      buffer.append('\'');
1664
1665      if (attributeTypesIterator.hasNext())
1666      {
1667        buffer.append(", ");
1668      }
1669    }
1670
1671    buffer.append("}, multipleAttributeBehavior=");
1672    buffer.append(multipleAttributeBehavior);
1673
1674    if (baseDN != null)
1675    {
1676      buffer.append(", baseDN='");
1677      buffer.append(baseDN);
1678      buffer.append('\'');
1679    }
1680
1681    if (filter != null)
1682    {
1683      buffer.append(", filter='");
1684      buffer.append(filter);
1685      buffer.append('\'');
1686    }
1687
1688    buffer.append(", preventConflictsWithSoftDeletedEntries=");
1689    buffer.append(preventConflictsWithSoftDeletedEntries);
1690    buffer.append(", preCommitValidationLevel=");
1691    buffer.append(preCommitValidationLevel);
1692    buffer.append(", postCommitValidationLevel=");
1693    buffer.append(postCommitValidationLevel);
1694    buffer.append(", alertOnPostCommitConflictDetection=");
1695    buffer.append(alertOnPostCommitConflictDetection);
1696    buffer.append(", createConflictPreventionDetailsEntry=");
1697    buffer.append(createConflictPreventionDetailsEntry);
1698    buffer.append(')');
1699  }
1700}