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