001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-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) 2007-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.util.Arrays;
041import java.util.Collections;
042import java.util.List;
043
044import com.unboundid.util.Extensible;
045import com.unboundid.util.InternalUseOnly;
046import com.unboundid.util.NotNull;
047import com.unboundid.util.Nullable;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.Validator;
051
052
053
054/**
055 * This class provides a framework that should be extended by all types of LDAP
056 * requests.  It provides methods for interacting with the set of controls to
057 * include as part of the request and configuring a response timeout, which is
058 * the maximum length of time that the SDK should wait for a response to the
059 * request before returning an error back to the caller.
060 * <BR><BR>
061 * {@code LDAPRequest} objects are not immutable and should not be considered
062 * threadsafe.  A single {@code LDAPRequest} object instance should not be used
063 * concurrently by multiple threads, but instead each thread wishing to process
064 * a request should have its own instance of that request.  The
065 * {@link #duplicate()} method may be used to create an exact copy of a request
066 * suitable for processing by a separate thread.
067 * <BR><BR>
068 * Note that even though this class is marked with the @Extensible annotation
069 * type, it should not be directly subclassed by third-party code.  Only the
070 * {@link ExtendedRequest} and {@link SASLBindRequest} subclasses are actually
071 * intended to be extended by third-party code.
072 */
073@Extensible()
074@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
075public abstract class LDAPRequest
076       implements ReadOnlyLDAPRequest
077{
078  /**
079   * The set of controls that will be used if none were provided.
080   */
081  @NotNull static final Control[] NO_CONTROLS = new Control[0];
082
083
084
085  /**
086   * The serial version UID for this serializable class.
087   */
088  private static final long serialVersionUID = -2040756188243320117L;
089
090
091
092  // Indicates whether to automatically follow referrals returned while
093  // processing this request.
094  @Nullable private Boolean followReferrals;
095
096  // The set of controls for this request.
097  @NotNull private Control[] controls;
098
099  // The current depth to use when following referrals.
100  private int referralDepth;
101
102  // The intermediate response listener for this request.
103  @Nullable private IntermediateResponseListener intermediateResponseListener;
104
105  // The maximum length of time in milliseconds to wait for the response from
106  // the server.  The default value of -1 indicates that it should be inherited
107  // from the associated connection.
108  private long responseTimeout;
109
110  // The referral connector to use when following referrals.
111  @Nullable private ReferralConnector referralConnector;
112
113
114
115  /**
116   * Creates a new LDAP request with the provided set of controls.
117   *
118   * @param  controls  The set of controls to include in this LDAP request.
119   */
120  protected LDAPRequest(@Nullable final Control[] controls)
121  {
122    if (controls == null)
123    {
124      this.controls = NO_CONTROLS;
125    }
126    else
127    {
128      this.controls = controls;
129    }
130
131    followReferrals = null;
132    referralDepth = 1;
133    responseTimeout = -1L;
134    intermediateResponseListener = null;
135    referralConnector = null;
136  }
137
138
139
140  /**
141   * {@inheritDoc}
142   */
143  @Override()
144  @NotNull()
145  public final Control[] getControls()
146  {
147    return controls;
148  }
149
150
151
152  /**
153   * {@inheritDoc}
154   */
155  @Override()
156  @NotNull()
157  public final List<Control> getControlList()
158  {
159    return Collections.unmodifiableList(Arrays.asList(controls));
160  }
161
162
163
164  /**
165   * {@inheritDoc}
166   */
167  @Override()
168  public final boolean hasControl()
169  {
170    return (controls.length > 0);
171  }
172
173
174
175  /**
176   * {@inheritDoc}
177   */
178  @Override()
179  public final boolean hasControl(@NotNull final String oid)
180  {
181    Validator.ensureNotNull(oid);
182
183    for (final Control c : controls)
184    {
185      if (c.getOID().equals(oid))
186      {
187        return true;
188      }
189    }
190
191    return false;
192  }
193
194
195
196  /**
197   * {@inheritDoc}
198   */
199  @Override()
200  @Nullable()
201  public final Control getControl(@NotNull final String oid)
202  {
203    Validator.ensureNotNull(oid);
204
205    for (final Control c : controls)
206    {
207      if (c.getOID().equals(oid))
208      {
209        return c;
210      }
211    }
212
213    return null;
214  }
215
216
217
218  /**
219   * Updates the set of controls associated with this request.  This must only
220   * be called by {@link UpdatableLDAPRequest}.
221   *
222   * @param  controls  The set of controls to use for this request.
223   */
224  final void setControlsInternal(@NotNull final Control[] controls)
225  {
226    this.controls = controls;
227  }
228
229
230
231  /**
232   * {@inheritDoc}
233   */
234  @Override()
235  public final long getResponseTimeoutMillis(
236                         @Nullable final LDAPConnection connection)
237  {
238    if ((responseTimeout < 0L) && (connection != null))
239    {
240      if (this instanceof ExtendedRequest)
241      {
242        final ExtendedRequest extendedRequest = (ExtendedRequest) this;
243        return connection.getConnectionOptions().
244             getExtendedOperationResponseTimeoutMillis(
245                  extendedRequest.getOID());
246      }
247      else
248      {
249        return connection.getConnectionOptions().getResponseTimeoutMillis(
250             getOperationType());
251      }
252    }
253    else
254    {
255      return responseTimeout;
256    }
257  }
258
259
260
261  /**
262   * Specifies the maximum length of time in milliseconds that processing on
263   * this operation should be allowed to block while waiting for a response
264   * from the server.  A value of zero indicates that no timeout should be
265   * enforced.  A value that is less than zero indicates that the default
266   * response timeout for the underlying connection should be used.
267   *
268   * @param  responseTimeout  The maximum length of time in milliseconds that
269   *                          processing on this operation should be allowed to
270   *                          block while waiting for a response from the
271   *                          server.
272   */
273  public final void setResponseTimeoutMillis(final long responseTimeout)
274  {
275    if (responseTimeout < 0L)
276    {
277      this.responseTimeout = -1L;
278    }
279    else
280    {
281      this.responseTimeout = responseTimeout;
282    }
283  }
284
285
286
287  /**
288   * Indicates whether to automatically follow any referrals encountered while
289   * processing this request.  If a value has been set for this request, then it
290   * will be returned.  Otherwise, the default from the connection options for
291   * the provided connection will be used.
292   *
293   * @param  connection  The connection whose connection options may be used in
294   *                     the course of making the determination.  It must not
295   *                     be {@code null}.
296   *
297   * @return  {@code true} if any referrals encountered during processing should
298   *          be automatically followed, or {@code false} if not.
299   */
300  @Override()
301  public final boolean followReferrals(@NotNull final LDAPConnection connection)
302  {
303    if (followReferrals == null)
304    {
305      return connection.getConnectionOptions().followReferrals();
306    }
307    else
308    {
309      return followReferrals;
310    }
311  }
312
313
314
315  /**
316   * Indicates whether automatic referral following is enabled for this request.
317   *
318   * @return  {@code Boolean.TRUE} if automatic referral following is enabled
319   *          for this request, {@code Boolean.FALSE} if not, or {@code null} if
320   *          a per-request behavior is not specified.
321   */
322  @Nullable()
323  final Boolean followReferralsInternal()
324  {
325    return followReferrals;
326  }
327
328
329
330  /**
331   * Specifies whether to automatically follow any referrals encountered while
332   * processing this request.  This may be used to override the default behavior
333   * defined in the connection options for the connection used to process the
334   * request.
335   *
336   * @param  followReferrals  Indicates whether to automatically follow any
337   *                          referrals encountered while processing this
338   *                          request.  It may be {@code null} to indicate that
339   *                          the determination should be based on the
340   *                          connection options for the connection used to
341   *                          process the request.
342   */
343  public final void setFollowReferrals(@Nullable final Boolean followReferrals)
344  {
345    this.followReferrals = followReferrals;
346  }
347
348
349
350  /**
351   * Retrieves the current depth to use when following referrals.
352   *
353   * @return  The current depth to use when following referrals.
354   */
355  protected int getReferralDepth()
356  {
357    return referralDepth;
358  }
359
360
361
362  /**
363   * Sets the current depth to use when following referrals.
364   *
365   * @param  referralDepth  The current depth to use when following referrals.
366   */
367  protected void setReferralDepth(final int referralDepth)
368  {
369    this.referralDepth = referralDepth;
370  }
371
372
373
374  /**
375   * {@inheritDoc}
376   */
377  @Override()
378  @NotNull()
379  public final ReferralConnector getReferralConnector(
380                                      @NotNull final LDAPConnection connection)
381  {
382    if (referralConnector == null)
383    {
384      return connection.getReferralConnector();
385    }
386    else
387    {
388      return referralConnector;
389    }
390  }
391
392
393
394  /**
395   * Retrieves the referral connector that has been set for this request.
396   *
397   * @return  The referral connector that has been set for this request, or
398   *          {@code null} if no referral connector has been set for this
399   *          request and the connection's default referral connector will be
400   *          used if necessary.
401   */
402  @Nullable()
403  protected final ReferralConnector getReferralConnectorInternal()
404  {
405    return referralConnector;
406  }
407
408
409
410  /**
411   * Sets the referral connector that should be used to establish connections
412   * for the purpose of following any referrals encountered when processing this
413   * request.
414   *
415   * @param  referralConnector  The referral connector that should be used to
416   *                            establish connections for the purpose of
417   *                            following any referral encountered when
418   *                            processing this request.  It may be
419   *                            {@code null} to use the default referral handler
420   *                            for the connection on which the referral was
421   *                            received.
422   */
423  public final void setReferralConnector(
424                         @Nullable final ReferralConnector referralConnector)
425  {
426    this.referralConnector = referralConnector;
427  }
428
429
430
431  /**
432   * Retrieves the intermediate response listener for this request, if any.
433   *
434   * @return  The intermediate response listener for this request, or
435   *          {@code null} if there is none.
436   */
437  @Nullable()
438  public final IntermediateResponseListener getIntermediateResponseListener()
439  {
440    return intermediateResponseListener;
441  }
442
443
444
445  /**
446   * Sets the intermediate response listener for this request.
447   *
448   * @param  listener  The intermediate response listener for this request.  It
449   *                   may be {@code null} to clear any existing listener.
450   */
451  public final void setIntermediateResponseListener(
452                         @Nullable final IntermediateResponseListener listener)
453  {
454    intermediateResponseListener = listener;
455  }
456
457
458
459  /**
460   * Processes this operation using the provided connection and returns the
461   * result.
462   *
463   * @param  connection  The connection to use to process the request.
464   * @param  depth       The current referral depth for this request.  It should
465   *                     always be one for the initial request, and should only
466   *                     be incremented when following referrals.
467   *
468   * @return  The result of processing this operation.
469   *
470   * @throws  LDAPException  If a problem occurs while processing the request.
471   */
472  @InternalUseOnly()
473  @NotNull()
474  protected abstract LDAPResult process(@NotNull LDAPConnection connection,
475                                        int depth)
476            throws LDAPException;
477
478
479
480  /**
481   * Retrieves the message ID for the last LDAP message sent using this request.
482   *
483   * @return  The message ID for the last LDAP message sent using this request,
484   *          or -1 if it no LDAP messages have yet been sent using this
485   *          request.
486   */
487  public abstract int getLastMessageID();
488
489
490
491  /**
492   * Retrieves the type of operation that is represented by this request.
493   *
494   * @return  The type of operation that is represented by this request.
495   */
496  @NotNull()
497  public abstract OperationType getOperationType();
498
499
500
501  /**
502   * {@inheritDoc}
503   */
504  @Override()
505  @NotNull()
506  public String toString()
507  {
508    final StringBuilder buffer = new StringBuilder();
509    toString(buffer);
510    return buffer.toString();
511  }
512
513
514
515  /**
516   * {@inheritDoc}
517   */
518  @Override()
519  public abstract void toString(@NotNull StringBuilder buffer);
520}