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