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