001/*
002 * Copyright 2017-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2022 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-2022 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.LinkedHashSet;
044import java.util.Set;
045
046import com.unboundid.asn1.ASN1Boolean;
047import com.unboundid.asn1.ASN1Element;
048import com.unboundid.asn1.ASN1Enumerated;
049import com.unboundid.asn1.ASN1OctetString;
050import com.unboundid.asn1.ASN1Sequence;
051import com.unboundid.asn1.ASN1Set;
052import com.unboundid.ldap.sdk.Control;
053import com.unboundid.ldap.sdk.Filter;
054import com.unboundid.ldap.sdk.LDAPException;
055import com.unboundid.ldap.sdk.ResultCode;
056import com.unboundid.util.CryptoHelper;
057import com.unboundid.util.Debug;
058import com.unboundid.util.NotMutable;
059import com.unboundid.util.NotNull;
060import com.unboundid.util.Nullable;
061import com.unboundid.util.StaticUtils;
062import com.unboundid.util.ThreadSafety;
063import com.unboundid.util.ThreadSafetyLevel;
064import com.unboundid.util.Validator;
065
066import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
067
068
069
070/**
071 * This class provides a request control that may be included in an add, modify,
072 * or modify DN request to ensure that the contents of that request will not
073 * result in a uniqueness conflict with any other entry in the server.  Each
074 * instance of this control should define exactly one uniqueness constraint for
075 * the associated operation.  Multiple instances of this control can be included
076 * in the same request to define multiple independent uniqueness constraints
077 * that must all be satisfied.  If any of the uniqueness constraints is not
078 * satisfied, then the corresponding LDAP result should have a result code of
079 * {@link ResultCode#ASSERTION_FAILED} and a {@link UniquenessResponseControl}
080 * for each uniqueness constraint that was not satisfied.
081 * <BR>
082 * <BLOCKQUOTE>
083 *   <B>NOTE:</B>  This class, and other classes within the
084 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
085 *   supported for use against Ping Identity, UnboundID, and
086 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
087 *   for proprietary functionality or for external specifications that are not
088 *   considered stable or mature enough to be guaranteed to work in an
089 *   interoperable way with other types of LDAP servers.
090 * </BLOCKQUOTE>
091 * <BR>
092 * The request properties must contain either one or more attribute types, a
093 * filter, or both.  If only a filter is specified, then the server will use
094 * that filter to identify conflicts (for an add request, any matches at all
095 * will be considered a conflict; for a modify or modify DN request, any matches
096 * with any entry other than the one being updated will be considered a
097 * conflict).  If a single attribute type is specified with no filter, then any
098 * change that would result in multiple entries having the same value for that
099 * attribute will be considered a conflict.  If multiple attribute types are
100 * specified, then the multiple attribute behavior will be used to determine how
101 * to identify conflicts, as documented in the
102 * {@link UniquenessMultipleAttributeBehavior} enum.  If both a set of attribute
103 * types and a filter are provided, then only entries matching both sets of
104 * criteria will be considered a conflict.
105 * <BR><BR>
106 * The server can perform two different searches in an attempt to identify
107 * conflicts.  In the pre-commit phase, it will attempt to identify any
108 * conflicts that already exist, and will reject the associated change if there
109 * are any.  In the post-commit phase, it can see if there were any conflicts
110 * introduced by the change itself or by another change happening at the same
111 * time.  If a conflict is detected in the post-commit phase, then the server
112 * won't have prevented it, but at least the control can be used to provide
113 * notification about it.  The server may also raise an administrative alert to
114 * notify administrators about the conflict.
115 * <BR><BR>
116 * Although post-commit validation on its own should be able to detect conflicts
117 * that arise as a result of concurrent changes in other instances, it is also
118 * possible to take additional measures to help prevent conflicts from arising
119 * in the first place.  The control may indicate that the server should create
120 * a temporary conflict prevention details entry before beginning pre-commit
121 * validation processing.  This entry may be found during pre-commit validation
122 * performed for any conflicting concurrent updates so that the conflicting
123 * operation is rejected.  This temporary entry will be automatically removed
124 * after uniqueness processing has completed, regardless of its success or
125 * failure.
126 * <BR><BR>
127 * This request control may be sent either directly to a Directory Server
128 * instance, or it may be sent to a Directory Proxy Server with or without entry
129 * balancing.  If the request is sent directly to a Directory Server, then only
130 * that one server will be checked for uniqueness conflicts, and it is possible
131 * that concurrent conflicts may be introduced on other servers that have not
132 * yet been replicated by the time control processing has completed.  If the
133 * request is sent to a Directory Proxy Server instance, then search may be
134 * processed in one or more backend servers based on the pre-commit and
135 * post-commit validation levels, and at the most paranoid levels, it is highly
136 * unlikely that any conflicts will go unnoticed.
137 * <BR><BR>
138 * The request control has an OID of 1.3.6.1.4.1.30221.2.5.52, a criticality of
139 * either {@code true} or {@code false}, and a value with the following
140 * encoding:
141 * <PRE>
142 *   UniquenessRequestValue ::= SEQUENCE {
143 *     uniquenessID                            [0] OCTET STRING,
144 *     attributeTypes                          [1] SET OF OCTET STRING OPTIONAL,
145 *     multipleAttributeBehavior               [2] ENUMERATED {
146 *       uniqueWithinEachAttribute                      (0),
147 *       uniqueAcrossAllAttributesIncludingInSameEntry  (1),
148 *       uniqueAcrossAllAttributesExceptInSameEntry     (2),
149 *       uniqueInCombination                            (3),
150 *       ... } DEFAULT uniqueWithinEachAttribute,
151 *     baseDN                                  [3] LDAPDN OPTIONAL,
152 *     filter                                  [4] Filter OPTIONAL,
153 *     preventConflictsWithSoftDeletedEntries  [5] BOOLEAN DEFAULT FALSE,
154 *     preCommitValidationLevel                [6] ENUMERATED {
155 *       none                        (0),
156 *       allSubtreeViews             (1),
157 *       allBackendSets              (2),
158 *       allAvailableBackendServers  (3),
159 *       ... } DEFAULT allSubtreeViews,
160 *     postCommitValidationLevel               [7] ENUMERATED {
161 *       none                        (0),
162 *       allSubtreeViews             (1),
163 *       allBackendSets              (2),
164 *       allAvailableBackendServers  (3),
165 *       ... } DEFAULT allSubtreeViews,
166 *     alertOnPostCommitConflictDetection      [8] BOOLEAN DEFAULT TRUE,
167 *     createConflictPreventionDetailsEntry    [9] BOOLEAN DEFAULT FALSE,
168 *     ... }
169 * </PRE>
170 * <BR><BR>
171 * <H2>Example</H2>
172 * The following example demonstrates how to use the uniqueness request control
173 * to only process an add operation if it does not result in multiple entries
174 * that have the same uid value:
175 * <BR><BR>
176 * <PRE>
177 * // Create the properties to build a uniqueness request control that
178 * // will try to prevent an add operation from creating a new entry
179 * // that has the same uid as an existing entry in the server.  During
180 * // pre-commit processing (which happens before the server actually
181 * // processes the add), the server will check at least one server in
182 * // each entry-balancing backend set (or just one server in a
183 * // non-entry-balanced deployment).  During post-commit processing
184 * // (which happens if the add succeeds), the server will double-check
185 * // that no conflicting entry was added on any available server in the
186 * // topology.  Also ensure that the server will not allow conflicts
187 * // with soft-deleted entries.
188 * final UniquenessRequestControlProperties uniquenessProperties =
189 *      new UniquenessRequestControlProperties("uid");
190 * uniquenessProperties.setPreCommitValidationLevel(
191 *      UniquenessValidationLevel.ALL_BACKEND_SETS);
192 * uniquenessProperties.setPostCommitValidationLevel(
193 *      UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS);
194 * uniquenessProperties.setPreventConflictsWithSoftDeletedEntries(true);
195 *
196 * // Create the request control.  It will be critical so that the
197 * // server will not attempt to process the add if it can't honor the
198 * // uniqueness request.
199 * final boolean isCritical = true;
200 * final String uniquenessID = "uid-uniqueness";
201 * final UniquenessRequestControl uniquenessRequestControl =
202 *      new UniquenessRequestControl(isCritical, uniquenessID,
203 *           uniquenessProperties);
204 *
205 * // Attach the control to an add request.
206 * addRequest.addControl(uniquenessRequestControl);
207 *
208 * // Send the add request to the server and read the result.
209 * try
210 * {
211 *   final LDAPResult addResult = connection.add(addRequest);
212 *
213 *   // The add operation succeeded, so the entry should have been
214 *   // created, but there is still the possibility that a post-commit
215 *   // conflict was discovered, indicating that another request
216 *   // processed at about the same time as our add introduced a
217 *   // conflicting entry.
218 *   final Map&lt;String,UniquenessResponseControl&gt; uniquenessResponses;
219 *   try
220 *   {
221 *     uniquenessResponses = UniquenessResponseControl.get(addResult);
222 *   }
223 *   catch (final LDAPException e)
224 *   {
225 *     throw new RuntimeException(
226 *          "The add succeeded, but an error occurred while trying " +
227 *               "to decode a uniqueness response control in add " +
228 *               "result " + addResult + ":  " +
229 *               StaticUtils.getExceptionMessage(e),
230 *          e);
231 *   }
232 *
233 *   final UniquenessResponseControl uniquenessResponseControl =
234 *        uniquenessResponses.get(uniquenessID);
235 *   if ((uniquenessResponseControl != null) &amp;&amp;
236 *        uniquenessResponseControl.uniquenessConflictFound())
237 *   {
238 *     throw new RuntimeException(
239 *          "The add succeeded, but a uniqueness conflict was found  " +
240 *               "Uniqueness validation message:  " +
241 *               uniquenessResponseControl.getValidationMessage());
242 *   }
243 * }
244 * catch (final LDAPException e)
245 * {
246 *   // The add attempt failed.  It might have been because of a
247 *   // uniqueness problem, or it could have been for some other reason.
248 *   // To figure out which it was, look to see if there is an
249 *   // appropriate uniqueness response control.
250 *   final Map&lt;String, UniquenessResponseControl&gt; uniquenessResponses;
251 *   try
252 *   {
253 *     uniquenessResponses =
254 *          UniquenessResponseControl.get(e.toLDAPResult());
255 *   }
256 *   catch (final LDAPException e2)
257 *   {
258 *     throw new LDAPException(e.getResultCode(),
259 *          "The add attempt failed with result " + e.toLDAPResult() +
260 *               ", and an error occurred while trying to decode a " +
261 *               "uniqueness response control in the result:  " +
262 *               StaticUtils.getExceptionMessage(e2),
263 *          e);
264 *   }
265 *
266 *   final UniquenessResponseControl uniquenessResponseControl =
267 *        uniquenessResponses.get(uniquenessID);
268 *   if (uniquenessResponseControl == null)
269 *   {
270 *     // The add result didn't include a uniqueness response control,
271 *     // indicating that the failure was not because of a uniqueness
272 *     // conflict.
273 *     throw e;
274 *   }
275 *
276 *   if (uniquenessResponseControl.uniquenessConflictFound())
277 *   {
278 *     // The add failed, and the uniqueness response control indicates
279 *     // that the failure was because of a uniqueness conflict.
280 *
281 *     final UniquenessValidationResult preCommitResult =
282 *          uniquenessResponseControl.getPreCommitValidationResult();
283 *     final UniquenessValidationResult postCommitResult =
284 *          uniquenessResponseControl.getPreCommitValidationResult();
285 *     final String validationMessage =
286 *          uniquenessResponseControl.getValidationMessage();
287 *
288 *     throw e;
289 *   }
290 *   else
291 *   {
292 *     // The add failed, but the uniqueness response control indicates
293 *     // that the failure was not because of a uniqueness conflict.
294 *     throw e;
295 *   }
296 * }
297 * </PRE>
298 */
299@NotMutable()
300@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
301public final class UniquenessRequestControl
302       extends Control
303{
304  /**
305   * The OID (1.3.6.1.4.1.30221.2.5.52) for the uniqueness request control.
306   */
307  @NotNull public static final String UNIQUENESS_REQUEST_OID =
308       "1.3.6.1.4.1.30221.2.5.52";
309
310
311
312  /**
313   * The BER type for the uniqueness ID element in the value sequence.
314   */
315  private static final byte TYPE_UNIQUENESS_ID = (byte) 0x80;
316
317
318
319  /**
320   * The BER type for the attribute types element in the value sequence.
321   */
322  private static final byte TYPE_ATTRIBUTE_TYPES = (byte) 0xA1;
323
324
325
326  /**
327   * The BER type for the multiple attribute behavior element in the value
328   * sequence.
329   */
330  private static final byte TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR = (byte) 0x82;
331
332
333
334  /**
335   * The BER type for the base DN element in the value sequence.
336   */
337  private static final byte TYPE_BASE_DN = (byte) 0x83;
338
339
340
341  /**
342   * The BER type for the filter element in the value sequence.
343   */
344  private static final byte TYPE_FILTER = (byte) 0xA4;
345
346
347
348  /**
349   * The BER type for the prevent conflicts with soft-deleted entries element in
350   * the value sequence.
351   */
352  private static final byte TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES =
353       (byte) 0x85;
354
355
356
357  /**
358   * The BER type for the pre-commit validation element in the value sequence.
359   */
360  private static final byte TYPE_PRE_COMMIT_VALIDATION_LEVEL = (byte) 0x86;
361
362
363
364  /**
365   * The BER type for the post-commit validation element in the value sequence.
366   */
367  private static final byte TYPE_POST_COMMIT_VALIDATION_LEVEL = (byte) 0x87;
368
369
370
371  /**
372   * The BER type for the value sequence element that indicates whether to
373   * raise an administrative alert if a conflict is detected during post-commit
374   * validation.
375   */
376  private static final byte TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION =
377       (byte) 0x88;
378
379
380
381  /**
382   * The BER type for the value sequence element that indicates whether to
383   * create a conflict prevention details entry before pre-commit validation as
384   * a means of helping to avoid conflicts.
385   */
386  private static final byte TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY =
387       (byte) 0x89;
388
389
390
391  /**
392   * The serial version UID for this serializable class.
393   */
394  private static final long serialVersionUID = 7976218379635922852L;
395
396
397
398  // Indicates whether the server should raise an administrative alert if a
399  // conflict is detected during post-commit validation.
400  private final boolean alertOnPostCommitConflictDetection;
401
402  // Indicates whether the server should create a conflict prevention details
403  // entry before pre-commit validation as a means of helping to avoid
404  // conflicts.
405  private final boolean createConflictPreventionDetailsEntry;
406
407  // Indicates whether to prevent conflicts with soft-deleted entries.
408  private final boolean preventConflictsWithSoftDeletedEntries;
409
410  // An optional filter that should be used in the course of identifying
411  // uniqueness conflicts.
412  @Nullable private final Filter filter;
413
414  // A potentially-empty set of attribute types that should be checked for
415  // uniqueness conflicts.
416  @NotNull private final Set<String> attributeTypes;
417
418  // An optional base DN to use when checking for conflicts.
419  @Nullable private final String baseDN;
420
421  // A value that will be used to correlate this request control with its
422  // corresponding response control.
423  @NotNull private final String uniquenessID;
424
425  // The behavior that the server should exhibit if multiple attribute types
426  // are configured.
427  @NotNull private final UniquenessMultipleAttributeBehavior
428       multipleAttributeBehavior;
429
430  // The level of validation that the server should perform before processing
431  // the associated change.
432  @NotNull private final UniquenessValidationLevel postCommitValidationLevel;
433
434  // The level of validation that the server should perform after processing the
435  // associated change.
436  @NotNull private final UniquenessValidationLevel preCommitValidationLevel;
437
438
439
440  /**
441   * Creates a new uniqueness request control with the provided information.
442   *
443   * @param  isCritical    Indicates whether the control should be considered
444   *                       critical.
445   * @param  uniquenessID  A value that will be used to correlate this request
446   *                       control with its corresponding response control.  If
447   *                       this is {@code null}, then a unique identifier will
448   *                       be automatically generated.
449   * @param  properties    The set of properties for this control.  It must not
450   *                       be {@code null}.
451   *
452   * @throws  LDAPException  If the provided properties cannot be used to create
453   *                         a valid uniqueness request control.
454   */
455  public UniquenessRequestControl(final boolean isCritical,
456              @Nullable final String uniquenessID,
457              @NotNull final UniquenessRequestControlProperties properties)
458         throws LDAPException
459  {
460    this((uniquenessID == null
461              ? CryptoHelper.getRandomUUID().toString()
462              : uniquenessID),
463         properties, isCritical);
464  }
465
466
467
468  /**
469   * Creates a new uniqueness request control with the provided information.
470   * Note that this version of the constructor takes the same set of arguments
471   * as the above constructor, but in a different order (to distinguish between
472   * the two versions), and with the additional constraint that the uniqueness
473   * ID must not be {@code null}.
474   *
475   * @param  uniquenessID  A value that will be used to correlate this request
476   *                       control with its corresponding response control.  It
477   *                       must not be {@code null}.
478   * @param  properties    The set of properties for this control.  It must not
479   *                       be {@code null}.
480   * @param  isCritical    Indicates whether the control should be considered
481   *                       critical.
482   *
483   * @throws  LDAPException  If the provided properties cannot be used to create
484   *                         a valid uniqueness request control.
485   */
486  private UniquenessRequestControl(@NotNull final String uniquenessID,
487               @NotNull final UniquenessRequestControlProperties properties,
488               final boolean isCritical)
489          throws LDAPException
490  {
491    super(UNIQUENESS_REQUEST_OID, isCritical,
492         encodeValue(uniquenessID, properties));
493
494    Validator.ensureNotNull(uniquenessID);
495    this.uniquenessID = uniquenessID;
496
497    attributeTypes = properties.getAttributeTypes();
498    multipleAttributeBehavior = properties.getMultipleAttributeBehavior();
499    baseDN = properties.getBaseDN();
500    filter = properties.getFilter();
501    preventConflictsWithSoftDeletedEntries =
502         properties.preventConflictsWithSoftDeletedEntries();
503    preCommitValidationLevel = properties.getPreCommitValidationLevel();
504    postCommitValidationLevel = properties.getPostCommitValidationLevel();
505    alertOnPostCommitConflictDetection =
506         properties.alertOnPostCommitConflictDetection();
507    createConflictPreventionDetailsEntry =
508         properties.createConflictPreventionDetailsEntry();
509
510    if (attributeTypes.isEmpty() && (filter == null))
511    {
512      throw new LDAPException(ResultCode.PARAM_ERROR,
513           ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get());
514    }
515  }
516
517
518
519  /**
520   * Encodes the provided information into an octet string that is suitable for
521   * use as the value of this control.
522   *
523   * @param  uniquenessID  A value that will be used to correlate this request
524   *                       control with its corresponding response control.  It
525   *                       must not be {@code null}.
526   * @param  properties    The set of properties for this control.  It must not
527   *                       be {@code null}.
528   *
529   * @return  The encoded value that was created.
530   */
531  @NotNull()
532  private static ASN1OctetString encodeValue(@NotNull final String uniquenessID,
533       @NotNull final UniquenessRequestControlProperties properties)
534  {
535    final ArrayList<ASN1Element> elements = new ArrayList<>(10);
536
537    elements.add(new ASN1OctetString(TYPE_UNIQUENESS_ID, uniquenessID));
538
539    final Set<String> attributeTypes = properties.getAttributeTypes();
540    if (!attributeTypes.isEmpty())
541    {
542      final ArrayList<ASN1Element> attributeTypeElements =
543           new ArrayList<>(attributeTypes.size());
544      for (final String attributeType : attributeTypes)
545      {
546        attributeTypeElements.add(new ASN1OctetString(attributeType));
547      }
548      elements.add(new ASN1Set(TYPE_ATTRIBUTE_TYPES, attributeTypeElements));
549    }
550
551    final UniquenessMultipleAttributeBehavior multipleAttributeBehavior =
552         properties.getMultipleAttributeBehavior();
553    if (multipleAttributeBehavior !=
554         UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE)
555    {
556      elements.add(new ASN1Enumerated(TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR,
557           multipleAttributeBehavior.intValue()));
558    }
559
560    final String baseDN = properties.getBaseDN();
561    if (baseDN != null)
562    {
563      elements.add(new ASN1OctetString(TYPE_BASE_DN, baseDN));
564    }
565
566    final Filter filter = properties.getFilter();
567    if (filter != null)
568    {
569      elements.add(new ASN1Element(TYPE_FILTER, filter.encode().encode()));
570    }
571
572    if (properties.preventConflictsWithSoftDeletedEntries())
573    {
574      elements.add(new ASN1Boolean(
575           TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES, true));
576    }
577
578    final UniquenessValidationLevel preCommitValidationLevel =
579         properties.getPreCommitValidationLevel();
580    if (preCommitValidationLevel != UniquenessValidationLevel.ALL_SUBTREE_VIEWS)
581    {
582      elements.add(new ASN1Enumerated(TYPE_PRE_COMMIT_VALIDATION_LEVEL,
583           preCommitValidationLevel.intValue()));
584    }
585
586    final UniquenessValidationLevel postCommitValidationLevel =
587         properties.getPostCommitValidationLevel();
588    if (postCommitValidationLevel !=
589         UniquenessValidationLevel.ALL_SUBTREE_VIEWS)
590    {
591      elements.add(new ASN1Enumerated(TYPE_POST_COMMIT_VALIDATION_LEVEL,
592           postCommitValidationLevel.intValue()));
593    }
594
595    if (! properties.alertOnPostCommitConflictDetection())
596    {
597      elements.add(new ASN1Boolean(
598           TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION, false));
599    }
600
601    if (properties.createConflictPreventionDetailsEntry())
602    {
603      elements.add(new ASN1Boolean(
604           TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY, true));
605    }
606
607    return new ASN1OctetString(new ASN1Sequence(elements).encode());
608  }
609
610
611
612  /**
613   * Creates a new uniqueness request control that is decoded from the provided
614   * generic control.
615   *
616   * @param  control  The control to be decoded as a uniqueness request control.
617   *                  It must not be {@code null}.
618   *
619   * @throws  LDAPException  If the provided control cannot be decoded as a
620   *                         valid uniqueness request control.
621   */
622  public UniquenessRequestControl(@NotNull final Control control)
623         throws LDAPException
624  {
625    super(control);
626
627    final ASN1OctetString value = control.getValue();
628    if (value == null)
629    {
630      throw new LDAPException(ResultCode.DECODING_ERROR,
631           ERR_UNIQUENESS_REQ_DECODE_NO_VALUE.get());
632    }
633
634    try
635    {
636      boolean decodedAlertOnPostCommitConflictDetection = true;
637      boolean decodedCreateConflictPreventionDetailsEntry = false;
638      boolean decodedPreventSoftDeletedConflicts = false;
639      Filter decodedFilter = null;
640      Set<String> decodedAttributeTypes = Collections.emptySet();
641      String decodedBaseDN = null;
642      String decodedUniquenessID = null;
643      UniquenessMultipleAttributeBehavior decodedMultipleAttributeBehavior =
644           UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE;
645      UniquenessValidationLevel decodedPreCommitLevel =
646           UniquenessValidationLevel.ALL_SUBTREE_VIEWS;
647      UniquenessValidationLevel decodedPostCommitLevel =
648           UniquenessValidationLevel.ALL_SUBTREE_VIEWS;
649
650      final ASN1Element[] elements =
651           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
652      for (final ASN1Element e : elements)
653      {
654        switch (e.getType())
655        {
656          case TYPE_UNIQUENESS_ID:
657            decodedUniquenessID =
658                 ASN1OctetString.decodeAsOctetString(e).stringValue();
659            break;
660          case TYPE_ATTRIBUTE_TYPES:
661            final ASN1Element[] atElements = ASN1Set.decodeAsSet(e).elements();
662            final LinkedHashSet<String> atNames = new LinkedHashSet<>(
663                 StaticUtils.computeMapCapacity(atElements.length));
664            for (final ASN1Element atElement : atElements)
665            {
666              atNames.add(ASN1OctetString.decodeAsOctetString(
667                   atElement).stringValue());
668            }
669            decodedAttributeTypes = Collections.unmodifiableSet(atNames);
670            break;
671          case TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR:
672            final int mabIntValue =
673                 ASN1Enumerated.decodeAsEnumerated(e).intValue();
674            decodedMultipleAttributeBehavior =
675                 UniquenessMultipleAttributeBehavior.valueOf(mabIntValue);
676            if (decodedMultipleAttributeBehavior == null)
677            {
678              throw new LDAPException(ResultCode.DECODING_ERROR,
679                   ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_MULTIPLE_ATTR_BEHAVIOR.get(
680                        mabIntValue));
681            }
682            break;
683          case TYPE_BASE_DN:
684            decodedBaseDN =
685                 ASN1OctetString.decodeAsOctetString(e).stringValue();
686            break;
687          case TYPE_FILTER:
688            decodedFilter = Filter.decode(ASN1Element.decode(e.getValue()));
689            break;
690          case TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES:
691            decodedPreventSoftDeletedConflicts =
692                 ASN1Boolean.decodeAsBoolean(e).booleanValue();
693            break;
694          case TYPE_PRE_COMMIT_VALIDATION_LEVEL:
695            final int preCommitIntValue =
696                 ASN1Enumerated.decodeAsEnumerated(e).intValue();
697            decodedPreCommitLevel =
698                 UniquenessValidationLevel.valueOf(preCommitIntValue);
699            if (decodedPreCommitLevel == null)
700            {
701              throw new LDAPException(ResultCode.DECODING_ERROR,
702                   ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_PRE_COMMIT_LEVEL.get(
703                        preCommitIntValue));
704            }
705            break;
706          case TYPE_POST_COMMIT_VALIDATION_LEVEL:
707            final int postCommitIntValue =
708                 ASN1Enumerated.decodeAsEnumerated(e).intValue();
709            decodedPostCommitLevel =
710                 UniquenessValidationLevel.valueOf(postCommitIntValue);
711            if (decodedPostCommitLevel == null)
712            {
713              throw new LDAPException(ResultCode.DECODING_ERROR,
714                   ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_POST_COMMIT_LEVEL.get(
715                        postCommitIntValue));
716            }
717            break;
718          case TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION:
719            decodedAlertOnPostCommitConflictDetection =
720                 ASN1Boolean.decodeAsBoolean(e).booleanValue();
721            break;
722          case TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY:
723            decodedCreateConflictPreventionDetailsEntry =
724                 ASN1Boolean.decodeAsBoolean(e).booleanValue();
725            break;
726          default:
727            throw new LDAPException(ResultCode.DECODING_ERROR,
728                 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_ELEMENT_TYPE.get(
729                      StaticUtils.toHex(e.getType())));
730        }
731      }
732
733      if (decodedUniquenessID == null)
734      {
735        throw new LDAPException(ResultCode.DECODING_ERROR,
736             ERR_UNIQUENESS_REQ_MISSING_UNIQUENESS_ID.get());
737      }
738
739      if (decodedAttributeTypes.isEmpty() && (decodedFilter == null))
740      {
741        throw new LDAPException(ResultCode.DECODING_ERROR,
742             ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get());
743      }
744
745      uniquenessID = decodedUniquenessID;
746      attributeTypes = decodedAttributeTypes;
747      multipleAttributeBehavior = decodedMultipleAttributeBehavior;
748      baseDN = decodedBaseDN;
749      filter = decodedFilter;
750      preventConflictsWithSoftDeletedEntries =
751           decodedPreventSoftDeletedConflicts;
752      preCommitValidationLevel = decodedPreCommitLevel;
753      postCommitValidationLevel = decodedPostCommitLevel;
754      alertOnPostCommitConflictDetection =
755           decodedAlertOnPostCommitConflictDetection;
756      createConflictPreventionDetailsEntry =
757           decodedCreateConflictPreventionDetailsEntry;
758    }
759    catch (final LDAPException le)
760    {
761      Debug.debugException(le);
762      throw le;
763    }
764    catch (final Exception e)
765    {
766      Debug.debugException(e);
767      throw new LDAPException(ResultCode.DECODING_ERROR,
768           ERR_UNIQUENESS_REQ_DECODE_ERROR_DECODING_VALUE.get(
769                StaticUtils.getExceptionMessage(e)),
770           e);
771    }
772  }
773
774
775
776  /**
777   * Retrieves the uniqueness identifier for this control, which may be used to
778   * identify the response control that corresponds to this request control.
779   * This is primarily useful for requests that contain multiple uniqueness
780   * controls, as there may be a separate response control for each.
781   *
782   * @return  The uniqueness identifier for this control.
783   */
784  @NotNull()
785  public String getUniquenessID()
786  {
787    return uniquenessID;
788  }
789
790
791
792  /**
793   * Retrieves the set of attribute types that the server will check for
794   * uniqueness conflicts.
795   *
796   * @return  The set of attribute types that the server will check for
797   *          uniqueness conflicts, or an empty set if only a filter should be
798   *          used to identify conflicts.
799   */
800  @NotNull()
801  public Set<String> getAttributeTypes()
802  {
803    return attributeTypes;
804  }
805
806
807
808  /**
809   * Retrieves the behavior that the server should exhibit if multiple attribute
810   * types are configured.
811   *
812   * @return  The behavior that the server should exhibit if multiple attribute
813   *          types are configured.
814   */
815  @NotNull()
816  public UniquenessMultipleAttributeBehavior getMultipleAttributeBehavior()
817  {
818    return multipleAttributeBehavior;
819  }
820
821
822
823  /**
824   * Retrieves the base DN that will be used for searches used to identify
825   * uniqueness conflicts, if defined.
826   *
827   * @return  The base DN that will be used for searches used to identify
828   *          uniqueness conflicts, or {@code null} if the server should search
829   *          below all public naming contexts.
830   */
831  @Nullable()
832  public String getBaseDN()
833  {
834    return baseDN;
835  }
836
837
838
839  /**
840   * Retrieves a filter that will be used to identify uniqueness conflicts, if
841   * defined.
842   *
843   * @return  A filter that will be used to identify uniqueness conflicts, or
844   *          {@code null} if no filter has been defined.
845   */
846  @Nullable()
847  public Filter getFilter()
848  {
849    return filter;
850  }
851
852
853
854  /**
855   * Indicates whether the server should attempt to identify conflicts with
856   * soft-deleted entries.
857   *
858   * @return  {@code true} if the server should identify conflicts with both
859   *          regular entries and soft-deleted entries, or {@code false} if the
860   *          server should only identify conflicts with regular entries.
861   */
862  public boolean preventConflictsWithSoftDeletedEntries()
863  {
864    return preventConflictsWithSoftDeletedEntries;
865  }
866
867
868
869  /**
870   * Retrieves the pre-commit validation level, which will be used to identify
871   * any conflicts before the associated request is processed.
872   *
873   * @return  The pre-commit validation level.
874   */
875  @NotNull()
876  public UniquenessValidationLevel getPreCommitValidationLevel()
877  {
878    return preCommitValidationLevel;
879  }
880
881
882
883  /**
884   * Retrieves the post-commit validation level, which will be used to identify
885   * any conflicts that were introduced by the request with which the control is
886   * associated, or by some other concurrent changed processed in the server.
887   *
888   * @return  The post-commit validation level.
889   */
890  @NotNull()
891  public UniquenessValidationLevel getPostCommitValidationLevel()
892  {
893    return postCommitValidationLevel;
894  }
895
896
897
898  /**
899   * Indicates whether the server should raise an administrative alert if a
900   * conflict is detected during post-commit validation processing.
901   *
902   * @return  {@code true} if the server should raise an administrative alert if
903   *          a conflict is detected during post-commit validation processing,
904   *          or {@code false} if not.
905   */
906  public boolean alertOnPostCommitConflictDetection()
907  {
908    return alertOnPostCommitConflictDetection;
909  }
910
911
912
913  /**
914   * Indicates whether the server should create a temporary conflict prevention
915   * details entry before beginning pre-commit validation to provide better
916   * support for preventing conflicts.  If created, the entry will be removed
917   * after post-commit validation processing has completed.
918   *
919   * @return  {@code true} if the server should create a temporary conflict
920   *          prevention details entry before beginning pre-commit validation,
921   *          or {@code false} if not.
922   */
923  public boolean createConflictPreventionDetailsEntry()
924  {
925    return createConflictPreventionDetailsEntry;
926  }
927
928
929
930  /**
931   * {@inheritDoc}
932   */
933  @Override()
934  @NotNull()
935  public String getControlName()
936  {
937    return INFO_UNIQUENESS_REQ_CONTROL_NAME.get();
938  }
939
940
941
942  /**
943   * {@inheritDoc}
944   */
945  @Override()
946  public void toString(@NotNull final StringBuilder buffer)
947  {
948    buffer.append("UniquenessRequestControl(isCritical=");
949    buffer.append(isCritical());
950    buffer.append(", uniquenessID='");
951    buffer.append(uniquenessID);
952    buffer.append("', attributeTypes={");
953
954    final Iterator<String> attributeTypesIterator = attributeTypes.iterator();
955    while (attributeTypesIterator.hasNext())
956    {
957      buffer.append('\'');
958      buffer.append(attributeTypesIterator.next());
959      buffer.append('\'');
960
961      if (attributeTypesIterator.hasNext())
962      {
963        buffer.append(", ");
964      }
965    }
966
967    buffer.append("}, multipleAttributeBehavior=");
968    buffer.append(multipleAttributeBehavior);
969
970    if (baseDN != null)
971    {
972      buffer.append(", baseDN='");
973      buffer.append(baseDN);
974      buffer.append('\'');
975    }
976
977    if (filter != null)
978    {
979      buffer.append(", filter='");
980      buffer.append(filter);
981      buffer.append('\'');
982    }
983
984    buffer.append(", preventConflictsWithSoftDeletedEntries=");
985    buffer.append(preventConflictsWithSoftDeletedEntries);
986    buffer.append(", preCommitValidationLevel=");
987    buffer.append(preCommitValidationLevel);
988    buffer.append(", postCommitValidationLevel=");
989    buffer.append(postCommitValidationLevel);
990    buffer.append(", alertOnPostCommitConflictDetection=");
991    buffer.append(alertOnPostCommitConflictDetection);
992    buffer.append(", createConflictPreventionDetailsEntry=");
993    buffer.append(createConflictPreventionDetailsEntry);
994    buffer.append(')');
995  }
996}