001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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(10);
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        closeRequested = true;
1481        setDisconnectInfo(DisconnectType.UNBIND, null, null);
1482    
1483        if (connectionPool == null)
1484        {
1485          terminate(null);
1486        }
1487        else
1488        {
1489          connectionPool.releaseDefunctConnection(this);
1490        }
1491      }
1492    
1493    
1494    
1495      /**
1496       * Unbinds from the server and closes the connection, optionally including
1497       * the provided set of controls in the unbind request.
1498       * <BR><BR>
1499       * If this method is invoked while any operations are in progress on this
1500       * connection, then the directory server may or may not abort processing for
1501       * those operations, depending on the type of operation and how far along the
1502       * server has already gotten while processing that operation.  It is
1503       * recommended that all active operations be abandoned, canceled, or allowed
1504       * to complete before attempting to close an active connection.
1505       *
1506       * @param  controls  The set of controls to include in the unbind request.  It
1507       *                   may be {@code null} if there are not to be any controls
1508       *                   sent in the unbind request.
1509       */
1510      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
1511      public void close(final Control[] controls)
1512      {
1513        closeRequested = true;
1514        setDisconnectInfo(DisconnectType.UNBIND, null, null);
1515    
1516        if (connectionPool == null)
1517        {
1518          terminate(controls);
1519        }
1520        else
1521        {
1522          connectionPool.releaseDefunctConnection(this);
1523        }
1524      }
1525    
1526    
1527    
1528      /**
1529       * Unbinds from the server and closes the connection, optionally including the
1530       * provided set of controls in the unbind request.  This method is only
1531       * intended for internal use, since it does not make any attempt to release
1532       * the connection back to its associated connection pool, if there is one.
1533       *
1534       * @param  controls  The set of controls to include in the unbind request.  It
1535       *                   may be {@code null} if there are not to be any controls
1536       *                   sent in the unbind request.
1537       */
1538      void terminate(final Control[] controls)
1539      {
1540        if (isConnected() && (! unbindRequestSent))
1541        {
1542          try
1543          {
1544            unbindRequestSent = true;
1545            setDisconnectInfo(DisconnectType.UNBIND, null, null);
1546            if (debugEnabled(DebugType.LDAP))
1547            {
1548              debug(Level.INFO, DebugType.LDAP, "Sending LDAP unbind request.");
1549            }
1550    
1551            connectionStatistics.incrementNumUnbindRequests();
1552            sendMessage(new LDAPMessage(nextMessageID(),
1553                 new UnbindRequestProtocolOp(), controls));
1554          }
1555          catch (Exception e)
1556          {
1557            debugException(e);
1558          }
1559        }
1560    
1561        setClosed();
1562      }
1563    
1564    
1565    
1566      /**
1567       * Indicates whether a request has been made to close this connection.
1568       *
1569       * @return  {@code true} if a request has been made to close this connection,
1570       *          or {@code false} if not.
1571       */
1572      boolean closeRequested()
1573      {
1574        return closeRequested;
1575      }
1576    
1577    
1578    
1579      /**
1580       * Indicates whether an unbind request has been sent over this connection.
1581       *
1582       * @return  {@code true} if an unbind request has been sent over this
1583       *          connection, or {@code false} if not.
1584       */
1585      boolean unbindRequestSent()
1586      {
1587        return unbindRequestSent;
1588      }
1589    
1590    
1591    
1592      /**
1593       * Indicates that this LDAP connection is part of the specified
1594       * connection pool.
1595       *
1596       * @param  connectionPool  The connection pool with which this LDAP connection
1597       *                         is associated.
1598       */
1599      void setConnectionPool(final AbstractConnectionPool connectionPool)
1600      {
1601        this.connectionPool = connectionPool;
1602      }
1603    
1604    
1605    
1606      /**
1607       * Retrieves the directory server root DSE, which provides information about
1608       * the directory server, including the capabilities that it provides and the
1609       * type of data that it is configured to handle.
1610       *
1611       * @return  The directory server root DSE, or {@code null} if it is not
1612       *          available.
1613       *
1614       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1615       *                         the server root DSE.
1616       */
1617      public RootDSE getRootDSE()
1618             throws LDAPException
1619      {
1620        return RootDSE.getRootDSE(this);
1621      }
1622    
1623    
1624    
1625      /**
1626       * Retrieves the directory server schema definitions, using the subschema
1627       * subentry DN contained in the server's root DSE.  For directory servers
1628       * containing a single schema, this should be sufficient for all purposes.
1629       * For servers with multiple schemas, it may be necessary to specify the DN
1630       * of the target entry for which to obtain the associated schema.
1631       *
1632       * @return  The directory server schema definitions, or {@code null} if the
1633       *          schema information could not be retrieved (e.g, the client does
1634       *          not have permission to read the server schema).
1635       *
1636       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1637       *                         the server schema.
1638       */
1639      public Schema getSchema()
1640             throws LDAPException
1641      {
1642        return Schema.getSchema(this, "");
1643      }
1644    
1645    
1646    
1647      /**
1648       * Retrieves the directory server schema definitions that govern the specified
1649       * entry.  The subschemaSubentry attribute will be retrieved from the target
1650       * entry, and then the appropriate schema definitions will be loaded from the
1651       * entry referenced by that attribute.  This may be necessary to ensure
1652       * correct behavior in servers that support multiple schemas.
1653       *
1654       * @param  entryDN  The DN of the entry for which to retrieve the associated
1655       *                  schema definitions.  It may be {@code null} or an empty
1656       *                  string if the subschemaSubentry attribute should be
1657       *                  retrieved from the server's root DSE.
1658       *
1659       * @return  The directory server schema definitions, or {@code null} if the
1660       *          schema information could not be retrieved (e.g, the client does
1661       *          not have permission to read the server schema).
1662       *
1663       * @throws  LDAPException  If a problem occurs while attempting to retrieve
1664       *                         the server schema.
1665       */
1666      public Schema getSchema(final String entryDN)
1667             throws LDAPException
1668      {
1669        return Schema.getSchema(this, entryDN);
1670      }
1671    
1672    
1673    
1674      /**
1675       * Retrieves the entry with the specified DN.  All user attributes will be
1676       * requested in the entry to return.
1677       *
1678       * @param  dn  The DN of the entry to retrieve.  It must not be {@code null}.
1679       *
1680       * @return  The requested entry, or {@code null} if the target entry does not
1681       *          exist or no entry was returned (e.g., if the authenticated user
1682       *          does not have permission to read the target entry).
1683       *
1684       * @throws  LDAPException  If a problem occurs while sending the request or
1685       *                         reading the response.
1686       */
1687      public SearchResultEntry getEntry(final String dn)
1688             throws LDAPException
1689      {
1690        return getEntry(dn, (String[]) null);
1691      }
1692    
1693    
1694    
1695      /**
1696       * Retrieves the entry with the specified DN.
1697       *
1698       * @param  dn          The DN of the entry to retrieve.  It must not be
1699       *                     {@code null}.
1700       * @param  attributes  The set of attributes to request for the target entry.
1701       *                     If it is {@code null}, then all user attributes will be
1702       *                     requested.
1703       *
1704       * @return  The requested entry, or {@code null} if the target entry does not
1705       *          exist or no entry was returned (e.g., if the authenticated user
1706       *          does not have permission to read the target entry).
1707       *
1708       * @throws  LDAPException  If a problem occurs while sending the request or
1709       *                         reading the response.
1710       */
1711      public SearchResultEntry getEntry(final String dn, final String... attributes)
1712             throws LDAPException
1713      {
1714        final Filter filter = Filter.createPresenceFilter("objectClass");
1715    
1716        final SearchResult result;
1717        try
1718        {
1719          final SearchRequest searchRequest =
1720               new SearchRequest(dn, SearchScope.BASE, DereferencePolicy.NEVER, 1,
1721                                 0, false, filter, attributes);
1722          result = search(searchRequest);
1723        }
1724        catch (LDAPException le)
1725        {
1726          if (le.getResultCode().equals(ResultCode.NO_SUCH_OBJECT))
1727          {
1728            return null;
1729          }
1730          else
1731          {
1732            throw le;
1733          }
1734        }
1735    
1736        if (! result.getResultCode().equals(ResultCode.SUCCESS))
1737        {
1738          throw new LDAPException(result);
1739        }
1740    
1741        final List<SearchResultEntry> entryList = result.getSearchEntries();
1742        if (entryList.isEmpty())
1743        {
1744          return null;
1745        }
1746        else
1747        {
1748          return entryList.get(0);
1749        }
1750      }
1751    
1752    
1753    
1754      /**
1755       * Processes an abandon request with the provided information.
1756       *
1757       * @param  requestID  The async request ID for the request to abandon.
1758       *
1759       * @throws  LDAPException  If a problem occurs while sending the request to
1760       *                         the server.
1761       */
1762      public void abandon(final AsyncRequestID requestID)
1763             throws LDAPException
1764      {
1765        abandon(requestID, null);
1766      }
1767    
1768    
1769    
1770      /**
1771       * Processes an abandon request with the provided information.
1772       *
1773       * @param  requestID  The async request ID for the request to abandon.
1774       * @param  controls   The set of controls to include in the abandon request.
1775       *                    It may be {@code null} or empty if there are no
1776       *                    controls.
1777       *
1778       * @throws  LDAPException  If a problem occurs while sending the request to
1779       *                         the server.
1780       */
1781      public void abandon(final AsyncRequestID requestID, final Control[] controls)
1782             throws LDAPException
1783      {
1784        if (debugEnabled(DebugType.LDAP))
1785        {
1786          debug(Level.INFO, DebugType.LDAP,
1787                "Sending LDAP abandon request for message ID " + requestID);
1788        }
1789    
1790        if (synchronousMode())
1791        {
1792          throw new LDAPException(ResultCode.NOT_SUPPORTED,
1793               ERR_ABANDON_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
1794        }
1795    
1796        final int messageID = requestID.getMessageID();
1797        try
1798        {
1799          connectionInternals.getConnectionReader().deregisterResponseAcceptor(
1800               messageID);
1801        }
1802        catch (final Exception e)
1803        {
1804          debugException(e);
1805        }
1806    
1807        connectionStatistics.incrementNumAbandonRequests();
1808        sendMessage(new LDAPMessage(nextMessageID(),
1809             new AbandonRequestProtocolOp(messageID), controls));
1810      }
1811    
1812    
1813    
1814      /**
1815       * Sends an abandon request with the provided information.
1816       *
1817       * @param  messageID  The message ID for the request to abandon.
1818       * @param  controls   The set of controls to include in the abandon request.
1819       *                    It may be {@code null} or empty if there are no
1820       *                    controls.
1821       *
1822       * @throws  LDAPException  If a problem occurs while sending the request to
1823       *                         the server.
1824       */
1825      void abandon(final int messageID, final Control... controls)
1826           throws LDAPException
1827      {
1828        if (debugEnabled(DebugType.LDAP))
1829        {
1830          debug(Level.INFO, DebugType.LDAP,
1831                "Sending LDAP abandon request for message ID " + messageID);
1832        }
1833    
1834        try
1835        {
1836          connectionInternals.getConnectionReader().deregisterResponseAcceptor(
1837               messageID);
1838        }
1839        catch (final Exception e)
1840        {
1841          debugException(e);
1842        }
1843    
1844        connectionStatistics.incrementNumAbandonRequests();
1845        sendMessage(new LDAPMessage(nextMessageID(),
1846             new AbandonRequestProtocolOp(messageID), controls));
1847      }
1848    
1849    
1850    
1851      /**
1852       * Processes an add operation with the provided information.
1853       *
1854       * @param  dn          The DN of the entry to add.  It must not be
1855       *                     {@code null}.
1856       * @param  attributes  The set of attributes to include in the entry to add.
1857       *                     It must not be {@code null}.
1858       *
1859       * @return  The result of processing the add operation.
1860       *
1861       * @throws  LDAPException  If the server rejects the add request, or if a
1862       *                         problem is encountered while sending the request or
1863       *                         reading the response.
1864       */
1865      public LDAPResult add(final String dn, final Attribute... attributes)
1866             throws LDAPException
1867      {
1868        ensureNotNull(dn, attributes);
1869    
1870        return add(new AddRequest(dn, attributes));
1871      }
1872    
1873    
1874    
1875      /**
1876       * Processes an add operation with the provided information.
1877       *
1878       * @param  dn          The DN of the entry to add.  It must not be
1879       *                     {@code null}.
1880       * @param  attributes  The set of attributes to include in the entry to add.
1881       *                     It must not be {@code null}.
1882       *
1883       * @return  The result of processing the add operation.
1884       *
1885       * @throws  LDAPException  If the server rejects the add request, or if a
1886       *                         problem is encountered while sending the request or
1887       *                         reading the response.
1888       */
1889      public LDAPResult add(final String dn, final Collection<Attribute> attributes)
1890             throws LDAPException
1891      {
1892        ensureNotNull(dn, attributes);
1893    
1894        return add(new AddRequest(dn, attributes));
1895      }
1896    
1897    
1898    
1899      /**
1900       * Processes an add operation with the provided information.
1901       *
1902       * @param  entry  The entry to add.  It must not be {@code null}.
1903       *
1904       * @return  The result of processing the add operation.
1905       *
1906       * @throws  LDAPException  If the server rejects the add request, or if a
1907       *                         problem is encountered while sending the request or
1908       *                         reading the response.
1909       */
1910      public LDAPResult add(final Entry entry)
1911             throws LDAPException
1912      {
1913        ensureNotNull(entry);
1914    
1915        return add(new AddRequest(entry));
1916      }
1917    
1918    
1919    
1920      /**
1921       * Processes an add operation with the provided information.
1922       *
1923       * @param  ldifLines  The lines that comprise an LDIF representation of the
1924       *                    entry to add.  It must not be empty or {@code null}.
1925       *
1926       * @return  The result of processing the add operation.
1927       *
1928       * @throws  LDIFException  If the provided entry lines cannot be decoded as an
1929       *                         entry in LDIF form.
1930       *
1931       * @throws  LDAPException  If the server rejects the add request, or if a
1932       *                         problem is encountered while sending the request or
1933       *                         reading the response.
1934       */
1935      public LDAPResult add(final String... ldifLines)
1936             throws LDIFException, LDAPException
1937      {
1938        return add(new AddRequest(ldifLines));
1939      }
1940    
1941    
1942    
1943      /**
1944       * Processes the provided add request.
1945       *
1946       * @param  addRequest  The add request to be processed.  It must not be
1947       *                     {@code null}.
1948       *
1949       * @return  The result of processing the add operation.
1950       *
1951       * @throws  LDAPException  If the server rejects the add request, or if a
1952       *                         problem is encountered while sending the request or
1953       *                         reading the response.
1954       */
1955      public LDAPResult add(final AddRequest addRequest)
1956             throws LDAPException
1957      {
1958        ensureNotNull(addRequest);
1959    
1960        final LDAPResult ldapResult = addRequest.process(this, 1);
1961    
1962        switch (ldapResult.getResultCode().intValue())
1963        {
1964          case ResultCode.SUCCESS_INT_VALUE:
1965          case ResultCode.NO_OPERATION_INT_VALUE:
1966            return ldapResult;
1967    
1968          default:
1969            throw new LDAPException(ldapResult);
1970        }
1971      }
1972    
1973    
1974    
1975      /**
1976       * Processes the provided add request.
1977       *
1978       * @param  addRequest  The add request to be processed.  It must not be
1979       *                     {@code null}.
1980       *
1981       * @return  The result of processing the add operation.
1982       *
1983       * @throws  LDAPException  If the server rejects the add request, or if a
1984       *                         problem is encountered while sending the request or
1985       *                         reading the response.
1986       */
1987      public LDAPResult add(final ReadOnlyAddRequest addRequest)
1988             throws LDAPException
1989      {
1990        return add((AddRequest) addRequest);
1991      }
1992    
1993    
1994    
1995      /**
1996       * Processes the provided add request as an asynchronous operation.
1997       *
1998       * @param  addRequest      The add request to be processed.  It must not be
1999       *                         {@code null}.
2000       * @param  resultListener  The async result listener to use to handle the
2001       *                         response for the add operation.  It may be
2002       *                         {@code null} if the result is going to be obtained
2003       *                         from the returned {@code AsyncRequestID} object via
2004       *                         the {@code Future} API.
2005       *
2006       * @return  An async request ID that may be used to reference the operation.
2007       *
2008       * @throws  LDAPException  If a problem occurs while sending the request.
2009       */
2010      public AsyncRequestID asyncAdd(final AddRequest addRequest,
2011                                     final AsyncResultListener resultListener)
2012             throws LDAPException
2013      {
2014        ensureNotNull(addRequest);
2015    
2016        if (synchronousMode())
2017        {
2018          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2019               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2020        }
2021    
2022        final AsyncResultListener listener;
2023        if (resultListener == null)
2024        {
2025          listener = DiscardAsyncListener.getInstance();
2026        }
2027        else
2028        {
2029          listener = resultListener;
2030        }
2031    
2032        return addRequest.processAsync(this, listener);
2033      }
2034    
2035    
2036    
2037      /**
2038       * Processes the provided add request as an asynchronous operation.
2039       *
2040       * @param  addRequest      The add request to be processed.  It must not be
2041       *                         {@code null}.
2042       * @param  resultListener  The async result listener to use to handle the
2043       *                         response for the add operation.  It may be
2044       *                         {@code null} if the result is going to be obtained
2045       *                         from the returned {@code AsyncRequestID} object via
2046       *                         the {@code Future} API.
2047       *
2048       * @return  An async request ID that may be used to reference the operation.
2049       *
2050       * @throws  LDAPException  If a problem occurs while sending the request.
2051       */
2052      public AsyncRequestID asyncAdd(final ReadOnlyAddRequest addRequest,
2053                                     final AsyncResultListener resultListener)
2054             throws LDAPException
2055      {
2056        if (synchronousMode())
2057        {
2058          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2059               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2060        }
2061    
2062        return asyncAdd((AddRequest) addRequest, resultListener);
2063      }
2064    
2065    
2066    
2067      /**
2068       * Processes a simple bind request with the provided DN and password.
2069       * <BR><BR>
2070       * The LDAP protocol specification forbids clients from attempting to perform
2071       * a bind on a connection in which one or more other operations are already in
2072       * progress.  If a bind is attempted while any operations are in progress,
2073       * then the directory server may or may not abort processing for those
2074       * operations, depending on the type of operation and how far along the
2075       * server has already gotten while processing that operation (unless the bind
2076       * request is one that will not cause the server to attempt to change the
2077       * identity of this connection, for example by including the retain identity
2078       * request control in the bind request if using the Commercial Edition of the
2079       * LDAP SDK in conjunction with an UnboundID Directory Server).  It is
2080       * recommended that all active operations be abandoned, canceled, or allowed
2081       * to complete before attempting to perform a bind on an active connection.
2082       *
2083       * @param  bindDN    The bind DN for the bind operation.
2084       * @param  password  The password for the simple bind operation.
2085       *
2086       * @return  The result of processing the bind operation.
2087       *
2088       * @throws  LDAPException  If the server rejects the bind request, or if a
2089       *                         problem occurs while sending the request or reading
2090       *                         the response.
2091       */
2092      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2093      public BindResult bind(final String bindDN, final String password)
2094             throws LDAPException
2095      {
2096        return bind(new SimpleBindRequest(bindDN, password));
2097      }
2098    
2099    
2100    
2101      /**
2102       * Processes the provided bind request.
2103       * <BR><BR>
2104       * The LDAP protocol specification forbids clients from attempting to perform
2105       * a bind on a connection in which one or more other operations are already in
2106       * progress.  If a bind is attempted while any operations are in progress,
2107       * then the directory server may or may not abort processing for those
2108       * operations, depending on the type of operation and how far along the
2109       * server has already gotten while processing that operation (unless the bind
2110       * request is one that will not cause the server to attempt to change the
2111       * identity of this connection, for example by including the retain identity
2112       * request control in the bind request if using the Commercial Edition of the
2113       * LDAP SDK in conjunction with an UnboundID Directory Server).  It is
2114       * recommended that all active operations be abandoned, canceled, or allowed
2115       * to complete before attempting to perform a bind on an active connection.
2116       *
2117       * @param  bindRequest  The bind request to be processed.  It must not be
2118       *                      {@code null}.
2119       *
2120       * @return  The result of processing the bind operation.
2121       *
2122       * @throws  LDAPException  If the server rejects the bind request, or if a
2123       *                         problem occurs while sending the request or reading
2124       *                         the response.
2125       */
2126      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2127      public BindResult bind(final BindRequest bindRequest)
2128             throws LDAPException
2129      {
2130        ensureNotNull(bindRequest);
2131    
2132        // We don't want to update the last bind request or update the cached
2133        // schema for this connection if it included the retain identity control.
2134        // However, that's only available in the Commercial Edition, so just
2135        // reference it by OID here.
2136        boolean hasRetainIdentityControl = false;
2137        for (final Control c : bindRequest.getControls())
2138        {
2139          if (c.getOID().equals("1.3.6.1.4.1.30221.2.5.3"))
2140          {
2141            hasRetainIdentityControl = true;
2142            break;
2143          }
2144        }
2145    
2146        if (! hasRetainIdentityControl)
2147        {
2148          lastBindRequest = null;
2149        }
2150    
2151        final BindResult bindResult = bindRequest.process(this, 1);
2152        if (bindResult.getResultCode().equals(ResultCode.SUCCESS))
2153        {
2154          if (! hasRetainIdentityControl)
2155          {
2156            lastBindRequest = bindRequest;
2157            if (connectionOptions.useSchema())
2158            {
2159              try
2160              {
2161                cachedSchema = getCachedSchema(this);
2162              }
2163              catch (Exception e)
2164              {
2165                debugException(e);
2166              }
2167            }
2168          }
2169    
2170          return bindResult;
2171        }
2172    
2173        if (bindResult.getResultCode().equals(ResultCode.SASL_BIND_IN_PROGRESS))
2174        {
2175          throw new SASLBindInProgressException(bindResult);
2176        }
2177        else
2178        {
2179          throw new LDAPException(bindResult);
2180        }
2181      }
2182    
2183    
2184    
2185      /**
2186       * Processes a compare operation with the provided information.
2187       *
2188       * @param  dn              The DN of the entry in which to make the
2189       *                         comparison.  It must not be {@code null}.
2190       * @param  attributeName   The attribute name for which to make the
2191       *                         comparison.  It must not be {@code null}.
2192       * @param  assertionValue  The assertion value to verify in the target entry.
2193       *                         It must not be {@code null}.
2194       *
2195       * @return  The result of processing the compare operation.
2196       *
2197       * @throws  LDAPException  If the server rejects the compare request, or if a
2198       *                         problem is encountered while sending the request or
2199       *                         reading the response.
2200       */
2201      public CompareResult compare(final String dn, final String attributeName,
2202                                   final String assertionValue)
2203             throws LDAPException
2204      {
2205        ensureNotNull(dn, attributeName, assertionValue);
2206    
2207        return compare(new CompareRequest(dn, attributeName, assertionValue));
2208      }
2209    
2210    
2211    
2212      /**
2213       * Processes the provided compare request.
2214       *
2215       * @param  compareRequest  The compare request to be processed.  It must not
2216       *                         be {@code null}.
2217       *
2218       * @return  The result of processing the compare operation.
2219       *
2220       * @throws  LDAPException  If the server rejects the compare request, or if a
2221       *                         problem is encountered while sending the request or
2222       *                         reading the response.
2223       */
2224      public CompareResult compare(final CompareRequest compareRequest)
2225             throws LDAPException
2226      {
2227        ensureNotNull(compareRequest);
2228    
2229        final LDAPResult result = compareRequest.process(this, 1);
2230        switch (result.getResultCode().intValue())
2231        {
2232          case ResultCode.COMPARE_FALSE_INT_VALUE:
2233          case ResultCode.COMPARE_TRUE_INT_VALUE:
2234            return new CompareResult(result);
2235    
2236          default:
2237            throw new LDAPException(result);
2238        }
2239      }
2240    
2241    
2242    
2243      /**
2244       * Processes the provided compare request.
2245       *
2246       * @param  compareRequest  The compare request to be processed.  It must not
2247       *                         be {@code null}.
2248       *
2249       * @return  The result of processing the compare operation.
2250       *
2251       * @throws  LDAPException  If the server rejects the compare request, or if a
2252       *                         problem is encountered while sending the request or
2253       *                         reading the response.
2254       */
2255      public CompareResult compare(final ReadOnlyCompareRequest compareRequest)
2256             throws LDAPException
2257      {
2258        return compare((CompareRequest) compareRequest);
2259      }
2260    
2261    
2262    
2263      /**
2264       * Processes the provided compare request as an asynchronous operation.
2265       *
2266       * @param  compareRequest  The compare request to be processed.  It must not
2267       *                         be {@code null}.
2268       * @param  resultListener  The async result listener to use to handle the
2269       *                         response for the compare operation.  It may be
2270       *                         {@code null} if the result is going to be obtained
2271       *                         from the returned {@code AsyncRequestID} object via
2272       *                         the {@code Future} API.
2273       *
2274       * @return  An async request ID that may be used to reference the operation.
2275       *
2276       * @throws  LDAPException  If a problem occurs while sending the request.
2277       */
2278      public AsyncRequestID asyncCompare(final CompareRequest compareRequest,
2279                                 final AsyncCompareResultListener resultListener)
2280             throws LDAPException
2281      {
2282        ensureNotNull(compareRequest);
2283    
2284        if (synchronousMode())
2285        {
2286          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2287               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2288        }
2289    
2290        final AsyncCompareResultListener listener;
2291        if (resultListener == null)
2292        {
2293          listener = DiscardAsyncListener.getInstance();
2294        }
2295        else
2296        {
2297          listener = resultListener;
2298        }
2299    
2300        return compareRequest.processAsync(this, listener);
2301      }
2302    
2303    
2304    
2305      /**
2306       * Processes the provided compare request as an asynchronous operation.
2307       *
2308       * @param  compareRequest  The compare request to be processed.  It must not
2309       *                         be {@code null}.
2310       * @param  resultListener  The async result listener to use to handle the
2311       *                         response for the compare operation.  It may be
2312       *                         {@code null} if the result is going to be obtained
2313       *                         from the returned {@code AsyncRequestID} object via
2314       *                         the {@code Future} API.
2315       *
2316       * @return  An async request ID that may be used to reference the operation.
2317       *
2318       * @throws  LDAPException  If a problem occurs while sending the request.
2319       */
2320      public AsyncRequestID asyncCompare(
2321                                 final ReadOnlyCompareRequest compareRequest,
2322                                 final AsyncCompareResultListener resultListener)
2323             throws LDAPException
2324      {
2325        if (synchronousMode())
2326        {
2327          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2328               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2329        }
2330    
2331        return asyncCompare((CompareRequest) compareRequest, resultListener);
2332      }
2333    
2334    
2335    
2336      /**
2337       * Deletes the entry with the specified DN.
2338       *
2339       * @param  dn  The DN of the entry to delete.  It must not be {@code null}.
2340       *
2341       * @return  The result of processing the delete operation.
2342       *
2343       * @throws  LDAPException  If the server rejects the delete request, or if a
2344       *                         problem is encountered while sending the request or
2345       *                         reading the response.
2346       */
2347      public LDAPResult delete(final String dn)
2348             throws LDAPException
2349      {
2350        return delete(new DeleteRequest(dn));
2351      }
2352    
2353    
2354    
2355      /**
2356       * Processes the provided delete request.
2357       *
2358       * @param  deleteRequest  The delete request to be processed.  It must not be
2359       *                        {@code null}.
2360       *
2361       * @return  The result of processing the delete operation.
2362       *
2363       * @throws  LDAPException  If the server rejects the delete request, or if a
2364       *                         problem is encountered while sending the request or
2365       *                         reading the response.
2366       */
2367      public LDAPResult delete(final DeleteRequest deleteRequest)
2368             throws LDAPException
2369      {
2370        ensureNotNull(deleteRequest);
2371    
2372        final LDAPResult ldapResult = deleteRequest.process(this, 1);
2373    
2374        switch (ldapResult.getResultCode().intValue())
2375        {
2376          case ResultCode.SUCCESS_INT_VALUE:
2377          case ResultCode.NO_OPERATION_INT_VALUE:
2378            return ldapResult;
2379    
2380          default:
2381            throw new LDAPException(ldapResult);
2382        }
2383      }
2384    
2385    
2386    
2387      /**
2388       * Processes the provided delete request.
2389       *
2390       * @param  deleteRequest  The delete request to be processed.  It must not be
2391       *                        {@code null}.
2392       *
2393       * @return  The result of processing the delete operation.
2394       *
2395       * @throws  LDAPException  If the server rejects the delete request, or if a
2396       *                         problem is encountered while sending the request or
2397       *                         reading the response.
2398       */
2399      public LDAPResult delete(final ReadOnlyDeleteRequest deleteRequest)
2400             throws LDAPException
2401      {
2402        return delete((DeleteRequest) deleteRequest);
2403      }
2404    
2405    
2406    
2407      /**
2408       * Processes the provided delete request as an asynchronous operation.
2409       *
2410       * @param  deleteRequest   The delete request to be processed.  It must not be
2411       *                         {@code null}.
2412       * @param  resultListener  The async result listener to use to handle the
2413       *                         response for the delete operation.  It may be
2414       *                         {@code null} if the result is going to be obtained
2415       *                         from the returned {@code AsyncRequestID} object via
2416       *                         the {@code Future} API.
2417       *
2418       * @return  An async request ID that may be used to reference the operation.
2419       *
2420       * @throws  LDAPException  If a problem occurs while sending the request.
2421       */
2422      public AsyncRequestID asyncDelete(final DeleteRequest deleteRequest,
2423                                 final AsyncResultListener resultListener)
2424             throws LDAPException
2425      {
2426        ensureNotNull(deleteRequest);
2427    
2428        if (synchronousMode())
2429        {
2430          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2431               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2432        }
2433    
2434        final AsyncResultListener listener;
2435        if (resultListener == null)
2436        {
2437          listener = DiscardAsyncListener.getInstance();
2438        }
2439        else
2440        {
2441          listener = resultListener;
2442        }
2443    
2444        return deleteRequest.processAsync(this, listener);
2445      }
2446    
2447    
2448    
2449      /**
2450       * Processes the provided delete request as an asynchronous operation.
2451       *
2452       * @param  deleteRequest   The delete request to be processed.  It must not be
2453       *                         {@code null}.
2454       * @param  resultListener  The async result listener to use to handle the
2455       *                         response for the delete operation.  It may be
2456       *                         {@code null} if the result is going to be obtained
2457       *                         from the returned {@code AsyncRequestID} object via
2458       *                         the {@code Future} API.
2459       *
2460       * @return  An async request ID that may be used to reference the operation.
2461       *
2462       * @throws  LDAPException  If a problem occurs while sending the request.
2463       */
2464      public AsyncRequestID asyncDelete(final ReadOnlyDeleteRequest deleteRequest,
2465                                 final AsyncResultListener resultListener)
2466             throws LDAPException
2467      {
2468        if (synchronousMode())
2469        {
2470          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2471               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2472        }
2473    
2474        return asyncDelete((DeleteRequest) deleteRequest, resultListener);
2475      }
2476    
2477    
2478    
2479      /**
2480       * Processes an extended request with the provided request OID.  Note that
2481       * because some types of extended operations return unusual result codes under
2482       * "normal" conditions, the server may not always throw an exception for a
2483       * failed extended operation like it does for other types of operations.  It
2484       * will throw an exception under conditions where there appears to be a
2485       * problem with the connection or the server to which the connection is
2486       * established, but there may be many circumstances in which an extended
2487       * operation is not processed correctly but this method does not throw an
2488       * exception.  In the event that no exception is thrown, it is the
2489       * responsibility of the caller to interpret the result to determine whether
2490       * the operation was processed as expected.
2491       * <BR><BR>
2492       * Note that extended operations which may change the state of this connection
2493       * (e.g., the StartTLS extended operation, which will add encryption to a
2494       * previously-unencrypted connection) should not be invoked while any other
2495       * operations are active on the connection.  It is recommended that all active
2496       * operations be abandoned, canceled, or allowed to complete before attempting
2497       * to process an extended operation that may change the state of this
2498       * connection.
2499       *
2500       * @param  requestOID  The OID for the extended request to process.  It must
2501       *                     not be {@code null}.
2502       *
2503       * @return  The extended result object that provides information about the
2504       *          result of the request processing.  It may or may not indicate that
2505       *          the operation was successful.
2506       *
2507       * @throws  LDAPException  If a problem occurs while sending the request or
2508       *                         reading the response.
2509       */
2510      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2511      public ExtendedResult processExtendedOperation(final String requestOID)
2512             throws LDAPException
2513      {
2514        ensureNotNull(requestOID);
2515    
2516        return processExtendedOperation(new ExtendedRequest(requestOID));
2517      }
2518    
2519    
2520    
2521      /**
2522       * Processes an extended request with the provided request OID and value.
2523       * Note that because some types of extended operations return unusual result
2524       * codes under "normal" conditions, the server may not always throw an
2525       * exception for a failed extended operation like it does for other types of
2526       * operations.  It will throw an exception under conditions where there
2527       * appears to be a problem with the connection or the server to which the
2528       * connection is established, but there may be many circumstances in which an
2529       * extended operation is not processed correctly but this method does not
2530       * throw an exception.  In the event that no exception is thrown, it is the
2531       * responsibility of the caller to interpret the result to determine whether
2532       * the operation was processed as expected.
2533       * <BR><BR>
2534       * Note that extended operations which may change the state of this connection
2535       * (e.g., the StartTLS extended operation, which will add encryption to a
2536       * previously-unencrypted connection) should not be invoked while any other
2537       * operations are active on the connection.  It is recommended that all active
2538       * operations be abandoned, canceled, or allowed to complete before attempting
2539       * to process an extended operation that may change the state of this
2540       * connection.
2541       *
2542       * @param  requestOID    The OID for the extended request to process.  It must
2543       *                       not be {@code null}.
2544       * @param  requestValue  The encoded value for the extended request to
2545       *                       process.  It may be {@code null} if there does not
2546       *                       need to be a value for the requested operation.
2547       *
2548       * @return  The extended result object that provides information about the
2549       *          result of the request processing.  It may or may not indicate that
2550       *          the operation was successful.
2551       *
2552       * @throws  LDAPException  If a problem occurs while sending the request or
2553       *                         reading the response.
2554       */
2555      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2556      public ExtendedResult processExtendedOperation(final String requestOID,
2557                                 final ASN1OctetString requestValue)
2558             throws LDAPException
2559      {
2560        ensureNotNull(requestOID);
2561    
2562        return processExtendedOperation(new ExtendedRequest(requestOID,
2563                                                            requestValue));
2564      }
2565    
2566    
2567    
2568      /**
2569       * Processes the provided extended request.  Note that because some types of
2570       * extended operations return unusual result codes under "normal" conditions,
2571       * the server may not always throw an exception for a failed extended
2572       * operation like it does for other types of operations.  It will throw an
2573       * exception under conditions where there appears to be a problem with the
2574       * connection or the server to which the connection is established, but there
2575       * may be many circumstances in which an extended operation is not processed
2576       * correctly but this method does not throw an exception.  In the event that
2577       * no exception is thrown, it is the responsibility of the caller to interpret
2578       * the result to determine whether the operation was processed as expected.
2579       * <BR><BR>
2580       * Note that extended operations which may change the state of this connection
2581       * (e.g., the StartTLS extended operation, which will add encryption to a
2582       * previously-unencrypted connection) should not be invoked while any other
2583       * operations are active on the connection.  It is recommended that all active
2584       * operations be abandoned, canceled, or allowed to complete before attempting
2585       * to process an extended operation that may change the state of this
2586       * connection.
2587       *
2588       * @param  extendedRequest  The extended request to be processed.  It must not
2589       *                          be {@code null}.
2590       *
2591       * @return  The extended result object that provides information about the
2592       *          result of the request processing.  It may or may not indicate that
2593       *          the operation was successful.
2594       *
2595       * @throws  LDAPException  If a problem occurs while sending the request or
2596       *                         reading the response.
2597       */
2598      @ThreadSafety(level=ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
2599      public ExtendedResult processExtendedOperation(
2600                                   final ExtendedRequest extendedRequest)
2601             throws LDAPException
2602      {
2603        ensureNotNull(extendedRequest);
2604    
2605        final ExtendedResult extendedResult = extendedRequest.process(this, 1);
2606    
2607        if ((extendedResult.getOID() == null) &&
2608            (extendedResult.getValue() == null))
2609        {
2610          switch (extendedResult.getResultCode().intValue())
2611          {
2612            case ResultCode.OPERATIONS_ERROR_INT_VALUE:
2613            case ResultCode.PROTOCOL_ERROR_INT_VALUE:
2614            case ResultCode.BUSY_INT_VALUE:
2615            case ResultCode.UNAVAILABLE_INT_VALUE:
2616            case ResultCode.OTHER_INT_VALUE:
2617            case ResultCode.SERVER_DOWN_INT_VALUE:
2618            case ResultCode.LOCAL_ERROR_INT_VALUE:
2619            case ResultCode.ENCODING_ERROR_INT_VALUE:
2620            case ResultCode.DECODING_ERROR_INT_VALUE:
2621            case ResultCode.TIMEOUT_INT_VALUE:
2622            case ResultCode.NO_MEMORY_INT_VALUE:
2623            case ResultCode.CONNECT_ERROR_INT_VALUE:
2624              throw new LDAPException(extendedResult);
2625          }
2626        }
2627    
2628        if ((extendedResult.getResultCode() == ResultCode.SUCCESS) &&
2629             extendedRequest.getOID().equals(
2630                  StartTLSExtendedRequest.STARTTLS_REQUEST_OID))
2631        {
2632          startTLSRequest = extendedRequest.duplicate();
2633        }
2634    
2635        return extendedResult;
2636      }
2637    
2638    
2639    
2640      /**
2641       * Applies the provided modification to the specified entry.
2642       *
2643       * @param  dn   The DN of the entry to modify.  It must not be {@code null}.
2644       * @param  mod  The modification to apply to the target entry.  It must not
2645       *              be {@code null}.
2646       *
2647       * @return  The result of processing the modify operation.
2648       *
2649       * @throws  LDAPException  If the server rejects the modify request, or if a
2650       *                         problem is encountered while sending the request or
2651       *                         reading the response.
2652       */
2653      public LDAPResult modify(final String dn, final Modification mod)
2654             throws LDAPException
2655      {
2656        ensureNotNull(dn, mod);
2657    
2658        return modify(new ModifyRequest(dn, mod));
2659      }
2660    
2661    
2662    
2663      /**
2664       * Applies the provided set of modifications to the specified entry.
2665       *
2666       * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
2667       * @param  mods  The set of modifications to apply to the target entry.  It
2668       *               must not be {@code null} or empty.  *
2669       * @return  The result of processing the modify operation.
2670       *
2671       * @throws  LDAPException  If the server rejects the modify request, or if a
2672       *                         problem is encountered while sending the request or
2673       *                         reading the response.
2674       */
2675      public LDAPResult modify(final String dn, final Modification... mods)
2676             throws LDAPException
2677      {
2678        ensureNotNull(dn, mods);
2679    
2680        return modify(new ModifyRequest(dn, mods));
2681      }
2682    
2683    
2684    
2685      /**
2686       * Applies the provided set of modifications to the specified entry.
2687       *
2688       * @param  dn    The DN of the entry to modify.  It must not be {@code null}.
2689       * @param  mods  The set of modifications to apply to the target entry.  It
2690       *               must not be {@code null} or empty.
2691       *
2692       * @return  The result of processing the modify operation.
2693       *
2694       * @throws  LDAPException  If the server rejects the modify request, or if a
2695       *                         problem is encountered while sending the request or
2696       *                         reading the response.
2697       */
2698      public LDAPResult modify(final String dn, final List<Modification> mods)
2699             throws LDAPException
2700      {
2701        ensureNotNull(dn, mods);
2702    
2703        return modify(new ModifyRequest(dn, mods));
2704      }
2705    
2706    
2707    
2708      /**
2709       * Processes a modify request from the provided LDIF representation of the
2710       * changes.
2711       *
2712       * @param  ldifModificationLines  The lines that comprise an LDIF
2713       *                                representation of a modify change record.
2714       *                                It must not be {@code null} or empty.
2715       *
2716       * @return  The result of processing the modify operation.
2717       *
2718       * @throws  LDIFException  If the provided set of lines cannot be parsed as an
2719       *                         LDIF modify change record.
2720       *
2721       * @throws  LDAPException  If the server rejects the modify request, or if a
2722       *                         problem is encountered while sending the request or
2723       *                         reading the response.
2724       *
2725       */
2726      public LDAPResult modify(final String... ldifModificationLines)
2727             throws LDIFException, LDAPException
2728      {
2729        ensureNotNull(ldifModificationLines);
2730    
2731        return modify(new ModifyRequest(ldifModificationLines));
2732      }
2733    
2734    
2735    
2736      /**
2737       * Processes the provided modify request.
2738       *
2739       * @param  modifyRequest  The modify request to be processed.  It must not be
2740       *                        {@code null}.
2741       *
2742       * @return  The result of processing the modify operation.
2743       *
2744       * @throws  LDAPException  If the server rejects the modify request, or if a
2745       *                         problem is encountered while sending the request or
2746       *                         reading the response.
2747       */
2748      public LDAPResult modify(final ModifyRequest modifyRequest)
2749             throws LDAPException
2750      {
2751        ensureNotNull(modifyRequest);
2752    
2753        final LDAPResult ldapResult = modifyRequest.process(this, 1);
2754    
2755        switch (ldapResult.getResultCode().intValue())
2756        {
2757          case ResultCode.SUCCESS_INT_VALUE:
2758          case ResultCode.NO_OPERATION_INT_VALUE:
2759            return ldapResult;
2760    
2761          default:
2762            throw new LDAPException(ldapResult);
2763        }
2764      }
2765    
2766    
2767    
2768      /**
2769       * Processes the provided modify request.
2770       *
2771       * @param  modifyRequest  The modify request to be processed.  It must not be
2772       *                        {@code null}.
2773       *
2774       * @return  The result of processing the modify operation.
2775       *
2776       * @throws  LDAPException  If the server rejects the modify request, or if a
2777       *                         problem is encountered while sending the request or
2778       *                         reading the response.
2779       */
2780      public LDAPResult modify(final ReadOnlyModifyRequest modifyRequest)
2781             throws LDAPException
2782      {
2783        return modify((ModifyRequest) modifyRequest);
2784      }
2785    
2786    
2787    
2788      /**
2789       * Processes the provided modify request as an asynchronous operation.
2790       *
2791       * @param  modifyRequest   The modify request to be processed.  It must not be
2792       *                         {@code null}.
2793       * @param  resultListener  The async result listener to use to handle the
2794       *                         response for the modify operation.  It may be
2795       *                         {@code null} if the result is going to be obtained
2796       *                         from the returned {@code AsyncRequestID} object via
2797       *                         the {@code Future} API.
2798       *
2799       * @return  An async request ID that may be used to reference the operation.
2800       *
2801       * @throws  LDAPException  If a problem occurs while sending the request.
2802       */
2803      public AsyncRequestID asyncModify(final ModifyRequest modifyRequest,
2804                                 final AsyncResultListener resultListener)
2805             throws LDAPException
2806      {
2807        ensureNotNull(modifyRequest);
2808    
2809        if (synchronousMode())
2810        {
2811          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2812               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2813        }
2814    
2815        final AsyncResultListener listener;
2816        if (resultListener == null)
2817        {
2818          listener = DiscardAsyncListener.getInstance();
2819        }
2820        else
2821        {
2822          listener = resultListener;
2823        }
2824    
2825        return modifyRequest.processAsync(this, listener);
2826      }
2827    
2828    
2829    
2830      /**
2831       * Processes the provided modify request as an asynchronous operation.
2832       *
2833       * @param  modifyRequest   The modify request to be processed.  It must not be
2834       *                         {@code null}.
2835       * @param  resultListener  The async result listener to use to handle the
2836       *                         response for the modify operation.  It may be
2837       *                         {@code null} if the result is going to be obtained
2838       *                         from the returned {@code AsyncRequestID} object via
2839       *                         the {@code Future} API.
2840       *
2841       * @return  An async request ID that may be used to reference the operation.
2842       *
2843       * @throws  LDAPException  If a problem occurs while sending the request.
2844       */
2845      public AsyncRequestID asyncModify(final ReadOnlyModifyRequest modifyRequest,
2846                                 final AsyncResultListener resultListener)
2847             throws LDAPException
2848      {
2849        if (synchronousMode())
2850        {
2851          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2852               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2853        }
2854    
2855        return asyncModify((ModifyRequest) modifyRequest, resultListener);
2856      }
2857    
2858    
2859    
2860      /**
2861       * Performs a modify DN operation with the provided information.
2862       *
2863       * @param  dn            The current DN for the entry to rename.  It must not
2864       *                       be {@code null}.
2865       * @param  newRDN        The new RDN to use for the entry.  It must not be
2866       *                       {@code null}.
2867       * @param  deleteOldRDN  Indicates whether to delete the current RDN value
2868       *                       from the entry.
2869       *
2870       * @return  The result of processing the modify DN operation.
2871       *
2872       * @throws  LDAPException  If the server rejects the modify DN request, or if
2873       *                         a problem is encountered while sending the request
2874       *                         or reading the response.
2875       */
2876      public LDAPResult modifyDN(final String dn, final String newRDN,
2877                                 final boolean deleteOldRDN)
2878             throws LDAPException
2879      {
2880        ensureNotNull(dn, newRDN);
2881    
2882        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN));
2883      }
2884    
2885    
2886    
2887      /**
2888       * Performs a modify DN operation with the provided information.
2889       *
2890       * @param  dn             The current DN for the entry to rename.  It must not
2891       *                        be {@code null}.
2892       * @param  newRDN         The new RDN to use for the entry.  It must not be
2893       *                        {@code null}.
2894       * @param  deleteOldRDN   Indicates whether to delete the current RDN value
2895       *                        from the entry.
2896       * @param  newSuperiorDN  The new superior DN for the entry.  It may be
2897       *                        {@code null} if the entry is not to be moved below a
2898       *                        new parent.
2899       *
2900       * @return  The result of processing the modify DN operation.
2901       *
2902       * @throws  LDAPException  If the server rejects the modify DN request, or if
2903       *                         a problem is encountered while sending the request
2904       *                         or reading the response.
2905       */
2906      public LDAPResult modifyDN(final String dn, final String newRDN,
2907                                 final boolean deleteOldRDN,
2908                                 final String newSuperiorDN)
2909             throws LDAPException
2910      {
2911        ensureNotNull(dn, newRDN);
2912    
2913        return modifyDN(new ModifyDNRequest(dn, newRDN, deleteOldRDN,
2914                                            newSuperiorDN));
2915      }
2916    
2917    
2918    
2919      /**
2920       * Processes the provided modify DN request.
2921       *
2922       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2923       *                          not be {@code null}.
2924       *
2925       * @return  The result of processing the modify DN operation.
2926       *
2927       * @throws  LDAPException  If the server rejects the modify DN request, or if
2928       *                         a problem is encountered while sending the request
2929       *                         or reading the response.
2930       */
2931      public LDAPResult modifyDN(final ModifyDNRequest modifyDNRequest)
2932             throws LDAPException
2933      {
2934        ensureNotNull(modifyDNRequest);
2935    
2936        final LDAPResult ldapResult = modifyDNRequest.process(this, 1);
2937    
2938        switch (ldapResult.getResultCode().intValue())
2939        {
2940          case ResultCode.SUCCESS_INT_VALUE:
2941          case ResultCode.NO_OPERATION_INT_VALUE:
2942            return ldapResult;
2943    
2944          default:
2945            throw new LDAPException(ldapResult);
2946        }
2947      }
2948    
2949    
2950    
2951      /**
2952       * Processes the provided modify DN request.
2953       *
2954       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2955       *                          not be {@code null}.
2956       *
2957       * @return  The result of processing the modify DN operation.
2958       *
2959       * @throws  LDAPException  If the server rejects the modify DN request, or if
2960       *                         a problem is encountered while sending the request
2961       *                         or reading the response.
2962       */
2963      public LDAPResult modifyDN(final ReadOnlyModifyDNRequest modifyDNRequest)
2964             throws LDAPException
2965      {
2966        return modifyDN((ModifyDNRequest) modifyDNRequest);
2967      }
2968    
2969    
2970    
2971      /**
2972       * Processes the provided modify DN request as an asynchronous operation.
2973       *
2974       * @param  modifyDNRequest  The modify DN request to be processed.  It must
2975       *                          not be {@code null}.
2976       * @param  resultListener  The async result listener to use to handle the
2977       *                         response for the modify DN operation.  It may be
2978       *                         {@code null} if the result is going to be obtained
2979       *                         from the returned {@code AsyncRequestID} object via
2980       *                         the {@code Future} API.
2981       *
2982       * @return  An async request ID that may be used to reference the operation.
2983       *
2984       * @throws  LDAPException  If a problem occurs while sending the request.
2985       */
2986      public AsyncRequestID asyncModifyDN(final ModifyDNRequest modifyDNRequest,
2987                                 final AsyncResultListener resultListener)
2988             throws LDAPException
2989      {
2990        ensureNotNull(modifyDNRequest);
2991    
2992        if (synchronousMode())
2993        {
2994          throw new LDAPException(ResultCode.NOT_SUPPORTED,
2995               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
2996        }
2997    
2998        final AsyncResultListener listener;
2999        if (resultListener == null)
3000        {
3001          listener = DiscardAsyncListener.getInstance();
3002        }
3003        else
3004        {
3005          listener = resultListener;
3006        }
3007    
3008        return modifyDNRequest.processAsync(this, listener);
3009      }
3010    
3011    
3012    
3013      /**
3014       * Processes the provided modify DN request as an asynchronous operation.
3015       *
3016       * @param  modifyDNRequest  The modify DN request to be processed.  It must
3017       *                          not be {@code null}.
3018       * @param  resultListener  The async result listener to use to handle the
3019       *                         response for the modify DN operation.  It may be
3020       *                         {@code null} if the result is going to be obtained
3021       *                         from the returned {@code AsyncRequestID} object via
3022       *                         the {@code Future} API.
3023       *
3024       * @return  An async request ID that may be used to reference the operation.
3025       *
3026       * @throws  LDAPException  If a problem occurs while sending the request.
3027       */
3028      public AsyncRequestID asyncModifyDN(
3029                                 final ReadOnlyModifyDNRequest modifyDNRequest,
3030                                 final AsyncResultListener resultListener)
3031             throws LDAPException
3032      {
3033        if (synchronousMode())
3034        {
3035          throw new LDAPException(ResultCode.NOT_SUPPORTED,
3036               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
3037        }
3038    
3039        return asyncModifyDN((ModifyDNRequest) modifyDNRequest, resultListener);
3040      }
3041    
3042    
3043    
3044      /**
3045       * Processes a search operation with the provided information.  The search
3046       * result entries and references will be collected internally and included in
3047       * the {@code SearchResult} object that is returned.
3048       * <BR><BR>
3049       * Note that if the search does not complete successfully, an
3050       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3051       * search result entries or references may have been returned before the
3052       * failure response is received.  In this case, the
3053       * {@code LDAPSearchException} methods like {@code getEntryCount},
3054       * {@code getSearchEntries}, {@code getReferenceCount}, and
3055       * {@code getSearchReferences} may be used to obtain information about those
3056       * entries and references.
3057       *
3058       * @param  baseDN      The base DN for the search request.  It must not be
3059       *                     {@code null}.
3060       * @param  scope       The scope that specifies the range of entries that
3061       *                     should be examined for the search.
3062       * @param  filter      The string representation of the filter to use to
3063       *                     identify matching entries.  It must not be
3064       *                     {@code null}.
3065       * @param  attributes  The set of attributes that should be returned in
3066       *                     matching entries.  It may be {@code null} or empty if
3067       *                     the default attribute set (all user attributes) is to
3068       *                     be requested.
3069       *
3070       * @return  A search result object that provides information about the
3071       *          processing of the search, including the set of matching entries
3072       *          and search references returned by the server.
3073       *
3074       * @throws  LDAPSearchException  If the search does not complete successfully,
3075       *                               or if a problem is encountered while parsing
3076       *                               the provided filter string, sending the
3077       *                               request, or reading the response.  If one
3078       *                               or more entries or references were returned
3079       *                               before the failure was encountered, then the
3080       *                               {@code LDAPSearchException} object may be
3081       *                               examined to obtain information about those
3082       *                               entries and/or references.
3083       */
3084      public SearchResult search(final String baseDN, final SearchScope scope,
3085                                 final String filter, final String... attributes)
3086             throws LDAPSearchException
3087      {
3088        ensureNotNull(baseDN, filter);
3089    
3090        try
3091        {
3092          return search(new SearchRequest(baseDN, scope, filter, attributes));
3093        }
3094        catch (LDAPSearchException lse)
3095        {
3096          debugException(lse);
3097          throw lse;
3098        }
3099        catch (LDAPException le)
3100        {
3101          debugException(le);
3102          throw new LDAPSearchException(le);
3103        }
3104      }
3105    
3106    
3107    
3108      /**
3109       * Processes a search operation with the provided information.  The search
3110       * result entries and references will be collected internally and included in
3111       * the {@code SearchResult} object that is returned.
3112       * <BR><BR>
3113       * Note that if the search does not complete successfully, an
3114       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3115       * search result entries or references may have been returned before the
3116       * failure response is received.  In this case, the
3117       * {@code LDAPSearchException} methods like {@code getEntryCount},
3118       * {@code getSearchEntries}, {@code getReferenceCount}, and
3119       * {@code getSearchReferences} may be used to obtain information about those
3120       * entries and references.
3121       *
3122       * @param  baseDN      The base DN for the search request.  It must not be
3123       *                     {@code null}.
3124       * @param  scope       The scope that specifies the range of entries that
3125       *                     should be examined for the search.
3126       * @param  filter      The filter to use to identify matching entries.  It
3127       *                     must not be {@code null}.
3128       * @param  attributes  The set of attributes that should be returned in
3129       *                     matching entries.  It may be {@code null} or empty if
3130       *                     the default attribute set (all user attributes) is to
3131       *                     be requested.
3132       *
3133       * @return  A search result object that provides information about the
3134       *          processing of the search, including the set of matching entries
3135       *          and search references returned by the server.
3136       *
3137       * @throws  LDAPSearchException  If the search does not complete successfully,
3138       *                               or if a problem is encountered while sending
3139       *                               the request or reading the response.  If one
3140       *                               or more entries or references were returned
3141       *                               before the failure was encountered, then the
3142       *                               {@code LDAPSearchException} object may be
3143       *                               examined to obtain information about those
3144       *                               entries and/or references.
3145       */
3146      public SearchResult search(final String baseDN, final SearchScope scope,
3147                                 final Filter filter, final String... attributes)
3148             throws LDAPSearchException
3149      {
3150        ensureNotNull(baseDN, filter);
3151    
3152        return search(new SearchRequest(baseDN, scope, filter, attributes));
3153      }
3154    
3155    
3156    
3157      /**
3158       * Processes a search operation with the provided information.
3159       * <BR><BR>
3160       * Note that if the search does not complete successfully, an
3161       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3162       * search result entries or references may have been returned before the
3163       * failure response is received.  In this case, the
3164       * {@code LDAPSearchException} methods like {@code getEntryCount},
3165       * {@code getSearchEntries}, {@code getReferenceCount}, and
3166       * {@code getSearchReferences} may be used to obtain information about those
3167       * entries and references (although if a search result listener was provided,
3168       * then it will have been used to make any entries and references available,
3169       * and they will not be available through the {@code getSearchEntries} and
3170       * {@code getSearchReferences} methods).
3171       *
3172       * @param  searchResultListener  The search result listener that should be
3173       *                               used to return results to the client.  It may
3174       *                               be {@code null} if the search results should
3175       *                               be collected internally and returned in the
3176       *                               {@code SearchResult} object.
3177       * @param  baseDN                The base DN for the search request.  It must
3178       *                               not be {@code null}.
3179       * @param  scope                 The scope that specifies the range of entries
3180       *                               that should be examined for the search.
3181       * @param  filter                The string representation of the filter to
3182       *                               use to identify matching entries.  It must
3183       *                               not be {@code null}.
3184       * @param  attributes            The set of attributes that should be returned
3185       *                               in matching entries.  It may be {@code null}
3186       *                               or empty if the default attribute set (all
3187       *                               user attributes) is to be requested.
3188       *
3189       * @return  A search result object that provides information about the
3190       *          processing of the search, potentially including the set of
3191       *          matching entries and search references returned by the server.
3192       *
3193       * @throws  LDAPSearchException  If the search does not complete successfully,
3194       *                               or if a problem is encountered while parsing
3195       *                               the provided filter string, sending the
3196       *                               request, or reading the response.  If one
3197       *                               or more entries or references were returned
3198       *                               before the failure was encountered, then the
3199       *                               {@code LDAPSearchException} object may be
3200       *                               examined to obtain information about those
3201       *                               entries and/or references.
3202       */
3203      public SearchResult search(final SearchResultListener searchResultListener,
3204                                 final String baseDN, final SearchScope scope,
3205                                 final String filter, final String... attributes)
3206             throws LDAPSearchException
3207      {
3208        ensureNotNull(baseDN, filter);
3209    
3210        try
3211        {
3212          return search(new SearchRequest(searchResultListener, baseDN, scope,
3213                                          filter, attributes));
3214        }
3215        catch (LDAPSearchException lse)
3216        {
3217          debugException(lse);
3218          throw lse;
3219        }
3220        catch (LDAPException le)
3221        {
3222          debugException(le);
3223          throw new LDAPSearchException(le);
3224        }
3225      }
3226    
3227    
3228    
3229      /**
3230       * Processes a search operation with the provided information.
3231       * <BR><BR>
3232       * Note that if the search does not complete successfully, an
3233       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3234       * search result entries or references may have been returned before the
3235       * failure response is received.  In this case, the
3236       * {@code LDAPSearchException} methods like {@code getEntryCount},
3237       * {@code getSearchEntries}, {@code getReferenceCount}, and
3238       * {@code getSearchReferences} may be used to obtain information about those
3239       * entries and references (although if a search result listener was provided,
3240       * then it will have been used to make any entries and references available,
3241       * and they will not be available through the {@code getSearchEntries} and
3242       * {@code getSearchReferences} methods).
3243       *
3244       * @param  searchResultListener  The search result listener that should be
3245       *                               used to return results to the client.  It may
3246       *                               be {@code null} if the search results should
3247       *                               be collected internally and returned in the
3248       *                               {@code SearchResult} object.
3249       * @param  baseDN                The base DN for the search request.  It must
3250       *                               not be {@code null}.
3251       * @param  scope                 The scope that specifies the range of entries
3252       *                               that should be examined for the search.
3253       * @param  filter                The filter to use to identify matching
3254       *                               entries.  It must not be {@code null}.
3255       * @param  attributes            The set of attributes that should be returned
3256       *                               in matching entries.  It may be {@code null}
3257       *                               or empty if the default attribute set (all
3258       *                               user attributes) is to be requested.
3259       *
3260       * @return  A search result object that provides information about the
3261       *          processing of the search, potentially including the set of
3262       *          matching entries and search references returned by the server.
3263       *
3264       * @throws  LDAPSearchException  If the search does not complete successfully,
3265       *                               or if a problem is encountered while sending
3266       *                               the request or reading the response.  If one
3267       *                               or more entries or references were returned
3268       *                               before the failure was encountered, then the
3269       *                               {@code LDAPSearchException} object may be
3270       *                               examined to obtain information about those
3271       *                               entries and/or references.
3272       */
3273      public SearchResult search(final SearchResultListener searchResultListener,
3274                                 final String baseDN, final SearchScope scope,
3275                                 final Filter filter, final String... attributes)
3276             throws LDAPSearchException
3277      {
3278        ensureNotNull(baseDN, filter);
3279    
3280        try
3281        {
3282          return search(new SearchRequest(searchResultListener, baseDN, scope,
3283                                          filter, attributes));
3284        }
3285        catch (LDAPSearchException lse)
3286        {
3287          debugException(lse);
3288          throw lse;
3289        }
3290        catch (LDAPException le)
3291        {
3292          debugException(le);
3293          throw new LDAPSearchException(le);
3294        }
3295      }
3296    
3297    
3298    
3299      /**
3300       * Processes a search operation with the provided information.  The search
3301       * result entries and references will be collected internally and included in
3302       * the {@code SearchResult} object that is returned.
3303       * <BR><BR>
3304       * Note that if the search does not complete successfully, an
3305       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3306       * search result entries or references may have been returned before the
3307       * failure response is received.  In this case, the
3308       * {@code LDAPSearchException} methods like {@code getEntryCount},
3309       * {@code getSearchEntries}, {@code getReferenceCount}, and
3310       * {@code getSearchReferences} may be used to obtain information about those
3311       * entries and references.
3312       *
3313       * @param  baseDN       The base DN for the search request.  It must not be
3314       *                      {@code null}.
3315       * @param  scope        The scope that specifies the range of entries that
3316       *                      should be examined for the search.
3317       * @param  derefPolicy  The dereference policy the server should use for any
3318       *                      aliases encountered while processing the search.
3319       * @param  sizeLimit    The maximum number of entries that the server should
3320       *                      return for the search.  A value of zero indicates that
3321       *                      there should be no limit.
3322       * @param  timeLimit    The maximum length of time in seconds that the server
3323       *                      should spend processing this search request.  A value
3324       *                      of zero indicates that there should be no limit.
3325       * @param  typesOnly    Indicates whether to return only attribute names in
3326       *                      matching entries, or both attribute names and values.
3327       * @param  filter       The string representation of the filter to use to
3328       *                      identify matching entries.  It must not be
3329       *                      {@code null}.
3330       * @param  attributes   The set of attributes that should be returned in
3331       *                      matching entries.  It may be {@code null} or empty if
3332       *                      the default attribute set (all user attributes) is to
3333       *                      be requested.
3334       *
3335       * @return  A search result object that provides information about the
3336       *          processing of the search, including the set of matching entries
3337       *          and search references returned by the server.
3338       *
3339       * @throws  LDAPSearchException  If the search does not complete successfully,
3340       *                               or if a problem is encountered while parsing
3341       *                               the provided filter string, sending the
3342       *                               request, or reading the response.  If one
3343       *                               or more entries or references were returned
3344       *                               before the failure was encountered, then the
3345       *                               {@code LDAPSearchException} object may be
3346       *                               examined to obtain information about those
3347       *                               entries and/or references.
3348       */
3349      public SearchResult search(final String baseDN, final SearchScope scope,
3350                                 final DereferencePolicy derefPolicy,
3351                                 final int sizeLimit, final int timeLimit,
3352                                 final boolean typesOnly, final String filter,
3353                                 final String... attributes)
3354             throws LDAPSearchException
3355      {
3356        ensureNotNull(baseDN, filter);
3357    
3358        try
3359        {
3360          return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
3361                                          timeLimit, typesOnly, filter,
3362                                          attributes));
3363        }
3364        catch (LDAPSearchException lse)
3365        {
3366          debugException(lse);
3367          throw lse;
3368        }
3369        catch (LDAPException le)
3370        {
3371          debugException(le);
3372          throw new LDAPSearchException(le);
3373        }
3374      }
3375    
3376    
3377    
3378      /**
3379       * Processes a search operation with the provided information.  The search
3380       * result entries and references will be collected internally and included in
3381       * the {@code SearchResult} object that is returned.
3382       * <BR><BR>
3383       * Note that if the search does not complete successfully, an
3384       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3385       * search result entries or references may have been returned before the
3386       * failure response is received.  In this case, the
3387       * {@code LDAPSearchException} methods like {@code getEntryCount},
3388       * {@code getSearchEntries}, {@code getReferenceCount}, and
3389       * {@code getSearchReferences} may be used to obtain information about those
3390       * entries and references.
3391       *
3392       * @param  baseDN       The base DN for the search request.  It must not be
3393       *                      {@code null}.
3394       * @param  scope        The scope that specifies the range of entries that
3395       *                      should be examined for the search.
3396       * @param  derefPolicy  The dereference policy the server should use for any
3397       *                      aliases encountered while processing the search.
3398       * @param  sizeLimit    The maximum number of entries that the server should
3399       *                      return for the search.  A value of zero indicates that
3400       *                      there should be no limit.
3401       * @param  timeLimit    The maximum length of time in seconds that the server
3402       *                      should spend processing this search request.  A value
3403       *                      of zero indicates that there should be no limit.
3404       * @param  typesOnly    Indicates whether to return only attribute names in
3405       *                      matching entries, or both attribute names and values.
3406       * @param  filter       The filter to use to identify matching entries.  It
3407       *                      must not be {@code null}.
3408       * @param  attributes   The set of attributes that should be returned in
3409       *                      matching entries.  It may be {@code null} or empty if
3410       *                      the default attribute set (all user attributes) is to
3411       *                      be requested.
3412       *
3413       * @return  A search result object that provides information about the
3414       *          processing of the search, including the set of matching entries
3415       *          and search references returned by the server.
3416       *
3417       * @throws  LDAPSearchException  If the search does not complete successfully,
3418       *                               or if a problem is encountered while sending
3419       *                               the request or reading the response.  If one
3420       *                               or more entries or references were returned
3421       *                               before the failure was encountered, then the
3422       *                               {@code LDAPSearchException} object may be
3423       *                               examined to obtain information about those
3424       *                               entries and/or references.
3425       */
3426      public SearchResult search(final String baseDN, final SearchScope scope,
3427                                 final DereferencePolicy derefPolicy,
3428                                 final int sizeLimit, final int timeLimit,
3429                                 final boolean typesOnly, final Filter filter,
3430                                 final String... attributes)
3431             throws LDAPSearchException
3432      {
3433        ensureNotNull(baseDN, filter);
3434    
3435        return search(new SearchRequest(baseDN, scope, derefPolicy, sizeLimit,
3436                                        timeLimit, typesOnly, filter, attributes));
3437      }
3438    
3439    
3440    
3441      /**
3442       * Processes a search operation with the provided information.
3443       * <BR><BR>
3444       * Note that if the search does not complete successfully, an
3445       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3446       * search result entries or references may have been returned before the
3447       * failure response is received.  In this case, the
3448       * {@code LDAPSearchException} methods like {@code getEntryCount},
3449       * {@code getSearchEntries}, {@code getReferenceCount}, and
3450       * {@code getSearchReferences} may be used to obtain information about those
3451       * entries and references (although if a search result listener was provided,
3452       * then it will have been used to make any entries and references available,
3453       * and they will not be available through the {@code getSearchEntries} and
3454       * {@code getSearchReferences} methods).
3455       *
3456       * @param  searchResultListener  The search result listener that should be
3457       *                               used to return results to the client.  It may
3458       *                               be {@code null} if the search results should
3459       *                               be collected internally and returned in the
3460       *                               {@code SearchResult} object.
3461       * @param  baseDN                The base DN for the search request.  It must
3462       *                               not be {@code null}.
3463       * @param  scope                 The scope that specifies the range of entries
3464       *                               that should be examined for the search.
3465       * @param  derefPolicy           The dereference policy the server should use
3466       *                               for any aliases encountered while processing
3467       *                               the search.
3468       * @param  sizeLimit             The maximum number of entries that the server
3469       *                               should return for the search.  A value of
3470       *                               zero indicates that there should be no limit.
3471       * @param  timeLimit             The maximum length of time in seconds that
3472       *                               the server should spend processing this
3473       *                               search request.  A value of zero indicates
3474       *                               that there should be no limit.
3475       * @param  typesOnly             Indicates whether to return only attribute
3476       *                               names in matching entries, or both attribute
3477       *                               names and values.
3478       * @param  filter                The string representation of the filter to
3479       *                               use to identify matching entries.  It must
3480       *                               not be {@code null}.
3481       * @param  attributes            The set of attributes that should be returned
3482       *                               in matching entries.  It may be {@code null}
3483       *                               or empty if the default attribute set (all
3484       *                               user attributes) is to be requested.
3485       *
3486       * @return  A search result object that provides information about the
3487       *          processing of the search, potentially including the set of
3488       *          matching entries and search references returned by the server.
3489       *
3490       * @throws  LDAPSearchException  If the search does not complete successfully,
3491       *                               or if a problem is encountered while parsing
3492       *                               the provided filter string, sending the
3493       *                               request, or reading the response.  If one
3494       *                               or more entries or references were returned
3495       *                               before the failure was encountered, then the
3496       *                               {@code LDAPSearchException} object may be
3497       *                               examined to obtain information about those
3498       *                               entries and/or references.
3499       */
3500      public SearchResult search(final SearchResultListener searchResultListener,
3501                                 final String baseDN, final SearchScope scope,
3502                                 final DereferencePolicy derefPolicy,
3503                                 final int sizeLimit, final int timeLimit,
3504                                 final boolean typesOnly, final String filter,
3505                                 final String... attributes)
3506             throws LDAPSearchException
3507      {
3508        ensureNotNull(baseDN, filter);
3509    
3510        try
3511        {
3512          return search(new SearchRequest(searchResultListener, baseDN, scope,
3513                                          derefPolicy, sizeLimit, timeLimit,
3514                                          typesOnly, filter, attributes));
3515        }
3516        catch (LDAPSearchException lse)
3517        {
3518          debugException(lse);
3519          throw lse;
3520        }
3521        catch (LDAPException le)
3522        {
3523          debugException(le);
3524          throw new LDAPSearchException(le);
3525        }
3526      }
3527    
3528    
3529    
3530      /**
3531       * Processes a search operation with the provided information.
3532       * <BR><BR>
3533       * Note that if the search does not complete successfully, an
3534       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3535       * search result entries or references may have been returned before the
3536       * failure response is received.  In this case, the
3537       * {@code LDAPSearchException} methods like {@code getEntryCount},
3538       * {@code getSearchEntries}, {@code getReferenceCount}, and
3539       * {@code getSearchReferences} may be used to obtain information about those
3540       * entries and references (although if a search result listener was provided,
3541       * then it will have been used to make any entries and references available,
3542       * and they will not be available through the {@code getSearchEntries} and
3543       * {@code getSearchReferences} methods).
3544       *
3545       * @param  searchResultListener  The search result listener that should be
3546       *                               used to return results to the client.  It may
3547       *                               be {@code null} if the search results should
3548       *                               be collected internally and returned in the
3549       *                               {@code SearchResult} object.
3550       * @param  baseDN                The base DN for the search request.  It must
3551       *                               not be {@code null}.
3552       * @param  scope                 The scope that specifies the range of entries
3553       *                               that should be examined for the search.
3554       * @param  derefPolicy           The dereference policy the server should use
3555       *                               for any aliases encountered while processing
3556       *                               the search.
3557       * @param  sizeLimit             The maximum number of entries that the server
3558       *                               should return for the search.  A value of
3559       *                               zero indicates that there should be no limit.
3560       * @param  timeLimit             The maximum length of time in seconds that
3561       *                               the server should spend processing this
3562       *                               search request.  A value of zero indicates
3563       *                               that there should be no limit.
3564       * @param  typesOnly             Indicates whether to return only attribute
3565       *                               names in matching entries, or both attribute
3566       *                               names and values.
3567       * @param  filter                The filter to use to identify matching
3568       *                               entries.  It must not be {@code null}.
3569       * @param  attributes            The set of attributes that should be returned
3570       *                               in matching entries.  It may be {@code null}
3571       *                               or empty if the default attribute set (all
3572       *                               user attributes) is to be requested.
3573       *
3574       * @return  A search result object that provides information about the
3575       *          processing of the search, potentially including the set of
3576       *          matching entries and search references returned by the server.
3577       *
3578       * @throws  LDAPSearchException  If the search does not complete successfully,
3579       *                               or if a problem is encountered while sending
3580       *                               the request or reading the response.  If one
3581       *                               or more entries or references were returned
3582       *                               before the failure was encountered, then the
3583       *                               {@code LDAPSearchException} object may be
3584       *                               examined to obtain information about those
3585       *                               entries and/or references.
3586       */
3587      public SearchResult search(final SearchResultListener searchResultListener,
3588                                 final String baseDN, final SearchScope scope,
3589                                 final DereferencePolicy derefPolicy,
3590                                 final int sizeLimit, final int timeLimit,
3591                                 final boolean typesOnly, final Filter filter,
3592                                 final String... attributes)
3593             throws LDAPSearchException
3594      {
3595        ensureNotNull(baseDN, filter);
3596    
3597        return search(new SearchRequest(searchResultListener, baseDN, scope,
3598                                        derefPolicy, sizeLimit, timeLimit,
3599                                        typesOnly, filter, attributes));
3600      }
3601    
3602    
3603    
3604      /**
3605       * Processes the provided search request.
3606       * <BR><BR>
3607       * Note that if the search does not complete successfully, an
3608       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3609       * search result entries or references may have been returned before the
3610       * failure response is received.  In this case, the
3611       * {@code LDAPSearchException} methods like {@code getEntryCount},
3612       * {@code getSearchEntries}, {@code getReferenceCount}, and
3613       * {@code getSearchReferences} may be used to obtain information about those
3614       * entries and references (although if a search result listener was provided,
3615       * then it will have been used to make any entries and references available,
3616       * and they will not be available through the {@code getSearchEntries} and
3617       * {@code getSearchReferences} methods).
3618       *
3619       * @param  searchRequest  The search request to be processed.  It must not be
3620       *                        {@code null}.
3621       *
3622       * @return  A search result object that provides information about the
3623       *          processing of the search, potentially including the set of
3624       *          matching entries and search references returned by the server.
3625       *
3626       * @throws  LDAPSearchException  If the search does not complete successfully,
3627       *                               or if a problem is encountered while sending
3628       *                               the request or reading the response.  If one
3629       *                               or more entries or references were returned
3630       *                               before the failure was encountered, then the
3631       *                               {@code LDAPSearchException} object may be
3632       *                               examined to obtain information about those
3633       *                               entries and/or references.
3634       */
3635      public SearchResult search(final SearchRequest searchRequest)
3636             throws LDAPSearchException
3637      {
3638        ensureNotNull(searchRequest);
3639    
3640        final SearchResult searchResult;
3641        try
3642        {
3643          searchResult = searchRequest.process(this, 1);
3644        }
3645        catch (LDAPSearchException lse)
3646        {
3647          debugException(lse);
3648          throw lse;
3649        }
3650        catch (LDAPException le)
3651        {
3652          debugException(le);
3653          throw new LDAPSearchException(le);
3654        }
3655    
3656        if (! searchResult.getResultCode().equals(ResultCode.SUCCESS))
3657        {
3658          throw new LDAPSearchException(searchResult);
3659        }
3660    
3661        return searchResult;
3662      }
3663    
3664    
3665    
3666      /**
3667       * Processes the provided search request.
3668       * <BR><BR>
3669       * Note that if the search does not complete successfully, an
3670       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3671       * search result entries or references may have been returned before the
3672       * failure response is received.  In this case, the
3673       * {@code LDAPSearchException} methods like {@code getEntryCount},
3674       * {@code getSearchEntries}, {@code getReferenceCount}, and
3675       * {@code getSearchReferences} may be used to obtain information about those
3676       * entries and references (although if a search result listener was provided,
3677       * then it will have been used to make any entries and references available,
3678       * and they will not be available through the {@code getSearchEntries} and
3679       * {@code getSearchReferences} methods).
3680       *
3681       * @param  searchRequest  The search request to be processed.  It must not be
3682       *                        {@code null}.
3683       *
3684       * @return  A search result object that provides information about the
3685       *          processing of the search, potentially including the set of
3686       *          matching entries and search references returned by the server.
3687       *
3688       * @throws  LDAPSearchException  If the search does not complete successfully,
3689       *                               or if a problem is encountered while sending
3690       *                               the request or reading the response.  If one
3691       *                               or more entries or references were returned
3692       *                               before the failure was encountered, then the
3693       *                               {@code LDAPSearchException} object may be
3694       *                               examined to obtain information about those
3695       *                               entries and/or references.
3696       */
3697      public SearchResult search(final ReadOnlySearchRequest searchRequest)
3698             throws LDAPSearchException
3699      {
3700        return search((SearchRequest) searchRequest);
3701      }
3702    
3703    
3704    
3705      /**
3706       * Processes a search operation with the provided information.  It is expected
3707       * that at most one entry will be returned from the search, and that no
3708       * additional content from the successful search result (e.g., diagnostic
3709       * message or response controls) are needed.
3710       * <BR><BR>
3711       * Note that if the search does not complete successfully, an
3712       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3713       * search result entries or references may have been returned before the
3714       * failure response is received.  In this case, the
3715       * {@code LDAPSearchException} methods like {@code getEntryCount},
3716       * {@code getSearchEntries}, {@code getReferenceCount}, and
3717       * {@code getSearchReferences} may be used to obtain information about those
3718       * entries and references.
3719       *
3720       * @param  baseDN      The base DN for the search request.  It must not be
3721       *                     {@code null}.
3722       * @param  scope       The scope that specifies the range of entries that
3723       *                     should be examined for the search.
3724       * @param  filter      The string representation of the filter to use to
3725       *                     identify matching entries.  It must not be
3726       *                     {@code null}.
3727       * @param  attributes  The set of attributes that should be returned in
3728       *                     matching entries.  It may be {@code null} or empty if
3729       *                     the default attribute set (all user attributes) is to
3730       *                     be requested.
3731       *
3732       * @return  The entry that was returned from the search, or {@code null} if no
3733       *          entry was returned or the base entry does not exist.
3734       *
3735       * @throws  LDAPSearchException  If the search does not complete successfully,
3736       *                               if more than a single entry is returned, or
3737       *                               if a problem is encountered while parsing the
3738       *                               provided filter string, sending the request,
3739       *                               or reading the response.  If one or more
3740       *                               entries or references were returned before
3741       *                               the failure was encountered, then the
3742       *                               {@code LDAPSearchException} object may be
3743       *                               examined to obtain information about those
3744       *                               entries and/or references.
3745       */
3746      public SearchResultEntry searchForEntry(final String baseDN,
3747                                              final SearchScope scope,
3748                                              final String filter,
3749                                              final String... attributes)
3750             throws LDAPSearchException
3751      {
3752        final SearchRequest r;
3753        try
3754        {
3755          r = new SearchRequest(baseDN, scope, DereferencePolicy.NEVER, 1, 0, false,
3756               filter, attributes);
3757        }
3758        catch (final LDAPException le)
3759        {
3760          debugException(le);
3761          throw new LDAPSearchException(le);
3762        }
3763    
3764        return searchForEntry(r);
3765      }
3766    
3767    
3768    
3769      /**
3770       * Processes a search operation with the provided information.  It is expected
3771       * that at most one entry will be returned from the search, and that no
3772       * additional content from the successful search result (e.g., diagnostic
3773       * message or response controls) are needed.
3774       * <BR><BR>
3775       * Note that if the search does not complete successfully, an
3776       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3777       * search result entries or references may have been returned before the
3778       * failure response is received.  In this case, the
3779       * {@code LDAPSearchException} methods like {@code getEntryCount},
3780       * {@code getSearchEntries}, {@code getReferenceCount}, and
3781       * {@code getSearchReferences} may be used to obtain information about those
3782       * entries and references.
3783       *
3784       * @param  baseDN      The base DN for the search request.  It must not be
3785       *                     {@code null}.
3786       * @param  scope       The scope that specifies the range of entries that
3787       *                     should be examined for the search.
3788       * @param  filter      The string representation of the filter to use to
3789       *                     identify matching entries.  It must not be
3790       *                     {@code null}.
3791       * @param  attributes  The set of attributes that should be returned in
3792       *                     matching entries.  It may be {@code null} or empty if
3793       *                     the default attribute set (all user attributes) is to
3794       *                     be requested.
3795       *
3796       * @return  The entry that was returned from the search, or {@code null} if no
3797       *          entry was returned or the base entry does not exist.
3798       *
3799       * @throws  LDAPSearchException  If the search does not complete successfully,
3800       *                               if more than a single entry is returned, or
3801       *                               if a problem is encountered while parsing the
3802       *                               provided filter string, sending the request,
3803       *                               or reading the response.  If one or more
3804       *                               entries or references were returned before
3805       *                               the failure was encountered, then the
3806       *                               {@code LDAPSearchException} object may be
3807       *                               examined to obtain information about those
3808       *                               entries and/or references.
3809       */
3810      public SearchResultEntry searchForEntry(final String baseDN,
3811                                              final SearchScope scope,
3812                                              final Filter filter,
3813                                              final String... attributes)
3814             throws LDAPSearchException
3815      {
3816        return searchForEntry(new SearchRequest(baseDN, scope,
3817             DereferencePolicy.NEVER, 1, 0, false, filter, attributes));
3818      }
3819    
3820    
3821    
3822      /**
3823       * Processes a search operation with the provided information.  It is expected
3824       * that at most one entry will be returned from the search, and that no
3825       * additional content from the successful search result (e.g., diagnostic
3826       * message or response controls) are needed.
3827       * <BR><BR>
3828       * Note that if the search does not complete successfully, an
3829       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3830       * search result entries or references may have been returned before the
3831       * failure response is received.  In this case, the
3832       * {@code LDAPSearchException} methods like {@code getEntryCount},
3833       * {@code getSearchEntries}, {@code getReferenceCount}, and
3834       * {@code getSearchReferences} may be used to obtain information about those
3835       * entries and references.
3836       *
3837       * @param  baseDN       The base DN for the search request.  It must not be
3838       *                      {@code null}.
3839       * @param  scope        The scope that specifies the range of entries that
3840       *                      should be examined for the search.
3841       * @param  derefPolicy  The dereference policy the server should use for any
3842       *                      aliases encountered while processing the search.
3843       * @param  timeLimit    The maximum length of time in seconds that the server
3844       *                      should spend processing this search request.  A value
3845       *                      of zero indicates that there should be no limit.
3846       * @param  typesOnly    Indicates whether to return only attribute names in
3847       *                      matching entries, or both attribute names and values.
3848       * @param  filter       The string representation of the filter to use to
3849       *                      identify matching entries.  It must not be
3850       *                      {@code null}.
3851       * @param  attributes   The set of attributes that should be returned in
3852       *                      matching entries.  It may be {@code null} or empty if
3853       *                      the default attribute set (all user attributes) is to
3854       *                      be requested.
3855       *
3856       * @return  The entry that was returned from the search, or {@code null} if no
3857       *          entry was returned or the base entry does not exist.
3858       *
3859       * @throws  LDAPSearchException  If the search does not complete successfully,
3860       *                               if more than a single entry is returned, or
3861       *                               if a problem is encountered while parsing the
3862       *                               provided filter string, sending the request,
3863       *                               or reading the response.  If one or more
3864       *                               entries or references were returned before
3865       *                               the failure was encountered, then the
3866       *                               {@code LDAPSearchException} object may be
3867       *                               examined to obtain information about those
3868       *                               entries and/or references.
3869       */
3870      public SearchResultEntry searchForEntry(final String baseDN,
3871                                              final SearchScope scope,
3872                                              final DereferencePolicy derefPolicy,
3873                                              final int timeLimit,
3874                                              final boolean typesOnly,
3875                                              final String filter,
3876                                              final String... attributes)
3877             throws LDAPSearchException
3878      {
3879        final SearchRequest r;
3880        try
3881        {
3882          r = new SearchRequest(baseDN, scope, derefPolicy, 1, timeLimit, typesOnly,
3883               filter, attributes);
3884        }
3885        catch (final LDAPException le)
3886        {
3887          debugException(le);
3888          throw new LDAPSearchException(le);
3889        }
3890    
3891        return searchForEntry(r);
3892      }
3893    
3894    
3895    
3896      /**
3897       * Processes a search operation with the provided information.  It is expected
3898       * that at most one entry will be returned from the search, and that no
3899       * additional content from the successful search result (e.g., diagnostic
3900       * message or response controls) are needed.
3901       * <BR><BR>
3902       * Note that if the search does not complete successfully, an
3903       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3904       * search result entries or references may have been returned before the
3905       * failure response is received.  In this case, the
3906       * {@code LDAPSearchException} methods like {@code getEntryCount},
3907       * {@code getSearchEntries}, {@code getReferenceCount}, and
3908       * {@code getSearchReferences} may be used to obtain information about those
3909       * entries and references.
3910       *
3911       * @param  baseDN       The base DN for the search request.  It must not be
3912       *                      {@code null}.
3913       * @param  scope        The scope that specifies the range of entries that
3914       *                      should be examined for the search.
3915       * @param  derefPolicy  The dereference policy the server should use for any
3916       *                      aliases encountered while processing the search.
3917       * @param  timeLimit    The maximum length of time in seconds that the server
3918       *                      should spend processing this search request.  A value
3919       *                      of zero indicates that there should be no limit.
3920       * @param  typesOnly    Indicates whether to return only attribute names in
3921       *                      matching entries, or both attribute names and values.
3922       * @param  filter       The filter to use to identify matching entries.  It
3923       *                      must not be {@code null}.
3924       * @param  attributes   The set of attributes that should be returned in
3925       *                      matching entries.  It may be {@code null} or empty if
3926       *                      the default attribute set (all user attributes) is to
3927       *                      be requested.
3928       *
3929       * @return  The entry that was returned from the search, or {@code null} if no
3930       *          entry was returned or the base entry does not exist.
3931       *
3932       * @throws  LDAPSearchException  If the search does not complete successfully,
3933       *                               if more than a single entry is returned, or
3934       *                               if a problem is encountered while parsing the
3935       *                               provided filter string, sending the request,
3936       *                               or reading the response.  If one or more
3937       *                               entries or references were returned before
3938       *                               the failure was encountered, then the
3939       *                               {@code LDAPSearchException} object may be
3940       *                               examined to obtain information about those
3941       *                               entries and/or references.
3942       */
3943      public SearchResultEntry searchForEntry(final String baseDN,
3944                                              final SearchScope scope,
3945                                              final DereferencePolicy derefPolicy,
3946                                              final int timeLimit,
3947                                              final boolean typesOnly,
3948                                              final Filter filter,
3949                                              final String... attributes)
3950           throws LDAPSearchException
3951      {
3952        return searchForEntry(new SearchRequest(baseDN, scope, derefPolicy, 1,
3953             timeLimit, typesOnly, filter, attributes));
3954      }
3955    
3956    
3957    
3958      /**
3959       * Processes the provided search request.  It is expected that at most one
3960       * entry will be returned from the search, and that no additional content from
3961       * the successful search result (e.g., diagnostic message or response
3962       * controls) are needed.
3963       * <BR><BR>
3964       * Note that if the search does not complete successfully, an
3965       * {@code LDAPSearchException} will be thrown  In some cases, one or more
3966       * search result entries or references may have been returned before the
3967       * failure response is received.  In this case, the
3968       * {@code LDAPSearchException} methods like {@code getEntryCount},
3969       * {@code getSearchEntries}, {@code getReferenceCount}, and
3970       * {@code getSearchReferences} may be used to obtain information about those
3971       * entries and references.
3972       *
3973       * @param  searchRequest  The search request to be processed.  If it is
3974       *                        configured with a search result listener or a size
3975       *                        limit other than one, then the provided request will
3976       *                        be duplicated with the appropriate settings.
3977       *
3978       * @return  The entry that was returned from the search, or {@code null} if no
3979       *          entry was returned or the base entry does not exist.
3980       *
3981       * @throws  LDAPSearchException  If the search does not complete successfully,
3982       *                               if more than a single entry is returned, or
3983       *                               if a problem is encountered while parsing the
3984       *                               provided filter string, sending the request,
3985       *                               or reading the response.  If one or more
3986       *                               entries or references were returned before
3987       *                               the failure was encountered, then the
3988       *                               {@code LDAPSearchException} object may be
3989       *                               examined to obtain information about those
3990       *                               entries and/or references.
3991       */
3992      public SearchResultEntry searchForEntry(final SearchRequest searchRequest)
3993             throws LDAPSearchException
3994      {
3995        final SearchRequest r;
3996        if ((searchRequest.getSearchResultListener() != null) ||
3997            (searchRequest.getSizeLimit() != 1))
3998        {
3999          r = new SearchRequest(searchRequest.getBaseDN(), searchRequest.getScope(),
4000               searchRequest.getDereferencePolicy(), 1,
4001               searchRequest.getTimeLimitSeconds(), searchRequest.typesOnly(),
4002               searchRequest.getFilter(), searchRequest.getAttributes());
4003    
4004          r.setFollowReferrals(searchRequest.followReferralsInternal());
4005          r.setResponseTimeoutMillis(searchRequest.getResponseTimeoutMillis(null));
4006    
4007          if (searchRequest.hasControl())
4008          {
4009            r.setControlsInternal(searchRequest.getControls());
4010          }
4011        }
4012        else
4013        {
4014          r = searchRequest;
4015        }
4016    
4017        final SearchResult result;
4018        try
4019        {
4020          result = search(r);
4021        }
4022        catch (final LDAPSearchException lse)
4023        {
4024          debugException(lse);
4025    
4026          if (lse.getResultCode() == ResultCode.NO_SUCH_OBJECT)
4027          {
4028            return null;
4029          }
4030    
4031          throw lse;
4032        }
4033    
4034        if (result.getEntryCount() == 0)
4035        {
4036          return null;
4037        }
4038        else
4039        {
4040          return result.getSearchEntries().get(0);
4041        }
4042      }
4043    
4044    
4045    
4046      /**
4047       * Processes the provided search request.  It is expected that at most one
4048       * entry will be returned from the search, and that no additional content from
4049       * the successful search result (e.g., diagnostic message or response
4050       * controls) are needed.
4051       * <BR><BR>
4052       * Note that if the search does not complete successfully, an
4053       * {@code LDAPSearchException} will be thrown  In some cases, one or more
4054       * search result entries or references may have been returned before the
4055       * failure response is received.  In this case, the
4056       * {@code LDAPSearchException} methods like {@code getEntryCount},
4057       * {@code getSearchEntries}, {@code getReferenceCount}, and
4058       * {@code getSearchReferences} may be used to obtain information about those
4059       * entries and references.
4060       *
4061       * @param  searchRequest  The search request to be processed.  If it is
4062       *                        configured with a search result listener or a size
4063       *                        limit other than one, then the provided request will
4064       *                        be duplicated with the appropriate settings.
4065       *
4066       * @return  The entry that was returned from the search, or {@code null} if no
4067       *          entry was returned or the base entry does not exist.
4068       *
4069       * @throws  LDAPSearchException  If the search does not complete successfully,
4070       *                               if more than a single entry is returned, or
4071       *                               if a problem is encountered while parsing the
4072       *                               provided filter string, sending the request,
4073       *                               or reading the response.  If one or more
4074       *                               entries or references were returned before
4075       *                               the failure was encountered, then the
4076       *                               {@code LDAPSearchException} object may be
4077       *                               examined to obtain information about those
4078       *                               entries and/or references.
4079       */
4080      public SearchResultEntry searchForEntry(
4081                                    final ReadOnlySearchRequest searchRequest)
4082             throws LDAPSearchException
4083      {
4084        return searchForEntry((SearchRequest) searchRequest);
4085      }
4086    
4087    
4088    
4089      /**
4090       * Processes the provided search request as an asynchronous operation.
4091       *
4092       * @param  searchRequest  The search request to be processed.  It must not be
4093       *                        {@code null}, and it must be configured with a
4094       *                        search result listener that is also an
4095       *                        {@code AsyncSearchResultListener}.
4096       *
4097       * @return  An async request ID that may be used to reference the operation.
4098       *
4099       * @throws  LDAPException  If the provided search request does not have a
4100       *                         search result listener that is an
4101       *                         {@code AsyncSearchResultListener}, or if a problem
4102       *                         occurs while sending the request.
4103       */
4104      public AsyncRequestID asyncSearch(final SearchRequest searchRequest)
4105             throws LDAPException
4106      {
4107        ensureNotNull(searchRequest);
4108    
4109        final SearchResultListener searchListener =
4110             searchRequest.getSearchResultListener();
4111        if (searchListener == null)
4112        {
4113          final LDAPException le = new LDAPException(ResultCode.PARAM_ERROR,
4114               ERR_ASYNC_SEARCH_NO_LISTENER.get());
4115          debugCodingError(le);
4116          throw le;
4117        }
4118        else if (! (searchListener instanceof AsyncSearchResultListener))
4119        {
4120          final LDAPException le = new LDAPException(ResultCode.PARAM_ERROR,
4121               ERR_ASYNC_SEARCH_INVALID_LISTENER.get());
4122          debugCodingError(le);
4123          throw le;
4124        }
4125    
4126        if (synchronousMode())
4127        {
4128          throw new LDAPException(ResultCode.NOT_SUPPORTED,
4129               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
4130        }
4131    
4132        return searchRequest.processAsync(this,
4133             (AsyncSearchResultListener) searchListener);
4134      }
4135    
4136    
4137    
4138      /**
4139       * Processes the provided search request as an asynchronous operation.
4140       *
4141       * @param  searchRequest  The search request to be processed.  It must not be
4142       *                        {@code null}, and it must be configured with a
4143       *                        search result listener that is also an
4144       *                        {@code AsyncSearchResultListener}.
4145       *
4146       * @return  An async request ID that may be used to reference the operation.
4147       *
4148       * @throws  LDAPException  If the provided search request does not have a
4149       *                         search result listener that is an
4150       *                         {@code AsyncSearchResultListener}, or if a problem
4151       *                         occurs while sending the request.
4152       */
4153      public AsyncRequestID asyncSearch(final ReadOnlySearchRequest searchRequest)
4154             throws LDAPException
4155      {
4156        if (synchronousMode())
4157        {
4158          throw new LDAPException(ResultCode.NOT_SUPPORTED,
4159               ERR_ASYNC_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
4160        }
4161    
4162        return asyncSearch((SearchRequest) searchRequest);
4163      }
4164    
4165    
4166    
4167      /**
4168       * Processes the provided generic request and returns the result.  This may
4169       * be useful for cases in which it is not known what type of operation the
4170       * request represents.
4171       *
4172       * @param  request  The request to be processed.
4173       *
4174       * @return  The result obtained from processing the request.
4175       *
4176       * @throws  LDAPException  If a problem occurs while sending the request or
4177       *                         reading the response.  Note simply having a
4178       *                         non-success result code in the response will not
4179       *                         cause an exception to be thrown.
4180       */
4181      public LDAPResult processOperation(final LDAPRequest request)
4182             throws LDAPException
4183      {
4184        return request.process(this, 1);
4185      }
4186    
4187    
4188    
4189      /**
4190       * Retrieves the referral connector that should be used to establish
4191       * connections for use when following referrals.
4192       *
4193       * @return  The referral connector that should be used to establish
4194       *          connections for use when following referrals.
4195       */
4196      public ReferralConnector getReferralConnector()
4197      {
4198        if (referralConnector == null)
4199        {
4200          return this;
4201        }
4202        else
4203        {
4204          return referralConnector;
4205        }
4206      }
4207    
4208    
4209    
4210      /**
4211       * Specifies the referral connector that should be used to establish
4212       * connections for use when following referrals.
4213       *
4214       * @param  referralConnector  The referral connector that should be used to
4215       *                            establish connections for use when following
4216       *                            referrals.
4217       */
4218      public void setReferralConnector(final ReferralConnector referralConnector)
4219      {
4220        if (referralConnector == null)
4221        {
4222          this.referralConnector = this;
4223        }
4224        else
4225        {
4226          this.referralConnector = referralConnector;
4227        }
4228      }
4229    
4230    
4231    
4232      /**
4233       * Sends the provided LDAP message to the server over this connection.
4234       *
4235       * @param  message  The LDAP message to send to the target server.
4236       *
4237       * @throws  LDAPException  If a problem occurs while sending the request.
4238       */
4239      void sendMessage(final LDAPMessage message)
4240             throws LDAPException
4241      {
4242        if (needsReconnect.compareAndSet(true, false))
4243        {
4244          reconnect();
4245        }
4246    
4247        final LDAPConnectionInternals internals = connectionInternals;
4248        if (internals == null)
4249        {
4250          throw new LDAPException(ResultCode.SERVER_DOWN,
4251                                  ERR_CONN_NOT_ESTABLISHED.get());
4252        }
4253        else
4254        {
4255          internals.sendMessage(message, connectionOptions.autoReconnect());
4256          lastCommunicationTime = System.currentTimeMillis();
4257        }
4258      }
4259    
4260    
4261    
4262      /**
4263       * Retrieves the message ID that should be used for the next request sent
4264       * over this connection.
4265       *
4266       * @return  The message ID that should be used for the next request sent over
4267       *          this connection, or -1 if this connection is not established.
4268       */
4269      int nextMessageID()
4270      {
4271        final LDAPConnectionInternals internals = connectionInternals;
4272        if (internals == null)
4273        {
4274          return -1;
4275        }
4276        else
4277        {
4278          return internals.nextMessageID();
4279        }
4280      }
4281    
4282    
4283    
4284      /**
4285       * Retrieves the disconnect info object for this connection, if available.
4286       *
4287       * @return  The disconnect info for this connection, or {@code null} if none
4288       *          is set.
4289       */
4290      DisconnectInfo getDisconnectInfo()
4291      {
4292        return disconnectInfo.get();
4293      }
4294    
4295    
4296    
4297      /**
4298       * Sets the disconnect type, message, and cause for this connection, if those
4299       * values have not been previously set.  It will not overwrite any values that
4300       * had been previously set.
4301       * <BR><BR>
4302       * This method may be called by code which is not part of the LDAP SDK to
4303       * provide additional information about the reason for the closure.  In that
4304       * case, this method must be called before the call to
4305       * {@link LDAPConnection#close}.
4306       *
4307       * @param  type     The disconnect type.  It must not be {@code null}.
4308       * @param  message  A message providing additional information about the
4309       *                  disconnect.  It may be {@code null} if no message is
4310       *                  available.
4311       * @param  cause    The exception that was caught to trigger the disconnect.
4312       *                  It may be {@code null} if the disconnect was not triggered
4313       *                  by an exception.
4314       */
4315      public void setDisconnectInfo(final DisconnectType type, final String message,
4316                                    final Throwable cause)
4317      {
4318        disconnectInfo.compareAndSet(null,
4319             new DisconnectInfo(this, type, message, cause));
4320      }
4321    
4322    
4323    
4324      /**
4325       * Sets the disconnect info for this connection, if it is not already set.
4326       *
4327       * @param  info  The disconnect info to be set, if it is not already set.
4328       *
4329       * @return  The disconnect info set for the connection, whether it was
4330       *          previously or newly set.
4331       */
4332      DisconnectInfo setDisconnectInfo(final DisconnectInfo info)
4333      {
4334        disconnectInfo.compareAndSet(null, info);
4335        return disconnectInfo.get();
4336      }
4337    
4338    
4339    
4340      /**
4341       * Retrieves the disconnect type for this connection, if available.
4342       *
4343       * @return  The disconnect type for this connection, or {@code null} if no
4344       *          disconnect type has been set.
4345       */
4346      public DisconnectType getDisconnectType()
4347      {
4348        final DisconnectInfo di = disconnectInfo.get();
4349        if (di == null)
4350        {
4351          return null;
4352        }
4353        else
4354        {
4355          return di.getType();
4356        }
4357      }
4358    
4359    
4360    
4361      /**
4362       * Retrieves the disconnect message for this connection, which may provide
4363       * additional information about the reason for the disconnect, if available.
4364       *
4365       * @return  The disconnect message for this connection, or {@code null} if
4366       *          no disconnect message has been set.
4367       */
4368      public String getDisconnectMessage()
4369      {
4370        final DisconnectInfo di = disconnectInfo.get();
4371        if (di == null)
4372        {
4373          return null;
4374        }
4375        else
4376        {
4377          return di.getMessage();
4378        }
4379      }
4380    
4381    
4382    
4383      /**
4384       * Retrieves the disconnect cause for this connection, which is an exception
4385       * or error that triggered the connection termination, if available.
4386       *
4387       * @return  The disconnect cause for this connection, or {@code null} if no
4388       *          disconnect cause has been set.
4389       */
4390      public Throwable getDisconnectCause()
4391      {
4392        final DisconnectInfo di = disconnectInfo.get();
4393        if (di == null)
4394        {
4395          return null;
4396        }
4397        else
4398        {
4399          return di.getCause();
4400        }
4401      }
4402    
4403    
4404    
4405      /**
4406       * Indicates that this connection has been closed and is no longer available
4407       * for use.
4408       */
4409      void setClosed()
4410      {
4411        needsReconnect.set(false);
4412    
4413        if (disconnectInfo.get() == null)
4414        {
4415          try
4416          {
4417            final StackTraceElement[] stackElements =
4418                 Thread.currentThread().getStackTrace();
4419            final StackTraceElement[] parentStackElements =
4420                 new StackTraceElement[stackElements.length - 1];
4421            System.arraycopy(stackElements, 1, parentStackElements, 0,
4422                 parentStackElements.length);
4423    
4424            setDisconnectInfo(DisconnectType.OTHER,
4425                 ERR_CONN_CLOSED_BY_UNEXPECTED_CALL_PATH.get(
4426                      getStackTrace(parentStackElements)),
4427                 null);
4428          }
4429          catch (final Exception e)
4430          {
4431            debugException(e);
4432          }
4433        }
4434    
4435        connectionStatistics.incrementNumDisconnects();
4436        final LDAPConnectionInternals internals = connectionInternals;
4437        if (internals != null)
4438        {
4439          internals.close();
4440          connectionInternals = null;
4441        }
4442    
4443        cachedSchema = null;
4444        lastCommunicationTime = -1L;
4445    
4446        synchronized (this)
4447        {
4448          final Timer t = timer;
4449          timer = null;
4450    
4451          if (t != null)
4452          {
4453            t.cancel();
4454          }
4455        }
4456      }
4457    
4458    
4459    
4460      /**
4461       * Registers the provided response acceptor with the connection reader.
4462       *
4463       * @param  messageID         The message ID for which the acceptor is to be
4464       *                           registered.
4465       * @param  responseAcceptor  The response acceptor to register.
4466       *
4467       * @throws  LDAPException  If another message acceptor is already registered
4468       *                         with the provided message ID.
4469       */
4470      void registerResponseAcceptor(final int messageID,
4471                                    final ResponseAcceptor responseAcceptor)
4472           throws LDAPException
4473      {
4474        if (needsReconnect.compareAndSet(true, false))
4475        {
4476          reconnect();
4477        }
4478    
4479        final LDAPConnectionInternals internals = connectionInternals;
4480        if (internals == null)
4481        {
4482          throw new LDAPException(ResultCode.SERVER_DOWN,
4483                                  ERR_CONN_NOT_ESTABLISHED.get());
4484        }
4485        else
4486        {
4487          internals.registerResponseAcceptor(messageID, responseAcceptor);
4488        }
4489      }
4490    
4491    
4492    
4493      /**
4494       * Deregisters the response acceptor associated with the provided message ID.
4495       *
4496       * @param  messageID  The message ID for which to deregister the associated
4497       *                    response acceptor.
4498       */
4499      void deregisterResponseAcceptor(final int messageID)
4500      {
4501        final LDAPConnectionInternals internals = connectionInternals;
4502        if (internals != null)
4503        {
4504          internals.deregisterResponseAcceptor(messageID);
4505        }
4506      }
4507    
4508    
4509    
4510      /**
4511       * Retrieves a timer for use with this connection, creating one if necessary.
4512       *
4513       * @return  A timer for use with this connection.
4514       */
4515      synchronized Timer getTimer()
4516      {
4517        if (timer == null)
4518        {
4519          timer = new Timer("Timer thread for " + toString(), true);
4520        }
4521    
4522        return timer;
4523      }
4524    
4525    
4526    
4527      /**
4528       * {@inheritDoc}
4529       */
4530      public LDAPConnection getReferralConnection(final LDAPURL referralURL,
4531                                                  final LDAPConnection connection)
4532             throws LDAPException
4533      {
4534        final String host = referralURL.getHost();
4535        final int    port = referralURL.getPort();
4536    
4537        BindRequest bindRequest = null;
4538        if (connection.lastBindRequest != null)
4539        {
4540          bindRequest = connection.lastBindRequest.getRebindRequest(host, port);
4541          if (bindRequest == null)
4542          {
4543            throw new LDAPException(ResultCode.REFERRAL,
4544                                    ERR_CONN_CANNOT_AUTHENTICATE_FOR_REFERRAL.get(
4545                                         host, port));
4546          }
4547        }
4548    
4549        final ExtendedRequest connStartTLSRequest = connection.startTLSRequest;
4550    
4551        final LDAPConnection conn = new LDAPConnection(connection.socketFactory,
4552             connection.connectionOptions, host, port);
4553    
4554        if (connStartTLSRequest != null)
4555        {
4556          try
4557          {
4558            final ExtendedResult startTLSResult =
4559                 conn.processExtendedOperation(connStartTLSRequest);
4560            if (startTLSResult.getResultCode() != ResultCode.SUCCESS)
4561            {
4562              throw new LDAPException(startTLSResult);
4563            }
4564          }
4565          catch (final LDAPException le)
4566          {
4567            debugException(le);
4568            conn.setDisconnectInfo(DisconnectType.SECURITY_PROBLEM, null, le);
4569            conn.close();
4570    
4571            throw le;
4572          }
4573        }
4574    
4575        if (bindRequest != null)
4576        {
4577          try
4578          {
4579            conn.bind(bindRequest);
4580          }
4581          catch (final LDAPException le)
4582          {
4583            debugException(le);
4584            conn.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
4585            conn.close();
4586    
4587            throw le;
4588          }
4589        }
4590    
4591        return conn;
4592      }
4593    
4594    
4595    
4596      /**
4597       * Retrieves the last successful bind request processed on this connection.
4598       *
4599       * @return  The last successful bind request processed on this connection.  It
4600       *          may be {@code null} if no bind has been performed, or if the last
4601       *          bind attempt was not successful.
4602       */
4603      public BindRequest getLastBindRequest()
4604      {
4605        return lastBindRequest;
4606      }
4607    
4608    
4609    
4610      /**
4611       * Retrieves the StartTLS request used to secure this connection.
4612       *
4613       * @return  The StartTLS request used to secure this connection, or
4614       *          {@code null} if StartTLS has not been used to secure this
4615       *          connection.
4616       */
4617      public ExtendedRequest getStartTLSRequest()
4618      {
4619        return startTLSRequest;
4620      }
4621    
4622    
4623    
4624      /**
4625       * Retrieves an instance of the {@code LDAPConnectionInternals} object for
4626       * this connection.
4627       *
4628       * @param  throwIfDisconnected  Indicates whether to throw an
4629       *                              {@code LDAPException} if the connection is not
4630       *                              established.
4631       *
4632       * @return  The {@code LDAPConnectionInternals} object for this connection, or
4633       *          {@code null} if the connection is not established and no exception
4634       *          should be thrown.
4635       *
4636       * @throws  LDAPException  If the connection is not established and
4637       *                         {@code throwIfDisconnected} is {@code true}.
4638       */
4639      LDAPConnectionInternals getConnectionInternals(
4640                                   final boolean throwIfDisconnected)
4641           throws LDAPException
4642      {
4643        final LDAPConnectionInternals internals = connectionInternals;
4644        if ((internals == null) && throwIfDisconnected)
4645        {
4646          throw new LDAPException(ResultCode.SERVER_DOWN,
4647               ERR_CONN_NOT_ESTABLISHED.get());
4648        }
4649        else
4650        {
4651          return internals;
4652        }
4653      }
4654    
4655    
4656    
4657      /**
4658       * Retrieves the cached schema for this connection, if applicable.
4659       *
4660       * @return  The cached schema for this connection, or {@code null} if it is
4661       *          not available (e.g., because the connection is not established,
4662       *          because {@link LDAPConnectionOptions#useSchema()} is false, or
4663       *          because an error occurred when trying to read the server schema).
4664       */
4665      Schema getCachedSchema()
4666      {
4667        return cachedSchema;
4668      }
4669    
4670    
4671    
4672      /**
4673       * Sets the cached schema for this connection.
4674       *
4675       * @param  cachedSchema  The cached schema for this connection.  It may be
4676       *                       {@code null} if no cached schema is available.
4677       */
4678      void setCachedSchema(final Schema cachedSchema)
4679      {
4680        this.cachedSchema = cachedSchema;
4681      }
4682    
4683    
4684    
4685      /**
4686       * Indicates whether this connection is operating in synchronous mode.
4687       *
4688       * @return  {@code true} if this connection is operating in synchronous mode,
4689       *          or {@code false} if not.
4690       */
4691      public boolean synchronousMode()
4692      {
4693        final LDAPConnectionInternals internals = connectionInternals;
4694        if (internals == null)
4695        {
4696          return false;
4697        }
4698        else
4699        {
4700          return internals.synchronousMode();
4701        }
4702      }
4703    
4704    
4705    
4706      /**
4707       * Reads a response from the server, blocking if necessary until the response
4708       * has been received.  This should only be used for connections operating in
4709       * synchronous mode.
4710       *
4711       * @param  messageID  The message ID for the response to be read.  Any
4712       *                    response read with a different message ID will be
4713       *                    discarded, unless it is an unsolicited notification in
4714       *                    which case it will be provided to any registered
4715       *                    unsolicited notification handler.
4716       *
4717       * @return  The response read from the server.
4718       *
4719       * @throws  LDAPException  If a problem occurs while reading the response.
4720       */
4721      LDAPResponse readResponse(final int messageID)
4722                   throws LDAPException
4723      {
4724        final LDAPConnectionInternals internals = connectionInternals;
4725        if (internals != null)
4726        {
4727          final LDAPResponse response =
4728               internals.getConnectionReader().readResponse(messageID);
4729          debugLDAPResult(response, this);
4730          return response;
4731        }
4732        else
4733        {
4734          final DisconnectInfo di = disconnectInfo.get();
4735          if (di == null)
4736          {
4737            return new ConnectionClosedResponse(ResultCode.CONNECT_ERROR,
4738                 ERR_CONN_READ_RESPONSE_NOT_ESTABLISHED.get());
4739          }
4740          else
4741          {
4742            return new ConnectionClosedResponse(di.getType().getResultCode(),
4743                 di.getMessage());
4744          }
4745        }
4746      }
4747    
4748    
4749    
4750      /**
4751       * Retrieves the time that this connection was established in the number of
4752       * milliseconds since January 1, 1970 UTC (the same format used by
4753       * {@code System.currentTimeMillis}.
4754       *
4755       * @return  The time that this connection was established, or -1 if the
4756       *          connection is not currently established.
4757       */
4758      public long getConnectTime()
4759      {
4760        final LDAPConnectionInternals internals = connectionInternals;
4761        if (internals != null)
4762        {
4763          return internals.getConnectTime();
4764        }
4765        else
4766        {
4767          return -1L;
4768        }
4769      }
4770    
4771    
4772    
4773      /**
4774       * Retrieves the time that this connection was last used to send or receive an
4775       * LDAP message.  The value will represent the number of milliseconds since
4776       * January 1, 1970 UTC (the same format used by
4777       * {@code System.currentTimeMillis}.
4778       *
4779       * @return  The time that this connection was last used to send or receive an
4780       *          LDAP message.  If the connection is not established, then -1 will
4781       *          be returned.  If the connection is established but no
4782       *          communication has been performed over the connection since it was
4783       *          established, then the value of {@link #getConnectTime()} will be
4784       *          returned.
4785       */
4786      public long getLastCommunicationTime()
4787      {
4788        if (lastCommunicationTime > 0L)
4789        {
4790          return lastCommunicationTime;
4791        }
4792        else
4793        {
4794          return getConnectTime();
4795        }
4796      }
4797    
4798    
4799    
4800      /**
4801       * Updates the last communication time for this connection to be the current
4802       * time.
4803       */
4804      void setLastCommunicationTime()
4805      {
4806        lastCommunicationTime = System.currentTimeMillis();
4807      }
4808    
4809    
4810    
4811      /**
4812       * Retrieves the connection statistics for this LDAP connection.
4813       *
4814       * @return  The connection statistics for this LDAP connection.
4815       */
4816      public LDAPConnectionStatistics getConnectionStatistics()
4817      {
4818        return connectionStatistics;
4819      }
4820    
4821    
4822    
4823      /**
4824       * Retrieves the number of outstanding operations on this LDAP connection
4825       * (i.e., the number of operations currently in progress).  The value will
4826       * only be valid for connections not configured to use synchronous mode.
4827       *
4828       * @return  The number of outstanding operations on this LDAP connection, or
4829       *          -1 if it cannot be determined (e.g., because the connection is not
4830       *          established or is operating in synchronous mode).
4831       */
4832      public int getActiveOperationCount()
4833      {
4834        final LDAPConnectionInternals internals = connectionInternals;
4835    
4836        if (internals == null)
4837        {
4838          return -1;
4839        }
4840        else
4841        {
4842          if (internals.synchronousMode())
4843          {
4844            return -1;
4845          }
4846          else
4847          {
4848            return internals.getConnectionReader().getActiveOperationCount();
4849          }
4850        }
4851      }
4852    
4853    
4854    
4855      /**
4856       * Retrieves the schema from the provided connection.  If the retrieved schema
4857       * matches schema that's already in use by other connections, the common
4858       * schema will be used instead of the newly-retrieved version.
4859       *
4860       * @param  c  The connection for which to retrieve the schema.
4861       *
4862       * @return  The schema retrieved from the given connection, or a cached
4863       *          schema if it matched a schema that was already in use.
4864       *
4865       * @throws  LDAPException  If a problem is encountered while retrieving or
4866       *                         parsing the schema.
4867       */
4868      private static Schema getCachedSchema(final LDAPConnection c)
4869             throws LDAPException
4870      {
4871        final Schema s = c.getSchema();
4872    
4873        synchronized (SCHEMA_SET)
4874        {
4875          return SCHEMA_SET.addAndGet(s);
4876        }
4877      }
4878    
4879    
4880    
4881      /**
4882       * Retrieves the connection attachment with the specified name.
4883       *
4884       * @param  name  The name of the attachment to retrieve.  It must not be
4885       *               {@code null}.
4886       *
4887       * @return  The connection attachment with the specified name, or {@code null}
4888       *          if there is no such attachment.
4889       */
4890      synchronized Object getAttachment(final String name)
4891      {
4892        if (attachments == null)
4893        {
4894          return null;
4895        }
4896        else
4897        {
4898          return attachments.get(name);
4899        }
4900      }
4901    
4902    
4903    
4904      /**
4905       * Sets a connection attachment with the specified name and value.
4906       *
4907       * @param  name   The name of the attachment to set.  It must not be
4908       *                {@code null}.
4909       * @param  value  The value to use for the attachment.  It may be {@code null}
4910       *                if an attachment with the specified name should be cleared
4911       *                rather than overwritten.
4912       */
4913      synchronized void setAttachment(final String name, final Object value)
4914      {
4915        if (attachments == null)
4916        {
4917          attachments = new HashMap<String,Object>(10);
4918        }
4919    
4920        if (value == null)
4921        {
4922          attachments.remove(name);
4923        }
4924        else
4925        {
4926          attachments.put(name, value);
4927        }
4928      }
4929    
4930    
4931    
4932      /**
4933       * Performs any necessary cleanup to ensure that this connection is properly
4934       * closed before it is garbage collected.
4935       *
4936       * @throws  Throwable  If the superclass finalizer throws an exception.
4937       */
4938      @Override()
4939      protected void finalize()
4940                throws Throwable
4941      {
4942        super.finalize();
4943    
4944        setDisconnectInfo(DisconnectType.CLOSED_BY_FINALIZER, null, null);
4945        setClosed();
4946      }
4947    
4948    
4949    
4950      /**
4951       * Retrieves a string representation of this LDAP connection.
4952       *
4953       * @return  A string representation of this LDAP connection.
4954       */
4955      @Override()
4956      public String toString()
4957      {
4958        final StringBuilder buffer = new StringBuilder();
4959        toString(buffer);
4960        return buffer.toString();
4961      }
4962    
4963    
4964    
4965      /**
4966       * Appends a string representation of this LDAP connection to the provided
4967       * buffer.
4968       *
4969       * @param  buffer  The buffer to which to append a string representation of
4970       *                 this LDAP connection.
4971       */
4972      public void toString(final StringBuilder buffer)
4973      {
4974        buffer.append("LDAPConnection(");
4975    
4976        final String name     = connectionName;
4977        final String poolName = connectionPoolName;
4978        if (name != null)
4979        {
4980          buffer.append("name='");
4981          buffer.append(name);
4982          buffer.append("', ");
4983        }
4984        else if (poolName != null)
4985        {
4986          buffer.append("poolName='");
4987          buffer.append(poolName);
4988          buffer.append("', ");
4989        }
4990    
4991        final LDAPConnectionInternals internals = connectionInternals;
4992        if ((internals != null) && internals.isConnected())
4993        {
4994          buffer.append("connected to ");
4995          buffer.append(internals.getHost());
4996          buffer.append(':');
4997          buffer.append(internals.getPort());
4998        }
4999        else
5000        {
5001          buffer.append("not connected");
5002        }
5003    
5004        buffer.append(')');
5005      }
5006    }