001    /*
002     * Copyright 2007-2012 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2012 UnboundID Corp.
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     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.util.Collection;
026    import java.util.List;
027    import java.util.Timer;
028    import java.util.concurrent.atomic.AtomicLong;
029    import java.util.logging.Level;
030    import javax.net.SocketFactory;
031    import javax.net.ssl.SSLContext;
032    import javax.net.ssl.SSLSocketFactory;
033    
034    import com.unboundid.asn1.ASN1OctetString;
035    import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
036    import com.unboundid.ldap.protocol.LDAPMessage;
037    import com.unboundid.ldap.protocol.LDAPResponse;
038    import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
039    import com.unboundid.ldap.sdk.schema.Schema;
040    import com.unboundid.ldif.LDIFException;
041    import com.unboundid.util.DebugType;
042    import com.unboundid.util.SynchronizedSocketFactory;
043    import com.unboundid.util.SynchronizedSSLSocketFactory;
044    import com.unboundid.util.ThreadSafety;
045    import com.unboundid.util.ThreadSafetyLevel;
046    import com.unboundid.util.WeakHashSet;
047    
048    import static com.unboundid.ldap.sdk.LDAPMessages.*;
049    import static com.unboundid.util.Debug.*;
050    import static com.unboundid.util.StaticUtils.*;
051    import static com.unboundid.util.Validator.*;
052    
053    
054    
055    /**
056     * This class provides a facility for interacting with an LDAPv3 directory
057     * server.  It provides a means of establishing a connection to the server,
058     * sending requests, and reading responses.  See
059     * <A HREF="http://www.ietf.org/rfc/rfc4511.txt">RFC 4511</A> for the LDAPv3
060     * protocol specification and more information about the types of operations
061     * defined in LDAP.
062     * <BR><BR>
063     * <H2>Creating, Establishing, and Authenticating Connections</H2>
064     * An LDAP connection can be established either at the time that the object is
065     * created or as a separate step.  Similarly, authentication can be performed on
066     * the connection at the time it is created, at the time it is established, or
067     * as a separate process.  For example:
068     * <BR><BR>
069     * <PRE>
070     *   // Create a new, unestablished connection.  Then connect and perform a
071     *   // simple bind as separate operations.
072     *   LDAPConnection c = new LDAPConnection();
073     *   c.connect(address, port);
074     *   BindResult bindResult = c.bind(bindDN, password);
075     *
076     *   // Create a new connection that is established at creation time, and then
077     *   // authenticate separately using simple authentication.
078     *   LDAPConnection c = new LDAPConnection(address, port);
079     *   BindResult bindResult = c.bind(bindDN, password);
080     *
081     *   // Create a new connection that is established and bound using simple
082     *   // authentication all in one step.
083     *   LDAPConnection c = new LDAPConnection(address, port, bindDN, password);
084     * </PRE>
085     * <BR><BR>
086     * When authentication is performed at the time that the connection is
087     * established, it is only possible to perform a simple bind and it is not
088     * possible to include controls in the bind request, nor is it possible to
089     * receive response controls if the bind was successful.  Therefore, it is
090     * recommended that authentication be performed as a separate step if the server
091     * may return response controls even in the event of a successful authentication
092     * (e.g., a control that may indicate that the user's password will soon
093     * expire).  See the {@link BindRequest} class for more information about
094     * authentication in the UnboundID LDAP SDK for Java.
095     * <BR><BR>
096     * By default, connections will use standard unencrypted network sockets.
097     * However, it may be desirable to create connections that use SSL/TLS to
098     * encrypt communication.  This can be done by specifying a
099     * {@link javax.net.SocketFactory} that should be used to create the socket to
100     * use to communicate with the directory server.  The
101     * {@link javax.net.ssl.SSLSocketFactory#getDefault} method or the
102     * {@link javax.net.ssl.SSLContext#getSocketFactory} method may be used to
103     * obtain a socket factory for performing SSL communication.  See the
104     * <A HREF=
105     * "http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html">
106     * JSSE Reference Guide</A> for more information on using these classes.
107     * Alternately, you may use the {@link com.unboundid.util.ssl.SSLUtil} class to
108     * simplify the process.
109     * <BR><BR>
110     * Whenever the connection is no longer needed, it may be terminated using the
111     * {@link LDAPConnection#close} method.
112     * <BR><BR>
113     * <H2>Processing LDAP Operations</H2>
114     * This class provides a number of methods for processing the different types of
115     * operations.  The types of operations that can be processed include:
116     * <UL>
117     *   <LI>Abandon -- This may be used to request that the server stop processing
118     *      on an operation that has been invoked asynchronously.</LI>
119     *   <LI>Add -- This may be used to add a new entry to the directory
120     *       server.  See the {@link AddRequest} class for more information about
121     *       processing add operations.</LI>
122     *   <LI>Bind -- This may be used to authenticate to the directory server.  See
123     *       the {@link BindRequest} class for more information about processing
124     *       bind operations.</LI>
125     *   <LI>Compare -- This may be used to determine whether a specified entry has
126     *       a given attribute value.  See the {@link CompareRequest} class for more
127     *       information about processing compare operations.</LI>
128     *   <LI>Delete -- This may be used to remove an entry from the directory
129     *       server.  See the {@link DeleteRequest} class for more information about
130     *       processing delete operations.</LI>
131     *   <LI>Extended -- This may be used to process an operation which is not
132     *       part of the core LDAP protocol but is a custom extension supported by
133     *       the directory server.  See the {@link ExtendedRequest} class for more
134     *       information about processing extended operations.</LI>
135     *   <LI>Modify -- This may be used to alter an entry in the directory
136     *       server.  See the {@link ModifyRequest} class for more information about
137     *       processing modify operations.</LI>
138     *   <LI>Modify DN -- This may be used to rename an entry or subtree and/or move
139     *       that entry or subtree below a new parent in the directory server.  See
140     *       the {@link ModifyDNRequest} class for more information about processing
141     *       modify DN operations.</LI>
142     *   <LI>Search -- This may be used to retrieve a set of entries in the server
143     *       that match a given set of criteria.  See the {@link SearchRequest}
144     *       class for more information about processing search operations.</LI>
145     * </UL>
146     * <BR><BR>
147     * Most of the methods in this class used to process operations operate in a
148     * synchronous manner.  In these cases, the SDK will send a request to the
149     * server and wait for a response to arrive before returning to the caller.  In
150     * these cases, the value returned will include the contents of that response,
151     * including the result code, diagnostic message, matched DN, referral URLs, and
152     * any controls that may have been included.  However, it also possible to
153     * process operations asynchronously, in which case the SDK will return control
154     * back to the caller after the request has been sent to the server but before
155     * the response has been received.  In this case, the SDK will return an
156     * {@link AsyncRequestID} object which may be used to later abandon or cancel
157     * that operation if necessary, and will notify the client when the response
158     * arrives via a listener interface.
159     * <BR><BR>
160     * This class is mostly threadsafe.  It is possible to process multiple
161     * concurrent operations over the same connection as long as the methods being
162     * invoked will not change the state of the connection in a way that might
163     * impact other operations in progress in unexpected ways.  In particular, the
164     * following should not be attempted while any other operations may be in
165     * progress on this connection:
166     * <UL>
167     *   <LI>
168     *     Using one of the {@code connect} methods to re-establish the connection.
169     *   </LI>
170     *   <LI>
171     *     Using one of the {@code close} methods to terminate the connection.
172     *   </LI>
173     *   <LI>
174     *     Using one of the {@code bind} methods to attempt to authenticate the
175     *     connection (unless you are certain that the bind will not impact the
176     *     identity of the associated connection, for example by including the
177     *     retain identity request control in the bind request if using the
178     *     Commercial Edition of the LDAP SDK in conjunction with an UnboundID
179     *     Directory Server).
180     *   </LI>
181     *   <LI>
182     *     Attempting to make a change to the way that the underlying communication
183     *     is processed (e.g., by using the StartTLS extended operation to convert
184     *     an insecure connection into a secure one).
185     *   </LI>
186     * </UL>
187     */
188    @ThreadSafety(level=ThreadSafetyLevel.MOSTLY_THREADSAFE)
189    public final class LDAPConnection
190           implements LDAPInterface, ReferralConnector
191    {
192      /**
193       * The counter that will be used when assigning connection IDs to connections.
194       */
195      private static final AtomicLong NEXT_CONNECTION_ID = new AtomicLong(0L);
196    
197    
198    
199      /**
200       * The default socket factory that will be used if no alternate factory is
201       * provided.
202       */
203      private static final SocketFactory DEFAULT_SOCKET_FACTORY =
204                                              SocketFactory.getDefault();
205    
206    
207    
208      /**
209       * A set of weak references to schema objects that can be shared across
210       * connections if they are identical.
211       */
212      private static final WeakHashSet<Schema> SCHEMA_SET =
213           new WeakHashSet<Schema>();
214    
215    
216    
217      // The connection pool with which this connection is associated, if
218      // applicable.
219      private AbstractConnectionPool connectionPool;
220    
221      // The last successful bind request processed on this connection.
222      private BindRequest lastBindRequest;
223    
224      // Indicates whether a request has been made to close this connection.
225      private volatile boolean closeRequested;
226    
227      // Indicates whether an unbind request has been sent over this connection.
228      private volatile boolean unbindRequestSent;
229    
230      // The disconnect type that explains the reason that this connection was
231      // disconnected, if applicable.
232      private volatile DisconnectType disconnectType;
233    
234      // The port of the server to which a connection should be re-established.
235      private int reconnectPort = -1;
236    
237      // The connection internals used to actually perform the network
238      // communication.
239      private volatile LDAPConnectionInternals connectionInternals;
240    
241      // The set of connection options for this connection.
242      private LDAPConnectionOptions connectionOptions;
243    
244      // The set of statistics for this connection.
245      private final LDAPConnectionStatistics connectionStatistics;
246    
247      // The unique identifier assigned to this connection when it was created.  It
248      // will not change over the life of the connection, even if the connection is
249      // closed and re-established (or even re-established to a different server).
250      private final long connectionID;
251    
252      // The time of the last rebind attempt.
253      private long lastReconnectTime;
254    
255      // The referral connector that will be used to establish connections to remote
256      // servers when following a referral.
257      private ReferralConnector referralConnector;
258    
259      // The cached schema read from the server.
260      private volatile Schema cachedSchema;
261    
262      // The socket factory used for the last connection attempt.
263      private SocketFactory lastUsedSocketFactory;
264    
265      // The socket factory used to create sockets for subsequent connection
266      // attempts.
267      private SocketFactory socketFactory;
268    
269      // A stack trace of the thread that last established this connection.
270      private StackTraceElement[] connectStackTrace;
271    
272      // The user-friendly name assigned to this connection.
273      private String connectionName;
274    
275      // The user-friendly name assigned to the connection pool with which this
276      // connection is associated.
277      private String connectionPoolName;
278    
279      // A string representation of the host and port to which the last connection
280      // attempt (whether successful or not, and whether it is still established)
281      // was made.
282      private String hostPort;
283    
284      // The address of the server to which a connection should be re-established.
285      private String reconnectAddress;
286    
287      // The disconnect message for this client connection, if available.
288      private volatile String disconnectMessage;
289    
290      // The disconnect cause for this client connection, if available.
291      private volatile Throwable disconnectCause;
292    
293      // A timer that may be used to enforce timeouts for asynchronous operations.
294      private Timer timer;
295    
296    
297    
298      /**
299       * Creates a new LDAP connection using the default socket factory and default
300       * set of connection options.  No actual network connection will be
301       * established.
302       */
303      public LDAPConnection()
304      {
305        this(null, null);
306      }
307    
308    
309    
310      /**
311       * Creates a new LDAP connection using the default socket factory and provided
312       * set of connection options.  No actual network connection will be
313       * established.
314       *
315       * @param  connectionOptions  The set of connection options to use for this
316       *                            connection.  If it is {@code null}, then a
317       *                            default set of options will be used.
318       */
319      public LDAPConnection(final LDAPConnectionOptions connectionOptions)
320      {
321        this(null, connectionOptions);
322      }
323    
324    
325    
326      /**
327       * Creates a new LDAP connection using the specified socket factory.  No
328       * actual network connection will be established.
329       *
330       * @param  socketFactory  The socket factory to use when establishing
331       *                        connections.  If it is {@code null}, then a default
332       *                        socket factory will be used.
333       */
334      public LDAPConnection(final SocketFactory socketFactory)
335      {
336        this(socketFactory, null);
337      }
338    
339    
340    
341      /**
342       * Creates a new LDAP connection using the specified socket factory.  No
343       * actual network connection will be established.
344       *
345       * @param  socketFactory      The socket factory to use when establishing
346       *                            connections.  If it is {@code null}, then a
347       *                            default socket factory will be used.
348       * @param  connectionOptions  The set of connection options to use for this
349       *                            connection.  If it is {@code null}, then a
350       *                            default set of options will be used.
351       */
352      public LDAPConnection(final SocketFactory socketFactory,
353                            final LDAPConnectionOptions connectionOptions)
354      {
355        connectionID = NEXT_CONNECTION_ID.getAndIncrement();
356    
357        if (connectionOptions == null)
358        {
359          this.connectionOptions = new LDAPConnectionOptions();
360        }
361        else
362        {
363          this.connectionOptions = connectionOptions.duplicate();
364        }
365    
366        final SocketFactory f;
367        if (socketFactory == null)
368        {
369          f = DEFAULT_SOCKET_FACTORY;
370        }
371        else
372        {
373          f = socketFactory;
374        }
375    
376        if (this.connectionOptions.allowConcurrentSocketFactoryUse())
377        {
378          this.socketFactory = f;
379        }
380        else
381        {
382          if (f instanceof SSLSocketFactory)
383          {
384            this.socketFactory =
385                 new SynchronizedSSLSocketFactory((SSLSocketFactory) f);
386          }
387          else
388          {
389            this.socketFactory = new SynchronizedSocketFactory(f);
390          }
391        }
392    
393        connectionStatistics = new LDAPConnectionStatistics();
394        connectionName       = null;
395        connectionPoolName   = null;
396        cachedSchema         = null;
397        timer                = null;
398    
399        referralConnector = this.connectionOptions.getReferralConnector();
400        if (referralConnector == null)
401        {
402          referralConnector = this;
403        }
404      }
405    
406    
407    
408      /**
409       * Creates a new, unauthenticated LDAP connection that is established to the
410       * specified server.
411       *
412       * @param  host  The address of the server to which the connection should be
413       *               established.  It must not be {@code null}.
414       * @param  port  The port number of the server to which the connection should
415       *               be established.  It should be a value between 1 and 65535,
416       *               inclusive.
417       *
418       * @throws  LDAPException  If a problem occurs while attempting to connect to
419       *                         the specified server.
420       */
421      public LDAPConnection(final String host, final int port)
422             throws LDAPException
423      {
424        this(null, null, host, port);
425      }
426    
427    
428    
429      /**
430       * Creates a new, unauthenticated LDAP connection that is established to the
431       * specified server.
432       *
433       * @param  connectionOptions  The set of connection options to use for this
434       *                            connection.  If it is {@code null}, then a
435       *                            default set of options will be used.
436       * @param  host               The address of the server to which the
437       *                            connection should be established.  It must not
438       *                            be {@code null}.
439       * @param  port               The port number of the server to which the
440       *                            connection should be established.  It should be
441       *                            a value between 1 and 65535, inclusive.
442       *
443       * @throws  LDAPException  If a problem occurs while attempting to connect to
444       *                         the specified server.
445       */
446      public LDAPConnection(final LDAPConnectionOptions connectionOptions,
447                            final String host, final int port)
448             throws LDAPException
449      {
450        this(null, connectionOptions, host, port);
451      }
452    
453    
454    
455      /**
456       * Creates a new, unauthenticated LDAP connection that is established to the
457       * specified server.
458       *
459       * @param  socketFactory  The socket factory to use when establishing
460       *                        connections.  If it is {@code null}, then a default
461       *                        socket factory will be used.
462       * @param  host           The address of the server to which the connection
463       *                        should be established.  It must not be {@code null}.
464       * @param  port           The port number of the server to which the
465       *                        connection should be established.  It should be a
466       *                        value between 1 and 65535, inclusive.
467       *
468       * @throws  LDAPException  If a problem occurs while attempting to connect to
469       *                         the specified server.
470       */
471      public LDAPConnection(final SocketFactory socketFactory, final String host,
472                            final int port)
473             throws LDAPException
474      {
475        this(socketFactory, null, host, port);
476      }
477    
478    
479    
480      /**
481       * Creates a new, unauthenticated LDAP connection that is established to the
482       * specified server.
483       *
484       * @param  socketFactory      The socket factory to use when establishing
485       *                            connections.  If it is {@code null}, then a
486       *                            default socket factory will be used.
487       * @param  connectionOptions  The set of connection options to use for this
488       *                            connection.  If it is {@code null}, then a
489       *                            default set of options will be used.
490       * @param  host               The address of the server to which the
491       *                            connection should be established.  It must not
492       *                            be {@code null}.
493       * @param  port               The port number of the server to which the
494       *                            connection should be established.  It should be
495       *                            a value between 1 and 65535, inclusive.
496       *
497       * @throws  LDAPException  If a problem occurs while attempting to connect to
498       *                         the specified server.
499       */
500      public LDAPConnection(final SocketFactory socketFactory,
501                            final LDAPConnectionOptions connectionOptions,
502                            final String host, final int port)
503             throws LDAPException
504      {
505        this(socketFactory, connectionOptions);
506    
507        connect(host, port);
508      }
509    
510    
511    
512      /**
513       * Creates a new LDAP connection that is established to the specified server
514       * and is authenticated as the specified user (via LDAP simple
515       * authentication).
516       *
517       * @param  host          The address of the server to which the connection
518       *                       should be established.  It must not be {@code null}.
519       * @param  port          The port number of the server to which the
520       *                       connection should be established.  It should be a
521       *                       value between 1 and 65535, inclusive.
522       * @param  bindDN        The DN to use to authenticate to the directory
523       *                       server.
524       * @param  bindPassword  The password to use to authenticate to the directory
525       *                       server.
526       *
527       * @throws  LDAPException  If a problem occurs while attempting to connect to
528       *                         the specified server.
529       */
530      public LDAPConnection(final String host, final int port, final String bindDN,
531                            final String bindPassword)
532             throws LDAPException
533      {
534        this(null, null, host, port, bindDN, bindPassword);
535      }
536    
537    
538    
539      /**
540       * Creates a new LDAP connection that is established to the specified server
541       * and is authenticated as the specified user (via LDAP simple
542       * authentication).
543       *
544       * @param  connectionOptions  The set of connection options to use for this
545       *                            connection.  If it is {@code null}, then a
546       *                            default set of options will be used.
547       * @param  host               The address of the server to which the
548       *                            connection should be established.  It must not
549       *                            be {@code null}.
550       * @param  port               The port number of the server to which the
551       *                            connection should be established.  It should be
552       *                            a value between 1 and 65535, inclusive.
553       * @param  bindDN             The DN to use to authenticate to the directory
554       *                            server.
555       * @param  bindPassword       The password to use to authenticate to the
556       *                            directory server.
557       *
558       * @throws  LDAPException  If a problem occurs while attempting to connect to
559       *                         the specified server.
560       */
561      public LDAPConnection(final LDAPConnectionOptions connectionOptions,
562                            final String host, final int port, final String bindDN,
563                            final String bindPassword)
564             throws LDAPException
565      {
566        this(null, connectionOptions, host, port, bindDN, bindPassword);
567      }
568    
569    
570    
571      /**
572       * Creates a new LDAP connection that is established to the specified server
573       * and is authenticated as the specified user (via LDAP simple
574       * authentication).
575       *
576       * @param  socketFactory  The socket factory to use when establishing
577       *                        connections.  If it is {@code null}, then a default
578       *                        socket factory will be used.
579       * @param  host           The address of the server to which the connection
580       *                        should be established.  It must not be {@code null}.
581       * @param  port           The port number of the server to which the
582       *                        connection should be established.  It should be a
583       *                        value between 1 and 65535, inclusive.
584       * @param  bindDN         The DN to use to authenticate to the directory
585       *                        server.
586       * @param  bindPassword   The password to use to authenticate to the directory
587       *                        server.
588       *
589       * @throws  LDAPException  If a problem occurs while attempting to connect to
590       *                         the specified server.
591       */
592      public LDAPConnection(final SocketFactory socketFactory, final String host,
593                            final int port, final String bindDN,
594                            final String bindPassword)
595             throws LDAPException
596      {
597        this(socketFactory, null, host, port, bindDN, bindPassword);
598      }
599    
600    
601    
602      /**
603       * Creates a new LDAP connection that is established to the specified server
604       * and is authenticated as the specified user (via LDAP simple
605       * authentication).
606       *
607       * @param  socketFactory      The socket factory to use when establishing
608       *                            connections.  If it is {@code null}, then a
609       *                            default socket factory will be used.
610       * @param  connectionOptions  The set of connection options to use for this
611       *                            connection.  If it is {@code null}, then a
612       *                            default set of options will be used.
613       * @param  host               The address of the server to which the
614       *                            connection should be established.  It must not
615       *                            be {@code null}.
616       * @param  port               The port number of the server to which the
617       *                            connection should be established.  It should be
618       *                            a value between 1 and 65535, inclusive.
619       * @param  bindDN             The DN to use to authenticate to the directory
620       *                            server.
621       * @param  bindPassword       The password to use to authenticate to the
622       *                            directory server.
623       *
624       * @throws  LDAPException  If a problem occurs while attempting to connect to
625       *                         the specified server.
626       */
627      public LDAPConnection(final SocketFactory socketFactory,
628                            final LDAPConnectionOptions connectionOptions,
629                            final String host, final int port, final String bindDN,
630                            final String bindPassword)
631             throws LDAPException
632      {
633        this(socketFactory, connectionOptions, host, port);
634    
635        try
636        {
637          bind(new SimpleBindRequest(bindDN, bindPassword));
638        }
639        catch (LDAPException le)
640        {
641          debugException(le);
642          setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
643          close();
644          throw le;
645        }
646      }
647    
648    
649    
650      /**
651       * Establishes an unauthenticated connection to the directory server using the
652       * provided information.  If the connection is already established, then it
653       * will be closed and re-established.
654       * <BR><BR>
655       * If this method is invoked while any operations are in progress on this
656       * connection, then the directory server may or may not abort processing for
657       * those operations, depending on the type of operation and how far along the
658       * server has already gotten while processing that operation.  It is
659       * recommended that all active operations be abandoned, canceled, or allowed
660       * to complete before attempting to re-establish an active connection.
661       *
662       * @param  host  The address of the server to which the connection should be
663       *               established.  It must not be {@code null}.
664       * @param  port  The port number of the server to which the connection should
665       *               be established.  It should be a value between 1 and 65535,
666       *               inclusive.
667       *
668       * @throws  LDAPException  If an error occurs while attempting to establish
669       *                         the connection.
670       */
671      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
672      public void connect(final String host, final int port)
673             throws LDAPException
674      {
675        connect(host, port, connectionOptions.getConnectTimeoutMillis());
676      }
677    
678    
679    
680      /**
681       * Establishes an unauthenticated connection to the directory server using the
682       * provided information.  If the connection is already established, then it
683       * will be closed and re-established.
684       * <BR><BR>
685       * If this method is invoked while any operations are in progress on this
686       * connection, then the directory server may or may not abort processing for
687       * those operations, depending on the type of operation and how far along the
688       * server has already gotten while processing that operation.  It is
689       * recommended that all active operations be abandoned, canceled, or allowed
690       * to complete before attempting to re-establish an active connection.
691       *
692       * @param  host     The address of the server to which the connection should
693       *                  be established.  It must not be {@code null}.
694       * @param  port     The port number of the server to which the connection
695       *                  should be established.  It should be a value between 1 and
696       *                  65535, inclusive.
697       * @param  timeout  The maximum length of time in milliseconds to wait for the
698       *                  connection to be established before failing, or zero to
699       *                  indicate that no timeout should be enforced (although if
700       *                  the attempt stalls long enough, then the underlying
701       *                  operating system may cause it to timeout).
702       *
703       * @throws  LDAPException  If an error occurs while attempting to establish
704       *                         the connection.
705       */
706      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
707      public void connect(final String host, final int port, final int timeout)
708             throws LDAPException
709      {
710        ensureNotNull(host, port);
711    
712        hostPort = host + ':' + port;
713    
714        if (isConnected())
715        {
716          setDisconnectInfo(DisconnectType.RECONNECT, null, null);
717          close();
718        }
719    
720        lastUsedSocketFactory = socketFactory;
721        disconnectType        = null;
722        disconnectMessage     = null;
723        disconnectCause       = null;
724        reconnectAddress      = host;
725        reconnectPort         = port;
726        cachedSchema          = null;
727        unbindRequestSent     = false;
728    
729        try
730        {
731          connectionStatistics.incrementNumConnects();
732          connectionInternals = new LDAPConnectionInternals(this, connectionOptions,
733               lastUsedSocketFactory, host, port, timeout);
734          connectionInternals.startConnectionReader();
735        }
736        catch (Exception e)
737        {
738          debugException(e);
739          setDisconnectInfo(DisconnectType.LOCAL_ERROR, null, e);
740          connectionInternals = null;
741          throw new LDAPException(ResultCode.CONNECT_ERROR,
742               ERR_CONN_CONNECT_ERROR.get(getHostPort(), getExceptionMessage(e)),
743               e);
744        }
745    
746        if (connectionOptions.useSchema())
747        {
748          try
749          {
750            cachedSchema = getCachedSchema(this);
751          }
752          catch (Exception e)
753          {
754            debugException(e);
755          }
756        }
757      }
758    
759    
760    
761      /**
762       * Attempts to re-establish a connection to the server and re-authenticate if
763       * appropriate.
764       *
765       * @throws  LDAPException  If a problem occurs while attempting to re-connect
766       *                         or re-authenticate.
767       */
768      public void reconnect()
769             throws LDAPException
770      {
771        if ((System.currentTimeMillis() - lastReconnectTime) < 1000L)
772        {
773          // If the last reconnect attempt was less than 1 second ago, then abort.
774          throw new LDAPException(ResultCode.SERVER_DOWN,
775                                  ERR_CONN_MULTIPLE_FAILURES.get());
776        }
777    
778        BindRequest bindRequest = null;
779        if (lastBindRequest != null)
780        {
781          bindRequest = lastBindRequest.getRebindRequest(reconnectAddress,
782                                                         reconnectPort);
783          if (bindRequest == null)
784          {
785            throw new LDAPException(ResultCode.SERVER_DOWN,
786                 ERR_CONN_CANNOT_REAUTHENTICATE.get(getHostPort()));
787          }
788        }
789    
790        setDisconnectInfo(DisconnectType.RECONNECT, null, null);
791        terminate(null);
792    
793        try
794        {
795          Thread.sleep(10);
796        } catch (final Exception e) {}
797    
798        connect(reconnectAddress, reconnectPort);
799    
800        if (bindRequest != null)
801        {
802          try
803          {
804            bind(bindRequest);
805          }
806          catch (LDAPException le)
807          {
808            debugException(le);
809            setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
810            terminate(null);
811    
812            throw le;
813          }
814        }
815    
816        lastReconnectTime = System.currentTimeMillis();
817      }
818    
819    
820    
821      /**
822       * Indicates whether this connection is currently established.
823       *
824       * @return  {@code true} if this connection is currently established, or
825       *          {@code false} if it is not.
826       */
827      public boolean isConnected()
828      {
829        final LDAPConnectionInternals internals = connectionInternals;
830    
831        if (internals == null)
832        {
833          return false;
834        }
835    
836        if (! internals.isConnected())
837        {
838          setClosed();
839          return false;
840        }
841    
842        return true;
843      }
844    
845    
846    
847      /**
848       * Converts this clear-text connection to one that encrypts all communication
849       * using Transport Layer Security.  This method is intended for use as a
850       * helper for processing in the course of the StartTLS extended operation and
851       * should not be used for other purposes.
852       *
853       * @param  sslContext  The SSL context to use when performing the negotiation.
854       *                     It must not be {@code null}.
855       *
856       * @throws  LDAPException  If a problem occurs while converting this
857       *                         connection to use TLS.
858       */
859      void convertToTLS(final SSLContext sslContext)
860           throws LDAPException
861      {
862        final LDAPConnectionInternals internals = connectionInternals;
863        if (internals == null)
864        {
865          throw new LDAPException(ResultCode.SERVER_DOWN,
866                                  ERR_CONN_NOT_ESTABLISHED.get());
867        }
868        else
869        {
870          internals.convertToTLS(sslContext);
871        }
872      }
873    
874    
875      /**
876       * Retrieves the set of connection options for this connection.  Changes to
877       * the object that is returned will directly impact this connection.
878       *
879       * @return  The set of connection options for this connection.
880       */
881      public LDAPConnectionOptions getConnectionOptions()
882      {
883        return connectionOptions;
884      }
885    
886    
887    
888      /**
889       * Specifies the set of connection options for this connection.  Some changes
890       * may not take effect for operations already in progress, and some changes
891       * may not take effect for a connection that is already established.
892       *
893       * @param  connectionOptions  The set of connection options for this
894       *                            connection.  It may be {@code null} if a default
895       *                            set of options is to be used.
896       */
897      public void setConnectionOptions(
898                       final LDAPConnectionOptions connectionOptions)
899      {
900        if (connectionOptions == null)
901        {
902          this.connectionOptions = new LDAPConnectionOptions();
903        }
904        else
905        {
906          final LDAPConnectionOptions newOptions = connectionOptions.duplicate();
907          if (debugEnabled(DebugType.LDAP) && newOptions.useSynchronousMode() &&
908              (! connectionOptions.useSynchronousMode()) && isConnected())
909          {
910            debug(Level.WARNING, DebugType.LDAP,
911                  "A call to LDAPConnection.setConnectionOptions() with " +
912                  "useSynchronousMode=true will have no effect for this " +
913                  "connection because it is already established.  The " +
914                  "useSynchronousMode option must be set before the connection " +
915                  "is established to have any effect.");
916          }
917    
918          this.connectionOptions = newOptions;
919        }
920      }
921    
922    
923    
924      /**
925       * Retrieves the socket factory that was used when creating the socket for the
926       * last connection attempt (whether successful or unsuccessful) for this LDAP
927       * connection.
928       *
929       * @return  The socket factory that was used when creating the socket for the
930       *          last connection attempt for this LDAP connection, or {@code null}
931       *          if no attempt has yet been made to establish this connection.
932       */
933      public SocketFactory getLastUsedSocketFactory()
934      {
935        return lastUsedSocketFactory;
936      }
937    
938    
939    
940      /**
941       * Retrieves the socket factory to use to create the socket for subsequent
942       * connection attempts.  This may or may not be the socket factory that was
943       * used to create the current established connection.
944       *
945       * @return  The socket factory to use to create the socket for subsequent
946       *          connection attempts.
947       */
948      public SocketFactory getSocketFactory()
949      {
950        return socketFactory;
951      }
952    
953    
954    
955      /**
956       * Specifies the socket factory to use to create the socket for subsequent
957       * connection attempts.  This will not impact any established connection.
958       *
959       * @param  socketFactory  The socket factory to use to create the socket for
960       *                        subsequent connection attempts.
961       */
962      public void setSocketFactory(final SocketFactory socketFactory)
963      {
964        if (socketFactory == null)
965        {
966          this.socketFactory = DEFAULT_SOCKET_FACTORY;
967        }
968        else
969        {
970          this.socketFactory = socketFactory;
971        }
972      }
973    
974    
975    
976      /**
977       * Retrieves a value that uniquely identifies this connection within the JVM
978       * Each {@code LDAPConnection} object will be assigned a different connection
979       * ID, and that connection ID will not change over the life of the object,
980       * even if the connection is closed and re-established (whether re-established
981       * to the same server or a different server).
982       *
983       * @return  A value that uniquely identifies this connection within the JVM.
984       */
985      public long getConnectionID()
986      {
987        return connectionID;
988      }
989    
990    
991    
992      /**
993       * Retrieves the user-friendly name that has been assigned to this connection.
994       *
995       * @return  The user-friendly name that has been assigned to this connection,
996       *          or {@code null} if none has been assigned.
997       */
998      public String getConnectionName()
999      {
1000        return connectionName;
1001      }
1002    
1003    
1004    
1005      /**
1006       * Specifies the user-friendly name that should be used for this connection.
1007       * This name may be used in debugging to help identify the purpose of this
1008       * connection.  This will have no effect for connections which are part of a
1009       * connection pool.
1010       *
1011       * @param  connectionName  The user-friendly name that should be used for this
1012       *                         connection.
1013       */
1014      public void setConnectionName(final String connectionName)
1015      {
1016        if (connectionPool == null)
1017        {
1018          this.connectionName = connectionName;
1019          if (connectionInternals != null)
1020          {
1021            final LDAPConnectionReader reader =
1022                 connectionInternals.getConnectionReader();
1023            reader.updateThreadName();
1024          }
1025        }
1026      }
1027    
1028    
1029    
1030      /**
1031       * Retrieves the user-friendly name that has been assigned to the connection
1032       * pool with which this connection is associated.
1033       *
1034       * @return  The user-friendly name that has been assigned to the connection
1035       *          pool with which this connection is associated, or {@code null} if
1036       *          none has been assigned or this connection is not associated with a
1037       *          connection pool.
1038       */
1039      public String getConnectionPoolName()
1040      {
1041        return connectionPoolName;
1042      }
1043    
1044    
1045    
1046      /**
1047       * Specifies the user-friendly name that should be used for the connection
1048       * pool with which this connection is associated.
1049       *
1050       * @param  connectionPoolName  The user-friendly name that should be used for
1051       *                             the connection pool with which this connection
1052       *                             is associated.
1053       */
1054      void setConnectionPoolName(final String connectionPoolName)
1055      {
1056        this.connectionPoolName = connectionPoolName;
1057        if (connectionInternals != null)
1058        {
1059          final LDAPConnectionReader reader =
1060               connectionInternals.getConnectionReader();
1061          reader.updateThreadName();
1062        }
1063      }
1064    
1065    
1066    
1067      /**
1068       * Retrieves a string representation of the host and port for the server to
1069       * to which the last connection attempt was made.  It does not matter whether
1070       * the connection attempt was successful, nor does it matter whether it is
1071       * still established.  This is intended for internal use in error messages.
1072       *
1073       * @return  A string representation of the host and port for the server to
1074       *          which the last connection attempt was made, or an empty string if
1075       *          no connection attempt has yet been made on this connection.
1076       */
1077      String getHostPort()
1078      {
1079        if (hostPort == null)
1080        {
1081          return "";
1082        }
1083        else
1084        {
1085          return hostPort;
1086        }
1087      }
1088    
1089    
1090    
1091      /**
1092       * Retrieves the address of the directory server to which this connection is
1093       * currently established.
1094       *
1095       * @return  The address of the directory server to which this connection is
1096       *          currently established, or {@code null} if the connection is not
1097       *          established.
1098       */
1099      public String getConnectedAddress()
1100      {
1101        final LDAPConnectionInternals internals = connectionInternals;
1102        if (internals == null)
1103        {
1104          return null;
1105        }
1106        else
1107        {
1108          return internals.getHost();
1109        }
1110      }
1111    
1112    
1113    
1114      /**
1115       * Retrieves the port of the directory server to which this connection is
1116       * currently established.
1117       *
1118       * @return  The port of the directory server to which this connection is
1119       *          currently established, or -1 if the connection is not established.
1120       */
1121      public int getConnectedPort()
1122      {
1123        final LDAPConnectionInternals internals = connectionInternals;
1124        if (internals == null)
1125        {
1126          return -1;
1127        }
1128        else
1129        {
1130          return internals.getPort();
1131        }
1132      }
1133    
1134    
1135    
1136      /**
1137       * Retrieves a stack trace of the thread that last attempted to establish this
1138       * connection.  Note that this will only be available if an attempt has been
1139       * made to establish this connection and the
1140       * {@link LDAPConnectionOptions#captureConnectStackTrace()} method for the
1141       * associated connection options returns {@code true}.
1142       *
1143       * @return  A stack trace of the thread that last attempted to establish this
1144       *          connection, or {@code null} connect stack traces are not enabled,
1145       *          or if no attempt has been made to establish this connection.
1146       */
1147      public StackTraceElement[] getConnectStackTrace()
1148      {
1149        return connectStackTrace;
1150      }
1151    
1152    
1153    
1154      /**
1155       * Provides a stack trace for the thread that last attempted to establish this
1156       * connection.
1157       *
1158       * @param  connectStackTrace  A stack trace for the thread that last attempted
1159       *                            to establish this connection.
1160       */
1161      void setConnectStackTrace(final StackTraceElement[] connectStackTrace)
1162      {
1163        this.connectStackTrace = connectStackTrace;
1164      }
1165    
1166    
1167    
1168      /**
1169       * Unbinds from the server and closes the connection.
1170       * <BR><BR>
1171       * If this method is invoked while any operations are in progress on this
1172       * connection, then the directory server may or may not abort processing for
1173       * those operations, depending on the type of operation and how far along the
1174       * server has already gotten while processing that operation.  It is
1175       * recommended that all active operations be abandoned, canceled, or allowed
1176       * to complete before attempting to close an active connection.
1177       */
1178      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1179      public void close()
1180      {
1181        closeRequested = true;
1182        setDisconnectInfo(DisconnectType.UNBIND, null, null);
1183    
1184        if (connectionPool == null)
1185        {
1186          terminate(null);
1187        }
1188        else
1189        {
1190          connectionPool.releaseDefunctConnection(this);
1191        }
1192      }
1193    
1194    
1195    
1196      /**
1197       * Unbinds from the server and closes the connection, optionally including
1198       * the provided set of controls in the unbind request.
1199       * <BR><BR>
1200       * If this method is invoked while any operations are in progress on this
1201       * connection, then the directory server may or may not abort processing for
1202       * those operations, depending on the type of operation and how far along the
1203       * server has already gotten while processing that operation.  It is
1204       * recommended that all active operations be abandoned, canceled, or allowed
1205       * to complete before attempting to close an active connection.
1206       *
1207       * @param  controls  The set of controls to include in the unbind request.  It
1208       *                   may be {@code null} if there are not to be any controls
1209       *                   sent in the unbind request.
1210       */
1211      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1212      public void close(final Control[] controls)
1213      {
1214        closeRequested = true;
1215        setDisconnectInfo(DisconnectType.UNBIND, null, null);
1216    
1217        if (connectionPool == null)
1218        {
1219          terminate(controls);
1220        }
1221        else
1222        {
1223          connectionPool.releaseDefunctConnection(this);
1224        }
1225      }
1226    
1227    
1228    
1229      /**
1230       * Unbinds from the server and closes the connection, optionally including the
1231       * provided set of controls in the unbind request.  This method is only
1232       * intended for internal use, since it does not make any attempt to release
1233       * the connection back to its associated connection pool, if there is one.
1234       *
1235       * @param  controls  The set of controls to include in the unbind request.  It
1236       *                   may be {@code null} if there are not to be any controls
1237       *                   sent in the unbind request.
1238       */
1239      void terminate(final Control[] controls)
1240      {
1241        if (isConnected() && (! unbindRequestSent))
1242        {
1243          try
1244          {
1245            unbindRequestSent = true;
1246            if (debugEnabled(DebugType.LDAP))
1247            {
1248              debug(Level.INFO, DebugType.LDAP, "Sending LDAP unbind request.");
1249            }
1250    
1251            connectionStatistics.incrementNumUnbindRequests();
1252            sendMessage(new LDAPMessage(nextMessageID(),
1253                 new UnbindRequestProtocolOp(), controls));
1254          }
1255          catch (Exception e)
1256          {
1257            debugException(e);
1258          }
1259        }
1260    
1261        setClosed();
1262      }
1263    
1264    
1265    
1266      /**
1267       * Indicates whether a request has been made to close this connection.
1268       *
1269       * @return  {@code true} if a request has been made to close this connection,
1270       *          or {@code false} if not.
1271       */
1272      boolean closeRequested()
1273      {
1274        return closeRequested;
1275      }
1276    
1277    
1278    
1279      /**
1280       * Indicates whether an unbind request has been sent over this connection.
1281       *
1282       * @return  {@code true} if an unbind request has been sent over this
1283       *          connection, or {@code false} if not.
1284       */
1285      boolean unbindRequestSent()
1286      {
1287        return unbindRequestSent;
1288      }
1289    
1290    
1291    
1292      /**
1293       * Indicates that this LDAP connection is part of the specified
1294       * connection pool.
1295       *
1296       * @param  connectionPool  The connection pool with which this LDAP connection
1297       *                         is associated.
1298       */
1299      void setConnectionPool(final AbstractConnectionPool connectionPool)
1300      {
1301        this.connectionPool = connectionPool;
1302      }
1303    
1304    
1305    
1306      /**
1307       * Retrieves the directory server root DSE, which provides information about
1308       * the directory server, including the capabilities that it provides and the
1309       * type of data that it is configured to handle.
1310       *
1311       * @return  The directory server root DSE, or {@code null} if it is not
1312       *          available.
1313       *
1314       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1315       *                         the server root DSE.
1316       */
1317      public RootDSE getRootDSE()
1318             throws LDAPException
1319      {
1320        return RootDSE.getRootDSE(this);
1321      }
1322    
1323    
1324    
1325      /**
1326       * Retrieves the directory server schema definitions, using the subschema
1327       * subentry DN contained in the server's root DSE.  For directory servers
1328       * containing a single schema, this should be sufficient for all purposes.
1329       * For servers with multiple schemas, it may be necessary to specify the DN
1330       * of the target entry for which to obtain the associated schema.
1331       *
1332       * @return  The directory server schema definitions, or {@code null} if the
1333       *          schema information could not be retrieved (e.g, the client does
1334       *          not have permission to read the server schema).
1335       *
1336       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1337       *                         the server schema.
1338       */
1339      public Schema getSchema()
1340             throws LDAPException
1341      {
1342        return Schema.getSchema(this, "");
1343      }
1344    
1345    
1346    
1347      /**
1348       * Retrieves the directory server schema definitions that govern the specified
1349       * entry.  The subschemaSubentry attribute will be retrieved from the target
1350       * entry, and then the appropriate schema definitions will be loaded from the
1351       * entry referenced by that attribute.  This may be necessary to ensure
1352       * correct behavior in servers that support multiple schemas.
1353       *
1354       * @param  entryDN  The DN of the entry for which to retrieve the associated
1355       *                  schema definitions.  It may be {@code null} or an empty
1356       *                  string if the subschemaSubentry attribute should be
1357       *                  retrieved from the server's root DSE.
1358       *
1359       * @return  The directory server schema definitions, or {@code null} if the
1360       *          schema information could not be retrieved (e.g, the client does
1361       *          not have permission to read the server schema).
1362       *
1363       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1364       *                         the server schema.
1365       */
1366      public Schema getSchema(final String entryDN)
1367             throws LDAPException
1368      {
1369        return Schema.getSchema(this, entryDN);
1370      }
1371    
1372    
1373    
1374      /**
1375       * Retrieves the entry with the specified DN.  All user attributes will be
1376       * requested in the entry to return.
1377       *
1378       * @param  dn  The DN of the entry to retrieve.  It must not be {@code null}.
1379       *
1380       * @return  The requested entry, or {@code null} if the target entry does not
1381       *          exist or no entry was returned (e.g., if the authenticated user
1382       *          does not have permission to read the target entry).
1383       *
1384       * @throws  LDAPException  If a problem occurs while sending the request or
1385       *                         reading the response.
1386       */
1387      public SearchResultEntry getEntry(final String dn)
1388             throws LDAPException
1389      {
1390        return getEntry(dn, (String[]) null);
1391      }
1392    
1393    
1394    
1395      /**
1396       * Retrieves the entry with the specified DN.
1397       *
1398       * @param  dn          The DN of the entry to retrieve.  It must not be
1399       *                     {@code null}.
1400       * @param  attributes  The set of attributes to request for the target entry.
1401       *                     If it is {@code null}, then all user attributes will be
1402       *                     requested.
1403       *
1404       * @return  The requested entry, or {@code null} if the target entry does not
1405       *          exist or no entry was returned (e.g., if the authenticated user
1406       *          does not have permission to read the target entry).
1407       *
1408       * @throws  LDAPException  If a problem occurs while sending the request or
1409       *                         reading the response.
1410       */
1411      public SearchResultEntry getEntry(final String dn, final String... attributes)
1412             throws LDAPException
1413      {
1414        final Filter filter = Filter.createPresenceFilter("objectClass");
1415    
1416        final SearchResult result;
1417        try
1418        {
1419          final SearchRequest searchRequest =
1420               new SearchRequest(dn, SearchScope.BASE, DereferencePolicy.NEVER, 1,
1421                                 0, false, filter, attributes);
1422          result = search(searchRequest);
1423        }
1424        catch (LDAPException le)
1425        {
1426          if (le.getResultCode().equals(ResultCode.NO_SUCH_OBJECT))
1427          {
1428            return null;
1429          }
1430          else
1431          {
1432            throw le;
1433          }
1434        }
1435    
1436        if (! result.getResultCode().equals(ResultCode.SUCCESS))
1437        {
1438          throw new LDAPException(result);
1439        }
1440    
1441        final List<SearchResultEntry> entryList = result.getSearchEntries();
1442        if (entryList.isEmpty())
1443        {
1444          return null;
1445        }
1446        else
1447        {
1448          return entryList.get(0);
1449        }
1450      }
1451    
1452    
1453    
1454      /**
1455       * Processes an abandon request with the provided information.
1456       *
1457       * @param  requestID  The async request ID for the request to abandon.
1458       *
1459       * @throws  LDAPException  If a problem occurs while sending the request to
1460       *                         the server.
1461       */
1462      public void abandon(final AsyncRequestID requestID)
1463             throws LDAPException
1464      {
1465        abandon(requestID, null);
1466      }
1467    
1468    
1469    
1470      /**
1471       * Processes an abandon request with the provided information.
1472       *
1473       * @param  requestID  The async request ID for the request to abandon.
1474       * @param  controls   The set of controls to include in the abandon request.
1475       *                    It may be {@code null} or empty if there are no
1476       *                    controls.
1477       *
1478       * @throws  LDAPException  If a problem occurs while sending the request to
1479       *                         the server.
1480       */
1481      public void abandon(final AsyncRequestID requestID, final Control[] controls)
1482             throws LDAPException
1483      {
1484        if (debugEnabled(DebugType.LDAP))
1485        {
1486          debug(Level.INFO, DebugType.LDAP,
1487                "Sending LDAP abandon request for message ID " + requestID);
1488        }
1489    
1490        if (synchronousMode())
1491        {
1492          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1493               ERR_ABANDON_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1494        }
1495    
1496        connectionStatistics.incrementNumAbandonRequests();
1497        sendMessage(new LDAPMessage(nextMessageID(),
1498             new AbandonRequestProtocolOp(requestID.getMessageID()), controls));
1499      }
1500    
1501    
1502    
1503      /**
1504       * Sends an abandon request with the provided information.
1505       *
1506       * @param  messageID  The message ID for the request to abandon.
1507       * @param  controls   The set of controls to include in the abandon request.
1508       *                    It may be {@code null} or empty if there are no
1509       *                    controls.
1510       *
1511       * @throws  LDAPException  If a problem occurs while sending the request to
1512       *                         the server.
1513       */
1514      void abandon(final int messageID, final Control... controls)
1515           throws LDAPException
1516      {
1517        if (debugEnabled(DebugType.LDAP))
1518        {
1519          debug(Level.INFO, DebugType.LDAP,
1520                "Sending LDAP abandon request for message ID " + messageID);
1521        }
1522    
1523        connectionStatistics.incrementNumAbandonRequests();
1524        sendMessage(new LDAPMessage(nextMessageID(),
1525             new AbandonRequestProtocolOp(messageID), controls));
1526      }
1527    
1528    
1529    
1530      /**
1531       * Processes an add operation with the provided information.
1532       *
1533       * @param  dn          The DN of the entry to add.  It must not be
1534       *                     {@code null}.
1535       * @param  attributes  The set of attributes to include in the entry to add.
1536       *                     It must not be {@code null}.
1537       *
1538       * @return  The result of processing the add operation.
1539       *
1540       * @throws  LDAPException  If the server rejects the add request, or if a
1541       *                         problem is encountered while sending the request or
1542       *                         reading the response.
1543       */
1544      public LDAPResult add(final String dn, final Attribute... attributes)
1545             throws LDAPException
1546      {
1547        ensureNotNull(dn, attributes);
1548    
1549        return add(new AddRequest(dn, attributes));
1550      }
1551    
1552    
1553    
1554      /**
1555       * Processes an add operation with the provided information.
1556       *
1557       * @param  dn          The DN of the entry to add.  It must not be
1558       *                     {@code null}.
1559       * @param  attributes  The set of attributes to include in the entry to add.
1560       *                     It must not be {@code null}.
1561       *
1562       * @return  The result of processing the add operation.
1563       *
1564       * @throws  LDAPException  If the server rejects the add request, or if a
1565       *                         problem is encountered while sending the request or
1566       *                         reading the response.
1567       */
1568      public LDAPResult add(final String dn, final Collection<Attribute> attributes)
1569             throws LDAPException
1570      {
1571        ensureNotNull(dn, attributes);
1572    
1573        return add(new AddRequest(dn, attributes));
1574      }
1575    
1576    
1577    
1578      /**
1579       * Processes an add operation with the provided information.
1580       *
1581       * @param  entry  The entry to add.  It must not be {@code null}.
1582       *
1583       * @return  The result of processing the add operation.
1584       *
1585       * @throws  LDAPException  If the server rejects the add request, or if a
1586       *                         problem is encountered while sending the request or
1587       *                         reading the response.
1588       */
1589      public LDAPResult add(final Entry entry)
1590             throws LDAPException
1591      {
1592        ensureNotNull(entry);
1593    
1594        return add(new AddRequest(entry));
1595      }
1596    
1597    
1598    
1599      /**
1600       * Processes an add operation with the provided information.
1601       *
1602       * @param  ldifLines  The lines that comprise an LDIF representation of the
1603       *                    entry to add.  It must not be empty or {@code null}.
1604       *
1605       * @return  The result of processing the add operation.
1606       *
1607       * @throws  LDIFException  If the provided entry lines cannot be decoded as an
1608       *                         entry in LDIF form.
1609       *
1610       * @throws  LDAPException  If the server rejects the add request, or if a
1611       *                         problem is encountered while sending the request or
1612       *                         reading the response.
1613       */
1614      public LDAPResult add(final String... ldifLines)
1615             throws LDIFException, LDAPException
1616      {
1617        return add(new AddRequest(ldifLines));
1618      }
1619    
1620    
1621    
1622      /**
1623       * Processes the provided add request.
1624       *
1625       * @param  addRequest  The add request to be processed.  It must not be
1626       *                     {@code null}.
1627       *
1628       * @return  The result of processing the add operation.
1629       *
1630       * @throws  LDAPException  If the server rejects the add request, or if a
1631       *                         problem is encountered while sending the request or
1632       *                         reading the response.
1633       */
1634      public LDAPResult add(final AddRequest addRequest)
1635             throws LDAPException
1636      {
1637        ensureNotNull(addRequest);
1638    
1639        final LDAPResult ldapResult = addRequest.process(this, 1);
1640    
1641        switch (ldapResult.getResultCode().intValue())
1642        {
1643          case ResultCode.SUCCESS_INT_VALUE:
1644          case ResultCode.NO_OPERATION_INT_VALUE:
1645            return ldapResult;
1646    
1647          default:
1648            throw new LDAPException(ldapResult);
1649        }
1650      }
1651    
1652    
1653    
1654      /**
1655       * Processes the provided add request.
1656       *
1657       * @param  addRequest  The add request to be processed.  It must not be
1658       *                     {@code null}.
1659       *
1660       * @return  The result of processing the add operation.
1661       *
1662       * @throws  LDAPException  If the server rejects the add request, or if a
1663       *                         problem is encountered while sending the request or
1664       *                         reading the response.
1665       */
1666      public LDAPResult add(final ReadOnlyAddRequest addRequest)
1667             throws LDAPException
1668      {
1669        return add((AddRequest) addRequest);
1670      }
1671    
1672    
1673    
1674      /**
1675       * Processes the provided add request as an asynchronous operation.
1676       *
1677       * @param  addRequest      The add request to be processed.  It must not be
1678       *                         {@code null}.
1679       * @param  resultListener  The async result listener to use to handle the
1680       *                         response for the add operation.  It must not be
1681       *                         {@code null}.
1682       *
1683       * @return  An async request ID that may be used to reference the operation.
1684       *
1685       * @throws  LDAPException  If a problem occurs while sending the request.
1686       */
1687      public AsyncRequestID asyncAdd(final AddRequest addRequest,
1688                                     final AsyncResultListener resultListener)
1689             throws LDAPException
1690      {
1691        ensureNotNull(addRequest, resultListener);
1692    
1693        if (synchronousMode())
1694        {
1695          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1696               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1697        }
1698    
1699        return addRequest.processAsync(this, resultListener);
1700      }
1701    
1702    
1703    
1704      /**
1705       * Processes the provided add request as an asynchronous operation.
1706       *
1707       * @param  addRequest      The add request to be processed.  It must not be
1708       *                         {@code null}.
1709       * @param  resultListener  The async result listener to use to handle the
1710       *                         response for the add operation.  It must not be
1711       *                         {@code null}.
1712       *
1713       * @return  An async request ID that may be used to reference the operation.
1714       *
1715       * @throws  LDAPException  If a problem occurs while sending the request.
1716       */
1717      public AsyncRequestID asyncAdd(final ReadOnlyAddRequest addRequest,
1718                                     final AsyncResultListener resultListener)
1719             throws LDAPException
1720      {
1721        if (synchronousMode())
1722        {
1723          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1724               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1725        }
1726    
1727        return asyncAdd((AddRequest) addRequest, resultListener);
1728      }
1729    
1730    
1731    
1732      /**
1733       * Processes a simple bind request with the provided DN and password.
1734       * <BR><BR>
1735       * The LDAP protocol specification forbids clients from attempting to perform
1736       * a bind on a connection in which one or more other operations are already in
1737       * progress.  If a bind is attempted while any operations are in progress,
1738       * then the directory server may or may not abort processing for those
1739       * operations, depending on the type of operation and how far along the
1740       * server has already gotten while processing that operation (unless the bind
1741       * request is one that will not cause the server to attempt to change the
1742       * identity of this connection, for example by including the retain identity
1743       * request control in the bind request if using the Commercial Edition of the
1744       * LDAP SDK in conjunction with an UnboundID Directory Server).  It is
1745       * recommended that all active operations be abandoned, canceled, or allowed
1746       * to complete before attempting to perform a bind on an active connection.
1747       *
1748       * @param  bindDN    The bind DN for the bind operation.
1749       * @param  password  The password for the simple bind operation.
1750       *
1751       * @return  The result of processing the bind operation.
1752       *
1753       * @throws  LDAPException  If the server rejects the bind request, or if a
1754       *                         problem occurs while sending the request or reading
1755       *                         the response.
1756       */
1757      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1758      public BindResult bind(final String bindDN, final String password)
1759             throws LDAPException
1760      {
1761        return bind(new SimpleBindRequest(bindDN, password));
1762      }
1763    
1764    
1765    
1766      /**
1767       * Processes the provided bind request.
1768       * <BR><BR>
1769       * The LDAP protocol specification forbids clients from attempting to perform
1770       * a bind on a connection in which one or more other operations are already in
1771       * progress.  If a bind is attempted while any operations are in progress,
1772       * then the directory server may or may not abort processing for those
1773       * operations, depending on the type of operation and how far along the
1774       * server has already gotten while processing that operation (unless the bind
1775       * request is one that will not cause the server to attempt to change the
1776       * identity of this connection, for example by including the retain identity
1777       * request control in the bind request if using the Commercial Edition of the
1778       * LDAP SDK in conjunction with an UnboundID Directory Server).  It is
1779       * recommended that all active operations be abandoned, canceled, or allowed
1780       * to complete before attempting to perform a bind on an active connection.
1781       *
1782       * @param  bindRequest  The bind request to be processed.  It must not be
1783       *                      {@code null}.
1784       *
1785       * @return  The result of processing the bind operation.
1786       *
1787       * @throws  LDAPException  If the server rejects the bind request, or if a
1788       *                         problem occurs while sending the request or reading
1789       *                         the response.
1790       */
1791      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1792      public BindResult bind(final BindRequest bindRequest)
1793             throws LDAPException
1794      {
1795        ensureNotNull(bindRequest);
1796    
1797        lastBindRequest = null;
1798    
1799        final BindResult bindResult = bindRequest.process(this, 1);
1800    
1801        if (bindResult.getResultCode().equals(ResultCode.SUCCESS))
1802        {
1803          // We don't want to update the last bind request or update the cached
1804          // schema for this connection if it included the retain identity control.
1805          // However, that's only available in the Commercial Edition, so just
1806          // reference it by OID here.
1807          boolean hasRetainIdentityControl = false;
1808          for (final Control c : bindRequest.getControls())
1809          {
1810            if (c.getOID().equals("1.3.6.1.4.1.30221.2.5.3"))
1811            {
1812              hasRetainIdentityControl = true;
1813              break;
1814            }
1815          }
1816    
1817          if (! hasRetainIdentityControl)
1818          {
1819            lastBindRequest = bindRequest;
1820    
1821            if (connectionOptions.useSchema())
1822            {
1823              try
1824              {
1825                cachedSchema = getCachedSchema(this);
1826              }
1827              catch (Exception e)
1828              {
1829                debugException(e);
1830              }
1831            }
1832          }
1833    
1834          return bindResult;
1835        }
1836    
1837        throw new LDAPException(bindResult);
1838      }
1839    
1840    
1841    
1842      /**
1843       * Processes a compare operation with the provided information.
1844       *
1845       * @param  dn              The DN of the entry in which to make the
1846       *                         comparison.  It must not be {@code null}.
1847       * @param  attributeName   The attribute name for which to make the
1848       *                         comparison.  It must not be {@code null}.
1849       * @param  assertionValue  The assertion value to verify in the target entry.
1850       *                         It must not be {@code null}.
1851       *
1852       * @return  The result of processing the compare operation.
1853       *
1854       * @throws  LDAPException  If the server rejects the compare request, or if a
1855       *                         problem is encountered while sending the request or
1856       *                         reading the response.
1857       */
1858      public CompareResult compare(final String dn, final String attributeName,
1859                                   final String assertionValue)
1860             throws LDAPException
1861      {
1862        ensureNotNull(dn, attributeName, assertionValue);
1863    
1864        return compare(new CompareRequest(dn, attributeName, assertionValue));
1865      }
1866    
1867    
1868    
1869      /**
1870       * Processes the provided compare request.
1871       *
1872       * @param  compareRequest  The compare request to be processed.  It must not
1873       *                         be {@code null}.
1874       *
1875       * @return  The result of processing the compare operation.
1876       *
1877       * @throws  LDAPException  If the server rejects the compare request, or if a
1878       *                         problem is encountered while sending the request or
1879       *                         reading the response.
1880       */
1881      public CompareResult compare(final CompareRequest compareRequest)
1882             throws LDAPException
1883      {
1884        ensureNotNull(compareRequest);
1885    
1886        final LDAPResult result = compareRequest.process(this, 1);
1887        switch (result.getResultCode().intValue())
1888        {
1889          case ResultCode.COMPARE_FALSE_INT_VALUE:
1890          case ResultCode.COMPARE_TRUE_INT_VALUE:
1891            return new CompareResult(result);
1892    
1893          default:
1894            throw new LDAPException(result);
1895        }
1896      }
1897    
1898    
1899    
1900      /**
1901       * Processes the provided compare request.
1902       *
1903       * @param  compareRequest  The compare request to be processed.  It must not
1904       *                         be {@code null}.
1905       *
1906       * @return  The result of processing the compare operation.
1907       *
1908       * @throws  LDAPException  If the server rejects the compare request, or if a
1909       *                         problem is encountered while sending the request or
1910       *                         reading the response.
1911       */
1912      public CompareResult compare(final ReadOnlyCompareRequest compareRequest)
1913             throws LDAPException
1914      {
1915        return compare((CompareRequest) compareRequest);
1916      }
1917    
1918    
1919    
1920      /**
1921       * Processes the provided compare request as an asynchronous operation.
1922       *
1923       * @param  compareRequest  The compare request to be processed.  It must not
1924       *                         be {@code null}.
1925       * @param  resultListener  The async result listener to use to handle the
1926       *                         response for the compare operation.  It must not be
1927       *                         {@code null}.
1928       *
1929       * @return  An async request ID that may be used to reference the operation.
1930       *
1931       * @throws  LDAPException  If a problem occurs while sending the request.
1932       */
1933      public AsyncRequestID asyncCompare(final CompareRequest compareRequest,
1934                                 final AsyncCompareResultListener resultListener)
1935             throws LDAPException
1936      {
1937        ensureNotNull(compareRequest, resultListener);
1938    
1939        if (synchronousMode())
1940        {
1941          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1942               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1943        }
1944    
1945        return compareRequest.processAsync(this, resultListener);
1946      }
1947    
1948    
1949    
1950      /**
1951       * Processes the provided compare request as an asynchronous operation.
1952       *
1953       * @param  compareRequest  The compare request to be processed.  It must not
1954       *                         be {@code null}.
1955       * @param  resultListener  The async result listener to use to handle the
1956       *                         response for the compare operation.  It must not be
1957       *                         {@code null}.
1958       *
1959       * @return  An async request ID that may be used to reference the operation.
1960       *
1961       * @throws  LDAPException  If a problem occurs while sending the request.
1962       */
1963      public AsyncRequestID asyncCompare(
1964                                 final ReadOnlyCompareRequest compareRequest,
1965                                 final AsyncCompareResultListener resultListener)
1966             throws LDAPException
1967      {
1968        if (synchronousMode())
1969        {
1970          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1971               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1972        }
1973    
1974        return asyncCompare((CompareRequest) compareRequest, resultListener);
1975      }
1976    
1977    
1978    
1979      /**
1980       * Deletes the entry with the specified DN.
1981       *
1982       * @param  dn  The DN of the entry to delete.  It must not be {@code null}.
1983       *
1984       * @return  The result of processing the delete operation.
1985       *
1986       * @throws  LDAPException  If the server rejects the delete request, or if a
1987       *                         problem is encountered while sending the request or
1988       *                         reading the response.
1989       */
1990      public LDAPResult delete(final String dn)
1991             throws LDAPException
1992      {
1993        return delete(new DeleteRequest(dn));
1994      }
1995    
1996    
1997    
1998      /**
1999       * Processes the provided delete request.
2000       *
2001       * @param  deleteRequest  The delete request to be processed.  It must not be
2002       *                        {@code null}.
2003       *
2004       * @return  The result of processing the delete operation.
2005       *
2006       * @throws  LDAPException  If the server rejects the delete request, or if a
2007       *                         problem is encountered while sending the request or
2008       *                         reading the response.
2009       */
2010      public LDAPResult delete(final DeleteRequest deleteRequest)
2011             throws LDAPException
2012      {
2013        ensureNotNull(deleteRequest);
2014    
2015        final LDAPResult ldapResult = deleteRequest.process(this, 1);
2016    
2017        switch (ldapResult.getResultCode().intValue())
2018        {
2019          case ResultCode.SUCCESS_INT_VALUE:
2020          case ResultCode.NO_OPERATION_INT_VALUE:
2021            return ldapResult;
2022    
2023          default:
2024            throw new LDAPException(ldapResult);
2025        }
2026      }
2027    
2028    
2029    
2030      /**
2031       * Processes the provided delete request.
2032       *
2033       * @param  deleteRequest  The delete request to be processed.  It must not be
2034       *                        {@code null}.
2035       *
2036       * @return  The result of processing the delete operation.
2037       *
2038       * @throws  LDAPException  If the server rejects the delete request, or if a
2039       *                         problem is encountered while sending the request or
2040       *                         reading the response.
2041       */
2042      public LDAPResult delete(final ReadOnlyDeleteRequest deleteRequest)
2043             throws LDAPException
2044      {
2045        return delete((DeleteRequest) deleteRequest);
2046      }
2047    
2048    
2049    
2050      /**
2051       * Processes the provided delete request as an asynchronous operation.
2052       *
2053       * @param  deleteRequest   The delete request to be processed.  It must not be
2054       *                         {@code null}.
2055       * @param  resultListener  The async result listener to use to handle the
2056       *                         response for the delete operation.  It must not be
2057       *                         {@code null}.
2058       *
2059       * @return  An async request ID that may be used to reference the operation.
2060       *
2061       * @throws  LDAPException  If a problem occurs while sending the request.
2062       */
2063      public AsyncRequestID asyncDelete(final DeleteRequest deleteRequest,
2064                                 final AsyncResultListener resultListener)
2065             throws LDAPException
2066      {
2067        ensureNotNull(deleteRequest, resultListener);
2068    
2069        if (synchronousMode())
2070        {
2071          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2072               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2073        }
2074    
2075        return deleteRequest.processAsync(this, resultListener);
2076      }
2077    
2078    
2079    
2080      /**
2081       * Processes the provided delete request as an asynchronous operation.
2082       *
2083       * @param  deleteRequest   The delete request to be processed.  It must not be
2084       *                         {@code null}.
2085       * @param  resultListener  The async result listener to use to handle the
2086       *                         response for the delete operation.  It must not be
2087       *                         {@code null}.
2088       *
2089       * @return  An async request ID that may be used to reference the operation.
2090       *
2091       * @throws  LDAPException  If a problem occurs while sending the request.
2092       */
2093      public AsyncRequestID asyncDelete(final ReadOnlyDeleteRequest deleteRequest,
2094                                 final AsyncResultListener resultListener)
2095             throws LDAPException
2096      {
2097        if (synchronousMode())
2098        {
2099          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2100               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2101        }
2102    
2103        return asyncDelete((DeleteRequest) deleteRequest, resultListener);
2104      }
2105    
2106    
2107    
2108      /**
2109       * Processes an extended request with the provided request OID.  Note that
2110       * because some types of extended operations return unusual result codes under
2111       * "normal" conditions, the server may not always throw an exception for a
2112       * failed extended operation like it does for other types of operations.  It
2113       * will throw an exception under conditions where there appears to be a
2114       * problem with the connection or the server to which the connection is
2115       * established, but there may be many circumstances in which an extended
2116       * operation is not processed correctly but this method does not throw an
2117       * exception.  In the event that no exception is thrown, it is the
2118       * responsibility of the caller to interpret the result to determine whether
2119       * the operation was processed as expected.
2120       * <BR><BR>
2121       * Note that extended operations which may change the state of this connection
2122       * (e.g., the StartTLS extended operation, which will add encryption to a
2123       * previously-unencrypted connection) should not be invoked while any other
2124       * operations are active on the connection.  It is recommended that all active
2125       * operations be abandoned, canceled, or allowed to complete before attempting
2126       * to process an extended operation that may change the state of this
2127       * connection.
2128       *
2129       * @param  requestOID  The OID for the extended request to process.  It must
2130       *                     not be {@code null}.
2131       *
2132       * @return  The extended result object that provides information about the
2133       *          result of the request processing.  It may or may not indicate that
2134       *          the operation was successful.
2135       *
2136       * @throws  LDAPException  If a problem occurs while sending the request or
2137       *                         reading the response.
2138       */
2139      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2140      public ExtendedResult processExtendedOperation(final String requestOID)
2141             throws LDAPException
2142      {
2143        ensureNotNull(requestOID);
2144    
2145        return processExtendedOperation(new ExtendedRequest(requestOID));
2146      }
2147    
2148    
2149    
2150      /**
2151       * Processes an extended request with the provided request OID and value.
2152       * Note that because some types of extended operations return unusual result
2153       * codes under "normal" conditions, the server may not always throw an
2154       * exception for a failed extended operation like it does for other types of
2155       * operations.  It will throw an exception under conditions where there
2156       * appears to be a problem with the connection or the server to which the
2157       * connection is established, but there may be many circumstances in which an
2158       * extended operation is not processed correctly but this method does not
2159       * throw an exception.  In the event that no exception is thrown, it is the
2160       * responsibility of the caller to interpret the result to determine whether
2161       * the operation was processed as expected.
2162       * <BR><BR>
2163       * Note that extended operations which may change the state of this connection
2164       * (e.g., the StartTLS extended operation, which will add encryption to a
2165       * previously-unencrypted connection) should not be invoked while any other
2166       * operations are active on the connection.  It is recommended that all active
2167       * operations be abandoned, canceled, or allowed to complete before attempting
2168       * to process an extended operation that may change the state of this
2169       * connection.
2170       *
2171       * @param  requestOID    The OID for the extended request to process.  It must
2172       *                       not be {@code null}.
2173       * @param  requestValue  The encoded value for the extended request to
2174       *                       process.  It may be {@code null} if there does not
2175       *                       need to be a value for the requested operation.
2176       *
2177       * @return  The extended result object that provides information about the
2178       *          result of the request processing.  It may or may not indicate that
2179       *          the operation was successful.
2180       *
2181       * @throws  LDAPException  If a problem occurs while sending the request or
2182       *                         reading the response.
2183       */
2184      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2185      public ExtendedResult processExtendedOperation(final String requestOID,
2186                                 final ASN1OctetString requestValue)
2187             throws LDAPException
2188      {
2189        ensureNotNull(requestOID);
2190    
2191        return processExtendedOperation(new ExtendedRequest(requestOID,
2192                                                            requestValue));
2193      }
2194    
2195    
2196    
2197      /**
2198       * Processes the provided extended request.  Note that because some types of
2199       * extended operations return unusual result codes under "normal" conditions,
2200       * the server may not always throw an exception for a failed extended
2201       * operation like it does for other types of operations.  It will throw an
2202       * exception under conditions where there appears to be a problem with the
2203       * connection or the server to which the connection is established, but there
2204       * may be many circumstances in which an extended operation is not processed
2205       * correctly but this method does not throw an exception.  In the event that
2206       * no exception is thrown, it is the responsibility of the caller to interpret
2207       * the result to determine whether the operation was processed as expected.
2208       * <BR><BR>
2209       * Note that extended operations which may change the state of this connection
2210       * (e.g., the StartTLS extended operation, which will add encryption to a
2211       * previously-unencrypted connection) should not be invoked while any other
2212       * operations are active on the connection.  It is recommended that all active
2213       * operations be abandoned, canceled, or allowed to complete before attempting
2214       * to process an extended operation that may change the state of this
2215       * connection.
2216       *
2217       * @param  extendedRequest  The extended request to be processed.  It must not
2218       *                          be {@code null}.
2219       *
2220       * @return  The extended result object that provides information about the
2221       *          result of the request processing.  It may or may not indicate that
2222       *          the operation was successful.
2223       *
2224       * @throws  LDAPException  If a problem occurs while sending the request or
2225       *                         reading the response.
2226       */
2227      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2228      public ExtendedResult processExtendedOperation(
2229                                   final ExtendedRequest extendedRequest)
2230             throws LDAPException
2231      {
2232        ensureNotNull(extendedRequest);
2233    
2234        final ExtendedResult extendedResult = extendedRequest.process(this, 1);
2235    
2236        if ((extendedResult.getOID() == null) &&
2237            (extendedResult.getValue() == null))
2238        {
2239          switch (extendedResult.getResultCode().intValue())
2240          {
2241            case ResultCode.OPERATIONS_ERROR_INT_VALUE:
2242            case ResultCode.PROTOCOL_ERROR_INT_VALUE:
2243            case ResultCode.BUSY_INT_VALUE:
2244            case ResultCode.UNAVAILABLE_INT_VALUE:
2245            case ResultCode.OTHER_INT_VALUE:
2246            case ResultCode.SERVER_DOWN_INT_VALUE:
2247            case ResultCode.LOCAL_ERROR_INT_VALUE:
2248            case ResultCode.ENCODING_ERROR_INT_VALUE:
2249            case ResultCode.DECODING_ERROR_INT_VALUE:
2250            case ResultCode.TIMEOUT_INT_VALUE:
2251            case ResultCode.NO_MEMORY_INT_VALUE:
2252            case ResultCode.CONNECT_ERROR_INT_VALUE:
2253              throw new LDAPException(extendedResult);
2254          }
2255        }
2256    
2257        return extendedResult;
2258      }
2259    
2260    
2261    
2262      /**
2263       * Applies the provided modification to the specified entry.
2264       *
2265       * @param  dn   The DN of the entry to modify.  It must not be {@code null}.
2266       * @param  mod  The modification to apply to the target entry.  It must not
2267       *              be {@code null}.
2268       *
2269       * @return  The result of processing the modify operation.
2270       *
2271       * @throws  LDAPException  If the server rejects the modify request, or if a
2272       *                         problem is encountered while sending the request or
2273       *                         reading the response.
2274       */
2275      public LDAPResult modify(final String dn, final Modification mod)
2276             throws LDAPException
2277      {
2278        ensureNotNull(dn, mod);
2279    
2280        return modify(new ModifyRequest(dn, mod));
2281      }
2282    
2283    
2284    
2285      /**
2286       * Applies the provided set of modifications to the specified entry.
2287       *
2288       * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
2289       * @param  mods  The set of modifications to apply to the target entry.  It
2290       *               must not be {@code null} or empty.  *
2291       * @return  The result of processing the modify operation.
2292       *
2293       * @throws  LDAPException  If the server rejects the modify request, or if a
2294       *                         problem is encountered while sending the request or
2295       *                         reading the response.
2296       */
2297      public LDAPResult modify(final String dn, final Modification... mods)
2298             throws LDAPException
2299      {
2300        ensureNotNull(dn, mods);
2301    
2302        return modify(new ModifyRequest(dn, mods));
2303      }
2304    
2305    
2306    
2307      /**
2308       * Applies the provided set of modifications to the specified entry.
2309       *
2310       * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
2311       * @param  mods  The set of modifications to apply to the target entry.  It
2312       *               must not be {@code null} or empty.
2313       *
2314       * @return  The result of processing the modify operation.
2315       *
2316       * @throws  LDAPException  If the server rejects the modify request, or if a
2317       *                         problem is encountered while sending the request or
2318       *                         reading the response.
2319       */
2320      public LDAPResult modify(final String dn, final List<Modification> mods)
2321             throws LDAPException
2322      {
2323        ensureNotNull(dn, mods);
2324    
2325        return modify(new ModifyRequest(dn, mods));
2326      }
2327    
2328    
2329    
2330      /**
2331       * Processes a modify request from the provided LDIF representation of the
2332       * changes.
2333       *
2334       * @param  ldifModificationLines  The lines that comprise an LDIF
2335       *                                representation of a modify change record.
2336       *                                It must not be {@code null} or empty.
2337       *
2338       * @return  The result of processing the modify operation.
2339       *
2340       * @throws  LDIFException  If the provided set of lines cannot be parsed as an
2341       *                         LDIF modify change record.
2342       *
2343       * @throws  LDAPException  If the server rejects the modify request, or if a
2344       *                         problem is encountered while sending the request or
2345       *                         reading the response.
2346       *
2347       */
2348      public LDAPResult modify(final String... ldifModificationLines)
2349             throws LDIFException, LDAPException
2350      {
2351        ensureNotNull(ldifModificationLines);
2352    
2353        return modify(new ModifyRequest(ldifModificationLines));
2354      }
2355    
2356    
2357    
2358      /**
2359       * Processes the provided modify request.
2360       *
2361       * @param  modifyRequest  The modify request to be processed.  It must not be
2362       *                        {@code null}.
2363       *
2364       * @return  The result of processing the modify operation.
2365       *
2366       * @throws  LDAPException  If the server rejects the modify request, or if a
2367       *                         problem is encountered while sending the request or
2368       *                         reading the response.
2369       */
2370      public LDAPResult modify(final ModifyRequest modifyRequest)
2371             throws LDAPException
2372      {
2373        ensureNotNull(modifyRequest);
2374    
2375        final LDAPResult ldapResult = modifyRequest.process(this, 1);
2376    
2377        switch (ldapResult.getResultCode().intValue())
2378        {
2379          case ResultCode.SUCCESS_INT_VALUE:
2380          case ResultCode.NO_OPERATION_INT_VALUE:
2381            return ldapResult;
2382    
2383          default:
2384            throw new LDAPException(ldapResult);
2385        }
2386      }
2387    
2388    
2389    
2390      /**
2391       * Processes the provided modify request.
2392       *
2393       * @param  modifyRequest  The modify request to be processed.  It must not be
2394       *                        {@code null}.
2395       *
2396       * @return  The result of processing the modify operation.
2397       *
2398       * @throws  LDAPException  If the server rejects the modify request, or if a
2399       *                         problem is encountered while sending the request or
2400       *                         reading the response.
2401       */
2402      public LDAPResult modify(final ReadOnlyModifyRequest modifyRequest)
2403             throws LDAPException
2404      {
2405        return modify((ModifyRequest) modifyRequest);
2406      }
2407    
2408    
2409    
2410      /**
2411       * Processes the provided modify request as an asynchronous operation.
2412       *
2413       * @param  modifyRequest   The modify request to be processed.  It must not be
2414       *                         {@code null}.
2415       * @param  resultListener  The async result listener to use to handle the
2416       *                         response for the modify operation.  It must not be
2417       *                         {@code null}.
2418       *
2419       * @return  An async request ID that may be used to reference the operation.
2420       *
2421       * @throws  LDAPException  If a problem occurs while sending the request.
2422       */
2423      public AsyncRequestID asyncModify(final ModifyRequest modifyRequest,
2424                                 final AsyncResultListener resultListener)
2425             throws LDAPException
2426      {
2427        ensureNotNull(modifyRequest, resultListener);
2428    
2429        if (synchronousMode())
2430        {
2431          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2432               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2433        }
2434    
2435        return modifyRequest.processAsync(this, resultListener);
2436      }
2437    
2438    
2439    
2440      /**
2441       * Processes the provided modify request as an asynchronous operation.
2442       *
2443       * @param  modifyRequest   The modify request to be processed.  It must not be
2444       *                         {@code null}.
2445       * @param  resultListener  The async result listener to use to handle the
2446       *                         response for the modify operation.  It must not be
2447       *                         {@code null}.
2448       *
2449       * @return  An async request ID that may be used to reference the operation.
2450       *
2451       * @throws  LDAPException  If a problem occurs while sending the request.
2452       */
2453      public AsyncRequestID asyncModify(final ReadOnlyModifyRequest modifyRequest,
2454                                 final AsyncResultListener resultListener)
2455             throws LDAPException
2456      {
2457        if (synchronousMode())
2458        {
2459          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2460               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2461        }
2462    
2463        return asyncModify((ModifyRequest) modifyRequest, resultListener);
2464      }
2465    
2466    
2467    
2468      /**
2469       * Performs a modify DN operation with the provided information.
2470       *
2471       * @param  dn            The current DN for the entry to rename.  It must not
2472       *                       be {@code null}.
2473       * @param  newRDN        The new RDN to use for the entry.  It must not be
2474       *                       {@code null}.
2475       * @param  deleteOldRDN  Indicates whether to delete the current RDN value
2476       *                       from the entry.
2477       *
2478       * @return  The result of processing the modify DN operation.
2479       *
2480       * @throws  LDAPException  If the server rejects the modify DN request, or if
2481       *                         a problem is encountered while sending the request
2482       *                         or reading the response.
2483       */
2484      public LDAPResult modifyDN(final String dn, final String newRDN,
2485                                 final boolean deleteOldRDN)
2486             throws LDAPException
2487      {
2488        ensureNotNull(dn, newRDN);
2489    
2490        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN));
2491      }
2492    
2493    
2494    
2495      /**
2496       * Performs a modify DN operation with the provided information.
2497       *
2498       * @param  dn             The current DN for the entry to rename.  It must not
2499       *                        be {@code null}.
2500       * @param  newRDN         The new RDN to use for the entry.  It must not be
2501       *                        {@code null}.
2502       * @param  deleteOldRDN   Indicates whether to delete the current RDN value
2503       *                        from the entry.
2504       * @param  newSuperiorDN  The new superior DN for the entry.  It may be
2505       *                        {@code null} if the entry is not to be moved below a
2506       *                        new parent.
2507       *
2508       * @return  The result of processing the modify DN operation.
2509       *
2510       * @throws  LDAPException  If the server rejects the modify DN request, or if
2511       *                         a problem is encountered while sending the request
2512       *                         or reading the response.
2513       */
2514      public LDAPResult modifyDN(final String dn, final String newRDN,
2515                                 final boolean deleteOldRDN,
2516                                 final String newSuperiorDN)
2517             throws LDAPException
2518      {
2519        ensureNotNull(dn, newRDN);
2520    
2521        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN,
2522                                            newSuperiorDN));
2523      }
2524    
2525    
2526    
2527      /**
2528       * Processes the provided modify DN request.
2529       *
2530       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2531       *                          not be {@code null}.
2532       *
2533       * @return  The result of processing the modify DN operation.
2534       *
2535       * @throws  LDAPException  If the server rejects the modify DN request, or if
2536       *                         a problem is encountered while sending the request
2537       *                         or reading the response.
2538       */
2539      public LDAPResult modifyDN(final ModifyDNRequest modifyDNRequest)
2540             throws LDAPException
2541      {
2542        ensureNotNull(modifyDNRequest);
2543    
2544        final LDAPResult ldapResult = modifyDNRequest.process(this, 1);
2545    
2546        switch (ldapResult.getResultCode().intValue())
2547        {
2548          case ResultCode.SUCCESS_INT_VALUE:
2549          case ResultCode.NO_OPERATION_INT_VALUE:
2550            return ldapResult;
2551    
2552          default:
2553            throw new LDAPException(ldapResult);
2554        }
2555      }
2556    
2557    
2558    
2559      /**
2560       * Processes the provided modify DN request.
2561       *
2562       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2563       *                          not be {@code null}.
2564       *
2565       * @return  The result of processing the modify DN operation.
2566       *
2567       * @throws  LDAPException  If the server rejects the modify DN request, or if
2568       *                         a problem is encountered while sending the request
2569       *                         or reading the response.
2570       */
2571      public LDAPResult modifyDN(final ReadOnlyModifyDNRequest modifyDNRequest)
2572             throws LDAPException
2573      {
2574        return modifyDN((ModifyDNRequest) modifyDNRequest);
2575      }
2576    
2577    
2578    
2579      /**
2580       * Processes the provided modify DN request as an asynchronous operation.
2581       *
2582       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2583       *                          not be {@code null}.
2584       * @param  resultListener  The async result listener to use to handle the
2585       *                         response for the modify DN operation.  It must not
2586       *                         be {@code null}.
2587       *
2588       * @return  An async request ID that may be used to reference the operation.
2589       *
2590       * @throws  LDAPException  If a problem occurs while sending the request.
2591       */
2592      public AsyncRequestID asyncModifyDN(final ModifyDNRequest modifyDNRequest,
2593                                 final AsyncResultListener resultListener)
2594             throws LDAPException
2595      {
2596        ensureNotNull(modifyDNRequest, resultListener);
2597    
2598        if (synchronousMode())
2599        {
2600          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2601               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2602        }
2603    
2604        return modifyDNRequest.processAsync(this, resultListener);
2605      }
2606    
2607    
2608    
2609      /**
2610       * Processes the provided modify DN request as an asynchronous operation.
2611       *
2612       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2613       *                          not be {@code null}.
2614       * @param  resultListener  The async result listener to use to handle the
2615       *                         response for the modify DN operation.  It must not
2616       *                         be {@code null}.
2617       *
2618       * @return  An async request ID that may be used to reference the operation.
2619       *
2620       * @throws  LDAPException  If a problem occurs while sending the request.
2621       */
2622      public AsyncRequestID asyncModifyDN(
2623                                 final ReadOnlyModifyDNRequest modifyDNRequest,
2624                                 final AsyncResultListener resultListener)
2625             throws LDAPException
2626      {
2627        if (synchronousMode())
2628        {
2629          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2630               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2631        }
2632    
2633        return asyncModifyDN((ModifyDNRequest) modifyDNRequest, resultListener);
2634      }
2635    
2636    
2637    
2638      /**
2639       * Processes a search operation with the provided information.  The search
2640       * result entries and references will be collected internally and included in
2641       * the {@code SearchResult} object that is returned.
2642       *
2643       * @param  baseDN      The base DN for the search request.  It must not be
2644       *                     {@code null}.
2645       * @param  scope       The scope that specifies the range of entries that
2646       *                     should be examined for the search.
2647       * @param  filter      The string representation of the filter to use to
2648       *                     identify matching entries.  It must not be
2649       *                     {@code null}.
2650       * @param  attributes  The set of attributes that should be returned in
2651       *                     matching entries.  It may be {@code null} or empty if
2652       *                     the default attribute set (all user attributes) is to
2653       *                     be requested.
2654       *
2655       * @return  A search result object that provides information about the
2656       *          processing of the search, including the set of matching entries
2657       *          and search references returned by the server.
2658       *
2659       * @throws  LDAPSearchException  If the search does not complete successfully,
2660       *                               or if a problem is encountered while parsing
2661       *                               the provided filter string, sending the
2662       *                               request, or reading the response.
2663       */
2664      public SearchResult search(final String baseDN, final SearchScope scope,
2665                                 final String filter, final String... attributes)
2666             throws LDAPSearchException
2667      {
2668        ensureNotNull(baseDN, filter);
2669    
2670        try
2671        {
2672          return search(new SearchRequest(baseDN, scope, filter, attributes));
2673        }
2674        catch (LDAPSearchException lse)
2675        {
2676          debugException(lse);
2677          throw lse;
2678        }
2679        catch (LDAPException le)
2680        {
2681          debugException(le);
2682          throw new LDAPSearchException(le);
2683        }
2684      }
2685    
2686    
2687    
2688      /**
2689       * Processes a search operation with the provided information.  The search
2690       * result entries and references will be collected internally and included in
2691       * the {@code SearchResult} object that is returned.
2692       *
2693       * @param  baseDN      The base DN for the search request.  It must not be
2694       *                     {@code null}.
2695       * @param  scope       The scope that specifies the range of entries that
2696       *                     should be examined for the search.
2697       * @param  filter      The filter to use to identify matching entries.  It
2698       *                     must not be {@code null}.
2699       * @param  attributes  The set of attributes that should be returned in
2700       *                     matching entries.  It may be {@code null} or empty if
2701       *                     the default attribute set (all user attributes) is to
2702       *                     be requested.
2703       *
2704       * @return  A search result object that provides information about the
2705       *          processing of the search, including the set of matching entries
2706       *          and search references returned by the server.
2707       *
2708       * @throws  LDAPSearchException  If the search does not complete successfully,
2709       *                               or if a problem is encountered while sending
2710       *                               the request or reading the response.
2711       */
2712      public SearchResult search(final String baseDN, final SearchScope scope,
2713                                 final Filter filter, final String... attributes)
2714             throws LDAPSearchException
2715      {
2716        ensureNotNull(baseDN, filter);
2717    
2718        return search(new SearchRequest(baseDN, scope, filter, attributes));
2719      }
2720    
2721    
2722    
2723      /**
2724       * Processes a search operation with the provided information.
2725       *
2726       * @param  searchResultListener  The search result listener that should be
2727       *                               used to return results to the client.  It may
2728       *                               be {@code null} if the search results should
2729       *                               be collected internally and returned in the
2730       *                               {@code SearchResult} object.
2731       * @param  baseDN                The base DN for the search request.  It must
2732       *                               not be {@code null}.
2733       * @param  scope                 The scope that specifies the range of entries
2734       *                               that should be examined for the search.
2735       * @param  filter                The string representation of the filter to
2736       *                               use to identify matching entries.  It must
2737       *                               not be {@code null}.
2738       * @param  attributes            The set of attributes that should be returned
2739       *                               in matching entries.  It may be {@code null}
2740       *                               or empty if the default attribute set (all
2741       *                               user attributes) is to be requested.
2742       *
2743       * @return  A search result object that provides information about the
2744       *          processing of the search, potentially including the set of
2745       *          matching entries and search references returned by the server.
2746       *
2747       * @throws  LDAPSearchException  If the search does not complete successfully,
2748       *                               or if a problem is encountered while parsing
2749       *                               the provided filter string, sending the
2750       *                               request, or reading the response.
2751       */
2752      public SearchResult search(final SearchResultListener searchResultListener,
2753                                 final String baseDN, final SearchScope scope,
2754                                 final String filter, final String... attributes)
2755             throws LDAPSearchException
2756      {
2757        ensureNotNull(baseDN, filter);
2758    
2759        try
2760        {
2761          return search(new SearchRequest(searchResultListener, baseDN, scope,
2762                                          filter, attributes));
2763        }
2764        catch (LDAPSearchException lse)
2765        {
2766          debugException(lse);
2767          throw lse;
2768        }
2769        catch (LDAPException le)
2770        {
2771          debugException(le);
2772          throw new LDAPSearchException(le);
2773        }
2774      }
2775    
2776    
2777    
2778      /**
2779       * Processes a search operation with the provided information.
2780       *
2781       * @param  searchResultListener  The search result listener that should be
2782       *                               used to return results to the client.  It may
2783       *                               be {@code null} if the search results should
2784       *                               be collected internally and returned in the
2785       *                               {@code SearchResult} object.
2786       * @param  baseDN                The base DN for the search request.  It must
2787       *                               not be {@code null}.
2788       * @param  scope                 The scope that specifies the range of entries
2789       *                               that should be examined for the search.
2790       * @param  filter                The filter to use to identify matching
2791       *                               entries.  It must not be {@code null}.
2792       * @param  attributes            The set of attributes that should be returned
2793       *                               in matching entries.  It may be {@code null}
2794       *                               or empty if the default attribute set (all
2795       *                               user attributes) is to be requested.
2796       *
2797       * @return  A search result object that provides information about the
2798       *          processing of the search, potentially including the set of
2799       *          matching entries and search references returned by the server.
2800       *
2801       * @throws  LDAPSearchException  If the search does not complete successfully,
2802       *                               or if a problem is encountered while sending
2803       *                               the request or reading the response.
2804       */
2805      public SearchResult search(final SearchResultListener searchResultListener,
2806                                 final String baseDN, final SearchScope scope,
2807                                 final Filter filter, final String... attributes)
2808             throws LDAPSearchException
2809      {
2810        ensureNotNull(baseDN, filter);
2811    
2812        try
2813        {
2814          return search(new SearchRequest(searchResultListener, baseDN, scope,
2815                                          filter, attributes));
2816        }
2817        catch (LDAPSearchException lse)
2818        {
2819          debugException(lse);
2820          throw lse;
2821        }
2822        catch (LDAPException le)
2823        {
2824          debugException(le);
2825          throw new LDAPSearchException(le);
2826        }
2827      }
2828    
2829    
2830    
2831      /**
2832       * Processes a search operation with the provided information.  The search
2833       * result entries and references will be collected internally and included in
2834       * the {@code SearchResult} object that is returned.
2835       *
2836       * @param  baseDN       The base DN for the search request.  It must not be
2837       *                      {@code null}.
2838       * @param  scope        The scope that specifies the range of entries that
2839       *                      should be examined for the search.
2840       * @param  derefPolicy  The dereference policy the server should use for any
2841       *                      aliases encountered while processing the search.
2842       * @param  sizeLimit    The maximum number of entries that the server should
2843       *                      return for the search.  A value of zero indicates that
2844       *                      there should be no limit.
2845       * @param  timeLimit    The maximum length of time in seconds that the server
2846       *                      should spend processing this search request.  A value
2847       *                      of zero indicates that there should be no limit.
2848       * @param  typesOnly    Indicates whether to return only attribute names in
2849       *                      matching entries, or both attribute names and values.
2850       * @param  filter       The string representation of the filter to use to
2851       *                      identify matching entries.  It must not be
2852       *                      {@code null}.
2853       * @param  attributes   The set of attributes that should be returned in
2854       *                      matching entries.  It may be {@code null} or empty if
2855       *                      the default attribute set (all user attributes) is to
2856       *                      be requested.
2857       *
2858       * @return  A search result object that provides information about the
2859       *          processing of the search, including the set of matching entries
2860       *          and search references returned by the server.
2861       *
2862       * @throws  LDAPSearchException  If the search does not complete successfully,
2863       *                               or if a problem is encountered while parsing
2864       *                               the provided filter string, sending the
2865       *                               request, or reading the response.
2866       */
2867      public SearchResult search(final String baseDN, final SearchScope scope,
2868                                 final DereferencePolicy derefPolicy,
2869                                 final int sizeLimit, final int timeLimit,
2870                                 final boolean typesOnly, final String filter,
2871                                 final String... attributes)
2872             throws LDAPSearchException
2873      {
2874        ensureNotNull(baseDN, filter);
2875    
2876        try
2877        {
2878          return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
2879                                          timeLimit, typesOnly, filter,
2880                                          attributes));
2881        }
2882        catch (LDAPSearchException lse)
2883        {
2884          debugException(lse);
2885          throw lse;
2886        }
2887        catch (LDAPException le)
2888        {
2889          debugException(le);
2890          throw new LDAPSearchException(le);
2891        }
2892      }
2893    
2894    
2895    
2896      /**
2897       * Processes a search operation with the provided information.  The search
2898       * result entries and references will be collected internally and included in
2899       * the {@code SearchResult} object that is returned.
2900       *
2901       * @param  baseDN       The base DN for the search request.  It must not be
2902       *                      {@code null}.
2903       * @param  scope        The scope that specifies the range of entries that
2904       *                      should be examined for the search.
2905       * @param  derefPolicy  The dereference policy the server should use for any
2906       *                      aliases encountered while processing the search.
2907       * @param  sizeLimit    The maximum number of entries that the server should
2908       *                      return for the search.  A value of zero indicates that
2909       *                      there should be no limit.
2910       * @param  timeLimit    The maximum length of time in seconds that the server
2911       *                      should spend processing this search request.  A value
2912       *                      of zero indicates that there should be no limit.
2913       * @param  typesOnly    Indicates whether to return only attribute names in
2914       *                      matching entries, or both attribute names and values.
2915       * @param  filter       The filter to use to identify matching entries.  It
2916       *                      must not be {@code null}.
2917       * @param  attributes   The set of attributes that should be returned in
2918       *                      matching entries.  It may be {@code null} or empty if
2919       *                      the default attribute set (all user attributes) is to
2920       *                      be requested.
2921       *
2922       * @return  A search result object that provides information about the
2923       *          processing of the search, including the set of matching entries
2924       *          and search references returned by the server.
2925       *
2926       * @throws  LDAPSearchException  If the search does not complete successfully,
2927       *                               or if a problem is encountered while sending
2928       *                               the request or reading the response.
2929       */
2930      public SearchResult search(final String baseDN, final SearchScope scope,
2931                                 final DereferencePolicy derefPolicy,
2932                                 final int sizeLimit, final int timeLimit,
2933                                 final boolean typesOnly, final Filter filter,
2934                                 final String... attributes)
2935             throws LDAPSearchException
2936      {
2937        ensureNotNull(baseDN, filter);
2938    
2939        return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
2940                                        timeLimit, typesOnly, filter, attributes));
2941      }
2942    
2943    
2944    
2945      /**
2946       * Processes a search operation with the provided information.
2947       *
2948       * @param  searchResultListener  The search result listener that should be
2949       *                               used to return results to the client.  It may
2950       *                               be {@code null} if the search results should
2951       *                               be collected internally and returned in the
2952       *                               {@code SearchResult} object.
2953       * @param  baseDN                The base DN for the search request.  It must
2954       *                               not be {@code null}.
2955       * @param  scope                 The scope that specifies the range of entries
2956       *                               that should be examined for the search.
2957       * @param  derefPolicy           The dereference policy the server should use
2958       *                               for any aliases encountered while processing
2959       *                               the search.
2960       * @param  sizeLimit             The maximum number of entries that the server
2961       *                               should return for the search.  A value of
2962       *                               zero indicates that there should be no limit.
2963       * @param  timeLimit             The maximum length of time in seconds that
2964       *                               the server should spend processing this
2965       *                               search request.  A value of zero indicates
2966       *                               that there should be no limit.
2967       * @param  typesOnly             Indicates whether to return only attribute
2968       *                               names in matching entries, or both attribute
2969       *                               names and values.
2970       * @param  filter                The string representation of the filter to
2971       *                               use to identify matching entries.  It must
2972       *                               not be {@code null}.
2973       * @param  attributes            The set of attributes that should be returned
2974       *                               in matching entries.  It may be {@code null}
2975       *                               or empty if the default attribute set (all
2976       *                               user attributes) is to be requested.
2977       *
2978       * @return  A search result object that provides information about the
2979       *          processing of the search, potentially including the set of
2980       *          matching entries and search references returned by the server.
2981       *
2982       * @throws  LDAPSearchException  If the search does not complete successfully,
2983       *                               or if a problem is encountered while parsing
2984       *                               the provided filter string, sending the
2985       *                               request, or reading the response.
2986       */
2987      public SearchResult search(final SearchResultListener searchResultListener,
2988                                 final String baseDN, final SearchScope scope,
2989                                 final DereferencePolicy derefPolicy,
2990                                 final int sizeLimit, final int timeLimit,
2991                                 final boolean typesOnly, final String filter,
2992                                 final String... attributes)
2993             throws LDAPSearchException
2994      {
2995        ensureNotNull(baseDN, filter);
2996    
2997        try
2998        {
2999          return search(new SearchRequest(searchResultListener, baseDN, scope,
3000                                          derefPolicy, sizeLimit, timeLimit,
3001                                          typesOnly, filter, attributes));
3002        }
3003        catch (LDAPSearchException lse)
3004        {
3005          debugException(lse);
3006          throw lse;
3007        }
3008        catch (LDAPException le)
3009        {
3010          debugException(le);
3011          throw new LDAPSearchException(le);
3012        }
3013      }
3014    
3015    
3016    
3017      /**
3018       * Processes a search operation with the provided information.
3019       *
3020       *
3021       * @param  searchResultListener  The search result listener that should be
3022       *                               used to return results to the client.  It may
3023       *                               be {@code null} if the search results should
3024       *                               be collected internally and returned in the
3025       *                               {@code SearchResult} object.
3026       * @param  baseDN                The base DN for the search request.  It must
3027       *                               not be {@code null}.
3028       * @param  scope                 The scope that specifies the range of entries
3029       *                               that should be examined for the search.
3030       * @param  derefPolicy           The dereference policy the server should use
3031       *                               for any aliases encountered while processing
3032       *                               the search.
3033       * @param  sizeLimit             The maximum number of entries that the server
3034       *                               should return for the search.  A value of
3035       *                               zero indicates that there should be no limit.
3036       * @param  timeLimit             The maximum length of time in seconds that
3037       *                               the server should spend processing this
3038       *                               search request.  A value of zero indicates
3039       *                               that there should be no limit.
3040       * @param  typesOnly             Indicates whether to return only attribute
3041       *                               names in matching entries, or both attribute
3042       *                               names and values.
3043       * @param  filter                The filter to use to identify matching
3044       *                               entries.  It must not be {@code null}.
3045       * @param  attributes            The set of attributes that should be returned
3046       *                               in matching entries.  It may be {@code null}
3047       *                               or empty if the default attribute set (all
3048       *                               user attributes) is to be requested.
3049       *
3050       * @return  A search result object that provides information about the
3051       *          processing of the search, potentially including the set of
3052       *          matching entries and search references returned by the server.
3053       *
3054       * @throws  LDAPSearchException  If the search does not complete successfully,
3055       *                               or if a problem is encountered while sending
3056       *                               the request or reading the response.
3057       */
3058      public SearchResult search(final SearchResultListener searchResultListener,
3059                                 final String baseDN, final SearchScope scope,
3060                                 final DereferencePolicy derefPolicy,
3061                                 final int sizeLimit, final int timeLimit,
3062                                 final boolean typesOnly, final Filter filter,
3063                                 final String... attributes)
3064             throws LDAPSearchException
3065      {
3066        ensureNotNull(baseDN, filter);
3067    
3068        return search(new SearchRequest(searchResultListener, baseDN, scope,
3069                                        derefPolicy, sizeLimit, timeLimit,
3070                                        typesOnly, filter, attributes));
3071      }
3072    
3073    
3074    
3075      /**
3076       * Processes the provided search request.
3077       *
3078       * @param  searchRequest  The search request to be processed.  It must not be
3079       *                        {@code null}.
3080       *
3081       * @return  A search result object that provides information about the
3082       *          processing of the search, potentially including the set of
3083       *          matching entries and search references returned by the server.
3084       *
3085       * @throws  LDAPSearchException  If the search does not complete successfully,
3086       *                               or if a problem is encountered while sending
3087       *                               the request or reading the response.
3088       */
3089      public SearchResult search(final SearchRequest searchRequest)
3090             throws LDAPSearchException
3091      {
3092        ensureNotNull(searchRequest);
3093    
3094        final SearchResult searchResult;
3095        try
3096        {
3097          searchResult = searchRequest.process(this, 1);
3098        }
3099        catch (LDAPSearchException lse)
3100        {
3101          debugException(lse);
3102          throw lse;
3103        }
3104        catch (LDAPException le)
3105        {
3106          debugException(le);
3107          throw new LDAPSearchException(le);
3108        }
3109    
3110        if (! searchResult.getResultCode().equals(ResultCode.SUCCESS))
3111        {
3112          throw new LDAPSearchException(searchResult);
3113        }
3114    
3115        return searchResult;
3116      }
3117    
3118    
3119    
3120      /**
3121       * Processes the provided search request.
3122       *
3123       * @param  searchRequest  The search request to be processed.  It must not be
3124       *                        {@code null}.
3125       *
3126       * @return  A search result object that provides information about the
3127       *          processing of the search, potentially including the set of
3128       *          matching entries and search references returned by the server.
3129       *
3130       * @throws  LDAPSearchException  If the search does not complete successfully,
3131       *                               or if a problem is encountered while sending
3132       *                               the request or reading the response.
3133       */
3134      public SearchResult search(final ReadOnlySearchRequest searchRequest)
3135             throws LDAPSearchException
3136      {
3137        return search((SearchRequest) searchRequest);
3138      }
3139    
3140    
3141    
3142      /**
3143       * Processes a search operation with the provided information.  It is expected
3144       * that at most one entry will be returned from the search, and that no
3145       * additional content from the successful search result (e.g., diagnostic
3146       * message or response controls) are needed.
3147       *
3148       * @param  baseDN      The base DN for the search request.  It must not be
3149       *                     {@code null}.
3150       * @param  scope       The scope that specifies the range of entries that
3151       *                     should be examined for the search.
3152       * @param  filter      The string representation of the filter to use to
3153       *                     identify matching entries.  It must not be
3154       *                     {@code null}.
3155       * @param  attributes  The set of attributes that should be returned in
3156       *                     matching entries.  It may be {@code null} or empty if
3157       *                     the default attribute set (all user attributes) is to
3158       *                     be requested.
3159       *
3160       * @return  The entry that was returned from the search, or {@code null} if no
3161       *          entry was returned or the base entry does not exist.
3162       *
3163       * @throws  LDAPSearchException  If the search does not complete successfully,
3164       *                               if more than a single entry is returned, or
3165       *                               if a problem is encountered while parsing the
3166       *                               provided filter string, sending the request,
3167       *                               or reading the response.
3168       */
3169      public SearchResultEntry searchForEntry(final String baseDN,
3170                                              final SearchScope scope,
3171                                              final String filter,
3172                                              final String... attributes)
3173             throws LDAPSearchException
3174      {
3175        final SearchRequest r;
3176        try
3177        {
3178          r = new SearchRequest(baseDN, scope, DereferencePolicy.NEVER, 1, 0, false,
3179               filter, attributes);
3180        }
3181        catch (final LDAPException le)
3182        {
3183          debugException(le);
3184          throw new LDAPSearchException(le);
3185        }
3186    
3187        return searchForEntry(r);
3188      }
3189    
3190    
3191    
3192      /**
3193       * Processes a search operation with the provided information.  It is expected
3194       * that at most one entry will be returned from the search, and that no
3195       * additional content from the successful search result (e.g., diagnostic
3196       * message or response controls) are needed.
3197       *
3198       * @param  baseDN      The base DN for the search request.  It must not be
3199       *                     {@code null}.
3200       * @param  scope       The scope that specifies the range of entries that
3201       *                     should be examined for the search.
3202       * @param  filter      The string representation of the filter to use to
3203       *                     identify matching entries.  It must not be
3204       *                     {@code null}.
3205       * @param  attributes  The set of attributes that should be returned in
3206       *                     matching entries.  It may be {@code null} or empty if
3207       *                     the default attribute set (all user attributes) is to
3208       *                     be requested.
3209       *
3210       * @return  The entry that was returned from the search, or {@code null} if no
3211       *          entry was returned or the base entry does not exist.
3212       *
3213       * @throws  LDAPSearchException  If the search does not complete successfully,
3214       *                               if more than a single entry is returned, or
3215       *                               if a problem is encountered while parsing the
3216       *                               provided filter string, sending the request,
3217       *                               or reading the response.
3218       */
3219      public SearchResultEntry searchForEntry(final String baseDN,
3220                                              final SearchScope scope,
3221                                              final Filter filter,
3222                                              final String... attributes)
3223             throws LDAPSearchException
3224      {
3225        return searchForEntry(new SearchRequest(baseDN, scope,
3226             DereferencePolicy.NEVER, 1, 0, false, filter, attributes));
3227      }
3228    
3229    
3230    
3231      /**
3232       * Processes a search operation with the provided information.  It is expected
3233       * that at most one entry will be returned from the search, and that no
3234       * additional content from the successful search result (e.g., diagnostic
3235       * message or response controls) are needed.
3236       *
3237       * @param  baseDN       The base DN for the search request.  It must not be
3238       *                      {@code null}.
3239       * @param  scope        The scope that specifies the range of entries that
3240       *                      should be examined for the search.
3241       * @param  derefPolicy  The dereference policy the server should use for any
3242       *                      aliases encountered while processing the search.
3243       * @param  timeLimit    The maximum length of time in seconds that the server
3244       *                      should spend processing this search request.  A value
3245       *                      of zero indicates that there should be no limit.
3246       * @param  typesOnly    Indicates whether to return only attribute names in
3247       *                      matching entries, or both attribute names and values.
3248       * @param  filter       The string representation of the filter to use to
3249       *                      identify matching entries.  It must not be
3250       *                      {@code null}.
3251       * @param  attributes   The set of attributes that should be returned in
3252       *                      matching entries.  It may be {@code null} or empty if
3253       *                      the default attribute set (all user attributes) is to
3254       *                      be requested.
3255       *
3256       * @return  The entry that was returned from the search, or {@code null} if no
3257       *          entry was returned or the base entry does not exist.
3258       *
3259       * @throws  LDAPSearchException  If the search does not complete successfully,
3260       *                               if more than a single entry is returned, or
3261       *                               if a problem is encountered while parsing the
3262       *                               provided filter string, sending the request,
3263       *                               or reading the response.
3264       */
3265      public SearchResultEntry searchForEntry(final String baseDN,
3266                                              final SearchScope scope,
3267                                              final DereferencePolicy derefPolicy,
3268                                              final int timeLimit,
3269                                              final boolean typesOnly,
3270                                              final String filter,
3271                                              final String... attributes)
3272             throws LDAPSearchException
3273      {
3274        final SearchRequest r;
3275        try
3276        {
3277          r = new SearchRequest(baseDN, scope, derefPolicy, 1, timeLimit, typesOnly,
3278               filter, attributes);
3279        }
3280        catch (final LDAPException le)
3281        {
3282          debugException(le);
3283          throw new LDAPSearchException(le);
3284        }
3285    
3286        return searchForEntry(r);
3287      }
3288    
3289    
3290    
3291      /**
3292       * Processes a search operation with the provided information.  It is expected
3293       * that at most one entry will be returned from the search, and that no
3294       * additional content from the successful search result (e.g., diagnostic
3295       * message or response controls) are needed.
3296       *
3297       * @param  baseDN       The base DN for the search request.  It must not be
3298       *                      {@code null}.
3299       * @param  scope        The scope that specifies the range of entries that
3300       *                      should be examined for the search.
3301       * @param  derefPolicy  The dereference policy the server should use for any
3302       *                      aliases encountered while processing the search.
3303       * @param  timeLimit    The maximum length of time in seconds that the server
3304       *                      should spend processing this search request.  A value
3305       *                      of zero indicates that there should be no limit.
3306       * @param  typesOnly    Indicates whether to return only attribute names in
3307       *                      matching entries, or both attribute names and values.
3308       * @param  filter       The filter to use to identify matching entries.  It
3309       *                      must not be {@code null}.
3310       * @param  attributes   The set of attributes that should be returned in
3311       *                      matching entries.  It may be {@code null} or empty if
3312       *                      the default attribute set (all user attributes) is to
3313       *                      be requested.
3314       *
3315       * @return  The entry that was returned from the search, or {@code null} if no
3316       *          entry was returned or the base entry does not exist.
3317       *
3318       * @throws  LDAPSearchException  If the search does not complete successfully,
3319       *                               if more than a single entry is returned, or
3320       *                               if a problem is encountered while parsing the
3321       *                               provided filter string, sending the request,
3322       *                               or reading the response.
3323       */
3324      public SearchResultEntry searchForEntry(final String baseDN,
3325                                              final SearchScope scope,
3326                                              final DereferencePolicy derefPolicy,
3327                                              final int timeLimit,
3328                                              final boolean typesOnly,
3329                                              final Filter filter,
3330                                              final String... attributes)
3331           throws LDAPSearchException
3332      {
3333        return searchForEntry(new SearchRequest(baseDN, scope, derefPolicy, 1,
3334             timeLimit, typesOnly, filter, attributes));
3335      }
3336    
3337    
3338    
3339      /**
3340       * Processes the provided search request.  It is expected that at most one
3341       * entry will be returned from the search, and that no additional content from
3342       * the successful search result (e.g., diagnostic message or response
3343       * controls) are needed.
3344       *
3345       * @param  searchRequest  The search request to be processed.  If it is
3346       *                        configured with a search result listener or a size
3347       *                        limit other than one, then the provided request will
3348       *                        be duplicated with the appropriate settings.
3349       *
3350       * It must not be
3351       *                        {@code null}, it must not be configured with a
3352       *                        search result listener, and it should be configured
3353       *                        with a size limit of one.
3354       *
3355       * @return  The entry that was returned from the search, or {@code null} if no
3356       *          entry was returned or the base entry does not exist.
3357       *
3358       * @throws  LDAPSearchException  If the search does not complete successfully,
3359       *                               if more than a single entry is returned, or
3360       *                               if a problem is encountered while parsing the
3361       *                               provided filter string, sending the request,
3362       *                               or reading the response.
3363       */
3364      public SearchResultEntry searchForEntry(final SearchRequest searchRequest)
3365             throws LDAPSearchException
3366      {
3367        final SearchRequest r;
3368        if ((searchRequest.getSearchResultListener() != null) ||
3369            (searchRequest.getSizeLimit() != 1))
3370        {
3371          r = new SearchRequest(searchRequest.getBaseDN(), searchRequest.getScope(),
3372               searchRequest.getDereferencePolicy(), 1,
3373               searchRequest.getTimeLimitSeconds(), searchRequest.typesOnly(),
3374               searchRequest.getFilter(), searchRequest.getAttributes());
3375    
3376          r.setFollowReferrals(searchRequest.followReferralsInternal());
3377          r.setResponseTimeoutMillis(searchRequest.getResponseTimeoutMillis(null));
3378    
3379          if (searchRequest.hasControl())
3380          {
3381            r.setControlsInternal(searchRequest.getControls());
3382          }
3383        }
3384        else
3385        {
3386          r = searchRequest;
3387        }
3388    
3389        final SearchResult result;
3390        try
3391        {
3392          result = search(r);
3393        }
3394        catch (final LDAPSearchException lse)
3395        {
3396          debugException(lse);
3397    
3398          if (lse.getResultCode() == ResultCode.NO_SUCH_OBJECT)
3399          {
3400            return null;
3401          }
3402    
3403          throw lse;
3404        }
3405    
3406        if (result.getEntryCount() == 0)
3407        {
3408          return null;
3409        }
3410        else
3411        {
3412          return result.getSearchEntries().get(0);
3413        }
3414      }
3415    
3416    
3417    
3418      /**
3419       * Processes the provided search request.  It is expected that at most one
3420       * entry will be returned from the search, and that no additional content from
3421       * the successful search result (e.g., diagnostic message or response
3422       * controls) are needed.
3423       *
3424       * @param  searchRequest  The search request to be processed.  If it is
3425       *                        configured with a search result listener or a size
3426       *                        limit other than one, then the provided request will
3427       *                        be duplicated with the appropriate settings.
3428       *
3429       * @return  The entry that was returned from the search, or {@code null} if no
3430       *          entry was returned or the base entry does not exist.
3431       *
3432       * @throws  LDAPSearchException  If the search does not complete successfully,
3433       *                               if more than a single entry is returned, or
3434       *                               if a problem is encountered while parsing the
3435       *                               provided filter string, sending the request,
3436       *                               or reading the response.
3437       */
3438      public SearchResultEntry searchForEntry(
3439                                    final ReadOnlySearchRequest searchRequest)
3440             throws LDAPSearchException
3441      {
3442        return searchForEntry((SearchRequest) searchRequest);
3443      }
3444    
3445    
3446    
3447      /**
3448       * Processes the provided search request as an asynchronous operation.
3449       *
3450       * @param  searchRequest  The search request to be processed.  It must not be
3451       *                        {@code null}, and it must be configured with a
3452       *                        search result listener that is an
3453       *                        {@code AsyncSearchResultListener}.
3454       *
3455       * @return  An async request ID that may be used to reference the operation.
3456       *
3457       * @throws  LDAPException  If the provided search request does not have a
3458       *                         search result listener that is an
3459       *                         {@code AsyncSearchResultListener}, or if a problem
3460       *                         occurs while sending the request.
3461       */
3462      public AsyncRequestID asyncSearch(final SearchRequest searchRequest)
3463             throws LDAPException
3464      {
3465        ensureNotNull(searchRequest);
3466    
3467        final SearchResultListener searchListener =
3468             searchRequest.getSearchResultListener();
3469        if (searchListener == null)
3470        {
3471          final LDAPException le = new LDAPException(ResultCode.PARAM_ERROR,
3472               ERR_ASYNC_SEARCH_NO_LISTENER.get());
3473          debugCodingError(le);
3474          throw le;
3475        }
3476        else if (! (searchListener instanceof AsyncSearchResultListener))
3477        {
3478          final LDAPException le = new LDAPException(ResultCode.PARAM_ERROR,
3479               ERR_ASYNC_SEARCH_INVALID_LISTENER.get());
3480          debugCodingError(le);
3481          throw le;
3482        }
3483    
3484        if (synchronousMode())
3485        {
3486          throw new LDAPException(ResultCode.NOT_SUPPORTED,
3487               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
3488        }
3489    
3490        return searchRequest.processAsync(this,
3491             (AsyncSearchResultListener) searchListener);
3492      }
3493    
3494    
3495    
3496      /**
3497       * Processes the provided search request as an asynchronous operation.
3498       *
3499       * @param  searchRequest  The search request to be processed.  It must not be
3500       *                        {@code null}, and it must be configured with a
3501       *                        search result listener that is an
3502       *                        {@code AsyncSearchResultListener}.
3503       *
3504       * @return  An async request ID that may be used to reference the operation.
3505       *
3506       * @throws  LDAPException  If the provided search request does not have a
3507       *                         search result listener that is an
3508       *                         {@code AsyncSearchResultListener}, or if a problem
3509       *                         occurs while sending the request.
3510       */
3511      public AsyncRequestID asyncSearch(final ReadOnlySearchRequest searchRequest)
3512             throws LDAPException
3513      {
3514        if (synchronousMode())
3515        {
3516          throw new LDAPException(ResultCode.NOT_SUPPORTED,
3517               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
3518        }
3519    
3520        return asyncSearch((SearchRequest) searchRequest);
3521      }
3522    
3523    
3524    
3525      /**
3526       * Processes the provided generic request and returns the result.  This may
3527       * be useful for cases in which it is not known what type of operation the
3528       * request represents.
3529       *
3530       * @param  request  The request to be processed.
3531       *
3532       * @return  The result obtained from processing the request.
3533       *
3534       * @throws  LDAPException  If a problem occurs while sending the request or
3535       *                         reading the response.  Note simply having a
3536       *                         non-success result code in the response will not
3537       *                         cause an exception to be thrown.
3538       */
3539      public LDAPResult processOperation(final LDAPRequest request)
3540             throws LDAPException
3541      {
3542        return request.process(this, 1);
3543      }
3544    
3545    
3546    
3547      /**
3548       * Retrieves the referral connector that should be used to establish
3549       * connections for use when following referrals.
3550       *
3551       * @return  The referral connector that should be used to establish
3552       *          connections for use when following referrals.
3553       */
3554      public ReferralConnector getReferralConnector()
3555      {
3556        if (referralConnector == null)
3557        {
3558          return this;
3559        }
3560        else
3561        {
3562          return referralConnector;
3563        }
3564      }
3565    
3566    
3567    
3568      /**
3569       * Specifies the referral connector that should be used to establish
3570       * connections for use when following referrals.
3571       *
3572       * @param  referralConnector  The referral connector that should be used to
3573       *                            establish connections for use when following
3574       *                            referrals.
3575       */
3576      public void setReferralConnector(final ReferralConnector referralConnector)
3577      {
3578        if (referralConnector == null)
3579        {
3580          this.referralConnector = this;
3581        }
3582        else
3583        {
3584          this.referralConnector = referralConnector;
3585        }
3586      }
3587    
3588    
3589    
3590      /**
3591       * Sends the provided LDAP message to the server over this connection.
3592       *
3593       * @param  message  The LDAP message to send to the target server.
3594       *
3595       * @throws  LDAPException  If a problem occurs while sending the request.
3596       */
3597      void sendMessage(final LDAPMessage message)
3598             throws LDAPException
3599      {
3600        final LDAPConnectionInternals internals = connectionInternals;
3601        if (internals == null)
3602        {
3603          throw new LDAPException(ResultCode.SERVER_DOWN,
3604                                  ERR_CONN_NOT_ESTABLISHED.get());
3605        }
3606        else
3607        {
3608          internals.sendMessage(message);
3609        }
3610      }
3611    
3612    
3613    
3614      /**
3615       * Retrieves the message ID that should be used for the next request sent
3616       * over this connection.
3617       *
3618       * @return  The message ID that should be used for the next request sent over
3619       *          this connection, or -1 if this connection is not established.
3620       */
3621      int nextMessageID()
3622      {
3623        final LDAPConnectionInternals internals = connectionInternals;
3624        if (internals == null)
3625        {
3626          return -1;
3627        }
3628        else
3629        {
3630          return internals.nextMessageID();
3631        }
3632      }
3633    
3634    
3635    
3636      /**
3637       * Sets the disconnect type, message, and cause for this connection, if those
3638       * values have not been previously set.  It will not overwrite any values that
3639       * had been previously set.
3640       * <BR><BR>
3641       * This method may be called by code which is not part of the LDAP SDK to
3642       * provide additional information about the reason for the closure.  In that
3643       * case, this method must be called before the call to
3644       * {@link LDAPConnection#close}.
3645       *
3646       * @param  type     The disconnect type.  It must not be {@code null}.
3647       * @param  message  A message providing additional information about the
3648       *                  disconnect.  It may be {@code null} if no message is
3649       *                  available.
3650       * @param  cause    The exception that was caught to trigger the disconnect.
3651       *                  It may be {@code null} if the disconnect was not triggered
3652       *                  by an exception.
3653       */
3654      public synchronized void setDisconnectInfo(final DisconnectType type,
3655                                                 final String message,
3656                                                 final Throwable cause)
3657      {
3658        ensureNotNull(type);
3659    
3660        // Don't overwrite any previous disconnect information.
3661        if (disconnectType != null)
3662        {
3663          return;
3664        }
3665    
3666        disconnectType    = type;
3667        disconnectMessage = message;
3668        disconnectCause   = cause;
3669      }
3670    
3671    
3672    
3673      /**
3674       * Retrieves the disconnect type for this connection, if available.
3675       *
3676       * @return  The disconnect type for this connection, or {@code null} if no
3677       *          disconnect type has been set.
3678       */
3679      public DisconnectType getDisconnectType()
3680      {
3681        return disconnectType;
3682      }
3683    
3684    
3685    
3686      /**
3687       * Retrieves the disconnect message for this connection, which may provide
3688       * additional information about the reason for the disconnect, if available.
3689       *
3690       * @return  The disconnect message for this connection, or {@code null} if
3691       *          no disconnect message has been set.
3692       */
3693      public String getDisconnectMessage()
3694      {
3695        return disconnectMessage;
3696      }
3697    
3698    
3699    
3700      /**
3701       * Retrieves the disconnect cause for this connection, which is an exception
3702       * or error that triggered the connection termination, if available.
3703       *
3704       * @return  The disconnect cause for this connection, or {@code null} if no
3705       *          disconnect cause has been set.
3706       */
3707      public Throwable getDisconnectCause()
3708      {
3709        return disconnectCause;
3710      }
3711    
3712    
3713    
3714      /**
3715       * Indicates that this connection has been closed and is no longer available
3716       * for use.
3717       */
3718      void setClosed()
3719      {
3720        connectionStatistics.incrementNumDisconnects();
3721        final LDAPConnectionInternals internals = connectionInternals;
3722        if (internals != null)
3723        {
3724          internals.close();
3725          connectionInternals = null;
3726        }
3727    
3728        cachedSchema = null;
3729    
3730        if (timer != null)
3731        {
3732          timer.cancel();
3733          timer = null;
3734        }
3735      }
3736    
3737    
3738    
3739      /**
3740       * Registers the provided response acceptor with the connection reader.
3741       *
3742       * @param  messageID         The message ID for which the acceptor is to be
3743       *                           registered.
3744       * @param  responseAcceptor  The response acceptor to register.
3745       *
3746       * @throws  LDAPException  If another message acceptor is already registered
3747       *                         with the provided message ID.
3748       */
3749      void registerResponseAcceptor(final int messageID,
3750                                    final ResponseAcceptor responseAcceptor)
3751           throws LDAPException
3752      {
3753        final LDAPConnectionInternals internals = connectionInternals;
3754        if (internals == null)
3755        {
3756          throw new LDAPException(ResultCode.SERVER_DOWN,
3757                                  ERR_CONN_NOT_ESTABLISHED.get());
3758        }
3759        else
3760        {
3761          internals.registerResponseAcceptor(messageID, responseAcceptor);
3762        }
3763      }
3764    
3765    
3766    
3767      /**
3768       * Deregisters the response acceptor associated with the provided message ID.
3769       *
3770       * @param  messageID  The message ID for which to deregister the associated
3771       *                    response acceptor.
3772       */
3773      void deregisterResponseAcceptor(final int messageID)
3774      {
3775        final LDAPConnectionInternals internals = connectionInternals;
3776        if (internals != null)
3777        {
3778          internals.deregisterResponseAcceptor(messageID);
3779        }
3780      }
3781    
3782    
3783    
3784      /**
3785       * Retrieves a timer for use with this connection, creating one if necessary.
3786       *
3787       * @return  A timer for use with this connection.
3788       */
3789      synchronized Timer getTimer()
3790      {
3791        if (timer == null)
3792        {
3793          timer = new Timer("Timer thread for " + toString(), true);
3794        }
3795    
3796        return timer;
3797      }
3798    
3799    
3800    
3801      /**
3802       * {@inheritDoc}
3803       */
3804      public LDAPConnection getReferralConnection(final LDAPURL referralURL,
3805                                                  final LDAPConnection connection)
3806             throws LDAPException
3807      {
3808        final String host = referralURL.getHost();
3809        final int    port = referralURL.getPort();
3810    
3811        BindRequest bindRequest = null;
3812        if (connection.lastBindRequest != null)
3813        {
3814          bindRequest = connection.lastBindRequest.getRebindRequest(host, port);
3815          if (bindRequest == null)
3816          {
3817            throw new LDAPException(ResultCode.REFERRAL,
3818                                    ERR_CONN_CANNOT_AUTHENTICATE_FOR_REFERRAL.get(
3819                                         host, port));
3820          }
3821        }
3822    
3823        final LDAPConnection conn = new LDAPConnection(connection.socketFactory,
3824             connection.connectionOptions, host, port);
3825    
3826        if (bindRequest != null)
3827        {
3828          try
3829          {
3830            conn.bind(bindRequest);
3831          }
3832          catch (LDAPException le)
3833          {
3834            debugException(le);
3835            conn.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
3836            conn.close();
3837    
3838            throw le;
3839          }
3840        }
3841    
3842        return conn;
3843      }
3844    
3845    
3846    
3847      /**
3848       * Retrieves the last successful bind request processed on this connection.
3849       *
3850       * @return  The last successful bind request processed on this connection.  It
3851       *          may be {@code null} if no bind has been performed, or if the last
3852       *          bind attempt was not successful.
3853       */
3854      BindRequest getLastBindRequest()
3855      {
3856        return lastBindRequest;
3857      }
3858    
3859    
3860    
3861      /**
3862       * Retrieves an instance of the {@code LDAPConnectionInternals} object for
3863       * this connection.
3864       *
3865       * @return  The {@code LDAPConnectionInternals} object for this connection, or
3866       *          {@code null} if the connection is not established.
3867       */
3868      LDAPConnectionInternals getConnectionInternals()
3869      {
3870        return connectionInternals;
3871      }
3872    
3873    
3874    
3875      /**
3876       * Retrieves the cached schema for this connection, if applicable.
3877       *
3878       * @return  The cached schema for this connection, or {@code null} if it is
3879       *          not available (e.g., because the connection is not established,
3880       *          because {@link LDAPConnectionOptions#useSchema()} is false, or
3881       *          because an error occurred when trying to read the server schema).
3882       */
3883      Schema getCachedSchema()
3884      {
3885        return cachedSchema;
3886      }
3887    
3888    
3889    
3890      /**
3891       * Indicates whether this connection is operating in synchronous mode.
3892       *
3893       * @return  {@code true} if this connection is operating in synchronous mode,
3894       *          or {@code false} if not.
3895       */
3896      public boolean synchronousMode()
3897      {
3898        final LDAPConnectionInternals internals = connectionInternals;
3899        if (internals == null)
3900        {
3901          return false;
3902        }
3903        else
3904        {
3905          return internals.synchronousMode();
3906        }
3907      }
3908    
3909    
3910    
3911      /**
3912       * Reads a response from the server, blocking if necessary until the response
3913       * has been received.  This should only be used for connections operating in
3914       * synchronous mode.
3915       *
3916       * @param  messageID  The message ID for the response to be read.  Any
3917       *                    response read with a different message ID will be
3918       *                    discarded, unless it is an unsolicited notification in
3919       *                    which case it will be provided to any registered
3920       *                    unsolicited notification handler.
3921       *
3922       * @return  The response read from the server.
3923       *
3924       * @throws  LDAPException  If a problem occurs while reading the response.
3925       */
3926      LDAPResponse readResponse(final int messageID)
3927                   throws LDAPException
3928      {
3929        final LDAPConnectionInternals internals = connectionInternals;
3930        if (internals != null)
3931        {
3932          return internals.getConnectionReader().readResponse(messageID);
3933        }
3934        else
3935        {
3936          if (disconnectType == null)
3937          {
3938            return new ConnectionClosedResponse(ResultCode.CONNECT_ERROR,
3939                 ERR_CONN_READ_RESPONSE_NOT_ESTABLISHED.get());
3940          }
3941          else
3942          {
3943            return new ConnectionClosedResponse(disconnectType.getResultCode(),
3944                 disconnectMessage);
3945          }
3946        }
3947      }
3948    
3949    
3950    
3951      /**
3952       * Retrieves the time that this connection was established in the number of
3953       * milliseconds since January 1, 1970 UTC (the same format used by
3954       * {@code System.currentTimeMillis}.
3955       *
3956       * @return  The time that this connection was established, or -1 if the
3957       *          connection is not currently established.
3958       */
3959      public long getConnectTime()
3960      {
3961        final LDAPConnectionInternals internals = connectionInternals;
3962        if (internals != null)
3963        {
3964          return internals.getConnectTime();
3965        }
3966        else
3967        {
3968          return -1L;
3969        }
3970      }
3971    
3972    
3973    
3974      /**
3975       * Retrieves the connection statistics for this LDAP connection.
3976       *
3977       * @return  The connection statistics for this LDAP connection.
3978       */
3979      public LDAPConnectionStatistics getConnectionStatistics()
3980      {
3981        return connectionStatistics;
3982      }
3983    
3984    
3985    
3986      /**
3987       * Retrieves the number of outstanding operations on this LDAP connection
3988       * (i.e., the number of operations currently in progress).  The value will
3989       * only be valid for connections not configured to use synchronous mode.
3990       *
3991       * @return  The number of outstanding operations on this LDAP connection, or
3992       *          -1 if it cannot be determined (e.g., because the connection is not
3993       *          established or is operating in synchronous mode).
3994       */
3995      public int getActiveOperationCount()
3996      {
3997        final LDAPConnectionInternals internals = connectionInternals;
3998    
3999        if (internals == null)
4000        {
4001          return -1;
4002        }
4003        else
4004        {
4005          if (internals.synchronousMode())
4006          {
4007            return -1;
4008          }
4009          else
4010          {
4011            return internals.getConnectionReader().getActiveOperationCount();
4012          }
4013        }
4014      }
4015    
4016    
4017    
4018      /**
4019       * Retrieves the schema from the provided connection.  If the retrieved schema
4020       * matches schema that's already in use by other connections, the common
4021       * schema will be used instead of the newly-retrieved version.
4022       *
4023       * @param  c  The connection for which to retrieve the schema.
4024       *
4025       * @return  The schema retrieved from the given connection, or a cached
4026       *          schema if it matched a schema that was already in use.
4027       *
4028       * @throws  LDAPException  If a problem is encountered while retrieving or
4029       *                         parsing the schema.
4030       */
4031      private static Schema getCachedSchema(final LDAPConnection c)
4032             throws LDAPException
4033      {
4034        final Schema s = c.getSchema();
4035    
4036        synchronized (SCHEMA_SET)
4037        {
4038          return SCHEMA_SET.addAndGet(s);
4039        }
4040      }
4041    
4042    
4043    
4044      /**
4045       * Performs any necessary cleanup to ensure that this connection is properly
4046       * closed before it is garbage collected.
4047       *
4048       * @throws  Throwable  If the superclass finalizer throws an exception.
4049       */
4050      @Override()
4051      protected void finalize()
4052                throws Throwable
4053      {
4054        super.finalize();
4055    
4056        setDisconnectInfo(DisconnectType.CLOSED_BY_FINALIZER, null, null);
4057        terminate(null);
4058      }
4059    
4060    
4061    
4062      /**
4063       * Retrieves a string representation of this LDAP connection.
4064       *
4065       * @return  A string representation of this LDAP connection.
4066       */
4067      @Override()
4068      public String toString()
4069      {
4070        final StringBuilder buffer = new StringBuilder();
4071        toString(buffer);
4072        return buffer.toString();
4073      }
4074    
4075    
4076    
4077      /**
4078       * Appends a string representation of this LDAP connection to the provided
4079       * buffer.
4080       *
4081       * @param  buffer  The buffer to which to append a string representation of
4082       *                 this LDAP connection.
4083       */
4084      public void toString(final StringBuilder buffer)
4085      {
4086        buffer.append("LDAPConnection(");
4087    
4088        final String name     = connectionName;
4089        final String poolName = connectionPoolName;
4090        if (name != null)
4091        {
4092          buffer.append("name='");
4093          buffer.append(name);
4094          buffer.append("', ");
4095        }
4096        else if (poolName != null)
4097        {
4098          buffer.append("poolName='");
4099          buffer.append(poolName);
4100          buffer.append("', ");
4101        }
4102    
4103        final LDAPConnectionInternals internals = connectionInternals;
4104        if ((internals != null) && internals.isConnected())
4105        {
4106          buffer.append("connected to ");
4107          buffer.append(internals.getHost());
4108          buffer.append(':');
4109          buffer.append(internals.getPort());
4110        }
4111        else
4112        {
4113          buffer.append("not connected");
4114        }
4115    
4116        buffer.append(')');
4117      }
4118    }