001/*
002 * Copyright 2009-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-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.protocol;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1Buffer;
031import com.unboundid.asn1.ASN1BufferSequence;
032import com.unboundid.asn1.ASN1Element;
033import com.unboundid.asn1.ASN1Enumerated;
034import com.unboundid.asn1.ASN1OctetString;
035import com.unboundid.asn1.ASN1Sequence;
036import com.unboundid.asn1.ASN1StreamReader;
037import com.unboundid.asn1.ASN1StreamReaderSequence;
038import com.unboundid.ldap.sdk.Control;
039import com.unboundid.ldap.sdk.ExtendedResult;
040import com.unboundid.ldap.sdk.LDAPException;
041import com.unboundid.ldap.sdk.LDAPResult;
042import com.unboundid.ldap.sdk.ResultCode;
043import com.unboundid.util.NotMutable;
044import com.unboundid.util.InternalUseOnly;
045import com.unboundid.util.ThreadSafety;
046import com.unboundid.util.ThreadSafetyLevel;
047
048import static com.unboundid.ldap.protocol.ProtocolMessages.*;
049import static com.unboundid.util.Debug.*;
050import static com.unboundid.util.StaticUtils.*;
051import static com.unboundid.util.Validator.*;
052
053
054
055/**
056 * This class provides an implementation of a extended response protocol op.
057 */
058@InternalUseOnly()
059@NotMutable()
060@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
061public final class ExtendedResponseProtocolOp
062       implements ProtocolOp
063{
064  /**
065   * The BER type for the response OID element.
066   */
067  public static final byte TYPE_RESPONSE_OID = (byte) 0x8A;
068
069
070
071  /**
072   * The BER type for the response value element.
073   */
074  public static final byte TYPE_RESPONSE_VALUE = (byte) 0x8B;
075
076
077
078  /**
079   * The serial version UID for this serializable class.
080   */
081  private static final long serialVersionUID = -7757619031268544913L;
082
083
084
085  // The value for this extended response.
086  private final ASN1OctetString responseValue;
087
088  // The result code for this extended response.
089  private final int resultCode;
090
091  // The referral URLs for this extended response.
092  private final List<String> referralURLs;
093
094  // The diagnostic message for this extended response.
095  private final String diagnosticMessage;
096
097  // The matched DN for this extended response.
098  private final String matchedDN;
099
100  // The OID for this extended response.
101  private final String responseOID;
102
103
104
105  /**
106   * Creates a new instance of this extended response protocol op with the
107   * provided information.
108   *
109   * @param  resultCode         The result code for this response.
110   * @param  matchedDN          The matched DN for this response, if available.
111   * @param  diagnosticMessage  The diagnostic message for this response, if
112   *                            any.
113   * @param  referralURLs       The list of referral URLs for this response, if
114   *                            any.
115   * @param  responseOID        The response OID for this response, if any.
116   * @param  responseValue      The value for this response, if any.
117   */
118  public ExtendedResponseProtocolOp(final int resultCode,
119                                    final String matchedDN,
120                                    final String diagnosticMessage,
121                                    final List<String> referralURLs,
122                                    final String responseOID,
123                                    final ASN1OctetString responseValue)
124  {
125    this.resultCode        = resultCode;
126    this.matchedDN         = matchedDN;
127    this.diagnosticMessage = diagnosticMessage;
128    this.responseOID       = responseOID;
129
130    if (referralURLs == null)
131    {
132      this.referralURLs = Collections.emptyList();
133    }
134    else
135    {
136      this.referralURLs = Collections.unmodifiableList(referralURLs);
137    }
138
139    if (responseValue == null)
140    {
141      this.responseValue = null;
142    }
143    else
144    {
145      this.responseValue =
146           new ASN1OctetString(TYPE_RESPONSE_VALUE, responseValue.getValue());
147    }
148  }
149
150
151
152  /**
153   * Creates a new extended response protocol op from the provided extended
154   * result object.
155   *
156   * @param  result  The extended result object to use to create this protocol
157   *                 op.
158   */
159  public ExtendedResponseProtocolOp(final LDAPResult result)
160  {
161    resultCode        = result.getResultCode().intValue();
162    matchedDN         = result.getMatchedDN();
163    diagnosticMessage = result.getDiagnosticMessage();
164    referralURLs      = toList(result.getReferralURLs());
165
166    if (result instanceof ExtendedResult)
167    {
168      final ExtendedResult r = (ExtendedResult) result;
169      responseOID   = r.getOID();
170      responseValue = r.getValue();
171    }
172    else
173    {
174      responseOID   = null;
175      responseValue = null;
176    }
177  }
178
179
180
181  /**
182   * Creates a new extended response protocol op read from the provided ASN.1
183   * stream reader.
184   *
185   * @param  reader  The ASN.1 stream reader from which to read the extended
186   *                 response.
187   *
188   * @throws  LDAPException  If a problem occurs while reading or parsing the
189   *                         extended response.
190   */
191  ExtendedResponseProtocolOp(final ASN1StreamReader reader)
192       throws LDAPException
193  {
194    try
195    {
196      final ASN1StreamReaderSequence opSequence = reader.beginSequence();
197      resultCode = reader.readEnumerated();
198
199      String s = reader.readString();
200      ensureNotNull(s);
201      if (s.length() == 0)
202      {
203        matchedDN = null;
204      }
205      else
206      {
207        matchedDN = s;
208      }
209
210      s = reader.readString();
211      ensureNotNull(s);
212      if (s.length() == 0)
213      {
214        diagnosticMessage = null;
215      }
216      else
217      {
218        diagnosticMessage = s;
219      }
220
221      ASN1OctetString value = null;
222      String oid = null;
223      final ArrayList<String> refs = new ArrayList<String>(1);
224      while (opSequence.hasMoreElements())
225      {
226        final byte type = (byte) reader.peek();
227        if (type == GenericResponseProtocolOp.TYPE_REFERRALS)
228        {
229          final ASN1StreamReaderSequence refSequence = reader.beginSequence();
230          while (refSequence.hasMoreElements())
231          {
232            refs.add(reader.readString());
233          }
234        }
235        else if (type == TYPE_RESPONSE_OID)
236        {
237          oid = reader.readString();
238        }
239        else if (type == TYPE_RESPONSE_VALUE)
240        {
241          value = new ASN1OctetString(type, reader.readBytes());
242        }
243        else
244        {
245          throw new LDAPException(ResultCode.DECODING_ERROR,
246               ERR_EXTENDED_RESPONSE_INVALID_ELEMENT.get(toHex(type)));
247        }
248      }
249
250      referralURLs  = Collections.unmodifiableList(refs);
251      responseOID   = oid;
252      responseValue = value;
253    }
254    catch (final LDAPException le)
255    {
256      debugException(le);
257      throw le;
258    }
259    catch (final Exception e)
260    {
261      debugException(e);
262      throw new LDAPException(ResultCode.DECODING_ERROR,
263           ERR_EXTENDED_RESPONSE_CANNOT_DECODE.get(getExceptionMessage(e)), e);
264    }
265  }
266
267
268
269  /**
270   * Retrieves the result code for this extended response.
271   *
272   * @return  The result code for this extended response.
273   */
274  public int getResultCode()
275  {
276    return resultCode;
277  }
278
279
280
281  /**
282   * Retrieves the matched DN for this extended response, if any.
283   *
284   * @return  The matched DN for this extended response, or {@code null} if
285   *          there is no matched DN.
286   */
287  public String getMatchedDN()
288  {
289    return matchedDN;
290  }
291
292
293
294  /**
295   * Retrieves the diagnostic message for this extended response, if any.
296   *
297   * @return  The diagnostic message for this extended response, or {@code null}
298   *          if there is no diagnostic message.
299   */
300  public String getDiagnosticMessage()
301  {
302    return diagnosticMessage;
303  }
304
305
306
307  /**
308   * Retrieves the list of referral URLs for this extended response.
309   *
310   * @return  The list of referral URLs for this extended response, or an empty
311   *          list if there are no referral URLs.
312   */
313  public List<String> getReferralURLs()
314  {
315    return referralURLs;
316  }
317
318
319
320  /**
321   * Retrieves the OID for this extended response, if any.
322   *
323   * @return  The OID for this extended response, or {@code null} if there is no
324   *          response OID.
325   */
326  public String getResponseOID()
327  {
328    return responseOID;
329  }
330
331
332
333  /**
334   * Retrieves the value for this extended response, if any.
335   *
336   * @return  The value for this extended response, or {@code null} if there is
337   *          no response value.
338   */
339  public ASN1OctetString getResponseValue()
340  {
341    return responseValue;
342  }
343
344
345
346  /**
347   * {@inheritDoc}
348   */
349  @Override()
350  public byte getProtocolOpType()
351  {
352    return LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE;
353  }
354
355
356
357  /**
358   * {@inheritDoc}
359   */
360  @Override()
361  public ASN1Element encodeProtocolOp()
362  {
363    final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(6);
364    elements.add(new ASN1Enumerated(getResultCode()));
365
366    final String mdn = getMatchedDN();
367    if (mdn == null)
368    {
369      elements.add(new ASN1OctetString());
370    }
371    else
372    {
373      elements.add(new ASN1OctetString(mdn));
374    }
375
376    final String dm = getDiagnosticMessage();
377    if (dm == null)
378    {
379      elements.add(new ASN1OctetString());
380    }
381    else
382    {
383      elements.add(new ASN1OctetString(dm));
384    }
385
386    final List<String> refs = getReferralURLs();
387    if (! refs.isEmpty())
388    {
389      final ArrayList<ASN1Element> refElements =
390           new ArrayList<ASN1Element>(refs.size());
391      for (final String r : refs)
392      {
393        refElements.add(new ASN1OctetString(r));
394      }
395      elements.add(new ASN1Sequence(GenericResponseProtocolOp.TYPE_REFERRALS,
396           refElements));
397    }
398
399    if (responseOID != null)
400    {
401      elements.add(new ASN1OctetString(TYPE_RESPONSE_OID, responseOID));
402    }
403
404    if (responseValue != null)
405    {
406      elements.add(responseValue);
407    }
408
409    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE,
410         elements);
411  }
412
413
414
415  /**
416   * Decodes the provided ASN.1 element as an extended response protocol op.
417   *
418   * @param  element  The ASN.1 element to be decoded.
419   *
420   * @return  The decoded extended response protocol op.
421   *
422   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
423   *                         an extended response protocol op.
424   */
425  public static ExtendedResponseProtocolOp decodeProtocolOp(
426                                                final ASN1Element element)
427         throws LDAPException
428  {
429    try
430    {
431      final ASN1Element[] elements =
432           ASN1Sequence.decodeAsSequence(element).elements();
433      final int resultCode =
434           ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue();
435
436      final String matchedDN;
437      final String md =
438           ASN1OctetString.decodeAsOctetString(elements[1]).stringValue();
439      if (md.length() > 0)
440      {
441        matchedDN = md;
442      }
443      else
444      {
445        matchedDN = null;
446      }
447
448      final String diagnosticMessage;
449      final String dm =
450           ASN1OctetString.decodeAsOctetString(elements[2]).stringValue();
451      if (dm.length() > 0)
452      {
453        diagnosticMessage = dm;
454      }
455      else
456      {
457        diagnosticMessage = null;
458      }
459
460      ASN1OctetString responseValue = null;
461      List<String> referralURLs = null;
462      String responseOID = null;
463      if (elements.length > 3)
464      {
465        for (int i=3; i < elements.length; i++)
466        {
467          switch (elements[i].getType())
468          {
469            case GenericResponseProtocolOp.TYPE_REFERRALS:
470              final ASN1Element[] refElements =
471                   ASN1Sequence.decodeAsSequence(elements[3]).elements();
472              referralURLs = new ArrayList<String>(refElements.length);
473              for (final ASN1Element e : refElements)
474              {
475                referralURLs.add(
476                     ASN1OctetString.decodeAsOctetString(e).stringValue());
477              }
478              break;
479
480            case TYPE_RESPONSE_OID:
481              responseOID = ASN1OctetString.decodeAsOctetString(elements[i]).
482                   stringValue();
483              break;
484
485            case TYPE_RESPONSE_VALUE:
486              responseValue = ASN1OctetString.decodeAsOctetString(elements[i]);
487              break;
488
489            default:
490              throw new LDAPException(ResultCode.DECODING_ERROR,
491                   ERR_EXTENDED_RESPONSE_INVALID_ELEMENT.get(
492                        toHex(elements[i].getType())));
493          }
494        }
495      }
496
497      return new ExtendedResponseProtocolOp(resultCode, matchedDN,
498           diagnosticMessage, referralURLs, responseOID, responseValue);
499    }
500    catch (final LDAPException le)
501    {
502      debugException(le);
503      throw le;
504    }
505    catch (final Exception e)
506    {
507      debugException(e);
508      throw new LDAPException(ResultCode.DECODING_ERROR,
509           ERR_EXTENDED_RESPONSE_CANNOT_DECODE.get(getExceptionMessage(e)),
510           e);
511    }
512  }
513
514
515
516  /**
517   * {@inheritDoc}
518   */
519  @Override()
520  public void writeTo(final ASN1Buffer buffer)
521  {
522    final ASN1BufferSequence opSequence =
523         buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE);
524    buffer.addEnumerated(resultCode);
525    buffer.addOctetString(matchedDN);
526    buffer.addOctetString(diagnosticMessage);
527
528    if (! referralURLs.isEmpty())
529    {
530      final ASN1BufferSequence refSequence =
531           buffer.beginSequence(GenericResponseProtocolOp.TYPE_REFERRALS);
532      for (final String s : referralURLs)
533      {
534        buffer.addOctetString(s);
535      }
536      refSequence.end();
537    }
538
539    if (responseOID != null)
540    {
541      buffer.addOctetString(TYPE_RESPONSE_OID, responseOID);
542    }
543
544    if (responseValue != null)
545    {
546      buffer.addOctetString(TYPE_RESPONSE_VALUE, responseValue.getValue());
547    }
548
549    opSequence.end();
550  }
551
552
553
554  /**
555   * Creates a extended result from this protocol op.
556   *
557   * @param  controls  The set of controls to include in the extended result.
558   *                   It may be empty or {@code null} if no controls should be
559   *                   included.
560   *
561   * @return  The extended result that was created.
562   */
563  public ExtendedResult toExtendedResult(final Control... controls)
564  {
565    final String[] referralArray = new String[referralURLs.size()];
566    referralURLs.toArray(referralArray);
567
568    return new ExtendedResult(-1, ResultCode.valueOf(resultCode),
569         diagnosticMessage, matchedDN, referralArray, responseOID,
570         responseValue, controls);
571  }
572
573
574
575  /**
576   * Retrieves a string representation of this protocol op.
577   *
578   * @return  A string representation of this protocol op.
579   */
580  @Override()
581  public String toString()
582  {
583    final StringBuilder buffer = new StringBuilder();
584    toString(buffer);
585    return buffer.toString();
586  }
587
588
589
590  /**
591   * {@inheritDoc}
592   */
593  @Override()
594  public void toString(final StringBuilder buffer)
595  {
596    buffer.append("ExtendedResponseProtocolOp(resultCode=");
597    buffer.append(resultCode);
598
599    if (matchedDN != null)
600    {
601      buffer.append(", matchedDN='");
602      buffer.append(matchedDN);
603      buffer.append('\'');
604    }
605
606    if (diagnosticMessage != null)
607    {
608      buffer.append(", diagnosticMessage='");
609      buffer.append(diagnosticMessage);
610      buffer.append('\'');
611    }
612
613    if (! referralURLs.isEmpty())
614    {
615      buffer.append(", referralURLs={");
616
617      final Iterator<String> iterator = referralURLs.iterator();
618      while (iterator.hasNext())
619      {
620        buffer.append('\'');
621        buffer.append(iterator.next());
622        buffer.append('\'');
623        if (iterator.hasNext())
624        {
625          buffer.append(',');
626        }
627      }
628
629      buffer.append('}');
630    }
631
632    if (responseOID != null)
633    {
634      buffer.append(", responseOID='");
635      buffer.append(responseOID);
636      buffer.append('\'');
637    }
638
639    buffer.append(')');
640  }
641}