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.Debug;
044import com.unboundid.util.InternalUseOnly;
045import com.unboundid.util.NotMutable;
046import com.unboundid.util.StaticUtils;
047import com.unboundid.util.ThreadSafety;
048import com.unboundid.util.ThreadSafetyLevel;
049import com.unboundid.util.Validator;
050
051import static com.unboundid.ldap.protocol.ProtocolMessages.*;
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      = StaticUtils.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      Validator.ensureNotNull(s);
201      if (s.isEmpty())
202      {
203        matchedDN = null;
204      }
205      else
206      {
207        matchedDN = s;
208      }
209
210      s = reader.readString();
211      Validator.ensureNotNull(s);
212      if (s.isEmpty())
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<>(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(
247                    StaticUtils.toHex(type)));
248        }
249      }
250
251      referralURLs  = Collections.unmodifiableList(refs);
252      responseOID   = oid;
253      responseValue = value;
254    }
255    catch (final LDAPException le)
256    {
257      Debug.debugException(le);
258      throw le;
259    }
260    catch (final Exception e)
261    {
262      Debug.debugException(e);
263      throw new LDAPException(ResultCode.DECODING_ERROR,
264           ERR_EXTENDED_RESPONSE_CANNOT_DECODE.get(
265                StaticUtils.getExceptionMessage(e)), e);
266    }
267  }
268
269
270
271  /**
272   * Retrieves the result code for this extended response.
273   *
274   * @return  The result code for this extended response.
275   */
276  public int getResultCode()
277  {
278    return resultCode;
279  }
280
281
282
283  /**
284   * Retrieves the matched DN for this extended response, if any.
285   *
286   * @return  The matched DN for this extended response, or {@code null} if
287   *          there is no matched DN.
288   */
289  public String getMatchedDN()
290  {
291    return matchedDN;
292  }
293
294
295
296  /**
297   * Retrieves the diagnostic message for this extended response, if any.
298   *
299   * @return  The diagnostic message for this extended response, or {@code null}
300   *          if there is no diagnostic message.
301   */
302  public String getDiagnosticMessage()
303  {
304    return diagnosticMessage;
305  }
306
307
308
309  /**
310   * Retrieves the list of referral URLs for this extended response.
311   *
312   * @return  The list of referral URLs for this extended response, or an empty
313   *          list if there are no referral URLs.
314   */
315  public List<String> getReferralURLs()
316  {
317    return referralURLs;
318  }
319
320
321
322  /**
323   * Retrieves the OID for this extended response, if any.
324   *
325   * @return  The OID for this extended response, or {@code null} if there is no
326   *          response OID.
327   */
328  public String getResponseOID()
329  {
330    return responseOID;
331  }
332
333
334
335  /**
336   * Retrieves the value for this extended response, if any.
337   *
338   * @return  The value for this extended response, or {@code null} if there is
339   *          no response value.
340   */
341  public ASN1OctetString getResponseValue()
342  {
343    return responseValue;
344  }
345
346
347
348  /**
349   * {@inheritDoc}
350   */
351  @Override()
352  public byte getProtocolOpType()
353  {
354    return LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE;
355  }
356
357
358
359  /**
360   * {@inheritDoc}
361   */
362  @Override()
363  public ASN1Element encodeProtocolOp()
364  {
365    final ArrayList<ASN1Element> elements = new ArrayList<>(6);
366    elements.add(new ASN1Enumerated(getResultCode()));
367
368    final String mdn = getMatchedDN();
369    if (mdn == null)
370    {
371      elements.add(new ASN1OctetString());
372    }
373    else
374    {
375      elements.add(new ASN1OctetString(mdn));
376    }
377
378    final String dm = getDiagnosticMessage();
379    if (dm == null)
380    {
381      elements.add(new ASN1OctetString());
382    }
383    else
384    {
385      elements.add(new ASN1OctetString(dm));
386    }
387
388    final List<String> refs = getReferralURLs();
389    if (! refs.isEmpty())
390    {
391      final ArrayList<ASN1Element> refElements = new ArrayList<>(refs.size());
392      for (final String r : refs)
393      {
394        refElements.add(new ASN1OctetString(r));
395      }
396      elements.add(new ASN1Sequence(GenericResponseProtocolOp.TYPE_REFERRALS,
397           refElements));
398    }
399
400    if (responseOID != null)
401    {
402      elements.add(new ASN1OctetString(TYPE_RESPONSE_OID, responseOID));
403    }
404
405    if (responseValue != null)
406    {
407      elements.add(responseValue);
408    }
409
410    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE,
411         elements);
412  }
413
414
415
416  /**
417   * Decodes the provided ASN.1 element as an extended response protocol op.
418   *
419   * @param  element  The ASN.1 element to be decoded.
420   *
421   * @return  The decoded extended response protocol op.
422   *
423   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
424   *                         an extended response protocol op.
425   */
426  public static ExtendedResponseProtocolOp decodeProtocolOp(
427                                                final ASN1Element element)
428         throws LDAPException
429  {
430    try
431    {
432      final ASN1Element[] elements =
433           ASN1Sequence.decodeAsSequence(element).elements();
434      final int resultCode =
435           ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue();
436
437      final String matchedDN;
438      final String md =
439           ASN1OctetString.decodeAsOctetString(elements[1]).stringValue();
440      if (! md.isEmpty())
441      {
442        matchedDN = md;
443      }
444      else
445      {
446        matchedDN = null;
447      }
448
449      final String diagnosticMessage;
450      final String dm =
451           ASN1OctetString.decodeAsOctetString(elements[2]).stringValue();
452      if (! dm.isEmpty())
453      {
454        diagnosticMessage = dm;
455      }
456      else
457      {
458        diagnosticMessage = null;
459      }
460
461      ASN1OctetString responseValue = null;
462      List<String> referralURLs = null;
463      String responseOID = null;
464      if (elements.length > 3)
465      {
466        for (int i=3; i < elements.length; i++)
467        {
468          switch (elements[i].getType())
469          {
470            case GenericResponseProtocolOp.TYPE_REFERRALS:
471              final ASN1Element[] refElements =
472                   ASN1Sequence.decodeAsSequence(elements[3]).elements();
473              referralURLs = new ArrayList<>(refElements.length);
474              for (final ASN1Element e : refElements)
475              {
476                referralURLs.add(
477                     ASN1OctetString.decodeAsOctetString(e).stringValue());
478              }
479              break;
480
481            case TYPE_RESPONSE_OID:
482              responseOID = ASN1OctetString.decodeAsOctetString(elements[i]).
483                   stringValue();
484              break;
485
486            case TYPE_RESPONSE_VALUE:
487              responseValue = ASN1OctetString.decodeAsOctetString(elements[i]);
488              break;
489
490            default:
491              throw new LDAPException(ResultCode.DECODING_ERROR,
492                   ERR_EXTENDED_RESPONSE_INVALID_ELEMENT.get(
493                        StaticUtils.toHex(elements[i].getType())));
494          }
495        }
496      }
497
498      return new ExtendedResponseProtocolOp(resultCode, matchedDN,
499           diagnosticMessage, referralURLs, responseOID, responseValue);
500    }
501    catch (final LDAPException le)
502    {
503      Debug.debugException(le);
504      throw le;
505    }
506    catch (final Exception e)
507    {
508      Debug.debugException(e);
509      throw new LDAPException(ResultCode.DECODING_ERROR,
510           ERR_EXTENDED_RESPONSE_CANNOT_DECODE.get(
511                StaticUtils.getExceptionMessage(e)),
512           e);
513    }
514  }
515
516
517
518  /**
519   * {@inheritDoc}
520   */
521  @Override()
522  public void writeTo(final ASN1Buffer buffer)
523  {
524    final ASN1BufferSequence opSequence =
525         buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE);
526    buffer.addEnumerated(resultCode);
527    buffer.addOctetString(matchedDN);
528    buffer.addOctetString(diagnosticMessage);
529
530    if (! referralURLs.isEmpty())
531    {
532      final ASN1BufferSequence refSequence =
533           buffer.beginSequence(GenericResponseProtocolOp.TYPE_REFERRALS);
534      for (final String s : referralURLs)
535      {
536        buffer.addOctetString(s);
537      }
538      refSequence.end();
539    }
540
541    if (responseOID != null)
542    {
543      buffer.addOctetString(TYPE_RESPONSE_OID, responseOID);
544    }
545
546    if (responseValue != null)
547    {
548      buffer.addOctetString(TYPE_RESPONSE_VALUE, responseValue.getValue());
549    }
550
551    opSequence.end();
552  }
553
554
555
556  /**
557   * Creates a extended result from this protocol op.
558   *
559   * @param  controls  The set of controls to include in the extended result.
560   *                   It may be empty or {@code null} if no controls should be
561   *                   included.
562   *
563   * @return  The extended result that was created.
564   */
565  public ExtendedResult toExtendedResult(final Control... controls)
566  {
567    final String[] referralArray = new String[referralURLs.size()];
568    referralURLs.toArray(referralArray);
569
570    return new ExtendedResult(-1, ResultCode.valueOf(resultCode),
571         diagnosticMessage, matchedDN, referralArray, responseOID,
572         responseValue, controls);
573  }
574
575
576
577  /**
578   * Retrieves a string representation of this protocol op.
579   *
580   * @return  A string representation of this protocol op.
581   */
582  @Override()
583  public String toString()
584  {
585    final StringBuilder buffer = new StringBuilder();
586    toString(buffer);
587    return buffer.toString();
588  }
589
590
591
592  /**
593   * {@inheritDoc}
594   */
595  @Override()
596  public void toString(final StringBuilder buffer)
597  {
598    buffer.append("ExtendedResponseProtocolOp(resultCode=");
599    buffer.append(resultCode);
600
601    if (matchedDN != null)
602    {
603      buffer.append(", matchedDN='");
604      buffer.append(matchedDN);
605      buffer.append('\'');
606    }
607
608    if (diagnosticMessage != null)
609    {
610      buffer.append(", diagnosticMessage='");
611      buffer.append(diagnosticMessage);
612      buffer.append('\'');
613    }
614
615    if (! referralURLs.isEmpty())
616    {
617      buffer.append(", referralURLs={");
618
619      final Iterator<String> iterator = referralURLs.iterator();
620      while (iterator.hasNext())
621      {
622        buffer.append('\'');
623        buffer.append(iterator.next());
624        buffer.append('\'');
625        if (iterator.hasNext())
626        {
627          buffer.append(',');
628        }
629      }
630
631      buffer.append('}');
632    }
633
634    if (responseOID != null)
635    {
636      buffer.append(", responseOID='");
637      buffer.append(responseOID);
638      buffer.append('\'');
639    }
640
641    buffer.append(')');
642  }
643}