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.LinkedHashMap;
043import java.util.Map;
044
045import com.unboundid.asn1.ASN1Boolean;
046import com.unboundid.asn1.ASN1Element;
047import com.unboundid.asn1.ASN1OctetString;
048import com.unboundid.asn1.ASN1Sequence;
049import com.unboundid.ldap.sdk.Control;
050import com.unboundid.ldap.sdk.DecodeableControl;
051import com.unboundid.ldap.sdk.LDAPException;
052import com.unboundid.ldap.sdk.LDAPResult;
053import com.unboundid.ldap.sdk.ResultCode;
054import com.unboundid.util.Debug;
055import com.unboundid.util.NotMutable;
056import com.unboundid.util.NotNull;
057import com.unboundid.util.Nullable;
058import com.unboundid.util.StaticUtils;
059import com.unboundid.util.ThreadSafety;
060import com.unboundid.util.ThreadSafetyLevel;
061import com.unboundid.util.Validator;
062
063import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
064
065
066
067/**
068 * This class provides a response control that may be included in the response
069 * to add, modify, and modify DN requests that included the
070 * {@link UniquenessRequestControl}.  It provides information about the
071 * uniqueness processing that was performed.
072 * <BR>
073 * <BLOCKQUOTE>
074 *   <B>NOTE:</B>  This class, and other classes within the
075 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
076 *   supported for use against Ping Identity, UnboundID, and
077 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
078 *   for proprietary functionality or for external specifications that are not
079 *   considered stable or mature enough to be guaranteed to work in an
080 *   interoperable way with other types of LDAP servers.
081 * </BLOCKQUOTE>
082 * <BR>
083 * The control has an OID of 1.3.6.1.4.1.30221.2.5.53 and a criticality of
084 * false.  It must have a value with the following encoding:
085 * <PRE>
086 *   UniquenessResponseValue ::= SEQUENCE {
087 *     uniquenessID                [0] OCTET STRING,
088 *     preCommitValidationPassed   [1] BOOLEAN OPTIONAL,
089 *     postCommitValidationPassed  [2] BOOLEAN OPTIONAL,
090 *     validationMessage           [3] OCTET STRING OPTIONAL,
091 *     ... }
092 * </PRE>
093 */
094@NotMutable()
095@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
096public final class UniquenessResponseControl
097       extends Control
098       implements DecodeableControl
099{
100  /**
101   * The OID (1.3.6.1.4.1.30221.2.5.53) for the uniqueness response control.
102   */
103  @NotNull public static final String UNIQUENESS_RESPONSE_OID =
104       "1.3.6.1.4.1.30221.2.5.53";
105
106
107
108  /**
109   * The BER type for the uniqueness ID element in the value sequence.
110   */
111  private static final byte TYPE_UNIQUENESS_ID = (byte) 0x80;
112
113
114
115  /**
116   * The BER type for the pre-commit validation passed element in the value
117   * sequence.
118   */
119  private static final byte TYPE_PRE_COMMIT_VALIDATION_PASSED = (byte) 0x81;
120
121
122
123  /**
124   * The BER type for the post-commit validation passed element in the value
125   * sequence.
126   */
127  private static final byte TYPE_POST_COMMIT_VALIDATION_PASSED = (byte) 0x82;
128
129
130
131  /**
132   * The BER type for the validation message element in the value sequence.
133   */
134  private static final byte TYPE_VALIDATION_MESSAGE = (byte) 0x83;
135
136
137
138  /**
139   * The serial version UID for this serializable class.
140   */
141  private static final long serialVersionUID = 5090348902351420617L;
142
143
144
145  // Indicates whether post-commit validation passed.
146  @Nullable private final Boolean postCommitValidationPassed;
147
148  // Indicates whether pre-commit validation passed.
149  @Nullable private final Boolean preCommitValidationPassed;
150
151  // A value that will be used to correlate this response control with its
152  // corresponding request control.
153  @NotNull private final String uniquenessID;
154
155  // The validation message, if any.
156  @Nullable private final String validationMessage;
157
158
159
160  /**
161   * Creates a new empty control instance that is intended to be used only for
162   * decoding controls via the {@code DecodeableControl} interface.
163   */
164  UniquenessResponseControl()
165  {
166    uniquenessID = null;
167    preCommitValidationPassed = null;
168    postCommitValidationPassed = null;
169    validationMessage = null;
170  }
171
172
173
174  /**
175   * Creates a new uniqueness response control with the provided information.
176   *
177   * @param  uniquenessID                The uniqueness ID that may be used to
178   *                                     correlate this uniqueness response
179   *                                     control with the corresponding request
180   *                                     control.  This must not be
181   *                                     {@code null}.
182   * @param  preCommitValidationPassed   Indicates whether the pre-commit
183   *                                     validation was successful.  This may be
184   *                                     {@code null} if no pre-commit
185   *                                     validation was attempted.
186   * @param  postCommitValidationPassed  Indicates whether the post-commit
187   *                                     validation was successful.  This may be
188   *                                     {@code null} if no post-commit
189   *                                     validation was attempted.
190   * @param  validationMessage           A message with additional information
191   *                                     about the validation processing.  This
192   *                                     may be {@code null} if no validation
193   *                                     message is needed.
194   */
195  public UniquenessResponseControl(@NotNull final String uniquenessID,
196              @Nullable final Boolean preCommitValidationPassed,
197              @Nullable final Boolean postCommitValidationPassed,
198              @Nullable final String validationMessage)
199  {
200    super(UNIQUENESS_RESPONSE_OID, false,
201         encodeValue(uniquenessID, preCommitValidationPassed,
202              postCommitValidationPassed, validationMessage));
203
204    Validator.ensureNotNull(uniquenessID);
205
206    this.uniquenessID = uniquenessID;
207    this.preCommitValidationPassed = preCommitValidationPassed;
208    this.postCommitValidationPassed = postCommitValidationPassed;
209    this.validationMessage = validationMessage;
210  }
211
212
213
214  /**
215   * Encodes the provided information into an ASN.1 octet string suitable for
216   * use as the value of this control.
217   *
218   * @param  uniquenessID                The uniqueness ID that may be used to
219   *                                     correlate this uniqueness response
220   *                                     control with the corresponding request
221   *                                     control.  This must not be
222   *                                     {@code null}.
223   * @param  preCommitValidationPassed   Indicates whether the pre-commit
224   *                                     validation was successful.  This may be
225   *                                     {@code null} if no pre-commit
226   *                                     validation was attempted.
227   * @param  postCommitValidationPassed  Indicates whether the post-commit
228   *                                     validation was successful.  This may be
229   *                                     {@code null} if no post-commit
230   *                                     validation was attempted.
231   * @param  validationMessage           A message with additional information
232   *                                     about the validation processing.  This
233   *                                     may be {@code null} if no validation
234   *                                     message is needed.
235   *
236   * @return  The encoded control value.
237   */
238  @NotNull()
239  private static ASN1OctetString encodeValue(
240               @NotNull final String uniquenessID,
241               @Nullable final Boolean preCommitValidationPassed,
242               @Nullable final Boolean postCommitValidationPassed,
243               @Nullable final String validationMessage)
244  {
245    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
246    elements.add(new ASN1OctetString(TYPE_UNIQUENESS_ID, uniquenessID));
247
248    if (preCommitValidationPassed != null)
249    {
250      elements.add(new ASN1Boolean(TYPE_PRE_COMMIT_VALIDATION_PASSED,
251           preCommitValidationPassed));
252    }
253
254    if (postCommitValidationPassed != null)
255    {
256      elements.add(new ASN1Boolean(TYPE_POST_COMMIT_VALIDATION_PASSED,
257           postCommitValidationPassed));
258    }
259
260    if (validationMessage != null)
261    {
262      elements.add(new ASN1OctetString(TYPE_VALIDATION_MESSAGE,
263           validationMessage));
264    }
265
266    return new ASN1OctetString(new ASN1Sequence(elements).encode());
267  }
268
269
270
271  /**
272   * Creates a new uniqueness response control with the provided information.
273   *
274   * @param  oid         The OID for the control.
275   * @param  isCritical  Indicates whether the control should be marked
276   *                     critical.
277   * @param  value       The encoded value for the control.  This may be
278   *                     {@code null} if no value was provided.
279   *
280   * @throws  LDAPException  If the provided control cannot be decoded as a
281   *                         uniqueness response control.
282   */
283  public UniquenessResponseControl(@NotNull final String oid,
284                                   final boolean isCritical,
285                                   @Nullable final ASN1OctetString value)
286         throws LDAPException
287  {
288    super(oid, isCritical, value);
289
290    if (value == null)
291    {
292      throw new LDAPException(ResultCode.DECODING_ERROR,
293           ERR_UNIQUENESS_RES_DECODE_NO_VALUE.get());
294    }
295
296    try
297    {
298      String id = null;
299      Boolean prePassed = null;
300      Boolean postPassed = null;
301      String message = null;
302      for (final ASN1Element e :
303           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
304      {
305        switch (e.getType())
306        {
307          case TYPE_UNIQUENESS_ID:
308            id = ASN1OctetString.decodeAsOctetString(e).stringValue();
309            break;
310          case TYPE_PRE_COMMIT_VALIDATION_PASSED:
311            prePassed = ASN1Boolean.decodeAsBoolean(e).booleanValue();
312            break;
313          case TYPE_POST_COMMIT_VALIDATION_PASSED:
314            postPassed = ASN1Boolean.decodeAsBoolean(e).booleanValue();
315            break;
316          case TYPE_VALIDATION_MESSAGE:
317            message = ASN1OctetString.decodeAsOctetString(e).stringValue();
318            break;
319          default:
320            throw new LDAPException(ResultCode.DECODING_ERROR,
321                 ERR_UNIQUENESS_RES_DECODE_UNKNOWN_ELEMENT_TYPE.get(
322                      StaticUtils.toHex(e.getType())));
323        }
324      }
325
326      if (id == null)
327      {
328        throw new LDAPException(ResultCode.DECODING_ERROR,
329             ERR_UNIQUENESS_RES_DECODE_NO_UNIQUENESS_ID.get());
330      }
331
332      uniquenessID = id;
333      preCommitValidationPassed = prePassed;
334      postCommitValidationPassed = postPassed;
335      validationMessage = message;
336    }
337    catch (final LDAPException le)
338    {
339      Debug.debugException(le);
340      throw le;
341    }
342    catch (final Exception e)
343    {
344      Debug.debugException(e);
345      throw new LDAPException(ResultCode.DECODING_ERROR,
346           ERR_UNIQUENESS_RES_DECODE_ERROR.get(
347                StaticUtils.getExceptionMessage(e)),
348           e);
349    }
350  }
351
352
353
354  /**
355   * {@inheritDoc}
356   */
357  @Override()
358  @NotNull()
359  public UniquenessResponseControl decodeControl(@NotNull final String oid,
360              final boolean isCritical,
361              @Nullable final ASN1OctetString value)
362         throws LDAPException
363  {
364    return new UniquenessResponseControl(oid, isCritical, value);
365  }
366
367
368
369  /**
370   * Retrieves the set of uniqueness response controls included in the provided
371   * result.
372   *
373   * @param  result  The result to process.
374   *
375   * @return  The set of uniqueness response controls included in the provided
376   *          result, indexed by uniqueness ID.  It may be empty if the result
377   *          does not include any uniqueness response controls.
378   *
379   * @throws  LDAPException  If a problem is encountered while getting the set
380   *                         of uniqueness response controls contained in the
381   *                         provided result.
382   */
383  @NotNull()
384  public static Map<String,UniquenessResponseControl> get(
385                     @NotNull final LDAPResult result)
386         throws LDAPException
387  {
388    final Control[] responseControls = result.getResponseControls();
389    if (responseControls.length == 0)
390    {
391      return Collections.emptyMap();
392    }
393
394    final LinkedHashMap<String,UniquenessResponseControl> controlMap =
395         new LinkedHashMap<>(StaticUtils.computeMapCapacity(
396              responseControls.length));
397    for (final Control c : responseControls)
398    {
399      if (! c.getOID().equals(UNIQUENESS_RESPONSE_OID))
400      {
401        continue;
402      }
403
404      final UniquenessResponseControl urc;
405      if (c instanceof UniquenessResponseControl)
406      {
407        urc = (UniquenessResponseControl) c;
408      }
409      else
410      {
411        urc = new UniquenessResponseControl().decodeControl(c.getOID(),
412             c.isCritical(), c.getValue());
413      }
414
415      final String uniquenessID = urc.getUniquenessID();
416      if (controlMap.containsKey(uniquenessID))
417      {
418        throw new LDAPException(ResultCode.DECODING_ERROR,
419             ERR_UNIQUENESS_RES_GET_ID_CONFLICT.get(uniquenessID));
420      }
421      else
422      {
423        controlMap.put(uniquenessID, urc);
424      }
425    }
426
427    return Collections.unmodifiableMap(controlMap);
428  }
429
430
431
432  /**
433   * Indicates whether a uniqueness conflict was found during processing.
434   *
435   * @return  {@code true} if a uniqueness conflict was found during processing,
436   *          or {@code false} if no conflict was found or if no validation was
437   *          attempted.
438   */
439  public boolean uniquenessConflictFound()
440  {
441    return ((preCommitValidationPassed == Boolean.FALSE) ||
442         (postCommitValidationPassed == Boolean.FALSE));
443  }
444
445
446
447  /**
448   * Retrieves the identifier that may be used to correlate this uniqueness
449   * response control with the corresponding request control.  This is primarily
450   * useful for requests that contain multiple uniqueness controls, as there may
451   * be a separate response control for each.
452   *
453   * @return  The identifier that may be used to correlate this uniqueness
454   *          response control with the corresponding request control.
455   */
456  @NotNull()
457  public String getUniquenessID()
458  {
459    return uniquenessID;
460  }
461
462
463
464  /**
465   * Retrieves the result of the server's pre-commit validation processing.
466   * The same information can be inferred from the
467   * {@link #getPreCommitValidationPassed()} method, but this method may provide
468   * a more intuitive result and does not have the possibility of a {@code null}
469   * return value.
470   *
471   * @return  {@link UniquenessValidationResult#VALIDATION_PASSED} if the
472   *          server did not find any conflicting entries during the pre-commit
473   *          check, {@link UniquenessValidationResult#VALIDATION_FAILED} if
474   *          the server found at least one conflicting entry during the
475   *          pre-commit check, or
476   *          {@link UniquenessValidationResult#VALIDATION_NOT_ATTEMPTED} if
477   *          the server did not attempt any pre-commit validation.
478   */
479  @NotNull()
480  public UniquenessValidationResult getPreCommitValidationResult()
481  {
482    if (preCommitValidationPassed == null)
483    {
484      return UniquenessValidationResult.VALIDATION_NOT_ATTEMPTED;
485    }
486    else if (preCommitValidationPassed)
487    {
488      return UniquenessValidationResult.VALIDATION_PASSED;
489    }
490    else
491    {
492      return UniquenessValidationResult.VALIDATION_FAILED;
493    }
494  }
495
496
497
498  /**
499   * Retrieves a value that indicates whether pre-commit validation was
500   * attempted, and whether that validation passed.  Note that this method is
501   * still supported and is not deprecated at this time, but the
502   * {@link #getPreCommitValidationResult()} is now the recommended way to get
503   * this information.
504   *
505   * @return  {@code Boolean.TRUE} if pre-commit validation was attempted and
506   *          passed, {@code Boolean.FALSE} if pre-commit validation was
507   *          attempted and did not pass, or {@code null} if pre-commit
508   *          validation was not attempted.
509   */
510  @Nullable()
511  public Boolean getPreCommitValidationPassed()
512  {
513    return preCommitValidationPassed;
514  }
515
516
517
518  /**
519   * Retrieves the result of the server's post-commit validation processing.
520   * The same information can be inferred from the
521   * {@link #getPostCommitValidationPassed()} method, but this method may
522   * provide a more intuitive result and does not have the possibility of a
523   * {@code null} return value.
524   *
525   * @return  {@link UniquenessValidationResult#VALIDATION_PASSED} if the
526   *          server did not find any conflicting entries during the post-commit
527   *          check, {@link UniquenessValidationResult#VALIDATION_FAILED} if
528   *          the server found at least one conflicting entry during the
529   *          post-commit check, or
530   *          {@link UniquenessValidationResult#VALIDATION_NOT_ATTEMPTED} if
531   *          the server did not attempt any post-commit validation.
532   */
533  @NotNull()
534  public UniquenessValidationResult getPostCommitValidationResult()
535  {
536    if (postCommitValidationPassed == null)
537    {
538      return UniquenessValidationResult.VALIDATION_NOT_ATTEMPTED;
539    }
540    else if (postCommitValidationPassed)
541    {
542      return UniquenessValidationResult.VALIDATION_PASSED;
543    }
544    else
545    {
546      return UniquenessValidationResult.VALIDATION_FAILED;
547    }
548  }
549
550
551
552  /**
553   * Retrieves a value that indicates whether post-commit validation was
554   * attempted, and whether that validation passed.
555   *
556   * @return  {@code Boolean.TRUE} if post-commit validation was attempted and
557   *          passed, {@code Boolean.FALSE} if post-commit validation was
558   *          attempted and did not pass, or {@code null} if post-commit
559   *          validation was not attempted.
560   */
561  @Nullable()
562  public Boolean getPostCommitValidationPassed()
563  {
564    return postCommitValidationPassed;
565  }
566
567
568
569  /**
570   * Retrieves a message with additional information about the validation
571   * processing that was performed.
572   *
573   * @return  A message with additional information about the validation
574   *          processing that was performed, or {@code null} if no validation
575   *          message is available.
576   */
577  @Nullable()
578  public String getValidationMessage()
579  {
580    return validationMessage;
581  }
582
583
584
585  /**
586   * {@inheritDoc}
587   */
588  @Override()
589  @NotNull()
590  public String getControlName()
591  {
592    return INFO_UNIQUENESS_RES_CONTROL_NAME.get();
593  }
594
595
596
597  /**
598   * {@inheritDoc}
599   */
600  @Override()
601  public void toString(@NotNull final StringBuilder buffer)
602  {
603    buffer.append("UniquenessResponseControl(uniquenessID='");
604    buffer.append(uniquenessID);
605    buffer.append("', preCommitValidationResult='");
606    buffer.append(getPreCommitValidationResult().getName());
607    buffer.append("', preCommitValidationResult='");
608    buffer.append(getPostCommitValidationResult().getName());
609    buffer.append('\'');
610
611    if (validationMessage != null)
612    {
613      buffer.append(", validationMessage='");
614      buffer.append(validationMessage);
615      buffer.append('\'');
616    }
617    buffer.append(')');
618  }
619}