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