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