001/*
002 * Copyright 2009-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-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) 2009-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;
042
043import com.unboundid.asn1.ASN1OctetString;
044import com.unboundid.asn1.ASN1StreamReader;
045import com.unboundid.asn1.ASN1StreamReaderSequence;
046import com.unboundid.ldap.protocol.LDAPResponse;
047import com.unboundid.util.Debug;
048import com.unboundid.util.Extensible;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.Nullable;
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 information about an LDAP
062 * intermediate response, which provides the ability for the directory server to
063 * return multiple messages in response to operations that would not otherwise
064 * support it.  Intermediate response messages will only be returned by the
065 * server if the client does something to explicitly indicate that it is able
066 * to accept them (e.g., by requesting an extended operation that may return
067 * intermediate response messages, or by including a control in a request that
068 * may cause the request to return intermediate response messages).
069 * Intermediate response messages may include one or both of the following:
070 * <UL>
071 *   <LI>Response OID -- An optional OID that can be used to identify the type
072 *       of intermediate response.</LI>
073 *   <LI>Value -- An optional element that provides the encoded value for this
074 *       intermediate response.  If a value is provided, then the encoding for
075 *       the value depends on the type of intermediate response.</LI>
076 * </UL>
077 * When requesting an operation which may return intermediate response messages,
078 * an {@link IntermediateResponseListener} must be provided for the associated
079 * request.  If an intermediate response message is returned for a request that
080 * does not have a registered {@code IntermediateResponseListener}, then it will
081 * be silently discarded.
082 */
083@Extensible()
084@NotMutable()
085@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
086public class IntermediateResponse
087       implements Serializable, LDAPResponse
088{
089  /**
090   * The BER type for the intermediate response OID element.
091   */
092  protected static final byte TYPE_INTERMEDIATE_RESPONSE_OID = (byte) 0x80;
093
094
095
096  /**
097   * The BER type for the intermediate response value element.
098   */
099  protected static final byte TYPE_INTERMEDIATE_RESPONSE_VALUE = (byte) 0x81;
100
101
102
103  /**
104   * An empty set of controls that will be used if no controls are provided.
105   */
106  @NotNull private static final Control[] NO_CONTROLS = new Control[0];
107
108
109
110  /**
111   * The serial version UID for this serializable class.
112   */
113  private static final long serialVersionUID = 218434694212935869L;
114
115
116
117  // The encoded value for this intermediate response, if available.
118  @Nullable private final ASN1OctetString value;
119
120  // The set of controls for this intermediate response.
121  @NotNull private final Control[] controls;
122
123  // The message ID for this intermediate response.
124  private final int messageID;
125
126  // The OID for this intermeddiate response.
127  @Nullable private final String oid;
128
129
130
131  /**
132   * Creates a new intermediate response with the provided information.
133   *
134   * @param  oid    The OID for this intermediate response.  It may be
135   *                {@code null} if there is no OID.
136   * @param  value  The value for this intermediate response.  It may be
137   *                {@code null} if there is no value.
138   */
139  public IntermediateResponse(@Nullable final String oid,
140                              @Nullable final ASN1OctetString value)
141  {
142    this(-1, oid, value, NO_CONTROLS);
143  }
144
145
146
147  /**
148   * Creates a new intermediate response with the provided information.
149   *
150   * @param  messageID  The message ID for the LDAP message containing this
151   *                    intermediate response.
152   * @param  oid        The OID for this intermediate response.  It may be
153   *                    {@code null} if there is no OID.
154   * @param  value      The value for this intermediate response.  It may be
155   *                    {@code null} if there is no value.
156   */
157  public IntermediateResponse(final int messageID, @Nullable final String oid,
158                              @Nullable final ASN1OctetString value)
159  {
160    this(messageID, oid, value, NO_CONTROLS);
161  }
162
163
164
165  /**
166   * Creates a new intermediate response with the provided information.
167   *
168   * @param  oid       The OID for this intermediate response.  It may be
169   *                   {@code null} if there is no OID.
170   * @param  value     The value for this intermediate response.  It may be
171   *                   {@code null} if there is no value.
172   * @param  controls  The set of controls for this intermediate response.
173   */
174  public IntermediateResponse(@Nullable final String oid,
175                              @Nullable final ASN1OctetString value,
176                              @Nullable final Control[] controls)
177  {
178    this(-1, oid, value, controls);
179  }
180
181
182
183  /**
184   * Creates a new intermediate response with the provided information.
185   *
186   * @param  messageID  The message ID for the LDAP message containing this
187   *                    intermediate response.
188   * @param  oid        The OID for this intermediate response.  It may be
189   *                    {@code null} if there is no OID.
190   * @param  value      The value for this intermediate response.  It may be
191   *                    {@code null} if there is no value.
192   * @param  controls   The set of controls for this intermediate response.
193   */
194  public IntermediateResponse(final int messageID, @Nullable final String oid,
195                              @Nullable final ASN1OctetString value,
196                              @Nullable final Control[] controls)
197  {
198    this.messageID = messageID;
199    this.oid       = oid;
200    this.value     = value;
201
202    if (controls == null)
203    {
204      this.controls = NO_CONTROLS;
205    }
206    else
207    {
208      this.controls = controls;
209    }
210  }
211
212
213
214  /**
215   * Creates a new intermediate response with the information from the provided
216   * intermediate response.
217   *
218   * @param  intermediateResponse  The intermediate response that should be used
219   *                               to create this new intermediate response.
220   */
221  protected IntermediateResponse(
222                 @NotNull final IntermediateResponse intermediateResponse)
223  {
224    messageID = intermediateResponse.messageID;
225    oid       = intermediateResponse.oid;
226    value     = intermediateResponse.value;
227    controls  = intermediateResponse.controls;
228  }
229
230
231
232  /**
233   * Creates a new intermediate response object with the provided message ID and
234   * with the protocol op and controls read from the given ASN.1 stream reader.
235   *
236   * @param  messageID        The LDAP message ID for the LDAP message that is
237   *                          associated with this intermediate response.
238   * @param  messageSequence  The ASN.1 stream reader sequence used in the
239   *                          course of reading the LDAP message elements.
240   * @param  reader           The ASN.1 stream reader from which to read the
241   *                          protocol op and controls.
242   *
243   * @return  The decoded intermediate response.
244   *
245   * @throws  LDAPException  If a problem occurs while reading or decoding data
246   *                         from the ASN.1 stream reader.
247   */
248  @NotNull()
249  static IntermediateResponse readFrom(final int messageID,
250              @NotNull final ASN1StreamReaderSequence messageSequence,
251              @NotNull final ASN1StreamReader reader)
252         throws LDAPException
253  {
254    try
255    {
256      String oid = null;
257      ASN1OctetString value = null;
258
259      final ASN1StreamReaderSequence opSequence = reader.beginSequence();
260      while (opSequence.hasMoreElements())
261      {
262        final byte type = (byte) reader.peek();
263        switch (type)
264        {
265          case TYPE_INTERMEDIATE_RESPONSE_OID:
266            oid = reader.readString();
267            break;
268          case TYPE_INTERMEDIATE_RESPONSE_VALUE:
269            value = new ASN1OctetString(type, reader.readBytes());
270            break;
271          default:
272            throw new LDAPException(ResultCode.DECODING_ERROR,
273                 ERR_INTERMEDIATE_RESPONSE_INVALID_ELEMENT.get(
274                      StaticUtils.toHex(type)));
275        }
276      }
277
278      final Control[] controls;
279      if (messageSequence.hasMoreElements())
280      {
281        final ArrayList<Control> controlList = new ArrayList<>(1);
282        final ASN1StreamReaderSequence controlSequence = reader.beginSequence();
283        while (controlSequence.hasMoreElements())
284        {
285          controlList.add(Control.readFrom(reader));
286        }
287
288        controls = new Control[controlList.size()];
289        controlList.toArray(controls);
290      }
291      else
292      {
293        controls = NO_CONTROLS;
294      }
295
296      return new IntermediateResponse(messageID, oid, value, controls);
297    }
298    catch (final LDAPException le)
299    {
300      Debug.debugException(le);
301      throw le;
302    }
303    catch (final Exception e)
304    {
305      Debug.debugException(e);
306      throw new LDAPException(ResultCode.DECODING_ERROR,
307           ERR_INTERMEDIATE_RESPONSE_CANNOT_DECODE.get(
308                StaticUtils.getExceptionMessage(e)),
309           e);
310    }
311  }
312
313
314
315  /**
316   * {@inheritDoc}
317   */
318  @Override()
319  public int getMessageID()
320  {
321    return messageID;
322  }
323
324
325
326  /**
327   * Retrieves the OID for this intermediate response, if any.
328   *
329   * @return  The OID for this intermediate response, or {@code null} if there
330   *          is no OID for this response.
331   */
332  @Nullable()
333  public final String getOID()
334  {
335    return oid;
336  }
337
338
339
340  /**
341   * Retrieves the encoded value for this intermediate response, if any.
342   *
343   * @return  The encoded value for this intermediate response, or {@code null}
344   *          if there is no value for this response.
345   */
346  @Nullable()
347  public final ASN1OctetString getValue()
348  {
349    return value;
350  }
351
352
353
354  /**
355   * Retrieves the set of controls returned with this intermediate response.
356   * Individual response controls of a specific type may be retrieved and
357   * decoded using the {@code get} method in the response control class.
358   *
359   * @return  The set of controls returned with this intermediate response.
360   */
361  @NotNull()
362  public final Control[] getControls()
363  {
364    return controls;
365  }
366
367
368
369  /**
370   * Retrieves the control with the specified OID.  If there is more than one
371   * control with the given OID, then the first will be returned.
372   *
373   * @param  oid  The OID of the control to retrieve.
374   *
375   * @return  The control with the requested OID, or {@code null} if there is no
376   *          such control for this intermediate response.
377   */
378  @Nullable()
379  public final Control getControl(@NotNull final String oid)
380  {
381    for (final Control c : controls)
382    {
383      if (c.getOID().equals(oid))
384      {
385        return c;
386      }
387    }
388
389    return null;
390  }
391
392
393
394  /**
395   * Retrieves the user-friendly name for the intermediate response, if
396   * available.  If no user-friendly name has been defined, but a response OID
397   * is available, then that will be returned.  If neither a user-friendly name
398   * nor a response OID are available, then {@code null} will be returned.
399   *
400   * @return  The user-friendly name for this intermediate response, the
401   *          response OID if a user-friendly name is not available but a
402   *          response OID is, or {@code null} if neither a user-friendly name
403   *          nor a response OID are available.
404   */
405  @Nullable()
406  public String getIntermediateResponseName()
407  {
408    // By default, we will return the OID (which may be null).  Subclasses
409    // should override this to provide the user-friendly name.
410    return oid;
411  }
412
413
414
415  /**
416   * Retrieves a human-readable string representation for the contents of the
417   * value for this intermediate response, if appropriate.  If one is provided,
418   * then it should be a relatively compact single-line representation of the
419   * most important elements of the value.
420   *
421   * @return  A human-readable string representation for the contents of the
422   *          value for this intermediate response, or {@code null} if there is
423   *          no value or no string representation is available.
424   */
425  @Nullable()
426  public String valueToString()
427  {
428    return null;
429  }
430
431
432
433  /**
434   * Retrieves a string representation of this intermediate response.
435   *
436   * @return  A string representation of this intermediate response.
437   */
438  @Override()
439  @NotNull()
440  public final String toString()
441  {
442    final StringBuilder buffer = new StringBuilder();
443    toString(buffer);
444    return buffer.toString();
445  }
446
447
448
449  /**
450   * Appends a string representation of this intermediate response to the
451   * provided buffer.
452   *
453   * @param  buffer  The buffer to which the string representation should be
454   *                 appended.
455   */
456  @Override()
457  public void toString(@NotNull final StringBuilder buffer)
458  {
459    buffer.append("IntermediateResponse(");
460
461    boolean added = false;
462
463    if (messageID >= 0)
464    {
465      buffer.append("messageID=");
466      buffer.append(messageID);
467      added = true;
468    }
469
470    if (oid != null)
471    {
472      if (added)
473      {
474        buffer.append(", ");
475      }
476
477      buffer.append("oid='");
478      buffer.append(oid);
479      buffer.append('\'');
480      added = true;
481    }
482
483    if (controls.length > 0)
484    {
485      if (added)
486      {
487        buffer.append(", ");
488      }
489
490      buffer.append("controls={");
491      for (int i=0; i < controls.length; i++)
492      {
493        if (i > 0)
494        {
495          buffer.append(", ");
496        }
497
498        buffer.append(controls[i]);
499      }
500      buffer.append('}');
501    }
502
503    buffer.append(')');
504  }
505}