001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2024 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) 2007-2024 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;
037
038
039
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.List;
043
044import com.unboundid.asn1.ASN1Exception;
045import com.unboundid.asn1.ASN1StreamReader;
046import com.unboundid.asn1.ASN1StreamReaderSequence;
047import com.unboundid.ldap.protocol.LDAPMessage;
048import com.unboundid.ldap.protocol.LDAPResponse;
049import com.unboundid.util.Debug;
050import com.unboundid.util.Extensible;
051import com.unboundid.util.NotMutable;
052import com.unboundid.util.NotNull;
053import com.unboundid.util.Nullable;
054import com.unboundid.util.StaticUtils;
055import com.unboundid.util.ThreadSafety;
056import com.unboundid.util.ThreadSafetyLevel;
057
058import static com.unboundid.ldap.sdk.LDAPMessages.*;
059
060
061
062/**
063 * This class provides a data structure for holding the elements that are common
064 * to most types of LDAP responses.  The elements contained in an LDAP result
065 * include:
066 * <UL>
067 *   <LI>Result Code -- An integer value that provides information about the
068 *       status of the operation.  See the {@link ResultCode} class for
069 *       information about a number of result codes defined in LDAP.</LI>
070 *   <LI>Diagnostic Message -- An optional string that may provide additional
071 *       information about the operation.  For example, if the operation failed,
072 *       it may include information about the reason for the failure.  It will
073 *       often (but not always) be absent in the result for successful
074 *       operations, and it may be absent in the result for failed
075 *       operations.</LI>
076 *   <LI>Matched DN -- An optional DN which specifies the entry that most
077 *       closely matched the DN of a non-existent entry in the server.  For
078 *       example, if an operation failed because the target entry did not exist,
079 *       then the matched DN field may specify the DN of the closest ancestor
080 *       to that entry that does exist in the server.</LI>
081 *   <LI>Referral URLs -- An optional set of LDAP URLs which refer to other
082 *       directories and/or locations within the DIT in which the operation may
083 *       be attempted.  If multiple referral URLs are provided, then they should
084 *       all be considered equivalent for the purpose of attempting the
085 *       operation (e.g., the different URLs may simply refer to different
086 *       servers in which the operation could be processed).</LI>
087 *   <LI>Response Controls -- An optional set of controls included in the
088 *       response from the server.  If any controls are included, then they may
089 *       provide additional information about the processing that was performed
090 *       by the server.</LI>
091 * </UL>
092 * <BR><BR>
093 * Note that even though this class is marked with the @Extensible annotation
094 * type, it should not be directly subclassed by third-party code.  Only the
095 * {@link BindResult} and {@link ExtendedResult} subclasses are actually
096 * intended to be extended by third-party code.
097 */
098@Extensible()
099@NotMutable()
100@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
101public class LDAPResult
102       implements Serializable, LDAPResponse
103{
104  /**
105   * The BER type for the set of referral URLs.
106   */
107  static final byte TYPE_REFERRAL_URLS = (byte) 0xA3;
108
109
110
111  /**
112   * The serial version UID for this serializable class.
113   */
114  private static final long serialVersionUID = 2215819095653175991L;
115
116
117
118  // The protocol op type for this result, if available.
119  @Nullable private final Byte protocolOpType;
120
121  // The set of controls from the response.
122  @NotNull private final Control[] responseControls;
123
124  // The message ID for the LDAP message that is associated with this LDAP
125  // result.
126  private final int messageID;
127
128  // The result code from the response.
129  @NotNull private final ResultCode resultCode;
130
131  // The diagnostic message from the response, if available.
132  @Nullable private final String diagnosticMessage;
133
134  // The matched DN from the response, if available.
135  @Nullable private final String matchedDN;
136
137  // The set of referral URLs from the response, if available.
138  @NotNull private final String[] referralURLs;
139
140
141
142  /**
143   * Creates a new LDAP result object based on the provided result.
144   *
145   * @param  result  The LDAP result object to use to initialize this result.
146   */
147  protected LDAPResult(@NotNull final LDAPResult result)
148  {
149    protocolOpType    = result.protocolOpType;
150    messageID         = result.messageID;
151    resultCode        = result.resultCode;
152    diagnosticMessage = result.diagnosticMessage;
153    matchedDN         = result.matchedDN;
154    referralURLs      = result.referralURLs;
155    responseControls  = result.responseControls;
156  }
157
158
159
160  /**
161   * Creates a new LDAP result object with the provided message ID and result
162   * code, and no other information.
163   *
164   * @param  messageID   The message ID for the LDAP message that is associated
165   *                     with this LDAP result.
166   * @param  resultCode  The result code from the response.
167   */
168  public LDAPResult(final int messageID, @NotNull final ResultCode resultCode)
169  {
170    this(null, messageID, resultCode, null, null, StaticUtils.NO_STRINGS,
171         NO_CONTROLS);
172  }
173
174
175
176  /**
177   * Creates a new LDAP result object with the provided information.
178   *
179   * @param  messageID          The message ID for the LDAP message that is
180   *                            associated with this LDAP result.
181   * @param  resultCode         The result code from the response.
182   * @param  diagnosticMessage  The diagnostic message from the response, if
183   *                            available.
184   * @param  matchedDN          The matched DN from the response, if available.
185   * @param  referralURLs       The set of referral URLs from the response, if
186   *                            available.
187   * @param  responseControls   The set of controls from the response, if
188   *                            available.
189   */
190  public LDAPResult(final int messageID, @NotNull final ResultCode resultCode,
191                    @Nullable final String diagnosticMessage,
192                    @Nullable final String matchedDN,
193                    @Nullable final String[] referralURLs,
194                    @Nullable final Control[] responseControls)
195  {
196    this(null, messageID, resultCode, diagnosticMessage, matchedDN,
197         referralURLs, responseControls);
198  }
199
200
201
202  /**
203   * Creates a new LDAP result object with the provided information.
204   *
205   * @param  messageID          The message ID for the LDAP message that is
206   *                            associated with this LDAP result.
207   * @param  resultCode         The result code from the response.
208   * @param  diagnosticMessage  The diagnostic message from the response, if
209   *                            available.
210   * @param  matchedDN          The matched DN from the response, if available.
211   * @param  referralURLs       The set of referral URLs from the response, if
212   *                            available.
213   * @param  responseControls   The set of controls from the response, if
214   *                            available.
215   */
216  public LDAPResult(final int messageID, @NotNull final ResultCode resultCode,
217                    @Nullable final String diagnosticMessage,
218                    @Nullable final String matchedDN,
219                    @Nullable final List<String> referralURLs,
220                    @Nullable final List<Control> responseControls)
221  {
222    this(null, messageID, resultCode, diagnosticMessage, matchedDN,
223         referralURLs, responseControls);
224  }
225
226
227
228  /**
229   * Creates a new LDAP result object with the provided information.
230   *
231   * @param  protocolOpType     The protocol op type for this result, if
232   *                            available.
233   * @param  messageID          The message ID for the LDAP message that is
234   *                            associated with this LDAP result.
235   * @param  resultCode         The result code from the response.
236   * @param  diagnosticMessage  The diagnostic message from the response, if
237   *                            available.
238   * @param  matchedDN          The matched DN from the response, if available.
239   * @param  referralURLs       The set of referral URLs from the response, if
240   *                            available.
241   * @param  responseControls   The set of controls from the response, if
242   *                            available.
243   */
244  private LDAPResult(@Nullable final Byte protocolOpType, final int messageID,
245                     @NotNull final ResultCode resultCode,
246                     @Nullable final String diagnosticMessage,
247                     @Nullable final String matchedDN,
248                     @Nullable final String[] referralURLs,
249                     @Nullable final Control[] responseControls)
250  {
251    this.protocolOpType    = protocolOpType;
252    this.messageID         = messageID;
253    this.resultCode        = resultCode;
254    this.diagnosticMessage = diagnosticMessage;
255    this.matchedDN         = matchedDN;
256
257    if (referralURLs == null)
258    {
259      this.referralURLs = StaticUtils.NO_STRINGS;
260    }
261    else
262    {
263      this.referralURLs = referralURLs;
264    }
265
266    if (responseControls == null)
267    {
268      this.responseControls = NO_CONTROLS;
269    }
270    else
271    {
272      this.responseControls = responseControls;
273    }
274  }
275
276
277
278  /**
279   * Creates a new LDAP result object with the provided information.
280   *
281   * @param  protocolOpType     The protocol op type for this result, if
282   *                            available.
283   * @param  messageID          The message ID for the LDAP message that is
284   *                            associated with this LDAP result.
285   * @param  resultCode         The result code from the response.
286   * @param  diagnosticMessage  The diagnostic message from the response, if
287   *                            available.
288   * @param  matchedDN          The matched DN from the response, if available.
289   * @param  referralURLs       The set of referral URLs from the response, if
290   *                            available.
291   * @param  responseControls   The set of controls from the response, if
292   *                            available.
293   */
294  private LDAPResult(@Nullable final Byte protocolOpType, final int messageID,
295                     @NotNull final ResultCode resultCode,
296                     @Nullable final String diagnosticMessage,
297                     @Nullable final String matchedDN,
298                     @Nullable final List<String> referralURLs,
299                     @Nullable final List<Control> responseControls)
300  {
301    this.protocolOpType    = protocolOpType;
302    this.messageID         = messageID;
303    this.resultCode        = resultCode;
304    this.diagnosticMessage = diagnosticMessage;
305    this.matchedDN         = matchedDN;
306
307    if ((referralURLs == null) || referralURLs.isEmpty())
308    {
309      this.referralURLs = StaticUtils.NO_STRINGS;
310    }
311    else
312    {
313      this.referralURLs = new String[referralURLs.size()];
314      referralURLs.toArray(this.referralURLs);
315    }
316
317    if ((responseControls == null) || responseControls.isEmpty())
318    {
319      this.responseControls = NO_CONTROLS;
320    }
321    else
322    {
323      this.responseControls = new Control[responseControls.size()];
324      responseControls.toArray(this.responseControls);
325    }
326  }
327
328
329
330  /**
331   * Creates a new LDAP result object with the provided message ID and with the
332   * protocol op and controls read from the given ASN.1 stream reader.
333   *
334   * @param  messageID        The LDAP message ID for the LDAP message that is
335   *                          associated with this LDAP result.
336   * @param  messageSequence  The ASN.1 stream reader sequence used in the
337   *                          course of reading the LDAP message elements.
338   * @param  reader           The ASN.1 stream reader from which to read the
339   *                          protocol op and controls.
340   *
341   * @return  The decoded LDAP result.
342   *
343   * @throws  LDAPException  If a problem occurs while reading or decoding data
344   *                         from the ASN.1 stream reader.
345   */
346  @NotNull()
347  static LDAPResult readLDAPResultFrom(final int messageID,
348              @NotNull final ASN1StreamReaderSequence messageSequence,
349              @NotNull final ASN1StreamReader reader)
350         throws LDAPException
351  {
352    try
353    {
354      final ASN1StreamReaderSequence protocolOpSequence =
355           reader.beginSequence();
356      final byte protocolOpType = protocolOpSequence.getType();
357
358      final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
359
360      String matchedDN = reader.readString();
361      if (matchedDN.isEmpty())
362      {
363        matchedDN = null;
364      }
365
366      String diagnosticMessage = reader.readString();
367      if (diagnosticMessage.isEmpty())
368      {
369        diagnosticMessage = null;
370      }
371
372      String[] referralURLs = StaticUtils.NO_STRINGS;
373      if (protocolOpSequence.hasMoreElements())
374      {
375        final ArrayList<String> refList = new ArrayList<>(1);
376        final ASN1StreamReaderSequence refSequence = reader.beginSequence();
377        while (refSequence.hasMoreElements())
378        {
379          refList.add(reader.readString());
380        }
381
382        referralURLs = new String[refList.size()];
383        refList.toArray(referralURLs);
384      }
385
386      Control[] responseControls = NO_CONTROLS;
387      if (messageSequence.hasMoreElements())
388      {
389        final ArrayList<Control> controlList = new ArrayList<>(1);
390        final ASN1StreamReaderSequence controlSequence = reader.beginSequence();
391        while (controlSequence.hasMoreElements())
392        {
393          controlList.add(Control.readFrom(reader));
394        }
395
396        responseControls = new Control[controlList.size()];
397        controlList.toArray(responseControls);
398      }
399
400      return new LDAPResult(protocolOpType, messageID, resultCode,
401           diagnosticMessage, matchedDN, referralURLs, responseControls);
402    }
403    catch (final LDAPException le)
404    {
405      Debug.debugException(le);
406      throw le;
407    }
408    catch (final ASN1Exception ae)
409    {
410      Debug.debugException(ae);
411      throw new LDAPException(ResultCode.DECODING_ERROR,
412           ERR_RESULT_CANNOT_DECODE.get(ae.getMessage()), ae);
413    }
414    catch (final Exception e)
415    {
416      Debug.debugException(e);
417      throw new LDAPException(ResultCode.DECODING_ERROR,
418           ERR_RESULT_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e);
419    }
420  }
421
422
423
424  /**
425   * Retrieves the message ID for the LDAP message with which this LDAP result
426   * is associated.
427   *
428   * @return  The message ID for the LDAP message with which this LDAP result
429   *          is associated.
430   */
431  @Override()
432  public final int getMessageID()
433  {
434    return messageID;
435  }
436
437
438
439  /**
440   * Retrieves the type of operation that triggered this result, if available.
441   *
442   * @return  The type of operation that triggered this result, or {@code null}
443   *          if the operation type is not available.
444   *
445   * Retrieves the BER type for the LDAP protocol op from which this
446   */
447  @Nullable()
448  public final OperationType getOperationType()
449  {
450    if (protocolOpType != null)
451    {
452      switch (protocolOpType)
453      {
454        case LDAPMessage.PROTOCOL_OP_TYPE_ADD_RESPONSE:
455          return OperationType.ADD;
456        case LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE:
457          return OperationType.BIND;
458        case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_RESPONSE:
459          return OperationType.COMPARE;
460        case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_RESPONSE:
461          return OperationType.DELETE;
462        case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
463          return OperationType.EXTENDED;
464        case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
465          return OperationType.MODIFY;
466        case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
467          return OperationType.MODIFY_DN;
468        case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE:
469          return OperationType.SEARCH;
470      }
471    }
472
473    return null;
474  }
475
476
477
478  /**
479   * Retrieves the result code from the response.
480   *
481   * @return  The result code from the response.
482   */
483  @NotNull()
484  public final ResultCode getResultCode()
485  {
486    return resultCode;
487  }
488
489
490
491  /**
492   * Retrieves the diagnostic message from the response, if available.
493   *
494   * @return  The diagnostic message from the response, or {@code null} if none
495   *          was provided.
496   */
497  @Nullable()
498  public final String getDiagnosticMessage()
499  {
500    return diagnosticMessage;
501  }
502
503
504
505  /**
506   * Retrieves the matched DN from the response, if available.
507   *
508   * @return  The matched DN from the response, or {@code null} if none was
509   *          provided.
510   */
511  @Nullable()
512  public final String getMatchedDN()
513  {
514    return matchedDN;
515  }
516
517
518
519  /**
520   * Retrieves the set of referral URLs from the response, if available.
521   *
522   * @return  The set of referral URLs from the response.  The array returned
523   *          may be empty if the response did not include any referral URLs.
524   */
525  @NotNull()
526  public final String[] getReferralURLs()
527  {
528    return referralURLs;
529  }
530
531
532
533  /**
534   * Retrieves the set of controls from the response, if available.  Individual
535   * response controls of a specific type may be retrieved and decoded using the
536   * {@code get} method in the response control class.
537   *
538   * @return  The set of controls from the response.  The array returned may be
539   *          empty if the response did not include any controls.
540   */
541  @NotNull()
542  public final Control[] getResponseControls()
543  {
544    return responseControls;
545  }
546
547
548
549  /**
550   * Indicates whether this result contains at least one control.
551   *
552   * @return  {@code true} if this result contains at least one control, or
553   *          {@code false} if not.
554   */
555  public final boolean hasResponseControl()
556  {
557    return (responseControls.length > 0);
558  }
559
560
561
562  /**
563   * Indicates whether this result contains at least one control with the
564   * specified OID.
565   *
566   * @param  oid  The object identifier for which to make the determination.  It
567   *              must not be {@code null}.
568   *
569   * @return  {@code true} if this result contains at least one control with
570   *          the specified OID, or {@code false} if not.
571   */
572  public final boolean hasResponseControl(@NotNull final String oid)
573  {
574    for (final Control c : responseControls)
575    {
576      if (c.getOID().equals(oid))
577      {
578        return true;
579      }
580    }
581
582    return false;
583  }
584
585
586
587  /**
588   * Retrieves the response control with the specified OID.  If there is more
589   * than one response control with the specified OID, then the first will be
590   * returned.
591   *
592   * @param  oid  The OID for the response control to retrieve.
593   *
594   * @return  The requested response control, or {@code null} if there is no
595   *          such response control.
596   */
597  @Nullable()
598  public final Control getResponseControl(@NotNull final String oid)
599  {
600    for (final Control c : responseControls)
601    {
602      if (c.getOID().equals(oid))
603      {
604        return c;
605      }
606    }
607
608    return null;
609  }
610
611
612
613  /**
614   * Retrieves a string representation of this LDAP result, consisting of
615   * the result code, diagnostic message (if present), matched DN (if present),
616   * and referral URLs (if present).
617   *
618   * @return  A string representation of this LDAP result.
619   */
620  @NotNull()
621  public String getResultString()
622  {
623    final StringBuilder buffer = new StringBuilder();
624    buffer.append("result code='");
625    buffer.append(resultCode);
626    buffer.append('\'');
627
628    if ((diagnosticMessage != null) && (! diagnosticMessage.isEmpty()))
629    {
630      buffer.append(" diagnostic message='");
631      buffer.append(diagnosticMessage);
632      buffer.append('\'');
633    }
634
635    if ((matchedDN != null) && (! matchedDN.isEmpty()))
636    {
637      buffer.append("  matched DN='");
638      buffer.append(matchedDN);
639      buffer.append('\'');
640    }
641
642    if ((referralURLs != null) && (referralURLs.length > 0))
643    {
644      buffer.append("  referral URLs={");
645
646      for (int i=0; i < referralURLs.length; i++)
647      {
648        if (i > 0)
649        {
650          buffer.append(", ");
651        }
652
653        buffer.append('\'');
654        buffer.append(referralURLs[i]);
655        buffer.append('\'');
656      }
657
658      buffer.append('}');
659    }
660
661    return buffer.toString();
662  }
663
664
665
666  /**
667   * Retrieves a string representation of this LDAP result.
668   *
669   * @return  A string representation of this LDAP result.
670   */
671  @Override()
672  @NotNull()
673  public String toString()
674  {
675    final StringBuilder buffer = new StringBuilder();
676    toString(buffer);
677    return buffer.toString();
678  }
679
680
681
682  /**
683   * Appends a string representation of this LDAP result to the provided buffer.
684   *
685   * @param  buffer  The buffer to which to append a string representation of
686   *                 this LDAP result.
687   */
688  @Override()
689  public void toString(@NotNull final StringBuilder buffer)
690  {
691    buffer.append("LDAPResult(resultCode=");
692    buffer.append(resultCode);
693
694    if (messageID >= 0)
695    {
696      buffer.append(", messageID=");
697      buffer.append(messageID);
698    }
699
700    if (protocolOpType != null)
701    {
702      switch (protocolOpType)
703      {
704        case LDAPMessage.PROTOCOL_OP_TYPE_ADD_RESPONSE:
705          buffer.append(", opType='add'");
706          break;
707        case LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE:
708          buffer.append(", opType='bind'");
709          break;
710        case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_RESPONSE:
711          buffer.append(", opType='compare'");
712          break;
713        case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_RESPONSE:
714          buffer.append(", opType='delete'");
715          break;
716        case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
717          buffer.append(", opType='extended'");
718          break;
719        case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
720          buffer.append(", opType='modify'");
721          break;
722        case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
723          buffer.append(", opType='modify DN'");
724          break;
725        case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE:
726          buffer.append(", opType='search'");
727          break;
728      }
729    }
730
731    if (diagnosticMessage != null)
732    {
733      buffer.append(", diagnosticMessage='");
734      buffer.append(diagnosticMessage);
735      buffer.append('\'');
736    }
737
738    if (matchedDN != null)
739    {
740      buffer.append(", matchedDN='");
741      buffer.append(matchedDN);
742      buffer.append('\'');
743    }
744
745    if (referralURLs.length > 0)
746    {
747      buffer.append(", referralURLs={");
748      for (int i=0; i < referralURLs.length; i++)
749      {
750        if (i > 0)
751        {
752          buffer.append(", ");
753        }
754
755        buffer.append('\'');
756        buffer.append(referralURLs[i]);
757        buffer.append('\'');
758      }
759      buffer.append('}');
760    }
761
762    if (responseControls.length > 0)
763    {
764      buffer.append(", responseControls={");
765      for (int i=0; i < responseControls.length; i++)
766      {
767        if (i > 0)
768        {
769          buffer.append(", ");
770        }
771
772        buffer.append(responseControls[i]);
773      }
774      buffer.append('}');
775    }
776
777    buffer.append(')');
778  }
779}