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
092
093  /**
094   * Creates a new LDAP request with the provided set of controls.
095   *
096   * @param  controls  The set of controls to include in this LDAP request.
097   */
098  protected LDAPRequest(final Control[] controls)
099  {
100    if (controls == null)
101    {
102      this.controls = NO_CONTROLS;
103    }
104    else
105    {
106      this.controls = controls;
107    }
108
109    followReferrals = null;
110    responseTimeout = -1L;
111    intermediateResponseListener = null;
112  }
113
114
115
116  /**
117   * Retrieves the set of controls for this request.  The caller must not alter
118   * this set of controls.
119   *
120   * @return  The set of controls for this request.
121   */
122  public final Control[] getControls()
123  {
124    return controls;
125  }
126
127
128
129  /**
130   * {@inheritDoc}
131   */
132  @Override()
133  public final List<Control> getControlList()
134  {
135    return Collections.unmodifiableList(Arrays.asList(controls));
136  }
137
138
139
140  /**
141   * {@inheritDoc}
142   */
143  @Override()
144  public final boolean hasControl()
145  {
146    return (controls.length > 0);
147  }
148
149
150
151  /**
152   * {@inheritDoc}
153   */
154  @Override()
155  public final boolean hasControl(final String oid)
156  {
157    ensureNotNull(oid);
158
159    for (final Control c : controls)
160    {
161      if (c.getOID().equals(oid))
162      {
163        return true;
164      }
165    }
166
167    return false;
168  }
169
170
171
172  /**
173   * {@inheritDoc}
174   */
175  @Override()
176  public final Control getControl(final String oid)
177  {
178    ensureNotNull(oid);
179
180    for (final Control c : controls)
181    {
182      if (c.getOID().equals(oid))
183      {
184        return c;
185      }
186    }
187
188    return null;
189  }
190
191
192
193  /**
194   * Updates the set of controls associated with this request.  This must only
195   * be called by {@link UpdatableLDAPRequest}.
196   *
197   * @param  controls  The set of controls to use for this request.
198   */
199  final void setControlsInternal(final Control[] controls)
200  {
201    this.controls = controls;
202  }
203
204
205
206  /**
207   * {@inheritDoc}
208   */
209  @Override()
210  public final long getResponseTimeoutMillis(final LDAPConnection connection)
211  {
212    if ((responseTimeout < 0L) && (connection != null))
213    {
214      if (this instanceof ExtendedRequest)
215      {
216        final ExtendedRequest extendedRequest = (ExtendedRequest) this;
217        return connection.getConnectionOptions().
218             getExtendedOperationResponseTimeoutMillis(
219                  extendedRequest.getOID());
220      }
221      else
222      {
223        return connection.getConnectionOptions().getResponseTimeoutMillis(
224             getOperationType());
225      }
226    }
227    else
228    {
229      return responseTimeout;
230    }
231  }
232
233
234
235  /**
236   * Specifies the maximum length of time in milliseconds that processing on
237   * this operation should be allowed to block while waiting for a response
238   * from the server.  A value of zero indicates that no timeout should be
239   * enforced.  A value that is less than zero indicates that the default
240   * response timeout for the underlying connection should be used.
241   *
242   * @param  responseTimeout  The maximum length of time in milliseconds that
243   *                          processing on this operation should be allowed to
244   *                          block while waiting for a response from the
245   *                          server.
246   */
247  public final void setResponseTimeoutMillis(final long responseTimeout)
248  {
249    if (responseTimeout < 0L)
250    {
251      this.responseTimeout = -1L;
252    }
253    else
254    {
255      this.responseTimeout = responseTimeout;
256    }
257  }
258
259
260
261  /**
262   * Indicates whether to automatically follow any referrals encountered while
263   * processing this request.  If a value has been set for this request, then it
264   * will be returned.  Otherwise, the default from the connection options for
265   * the provided connection will be used.
266   *
267   * @param  connection  The connection whose connection options may be used in
268   *                     the course of making the determination.  It must not
269   *                     be {@code null}.
270   *
271   * @return  {@code true} if any referrals encountered during processing should
272   *          be automatically followed, or {@code false} if not.
273   */
274  @Override()
275  public final boolean followReferrals(final LDAPConnection connection)
276  {
277    if (followReferrals == null)
278    {
279      return connection.getConnectionOptions().followReferrals();
280    }
281    else
282    {
283      return followReferrals;
284    }
285  }
286
287
288
289  /**
290   * Indicates whether automatic referral following is enabled for this request.
291   *
292   * @return  {@code Boolean.TRUE} if automatic referral following is enabled
293   *          for this request, {@code Boolean.FALSE} if not, or {@code null} if
294   *          a per-request behavior is not specified.
295   */
296  final Boolean followReferralsInternal()
297  {
298    return followReferrals;
299  }
300
301
302
303  /**
304   * Specifies whether to automatically follow any referrals encountered while
305   * processing this request.  This may be used to override the default behavior
306   * defined in the connection options for the connection used to process the
307   * request.
308   *
309   * @param  followReferrals  Indicates whether to automatically follow any
310   *                          referrals encountered while processing this
311   *                          request.  It may be {@code null} to indicate that
312   *                          the determination should be based on the
313   *                          connection options for the connection used to
314   *                          process the request.
315   */
316  public final void setFollowReferrals(final Boolean followReferrals)
317  {
318    this.followReferrals = followReferrals;
319  }
320
321
322
323  /**
324   * Retrieves the intermediate response listener for this request, if any.
325   *
326   * @return  The intermediate response listener for this request, or
327   *          {@code null} if there is none.
328   */
329  public final IntermediateResponseListener getIntermediateResponseListener()
330  {
331    return intermediateResponseListener;
332  }
333
334
335
336  /**
337   * Sets the intermediate response listener for this request.
338   *
339   * @param  listener  The intermediate response listener for this request.  It
340   *                   may be {@code null} to clear any existing listener.
341   */
342  public final void setIntermediateResponseListener(
343                         final IntermediateResponseListener listener)
344  {
345    intermediateResponseListener = listener;
346  }
347
348
349
350  /**
351   * Processes this operation using the provided connection and returns the
352   * result.
353   *
354   * @param  connection  The connection to use to process the request.
355   * @param  depth       The current referral depth for this request.  It should
356   *                     always be one for the initial request, and should only
357   *                     be incremented when following referrals.
358   *
359   * @return  The result of processing this operation.
360   *
361   * @throws  LDAPException  If a problem occurs while processing the request.
362   */
363  @InternalUseOnly()
364  protected abstract LDAPResult process(LDAPConnection connection, int depth)
365            throws LDAPException;
366
367
368
369  /**
370   * Retrieves the message ID for the last LDAP message sent using this request.
371   *
372   * @return  The message ID for the last LDAP message sent using this request,
373   *          or -1 if it no LDAP messages have yet been sent using this
374   *          request.
375   */
376  public abstract int getLastMessageID();
377
378
379
380  /**
381   * Retrieves the type of operation that is represented by this request.
382   *
383   * @return  The type of operation that is represented by this request.
384   */
385  public abstract OperationType getOperationType();
386
387
388
389  /**
390   * {@inheritDoc}
391   */
392  @Override()
393  public String toString()
394  {
395    final StringBuilder buffer = new StringBuilder();
396    toString(buffer);
397    return buffer.toString();
398  }
399
400
401
402  /**
403   * {@inheritDoc}
404   */
405  @Override()
406  public abstract void toString(StringBuilder buffer);
407}