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