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