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