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