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