From 72650d4cc41c64136d064967d7fec3726d850fee Mon Sep 17 00:00:00 2001
From: Ludovic Poitou <ludovic.poitou@forgerock.com>
Date: Thu, 14 Oct 2010 11:52:28 +0000
Subject: [PATCH] Multiple enhancements and bug fixes to the SDK (update from OpenDS by matthew_swift):

---
 sdk/src/org/opends/sdk/requests/SearchRequestImpl.java                                        |   10 
 sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java                                           |  118 
 sdk/src/org/opends/sdk/AbstractLoadBalancingAlgorithm.java                                    |  428 ++
 sdk/src/com/sun/opends/sdk/tools/ArgumentParserConnectionFactory.java                         |   10 
 sdk/src/com/sun/opends/sdk/util/AsynchronousFutureResult.java                                 |  483 ++
 sdk/examples/org/opends/sdk/examples/server/proxy/package-info.java                           |   34 
 sdk/src/com/sun/opends/sdk/tools/AuthenticatedConnectionFactory.java                          |   44 
 sdk/examples/org/opends/sdk/examples/client/asyncsearch/Main.java                             |  322 +
 sdk/src/org/opends/sdk/AbstractConnection.java                                                |   10 
 sdk/src/org/opends/sdk/LDAPConnectionFactory.java                                             |   92 
 sdk/src/org/opends/sdk/ServerConnectionFactory.java                                           |   17 
 sdk/examples/org/opends/sdk/examples/server/store/Main.java                                   |  589 +++
 sdk/src/com/sun/opends/sdk/ldap/AbstractLDAPFutureResultImpl.java                             |   27 
 sdk/src/org/opends/sdk/SynchronousConnection.java                                             |   23 
 sdk/src/com/sun/opends/sdk/ldap/GlobalTransportFactory.java                                   |   48 
 sdk/src/org/opends/sdk/responses/ExtendedResultDecoder.java                                   |   75 
 sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java                                   |   10 
 sdk/src/com/sun/opends/sdk/ldap/LDAPBindFutureResultImpl.java                                 |    7 
 sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPListenerOptions.java                               |    2 
 sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java         |    4 
 sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java         |    4 
 sdk/src/org/opends/sdk/LDAPClientContext.java                                                 |   64 
 sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java                               |   18 
 sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java                                    |   28 
 sdk/examples/org/opends/sdk/examples/client/asyncsearch/package-info.java                     |   35 
 sdk/src/com/sun/opends/sdk/ldap/ASN1BufferWriter.java                                         |    8 
 sdk/nbproject/ide-targets.xml                                                                 |   38 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/DigestMD5SASLBindRequestTestCase.java |   33 
 sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java                                   |   10 
 sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPOptions.java                                       |    2 
 sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequest.java                                 |  439 +
 sdk/src/com/sun/opends/sdk/ldap/LDAPConnectionFactoryImpl.java                                |   69 
 sdk/src/org/opends/sdk/controls/package-info.java                                             |    4 
 sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequest.java                                    |  277 +
 sdk/src/com/sun/opends/sdk/ldap/LDAPCompareFutureResultImpl.java                              |    9 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/ConnectionFactoryTestCase.java                 |  148 
 sdk/src/com/sun/opends/sdk/ldap/LDAPExtendedFutureResultImpl.java                             |    8 
 sdk/src/com/sun/opends/sdk/extensions/PasswordPolicyStateExtendedRequest.java                 |    7 
 sdk/src/org/opends/sdk/requests/CancelExtendedRequestImpl.java                                |   27 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/TestCaseUtils.java                             |   42 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPListenerTestCase.java                      |  776 ++++
 sdk/src/org/opends/sdk/requests/Requests.java                                                 |   54 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/EntriesTestCase.java                           |  229 +
 sdk/src/org/opends/sdk/requests/CancelExtendedRequest.java                                    |   20 
 sdk/lib/gmbal-api-only.jar                                                                    |    0 
 sdk/src/org/opends/sdk/requests/SearchRequest.java                                            |   16 
 sdk/src/org/opends/sdk/package-info.java                                                      |    5 
 sdk/nbproject/ide-file-targets.xml                                                            |  218 +
 sdk/examples/org/opends/sdk/examples/client/search/Main.java                                  |  187 +
 sdk/examples/org/opends/sdk/examples/server/proxy/Main.java                                   |  515 +++
 sdk/src/com/sun/opends/sdk/ldap/LDAPFutureResultImpl.java                                     |   14 
 sdk/src/overview.html                                                                         |  103 
 sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java                                         |  293 -
 sdk/src/org/opends/sdk/Entry.java                                                             |    2 
 sdk/src/org/opends/sdk/FailoverLoadBalancingAlgorithm.java                                    |  272 -
 sdk/nbproject/genfiles.properties                                                             |    5 
 sdk/examples/org/opends/sdk/examples/server/store/package-info.java                           |   35 
 sdk/src/org/opends/sdk/AbstractAsynchronousConnection.java                                    |   20 
 sdk/examples/org/opends/sdk/examples/client/search/package-info.java                          |   34 
 sdk/src/org/opends/sdk/ServerConnection.java                                                  |   43 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/GSSAPISASLBindRequestTestCase.java    |   16 
 sdk/src/com/sun/opends/sdk/ldap/LDAPWriter.java                                               |    4 
 sdk/src/org/opends/sdk/AbstractConnectionFactory.java                                         |    7 
 sdk/lib/grizzly.jar                                                                           |    0 
 sdk/src/org/opends/sdk/requests/PasswordModifyExtendedRequestImpl.java                        |   11 
 sdk/src/com/sun/opends/sdk/ldap/InternalConnection.java                                       |   38 
 sdk/examples/org/opends/sdk/examples/client/modify/Main.java                                  |  166 
 sdk/src/org/opends/sdk/HeartBeatConnectionFactory.java                                        |   34 
 sdk/src/org/opends/sdk/Entries.java                                                           |  182 +
 sdk/src/org/opends/sdk/LoadBalancer.java                                                      |  100 
 sdk/nbproject/project.xml                                                                     |  283 +
 sdk/examples/org/opends/sdk/examples/client/modify/package-info.java                          |   36 
 sdk/src/org/opends/sdk/ErrorResultException.java                                              |   55 
 sdk/src/com/sun/opends/sdk/extensions/GetConnectionIDExtendedRequest.java                     |    7 
 sdk/src/org/opends/sdk/LinkedAttribute.java                                                   |  235 
 sdk/src/org/opends/sdk/requests/WhoAmIExtendedRequestImpl.java                                |   11 
 sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java                                         |   16 
 sdk/src/org/opends/sdk/requests/StartTLSExtendedRequestImpl.java                              |   16 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/EntryTestCase.java                             |    2 
 sdk/build.xml                                                                                 |   36 
 sdk/src/org/opends/sdk/requests/AbandonRequest.java                                           |   16 
 sdk/src/com/sun/opends/sdk/util/RecursiveFutureResult.java                                    |    4 
 sdk/src/org/opends/sdk/LoadBalancingAlgorithm.java                                            |   12 
 sdk/src/org/opends/sdk/LDAPListener.java                                                      |  234 
 sdk/src/com/sun/opends/sdk/ldap/LDAPSearchFutureResultImpl.java                               |    7 
 sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/GlobalTransportFactoryTestCase.java   |    4 
 sdk/src/com/sun/opends/sdk/ldap/LDAPListenerImpl.java                                         |   64 
 sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequestImpl.java                             |  462 +-
 sdk/src/org/opends/sdk/requests/Request.java                                                  |    4 
 sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java                                         |    6 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/DNTestCase.java                                |   15 
 sdk/resource/example-2000.ldif.zip                                                            |    0 
 sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java                                       |   22 
 sdk/tests/unit-tests-testng/src/org/opends/sdk/LinkedAttributeTestCase.java                   |  417 ++
 sdk/src/org/opends/sdk/RoundRobinLoadBalancingAlgorithm.java                                  |  178 +
 sdk/src/com/sun/opends/sdk/extensions/GetSymmetricKeyExtendedRequest.java                     |    7 
 sdk/src/org/opends/sdk/Connections.java                                                       |  160 
 sdk/src/org/opends/sdk/responses/AbstractExtendedResultDecoder.java                           |  137 
 sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java                                               |    6 
 sdk/src/org/opends/sdk/ConnectionPool.java                                                    |   47 
 sdk/src/com/sun/opends/sdk/ldap/TimeoutChecker.java                                           |    2 
 sdk/src/com/sun/opends/sdk/util/StaticUtils.java                                              |  108 
 sdk/src/org/opends/sdk/InternalConnectionFactory.java                                         |   34 
 sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequestImpl.java                                |  433 +-
 sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPServer.java                                |   59 
 105 files changed, 8,238 insertions(+), 1,919 deletions(-)

diff --git a/sdk/build.xml b/sdk/build.xml
index 97fd7c8..ad5c080 100755
--- a/sdk/build.xml
+++ b/sdk/build.xml
@@ -363,7 +363,7 @@
         <mkdir dir="${classes.dir}" />
         <mkdir dir="${build.lib.dir}" />
 
-        <javac srcdir="${src.dir}:${srcgen.dir}"
+        <javac srcdir="${src.dir}:${srcgen.dir}:${examples.dir}"
                destdir="${classes.dir}"
                debug="on"
                debuglevel="${build.debuglevel}"
@@ -408,11 +408,11 @@
 
         <jar jarfile="${pdir}/lib/${SHORT_NAME}.jar"
              basedir="${classes.dir}"
-             excludes="**/*_fr.properties,**/*_ja.properties,**/*_de.properties,**/*_es.properties,**/*_zh_TW.properties,**/*_zh_CN.properties,**/*_ko.properties"
+             excludes="**/examples/**,**/*_fr.properties,**/*_ja.properties,**/*_de.properties,**/*_es.properties,**/*_zh_TW.properties,**/*_zh_CN.properties,**/*_ko.properties"
              compress="true"
              index="true" />
 
-        <jar jarfile="${pdir}/lib/${SHORT_NAME}_fr.jar"
+    	<jar jarfile="${pdir}/lib/${SHORT_NAME}_fr.jar"
              basedir="${classes.dir}"
              includes="**/*_fr.properties"
              compress="true"
@@ -453,6 +453,29 @@
              includes="**/*_zh_TW.properties"
              compress="true"
              index="true" />
+    	
+    	<mkdir dir="${build.dir}/examples"/>
+    	
+    	<!-- Create examples.zip -->
+    	<jar jarfile="${build.dir}/examples/examples.jar"
+    	                     basedir="${classes.dir}"
+    	                     includes="**/examples/**"
+    	                     compress="true"
+    	                     index="true" />
+    	
+    	<zip destfile="${build.dir}/examples/src.zip">
+            <zipfileset dir="${examples.dir}" filemode="644" dirmode="755" />
+    	</zip>
+
+        <copy todir="${build.dir}/examples">
+            <fileset file="${resource.dir}/example-2000.ldif.zip" />
+        </copy>
+
+    	<zip destfile="${pdir}/examples.zip">
+    	    <zipfileset dir="${build.dir}/examples"
+    	                filemode="644"
+    	                dirmode="700" />
+        </zip>
 
         <copy todir="${pdir}/lib">
             <fileset file="${lib.dir}/*.jar" />
@@ -510,13 +533,15 @@
         <mkdir dir="${javadoc.dir}" />
 
         <javadoc access="protected"
-                 windowtitle="${PRODUCT_NAME} API Documentation"
+                 windowtitle="${PRODUCT_NAME} Documentation"
+        	     doctitle="${PRODUCT_NAME} Documentation"
                  maxmemory="${MEM}"
                  classpath="${lib.dir}/grizzly.jar"
                  destdir="${javadoc.dir}"
                  packagenames="org.opends.sdk.*"
                  source="1.5"
-                 sourcepath="src:src-generated">
+                 sourcepath="src:src-generated"
+                 overview="src/overview.html">
             <link href="http://java.sun.com/javase/6/docs/api/" />
         </javadoc>
     </target>
@@ -1159,6 +1184,7 @@
             <zipfileset dir="${srcgen.dir}" filemode="644" dirmode="755" />
         </zip>
     </target>
+	
     <!-- The build target that should be used for nightly builds. -->
     <target name="nightly"
        depends="nightlybuild,nightlytests"
diff --git a/sdk/examples/org/opends/sdk/examples/client/asyncsearch/Main.java b/sdk/examples/org/opends/sdk/examples/client/asyncsearch/Main.java
new file mode 100644
index 0000000..00b3f33
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/client/asyncsearch/Main.java
@@ -0,0 +1,322 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.examples.client.asyncsearch;
+
+
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+
+import org.opends.sdk.*;
+import org.opends.sdk.ldif.LDIFEntryWriter;
+import org.opends.sdk.requests.BindRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.responses.SearchResultReference;
+
+
+
+/**
+ * An example client application which searches a Directory Server using the
+ * asynchronous APIs. This example takes the following command line parameters:
+ *
+ * <pre>
+ *  &lt;host> &lt;port> &lt;username> &lt;password>
+ *      &lt;baseDN> &lt;scope> &lt;filter> [&lt;attibute> &lt;attribute> ...]
+ * </pre>
+ */
+public final class Main
+{
+  private static final class BindResultHandlerImpl implements
+      ResultHandler<BindResult>
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleErrorResult(final ErrorResultException error)
+    {
+      System.err.println(error.getMessage());
+      resultCode = error.getResult().getResultCode().intValue();
+      COMPLETION_LATCH.countDown();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleResult(final BindResult result)
+    {
+      // Bind succeeded: initiate search.
+      final SearchRequest request = Requests.newSearchRequest(baseDN, scope,
+          filter, attributes);
+      connection.search(request, new SearchResultHandlerImpl());
+    }
+
+  }
+
+
+
+  private static final class ConnectResultHandlerImpl implements
+      ResultHandler<AsynchronousConnection>
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleErrorResult(final ErrorResultException error)
+    {
+      System.err.println(error.getMessage());
+      resultCode = error.getResult().getResultCode().intValue();
+      COMPLETION_LATCH.countDown();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleResult(final AsynchronousConnection connection)
+    {
+      // Connect succeeded: save connection and initiate bind.
+      Main.connection = connection;
+
+      final BindRequest request = Requests.newSimpleBindRequest(userName,
+          password);
+      connection.bind(request, new BindResultHandlerImpl());
+    }
+
+  }
+
+
+
+  private static final class SearchResultHandlerImpl implements
+      SearchResultHandler
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized boolean handleEntry(final SearchResultEntry entry)
+    {
+      try
+      {
+        WRITER.writeComment("Search result entry: "
+            + entry.getName().toString());
+        WRITER.writeEntry(entry);
+      }
+      catch (final IOException e)
+      {
+        System.err.println(e.getMessage());
+        resultCode = ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
+        COMPLETION_LATCH.countDown();
+        return false;
+      }
+      return true;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleErrorResult(final ErrorResultException error)
+    {
+      System.err.println(error.getMessage());
+      resultCode = error.getResult().getResultCode().intValue();
+      COMPLETION_LATCH.countDown();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized boolean handleReference(
+        final SearchResultReference reference)
+    {
+      try
+      {
+        WRITER.writeComment("Search result reference: "
+            + reference.getURIs().toString());
+      }
+      catch (final IOException e)
+      {
+        System.err.println(e.getMessage());
+        resultCode = ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
+        COMPLETION_LATCH.countDown();
+        return false;
+      }
+      return true;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleResult(final Result result)
+    {
+      resultCode = result.getResultCode().intValue();
+      COMPLETION_LATCH.countDown();
+    }
+
+  }
+
+
+
+  private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
+  private static final LDIFEntryWriter WRITER = new LDIFEntryWriter(System.out);
+  private static String userName;
+  private static String password;
+  private static String baseDN;
+
+  private static SearchScope scope;
+
+  private static String filter;
+
+  private static String[] attributes;
+
+  private static AsynchronousConnection connection = null;
+
+  private static int resultCode = 0;
+
+
+
+  /**
+   * Main method.
+   *
+   * @param args
+   *          The command line arguments: host, port, username, password, base
+   *          DN, scope, filter, and zero or more attributes to be retrieved.
+   */
+  public static void main(final String[] args)
+  {
+    if (args.length < 7)
+    {
+      System.err.println("Usage: host port username password baseDN scope "
+          + "filter [attribute ...]");
+      System.exit(1);
+    }
+
+    // Parse command line arguments.
+    final String hostName = args[0];
+    final int port = Integer.parseInt(args[1]);
+    userName = args[2];
+    password = args[3];
+    baseDN = args[4];
+    final String scopeString = args[5];
+    filter = args[6];
+    if (args.length > 7)
+    {
+      attributes = Arrays.copyOfRange(args, 7, args.length);
+    }
+    else
+    {
+      attributes = new String[0];
+    }
+
+    if (scopeString.equalsIgnoreCase("base"))
+    {
+      scope = SearchScope.BASE_OBJECT;
+    }
+    else if (scopeString.equalsIgnoreCase("one"))
+    {
+      scope = SearchScope.SINGLE_LEVEL;
+    }
+    else if (scopeString.equalsIgnoreCase("sub"))
+    {
+      scope = SearchScope.WHOLE_SUBTREE;
+    }
+    else if (scopeString.equalsIgnoreCase("subordinates"))
+    {
+      scope = SearchScope.SUBORDINATES;
+    }
+    else
+    {
+      System.err.println("Unknown scope: " + scopeString);
+      System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
+      return;
+    }
+
+    // Initiate the asynchronous connect, bind, and search.
+    final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName,
+        port);
+    factory.getAsynchronousConnection(new ConnectResultHandlerImpl());
+
+    // Await completion.
+    try
+    {
+      COMPLETION_LATCH.await();
+    }
+    catch (final InterruptedException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
+      return;
+    }
+
+    try
+    {
+      WRITER.flush();
+    }
+    catch (final IOException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
+      return;
+    }
+
+    if (connection != null)
+    {
+      connection.close();
+    }
+
+    System.exit(resultCode);
+  }
+
+
+
+  private Main()
+  {
+    // Not used.
+  }
+}
diff --git a/sdk/examples/org/opends/sdk/examples/client/asyncsearch/package-info.java b/sdk/examples/org/opends/sdk/examples/client/asyncsearch/package-info.java
new file mode 100644
index 0000000..04adebb
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/client/asyncsearch/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+/**
+ * An example client application which searches a Directory Server using the
+ * asynchronous APIs.
+ */
+package org.opends.sdk.examples.client.asyncsearch;
+
+
+
diff --git a/sdk/examples/org/opends/sdk/examples/client/modify/Main.java b/sdk/examples/org/opends/sdk/examples/client/modify/Main.java
new file mode 100644
index 0000000..6968f5e
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/client/modify/Main.java
@@ -0,0 +1,166 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.examples.client.modify;
+
+
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.opends.sdk.Connection;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.LDAPConnectionFactory;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ConnectionChangeRecordWriter;
+import org.opends.sdk.ldif.LDIFChangeRecordReader;
+
+
+
+/**
+ * An example client application which applies update operations to a Directory
+ * Server. The update operations will be read from an LDIF file, or stdin if no
+ * filename is provided. This example takes the following command line
+ * parameters (it will read from stdin if no LDIF file is provided):
+ *
+ * <pre>
+ *  &lt;host> &lt;port> &lt;username> &lt;password> [&lt;ldifFile>]
+ * </pre>
+ */
+public final class Main
+{
+  /**
+   * Main method.
+   *
+   * @param args
+   *          The command line arguments: host, port, username, password, LDIF
+   *          file name containing the update operations (will use stdin if not
+   *          provided).
+   */
+  public static void main(final String[] args)
+  {
+    if (args.length < 4 || args.length > 5)
+    {
+      System.err.println("Usage: host port username password [ldifFileName]");
+      System.exit(1);
+    }
+
+    // Parse command line arguments.
+    final String hostName = args[0];
+    final int port = Integer.parseInt(args[1]);
+    final String userName = args[2];
+    final String password = args[3];
+
+    // Create the LDIF reader which will either used the named file, if
+    // provided, or stdin.
+    InputStream ldif;
+    if (args.length > 4)
+    {
+      try
+      {
+        ldif = new FileInputStream(args[4]);
+      }
+      catch (final FileNotFoundException e)
+      {
+        System.err.println(e.getMessage());
+        System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
+        return;
+      }
+    }
+    else
+    {
+      ldif = System.in;
+    }
+    final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(ldif);
+
+    // Connect and bind to the server.
+    final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName,
+        port);
+    Connection connection = null;
+
+    try
+    {
+      connection = factory.getConnection();
+      connection.bind(userName, password);
+
+      // Write the changes.
+      final ConnectionChangeRecordWriter writer = new ConnectionChangeRecordWriter(
+          connection);
+      while (reader.hasNext())
+      {
+        ChangeRecord changeRecord = reader.readChangeRecord();
+        writer.writeChangeRecord(changeRecord);
+        System.err.println("Successfully modified entry "
+            + changeRecord.getName().toString());
+      }
+    }
+    catch (final ErrorResultException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(e.getResult().getResultCode().intValue());
+      return;
+    }
+    catch (final InterruptedException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
+      return;
+    }
+    catch (final IOException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
+      return;
+    }
+    finally
+    {
+      if (connection != null)
+      {
+        connection.close();
+      }
+
+      try
+      {
+        reader.close();
+      }
+      catch (final IOException ignored)
+      {
+        // Ignore.
+      }
+    }
+  }
+
+
+
+  private Main()
+  {
+    // Not used.
+  }
+}
diff --git a/sdk/examples/org/opends/sdk/examples/client/modify/package-info.java b/sdk/examples/org/opends/sdk/examples/client/modify/package-info.java
new file mode 100644
index 0000000..4a47232
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/client/modify/package-info.java
@@ -0,0 +1,36 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+/**
+ * An example client application which applies update operations to a Directory
+ * Server. The update operations will be read from an LDIF file, or stdin if no
+ * filename is provided.
+ */
+package org.opends.sdk.examples.client.modify;
+
+
+
diff --git a/sdk/examples/org/opends/sdk/examples/client/search/Main.java b/sdk/examples/org/opends/sdk/examples/client/search/Main.java
new file mode 100644
index 0000000..8809bcd
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/client/search/Main.java
@@ -0,0 +1,187 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.examples.client.search;
+
+
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.opends.sdk.*;
+import org.opends.sdk.ldif.ConnectionEntryReader;
+import org.opends.sdk.ldif.LDIFEntryWriter;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.responses.SearchResultReference;
+
+
+
+/**
+ * An example client application which searches a Directory Server. This example
+ * takes the following command line parameters:
+ *
+ * <pre>
+ *  &lt;host> &lt;port> &lt;username> &lt;password>
+ *      &lt;baseDN> &lt;scope> &lt;filter> [&lt;attibute> &lt;attribute> ...]
+ * </pre>
+ */
+public final class Main
+{
+  /**
+   * Main method.
+   *
+   * @param args
+   *          The command line arguments: host, port, username, password, base
+   *          DN, scope, filter, and zero or more attributes to be retrieved.
+   */
+  public static void main(final String[] args)
+  {
+    if (args.length < 7)
+    {
+      System.err.println("Usage: host port username password baseDN scope "
+          + "filter [attribute ...]");
+      System.exit(1);
+    }
+
+    // Parse command line arguments.
+    final String hostName = args[0];
+    final int port = Integer.parseInt(args[1]);
+    final String userName = args[2];
+    final String password = args[3];
+    final String baseDN = args[4];
+    final String scopeString = args[5];
+    final String filter = args[6];
+    String[] attributes;
+    if (args.length > 7)
+    {
+      attributes = Arrays.copyOfRange(args, 7, args.length);
+    }
+    else
+    {
+      attributes = new String[0];
+    }
+
+    SearchScope scope;
+    if (scopeString.equalsIgnoreCase("base"))
+    {
+      scope = SearchScope.BASE_OBJECT;
+    }
+    else if (scopeString.equalsIgnoreCase("one"))
+    {
+      scope = SearchScope.SINGLE_LEVEL;
+    }
+    else if (scopeString.equalsIgnoreCase("sub"))
+    {
+      scope = SearchScope.WHOLE_SUBTREE;
+    }
+    else if (scopeString.equalsIgnoreCase("subordinates"))
+    {
+      scope = SearchScope.SUBORDINATES;
+    }
+    else
+    {
+      System.err.println("Unknown scope: " + scopeString);
+      System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
+      return;
+    }
+
+    // Create an LDIF writer which will write the search results to stdout.
+    final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
+
+    // Connect and bind to the server.
+    final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName,
+        port);
+    Connection connection = null;
+
+    try
+    {
+      connection = factory.getConnection();
+      connection.bind(userName, password);
+
+      // Read the entries and output them as LDIF.
+      final ConnectionEntryReader reader = connection.search(baseDN, scope,
+          filter, attributes);
+      while (reader.hasNext())
+      {
+        if (!reader.isReference())
+        {
+          final SearchResultEntry entry = reader.readEntry();
+          writer.writeComment("Search result entry: "
+              + entry.getName().toString());
+          writer.writeEntry(entry);
+        }
+        else
+        {
+          final SearchResultReference ref = reader.readReference();
+
+          // Got a continuation reference.
+          writer.writeComment("Search result reference: "
+              + ref.getURIs().toString());
+        }
+      }
+      writer.flush();
+    }
+    catch (final ErrorResultException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(e.getResult().getResultCode().intValue());
+      return;
+    }
+    catch (final ErrorResultIOException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(e.getCause().getResult().getResultCode().intValue());
+      return;
+    }
+    catch (final InterruptedException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
+      return;
+    }
+    catch (final IOException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
+      return;
+    }
+    finally
+    {
+      if (connection != null)
+      {
+        connection.close();
+      }
+    }
+  }
+
+
+
+  private Main()
+  {
+    // Not used.
+  }
+}
diff --git a/sdk/examples/org/opends/sdk/examples/client/search/package-info.java b/sdk/examples/org/opends/sdk/examples/client/search/package-info.java
new file mode 100644
index 0000000..587d7bd
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/client/search/package-info.java
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+/**
+ * An example client application which searches a Directory Server.
+ */
+package org.opends.sdk.examples.client.search;
+
+
+
diff --git a/sdk/examples/org/opends/sdk/examples/server/proxy/Main.java b/sdk/examples/org/opends/sdk/examples/server/proxy/Main.java
new file mode 100644
index 0000000..c95fddf
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/server/proxy/Main.java
@@ -0,0 +1,515 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.examples.server.proxy;
+
+
+
+import static org.opends.sdk.ErrorResultException.newErrorResult;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.*;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.CompareResult;
+import org.opends.sdk.responses.ExtendedResult;
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * An LDAP load balancing proxy which forwards requests to one or more remote
+ * Directory Servers. This is implementation is very simple and is only intended
+ * as an example:
+ * <ul>
+ * <li>It does not support SSL connections
+ * <li>It does not support StartTLS
+ * <li>It does not support Abandon or Cancel requests
+ * <li>Very basic authentication and authorization support.
+ * </ul>
+ * This example takes the following command line parameters:
+ *
+ * <pre>
+ *  &lt;listenAddress> &lt;listenPort> &lt;remoteAddress1> &lt;remotePort1>
+ *      [&lt;remoteAddress2> &lt;remotePort2> ...]
+ * </pre>
+ */
+public final class Main
+{
+  /**
+   * Proxy server connection factory implementation.
+   */
+  private static final class Proxy implements
+      ServerConnectionFactory<LDAPClientContext, Integer>
+  {
+    private final class ServerConnectionImpl implements
+        ServerConnection<Integer>
+    {
+
+      private volatile AsynchronousConnection connection = null;
+
+      private volatile boolean isUnbindRequired = false;
+
+
+
+      private AsynchronousConnection getConnection()
+          throws ErrorResultException
+      {
+        if (connection == null)
+        {
+          synchronized (this)
+          {
+            if (connection == null)
+            {
+              try
+              {
+                connection = factory.getAsynchronousConnection(null).get();
+              }
+              catch (InterruptedException e)
+              {
+                throw new RuntimeException(e);
+              }
+            }
+          }
+        }
+        return connection;
+      }
+
+
+
+      private ServerConnectionImpl(final LDAPClientContext clientContext)
+      {
+        // Nothing to do.
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleAbandon(final Integer requestContext,
+          final AbandonRequest request) throws UnsupportedOperationException
+      {
+        // Not implemented.
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleAdd(final Integer requestContext,
+          final AddRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        try
+        {
+          getConnection().add(request, resultHandler,
+              intermediateResponseHandler);
+        }
+        catch (ErrorResultException e)
+        {
+          resultHandler.handleErrorResult(e);
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleBind(final Integer requestContext, final int version,
+          final BindRequest request,
+          final ResultHandler<? super BindResult> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        if (request.getAuthenticationType() != ((byte) 0x80))
+        {
+          // TODO: SASL authentication not implemented.
+          resultHandler.handleErrorResult(newErrorResult(
+              ResultCode.PROTOCOL_ERROR,
+              "non-SIMPLE authentication not supported: "
+                  + request.getAuthenticationType()));
+        }
+        else
+        {
+          // Note that this connection has received a bind request: the
+          // connection should be reverted back to anonymous when the client
+          // unbinds.
+          isUnbindRequired = true;
+
+          try
+          {
+            getConnection().bind(request, resultHandler,
+                intermediateResponseHandler);
+          }
+          catch (ErrorResultException e)
+          {
+            resultHandler.handleErrorResult(e);
+          }
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleCompare(final Integer requestContext,
+          final CompareRequest request,
+          final ResultHandler<? super CompareResult> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        try
+        {
+          getConnection().compare(request, resultHandler,
+              intermediateResponseHandler);
+        }
+        catch (ErrorResultException e)
+        {
+          resultHandler.handleErrorResult(e);
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleConnectionClosed(final Integer requestContext,
+          final UnbindRequest request)
+      {
+        // Client connection closed: release the proxy connection.
+        close();
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleConnectionDisconnected(final ResultCode resultCode,
+          final String message)
+      {
+        // Client disconnected by server: release the proxy connection.
+        close();
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleConnectionError(final Throwable error)
+      {
+        // Client connection failed: release the proxy connection.
+        close();
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleDelete(final Integer requestContext,
+          final DeleteRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        try
+        {
+          getConnection().delete(request, resultHandler,
+              intermediateResponseHandler);
+        }
+        catch (ErrorResultException e)
+        {
+          resultHandler.handleErrorResult(e);
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public <R extends ExtendedResult> void handleExtendedRequest(
+          final Integer requestContext, final ExtendedRequest<R> request,
+          final ResultHandler<? super R> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        if (request.getOID().equals(CancelExtendedRequest.OID))
+        {
+          // TODO: not implemented.
+          resultHandler.handleErrorResult(newErrorResult(
+              ResultCode.PROTOCOL_ERROR,
+              "Cancel extended request operation not supported"));
+        }
+        else if (request.getOID().equals(StartTLSExtendedRequest.OID))
+        {
+          // TODO: not implemented.
+          resultHandler.handleErrorResult(newErrorResult(
+              ResultCode.PROTOCOL_ERROR,
+              "StartTLS extended request operation not supported"));
+        }
+        else
+        {
+          // Forward all other extended operations.
+          try
+          {
+            getConnection().extendedRequest(request, resultHandler,
+                intermediateResponseHandler);
+          }
+          catch (ErrorResultException e)
+          {
+            resultHandler.handleErrorResult(e);
+          }
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleModify(final Integer requestContext,
+          final ModifyRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        try
+        {
+          getConnection().modify(request, resultHandler,
+              intermediateResponseHandler);
+        }
+        catch (ErrorResultException e)
+        {
+          resultHandler.handleErrorResult(e);
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleModifyDN(final Integer requestContext,
+          final ModifyDNRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        try
+        {
+          getConnection().modifyDN(request, resultHandler,
+              intermediateResponseHandler);
+        }
+        catch (ErrorResultException e)
+        {
+          resultHandler.handleErrorResult(e);
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleSearch(final Integer requestContext,
+          final SearchRequest request, final SearchResultHandler resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        try
+        {
+          getConnection().search(request, resultHandler,
+              intermediateResponseHandler);
+        }
+        catch (ErrorResultException e)
+        {
+          resultHandler.handleErrorResult(e);
+        }
+      }
+
+
+
+      private void close()
+      {
+        if (isUnbindRequired)
+        {
+          synchronized (this)
+          {
+            if (connection != null)
+            {
+              connection.bind(Requests.newSimpleBindRequest(),
+                  new ResultHandler<Result>()
+                  {
+
+                    public void handleErrorResult(ErrorResultException error)
+                    {
+                      // The rebind failed - this is bad because if the
+                      // connection is pooled it will remain authenticated as
+                      // the wrong user.
+                      handleResult(error.getResult());
+                    }
+
+
+
+                    public void handleResult(Result result)
+                    {
+                      synchronized (ServerConnectionImpl.this)
+                      {
+                        if (connection != null)
+                        {
+                          connection.close();
+                        }
+                      }
+                    }
+                  });
+            }
+          }
+        }
+      }
+
+    }
+
+
+
+    private final ConnectionFactory factory;
+
+
+
+    private Proxy(final ConnectionFactory factory)
+    {
+      this.factory = factory;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ServerConnection<Integer> handleAccept(
+        final LDAPClientContext clientContext) throws ErrorResultException
+    {
+      return new ServerConnectionImpl(clientContext);
+    }
+
+  }
+
+
+
+  /**
+   * Main method.
+   *
+   * @param args
+   *          The command line arguments: listen address, listen port, remote
+   *          address1, remote port1, remote address2, remote port2, ...
+   */
+  public static void main(final String[] args)
+  {
+    if (args.length < 4 || args.length % 2 != 0)
+    {
+      System.err.println("Usage: listenAddress listenPort "
+          + "remoteAddress1 remotePort1 remoteAddress2 remotePort2");
+      System.exit(1);
+    }
+
+    // Parse command line arguments.
+    final String localAddress = args[0];
+    final int localPort = Integer.parseInt(args[1]);
+
+    // Create load balancer.
+    final List<ConnectionFactory> factories = new LinkedList<ConnectionFactory>();
+    for (int i = 2; i < args.length; i += 2)
+    {
+      final String remoteAddress = args[i];
+      final int remotePort = Integer.parseInt(args[i + 1]);
+
+      // factories.add(Connections.newConnectionPool(new LDAPConnectionFactory(
+      //    remoteAddress, remotePort), Integer.MAX_VALUE));
+      factories.add(new LDAPConnectionFactory(remoteAddress, remotePort));
+    }
+    final RoundRobinLoadBalancingAlgorithm algorithm = new RoundRobinLoadBalancingAlgorithm(
+        factories);
+    final ConnectionFactory factory = Connections.newLoadBalancer(algorithm);
+
+    // Create listener.
+    final LDAPListenerOptions options = new LDAPListenerOptions()
+        .setBacklog(4096);
+    LDAPListener listener = null;
+    try
+    {
+      listener = new LDAPListener(localAddress, localPort, new Proxy(factory),
+          options);
+      System.out.println("Press any key to stop the server...");
+      System.in.read();
+    }
+    catch (final IOException e)
+    {
+      System.out
+          .println("Error listening on " + localAddress + ":" + localPort);
+      e.printStackTrace();
+    }
+    finally
+    {
+      if (listener != null)
+      {
+        listener.close();
+      }
+    }
+  }
+
+
+
+  private Main()
+  {
+    // Not used.
+  }
+}
diff --git a/sdk/examples/org/opends/sdk/examples/server/proxy/package-info.java b/sdk/examples/org/opends/sdk/examples/server/proxy/package-info.java
new file mode 100644
index 0000000..e940d72
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/server/proxy/package-info.java
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+/**
+ * An example LDAP proxy which forwards requests to a remote Directory Server.
+ */
+package org.opends.sdk.examples.server.proxy;
+
+
+
diff --git a/sdk/examples/org/opends/sdk/examples/server/store/Main.java b/sdk/examples/org/opends/sdk/examples/server/store/Main.java
new file mode 100644
index 0000000..cae70d8
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/server/store/Main.java
@@ -0,0 +1,589 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.examples.server.store;
+
+
+
+import static org.opends.sdk.ErrorResultException.newErrorResult;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.NavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.opends.sdk.*;
+import org.opends.sdk.ldif.LDIFEntryReader;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+
+
+
+/**
+ * An LDAP directory server which exposes data contained in an LDIF file. This
+ * is implementation is very simple and is only intended as an example:
+ * <ul>
+ * <li>It does not support SSL connections
+ * <li>It does not support StartTLS
+ * <li>It does not support Abandon or Cancel requests
+ * <li>Very basic authentication and authorization support.
+ * </ul>
+ * This example takes the following command line parameters:
+ *
+ * <pre>
+ *  &lt;listenAddress> &lt;listenPort> [&lt;ldifFile>]
+ * </pre>
+ */
+public final class Main
+{
+  /**
+   * Proxy server connection factory implementation.
+   */
+  private static final class Store implements
+      ServerConnectionFactory<LDAPClientContext, Integer>
+  {
+    private final class ServerConnectionImpl implements
+        ServerConnection<Integer>
+    {
+
+      private ServerConnectionImpl(final LDAPClientContext clientContext)
+      {
+        // Nothing to do.
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleAbandon(final Integer requestContext,
+          final AbandonRequest request) throws UnsupportedOperationException
+      {
+        // Not implemented.
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleAdd(final Integer requestContext,
+          final AddRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        // TODO: controls.
+        entryLock.writeLock().lock();
+        try
+        {
+          DN dn = request.getName();
+          if (entries.containsKey(dn))
+          {
+            resultHandler.handleErrorResult(ErrorResultException
+                .newErrorResult(ResultCode.ENTRY_ALREADY_EXISTS, "The entry "
+                    + dn.toString() + " already exists"));
+          }
+
+          DN parent = dn.parent();
+          if (!entries.containsKey(parent))
+          {
+            resultHandler.handleErrorResult(ErrorResultException
+                .newErrorResult(ResultCode.NO_SUCH_OBJECT, "The parent entry "
+                    + parent.toString() + " does not exist"));
+          }
+          else
+          {
+            entries.put(dn, request);
+            resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+          }
+        }
+        finally
+        {
+          entryLock.writeLock().unlock();
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleBind(final Integer requestContext, final int version,
+          final BindRequest request,
+          final ResultHandler<? super BindResult> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        if (request.getAuthenticationType() != ((byte) 0x80))
+        {
+          // TODO: SASL authentication not implemented.
+          resultHandler.handleErrorResult(newErrorResult(
+              ResultCode.PROTOCOL_ERROR,
+              "non-SIMPLE authentication not supported: "
+                  + request.getAuthenticationType()));
+        }
+        else
+        {
+          // TODO: always succeed.
+          resultHandler.handleResult(Responses
+              .newBindResult(ResultCode.SUCCESS));
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleCompare(final Integer requestContext,
+          final CompareRequest request,
+          final ResultHandler<? super CompareResult> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        // TODO:
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleConnectionClosed(final Integer requestContext,
+          final UnbindRequest request)
+      {
+        // Nothing to do.
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleConnectionDisconnected(final ResultCode resultCode,
+          final String message)
+      {
+        // Nothing to do.
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleConnectionError(final Throwable error)
+      {
+        // Nothing to do.
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleDelete(final Integer requestContext,
+          final DeleteRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        // TODO: controls.
+        entryLock.writeLock().lock();
+        try
+        {
+          // TODO: check for children.
+          DN dn = request.getName();
+          if (!entries.containsKey(dn))
+          {
+            resultHandler.handleErrorResult(ErrorResultException
+                .newErrorResult(ResultCode.NO_SUCH_OBJECT,
+                    "The entry " + dn.toString() + " does not exist"));
+          }
+          else
+          {
+            entries.remove(dn);
+            resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+          }
+        }
+        finally
+        {
+          entryLock.writeLock().unlock();
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public <R extends ExtendedResult> void handleExtendedRequest(
+          final Integer requestContext, final ExtendedRequest<R> request,
+          final ResultHandler<? super R> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        // TODO: not implemented.
+        resultHandler.handleErrorResult(newErrorResult(
+            ResultCode.PROTOCOL_ERROR,
+            "Extended request operation not supported"));
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleModify(final Integer requestContext,
+          final ModifyRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        // TODO: controls.
+        // TODO: read lock is not really enough since concurrent updates may
+        // still occur to the same entry.
+        entryLock.readLock().lock();
+        try
+        {
+          DN dn = request.getName();
+          Entry entry = entries.get(dn);
+          if (entry == null)
+          {
+            resultHandler.handleErrorResult(ErrorResultException
+                .newErrorResult(ResultCode.NO_SUCH_OBJECT,
+                    "The entry " + dn.toString() + " does not exist"));
+          }
+
+          Entry newEntry = new LinkedHashMapEntry(entry);
+          for (Modification mod : request.getModifications())
+          {
+            ModificationType modType = mod.getModificationType();
+            if (modType.equals(ModificationType.ADD))
+            {
+              // TODO: Reject empty attribute and duplicate values.
+              newEntry.addAttribute(mod.getAttribute(), null);
+            }
+            else if (modType.equals(ModificationType.DELETE))
+            {
+              // TODO: Reject missing values.
+              newEntry.removeAttribute(mod.getAttribute(), null);
+            }
+            else if (modType.equals(ModificationType.REPLACE))
+            {
+              newEntry.replaceAttribute(mod.getAttribute());
+            }
+            else
+            {
+              resultHandler.handleErrorResult(newErrorResult(
+                  ResultCode.PROTOCOL_ERROR,
+                  "Modify request contains an unsupported modification type"));
+              return;
+            }
+          }
+
+          entries.put(dn, newEntry);
+          resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+        }
+        finally
+        {
+          entryLock.readLock().unlock();
+        }
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleModifyDN(final Integer requestContext,
+          final ModifyDNRequest request,
+          final ResultHandler<? super Result> resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        // TODO: not implemented.
+        resultHandler.handleErrorResult(newErrorResult(
+            ResultCode.PROTOCOL_ERROR,
+            "ModifyDN request operation not supported"));
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void handleSearch(final Integer requestContext,
+          final SearchRequest request, final SearchResultHandler resultHandler,
+          final IntermediateResponseHandler intermediateResponseHandler)
+          throws UnsupportedOperationException
+      {
+        // TODO: controls, limits, etc.
+        entryLock.readLock().lock();
+        try
+        {
+          DN dn = request.getName();
+          Entry baseEntry = entries.get(dn);
+          if (baseEntry == null)
+          {
+            resultHandler.handleErrorResult(ErrorResultException
+                .newErrorResult(ResultCode.NO_SUCH_OBJECT,
+                    "The entry " + dn.toString() + " does not exist"));
+          }
+
+          SearchScope scope = request.getScope();
+          if (scope.equals(SearchScope.BASE_OBJECT))
+          {
+            sendEntry(request, resultHandler, baseEntry);
+          }
+          else if (scope.equals(SearchScope.SINGLE_LEVEL))
+          {
+            sendEntry(request, resultHandler, baseEntry);
+
+            NavigableMap<DN, Entry> subtree = entries.tailMap(dn, false);
+            for (Entry entry : subtree.values())
+            {
+              DN childDN = entry.getName();
+              if (childDN.isChildOf(dn))
+              {
+                if (!sendEntry(request, resultHandler, entry))
+                {
+                  // Caller has asked to stop sending results.
+                  break;
+                }
+              }
+              else if (!childDN.isSubordinateOrEqualTo(dn))
+              {
+                // The remaining entries will be out of scope.
+                break;
+              }
+            }
+          }
+          else if (scope.equals(SearchScope.WHOLE_SUBTREE))
+          {
+            NavigableMap<DN, Entry> subtree = entries.tailMap(dn);
+            for (Entry entry : subtree.values())
+            {
+              DN childDN = entry.getName();
+              if (childDN.isSubordinateOrEqualTo(dn))
+              {
+                if (!sendEntry(request, resultHandler, entry))
+                {
+                  // Caller has asked to stop sending results.
+                  break;
+                }
+              }
+              else
+              {
+                // The remaining entries will be out of scope.
+                break;
+              }
+            }
+          }
+          else
+          {
+            resultHandler.handleErrorResult(newErrorResult(
+                ResultCode.PROTOCOL_ERROR,
+                "Search request contains an unsupported search scope"));
+            return;
+          }
+
+          resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+        }
+        finally
+        {
+          entryLock.readLock().unlock();
+        }
+      }
+
+
+
+      private boolean sendEntry(SearchRequest request,
+          SearchResultHandler resultHandler, Entry entry)
+      {
+        // TODO: check filter, strip attributes.
+        return resultHandler.handleEntry(Responses.newSearchResultEntry(entry));
+      }
+    }
+
+
+
+    private final ConcurrentSkipListMap<DN, Entry> entries;
+
+    private final ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
+
+
+
+    private Store(final ConcurrentSkipListMap<DN, Entry> entries)
+    {
+      this.entries = entries;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ServerConnection<Integer> handleAccept(
+        final LDAPClientContext clientContext) throws ErrorResultException
+    {
+      return new ServerConnectionImpl(clientContext);
+    }
+
+  }
+
+
+
+  /**
+   * Main method.
+   *
+   * @param args
+   *          The command line arguments: listen address, listen port, ldifFile
+   */
+  public static void main(final String[] args)
+  {
+    if (args.length != 3)
+    {
+      System.err.println("Usage: listenAddress listenPort ldifFile");
+      System.exit(1);
+    }
+
+    // Parse command line arguments.
+    final String localAddress = args[0];
+    final int localPort = Integer.parseInt(args[1]);
+
+    // Read the LDIF.
+    InputStream ldif;
+    try
+    {
+      ldif = new FileInputStream(args[2]);
+    }
+    catch (final FileNotFoundException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
+      return;
+    }
+
+    final LDIFEntryReader reader = new LDIFEntryReader(ldif);
+    ConcurrentSkipListMap<DN, Entry> entries = new ConcurrentSkipListMap<DN, Entry>();
+    try
+    {
+      while (reader.hasNext())
+      {
+        Entry entry = reader.readEntry();
+        entries.put(entry.getName(), entry);
+      }
+    }
+    catch (final IOException e)
+    {
+      System.err.println(e.getMessage());
+      System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
+      return;
+    }
+    finally
+    {
+      try
+      {
+        reader.close();
+      }
+      catch (final IOException ignored)
+      {
+        // Ignore.
+      }
+    }
+
+    // Quickly sanity check that every entry (except root entries) have a
+    // parent.
+    boolean isValid = true;
+    for (DN dn : entries.keySet())
+    {
+      if (dn.size() > 1)
+      {
+        DN parent = dn.parent();
+        if (!entries.containsKey(parent))
+        {
+          System.err.println("The entry \"" + dn.toString()
+              + "\" does not have a parent");
+          isValid = false;
+        }
+      }
+    }
+    if (!isValid)
+    {
+      System.exit(1);
+    }
+
+    // Create listener.
+    final LDAPListenerOptions options = new LDAPListenerOptions()
+        .setBacklog(4096);
+    LDAPListener listener = null;
+    try
+    {
+      listener = new LDAPListener(localAddress, localPort, new Store(entries),
+          options);
+      System.out.println("Press any key to stop the server...");
+      System.in.read();
+    }
+    catch (final IOException e)
+    {
+      System.out
+          .println("Error listening on " + localAddress + ":" + localPort);
+      e.printStackTrace();
+    }
+    finally
+    {
+      if (listener != null)
+      {
+        listener.close();
+      }
+    }
+  }
+
+
+
+  private Main()
+  {
+    // Not used.
+  }
+}
diff --git a/sdk/examples/org/opends/sdk/examples/server/store/package-info.java b/sdk/examples/org/opends/sdk/examples/server/store/package-info.java
new file mode 100644
index 0000000..e4df616
--- /dev/null
+++ b/sdk/examples/org/opends/sdk/examples/server/store/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+/**
+ * An example LDAP data store which exposes data which is contained within an
+ * LDIF file.
+ */
+package org.opends.sdk.examples.server.store;
+
+
+
diff --git a/sdk/lib/gmbal-api-only.jar b/sdk/lib/gmbal-api-only.jar
new file mode 100644
index 0000000..a8f9750
--- /dev/null
+++ b/sdk/lib/gmbal-api-only.jar
Binary files differ
diff --git a/sdk/lib/grizzly.jar b/sdk/lib/grizzly.jar
index 137fb72..ab8e441 100644
--- a/sdk/lib/grizzly.jar
+++ b/sdk/lib/grizzly.jar
Binary files differ
diff --git a/sdk/nbproject/genfiles.properties b/sdk/nbproject/genfiles.properties
new file mode 100644
index 0000000..3c5d60b
--- /dev/null
+++ b/sdk/nbproject/genfiles.properties
@@ -0,0 +1,5 @@
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/jdk.xml.data.CRC32=f285a38e
+nbproject/jdk.xml.script.CRC32=5342cb35
+nbproject/jdk.xml.stylesheet.CRC32=c45af3dc
diff --git a/sdk/nbproject/ide-file-targets.xml b/sdk/nbproject/ide-file-targets.xml
new file mode 100644
index 0000000..7ba9c68
--- /dev/null
+++ b/sdk/nbproject/ide-file-targets.xml
@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project basedir=".." name="Directory Server-IDE">
+    <!-- Import build.xml properties and targets -->
+    <import file="../build.xml"/>
+    <path id="opends.path">
+        <pathelement path="build/classes:build/build-tools/classes:lib/activation.jar:lib/aspectjrt.jar:lib/je.jar:lib/mail.jar:build/quicksetup/classes:build/build-tools/build-tools.jar:build/unit-tests/classes:ext/testng/lib/testng-5.7b-jdk15.jar:ext/ant/lib/ant.jar:ext/svnkit/svnkit.jar:ext/emma/lib/emma.jar:build/dsml/classes:resource/dsml/lib/jaxb-api.jar:resource/dsml/lib/jaxb-impl.jar:resource/dsml/lib/jsr173_1.0_api.jar:resource/dsml/lib/saaj-1.3.jar:resource/dsml/lib/saaj-impl-1.3.jar:resource/dsml/lib/j2ee.jar"/>
+        <pathelement location="build/build-tools/classes"/>
+        <pathelement location="build/classes"/>
+        <pathelement location="build/quicksetup/classes"/>
+        <pathelement location="build/unit-tests/classes"/>
+        <pathelement location="build/dsml/classes"/>
+        <fileset dir="${opendmk.lib.dir}">
+            <include name="*.jar"/>
+        </fileset>
+    </path>
+    <!-- Prepare testng unit tests environment -->
+    <taskdef resource="testngtasks">
+        <classpath>
+            <fileset dir="${testng.lib.dir}">
+                <include name="*.jar"/>
+            </fileset>
+        </classpath>
+    </taskdef>
+    <target name="prepare-test">
+        <delete failonerror="false">
+            <fileset dir="${unittest.report.dir}" includes="*"/>
+        </delete>
+        <mkdir dir="${unittest.report.dir}"/>
+    </target>
+    <!-- -->
+    <!-- Run a selected testng file -->
+    <!-- -->
+    <target depends="prepare-test" name="run-selected-testng-file">
+        <fail unless="run.class">Must set property 'run.class'</fail>
+        <echo message="Running test (normal): ${run.class}"/>
+        <testng dumpCommand="true" enableAssert="false" haltonfailure="false" listeners="org.opends.server.TestListener org.testng.reporters.FailedReporter" outputdir="${unittest.report.dir}" suiteRunnerClass="org.opends.server.SuiteRunner" useDefaultListeners="false" verbose="0">
+            <classpath refid="opends.path"/>
+            <jvmarg value="-Dorg.opends.server.BuildRoot=${basedir}"/>
+            <jvmarg value="-Dorg.opends.server.RunningUnitTests=true"/>
+            <jvmarg value="-Dorg.opends.test.suppressOutput=false"/>
+            <jvmarg value="-Dorg.opends.test.pauseOnFailure=false"/>
+            <jvmarg value="-Dorg.opends.test.debug.target=false"/>
+            <jvmarg value="-Dorg.opends.server.snmp.opendmk=${opendmk.lib.dir}"/>
+            <jvmarg value="-Dorg.opends.test.copyClassesToTestPackage=true"/>
+            <jvmarg value="-Dtest.progress=all"/>
+            <jvmarg value="-Xms192M"/>
+            <jvmarg value="-Xmx192M"/>
+            <classfileset file="${unittest.classes.dir}/${run.class}.class"/>
+        </testng>
+    </target>
+    <!-- -->
+    <!-- Debug a selected file in testng unit tests sources -->
+    <!-- -->
+    <target name="debug-selected-testng-file">
+        <fail unless="debug.class">Must set property 'debug.class'</fail>
+        <echo message="Debugging test (normal): ${debug.class}"/>
+        <ant antfile="build.xml" inheritall="false" target="dynamicconstants"/>
+        <nbjpdastart addressproperty="jpda.address" name="Directory Server" transport="dt_socket">
+            <classpath refid="opends.path"/>
+        </nbjpdastart>
+        <testng enableAssert="false" haltonfailure="false" listeners="org.opends.server.TestListener org.testng.reporters.FailedReporter" outputdir="${unittest.report.dir}" suiteRunnerClass="org.opends.server.SuiteRunner" useDefaultListeners="false" verbose="5">
+            <classpath refid="opends.path"/>
+            <jvmarg value="-Dorg.opends.server.LdapPort=1389"/>
+            <jvmarg value="-Dorg.opends.server.BuildRoot=${basedir}"/>
+            <jvmarg value="-Dorg.opends.server.RunningUnitTests=true"/>
+            <jvmarg value="-Dorg.opends.test.suppressOutput=false"/>
+            <jvmarg value="-Dorg.opends.test.pauseOnFailure=false"/>
+            <jvmarg value="-Dorg.opends.test.debug.target=false"/>
+            <jvmarg value="-Dorg.opends.server.snmp.opendmk=${opendmk.lib.dir}"/>
+            <jvmarg value="-Dorg.opends.test.copyClassesToTestPackage=true"/>
+            <jvmarg value="-Dtest.progress=all"/>
+            <jvmarg value="-Xms192M"/>
+            <jvmarg value="-Xmx192M"/>
+            <jvmarg value="-Xdebug"/>
+            <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
+            <classfileset file="${unittest.classes.dir}/${debug.class}.class"/>
+        </testng>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/server folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-server">
+        <fail unless="files">Must set property 'files'</fail>
+        <echo message="Compiling source (normal): ${files}"/>
+        <!-- TODO decide on and define some value for ${build.classes.dir} -->
+        <mkdir dir="${classes.dir}"/>
+        <javac destdir="${classes.dir}" includes="${files}" source="1.5" srcdir="src/server">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in testng unit tests source folder -->
+    <!-- -->
+    <target name="compile-selected-testng-file">
+        <!-- Compile the test cases -->
+        <echo message="Compiling test (normal): ${files}"/>
+        <mkdir dir="${unittest.classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" deprecation="true" destdir="${unittest.classes.dir}" fork="true" includes="${files}" memoryInitialSize="${MEM}" memoryMaximumSize="${MEM}" source="1.5" srcdir="${unittest.testng.src.dir}" target="1.5">
+            <compilerarg value="-Xlint:all"/>
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Run a selected class in src/server folder -->
+    <!-- -->
+    <target depends="dynamicconstants" name="run-selected-file-in-server">
+        <fail unless="run.class">Must set property 'run.class'</fail>
+        <property location="${package.dir}/${SHORT_NAME}-${VERSION_NUMBER_STRING}" name="pdir"/>
+        <ant antfile="build.xml" inheritall="false" target="dynamicconstants"/>
+        <java classname="${run.class}" failonerror="true" fork="true">
+            <classpath refid="opends.path"/>
+            <jvmarg value="-Dorg.opends.server.BuildRoot=${pdir}"/>
+            <jvmarg value="-Dorg.opends.server.scriptName=start-ds"/>
+            <jvmarg value="-Dorg.opends.server.ServerRoot=${pdir}"/>
+            <arg value="--configClass=org.opends.server.extensions.ConfigFileHandler"/>
+            <arg value="--configFile=${pdir}/config/config.ldif"/>
+            <arg value="--nodetach"/>
+        </java>
+    </target>
+    <!-- -->
+    <!-- Debug a selected file in src/server folder -->
+    <!-- -->
+    <target depends="dynamicconstants" name="debug-selected-file-in-server">
+        <fail unless="debug.class">Must set property 'debug.class'</fail>
+        <property location="${package.dir}/${SHORT_NAME}-${VERSION_NUMBER_STRING}" name="pdir"/>
+        <ant antfile="build.xml" inheritall="false" target="dynamicconstants"/>
+        <nbjpdastart addressproperty="jpda.address" name="Directory Server" transport="dt_socket">
+            <classpath refid="opends.path"/>
+        </nbjpdastart>
+        <java classname="${debug.class}" fork="true">
+            <classpath refid="cp"/>
+            <jvmarg value="-Xdebug"/>
+            <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
+            <jvmarg value="-Dorg.opends.server.BuildRoot=${pdir}"/>
+            <jvmarg value="-Dorg.opends.server.scriptName=start-ds"/>
+            <jvmarg value="-Dorg.opends.server.ServerRoot=${pdir}"/>
+            <jvmarg value="-Dorg.opends.server.debug.enabled=true"/>
+            <arg value="--configClass=org.opends.server.extensions.ConfigFileHandler"/>
+            <arg value="--configFile=${pdir}/config/config.ldif"/>
+            <arg value="--nodetach"/>
+        </java>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/ads folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-ads">
+        <fail unless="files">Must set property 'files'</fail>
+        <echo message="Compiling source (normal): ${files}"/>
+        <mkdir dir="${classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" destdir="${classes.dir}" includes="${files}" source="1.5" srcdir="src/ads">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/build-tools folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-build-tools">
+        <fail unless="files">Must set property 'files'</fail>
+        <echo message="Compiling source (normal): ${files}"/>
+        <mkdir dir="${buildtools.classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" destdir="${buildtools.classes.dir}" includes="${files}" source="1.5" srcdir="src/build-tools">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/guitools folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-guitools">
+        <fail unless="files">Must set property 'files'</fail>
+        <echo message="Compiling source (normal): ${files}"/>
+        <mkdir dir="${classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" destdir="${classes.dir}" includes="${files}" source="1.5" srcdir="src/guitools">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/messages/src folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-messages-src">
+        <fail unless="files">Must set property 'files'</fail>
+        <echo message="Compiling source (normal): ${files}"/>
+        <mkdir dir="${classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" destdir="${classes.dir}" includes="${files}" source="1.5" srcdir="src/messages/src">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/quicksetup folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-quicksetup">
+        <fail unless="files">Must set property 'files'</fail>
+        <mkdir dir="${quicksetup.classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" destdir="${quicksetup.classes.dir}" includes="${files}" source="1.5" srcdir="src/quicksetup">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/dsml folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-dsml">
+        <fail unless="files">Must set property 'files'</fail>
+        <echo message="Compiling source (normal): ${files}"/>
+        <mkdir dir="${dsml.classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" destdir="${dsml.classes.dir}" includes="${files}" source="1.5" srcdir="src/dsml">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+    <!-- -->
+    <!-- Compile a selected file in src/snmp/src folder -->
+    <!-- -->
+    <target name="compile-selected-files-in-snmp">
+        <fail unless="files">Must set property 'files'</fail>
+        <mkdir dir="${classes.dir}"/>
+        <javac debug="on" debuglevel="${build.debuglevel}" destdir="${classes.dir}" includes="${files}" source="1.5" srcdir="src/snmp/src">
+            <classpath refid="opends.path"/>
+        </javac>
+    </target>
+</project>
diff --git a/sdk/nbproject/ide-targets.xml b/sdk/nbproject/ide-targets.xml
new file mode 100644
index 0000000..b733c70
--- /dev/null
+++ b/sdk/nbproject/ide-targets.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project basedir=".." name="Directory Server-IDE">
+    
+    <!-- Import build.xml definitions -->
+    <import file="../build.xml"/>
+    
+    <!-- OpenDS path -->
+    <path id="opends.path">
+        <pathelement path="build/classes:build/build-tools/classes:lib/activation.jar:lib/aspectjrt.jar:lib/je.jar:lib/mail.jar:build/quicksetup/classes:build/build-tools/build-tools.jar:build/unit-tests/classes:ext/testng/lib/testng-5.7b-jdk15.jar:ext/ant/lib/ant.jar:ext/svnkit/svnkit.jar:ext/emma/lib/emma.jar:build/dsml/classes:resource/dsml/lib/jaxb-api.jar:resource/dsml/lib/jaxb-impl.jar:resource/dsml/lib/jsr173_1.0_api.jar:resource/dsml/lib/saaj-1.3.jar:resource/dsml/lib/saaj-impl-1.3.jar:resource/dsml/lib/j2ee.jar"/>
+        <pathelement location="build/build-tools/classes"/>
+        <pathelement location="build/classes"/>
+        <pathelement location="build/quicksetup/classes"/>
+        <pathelement location="build/unit-tests/classes"/>
+        <pathelement location="build/dsml/classes"/>
+    </path>
+    
+    <!-- -->
+    <!-- Debug target call by NetBeans IDE -->
+    <!-- -->
+    <target depends="dynamicconstants" name="debug-nb">
+        <!-- Set properties needed to find the packaged files -->
+        <property location="${package.dir}/${SHORT_NAME}-${VERSION_NUMBER_STRING}" name="pdir"/>
+        <nbjpdastart addressproperty="jpda.address" name="Directory Server" transport="dt_socket">
+            <classpath refid="opends.path"/>
+        </nbjpdastart>
+        <java classname="org.opends.server.core.DirectoryServer" failonerror="true" fork="true">
+            <classpath refid="opends.path"/>
+            <jvmarg value="-Dorg.opends.server.BuildRoot=${pdir}"/>
+            <jvmarg value="-Dorg.opends.server.scriptName=start-ds"/>
+            <jvmarg value="-Dorg.opends.server.ServerRoot=${pdir}"/>
+            <arg value="--configClass=org.opends.server.extensions.ConfigFileHandler"/>
+            <arg value="--configFile=${pdir}/config/config.ldif"/>
+            <arg value="--nodetach"/>
+            <jvmarg value="-Xdebug"/>
+            <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
+        </java>
+    </target>
+</project>
diff --git a/sdk/nbproject/project.xml b/sdk/nbproject/project.xml
new file mode 100644
index 0000000..9f65884
--- /dev/null
+++ b/sdk/nbproject/project.xml
@@ -0,0 +1,283 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.ant.freeform</type>
+    <configuration>
+        <general-data xmlns="http://www.netbeans.org/ns/freeform-project/1">
+            <name>OpenDJ SDK</name>
+        </general-data>
+        <general-data xmlns="http://www.netbeans.org/ns/freeform-project/2">
+            <!-- Do not use Project Properties customizer when editing this file manually. -->
+            <name>OpenDJ SDK</name>
+            <properties/>
+            <folders>
+                <source-folder>
+                    <label>OpenDJ SDK</label>
+                    <location>.</location>
+                    <encoding>UTF-8</encoding>
+                </source-folder>
+                <source-folder>
+                    <label>src</label>
+                    <type>java</type>
+                    <location>src</location>
+                    <encoding>UTF-8</encoding>
+                </source-folder>
+                <source-folder>
+                    <label>tests/unit-tests-testng/src</label>
+                    <type>java</type>
+                    <location>tests/unit-tests-testng/src</location>
+                    <encoding>UTF-8</encoding>
+                </source-folder>
+            </folders>
+            <ide-actions>
+                <action name="build">
+                    <script>build.xml</script>
+                    <target>package</target>
+                </action>
+                <action name="clean">
+                    <script>build.xml</script>
+                    <target>clean</target>
+                </action>
+                <action name="javadoc">
+                    <script>build.xml</script>
+                    <target>javadoc</target>
+                </action>
+                <action name="run">
+                    <script>build.xml</script>
+                    <target>run-server</target>
+                </action>
+                <action name="test">
+                    <script>build.xml</script>
+                    <target>test</target>
+                </action>
+                <action name="rebuild">
+                    <script>build.xml</script>
+                    <target>clean</target>
+                    <target>package</target>
+                </action>
+                <action name="debug">
+                    <script>nbproject/ide-targets.xml</script>
+                    <target>debug-nb</target>
+                </action>
+                <action name="run.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>run-selected-testng-file</target>
+                    <context>
+                        <property>run.class</property>
+                        <folder>tests/unit-tests-testng/src/server</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path-noext</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
+                <action name="debug.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>debug-selected-testng-file</target>
+                    <context>
+                        <property>debug.class</property>
+                        <folder>tests/unit-tests-testng/src/server</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path-noext</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-server</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/server</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-testng-file</target>
+                    <context>
+                        <property>files</property>
+                        <folder>tests/unit-tests-testng/src/server</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="run.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>run-selected-file-in-server</target>
+                    <context>
+                        <property>run.class</property>
+                        <folder>src/server</folder>
+                        <pattern>\.java$</pattern>
+                        <format>java-name</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
+                <action name="debug.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>debug-selected-file-in-server</target>
+                    <context>
+                        <property>debug.class</property>
+                        <folder>src/server</folder>
+                        <pattern>\.java$</pattern>
+                        <format>java-name</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-ads</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/ads</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-build-tools</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/build-tools</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-guitools</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/guitools</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-messages-src</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/messages/src</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-quicksetup</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/quicksetup</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-dsml</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/dsml</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-snmp</target>
+                    <context>
+                        <property>files</property>
+                        <folder>src/snmp/src</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+                <action name="compile.single">
+                    <script>nbproject/ide-file-targets.xml</script>
+                    <target>compile-selected-files-in-server</target>
+                    <context>
+                        <property>files</property>
+                        <folder>tests/unit-tests-testng/src/server</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
+            </ide-actions>
+            <view>
+                <items>
+                    <source-folder style="packages">
+                        <label>src</label>
+                        <location>src</location>
+                    </source-folder>
+                    <source-folder style="packages">
+                        <label>tests/unit-tests-testng/src</label>
+                        <location>tests/unit-tests-testng/src</location>
+                    </source-folder>
+                    <source-file>
+                        <location>build.xml</location>
+                    </source-file>
+                </items>
+                <context-menu>
+                    <ide-action name="build"/>
+                    <ide-action name="rebuild"/>
+                    <ide-action name="clean"/>
+                    <ide-action name="javadoc"/>
+                    <ide-action name="run"/>
+                    <ide-action name="test"/>
+                    <ide-action name="debug"/>
+                </context-menu>
+            </view>
+            <subprojects/>
+        </general-data>
+        <java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/3">
+            <compilation-unit>
+                <package-root>src</package-root>
+                <classpath mode="compile">src-generated</classpath>
+                <source-level>1.6</source-level>
+            </compilation-unit>
+            <compilation-unit>
+                <package-root>tests/unit-tests-testng/src</package-root>
+                <unit-tests/>
+                <source-level>1.6</source-level>
+            </compilation-unit>
+        </java-data>
+    </configuration>
+</project>
diff --git a/sdk/resource/example-2000.ldif.zip b/sdk/resource/example-2000.ldif.zip
new file mode 100644
index 0000000..816619c
--- /dev/null
+++ b/sdk/resource/example-2000.ldif.zip
Binary files differ
diff --git a/sdk/src/com/sun/opends/sdk/extensions/GetConnectionIDExtendedRequest.java b/sdk/src/com/sun/opends/sdk/extensions/GetConnectionIDExtendedRequest.java
index 6166612..6b185e3 100644
--- a/sdk/src/com/sun/opends/sdk/extensions/GetConnectionIDExtendedRequest.java
+++ b/sdk/src/com/sun/opends/sdk/extensions/GetConnectionIDExtendedRequest.java
@@ -38,6 +38,7 @@
 import org.opends.sdk.requests.AbstractExtendedRequest;
 import org.opends.sdk.requests.ExtendedRequest;
 import org.opends.sdk.requests.ExtendedRequestDecoder;
+import org.opends.sdk.responses.AbstractExtendedResultDecoder;
 import org.opends.sdk.responses.ExtendedResult;
 import org.opends.sdk.responses.ExtendedResultDecoder;
 
@@ -75,13 +76,13 @@
 
 
 
-  private static final class ResultDecoder implements
-      ExtendedResultDecoder<GetConnectionIDExtendedResult>
+  private static final class ResultDecoder extends
+      AbstractExtendedResultDecoder<GetConnectionIDExtendedResult>
   {
     /**
      * {@inheritDoc}
      */
-    public GetConnectionIDExtendedResult adaptExtendedErrorResult(
+    public GetConnectionIDExtendedResult newExtendedErrorResult(
         final ResultCode resultCode, final String matchedDN,
         final String diagnosticMessage)
     {
diff --git a/sdk/src/com/sun/opends/sdk/extensions/GetSymmetricKeyExtendedRequest.java b/sdk/src/com/sun/opends/sdk/extensions/GetSymmetricKeyExtendedRequest.java
index 4056c87..30ad68f 100644
--- a/sdk/src/com/sun/opends/sdk/extensions/GetSymmetricKeyExtendedRequest.java
+++ b/sdk/src/com/sun/opends/sdk/extensions/GetSymmetricKeyExtendedRequest.java
@@ -42,6 +42,7 @@
 import org.opends.sdk.requests.AbstractExtendedRequest;
 import org.opends.sdk.requests.ExtendedRequest;
 import org.opends.sdk.requests.ExtendedRequestDecoder;
+import org.opends.sdk.responses.AbstractExtendedResultDecoder;
 import org.opends.sdk.responses.ExtendedResult;
 import org.opends.sdk.responses.ExtendedResultDecoder;
 import org.opends.sdk.responses.Responses;
@@ -116,11 +117,11 @@
 
 
 
-  private static final class ResultDecoder implements
-      ExtendedResultDecoder<ExtendedResult>
+  private static final class ResultDecoder extends
+      AbstractExtendedResultDecoder<ExtendedResult>
   {
 
-    public ExtendedResult adaptExtendedErrorResult(final ResultCode resultCode,
+    public ExtendedResult newExtendedErrorResult(final ResultCode resultCode,
         final String matchedDN, final String diagnosticMessage)
     {
       return Responses.newGenericExtendedResult(resultCode).setMatchedDN(
diff --git a/sdk/src/com/sun/opends/sdk/extensions/PasswordPolicyStateExtendedRequest.java b/sdk/src/com/sun/opends/sdk/extensions/PasswordPolicyStateExtendedRequest.java
index 59a06e1..c73adab 100644
--- a/sdk/src/com/sun/opends/sdk/extensions/PasswordPolicyStateExtendedRequest.java
+++ b/sdk/src/com/sun/opends/sdk/extensions/PasswordPolicyStateExtendedRequest.java
@@ -49,6 +49,7 @@
 import org.opends.sdk.requests.AbstractExtendedRequest;
 import org.opends.sdk.requests.ExtendedRequest;
 import org.opends.sdk.requests.ExtendedRequestDecoder;
+import org.opends.sdk.responses.AbstractExtendedResultDecoder;
 import org.opends.sdk.responses.ExtendedResult;
 import org.opends.sdk.responses.ExtendedResultDecoder;
 
@@ -227,14 +228,14 @@
 
 
 
-  private static final class ResultDecoder implements
-      ExtendedResultDecoder<PasswordPolicyStateExtendedResult>
+  private static final class ResultDecoder extends
+      AbstractExtendedResultDecoder<PasswordPolicyStateExtendedResult>
   {
 
     /**
      * {@inheritDoc}
      */
-    public PasswordPolicyStateExtendedResult adaptExtendedErrorResult(
+    public PasswordPolicyStateExtendedResult newExtendedErrorResult(
         final ResultCode resultCode, final String matchedDN,
         final String diagnosticMessage)
     {
diff --git a/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java b/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java
index 51999bd..78ff32d 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferReader.java
@@ -45,9 +45,9 @@
 import org.opends.sdk.asn1.ASN1Reader;
 import org.opends.sdk.asn1.AbstractASN1Reader;
 
-import com.sun.grizzly.Buffer;
-import com.sun.grizzly.memory.ByteBuffersBuffer;
-import com.sun.grizzly.memory.CompositeBuffer;
+import org.glassfish.grizzly.Buffer;
+import org.glassfish.grizzly.memory.ByteBuffersBuffer;
+import org.glassfish.grizzly.memory.CompositeBuffer;
 import com.sun.opends.sdk.util.StaticUtils;
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferWriter.java b/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferWriter.java
index fcb4096..2f3e8e5 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferWriter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/ASN1BufferWriter.java
@@ -42,10 +42,10 @@
 import org.opends.sdk.asn1.ASN1Writer;
 import org.opends.sdk.asn1.AbstractASN1Writer;
 
-import com.sun.grizzly.Buffer;
-import com.sun.grizzly.Cacheable;
-import com.sun.grizzly.ThreadCache;
-import com.sun.grizzly.memory.ByteBufferWrapper;
+import org.glassfish.grizzly.Buffer;
+import org.glassfish.grizzly.Cacheable;
+import org.glassfish.grizzly.ThreadCache;
+import org.glassfish.grizzly.memory.ByteBufferWrapper;
 import com.sun.opends.sdk.util.StaticUtils;
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/AbstractLDAPFutureResultImpl.java b/sdk/src/com/sun/opends/sdk/ldap/AbstractLDAPFutureResultImpl.java
index 0cc8329..4881524 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/AbstractLDAPFutureResultImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/AbstractLDAPFutureResultImpl.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package com.sun.opends.sdk.ldap;
@@ -34,7 +34,7 @@
 import org.opends.sdk.responses.IntermediateResponse;
 import org.opends.sdk.responses.Result;
 
-import com.sun.opends.sdk.util.AbstractFutureResult;
+import com.sun.opends.sdk.util.AsynchronousFutureResult;
 
 
 
@@ -45,12 +45,11 @@
  *          The type of result returned by this future.
  */
 abstract class AbstractLDAPFutureResultImpl<S extends Result> extends
-    AbstractFutureResult<S> implements FutureResult<S>,
-    IntermediateResponseHandler
+    AsynchronousFutureResult<S> implements IntermediateResponseHandler
 {
   private final AsynchronousConnection connection;
 
-  private final int messageID;
+  private final int requestID;
 
   private IntermediateResponseHandler intermediateResponseHandler;
 
@@ -58,13 +57,13 @@
 
 
 
-  AbstractLDAPFutureResultImpl(final int messageID,
+  AbstractLDAPFutureResultImpl(final int requestID,
       final ResultHandler<? super S> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler,
       final AsynchronousConnection connection)
   {
     super(resultHandler);
-    this.messageID = messageID;
+    this.requestID = requestID;
     this.connection = connection;
     this.intermediateResponseHandler = intermediateResponseHandler;
     this.timestamp = System.currentTimeMillis();
@@ -75,13 +74,15 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public final int getRequestID()
   {
-    return messageID;
+    return requestID;
   }
 
 
 
+  @Override
   public final boolean handleIntermediateResponse(
       final IntermediateResponse response)
   {
@@ -112,7 +113,7 @@
   protected final ErrorResultException handleCancelRequest(
       final boolean mayInterruptIfRunning)
   {
-    connection.abandon(Requests.newAbandonRequest(messageID));
+    connection.abandon(Requests.newAbandonRequest(requestID));
     return null;
   }
 
@@ -121,8 +122,8 @@
   @Override
   protected void toString(final StringBuilder sb)
   {
-    sb.append(" messageID = ");
-    sb.append(messageID);
+    sb.append(" requestID = ");
+    sb.append(requestID);
     sb.append(" timestamp = ");
     sb.append(timestamp);
     super.toString(sb);
@@ -132,8 +133,8 @@
 
   final void adaptErrorResult(final Result result)
   {
-    final S errorResult = newErrorResult(result.getResultCode(), result
-        .getDiagnosticMessage(), result.getCause());
+    final S errorResult = newErrorResult(result.getResultCode(),
+        result.getDiagnosticMessage(), result.getCause());
     setResultOrError(errorResult);
   }
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/GlobalTransportFactory.java b/sdk/src/com/sun/opends/sdk/ldap/GlobalTransportFactory.java
index 85d102d..a4d92bc 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/GlobalTransportFactory.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/GlobalTransportFactory.java
@@ -31,11 +31,11 @@
 
 import java.io.IOException;
 
-import com.sun.grizzly.TransportFactory;
-import com.sun.grizzly.nio.NIOTransportFactory;
-import com.sun.grizzly.nio.transport.TCPNIOTransport;
-import com.sun.grizzly.nio.transport.UDPNIOTransport;
-import com.sun.grizzly.threadpool.ThreadPoolConfig;
+import org.glassfish.grizzly.TransportFactory;
+import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
+import org.glassfish.grizzly.nio.transport.UDPNIOTransport;
+import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
+
 import com.sun.opends.sdk.util.StaticUtils;
 
 
@@ -43,9 +43,9 @@
 /**
  * A static Grizzly transport that can be used by default globally in the SDK.
  */
-public final class GlobalTransportFactory extends NIOTransportFactory
+public final class GlobalTransportFactory extends TransportFactory
 {
-  private static final GlobalTransportFactory INSTANCE = new GlobalTransportFactory();
+  private static boolean isInitialized = false;
 
 
 
@@ -54,22 +54,14 @@
    *
    * @return The global Grizzly transport factory.
    */
-  public static TransportFactory getInstance()
+  public static synchronized TransportFactory getInstance()
   {
-    return INSTANCE;
-  }
-
-
-
-  /**
-   * Sets the global Grizzly transport factory.
-   *
-   * @param factory
-   *          The global Grizzly transport factory.
-   */
-  public static void setInstance(final TransportFactory factory)
-  {
-    throw new UnsupportedOperationException("not yet implemented");
+    if (!isInitialized)
+    {
+      TransportFactory.setInstance(new GlobalTransportFactory());
+      isInitialized = true;
+    }
+    return TransportFactory.getInstance();
   }
 
 
@@ -94,7 +86,7 @@
 
 
   /**
-   * Close the {@link com.sun.grizzly.TransportFactory} and release all
+   * Close the {@link org.glassfish.grizzly.TransportFactory} and release all
    * resources.
    */
   @Override
@@ -118,9 +110,9 @@
 
 
   /**
-   * Create instance of TCP {@link com.sun.grizzly.Transport}.
+   * Create instance of TCP {@link org.glassfish.grizzly.Transport}.
    *
-   * @return instance of TCP {@link com.sun.grizzly.Transport}.
+   * @return instance of TCP {@link org.glassfish.grizzly.Transport}.
    */
   @Override
   public synchronized TCPNIOTransport createTCPTransport()
@@ -149,8 +141,9 @@
 
 
   /**
-   * Creating an UDP transport is unsupported with this factory. A {@code
-   * UnsupportedOperationException} will be thrown when this method is called.
+   * Creating an UDP transport is unsupported with this factory. A
+   * {@code UnsupportedOperationException} will be thrown when this method is
+   * called.
    *
    * @return This method will always throw {@code UnsupportedOperationException}
    *         .
@@ -210,4 +203,5 @@
 
     super.initialize();
   }
+
 }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPListenerOptions.java b/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPListenerOptions.java
index ec3e223..ed2de71 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPListenerOptions.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPListenerOptions.java
@@ -31,7 +31,7 @@
 
 import org.opends.sdk.LDAPListenerOptions;
 
-import com.sun.grizzly.nio.transport.TCPNIOTransport;
+import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
 
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPOptions.java b/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPOptions.java
index f75e634..d6f76ec 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPOptions.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/GrizzlyLDAPOptions.java
@@ -31,7 +31,7 @@
 
 import org.opends.sdk.LDAPOptions;
 
-import com.sun.grizzly.nio.transport.TCPNIOTransport;
+import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
 
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/InternalConnection.java b/sdk/src/com/sun/opends/sdk/ldap/InternalConnection.java
index 00d07e7..99200ef 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/InternalConnection.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/InternalConnection.java
@@ -50,8 +50,7 @@
 public final class InternalConnection extends AbstractAsynchronousConnection
 {
   private static final class InternalBindFutureResultImpl extends
-      AbstractLDAPFutureResultImpl<BindResult> implements
-      FutureResult<BindResult>
+      AbstractLDAPFutureResultImpl<BindResult>
   {
     private final BindRequest bindRequest;
 
@@ -90,8 +89,8 @@
     BindResult newErrorResult(final ResultCode resultCode,
         final String diagnosticMessage, final Throwable cause)
     {
-      return Responses.newBindResult(resultCode).setDiagnosticMessage(
-          diagnosticMessage).setCause(cause);
+      return Responses.newBindResult(resultCode)
+          .setDiagnosticMessage(diagnosticMessage).setCause(cause);
     }
   }
 
@@ -120,6 +119,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<Void> abandon(final AbandonRequest request)
       throws UnsupportedOperationException, IllegalStateException,
       NullPointerException
@@ -134,6 +134,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<Result> add(final AddRequest request,
       final ResultHandler<? super Result> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler)
@@ -152,6 +153,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public void addConnectionEventListener(final ConnectionEventListener listener)
       throws IllegalStateException, NullPointerException
   {
@@ -164,6 +166,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<BindResult> bind(final BindRequest request,
       final ResultHandler<? super BindResult> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler)
@@ -182,6 +185,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public void close(final UnbindRequest request, final String reason)
   {
     final int i = messageID.getAndIncrement();
@@ -193,6 +197,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<CompareResult> compare(final CompareRequest request,
       final ResultHandler<? super CompareResult> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler)
@@ -211,6 +216,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<Result> delete(final DeleteRequest request,
       final ResultHandler<? super Result> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler)
@@ -229,6 +235,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public <R extends ExtendedResult> FutureResult<R> extendedRequest(
       final ExtendedRequest<R> request,
       final ResultHandler<? super R> resultHandler,
@@ -248,6 +255,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public boolean isClosed()
   {
     // FIXME: this should be true after close has been called.
@@ -259,6 +267,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public boolean isValid()
   {
     // FIXME: this should be false if this connection is disconnected.
@@ -270,6 +279,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<Result> modify(final ModifyRequest request,
       final ResultHandler<? super Result> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler)
@@ -288,6 +298,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<Result> modifyDN(final ModifyDNRequest request,
       final ResultHandler<? super Result> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler)
@@ -306,6 +317,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public void removeConnectionEventListener(
       final ConnectionEventListener listener) throws NullPointerException
   {
@@ -318,6 +330,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<Result> search(final SearchRequest request,
       final SearchResultHandler resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler)
@@ -327,7 +340,22 @@
     final int i = messageID.getAndIncrement();
     final LDAPSearchFutureResultImpl future = new LDAPSearchFutureResultImpl(i,
         request, resultHandler, intermediateResponseHandler, this);
-    serverConnection.handleSearch(i, request, future, future, future);
+    serverConnection.handleSearch(i, request, future, future);
     return future;
   }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("InternalConnection(");
+    builder.append(String.valueOf(serverConnection));
+    builder.append(')');
+    return builder.toString();
+  }
+
 }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPBindFutureResultImpl.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPBindFutureResultImpl.java
index 9d9b058..9d8d77e 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPBindFutureResultImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPBindFutureResultImpl.java
@@ -40,19 +40,18 @@
  * Bind result future implementation.
  */
 final class LDAPBindFutureResultImpl extends
-    AbstractLDAPFutureResultImpl<BindResult> implements
-    FutureResult<BindResult>
+    AbstractLDAPFutureResultImpl<BindResult>
 {
   private final BindClient bindClient;
 
 
 
-  LDAPBindFutureResultImpl(final int messageID, final BindClient bindClient,
+  LDAPBindFutureResultImpl(final int requestID, final BindClient bindClient,
       final ResultHandler<? super BindResult> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler,
       final AsynchronousConnection connection)
   {
-    super(messageID, resultHandler, intermediateResponseHandler, connection);
+    super(requestID, resultHandler, intermediateResponseHandler, connection);
     this.bindClient = bindClient;
   }
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java
index ea42e73..9a8b55f 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPClientFilter.java
@@ -43,14 +43,14 @@
 import org.opends.sdk.requests.*;
 import org.opends.sdk.responses.*;
 
-import com.sun.grizzly.Buffer;
-import com.sun.grizzly.Connection;
-import com.sun.grizzly.EmptyCompletionHandler;
-import com.sun.grizzly.Grizzly;
-import com.sun.grizzly.attributes.Attribute;
-import com.sun.grizzly.filterchain.BaseFilter;
-import com.sun.grizzly.filterchain.FilterChainContext;
-import com.sun.grizzly.filterchain.NextAction;
+import org.glassfish.grizzly.Buffer;
+import org.glassfish.grizzly.Connection;
+import org.glassfish.grizzly.EmptyCompletionHandler;
+import org.glassfish.grizzly.Grizzly;
+import org.glassfish.grizzly.attributes.Attribute;
+import org.glassfish.grizzly.filterchain.BaseFilter;
+import org.glassfish.grizzly.filterchain.FilterChainContext;
+import org.glassfish.grizzly.filterchain.NextAction;
 
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPCompareFutureResultImpl.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPCompareFutureResultImpl.java
index df491a0..193ddc8 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPCompareFutureResultImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPCompareFutureResultImpl.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package com.sun.opends.sdk.ldap;
@@ -40,20 +40,19 @@
  * Compare result future implementation.
  */
 final class LDAPCompareFutureResultImpl extends
-    AbstractLDAPFutureResultImpl<CompareResult> implements
-    FutureResult<CompareResult>
+    AbstractLDAPFutureResultImpl<CompareResult>
 {
   private final CompareRequest request;
 
 
 
-  LDAPCompareFutureResultImpl(final int messageID,
+  LDAPCompareFutureResultImpl(final int requestID,
       final CompareRequest request,
       final ResultHandler<? super CompareResult> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler,
       final AsynchronousConnection connection)
   {
-    super(messageID, resultHandler, intermediateResponseHandler, connection);
+    super(requestID, resultHandler, intermediateResponseHandler, connection);
     this.request = request;
   }
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java
index 25f7d84..dbe9ed3 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java
@@ -44,12 +44,12 @@
 import org.opends.sdk.requests.*;
 import org.opends.sdk.responses.*;
 
-import com.sun.grizzly.CompletionHandler;
-import com.sun.grizzly.filterchain.DefaultFilterChain;
-import com.sun.grizzly.filterchain.Filter;
-import com.sun.grizzly.filterchain.FilterChain;
-import com.sun.grizzly.ssl.SSLEngineConfigurator;
-import com.sun.grizzly.ssl.SSLFilter;
+import org.glassfish.grizzly.CompletionHandler;
+import org.glassfish.grizzly.filterchain.DefaultFilterChain;
+import org.glassfish.grizzly.filterchain.Filter;
+import org.glassfish.grizzly.filterchain.FilterChain;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.grizzly.ssl.SSLFilter;
 import com.sun.opends.sdk.util.CompletedFutureResult;
 import com.sun.opends.sdk.util.StaticUtils;
 import com.sun.opends.sdk.util.Validator;
@@ -64,7 +64,7 @@
 final class LDAPConnection extends AbstractAsynchronousConnection implements
     AsynchronousConnection
 {
-  private final com.sun.grizzly.Connection<?> connection;
+  private final org.glassfish.grizzly.Connection<?> connection;
 
   private Result connectionInvalidReason;
 
@@ -79,9 +79,8 @@
 
   private boolean bindOrStartTLSInProgress = false;
 
-  private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>
-    pendingRequests =
-      new ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
+  private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>> pendingRequests =
+    new ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
 
   private final Object stateLock = new Object();
 
@@ -99,7 +98,7 @@
    * @param options
    *          The LDAP client options.
    */
-  LDAPConnection(final com.sun.grizzly.Connection<?> connection,
+  LDAPConnection(final org.glassfish.grizzly.Connection<?> connection,
       final LDAPOptions options)
   {
     this.connection = connection;
@@ -120,20 +119,20 @@
     {
       if (connectionInvalidReason != null)
       {
-        return new CompletedFutureResult<Void>(ErrorResultException
-            .wrap(connectionInvalidReason), messageID);
+        return new CompletedFutureResult<Void>(
+            ErrorResultException.wrap(connectionInvalidReason), messageID);
       }
       if (bindOrStartTLSInProgress)
       {
         final Result errorResult = Responses.newResult(
             ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
             "Bind or Start TLS operation in progress");
-        return new CompletedFutureResult<Void>(ErrorResultException
-            .wrap(errorResult), messageID);
+        return new CompletedFutureResult<Void>(
+            ErrorResultException.wrap(errorResult), messageID);
       }
 
       // First remove the future associated with the request to be abandoned.
-      pendingRequest = pendingRequests.remove(request.getMessageID());
+      pendingRequest = pendingRequests.remove(request.getRequestID());
     }
 
     if (pendingRequest == null)
@@ -169,8 +168,8 @@
       final Result errorResult = Responses.newResult(
           ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
       connectionErrorOccurred(errorResult);
-      return new CompletedFutureResult<Void>(ErrorResultException
-          .wrap(errorResult), messageID);
+      return new CompletedFutureResult<Void>(
+          ErrorResultException.wrap(errorResult), messageID);
     }
   }
 
@@ -258,18 +257,20 @@
     BindClient context;
     try
     {
-      context = request.createBindClient(
-          connection.getPeerAddress() instanceof InetSocketAddress ?
-              ((InetSocketAddress)connection.getPeerAddress()).getHostName() :
-              connection.getPeerAddress().toString());
+      context = request
+          .createBindClient(connection.getPeerAddress() instanceof InetSocketAddress ?
+              ((InetSocketAddress) connection
+              .getPeerAddress()).getHostName() : connection.getPeerAddress()
+              .toString());
     }
     catch (final Exception e)
     {
       // FIXME: I18N need to have a better error message.
       // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
-          "An error occurred while creating a bind context").setCause(e);
+      final Result errorResult = Responses
+          .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+          .setDiagnosticMessage(
+              "An error occurred while creating a bind context").setCause(e);
       final ErrorResultException error = ErrorResultException.wrap(errorResult);
       if (resultHandler != null)
       {
@@ -349,9 +350,13 @@
     // FIXME: I18N need to internationalize this message.
     Validator.ensureNotNull(request);
 
-    close(request, false, Responses.newResult(
-        ResultCode.CLIENT_SIDE_USER_CANCELLED).setDiagnosticMessage(
-        "Connection closed by client" + (reason != null ? ": " + reason : "")));
+    close(
+        request,
+        false,
+        Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
+            .setDiagnosticMessage(
+                "Connection closed by client"
+                    + (reason != null ? ": " + reason : "")));
   }
 
 
@@ -494,7 +499,7 @@
       if (bindOrStartTLSInProgress)
       {
         future.setResultOrError(request.getResultDecoder()
-            .adaptExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
+            .newExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
                 "Bind or Start TLS operation in progress"));
         return future;
       }
@@ -503,14 +508,14 @@
         if (!pendingRequests.isEmpty())
         {
           future.setResultOrError(request.getResultDecoder()
-              .adaptExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
+              .newExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
                   "There are pending operations on this connection"));
           return future;
         }
         if (isTLSEnabled())
         {
           future.setResultOrError(request.getResultDecoder()
-              .adaptExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
+              .newExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
                   "This connection is already TLS enabled"));
         }
         bindOrStartTLSInProgress = true;
@@ -754,13 +759,29 @@
 
 
 
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("LDAPConnection(");
+    builder.append(connection.getLocalAddress());
+    builder.append(',');
+    builder.append(connection.getPeerAddress());
+    builder.append(')');
+    return builder.toString();
+  }
+
+
+
   int addPendingRequest(final AbstractLDAPFutureResultImpl<?> request)
       throws ErrorResultException
   {
     final int newMsgID = nextMsgID.getAndIncrement();
-    synchronized(stateLock)
+    synchronized (stateLock)
     {
-      if(connectionInvalidReason != null)
+      if (connectionInvalidReason != null)
       {
         throw ErrorResultException.wrap(connectionInvalidReason);
       }
@@ -779,9 +800,9 @@
     {
       for (int requestID : pendingRequests.keySet())
       {
-        final AbstractLDAPFutureResultImpl<?> future =
-            pendingRequests.get(requestID);
-        if(future != null)
+        final AbstractLDAPFutureResultImpl<?> future = pendingRequests
+            .get(requestID);
+        if (future != null)
         {
           final long diff = (future.getTimestamp() + timeout) - currentTime;
           if (diff <= 0 && pendingRequests.remove(requestID) != null)
@@ -814,9 +835,15 @@
 
     synchronized (stateLock)
     {
-      if (isClosed || connectionInvalidReason != null)
+      if (isClosed) {
+        // Already closed.
+        return;
+      }
+
+      if (connectionInvalidReason != null)
       {
         // Already closed.
+        isClosed = true;
         return;
       }
 
@@ -832,20 +859,15 @@
       }
 
       // Mark the connection as invalid.
-      connectionInvalidReason =
-          reason.getResultCode() == ResultCode.CLIENT_SIDE_USER_CANCELLED ?
-              reason : Responses.newResult(
-              ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(
-              ErrorResultException.wrap(reason)).setDiagnosticMessage(
-              "Connection closed: " + reason.getDiagnosticMessage());
+      connectionInvalidReason = reason;
     }
 
     // First abort all outstanding requests.
     for (int requestID : pendingRequests.keySet())
     {
-      final AbstractLDAPFutureResultImpl<?> future =
-          pendingRequests.remove(requestID);
-      if(future != null)
+      final AbstractLDAPFutureResultImpl<?> future = pendingRequests
+          .remove(requestID);
+      if (future != null)
       {
         future.adaptErrorResult(reason);
       }
@@ -939,8 +961,8 @@
   {
     if (customFilterChain == null)
     {
-      customFilterChain = new DefaultFilterChain((FilterChain) connection
-          .getProcessor());
+      customFilterChain = new DefaultFilterChain(
+          (FilterChain) connection.getProcessor());
       connection.setProcessor(customFilterChain);
     }
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPConnectionFactoryImpl.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPConnectionFactoryImpl.java
index d56efdd..9b9c24f 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPConnectionFactoryImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPConnectionFactoryImpl.java
@@ -42,13 +42,13 @@
 import org.opends.sdk.responses.Responses;
 import org.opends.sdk.responses.Result;
 
-import com.sun.grizzly.CompletionHandler;
-import com.sun.grizzly.Connection;
-import com.sun.grizzly.EmptyCompletionHandler;
-import com.sun.grizzly.filterchain.DefaultFilterChain;
-import com.sun.grizzly.filterchain.FilterChain;
-import com.sun.grizzly.filterchain.TransportFilter;
-import com.sun.grizzly.nio.transport.TCPNIOTransport;
+import org.glassfish.grizzly.CompletionHandler;
+import org.glassfish.grizzly.Connection;
+import org.glassfish.grizzly.EmptyCompletionHandler;
+import org.glassfish.grizzly.filterchain.DefaultFilterChain;
+import org.glassfish.grizzly.filterchain.FilterChain;
+import org.glassfish.grizzly.filterchain.TransportFilter;
+import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
 import com.sun.opends.sdk.util.CompletedFutureResult;
 import com.sun.opends.sdk.util.FutureResultTransformer;
 import com.sun.opends.sdk.util.RecursiveFutureResult;
@@ -87,7 +87,10 @@
           // Ensure that the connection is closed.
           try
           {
-            connection.close();
+            if (connection != null)
+            {
+              connection.close();
+            }
           }
           catch (final Exception e)
           {
@@ -148,10 +151,10 @@
                     @Override
                     public void failed(final Throwable throwable)
                     {
-                      final Result errorResult = Responses.newResult(
-                          ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(
-                          throwable).setDiagnosticMessage(
-                          throwable.getMessage());
+                      final Result errorResult = Responses
+                          .newResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR)
+                          .setCause(throwable)
+                          .setDiagnosticMessage(throwable.getMessage());
                       handler.handleErrorResult(ErrorResultException
                           .wrap(errorResult));
                     }
@@ -160,9 +163,9 @@
             }
             catch (final IOException ioe)
             {
-              final Result errorResult = Responses.newResult(
-                  ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(ioe)
-                  .setDiagnosticMessage(ioe.getMessage());
+              final Result errorResult = Responses
+                  .newResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR)
+                  .setCause(ioe).setDiagnosticMessage(ioe.getMessage());
               throw ErrorResultException.wrap(errorResult);
             }
           }
@@ -259,8 +262,8 @@
 
     this.socketAddress = address;
     this.options = new LDAPOptions(options);
-    this.clientFilter = new LDAPClientFilter(new LDAPReader(this.options
-        .getDecodeOptions()), 0);
+    this.clientFilter = new LDAPClientFilter(new LDAPReader(
+        this.options.getDecodeOptions()), 0);
     this.defaultFilterChain = new DefaultFilterChain();
     this.defaultFilterChain.add(new TransportFilter());
     this.defaultFilterChain.add(clientFilter);
@@ -292,6 +295,32 @@
 
 
 
+  /**
+   * Returns the address of the Directory Server.
+   *
+   * @return The address of the Directory Server.
+   */
+  public SocketAddress getSocketAddress()
+  {
+    return socketAddress;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("LDAPConnectionFactory(");
+    builder.append(getSocketAddress().toString());
+    builder.append(')');
+    return builder.toString();
+  }
+
+
+
   private LDAPConnection adaptConnection(final Connection<?> connection)
   {
     // Test shows that its much faster with non block writes but risk
@@ -314,9 +343,9 @@
       t = t.getCause();
     }
 
-    final Result result = Responses.newResult(
-        ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(t).setDiagnosticMessage(
-        t.getMessage());
+    final Result result = Responses
+        .newResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(t)
+        .setDiagnosticMessage(t.getMessage());
     return ErrorResultException.wrap(result);
   }
 }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPExtendedFutureResultImpl.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPExtendedFutureResultImpl.java
index 1c162a6..6005fa4 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPExtendedFutureResultImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPExtendedFutureResultImpl.java
@@ -43,19 +43,19 @@
  *          The type of result returned by this future.
  */
 final class LDAPExtendedFutureResultImpl<R extends ExtendedResult> extends
-    AbstractLDAPFutureResultImpl<R> implements FutureResult<R>
+    AbstractLDAPFutureResultImpl<R>
 {
   private final ExtendedRequest<R> request;
 
 
 
-  LDAPExtendedFutureResultImpl(final int messageID,
+  LDAPExtendedFutureResultImpl(final int requestID,
       final ExtendedRequest<R> request,
       final ResultHandler<? super R> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler,
       final AsynchronousConnection connection)
   {
-    super(messageID, resultHandler, intermediateResponseHandler, connection);
+    super(requestID, resultHandler, intermediateResponseHandler, connection);
     this.request = request;
   }
 
@@ -107,7 +107,7 @@
   R newErrorResult(final ResultCode resultCode, final String diagnosticMessage,
       final Throwable cause)
   {
-    return request.getResultDecoder().adaptExtendedErrorResult(resultCode, "",
+    return request.getResultDecoder().newExtendedErrorResult(resultCode, "",
         diagnosticMessage);
   }
 }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPFutureResultImpl.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPFutureResultImpl.java
index 3733164..bcf8052 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPFutureResultImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPFutureResultImpl.java
@@ -29,7 +29,10 @@
 
 
 
-import org.opends.sdk.*;
+import org.opends.sdk.AsynchronousConnection;
+import org.opends.sdk.IntermediateResponseHandler;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultHandler;
 import org.opends.sdk.requests.Request;
 import org.opends.sdk.responses.Responses;
 import org.opends.sdk.responses.Result;
@@ -40,18 +43,17 @@
  * Result future implementation.
  */
 final class LDAPFutureResultImpl extends AbstractLDAPFutureResultImpl<Result>
-    implements FutureResult<Result>
 {
   private final Request request;
 
 
 
-  LDAPFutureResultImpl(final int messageID, final Request request,
+  LDAPFutureResultImpl(final int requestID, final Request request,
       final ResultHandler<? super Result> resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler,
       final AsynchronousConnection connection)
   {
-    super(messageID, resultHandler, intermediateResponseHandler, connection);
+    super(requestID, resultHandler, intermediateResponseHandler, connection);
     this.request = request;
   }
 
@@ -85,7 +87,7 @@
   Result newErrorResult(final ResultCode resultCode,
       final String diagnosticMessage, final Throwable cause)
   {
-    return Responses.newResult(resultCode).setDiagnosticMessage(
-        diagnosticMessage).setCause(cause);
+    return Responses.newResult(resultCode)
+        .setDiagnosticMessage(diagnosticMessage).setCause(cause);
   }
 }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPListenerImpl.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPListenerImpl.java
index f534f28..90dd3a3 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPListenerImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPListenerImpl.java
@@ -32,21 +32,23 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.net.SocketAddress;
+import java.util.logging.Level;
 
 import javax.net.ssl.SSLContext;
 
+import org.glassfish.grizzly.filterchain.DefaultFilterChain;
+import org.glassfish.grizzly.filterchain.FilterChain;
+import org.glassfish.grizzly.filterchain.TransportFilter;
+import org.glassfish.grizzly.nio.transport.TCPNIOServerConnection;
+import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.grizzly.ssl.SSLFilter;
 import org.opends.sdk.DecodeOptions;
 import org.opends.sdk.LDAPClientContext;
 import org.opends.sdk.LDAPListenerOptions;
 import org.opends.sdk.ServerConnectionFactory;
 
-import com.sun.grizzly.filterchain.DefaultFilterChain;
-import com.sun.grizzly.filterchain.FilterChain;
-import com.sun.grizzly.filterchain.TransportFilter;
-import com.sun.grizzly.nio.transport.TCPNIOServerConnection;
-import com.sun.grizzly.nio.transport.TCPNIOTransport;
-import com.sun.grizzly.ssl.SSLEngineConfigurator;
-import com.sun.grizzly.ssl.SSLFilter;
+import com.sun.opends.sdk.util.StaticUtils;
 
 
 
@@ -118,9 +120,53 @@
   /**
    * {@inheritDoc}
    */
-  public void close() throws IOException
+  @Override
+  public void close()
   {
-    transport.unbind(serverConnection);
+    try
+    {
+      serverConnection.close().get();
+    }
+    catch (final InterruptedException e)
+    {
+      // Cannot handle here.
+      Thread.currentThread().interrupt();
+    }
+    catch (final Exception e)
+    {
+      // Ignore the exception.
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+      {
+        StaticUtils.DEBUG_LOG.log(Level.WARNING,
+            "Exception occurred while closing listener:" + e.getMessage(), e);
+      }
+    }
+  }
+
+
+
+  /**
+   * Returns the address that this LDAP listener is listening on.
+   *
+   * @return The address that this LDAP listener is listening on.
+   */
+  public SocketAddress getSocketAddress()
+  {
+    return serverConnection.getLocalAddress();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("LDAPListener(");
+    builder.append(getSocketAddress().toString());
+    builder.append(')');
+    return builder.toString();
   }
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPSearchFutureResultImpl.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPSearchFutureResultImpl.java
index 70a8e2d..a7c71ea 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPSearchFutureResultImpl.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPSearchFutureResultImpl.java
@@ -42,8 +42,7 @@
  * Search result future implementation.
  */
 final class LDAPSearchFutureResultImpl extends
-    AbstractLDAPFutureResultImpl<Result> implements FutureResult<Result>,
-    SearchResultHandler
+    AbstractLDAPFutureResultImpl<Result> implements SearchResultHandler
 {
 
   private SearchResultHandler searchResultHandler;
@@ -52,12 +51,12 @@
 
 
 
-  LDAPSearchFutureResultImpl(final int messageID, final SearchRequest request,
+  LDAPSearchFutureResultImpl(final int requestID, final SearchRequest request,
       final SearchResultHandler resultHandler,
       final IntermediateResponseHandler intermediateResponseHandler,
       final AsynchronousConnection connection)
   {
-    super(messageID, resultHandler, intermediateResponseHandler, connection);
+    super(requestID, resultHandler, intermediateResponseHandler, connection);
     this.request = request;
     this.searchResultHandler = resultHandler;
   }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java
index 3bd1034..2dbe61b 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPServerFilter.java
@@ -33,24 +33,25 @@
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
-import java.util.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 
+import org.glassfish.grizzly.Buffer;
+import org.glassfish.grizzly.Connection;
+import org.glassfish.grizzly.Grizzly;
+import org.glassfish.grizzly.attributes.Attribute;
+import org.glassfish.grizzly.filterchain.*;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.grizzly.ssl.SSLFilter;
+import org.glassfish.grizzly.ssl.SSLUtils;
 import org.opends.sdk.*;
 import org.opends.sdk.controls.Control;
 import org.opends.sdk.requests.*;
 import org.opends.sdk.responses.*;
 
-import com.sun.grizzly.Buffer;
-import com.sun.grizzly.Connection;
-import com.sun.grizzly.Grizzly;
-import com.sun.grizzly.attributes.Attribute;
-import com.sun.grizzly.filterchain.*;
-import com.sun.grizzly.ssl.SSLEngineConfigurator;
-import com.sun.grizzly.ssl.SSLFilter;
-import com.sun.grizzly.ssl.SSLUtils;
 import com.sun.opends.sdk.util.StaticUtils;
 import com.sun.opends.sdk.util.Validator;
 
@@ -80,7 +81,7 @@
 
 
     @Override
-    public boolean handleIntermediateResponse(
+    public final boolean handleIntermediateResponse(
         final IntermediateResponse response)
     {
       final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
@@ -100,7 +101,6 @@
       }
       return true;
     }
-
   }
 
 
@@ -204,12 +204,7 @@
   {
     private final Connection<?> connection;
 
-    // Connection state guarded by stateLock.
-    private final Object stateLock = new Object();
-    private List<ConnectionEventListener> connectionEventListeners = null;
-    private boolean isClosed = false;
-    private Throwable connectionError = null;
-    private ExtendedResult disconnectNotification = null;
+    private volatile boolean isClosed = false;
 
     private ServerConnection<Integer> serverConnection = null;
 
@@ -222,45 +217,10 @@
 
 
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void addConnectionEventListener(
-        final ConnectionEventListener listener) throws NullPointerException
-    {
-      Validator.ensureNotNull(listener);
-
-      boolean invokeImmediately = false;
-      synchronized (stateLock)
-      {
-        if (isClosed)
-        {
-          invokeImmediately = true;
-        }
-        else
-        {
-          if (connectionEventListeners == null)
-          {
-            connectionEventListeners = new LinkedList<ConnectionEventListener>();
-          }
-          connectionEventListeners.add(listener);
-        }
-      }
-
-      // Invoke listener immediately if this connection is already closed.
-      if (invokeImmediately)
-      {
-        invokeListener(listener);
-      }
-    }
-
-
-
     @Override
     public void disconnect()
     {
-      LDAPServerFilter.notifyConnectionClosed(connection, -1, null);
+      LDAPServerFilter.notifyConnectionDisconnected(connection, null, null);
     }
 
 
@@ -274,7 +234,8 @@
           .newGenericExtendedResult(resultCode)
           .setOID(OID_NOTICE_OF_DISCONNECTION).setDiagnosticMessage(message);
       sendUnsolicitedNotification(notification);
-      disconnect();
+      LDAPServerFilter.notifyConnectionDisconnected(connection, resultCode,
+          message);
     }
 
 
@@ -325,32 +286,7 @@
     @Override
     public boolean isClosed()
     {
-      final boolean tmp;
-      synchronized (stateLock)
-      {
-        tmp = isClosed;
-      }
-      return tmp;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void removeConnectionEventListener(
-        final ConnectionEventListener listener) throws NullPointerException
-    {
-      Validator.ensureNotNull(listener);
-
-      synchronized (stateLock)
-      {
-        if (connectionEventListeners != null)
-        {
-          connectionEventListeners.remove(listener);
-        }
-      }
+      return isClosed;
     }
 
 
@@ -372,38 +308,6 @@
       {
         asn1Writer.recycle();
       }
-
-      // Update state and notify event listeners if necessary, only if the
-      // notification was sent successfully.
-      if (notification.getOID().equals(OID_NOTICE_OF_DISCONNECTION))
-      {
-        // Don't notify listeners yet - wait for disconnect.
-        synchronized (stateLock)
-        {
-          disconnectNotification = notification;
-        }
-      }
-      else
-      {
-        // Notify listeners.
-        List<ConnectionEventListener> tmpList = null;
-        synchronized (stateLock)
-        {
-          if (!isClosed && connectionEventListeners != null)
-          {
-            tmpList = new ArrayList<ConnectionEventListener>(
-                connectionEventListeners);
-          }
-        }
-
-        if (tmpList != null)
-        {
-          for (final ConnectionEventListener listener : tmpList)
-          {
-            listener.handleUnsolicitedNotification(notification);
-          }
-        }
-      }
     }
 
 
@@ -435,6 +339,29 @@
 
 
 
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+      StringBuilder builder = new StringBuilder();
+      builder.append("LDAPClientContext(");
+      builder.append(getLocalAddress());
+      builder.append(',');
+      builder.append(getPeerAddress());
+      builder.append(')');
+      return builder.toString();
+    }
+
+
+
+    private void close()
+    {
+      isClosed = true;
+    }
+
+
+
     private ServerConnection<Integer> getServerConnection()
     {
       return serverConnection;
@@ -442,87 +369,6 @@
 
 
 
-    private void invokeListener(final ConnectionEventListener listener)
-    {
-      if (connectionError != null)
-      {
-        final Result result;
-        if (connectionError instanceof DecodeException)
-        {
-          final DecodeException e = (DecodeException) connectionError;
-          result = Responses.newResult(ResultCode.PROTOCOL_ERROR)
-              .setDiagnosticMessage(e.getMessage()).setCause(connectionError);
-        }
-        else
-        {
-          result = Responses.newResult(ResultCode.OTHER)
-              .setDiagnosticMessage(connectionError.getMessage())
-              .setCause(connectionError);
-        }
-        listener
-            .handleConnectionError(false, ErrorResultException.wrap(result));
-      }
-      else if (disconnectNotification != null)
-      {
-        listener.handleConnectionError(true,
-            ErrorResultException.wrap(disconnectNotification));
-      }
-      else
-      {
-        listener.handleConnectionClosed();
-      }
-    }
-
-
-
-    private void notifyConnectionClosed(final int messageID,
-        final UnbindRequest unbindRequest)
-    {
-      final List<ConnectionEventListener> tmpList;
-      synchronized (stateLock)
-      {
-        if (!isClosed)
-        {
-          isClosed = true;
-        }
-        tmpList = connectionEventListeners;
-        connectionEventListeners = null;
-      }
-      if (tmpList != null)
-      {
-        for (final ConnectionEventListener listener : tmpList)
-        {
-          invokeListener(listener);
-        }
-      }
-    }
-
-
-
-    private void notifyConnectionException(final Throwable error)
-    {
-      final List<ConnectionEventListener> tmpList;
-      synchronized (stateLock)
-      {
-        if (!isClosed)
-        {
-          connectionError = error;
-          isClosed = true;
-        }
-        tmpList = connectionEventListeners;
-        connectionEventListeners = null;
-      }
-      if (tmpList != null)
-      {
-        for (final ConnectionEventListener listener : tmpList)
-        {
-          invokeListener(listener);
-        }
-      }
-    }
-
-
-
     private void setServerConnection(
         final ServerConnection<Integer> serverConnection)
     {
@@ -875,12 +721,10 @@
   private static final LDAPWriter LDAP_WRITER = new LDAPWriter();
 
   private static final Attribute<ClientContextImpl> LDAP_CONNECTION_ATTR =
-    Grizzly.DEFAULT_ATTRIBUTE_BUILDER
-      .createAttribute("LDAPServerConnection");
+    Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPServerConnection");
 
   private static final Attribute<ASN1BufferReader> LDAP_ASN1_READER_ATTR =
-    Grizzly.DEFAULT_ATTRIBUTE_BUILDER
-      .createAttribute("LDAPASN1Reader");
+    Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPASN1Reader");
 
 
 
@@ -891,8 +735,8 @@
         .remove(connection);
     if (clientContext != null)
     {
-      // First notify connection event listeners.
-      clientContext.notifyConnectionClosed(messageID, unbindRequest);
+      // Close the connection context.
+      clientContext.close();
 
       // Notify the server connection: it may be null if disconnect is invoked
       // during accept.
@@ -925,15 +769,16 @@
 
 
 
-  private static void notifyConnectionException(final Connection<?> connection,
-      final Throwable error)
+  private static void notifyConnectionDisconnected(
+      final Connection<?> connection, final ResultCode resultCode,
+      final String message)
   {
     final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR
         .remove(connection);
     if (clientContext != null)
     {
-      // First notify connection event listeners.
-      clientContext.notifyConnectionException(error);
+      // Close the connection context.
+      clientContext.close();
 
       // Notify the server connection: it may be null if disconnect is invoked
       // during accept.
@@ -941,7 +786,40 @@
           .getServerConnection();
       if (serverConnection != null)
       {
-        serverConnection.handleConnectionException(error);
+        serverConnection.handleConnectionDisconnected(resultCode, message);
+      }
+
+      // Close the connection.
+      try
+      {
+        connection.close();
+      }
+      catch (final IOException e)
+      {
+        StaticUtils.DEBUG_LOG.warning("Error closing connection: " + e);
+      }
+    }
+  }
+
+
+
+  private static void notifyConnectionException(final Connection<?> connection,
+      final Throwable error)
+  {
+    final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR
+        .remove(connection);
+    if (clientContext != null)
+    {
+      // Close the connection context.
+      clientContext.close();
+
+      // Notify the server connection: it may be null if disconnect is invoked
+      // during accept.
+      final ServerConnection<Integer> serverConnection = clientContext
+          .getServerConnection();
+      if (serverConnection != null)
+      {
+        serverConnection.handleConnectionError(error);
       }
 
       // Close the connection.
@@ -1079,7 +957,7 @@
           ctx.getConnection()).getServerConnection();
       final SearchHandler handler = new SearchHandler(messageID,
           ctx.getConnection());
-      conn.handleSearch(messageID, request, handler, handler, handler);
+      conn.handleSearch(messageID, request, handler, handler);
     }
 
 
@@ -1102,6 +980,7 @@
           new UnsupportedMessageException(messageID, messageTag, messageBytes));
     }
   };
+
   private final int maxASN1ElementSize;
 
   private final LDAPReader ldapReader;
@@ -1139,7 +1018,7 @@
     {
       final ClientContextImpl clientContext = new ClientContextImpl(connection);
       final ServerConnection<Integer> serverConn = listener
-          .getConnectionFactory().accept(clientContext);
+          .getConnectionFactory().handleAccept(clientContext);
       clientContext.setServerConnection(serverConn);
       LDAP_CONNECTION_ATTR.set(connection, clientContext);
     }
@@ -1194,7 +1073,7 @@
 
 
   private synchronized void installFilter(final Connection<?> connection,
-      final com.sun.grizzly.filterchain.Filter filter)
+      final org.glassfish.grizzly.filterchain.Filter filter)
   {
     FilterChain filterChain = (FilterChain) connection.getProcessor();
     if (filter instanceof SSLFilter)
diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPWriter.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPWriter.java
index 4417714..feaabf7 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPWriter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPWriter.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package com.sun.opends.sdk.ldap;
@@ -204,7 +204,7 @@
           request));
     }
     encodeMessageHeader(writer, messageID);
-    writer.writeInteger(OP_TYPE_ABANDON_REQUEST, request.getMessageID());
+    writer.writeInteger(OP_TYPE_ABANDON_REQUEST, request.getRequestID());
     encodeMessageFooter(writer, request);
   }
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java b/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java
index 20e40cc..5e817ba 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/SASLDecoderTransformer.java
@@ -32,10 +32,10 @@
 import org.opends.sdk.ConnectionSecurityLayer;
 import org.opends.sdk.ErrorResultException;
 
-import com.sun.grizzly.*;
-import com.sun.grizzly.attributes.AttributeStorage;
-import com.sun.grizzly.memory.MemoryManager;
-import com.sun.grizzly.memory.MemoryUtils;
+import org.glassfish.grizzly.*;
+import org.glassfish.grizzly.attributes.AttributeStorage;
+import org.glassfish.grizzly.memory.Buffers;
+import org.glassfish.grizzly.memory.MemoryManager;
 
 
 
@@ -96,7 +96,7 @@
 
     try
     {
-      final Buffer output = MemoryUtils.wrap(memoryManager, bindContext.unwrap(
+      final Buffer output = Buffers.wrap(memoryManager, bindContext.unwrap(
           buffer, 0, len));
       return TransformationResult.createCompletedResult(output, input);
     }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java b/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java
index 3e182f9..4878ab7 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/SASLEncoderTransformer.java
@@ -32,10 +32,10 @@
 import org.opends.sdk.ConnectionSecurityLayer;
 import org.opends.sdk.ErrorResultException;
 
-import com.sun.grizzly.*;
-import com.sun.grizzly.attributes.AttributeStorage;
-import com.sun.grizzly.memory.MemoryManager;
-import com.sun.grizzly.memory.MemoryUtils;
+import org.glassfish.grizzly.*;
+import org.glassfish.grizzly.attributes.AttributeStorage;
+import org.glassfish.grizzly.memory.Buffers;
+import org.glassfish.grizzly.memory.MemoryManager;
 
 
 
@@ -96,7 +96,7 @@
 
     try
     {
-      final Buffer output = MemoryUtils.wrap(memoryManager, bindContext.wrap(
+      final Buffer output = Buffers.wrap(memoryManager, bindContext.wrap(
           buffer, 0, len));
       return TransformationResult.createCompletedResult(output, input);
     }
diff --git a/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java b/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java
index 5676d44..c08fa1c 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/SASLFilter.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package com.sun.opends.sdk.ldap;
@@ -31,8 +31,8 @@
 
 import org.opends.sdk.ConnectionSecurityLayer;
 
-import com.sun.grizzly.Buffer;
-import com.sun.grizzly.filterchain.AbstractCodecFilter;
+import org.glassfish.grizzly.Buffer;
+import org.glassfish.grizzly.filterchain.AbstractCodecFilter;
 
 
 
diff --git a/sdk/src/com/sun/opends/sdk/ldap/TimeoutChecker.java b/sdk/src/com/sun/opends/sdk/ldap/TimeoutChecker.java
index 7139e0b..bdae4a6 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/TimeoutChecker.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/TimeoutChecker.java
@@ -33,7 +33,7 @@
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 
-import com.sun.grizzly.utils.LinkedTransferQueue;
+import org.glassfish.grizzly.utils.LinkedTransferQueue;
 import com.sun.opends.sdk.util.StaticUtils;
 
 
diff --git a/sdk/src/com/sun/opends/sdk/tools/ArgumentParserConnectionFactory.java b/sdk/src/com/sun/opends/sdk/tools/ArgumentParserConnectionFactory.java
index 78f7329..48b7a78 100644
--- a/sdk/src/com/sun/opends/sdk/tools/ArgumentParserConnectionFactory.java
+++ b/sdk/src/com/sun/opends/sdk/tools/ArgumentParserConnectionFactory.java
@@ -644,6 +644,16 @@
 
 
 
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return connFactory.toString();
+  }
+
+
+
   private String getKDC() throws ArgumentException, CLIException
   {
     String value = null;
diff --git a/sdk/src/com/sun/opends/sdk/tools/AuthenticatedConnectionFactory.java b/sdk/src/com/sun/opends/sdk/tools/AuthenticatedConnectionFactory.java
index a331e0e..f66b335 100644
--- a/sdk/src/com/sun/opends/sdk/tools/AuthenticatedConnectionFactory.java
+++ b/sdk/src/com/sun/opends/sdk/tools/AuthenticatedConnectionFactory.java
@@ -50,13 +50,13 @@
  * operations with the exception of Bind requests. Attempts to perform a Bind
  * will result in an {@code UnsupportedOperationException}.
  * <p>
- * In addition, the returned connections support retrieval of the {@code
- * BindResult} returned from the initial Bind request, or last rebind.
+ * In addition, the returned connections support retrieval of the
+ * {@code BindResult} returned from the initial Bind request, or last rebind.
  * <p>
  * Support for connection re-authentication is provided through the
  * {@link #setRebindAllowed} method which, if set to {@code true}, causes
- * subsequent connections created using the factory to support the {@code
- * rebind} method.
+ * subsequent connections created using the factory to support the
+ * {@code rebind} method.
  * <p>
  * If the Bind request fails for some reason (e.g. invalid credentials), then
  * the connection attempt will fail and an {@code ErrorResultException} will be
@@ -388,8 +388,8 @@
      * @throws UnsupportedOperationException
      *           If this connection does not support rebind operations.
      * @throws IllegalStateException
-     *           If this connection has already been closed, i.e. if {@code
-     *           isClosed() == true}.
+     *           If this connection has already been closed, i.e. if
+     *           {@code isClosed() == true}.
      */
     public FutureResult<BindResult> rebind(
         final ResultHandler<? super BindResult> handler)
@@ -481,6 +481,20 @@
       return connection.searchSingleEntry(request, resultHandler);
     }
 
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+      StringBuilder builder = new StringBuilder();
+      builder.append("AuthenticatedConnection(");
+      builder.append(connection);
+      builder.append(')');
+      return builder.toString();
+    }
+
   }
 
 
@@ -559,8 +573,8 @@
      * @throws UnsupportedOperationException
      *           If this connection does not support rebind operations.
      * @throws IllegalStateException
-     *           If this connection has already been closed, i.e. if {@code
-     *           isClosed() == true}.
+     *           If this connection has already been closed, i.e. if
+     *           {@code isClosed() == true}.
      */
     public BindResult rebind() throws ErrorResultException,
         InterruptedException, UnsupportedOperationException,
@@ -730,4 +744,18 @@
     return this;
   }
 
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("AuthenticatedConnectionFactory(");
+    builder.append(String.valueOf(parentFactory));
+    builder.append(')');
+    return builder.toString();
+  }
+
 }
diff --git a/sdk/src/com/sun/opends/sdk/util/AsynchronousFutureResult.java b/sdk/src/com/sun/opends/sdk/util/AsynchronousFutureResult.java
new file mode 100644
index 0000000..0fe5a68
--- /dev/null
+++ b/sdk/src/com/sun/opends/sdk/util/AsynchronousFutureResult.java
@@ -0,0 +1,483 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
+ */
+
+package com.sun.opends.sdk.util;
+
+
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
+
+import org.opends.sdk.*;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code FutureResult}
+ * interface, to minimize the effort required to implement this interface.
+ * <p>
+ * This {@code FutureResult} implementation provides the following features:
+ * <ul>
+ * <li>The {@link #get} methods throw {@link ErrorResultException}s instead of
+ * the more generic {@code ExecutionException}s.
+ * <li>The {@link #get} methods never throw {@code CancellationException} since
+ * requests in this SDK can usually be cancelled via other external means (e.g.
+ * the {@code Cancel} extended operation) for which there are well defined error
+ * results. Therefore cancellation is always signalled by throwing a
+ * {@link CancelledResultException} in order to be consistent with other error
+ * results.
+ * <li>A {@link ResultHandler} can be provided to the constructor. The result
+ * handler will be invoked immediately after the result or error is received but
+ * before threads blocked on {@link #get} are released. More specifically,
+ * result handler invocation <i>happens-before</i> a call to {@link #get}.
+ * <b>NOTE:</b> a result handler which attempts to call {@link #get} will
+ * deadlock.
+ * <li>Sub-classes may choose to implement specific cancellation cleanup by
+ * implementing the {@link #handleCancelRequest} method.
+ * </ul>
+ *
+ * @param <M>
+ *          The type of result returned by this completion future.
+ */
+public class AsynchronousFutureResult<M> implements FutureResult<M>,
+    ResultHandler<M>
+{
+  @SuppressWarnings("serial")
+  private final class Sync extends AbstractQueuedSynchronizer
+  {
+    // State value representing the initial state before a result has
+    // been received.
+    private static final int WAITING = 0;
+
+    // State value representing that a result has been received and is
+    // being processed.
+    private static final int PENDING = 1;
+
+    // State value representing that the request was cancelled.
+    private static final int CANCELLED = 2;
+
+    // State value representing that the request has failed.
+    private static final int FAIL = 3;
+
+    // State value representing that the request has succeeded.
+    private static final int SUCCESS = 4;
+
+    // These do not need to be volatile since their values are published
+    // by updating the state after they are set and reading the state
+    // immediately before they are read.
+    private ErrorResultException errorResult = null;
+
+    private M result = null;
+
+
+
+    /**
+     * Allow all threads to acquire if future has completed.
+     */
+    @Override
+    protected int tryAcquireShared(final int ignore)
+    {
+      return innerIsDone() ? 1 : -1;
+    }
+
+
+
+    /**
+     * Signal that the future has completed and threads waiting on get() can be
+     * released.
+     */
+    @Override
+    protected boolean tryReleaseShared(final int finalState)
+    {
+      // Ensures that errorResult/result is published.
+      setState(finalState);
+      return true;
+    }
+
+
+
+    boolean innerCancel(final boolean mayInterruptIfRunning)
+    {
+      if (!isCancelable() || !setStatePending())
+      {
+        return false;
+      }
+
+      // Perform implementation defined cancellation.
+      ErrorResultException errorResult = handleCancelRequest(mayInterruptIfRunning);
+      if (errorResult == null)
+      {
+        final Result result = Responses
+            .newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED);
+        errorResult = ErrorResultException.wrap(result);
+      }
+      this.errorResult = errorResult;
+
+      try
+      {
+        // Invoke error result completion handler.
+        if (handler != null)
+        {
+          handler.handleErrorResult(errorResult);
+        }
+      }
+      finally
+      {
+        releaseShared(CANCELLED); // Publishes errorResult.
+      }
+
+      return true;
+    }
+
+
+
+    M innerGet() throws ErrorResultException, InterruptedException
+    {
+      acquireSharedInterruptibly(0);
+      return get0();
+    }
+
+
+
+    M innerGet(final long nanosTimeout) throws ErrorResultException,
+        TimeoutException, InterruptedException
+    {
+      if (!tryAcquireSharedNanos(0, nanosTimeout))
+      {
+        throw new TimeoutException();
+      }
+      else
+      {
+        return get0();
+      }
+    }
+
+
+
+    boolean innerIsCancelled()
+    {
+      return getState() == CANCELLED;
+    }
+
+
+
+    boolean innerIsDone()
+    {
+      return getState() > 1;
+    }
+
+
+
+    void innerSetErrorResult(final ErrorResultException errorResult)
+    {
+      if (setStatePending())
+      {
+        this.errorResult = errorResult;
+
+        try
+        {
+          // Invoke error result completion handler.
+          if (handler != null)
+          {
+            handler.handleErrorResult(errorResult);
+          }
+        }
+        finally
+        {
+          releaseShared(FAIL); // Publishes errorResult.
+        }
+      }
+    }
+
+
+
+    void innerSetResult(final M result)
+    {
+      if (setStatePending())
+      {
+        this.result = result;
+
+        try
+        {
+          // Invoke result completion handler.
+          if (handler != null)
+          {
+            handler.handleResult(result);
+          }
+        }
+        finally
+        {
+          releaseShared(SUCCESS); // Publishes result.
+        }
+      }
+    }
+
+
+
+    private M get0() throws ErrorResultException
+    {
+      if (errorResult != null)
+      {
+        // State must be FAILED or CANCELLED.
+        throw errorResult;
+      }
+      else
+      {
+        // State must be SUCCESS.
+        return result;
+      }
+    }
+
+
+
+    private boolean setStatePending()
+    {
+      for (;;)
+      {
+        final int s = getState();
+        if (s != WAITING)
+        {
+          return false;
+        }
+        if (compareAndSetState(s, PENDING))
+        {
+          return true;
+        }
+      }
+    }
+  }
+
+
+
+  private final Sync sync = new Sync();
+
+  private final ResultHandler<? super M> handler;
+
+  private final int requestID;
+
+
+
+  /**
+   * Creates a new asynchronous future result with the provided result handler
+   * and a request ID of -1.
+   *
+   * @param handler
+   *          A result handler which will be forwarded the result or error when
+   *          it arrives, may be {@code null}.
+   */
+  public AsynchronousFutureResult(final ResultHandler<? super M> handler)
+  {
+    this(handler, -1);
+  }
+
+
+
+  /**
+   * Creates a new asynchronous future result with the provided result handler
+   * and request ID.
+   *
+   * @param handler
+   *          A result handler which will be forwarded the result or error when
+   *          it arrives, may be {@code null}.
+   * @param requestID
+   *          The request ID which will be returned by the default
+   *          implementation of {@link #getRequestID}.
+   */
+  public AsynchronousFutureResult(final ResultHandler<? super M> handler,
+      final int requestID)
+  {
+    this.handler = handler;
+    this.requestID = requestID;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final boolean cancel(final boolean mayInterruptIfRunning)
+  {
+    return sync.innerCancel(mayInterruptIfRunning);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final M get() throws ErrorResultException, InterruptedException
+  {
+    return sync.innerGet();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final M get(final long timeout, final TimeUnit unit)
+      throws ErrorResultException, TimeoutException, InterruptedException
+  {
+    return sync.innerGet(unit.toNanos(timeout));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation returns the request ID passed in during
+   * construction, or -1 if none was provided.
+   */
+  @Override
+  public int getRequestID()
+  {
+    return requestID;
+  }
+
+
+
+  /**
+   * Sets the error result associated with this future. If ({@code isDone() ==
+   * true}) then the error result will be ignored, otherwise the result handler
+   * will be invoked if one was provided and, on return, any threads waiting on
+   * {@link #get} will be released and the provided error result will be thrown.
+   *
+   * @param errorResult
+   *          The error result.
+   */
+  @Override
+  public final void handleErrorResult(final ErrorResultException errorResult)
+  {
+    sync.innerSetErrorResult(errorResult);
+  }
+
+
+
+  /**
+   * Sets the result associated with this future. If ({@code isDone() == true})
+   * then the result will be ignored, otherwise the result handler will be
+   * invoked if one was provided and, on return, any threads waiting on
+   * {@link #get} will be released and the provided result will be returned.
+   *
+   * @param result
+   *          The result.
+   */
+  @Override
+  public final void handleResult(final M result)
+  {
+    sync.innerSetResult(result);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final boolean isCancelled()
+  {
+    return sync.innerIsCancelled();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final boolean isDone()
+  {
+    return sync.innerIsDone();
+  }
+
+
+
+  /**
+   * Invoked when {@link #cancel} is called and {@code isDone() == false} and
+   * immediately before any threads waiting on {@link #get} are released.
+   * Implementations may choose to return a custom error result if needed or
+   * return {@code null} if the following default error result is acceptable:
+   *
+   * <pre>
+   * Result result = Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED);
+   * </pre>
+   *
+   * In addition, implementations may perform other cleanup, for example, by
+   * issuing an LDAP abandon request. The default implementation is to do
+   * nothing.
+   *
+   * @param mayInterruptIfRunning
+   *          {@code true} if the thread executing executing the response
+   *          handler should be interrupted; otherwise, in-progress response
+   *          handlers are allowed to complete.
+   * @return The custom error result, or {@code null} if the default is
+   *         acceptable.
+   */
+  protected ErrorResultException handleCancelRequest(
+      final boolean mayInterruptIfRunning)
+  {
+    // Do nothing by default.
+    return null;
+  }
+
+
+
+  /**
+   * Indicates whether this future result can be canceled.
+   *
+   * @return {@code true} if this future result is cancelable or {@code false}
+   *         otherwise.
+   */
+  protected boolean isCancelable()
+  {
+    // Return true by default.
+    return true;
+  }
+
+
+
+  /**
+   * Appends a string representation of this future's state to the provided
+   * builder.
+   *
+   * @param sb
+   *          The string builder.
+   */
+  protected void toString(final StringBuilder sb)
+  {
+    sb.append(" state = ");
+    sb.append(sync);
+  }
+
+}
diff --git a/sdk/src/com/sun/opends/sdk/util/RecursiveFutureResult.java b/sdk/src/com/sun/opends/sdk/util/RecursiveFutureResult.java
index e719784..0857e0a 100644
--- a/sdk/src/com/sun/opends/sdk/util/RecursiveFutureResult.java
+++ b/sdk/src/com/sun/opends/sdk/util/RecursiveFutureResult.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package com.sun.opends.sdk.util;
@@ -54,7 +54,7 @@
 public abstract class RecursiveFutureResult<M, N> implements FutureResult<N>,
     ResultHandler<M>
 {
-  private final class FutureResultImpl extends AbstractFutureResult<N>
+  private final class FutureResultImpl extends AsynchronousFutureResult<N>
   {
     private FutureResultImpl(final ResultHandler<? super N> handler)
     {
diff --git a/sdk/src/com/sun/opends/sdk/util/StaticUtils.java b/sdk/src/com/sun/opends/sdk/util/StaticUtils.java
index e40902c..64cdabb 100644
--- a/sdk/src/com/sun/opends/sdk/util/StaticUtils.java
+++ b/sdk/src/com/sun/opends/sdk/util/StaticUtils.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package com.sun.opends.sdk.util;
@@ -35,6 +35,10 @@
 import java.lang.reflect.InvocationTargetException;
 import java.text.ParseException;
 import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.zip.DataFormatException;
@@ -67,6 +71,10 @@
   private static final TimeZone TIME_ZONE_UTC_OBJ = TimeZone
       .getTimeZone(TIME_ZONE_UTC);
 
+  private static ScheduledExecutorService defaultScheduler = null;
+
+  private static final Object DEFAULT_SCHEDULER_LOCK = new Object();
+
 
 
   /**
@@ -1394,6 +1402,27 @@
 
 
   /**
+   * Returns the default scheduler which should be used by the SDK.
+   *
+   * @return The default scheduler.
+   */
+  public static ScheduledExecutorService getDefaultScheduler()
+  {
+    synchronized (DEFAULT_SCHEDULER_LOCK)
+    {
+      if (defaultScheduler == null)
+      {
+        final ThreadFactory factory = newThreadFactory(null,
+            "OpenDS SDK Default Scheduler", true);
+        defaultScheduler = Executors.newSingleThreadScheduledExecutor(factory);
+      }
+    }
+    return defaultScheduler;
+  }
+
+
+
+  /**
    * Retrieves the best human-readable message for the provided exception. For
    * exceptions defined in the OpenDS project, it will attempt to use the
    * message (combining it with the message ID if available). For some
@@ -1711,6 +1740,83 @@
 
 
   /**
+   * Returns a string whose content is the string representation of the objects
+   * contained in the provided collection concatenated together using the
+   * provided separator.
+   *
+   * @param c
+   *          The collection whose elements are to be joined.
+   * @param separator
+   *          The separator string.
+   * @return A string whose content is the string representation of the objects
+   *         contained in the provided collection concatenated together using
+   *         the provided separator.
+   * @throws NullPointerException
+   *           If {@code c} or {@code separator} were {@code null}.
+   */
+  public static String joinCollection(Collection<?> c, String separator)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(c, separator);
+
+    switch (c.size())
+    {
+    case 0:
+      return "";
+    case 1:
+      return String.valueOf(c.iterator().next());
+    default:
+      StringBuilder builder = new StringBuilder();
+      Iterator<?> i = c.iterator();
+      builder.append(i.next());
+      while (i.hasNext())
+      {
+        builder.append(separator);
+        builder.append(i.next());
+      }
+      String s = builder.toString();
+      return s;
+    }
+  }
+
+
+
+
+  /**
+   * Creates a new thread factory which will create threads using the specified
+   * thread group, naming template, and daemon status.
+   *
+   * @param group
+   *          The thread group, which may be {@code null}.
+   * @param nameTemplate
+   *          The thread name format string which may contain a "%d" format
+   *          option which will be substituted with the thread count.
+   * @param isDaemon
+   *          Indicates whether or not threads should be daemon threads.
+   * @return The new thread factory.
+   */
+  public static ThreadFactory newThreadFactory(final ThreadGroup group,
+      final String nameTemplate, final boolean isDaemon)
+  {
+    return new ThreadFactory()
+    {
+      private final AtomicInteger count = new AtomicInteger();
+
+
+
+      public Thread newThread(Runnable r)
+      {
+        final String name = String
+            .format(nameTemplate, count.getAndIncrement());
+        final Thread t = new Thread(group, r, name);
+        t.setDaemon(isDaemon);
+        return t;
+      }
+    };
+  }
+
+
+  /**
    * Returns a string representation of the contents of the provided byte
    * sequence using hexadecimal characters and a space between each byte.
    *
diff --git a/sdk/src/org/opends/sdk/AbstractAsynchronousConnection.java b/sdk/src/org/opends/sdk/AbstractAsynchronousConnection.java
index 56ec077..685567d 100644
--- a/sdk/src/org/opends/sdk/AbstractAsynchronousConnection.java
+++ b/sdk/src/org/opends/sdk/AbstractAsynchronousConnection.java
@@ -44,8 +44,8 @@
 
 
 /**
- * This class provides a skeletal implementation of the {@code
- * AsynchronousConnection} interface, to minimize the effort required to
+ * This class provides a skeletal implementation of the
+ * {@code AsynchronousConnection} interface, to minimize the effort required to
  * implement this interface.
  */
 public abstract class AbstractAsynchronousConnection implements
@@ -394,9 +394,8 @@
    * {@inheritDoc}
    */
   public FutureResult<Result> search(final SearchRequest request,
-      final SearchResultHandler handler)
-      throws UnsupportedOperationException, IllegalStateException,
-      NullPointerException
+      final SearchResultHandler handler) throws UnsupportedOperationException,
+      IllegalStateException, NullPointerException
   {
     return search(request, handler, null);
   }
@@ -417,4 +416,15 @@
     innerFuture.setResultFuture(future);
     return innerFuture;
   }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * Sub-classes should provide an implementation which returns an appropriate
+   * description of the connection which may be used for debugging purposes.
+   */
+  public abstract String toString();
+
 }
diff --git a/sdk/src/org/opends/sdk/AbstractConnection.java b/sdk/src/org/opends/sdk/AbstractConnection.java
index c56e848..d4c8ea7 100644
--- a/sdk/src/org/opends/sdk/AbstractConnection.java
+++ b/sdk/src/org/opends/sdk/AbstractConnection.java
@@ -481,4 +481,14 @@
     return searchSingleEntry(request);
   }
 
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * Sub-classes should provide an implementation which returns an appropriate
+   * description of the connection which may be used for debugging purposes.
+   */
+  public abstract String toString();
+
 }
diff --git a/sdk/src/org/opends/sdk/AbstractConnectionFactory.java b/sdk/src/org/opends/sdk/AbstractConnectionFactory.java
index 7ae9ee2..a951488 100644
--- a/sdk/src/org/opends/sdk/AbstractConnectionFactory.java
+++ b/sdk/src/org/opends/sdk/AbstractConnectionFactory.java
@@ -80,4 +80,11 @@
   {
     return getAsynchronousConnection(null).get().getSynchronousConnection();
   }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract String toString();
 }
diff --git a/sdk/src/org/opends/sdk/AbstractLoadBalancingAlgorithm.java b/sdk/src/org/opends/sdk/AbstractLoadBalancingAlgorithm.java
new file mode 100644
index 0000000..75826a1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractLoadBalancingAlgorithm.java
@@ -0,0 +1,428 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+
+import org.opends.sdk.responses.Responses;
+
+import com.sun.opends.sdk.util.AsynchronousFutureResult;
+import com.sun.opends.sdk.util.StaticUtils;
+import com.sun.opends.sdk.util.Validator;
+
+
+
+/**
+ * An abstract load balancing algorithm providing monitoring and failover
+ * capabilities.
+ * <p>
+ * Implementations should override the method
+ * {@code getInitialConnectionFactoryIndex()} in order to provide the policy for
+ * selecting the first connection factory to use for each connection request.
+ */
+abstract class AbstractLoadBalancingAlgorithm implements LoadBalancingAlgorithm
+{
+  private final class MonitoredConnectionFactory extends
+      AbstractConnectionFactory implements
+      ResultHandler<AsynchronousConnection>
+  {
+
+    private final ConnectionFactory factory;
+
+    private final AtomicBoolean isOperational = new AtomicBoolean(true);
+
+    private volatile FutureResult<?> pendingConnectFuture = null;
+
+    private final int index;
+
+
+
+    private MonitoredConnectionFactory(final ConnectionFactory factory,
+        final int index)
+    {
+      this.factory = factory;
+      this.index = index;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FutureResult<AsynchronousConnection> getAsynchronousConnection(
+        final ResultHandler<? super AsynchronousConnection> resultHandler)
+    {
+      final AsynchronousFutureResult<AsynchronousConnection> future =
+        new AsynchronousFutureResult<AsynchronousConnection>(resultHandler);
+
+      final ResultHandler<AsynchronousConnection> failoverHandler =
+        new ResultHandler<AsynchronousConnection>()
+      {
+        @Override
+        public void handleErrorResult(final ErrorResultException error)
+        {
+          // Attempt failed - try next factory.
+          notifyOffline(error);
+
+          final int nextIndex = (index + 1) % monitoredFactories.size();
+          try
+          {
+            final MonitoredConnectionFactory nextFactory =
+              getMonitoredConnectionFactory(nextIndex);
+            nextFactory.getAsynchronousConnection(future);
+          }
+          catch (final ErrorResultException e)
+          {
+            future.handleErrorResult(e);
+          }
+        }
+
+
+
+        @Override
+        public void handleResult(final AsynchronousConnection result)
+        {
+          notifyOnline();
+          future.handleResult(result);
+        }
+      };
+
+      factory.getAsynchronousConnection(failoverHandler);
+      return future;
+    }
+
+
+
+    /**
+     * Handle monitoring connection request failure.
+     */
+    @Override
+    public void handleErrorResult(final ErrorResultException error)
+    {
+      notifyOffline(error);
+    }
+
+
+
+    /**
+     * Handle monitoring connection request success.
+     */
+    @Override
+    public void handleResult(final AsynchronousConnection connection)
+    {
+      notifyOnline();
+
+      // The connection is not going to be used, so close it immediately.
+      connection.close();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString()
+    {
+      return factory.toString();
+    }
+
+
+
+    /**
+     * Attempt to connect to the factory if it is offline and there is no
+     * pending monitoring request.
+     */
+    private synchronized void checkIfAvailable()
+    {
+      if (!isOperational.get()
+          && (pendingConnectFuture == null || pendingConnectFuture.isDone()))
+      {
+        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+        {
+          StaticUtils.DEBUG_LOG.finest(String
+              .format("Attempting connect on factory " + this));
+        }
+        pendingConnectFuture = factory.getAsynchronousConnection(this);
+      }
+    }
+
+
+
+    private void notifyOffline(final ErrorResultException error)
+    {
+      if (isOperational.getAndSet(false))
+      {
+        // Transition from online to offline.
+        synchronized (stateLock)
+        {
+          offlineFactoriesCount++;
+          if (offlineFactoriesCount == 1)
+          {
+            // Enable monitoring.
+            monitoringFuture = scheduler.scheduleWithFixedDelay(
+                new MonitorThread(), 0, monitoringInterval,
+                monitoringIntervalTimeUnit);
+          }
+        }
+
+        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+        {
+          StaticUtils.DEBUG_LOG.fine(String.format("Connection factory "
+              + factory + " is no longer operational: " + error.getMessage()));
+        }
+      }
+    }
+
+
+
+    private void notifyOnline()
+    {
+      if (!isOperational.getAndSet(true))
+      {
+        // Transition from offline to online.
+        synchronized (stateLock)
+        {
+          offlineFactoriesCount--;
+          if (offlineFactoriesCount == 0)
+          {
+            monitoringFuture.cancel(false);
+            monitoringFuture = null;
+          }
+        }
+
+        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+        {
+          StaticUtils.DEBUG_LOG.fine(String.format("Connection factory "
+              + factory + " is now operational"));
+        }
+      }
+    }
+  }
+
+
+
+  private final class MonitorThread implements Runnable
+  {
+    private MonitorThread()
+    {
+      // Nothing to do.
+    }
+
+
+
+    @Override
+    public void run()
+    {
+      for (final MonitoredConnectionFactory factory : monitoredFactories)
+      {
+        factory.checkIfAvailable();
+      }
+    }
+  }
+
+
+
+  private final List<MonitoredConnectionFactory> monitoredFactories;
+
+  private final ScheduledExecutorService scheduler;
+
+  private final Object stateLock = new Object();
+
+  // Guarded by stateLock.
+  private int offlineFactoriesCount = 0;
+
+  private final long monitoringInterval;
+
+  private final TimeUnit monitoringIntervalTimeUnit;
+
+  // Guarded by stateLock.
+  private ScheduledFuture<?> monitoringFuture;
+
+
+
+  /**
+   * Creates a new abstract load balancing algorithm.
+   *
+   * @param factories
+   *          The connection factories.
+   */
+  AbstractLoadBalancingAlgorithm(final Collection<ConnectionFactory> factories)
+  {
+    this(factories, StaticUtils.getDefaultScheduler());
+  }
+
+
+
+  /**
+   * Creates a new abstract load balancing algorithm.
+   *
+   * @param factories
+   *          The connection factories.
+   * @param scheduler
+   *          The scheduler which should for periodically monitoring dead
+   *          connection factories to see if they are usable again.
+   */
+  AbstractLoadBalancingAlgorithm(final Collection<ConnectionFactory> factories,
+      final ScheduledExecutorService scheduler)
+  {
+    this(factories, StaticUtils.getDefaultScheduler(), 10, TimeUnit.SECONDS);
+  }
+
+
+
+  /**
+   * Creates a new abstract load balancing algorithm.
+   *
+   * @param factories
+   *          The connection factories.
+   * @param scheduler
+   *          The scheduler which should for periodically monitoring dead
+   *          connection factories to see if they are usable again.
+   * @param interval
+   *          The interval between attempts to poll offline factories.
+   * @param unit
+   *          The time unit for the interval between attempts to poll offline
+   *          factories.
+   */
+  AbstractLoadBalancingAlgorithm(final Collection<ConnectionFactory> factories,
+      final ScheduledExecutorService scheduler, final long interval,
+      final TimeUnit unit)
+  {
+    Validator.ensureNotNull(factories, scheduler, unit);
+
+    this.monitoredFactories = new ArrayList<MonitoredConnectionFactory>(
+        factories.size());
+    int i = 0;
+    for (final ConnectionFactory f : factories)
+    {
+      this.monitoredFactories.add(new MonitoredConnectionFactory(f, i++));
+    }
+    this.scheduler = scheduler;
+    this.monitoringInterval = interval;
+    this.monitoringIntervalTimeUnit = unit;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final ConnectionFactory getConnectionFactory()
+      throws ErrorResultException
+  {
+    final int index = getInitialConnectionFactoryIndex();
+    return getMonitoredConnectionFactory(index);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append(getAlgorithmName());
+    builder.append('(');
+    boolean isFirst = true;
+    for (final ConnectionFactory factory : monitoredFactories)
+    {
+      if (!isFirst)
+      {
+        builder.append(',');
+      }
+      else
+      {
+        isFirst = false;
+      }
+      builder.append(factory);
+    }
+    builder.append(')');
+    return builder.toString();
+  }
+
+
+
+  /**
+   * Returns the name of this load balancing algorithm.
+   *
+   * @return The name of this load balancing algorithm.
+   */
+  abstract String getAlgorithmName();
+
+
+
+  /**
+   * Returns the index of the first connection factory which should be used in
+   * order to satisfy the next connection request.
+   *
+   * @return The index of the first connection factory which should be used in
+   *         order to satisfy the next connection request.
+   */
+  abstract int getInitialConnectionFactoryIndex();
+
+
+
+  // Return the first factory after index which is operational.
+  private MonitoredConnectionFactory getMonitoredConnectionFactory(
+      final int initialIndex) throws ErrorResultException
+  {
+    int index = initialIndex;
+    final int maxIndex = monitoredFactories.size();
+    do
+    {
+      final MonitoredConnectionFactory factory = monitoredFactories.get(index);
+      if (factory.isOperational.get())
+      {
+        return factory;
+      }
+      index = (index + 1) % maxIndex;
+    }
+    while (index != initialIndex);
+
+    // All factories are offline so give up. We could have a
+    // configurable policy here such as waiting indefinitely, or for a
+    // configurable timeout period.
+    throw ErrorResultException.wrap(Responses.newResult(
+        ResultCode.CLIENT_SIDE_CONNECT_ERROR).setDiagnosticMessage(
+        "No operational connection factories available"));
+  }
+}
diff --git a/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java b/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java
index 1027917..425e8e0 100644
--- a/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java
+++ b/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java
@@ -389,6 +389,20 @@
       return connection.searchSingleEntry(request, resultHandler);
     }
 
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+      StringBuilder builder = new StringBuilder();
+      builder.append("AuthenticatedConnection(");
+      builder.append(connection);
+      builder.append(')');
+      return builder.toString();
+    }
+
   }
 
 
@@ -502,4 +516,18 @@
     return future.futureBindResult;
   }
 
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("AuthenticatedConnectionFactory(");
+    builder.append(String.valueOf(parentFactory));
+    builder.append(')');
+    return builder.toString();
+  }
+
 }
diff --git a/sdk/src/org/opends/sdk/ConnectionPool.java b/sdk/src/org/opends/sdk/ConnectionPool.java
index 1789d1d..a7b5f75 100644
--- a/sdk/src/org/opends/sdk/ConnectionPool.java
+++ b/sdk/src/org/opends/sdk/ConnectionPool.java
@@ -38,7 +38,7 @@
 import org.opends.sdk.responses.*;
 import org.opends.sdk.schema.Schema;
 
-import com.sun.opends.sdk.util.AbstractFutureResult;
+import com.sun.opends.sdk.util.AsynchronousFutureResult;
 import com.sun.opends.sdk.util.CompletedFutureResult;
 import com.sun.opends.sdk.util.StaticUtils;
 
@@ -51,24 +51,13 @@
 {
   // Future used for waiting for pooled connections to become available.
   private static final class FuturePooledConnection extends
-      AbstractFutureResult<AsynchronousConnection>
+      AsynchronousFutureResult<AsynchronousConnection>
   {
     private FuturePooledConnection(
         final ResultHandler<? super AsynchronousConnection> handler)
     {
       super(handler);
     }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public int getRequestID()
-    {
-      return -1;
-    }
-
   }
 
 
@@ -289,8 +278,7 @@
 
 
 
-    public void handleUnsolicitedNotification(
-        final ExtendedResult notification)
+    public void handleUnsolicitedNotification(final ExtendedResult notification)
     {
       // Ignore
     }
@@ -567,6 +555,20 @@
       }
       return connection.searchSingleEntry(request, resultHandler);
     }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+      StringBuilder builder = new StringBuilder();
+      builder.append("PooledConnection(");
+      builder.append(connection);
+      builder.append(')');
+      return builder.toString();
+    }
   }
 
 
@@ -738,7 +740,22 @@
       handler.handleResult(pooledConnection);
     }
     return new CompletedFutureResult<AsynchronousConnection>(pooledConnection);
+  }
 
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("ConnectionPool(");
+    builder.append(String.valueOf(connectionFactory));
+    builder.append(',');
+    builder.append(poolSize);
+    builder.append(')');
+    return builder.toString();
   }
 
 
diff --git a/sdk/src/org/opends/sdk/Connections.java b/sdk/src/org/opends/sdk/Connections.java
index 9d9fec6..74813f0 100644
--- a/sdk/src/org/opends/sdk/Connections.java
+++ b/sdk/src/org/opends/sdk/Connections.java
@@ -22,14 +22,13 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk;
 
 
 
-import java.util.Collection;
 import java.util.concurrent.TimeUnit;
 
 import org.opends.sdk.requests.BindRequest;
@@ -104,69 +103,6 @@
 
 
   /**
-   * Creates a new connection factory providing fault tolerance across multiple
-   * underlying connection factories.
-   * <p>
-   * The returned fail-over connection factory forwards connection requests to
-   * one of the provided connection factories. If the request fails for some
-   * reason (for example, due to network failure), then the fail-over connection
-   * factory switches to another connection faction, repeating the process until
-   * the connection request succeeds, or until all the connection factories are
-   * determined to be unavailable, in which case the connection request will
-   * fail.
-   * <p>
-   * The implementation periodically attempts to connect to failed connection
-   * factories in order to determine if they have become available again.
-   *
-   * @param factories
-   *          The connection factories which will be used for fail-over.
-   * @return The new fail-over connection factory.
-   * @throws NullPointerException
-   *           If {@code factories} was {@code null}.
-   */
-  public static ConnectionFactory newFailoverConnectionFactory(
-      final Collection<ConnectionFactory> factories)
-      throws NullPointerException
-  {
-    final FailoverLoadBalancingAlgorithm algorithm = new FailoverLoadBalancingAlgorithm(
-        factories);
-    return new LoadBalancingConnectionFactory(algorithm);
-  }
-
-
-
-  /**
-   * Creates a new connection factory providing fault tolerance across multiple
-   * underlying connection factories.
-   * <p>
-   * The returned fail-over connection factory forwards connection requests to
-   * one of the provided connection factories. If the request fails for some
-   * reason (for example, due to network failure), then the fail-over connection
-   * factory switches to another connection faction, repeating the process until
-   * the connection request succeeds, or until all the connection factories are
-   * determined to be unavailable, in which case the connection request will
-   * fail.
-   * <p>
-   * The implementation periodically attempts to connect to failed connection
-   * factories in order to determine if they have become available again.
-   *
-   * @param factories
-   *          The connection factories which will be used for fail-over.
-   * @return The new fail-over connection factory.
-   * @throws NullPointerException
-   *           If {@code factories} was {@code null}.
-   */
-  public static ConnectionFactory newFailoverConnectionFactory(
-      final ConnectionFactory... factories) throws NullPointerException
-  {
-    final FailoverLoadBalancingAlgorithm algorithm = new FailoverLoadBalancingAlgorithm(
-        factories);
-    return new LoadBalancingConnectionFactory(algorithm);
-  }
-
-
-
-  /**
    * Creates a new connection factory which will create connections using the
    * provided connection factory and periodically probe any created connections
    * in order to detect that they are still alive.
@@ -230,10 +166,21 @@
 
 
   /**
-   * Creates a new connection factory which will create internal connections
-   * using the provided server connection factory. The server connection will be
-   * provided with request context whose value is unique integer associated with
-   * the request.
+   * Creates a new connection factory which binds internal client connections to
+   * {@link ServerConnection}s created using the provided
+   * {@link ServerConnectionFactory}.
+   * <p>
+   * When processing requests, {@code ServerConnection} implementations are
+   * passed an integer as the first parameter. This integer represents a pseudo
+   * {@code requestID} which is incremented for each successive internal request
+   * on a per client connection basis. The request ID may be useful for logging
+   * purposes.
+   * <p>
+   * An internal connection factory does not require {@code ServerConnection}
+   * implementations to return a result when processing requests. However, it is
+   * recommended that implementations do always return results even for
+   * abandoned requests. This is because application client threads may block
+   * indefinitely waiting for results.
    *
    * @param <C>
    *          The type of client context.
@@ -255,6 +202,81 @@
 
 
 
+  /**
+   * Creates a new load balancer which will obtain connections using the
+   * provided load balancing algorithm.
+   *
+   * @param algorithm
+   *          The load balancing algorithm which will be used to obtain the next
+   * @return The new load balancer.
+   * @throws NullPointerException
+   *           If {@code algorithm} was {@code null}.
+   */
+  public static ConnectionFactory newLoadBalancer(
+      final LoadBalancingAlgorithm algorithm) throws NullPointerException
+  {
+    return new LoadBalancer(algorithm);
+  }
+
+
+
+  /**
+   * Creates a new connection factory which forwards connection requests to the
+   * provided factory, but whose {@code toString} method will always return
+   * {@code name}.
+   * <p>
+   * This method may be useful for debugging purposes in order to more easily
+   * identity connection factories.
+   *
+   * @param factory
+   *          The connection factory to be named.
+   * @param name
+   *          The name of the connection factory.
+   * @return The named connection factory.
+   * @throws NullPointerException
+   *           If {@code factory} or {@code name} was {@code null}.
+   */
+  public static ConnectionFactory newNamedConnectionFactory(
+      final ConnectionFactory factory, final String name)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(factory, name);
+
+    return new ConnectionFactory()
+    {
+
+      @Override
+      public FutureResult<AsynchronousConnection> getAsynchronousConnection(
+          final ResultHandler<? super AsynchronousConnection> handler)
+      {
+        return factory.getAsynchronousConnection(handler);
+      }
+
+
+
+      @Override
+      public Connection getConnection() throws ErrorResultException,
+          InterruptedException
+      {
+        return factory.getConnection();
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public String toString()
+      {
+        return name;
+      }
+
+    };
+  }
+
+
+
   // Prevent instantiation.
   private Connections()
   {
diff --git a/sdk/src/org/opends/sdk/Entries.java b/sdk/src/org/opends/sdk/Entries.java
index 14fbaae..55a17da 100644
--- a/sdk/src/org/opends/sdk/Entries.java
+++ b/sdk/src/org/opends/sdk/Entries.java
@@ -30,14 +30,21 @@
 
 
 import java.util.Collection;
+import java.util.Iterator;
+
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.requests.Requests;
 
 import com.sun.opends.sdk.util.Function;
 import com.sun.opends.sdk.util.Iterables;
+import com.sun.opends.sdk.util.Validator;
 
 
 
 /**
  * This class contains methods for creating and manipulating entries.
+ *
+ * @see Entry
  */
 public final class Entries
 {
@@ -58,6 +65,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public boolean addAttribute(final Attribute attribute)
         throws UnsupportedOperationException, NullPointerException
     {
@@ -69,6 +77,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public boolean addAttribute(final Attribute attribute,
         final Collection<ByteString> duplicateValues)
         throws UnsupportedOperationException, NullPointerException
@@ -81,6 +90,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public Entry addAttribute(final String attributeDescription,
         final Object... values) throws LocalizedIllegalArgumentException,
         UnsupportedOperationException, NullPointerException
@@ -90,6 +100,7 @@
 
 
 
+    @Override
     public Entry clearAttributes() throws UnsupportedOperationException
     {
       throw new UnsupportedOperationException();
@@ -97,6 +108,7 @@
 
 
 
+    @Override
     public boolean containsAttribute(final Attribute attribute,
         final Collection<ByteString> missingValues) throws NullPointerException
     {
@@ -105,6 +117,7 @@
 
 
 
+    @Override
     public boolean containsAttribute(final String attributeDescription,
         final Object... values) throws LocalizedIllegalArgumentException,
         NullPointerException
@@ -125,19 +138,21 @@
 
 
 
+    @Override
     public Iterable<Attribute> getAllAttributes()
     {
-      return Iterables.unmodifiable(Iterables.transform(entry
-          .getAllAttributes(), UNMODIFIABLE_ATTRIBUTE_FUNCTION));
+      return Iterables.unmodifiable(Iterables.transform(
+          entry.getAllAttributes(), UNMODIFIABLE_ATTRIBUTE_FUNCTION));
     }
 
 
 
+    @Override
     public Iterable<Attribute> getAllAttributes(
         final AttributeDescription attributeDescription)
     {
-      return Iterables.unmodifiable(Iterables.transform(entry
-          .getAllAttributes(attributeDescription),
+      return Iterables.unmodifiable(Iterables.transform(
+          entry.getAllAttributes(attributeDescription),
           UNMODIFIABLE_ATTRIBUTE_FUNCTION));
     }
 
@@ -146,17 +161,19 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public Iterable<Attribute> getAllAttributes(
         final String attributeDescription)
         throws LocalizedIllegalArgumentException, NullPointerException
     {
-      return Iterables.unmodifiable(Iterables.transform(entry
-          .getAllAttributes(attributeDescription),
+      return Iterables.unmodifiable(Iterables.transform(
+          entry.getAllAttributes(attributeDescription),
           UNMODIFIABLE_ATTRIBUTE_FUNCTION));
     }
 
 
 
+    @Override
     public Attribute getAttribute(
         final AttributeDescription attributeDescription)
     {
@@ -176,6 +193,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public Attribute getAttribute(final String attributeDescription)
         throws LocalizedIllegalArgumentException, NullPointerException
     {
@@ -192,6 +210,7 @@
 
 
 
+    @Override
     public int getAttributeCount()
     {
       return entry.getAttributeCount();
@@ -202,6 +221,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public DN getName()
     {
       return entry.getName();
@@ -223,6 +243,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public boolean removeAttribute(final Attribute attribute,
         final Collection<ByteString> missingValues)
         throws UnsupportedOperationException, NullPointerException
@@ -232,6 +253,7 @@
 
 
 
+    @Override
     public boolean removeAttribute(
         final AttributeDescription attributeDescription)
         throws UnsupportedOperationException, NullPointerException
@@ -244,6 +266,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public Entry removeAttribute(final String attributeDescription,
         final Object... values) throws LocalizedIllegalArgumentException,
         UnsupportedOperationException, NullPointerException
@@ -256,6 +279,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public boolean replaceAttribute(final Attribute attribute)
         throws UnsupportedOperationException, NullPointerException
     {
@@ -267,6 +291,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public Entry replaceAttribute(final String attributeDescription,
         final Object... values) throws LocalizedIllegalArgumentException,
         UnsupportedOperationException, NullPointerException
@@ -276,6 +301,7 @@
 
 
 
+    @Override
     public Entry setName(final DN dn) throws UnsupportedOperationException,
         NullPointerException
     {
@@ -287,6 +313,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public Entry setName(final String dn)
         throws LocalizedIllegalArgumentException,
         UnsupportedOperationException, NullPointerException
@@ -309,10 +336,11 @@
 
 
 
-  private static final Function<Attribute, Attribute, Void>
-    UNMODIFIABLE_ATTRIBUTE_FUNCTION = new Function<Attribute, Attribute, Void>()
+  private static final Function<Attribute, Attribute, Void> UNMODIFIABLE_ATTRIBUTE_FUNCTION =
+    new Function<Attribute, Attribute, Void>()
   {
 
+    @Override
     public Attribute apply(final Attribute value, final Void p)
     {
       return Attributes.unmodifiableAttribute(value);
@@ -323,8 +351,144 @@
 
 
   /**
+   * Creates a new modify request containing a list of modifications which can
+   * be used to transform {@code fromEntry} into entry {@code toEntry}.
+   * <p>
+   * The modify request is reversible: it will contain only modifications of
+   * type {@link ModificationType#ADD ADD} and {@link ModificationType#DELETE
+   * DELETE}.
+   * <p>
+   * Finally, the modify request will use the distinguished name taken from
+   * {@code fromEntry}. Moreover, this method will not check to see if both
+   * {@code fromEntry} and {@code toEntry} have the same distinguished name.
+   * <p>
+   * This method is equivalent to:
+   *
+   * <pre>
+   * ModifyRequest request = Requests.newModifyRequest(fromEntry, toEntry);
+   * </pre>
+   *
+   * @param fromEntry
+   *          The source entry.
+   * @param toEntry
+   *          The destination entry.
+   * @return A modify request containing a list of modifications which can be
+   *         used to transform {@code fromEntry} into entry {@code toEntry}.
+   * @throws NullPointerException
+   *           If {@code fromEntry} or {@code toEntry} were {@code null}.
+   * @see Requests#newModifyRequest(Entry, Entry)
+   */
+  public static final ModifyRequest diffEntries(final Entry fromEntry,
+      final Entry toEntry) throws NullPointerException
+  {
+    Validator.ensureNotNull(fromEntry, toEntry);
+
+    final ModifyRequest request = Requests
+        .newModifyRequest(fromEntry.getName());
+
+    TreeMapEntry tfrom;
+    if (fromEntry instanceof TreeMapEntry)
+    {
+      tfrom = (TreeMapEntry) fromEntry;
+    }
+    else
+    {
+      tfrom = new TreeMapEntry(fromEntry);
+    }
+
+    TreeMapEntry tto;
+    if (toEntry instanceof TreeMapEntry)
+    {
+      tto = (TreeMapEntry) toEntry;
+    }
+    else
+    {
+      tto = new TreeMapEntry(toEntry);
+    }
+
+    final Iterator<Attribute> ifrom = tfrom.getAllAttributes().iterator();
+    final Iterator<Attribute> ito = tto.getAllAttributes().iterator();
+
+    Attribute afrom = ifrom.hasNext() ? ifrom.next() : null;
+    Attribute ato = ito.hasNext() ? ito.next() : null;
+
+    while (afrom != null && ato != null)
+    {
+      final AttributeDescription adfrom = afrom.getAttributeDescription();
+      final AttributeDescription adto = ato.getAttributeDescription();
+
+      final int cmp = adfrom.compareTo(adto);
+      if (cmp == 0)
+      {
+        // Attribute is in both entries. Compute the set of values to be added
+        // and removed. We won't replace the attribute because this is not
+        // reversible.
+        final Attribute addedValues = new LinkedAttribute(ato);
+        addedValues.removeAll(afrom);
+        if (!addedValues.isEmpty())
+        {
+          request.addModification(new Modification(ModificationType.ADD,
+              addedValues));
+        }
+
+        final Attribute deletedValues = new LinkedAttribute(afrom);
+        deletedValues.removeAll(ato);
+        if (!deletedValues.isEmpty())
+        {
+          request.addModification(new Modification(ModificationType.DELETE,
+              deletedValues));
+        }
+
+        afrom = ifrom.hasNext() ? ifrom.next() : null;
+        ato = ito.hasNext() ? ito.next() : null;
+      }
+      else if (cmp < 0)
+      {
+        // afrom in source, but not destination.
+        request
+            .addModification(new Modification(ModificationType.DELETE, afrom));
+        afrom = ifrom.hasNext() ? ifrom.next() : null;
+      }
+      else
+      {
+        // ato in destination, but not in source.
+        request.addModification(new Modification(ModificationType.ADD, ato));
+        ato = ito.hasNext() ? ito.next() : null;
+      }
+    }
+
+    // Additional attributes in source entry: these must be deleted.
+    if (afrom != null)
+    {
+      request.addModification(new Modification(ModificationType.DELETE, afrom));
+    }
+
+    while (ifrom.hasNext())
+    {
+      final Attribute a = ifrom.next();
+      request.addModification(new Modification(ModificationType.DELETE, a));
+    }
+
+    // Additional attributes in destination entry: these must be added.
+    if (ato != null)
+    {
+      request.addModification(new Modification(ModificationType.ADD, ato));
+    }
+
+    while (ito.hasNext())
+    {
+      final Attribute a = ito.next();
+      request.addModification(new Modification(ModificationType.ADD, a));
+    }
+
+    return request;
+  }
+
+
+
+  /**
    * Returns a read-only view of {@code entry} and its attributes. Query
-   * operations on the returned entry and its attributes"read-through" to the
+   * operations on the returned entry and its attributes "read-through" to the
    * underlying entry or attribute, and attempts to modify the returned entry
    * and its attributes either directly or indirectly via an iterator result in
    * an {@code UnsupportedOperationException}.
diff --git a/sdk/src/org/opends/sdk/Entry.java b/sdk/src/org/opends/sdk/Entry.java
index e237739..9a573e9 100644
--- a/sdk/src/org/opends/sdk/Entry.java
+++ b/sdk/src/org/opends/sdk/Entry.java
@@ -57,6 +57,8 @@
  * {@link #replaceAttribute} and the conditions, if any, where a reference to
  * the passed in attribute is maintained.
  * </ul>
+ *
+ * @see Entries
  */
 public interface Entry
 {
diff --git a/sdk/src/org/opends/sdk/ErrorResultException.java b/sdk/src/org/opends/sdk/ErrorResultException.java
index f64756f..6d57f82 100644
--- a/sdk/src/org/opends/sdk/ErrorResultException.java
+++ b/sdk/src/org/opends/sdk/ErrorResultException.java
@@ -31,6 +31,7 @@
 
 import java.util.concurrent.ExecutionException;
 
+import org.opends.sdk.responses.Responses;
 import org.opends.sdk.responses.Result;
 
 
@@ -42,6 +43,60 @@
 @SuppressWarnings("serial")
 public class ErrorResultException extends ExecutionException
 {
+
+  /**
+   * Creates a new error result exception with the provided result code and
+   * diagnostic message.
+   *
+   * @param resultCode
+   *          The result code.
+   * @param diagnosticMessage
+   *          The diagnostic message, which may be empty or {@code null}
+   *          indicating that none was provided.
+   * @return The new error result exception.
+   * @throws IllegalArgumentException
+   *           If the provided result code does not represent a failure.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  public static ErrorResultException newErrorResult(ResultCode resultCode,
+      String diagnosticMessage) throws IllegalArgumentException,
+      NullPointerException
+  {
+    return newErrorResult(resultCode, diagnosticMessage, null);
+  }
+
+
+
+  /**
+   * Creates a new error result exception with the provided result code,
+   * diagnostic message, and cause.
+   *
+   * @param resultCode
+   *          The result code.
+   * @param diagnosticMessage
+   *          The diagnostic message, which may be empty or {@code null}
+   *          indicating that none was provided.
+   * @param cause
+   *          The throwable cause, which may be null indicating that none was
+   *          provided.
+   * @return The new error result exception.
+   * @throws IllegalArgumentException
+   *           If the provided result code does not represent a failure.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  public static ErrorResultException newErrorResult(ResultCode resultCode,
+      String diagnosticMessage, Throwable cause)
+      throws IllegalArgumentException, NullPointerException
+  {
+    Result result = Responses.newResult(resultCode)
+        .setDiagnosticMessage(diagnosticMessage).setCause(cause);
+    return wrap(result);
+  }
+
+
+
   /**
    * Wraps the provided result in an appropriate error result exception. The
    * type of error result exception used depends on the underlying result code.
diff --git a/sdk/src/org/opends/sdk/FailoverLoadBalancingAlgorithm.java b/sdk/src/org/opends/sdk/FailoverLoadBalancingAlgorithm.java
index 4910e44..6ac3148 100644
--- a/sdk/src/org/opends/sdk/FailoverLoadBalancingAlgorithm.java
+++ b/sdk/src/org/opends/sdk/FailoverLoadBalancingAlgorithm.java
@@ -29,15 +29,9 @@
 
 
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
-import java.util.logging.Level;
-
-import org.opends.sdk.responses.Responses;
-
-import com.sun.opends.sdk.util.StaticUtils;
-import com.sun.opends.sdk.util.Validator;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 
 
@@ -45,213 +39,115 @@
  * A fail-over load balancing algorithm provides fault tolerance across multiple
  * underlying connection factories.
  * <p>
+ * This algorithm is typically used for load-balancing <i>between</i> data
+ * centers, where there is preference to always always forward connection
+ * requests to the <i>closest available</i> data center. This algorithm
+ * contrasts with the {@link RoundRobinLoadBalancingAlgorithm} which is used for
+ * load-balancing <i>within</i> a data center.
+ * <p>
+ * This algorithm selects connection factories based on the order in which they
+ * were provided during construction. More specifically, an attempt to obtain a
+ * connection factory will always return the <i>first operational</i> connection
+ * factory in the list. Applications should, therefore, organize the connection
+ * factories such that the <i>preferred</i> (usually the closest) connection
+ * factories appear before those which are less preferred.
+ * <p>
  * If a problem occurs that temporarily prevents connections from being obtained
- * for one of the connection factories, then this algorithm "fails over" to
- * another operational connection factory in the list. If none of the connection
- * factories are operational then a {@code ConnectionException} is returned to
- * the client.
+ * for one of the connection factories, then this algorithm automatically
+ * "fails over" to the next operational connection factory in the list. If none
+ * of the connection factories are operational then a
+ * {@code ConnectionException} is returned to the client.
  * <p>
  * The implementation periodically attempts to connect to failed connection
  * factories in order to determine if they have become available again.
+ *
+ * @see RoundRobinLoadBalancingAlgorithm
+ * @see Connections#newLoadBalancer(LoadBalancingAlgorithm)
  */
-class FailoverLoadBalancingAlgorithm implements LoadBalancingAlgorithm
+public final class FailoverLoadBalancingAlgorithm extends
+    AbstractLoadBalancingAlgorithm
 {
-  private static final class MonitoredConnectionFactory extends
-      AbstractConnectionFactory implements
-      ResultHandler<AsynchronousConnection>
-  {
-    private final ConnectionFactory factory;
-
-    private volatile boolean isOperational;
-
-    private volatile FutureResult<?> pendingConnectFuture;
-
-
-
-    private MonitoredConnectionFactory(final ConnectionFactory factory)
-    {
-      this.factory = factory;
-      this.isOperational = true;
-    }
-
-
-
-    @Override
-    public FutureResult<AsynchronousConnection> getAsynchronousConnection(
-        final ResultHandler<? super AsynchronousConnection> resultHandler)
-    {
-      final ResultHandler<AsynchronousConnection> handler =
-        new ResultHandler<AsynchronousConnection>()
-      {
-        public void handleErrorResult(final ErrorResultException error)
-        {
-          isOperational = false;
-          if (resultHandler != null)
-          {
-            resultHandler.handleErrorResult(error);
-          }
-          if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
-          {
-            StaticUtils.DEBUG_LOG
-                .warning(String.format("Connection factory " + factory
-                    + " is no longer operational: " + error.getMessage()));
-          }
-        }
-
-
-
-        public void handleResult(final AsynchronousConnection result)
-        {
-          isOperational = true;
-          if (resultHandler != null)
-          {
-            resultHandler.handleResult(result);
-          }
-          if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
-          {
-            StaticUtils.DEBUG_LOG.warning(String.format("Connection factory "
-                + factory + " is now operational"));
-          }
-        }
-      };
-      return factory.getAsynchronousConnection(handler);
-    }
-
-
-
-    public void handleErrorResult(final ErrorResultException error)
-    {
-      isOperational = false;
-    }
-
-
-
-    public void handleResult(final AsynchronousConnection result)
-    {
-      isOperational = true;
-      // TODO: Notify the server is back up
-      result.close();
-    }
-
-
-
-    private boolean isOperational()
-    {
-      return isOperational;
-    }
-  }
-
-
-
-  private final class MonitorThread extends Thread
-  {
-    private MonitorThread()
-    {
-      super("Connection Factory Health Monitor");
-      this.setDaemon(true);
-    }
-
-
-
-    @Override
-    public void run()
-    {
-      while (true)
-      {
-        for (final MonitoredConnectionFactory f : monitoredFactories)
-        {
-          if (!f.isOperational
-              && (f.pendingConnectFuture == null || f.pendingConnectFuture
-                  .isDone()))
-          {
-            if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
-            {
-              StaticUtils.DEBUG_LOG.finest(String
-                  .format("Attempting connect on factory " + f));
-            }
-            f.pendingConnectFuture = f.factory.getAsynchronousConnection(f);
-          }
-        }
-
-        try
-        {
-          sleep(10000);
-        }
-        catch (final InterruptedException e)
-        {
-          // Termination requested - exit.
-          break;
-        }
-      }
-    }
-  }
-
-
-
-  private final List<MonitoredConnectionFactory> monitoredFactories;
-
-
 
   /**
-   * Creates a new fail-over load balancing algorithm which will fail-over
-   * across the provided collection of connection factories.
+   * Creates a new fail-over load balancing algorithm which will use a default
+   * scheduler for monitoring offline connection factories every 10 seconds.
    *
    * @param factories
-   *          The connection factories which will be used for fail-over.
+   *          The ordered collection of connection factories.
    */
   public FailoverLoadBalancingAlgorithm(
       final Collection<ConnectionFactory> factories)
   {
-    Validator.ensureNotNull(factories);
-
-    monitoredFactories = new ArrayList<MonitoredConnectionFactory>(factories
-        .size());
-    for (final ConnectionFactory f : factories)
-    {
-      monitoredFactories.add(new MonitoredConnectionFactory(f));
-    }
-
-    new MonitorThread().start();
+    super(factories);
   }
 
 
 
   /**
-   * Creates a new fail-over load balancing algorithm which will fail-over
-   * across the provided list of connection factories.
+   * Creates a new fail-over load balancing algorithm which will use the
+   * provided scheduler for monitoring offline connection factories every 10
+   * seconds.
    *
    * @param factories
-   *          The connection factories which will be used for fail-over.
+   *          The ordered collection of connection factories.
+   * @param scheduler
+   *          The scheduler which should for periodically monitoring offline
+   *          connection factories to see if they are usable again.
    */
-  public FailoverLoadBalancingAlgorithm(final ConnectionFactory... factories)
+  public FailoverLoadBalancingAlgorithm(
+      final Collection<ConnectionFactory> factories,
+      final ScheduledExecutorService scheduler)
   {
-    Validator.ensureNotNull((Object[]) factories);
-
-    monitoredFactories = new ArrayList<MonitoredConnectionFactory>(
-        factories.length);
-    for (final ConnectionFactory f : factories)
-    {
-      monitoredFactories.add(new MonitoredConnectionFactory(f));
-    }
-
-    new MonitorThread().start();
+    super(factories, scheduler);
   }
 
 
 
-  public ConnectionFactory getNextConnectionFactory()
-      throws ErrorResultException
+  /**
+   * Creates a new fail-over load balancing algorithm which will use the
+   * provided scheduler for monitoring offline connection factories.
+   *
+   * @param factories
+   *          The ordered collection of connection factories.
+   * @param scheduler
+   *          The scheduler which should for periodically monitoring offline
+   *          connection factories to see if they are usable again.
+   * @param interval
+   *          The interval between attempts to poll offline connection
+   *          factories.
+   * @param unit
+   *          The time unit for the interval between attempts to poll offline
+   *          connection factories.
+   */
+  public FailoverLoadBalancingAlgorithm(
+      final Collection<ConnectionFactory> factories,
+      final ScheduledExecutorService scheduler, final long interval,
+      final TimeUnit unit)
   {
-    for (final MonitoredConnectionFactory f : monitoredFactories)
-    {
-      if (f.isOperational())
-      {
-        return f;
-      }
-    }
-
-    throw ErrorResultException.wrap(Responses.newResult(
-        ResultCode.CLIENT_SIDE_CONNECT_ERROR).setDiagnosticMessage(
-        "No operational connection factories available"));
+    super(factories, scheduler, interval, unit);
   }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  String getAlgorithmName()
+  {
+    return "Failover";
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  int getInitialConnectionFactoryIndex()
+  {
+    // Always start with the first connection factory.
+    return 0;
+  }
+
 }
diff --git a/sdk/src/org/opends/sdk/HeartBeatConnectionFactory.java b/sdk/src/org/opends/sdk/HeartBeatConnectionFactory.java
index ccf41df..381cc43 100644
--- a/sdk/src/org/opends/sdk/HeartBeatConnectionFactory.java
+++ b/sdk/src/org/opends/sdk/HeartBeatConnectionFactory.java
@@ -198,8 +198,7 @@
 
 
 
-    public void handleUnsolicitedNotification(
-        final ExtendedResult notification)
+    public void handleUnsolicitedNotification(final ExtendedResult notification)
     {
       // Do nothing
     }
@@ -455,14 +454,27 @@
     {
       return connection.searchSingleEntry(request, resultHandler);
     }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+      StringBuilder builder = new StringBuilder();
+      builder.append("HeartBeatConnection(");
+      builder.append(connection);
+      builder.append(')');
+      return builder.toString();
+    }
   }
 
 
 
   private final class FutureResultImpl extends
       FutureResultTransformer<AsynchronousConnection, AsynchronousConnection>
-      implements FutureResult<AsynchronousConnection>,
-      ResultHandler<AsynchronousConnection>
+      implements ResultHandler<AsynchronousConnection>
   {
 
     private FutureResultImpl(
@@ -616,4 +628,18 @@
     future.setFutureResult(parentFactory.getAsynchronousConnection(future));
     return future;
   }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("HeartBeatConnectionFactory(");
+    builder.append(String.valueOf(parentFactory));
+    builder.append(')');
+    return builder.toString();
+  }
 }
diff --git a/sdk/src/org/opends/sdk/InternalConnectionFactory.java b/sdk/src/org/opends/sdk/InternalConnectionFactory.java
index 8749d7a..90d248e 100644
--- a/sdk/src/org/opends/sdk/InternalConnectionFactory.java
+++ b/sdk/src/org/opends/sdk/InternalConnectionFactory.java
@@ -35,8 +35,20 @@
 
 
 /**
- * A special {@code ConnectionFactory} that can be used to perform internal
- * operations against a {@code ServerConnectionFactory} implementation.
+ * A special {@code ConnectionFactory} which waits for internal connection
+ * requests and binds them to a {@link ServerConnection} created using the
+ * provided {@link ServerConnectionFactory}.
+ * <p>
+ * When processing requests, {@code ServerConnection} implementations are passed
+ * an integer as the first parameter. This integer represents a pseudo
+ * {@code requestID} which is incremented for each successive internal request
+ * on a per connection basis. The request ID may be useful for logging purposes.
+ * <p>
+ * An {@code InternalConnectionFactory} does not require
+ * {@code ServerConnection} implementations to return a result when processing
+ * requests. However, it is recommended that implementations do always return
+ * results even for abandoned requests. This is because application client
+ * threads may block indefinitely waiting for results.
  *
  * @param <C>
  *          The type of client context.
@@ -66,7 +78,7 @@
     final ServerConnection<Integer> serverConnection;
     try
     {
-      serverConnection = factory.accept(clientContext);
+      serverConnection = factory.handleAccept(clientContext);
     }
     catch (final ErrorResultException e)
     {
@@ -85,4 +97,20 @@
     }
     return new CompletedFutureResult<AsynchronousConnection>(connection);
   }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("InternalConnectionFactory(");
+    builder.append(String.valueOf(clientContext));
+    builder.append(',');
+    builder.append(String.valueOf(factory));
+    builder.append(')');
+    return builder.toString();
+  }
 }
diff --git a/sdk/src/org/opends/sdk/LDAPClientContext.java b/sdk/src/org/opends/sdk/LDAPClientContext.java
index 878e020..22a2f93 100644
--- a/sdk/src/org/opends/sdk/LDAPClientContext.java
+++ b/sdk/src/org/opends/sdk/LDAPClientContext.java
@@ -38,36 +38,20 @@
 
 
 /**
- * An LDAP client which has connected to a {@link ServerConnectionFactory}. The
+ * An LDAP client which has connected to a {@link ServerConnectionFactory}. An
  * LDAP client context can be used to query information about the client's
  * connection such as their network address, as well as managing the state of
  * the connection.
  */
 public interface LDAPClientContext
 {
-  /**
-   * Registers the provided connection event listener so that it will be
-   * notified when the underlying connection is closed by the client, receives
-   * an unsolicited notification, or experiences a fatal error.
-   * <p>
-   * This method provides a event notification mechanism which can be used by
-   * asynchronous request handler implementations to detect connection
-   * termination.
-   *
-   * @param listener
-   *          The listener which wants to be notified when events occur on the
-   *          underlying connection.
-   * @throws NullPointerException
-   *           If the {@code listener} was {@code null}.
-   * @see #isClosed
-   */
-  void addConnectionEventListener(ConnectionEventListener listener)
-      throws NullPointerException;
-
-
 
   /**
    * Disconnects the client without sending a disconnect notification.
+   * <p>
+   * <b>Server connections:</b> invoking this method causes
+   * {@link ServerConnection#handleConnectionDisconnected
+   * handleConnectionDisconnected} to be called before this method returns.
    */
   void disconnect();
 
@@ -76,6 +60,10 @@
   /**
    * Disconnects the client and sends a disconnect notification, if possible,
    * containing the provided result code and diagnostic message.
+   * <p>
+   * <b>Server connections:</b> invoking this method causes
+   * {@link ServerConnection#handleConnectionDisconnected
+   * handleConnectionDisconnected} to be called before this method returns.
    *
    * @param resultCode
    *          The result code which should be included with the disconnect
@@ -118,39 +106,27 @@
 
 
   /**
-   * Returns {@code true} if the underlying connection is closed by the client,
-   * receives an unsolicited notification, or experiences a fatal error.
+   * Returns {@code true} if the underlying connection has been closed as a
+   * result of a client disconnect, a fatal connection error, or a server-side
+   * {@link #disconnect}.
    * <p>
    * This method provides a polling mechanism which can be used by synchronous
    * request handler implementations to detect connection termination.
+   * <p>
+   * <b>Server connections:</b> this method will always return {@code true} when
+   * called from within {@link ServerConnection#handleConnectionClosed
+   * handleConnectionClosed},
+   * {@link ServerConnection#handleConnectionDisconnected
+   * handleConnectionDisconnected}, or
+   * {@link ServerConnection#handleConnectionError handleConnectionError}.
    *
-   * @return {@code true} if the underlying connection is closed by the client,
-   *         receives an unsolicited notification, or experiences a fatal error,
-   *         otherwise {@code false}.
-   * @see #addConnectionEventListener
+   * @return {@code true} if the underlying connection has been closed.
    */
   boolean isClosed();
 
 
 
   /**
-   * Removes the provided connection event listener from this client context so
-   * that it will no longer be notified when the underlying connection is closed
-   * by the application, receives an unsolicited notification, or experiences a
-   * fatal error.
-   *
-   * @param listener
-   *          The listener which no longer wants to be notified when events
-   *          occur on the underlying connection.
-   * @throws NullPointerException
-   *           If the {@code listener} was {@code null}.
-   */
-  void removeConnectionEventListener(ConnectionEventListener listener)
-      throws NullPointerException;
-
-
-
-  /**
    * Sends an unsolicited notification to the client.
    *
    * @param notification
diff --git a/sdk/src/org/opends/sdk/LDAPConnectionFactory.java b/sdk/src/org/opends/sdk/LDAPConnectionFactory.java
index 5cbde4a..a632071 100644
--- a/sdk/src/org/opends/sdk/LDAPConnectionFactory.java
+++ b/sdk/src/org/opends/sdk/LDAPConnectionFactory.java
@@ -29,6 +29,7 @@
 
 
 
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 
@@ -131,8 +132,31 @@
 
 
   /**
+   * Returns the {@code InetAddress} that this LDAP listener is listening on.
+   *
+   * @return The {@code InetAddress} that this LDAP listener is listening on, or
+   *         {@code null} if it is unknown.
+   */
+  public InetAddress getAddress()
+  {
+    final SocketAddress socketAddress = getSocketAddress();
+    if (socketAddress instanceof InetSocketAddress)
+    {
+      final InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+      return inetSocketAddress.getAddress();
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+
+
+  /**
    * {@inheritDoc}
    */
+  @Override
   public FutureResult<AsynchronousConnection> getAsynchronousConnection(
       final ResultHandler<? super AsynchronousConnection> handler)
   {
@@ -144,9 +168,77 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public Connection getConnection() throws ErrorResultException,
       InterruptedException
   {
     return impl.getConnection();
   }
+
+
+
+  /**
+   * Returns the host name that this LDAP listener is listening on.
+   *
+   * @return The host name that this LDAP listener is listening on, or
+   *         {@code null} if it is unknown.
+   */
+  public String getHostname()
+  {
+    final SocketAddress socketAddress = getSocketAddress();
+    if (socketAddress instanceof InetSocketAddress)
+    {
+      final InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+      return inetSocketAddress.getHostName();
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+
+
+  /**
+   * Returns the port that this LDAP listener is listening on.
+   *
+   * @return The port that this LDAP listener is listening on, or {@code -1} if
+   *         it is unknown.
+   */
+  public int getPort()
+  {
+    final SocketAddress socketAddress = getSocketAddress();
+    if (socketAddress instanceof InetSocketAddress)
+    {
+      final InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+      return inetSocketAddress.getPort();
+    }
+    else
+    {
+      return -1;
+    }
+  }
+
+
+
+  /**
+   * Returns the address that this LDAP listener is listening on.
+   *
+   * @return The address that this LDAP listener is listening on.
+   */
+  public SocketAddress getSocketAddress()
+  {
+    return impl.getSocketAddress();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return impl.toString();
+  }
 }
diff --git a/sdk/src/org/opends/sdk/LDAPListener.java b/sdk/src/org/opends/sdk/LDAPListener.java
index 6038cba..619abaa 100644
--- a/sdk/src/org/opends/sdk/LDAPListener.java
+++ b/sdk/src/org/opends/sdk/LDAPListener.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk;
@@ -31,6 +31,7 @@
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 
@@ -41,7 +42,21 @@
 
 /**
  * An LDAP server connection listener which waits for LDAP connection requests
- * to come in over the network and binds them to a {@link ServerConnection}.
+ * to come in over the network and binds them to a {@link ServerConnection}
+ * created using the provided {@link ServerConnectionFactory}.
+ * <p>
+ * When processing requests, {@code ServerConnection} implementations are passed
+ * an integer as the first parameter. This integer represents the
+ * {@code requestID} associated with the client request and corresponds to the
+ * {@code requestID} passed as a parameter to abandon and cancel extended
+ * requests. The request ID may also be useful for logging purposes.
+ * <p>
+ * An {@code LDAPListener} does not require {@code ServerConnection}
+ * implementations to return a result when processing requests. More
+ * specifically, an {@code LDAPListener} does not maintain any internal state
+ * information associated with each request which must be released. This is
+ * useful when implementing LDAP abandon operations which may prevent results
+ * being sent for abandoned operations.
  * <p>
  * The following code illustrates how to create a simple LDAP server:
  *
@@ -59,7 +74,7 @@
  *
  *
  *
- *   public void add(Integer context, AddRequest request,
+ *   public void add(Integer requestID, AddRequest request,
  *       ResultHandler&lt;Result&gt; handler,
  *       IntermediateResponseHandler intermediateResponseHandler)
  *       throws UnsupportedOperationException
@@ -73,9 +88,10 @@
  *
  *
  *
- * class MyServer implements ServerConnectionFactory&lt;LDAPClientContext, Integer&gt;
+ * class MyServer implements
+ *     ServerConnectionFactory&lt;LDAPClientContext, RequestContext&gt;
  * {
- *   public ServerConnection&lt;Integer&gt; accept(LDAPClientContext context)
+ *   public ServerConnection&lt;RequestContext&gt; accept(LDAPClientContext context)
  *   {
  *     System.out.println(&quot;Connection from: &quot; + context.getPeerAddress());
  *     return new MyClientConnection(context);
@@ -160,64 +176,6 @@
    * Creates a new LDAP listener implementation which will listen for LDAP
    * client connections at the provided address.
    *
-   * @param port
-   *          The port to listen on.
-   * @param host
-   *          The address to listen on.
-   * @param factory
-   *          The server connection factory which will be used to create server
-   *          connections.
-   * @throws IOException
-   *           If an error occurred while trying to listen on the provided
-   *           address.
-   * @throws NullPointerException
-   *           If {@code host} or {code factory} was {@code null}.
-   */
-  public LDAPListener(final int port, final String host,
-      final ServerConnectionFactory<LDAPClientContext, Integer> factory)
-      throws IOException, NullPointerException
-  {
-    this(port, host, factory, new LDAPListenerOptions());
-  }
-
-
-
-  /**
-   * Creates a new LDAP listener implementation which will listen for LDAP
-   * client connections at the provided address.
-   *
-   * @param port
-   *          The port to listen on.
-   * @param host
-   *          The address to listen on.
-   * @param factory
-   *          The server connection factory which will be used to create server
-   *          connections.
-   * @param options
-   *          The LDAP listener options.
-   * @throws IOException
-   *           If an error occurred while trying to listen on the provided
-   *           address.
-   * @throws NullPointerException
-   *           If {@code host}, {code factory}, or {@code options} was {@code
-   *           null}.
-   */
-  public LDAPListener(final int port, final String host,
-      final ServerConnectionFactory<LDAPClientContext, Integer> factory,
-      final LDAPListenerOptions options) throws IOException,
-      NullPointerException
-  {
-    Validator.ensureNotNull(host, factory, options);
-    final SocketAddress address = new InetSocketAddress(host, port);
-    this.impl = new LDAPListenerImpl(address, factory, options);
-  }
-
-
-
-  /**
-   * Creates a new LDAP listener implementation which will listen for LDAP
-   * client connections at the provided address.
-   *
    * @param address
    *          The address to listen on.
    * @param factory
@@ -268,14 +226,158 @@
 
 
   /**
-   * Closes this LDAP connection listener.
+   * Creates a new LDAP listener implementation which will listen for LDAP
+   * client connections at the provided address.
    *
+   * @param host
+   *          The address to listen on.
+   * @param port
+   *          The port to listen on.
+   * @param factory
+   *          The server connection factory which will be used to create server
+   *          connections.
    * @throws IOException
-   *           If an IO error occurred while closing this listener.
+   *           If an error occurred while trying to listen on the provided
+   *           address.
+   * @throws NullPointerException
+   *           If {@code host} or {code factory} was {@code null}.
    */
-  public void close() throws IOException
+  public LDAPListener(final String host, final int port,
+      final ServerConnectionFactory<LDAPClientContext, Integer> factory)
+      throws IOException, NullPointerException
+  {
+    this(host, port, factory, new LDAPListenerOptions());
+  }
+
+
+
+  /**
+   * Creates a new LDAP listener implementation which will listen for LDAP
+   * client connections at the provided address.
+   *
+   * @param host
+   *          The address to listen on.
+   * @param port
+   *          The port to listen on.
+   * @param factory
+   *          The server connection factory which will be used to create server
+   *          connections.
+   * @param options
+   *          The LDAP listener options.
+   * @throws IOException
+   *           If an error occurred while trying to listen on the provided
+   *           address.
+   * @throws NullPointerException
+   *           If {@code host}, {code factory}, or {@code options} was
+   *           {@code null}.
+   */
+  public LDAPListener(final String host, final int port,
+      final ServerConnectionFactory<LDAPClientContext, Integer> factory,
+      final LDAPListenerOptions options) throws IOException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(host, factory, options);
+    final SocketAddress address = new InetSocketAddress(host, port);
+    this.impl = new LDAPListenerImpl(address, factory, options);
+  }
+
+
+
+  /**
+   * Closes this LDAP connection listener.
+   */
+  @Override
+  public void close()
   {
     impl.close();
   }
 
+
+
+  /**
+   * Returns the {@code InetAddress} that this LDAP listener is listening on.
+   *
+   * @return The {@code InetAddress} that this LDAP listener is listening on, or
+   *         {@code null} if it is unknown.
+   */
+  public InetAddress getAddress()
+  {
+    final SocketAddress socketAddress = getSocketAddress();
+    if (socketAddress instanceof InetSocketAddress)
+    {
+      final InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+      return inetSocketAddress.getAddress();
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+
+
+  /**
+   * Returns the host name that this LDAP listener is listening on.
+   *
+   * @return The host name that this LDAP listener is listening on, or
+   *         {@code null} if it is unknown.
+   */
+  public String getHostname()
+  {
+    final SocketAddress socketAddress = getSocketAddress();
+    if (socketAddress instanceof InetSocketAddress)
+    {
+      final InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+      return inetSocketAddress.getHostName();
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+
+
+  /**
+   * Returns the port that this LDAP listener is listening on.
+   *
+   * @return The port that this LDAP listener is listening on, or {@code -1} if
+   *         it is unknown.
+   */
+  public int getPort()
+  {
+    final SocketAddress socketAddress = getSocketAddress();
+    if (socketAddress instanceof InetSocketAddress)
+    {
+      final InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+      return inetSocketAddress.getPort();
+    }
+    else
+    {
+      return -1;
+    }
+  }
+
+
+
+  /**
+   * Returns the address that this LDAP listener is listening on.
+   *
+   * @return The address that this LDAP listener is listening on.
+   */
+  public SocketAddress getSocketAddress()
+  {
+    return impl.getSocketAddress();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return impl.toString();
+  }
+
 }
diff --git a/sdk/src/org/opends/sdk/LinkedAttribute.java b/sdk/src/org/opends/sdk/LinkedAttribute.java
index 89ba995..5fb320a 100644
--- a/sdk/src/org/opends/sdk/LinkedAttribute.java
+++ b/sdk/src/org/opends/sdk/LinkedAttribute.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk;
@@ -57,31 +57,6 @@
 
 
 
-    boolean addAll(final LinkedAttribute attribute,
-        final Collection<? extends ByteString> values,
-        final Collection<? super ByteString> duplicateValues)
-        throws NullPointerException
-    {
-      // TODO: could optimize if values is a BasicAttribute.
-      ensureCapacity(attribute, values.size());
-      boolean modified = false;
-      for (final ByteString value : values)
-      {
-        if (add(attribute, value))
-        {
-          modified = true;
-        }
-        else if (duplicateValues != null)
-        {
-          duplicateValues.add(value);
-        }
-      }
-      resize(attribute);
-      return modified;
-    }
-
-
-
     abstract void clear(LinkedAttribute attribute);
 
 
@@ -93,7 +68,8 @@
     boolean containsAll(final LinkedAttribute attribute,
         final Collection<?> values)
     {
-      // TODO: could optimize if objects is a BasicAttribute.
+      // TODO: could optimize if objects is a LinkedAttribute having the same
+      // equality matching rule.
       for (final Object value : values)
       {
         if (!contains(attribute, ByteString.valueOf(value)))
@@ -106,10 +82,6 @@
 
 
 
-    abstract void ensureCapacity(LinkedAttribute attribute, int size);
-
-
-
     abstract ByteString firstValue(LinkedAttribute attribute)
         throws NoSuchElementException;
 
@@ -123,31 +95,6 @@
 
 
 
-    <T> boolean removeAll(final LinkedAttribute attribute,
-        final Collection<T> values, final Collection<? super T> missingValues)
-    {
-      // TODO: could optimize if objects is a BasicAttribute.
-      boolean modified = false;
-      for (final T value : values)
-      {
-        if (remove(attribute, ByteString.valueOf(value)))
-        {
-          modified = true;
-        }
-        else if (missingValues != null)
-        {
-          missingValues.add(value);
-        }
-      }
-      return modified;
-    }
-
-
-
-    abstract void resize(LinkedAttribute attribute);
-
-
-
     abstract <T> boolean retainAll(LinkedAttribute attribute,
         Collection<T> values, Collection<? super T> missingValues);
 
@@ -196,14 +143,6 @@
 
 
     @Override
-    void ensureCapacity(final LinkedAttribute attribute, final int size)
-    {
-      // Nothing to do.
-    }
-
-
-
-    @Override
     ByteString firstValue(final LinkedAttribute attribute)
         throws NoSuchElementException
     {
@@ -224,6 +163,7 @@
 
 
 
+        @Override
         public boolean hasNext()
         {
           return iterator.hasNext();
@@ -231,6 +171,7 @@
 
 
 
+        @Override
         public ByteString next()
         {
           if (attribute.pimpl != expectedImpl)
@@ -245,6 +186,7 @@
 
 
 
+        @Override
         public void remove()
         {
           if (attribute.pimpl != expectedImpl)
@@ -292,37 +234,11 @@
 
 
     @Override
-    void resize(final LinkedAttribute attribute)
-    {
-      // May need to resize if initial size estimate was wrong (e.g. all
-      // values in added collection were the same).
-      switch (attribute.multipleValues.size())
-      {
-      case 0:
-        attribute.multipleValues = null;
-        attribute.pimpl = ZERO_VALUE_IMPL;
-        break;
-      case 1:
-        final Map.Entry<ByteString, ByteString> e = attribute.multipleValues
-            .entrySet().iterator().next();
-        attribute.singleValue = e.getValue();
-        attribute.normalizedSingleValue = e.getKey();
-        attribute.multipleValues = null;
-        attribute.pimpl = SINGLE_VALUE_IMPL;
-        break;
-      default:
-        // Nothing to do.
-        break;
-      }
-    }
-
-
-
-    @Override
     <T> boolean retainAll(final LinkedAttribute attribute,
         final Collection<T> values, final Collection<? super T> missingValues)
     {
-      // TODO: could optimize if objects is a BasicAttribute.
+      // TODO: could optimize if objects is a LinkedAttribute having the same
+      // equality matching rule.
       if (values.isEmpty())
       {
         clear(attribute);
@@ -367,6 +283,32 @@
     {
       return attribute.multipleValues.size();
     }
+
+
+
+    private void resize(final LinkedAttribute attribute)
+    {
+      // May need to resize if initial size estimate was wrong (e.g. all
+      // values in added collection were the same).
+      switch (attribute.multipleValues.size())
+      {
+      case 0:
+        attribute.multipleValues = null;
+        attribute.pimpl = ZERO_VALUE_IMPL;
+        break;
+      case 1:
+        final Map.Entry<ByteString, ByteString> e = attribute.multipleValues
+            .entrySet().iterator().next();
+        attribute.singleValue = e.getValue();
+        attribute.normalizedSingleValue = e.getKey();
+        attribute.multipleValues = null;
+        attribute.pimpl = SINGLE_VALUE_IMPL;
+        break;
+      default:
+        // Nothing to do.
+        break;
+      }
+    }
   }
 
 
@@ -416,25 +358,6 @@
 
 
     @Override
-    void ensureCapacity(final LinkedAttribute attribute, final int size)
-    {
-      if (size == 0)
-      {
-        return;
-      }
-
-      attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(
-          1 + size);
-      attribute.multipleValues.put(attribute.normalizedSingleValue,
-          attribute.singleValue);
-      attribute.singleValue = null;
-      attribute.normalizedSingleValue = null;
-      attribute.pimpl = MULTI_VALUE_IMPL;
-    }
-
-
-
-    @Override
     ByteString firstValue(final LinkedAttribute attribute)
         throws NoSuchElementException
     {
@@ -461,6 +384,7 @@
 
 
 
+        @Override
         public boolean hasNext()
         {
           return hasNext;
@@ -468,6 +392,7 @@
 
 
 
+        @Override
         public ByteString next()
         {
           if (attribute.pimpl != expectedImpl)
@@ -487,6 +412,7 @@
 
 
 
+        @Override
         public void remove()
         {
           if (attribute.pimpl != expectedImpl)
@@ -526,18 +452,11 @@
 
 
     @Override
-    void resize(final LinkedAttribute attribute)
-    {
-      // Nothing to do.
-    }
-
-
-
-    @Override
     <T> boolean retainAll(final LinkedAttribute attribute,
         final Collection<T> values, final Collection<? super T> missingValues)
     {
-      // TODO: could optimize if objects is a BasicAttribute.
+      // TODO: could optimize if objects is a LinkedAttribute having the same
+      // equality matching rule.
       if (values.isEmpty())
       {
         clear(attribute);
@@ -549,8 +468,8 @@
       boolean retained = false;
       for (final T value : values)
       {
-        final ByteString normalizedValue = normalizeValue(attribute, ByteString
-            .valueOf(value));
+        final ByteString normalizedValue = normalizeValue(attribute,
+            ByteString.valueOf(value));
         if (normalizedSingleValue.equals(normalizedValue))
         {
           if (missingValues == null)
@@ -627,20 +546,6 @@
 
 
     @Override
-    void ensureCapacity(final LinkedAttribute attribute, final int size)
-    {
-      if (size < 2)
-      {
-        return;
-      }
-
-      attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(size);
-      attribute.pimpl = MULTI_VALUE_IMPL;
-    }
-
-
-
-    @Override
     ByteString firstValue(final LinkedAttribute attribute)
         throws NoSuchElementException
     {
@@ -654,6 +559,7 @@
     {
       return new Iterator<ByteString>()
       {
+        @Override
         public boolean hasNext()
         {
           return false;
@@ -661,6 +567,7 @@
 
 
 
+        @Override
         public ByteString next()
         {
           if (attribute.pimpl != ZERO_VALUE_IMPL)
@@ -675,6 +582,7 @@
 
 
 
+        @Override
         public void remove()
         {
           if (attribute.pimpl != ZERO_VALUE_IMPL)
@@ -701,14 +609,6 @@
 
 
     @Override
-    void resize(final LinkedAttribute attribute)
-    {
-      // Nothing to do.
-    }
-
-
-
-    @Override
     <T> boolean retainAll(final LinkedAttribute attribute,
         final Collection<T> values, final Collection<? super T> missingValues)
     {
@@ -736,6 +636,7 @@
    */
   public static final AttributeFactory FACTORY = new AttributeFactory()
   {
+    @Override
     public Attribute newAttribute(
         final AttributeDescription attributeDescription)
         throws NullPointerException
@@ -843,8 +744,8 @@
    * @param values
    *          The attribute values.
    * @throws NullPointerException
-   *           If {@code attributeDescription} or {@code values} was {@code
-   *           null}.
+   *           If {@code attributeDescription} or {@code values} was
+   *           {@code null}.
    */
   public LinkedAttribute(final AttributeDescription attributeDescription,
       final ByteString... values) throws NullPointerException
@@ -864,8 +765,8 @@
    * @param values
    *          The attribute values.
    * @throws NullPointerException
-   *           If {@code attributeDescription} or {@code values} was {@code
-   *           null}.
+   *           If {@code attributeDescription} or {@code values} was
+   *           {@code null}.
    */
   public LinkedAttribute(final AttributeDescription attributeDescription,
       final Collection<ByteString> values) throws NullPointerException
@@ -941,8 +842,8 @@
    *           If {@code attributeDescription} could not be decoded using the
    *           default schema.
    * @throws NullPointerException
-   *           If {@code attributeDescription} or {@code values} was {@code
-   *           null}.
+   *           If {@code attributeDescription} or {@code values} was
+   *           {@code null}.
    */
   public LinkedAttribute(final String attributeDescription,
       final Object... values) throws LocalizedIllegalArgumentException,
@@ -978,7 +879,22 @@
       throws NullPointerException
   {
     Validator.ensureNotNull(values);
-    return pimpl.addAll(this, values, duplicateValues);
+
+    // TODO: could optimize if objects is a LinkedAttribute having the same
+    // equality matching rule.
+    boolean modified = false;
+    for (final ByteString value : values)
+    {
+      if (add(value))
+      {
+        modified = true;
+      }
+      else if (duplicateValues != null)
+      {
+        duplicateValues.add(value);
+      }
+    }
+    return modified;
   }
 
 
@@ -1072,7 +988,22 @@
       final Collection<? super T> missingValues) throws NullPointerException
   {
     Validator.ensureNotNull(values);
-    return pimpl.removeAll(this, values, missingValues);
+
+    // TODO: could optimize if objects is a LinkedAttribute having the same
+    // equality matching rule.
+    boolean modified = false;
+    for (final T value : values)
+    {
+      if (remove(ByteString.valueOf(value)))
+      {
+        modified = true;
+      }
+      else if (missingValues != null)
+      {
+        missingValues.add(value);
+      }
+    }
+    return modified;
   }
 
 
diff --git a/sdk/src/org/opends/sdk/LoadBalancer.java b/sdk/src/org/opends/sdk/LoadBalancer.java
new file mode 100644
index 0000000..4dd63d9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/LoadBalancer.java
@@ -0,0 +1,100 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import com.sun.opends.sdk.util.CompletedFutureResult;
+import com.sun.opends.sdk.util.Validator;
+
+
+
+/**
+ * A load balancing connection factory allocates connections using the provided
+ * algorithm.
+ */
+final class LoadBalancer extends AbstractConnectionFactory
+{
+  private final LoadBalancingAlgorithm algorithm;
+
+
+
+  /**
+   * Creates a new load balancer using the provided algorithm.
+   *
+   * @param algorithm
+   *          The load balancing algorithm which will be used to obtain the next
+   *          connection factory.
+   */
+  public LoadBalancer(final LoadBalancingAlgorithm algorithm)
+  {
+    Validator.ensureNotNull(algorithm);
+    this.algorithm = algorithm;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public FutureResult<AsynchronousConnection> getAsynchronousConnection(
+      final ResultHandler<? super AsynchronousConnection> resultHandler)
+  {
+    final ConnectionFactory factory;
+
+    try
+    {
+      factory = algorithm.getConnectionFactory();
+    }
+    catch (final ErrorResultException e)
+    {
+      if (resultHandler != null)
+      {
+        resultHandler.handleErrorResult(e);
+      }
+      return new CompletedFutureResult<AsynchronousConnection>(e);
+    }
+
+    return factory.getAsynchronousConnection(resultHandler);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("LoadBalancer(");
+    builder.append(String.valueOf(algorithm));
+    builder.append(')');
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/LoadBalancingAlgorithm.java b/sdk/src/org/opends/sdk/LoadBalancingAlgorithm.java
index 6e9ffb0..fc81c3d 100644
--- a/sdk/src/org/opends/sdk/LoadBalancingAlgorithm.java
+++ b/sdk/src/org/opends/sdk/LoadBalancingAlgorithm.java
@@ -32,16 +32,18 @@
 /**
  * A load balancing algorithm distributes connection requests across one or more
  * underlying connection factories in an implementation defined manner.
+ *
+ * @see Connections#newLoadBalancer(LoadBalancingAlgorithm) newLoadBalancer
  */
-interface LoadBalancingAlgorithm
+public interface LoadBalancingAlgorithm
 {
   /**
-   * Returns the next connection factory which should be used in order to obtain
-   * a connection.
+   * Returns a connection factory which should be used in order to satisfy the
+   * next connection request.
    *
-   * @return The next connection factory.
+   * @return The connection factory.
    * @throws ErrorResultException
    *           If no connection factories are available for use.
    */
-  ConnectionFactory getNextConnectionFactory() throws ErrorResultException;
+  ConnectionFactory getConnectionFactory() throws ErrorResultException;
 }
diff --git a/sdk/src/org/opends/sdk/RoundRobinLoadBalancingAlgorithm.java b/sdk/src/org/opends/sdk/RoundRobinLoadBalancingAlgorithm.java
new file mode 100644
index 0000000..cf4ab46
--- /dev/null
+++ b/sdk/src/org/opends/sdk/RoundRobinLoadBalancingAlgorithm.java
@@ -0,0 +1,178 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+
+/**
+ * A round robin load balancing algorithm distributes connection requests across
+ * a list of connection factories one at a time. When the end of the list is
+ * reached, the algorithm starts again from the beginning.
+ * <p>
+ * This algorithm is typically used for load-balancing <i>within</i> data
+ * centers, where load must be distributed equally across multiple data sources.
+ * This algorithm contrasts with the {@link FailoverLoadBalancingAlgorithm}
+ * which is used for load-balancing <i>between</i> data centers.
+ * <p>
+ * If a problem occurs that temporarily prevents connections from being obtained
+ * for one of the connection factories, then this algorithm automatically
+ * "fails over" to the next operational connection factory in the list. If none
+ * of the connection factories are operational then a
+ * {@code ConnectionException} is returned to the client.
+ * <p>
+ * The implementation periodically attempts to connect to failed connection
+ * factories in order to determine if they have become available again.
+ *
+ * @see FailoverLoadBalancingAlgorithm
+ * @see Connections#newLoadBalancer(LoadBalancingAlgorithm)
+ */
+public final class RoundRobinLoadBalancingAlgorithm extends
+    AbstractLoadBalancingAlgorithm
+{
+  private final int maxIndex;
+
+  private final AtomicInteger nextIndex = new AtomicInteger(-1);
+
+
+
+  /**
+   * Creates a new round robin load balancing algorithm which will use a default
+   * scheduler for monitoring offline connection factories every 10 seconds.
+   *
+   * @param factories
+   *          The unordered collection of connection factories.
+   */
+  public RoundRobinLoadBalancingAlgorithm(
+      final Collection<ConnectionFactory> factories)
+  {
+    super(factories);
+    this.maxIndex = factories.size();
+  }
+
+
+
+  /**
+   * Creates a new round robin load balancing algorithm which will use the
+   * provided scheduler for monitoring offline connection factories every 10
+   * seconds.
+   *
+   * @param factories
+   *          The unordered collection of connection factories.
+   * @param scheduler
+   *          The scheduler which should for periodically monitoring offline
+   *          connection factories to see if they are usable again.
+   */
+  public RoundRobinLoadBalancingAlgorithm(
+      final Collection<ConnectionFactory> factories,
+      final ScheduledExecutorService scheduler)
+  {
+    super(factories, scheduler);
+    this.maxIndex = factories.size();
+  }
+
+
+
+  /**
+   * Creates a new round robin load balancing algorithm which will use the
+   * provided scheduler for monitoring offline connection factories.
+   *
+   * @param factories
+   *          The unordered collection of connection factories.
+   * @param scheduler
+   *          The scheduler which should for periodically monitoring offline
+   *          connection factories to see if they are usable again.
+   * @param interval
+   *          The interval between attempts to poll offline connection
+   *          factories.
+   * @param unit
+   *          The time unit for the interval between attempts to poll offline
+   *          connection factories.
+   */
+  public RoundRobinLoadBalancingAlgorithm(
+      final Collection<ConnectionFactory> factories,
+      final ScheduledExecutorService scheduler, final long interval,
+      final TimeUnit unit)
+  {
+    super(factories, scheduler, interval, unit);
+    this.maxIndex = factories.size();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  String getAlgorithmName()
+  {
+    return "RoundRobin";
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  int getInitialConnectionFactoryIndex()
+  {
+    // A round robin pool of one connection factories is unlikely in practice
+    // and requires special treatment.
+    if (maxIndex == 1)
+    {
+      return 0;
+    }
+
+    // Determine the next factory to use: avoid blocking algorithm.
+    int oldNextIndex;
+    int newNextIndex;
+    do
+    {
+      oldNextIndex = nextIndex.get();
+      newNextIndex = oldNextIndex + 1;
+      if (newNextIndex == maxIndex)
+      {
+        newNextIndex = 0;
+      }
+    }
+    while (!nextIndex.compareAndSet(oldNextIndex, newNextIndex));
+
+    // There's a potential, but benign, race condition here: other threads could
+    // jump in and rotate through the list before we return the connection
+    // factory.
+    return newNextIndex;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ServerConnection.java b/sdk/src/org/opends/sdk/ServerConnection.java
index 950f6dc..b7c560d 100644
--- a/sdk/src/org/opends/sdk/ServerConnection.java
+++ b/sdk/src/org/opends/sdk/ServerConnection.java
@@ -39,9 +39,21 @@
 
 /**
  * A handler interface for processing requests from clients.
+ * <p>
+ * Implementations should always return results via the provided
+ * {@code RequestHandler} when processing requests unless otherwise indicated by
+ * the component passing requests to the {@code ServerConnection}. For example,
+ * an {@link LDAPListener} does not require {@code ServerConnection}
+ * implementations to return results, which may be useful when implementing
+ * abandon operation functionality.
+ * <p>
+ * Note that {@code ServerConnection}s may be stacked, and that a lower
+ * {@code ServerConnection} implementation, such as a logger, may require upper
+ * {@code ServerConnection} implementations to always return results.
  *
  * @param <C>
  *          The type of request context.
+ * @see ServerConnectionFactory
  */
 public interface ServerConnection<C>
 {
@@ -137,7 +149,8 @@
    * request.
    *
    * @param requestContext
-   *          The request context.
+   *          The request context which should be ignored if there was no
+   *          associated unbind request.
    * @param request
    *          The unbind request, which may be {@code null} if one was not sent
    *          before the connection was closed.
@@ -147,12 +160,28 @@
 
 
   /**
+   * Invoked when the server disconnects the client connection, possibly using a
+   * disconnect notification.
+   *
+   * @param resultCode
+   *          The result code which was included with the disconnect
+   *          notification, or {@code null} if no disconnect notification was
+   *          sent.
+   * @param message
+   *          The diagnostic message, which may be empty or {@code null}
+   *          indicating that none was provided.
+   */
+  void handleConnectionDisconnected(ResultCode resultCode, String message);
+
+
+
+  /**
    * Invoked when an error occurs on the connection and it is no longer usable.
    *
    * @param error
    *          The exception describing the problem that occurred.
    */
-  void handleConnectionException(Throwable error);
+  void handleConnectionError(Throwable error);
 
 
 
@@ -258,11 +287,8 @@
    * @param request
    *          The search request.
    * @param resultHandler
-   *          The handler which should be used to send back the result to the
-   *          client.
-   * @param searchResulthandler
-   *          The handler which should be used to send back the search result
-   *          entries and references to the client.
+   *          The handler which should be used to send back the search results
+   *          to the client.
    * @param intermediateResponseHandler
    *          The handler which should be used to send back any intermediate
    *          responses to the client.
@@ -270,8 +296,7 @@
    *           If this server connection does not handle search requests.
    */
   void handleSearch(C requestContext, SearchRequest request,
-      ResultHandler<? super Result> resultHandler,
-      SearchResultHandler searchResulthandler,
+      SearchResultHandler resultHandler,
       IntermediateResponseHandler intermediateResponseHandler)
       throws UnsupportedOperationException;
 }
diff --git a/sdk/src/org/opends/sdk/ServerConnectionFactory.java b/sdk/src/org/opends/sdk/ServerConnectionFactory.java
index 72f3928..4985d08 100644
--- a/sdk/src/org/opends/sdk/ServerConnectionFactory.java
+++ b/sdk/src/org/opends/sdk/ServerConnectionFactory.java
@@ -31,17 +31,26 @@
 
 /**
  * A handler interface for accepting new connections from clients.
+ * <p>
+ * A connection listener implementation, such as {@link LDAPListener} or
+ * {@link Connections#newInternalConnectionFactory newInternalConnectionFactory}
+ * , invoke the method {@link #handleAccept(Object) handleAccept} whenever a new
+ * client connection is accepted.
  *
  * @param <C>
  *          The type of client context.
  * @param <R>
  *          The type of request context.
+ * @see LDAPListener
+ * @see Connections#newInternalConnectionFactory(ServerConnectionFactory,
+ *      Object) newInternalConnectionFactory
  */
 public interface ServerConnectionFactory<C, R>
 {
   /**
-   * Returns a {@code ServerConnection} which will be used to handle requests
-   * from a client connection.
+   * Invoked when a new client connection is accepted by the associated
+   * listener. Implementations should return a {@code ServerConnection} which
+   * will be used to handle requests from the client connection.
    *
    * @param clientContext
    *          The protocol dependent context information associated with the
@@ -49,11 +58,11 @@
    *          information about the client such as their address and level
    *          connection security. It may also be used to manage the state of
    *          the client's connection.
-   * @return a {@code ServerConnection} which will be used to handle requests
+   * @return A {@code ServerConnection} which will be used to handle requests
    *         from a client connection.
    * @throws ErrorResultException
    *           If this server connection factory cannot accept the client
    *           connection.
    */
-  ServerConnection<R> accept(C clientContext) throws ErrorResultException;
+  ServerConnection<R> handleAccept(C clientContext) throws ErrorResultException;
 }
diff --git a/sdk/src/org/opends/sdk/SynchronousConnection.java b/sdk/src/org/opends/sdk/SynchronousConnection.java
index b1bac79..b382b5c 100644
--- a/sdk/src/org/opends/sdk/SynchronousConnection.java
+++ b/sdk/src/org/opends/sdk/SynchronousConnection.java
@@ -39,6 +39,7 @@
 import java.util.concurrent.BlockingQueue;
 
 
+
 /**
  * A {@code SynchronousConnection} adapts an {@code AsynchronousConnection} into
  * a synchronous {@code Connection}.
@@ -361,15 +362,27 @@
     }
   }
 
+
+
   /**
    * {@inheritDoc}
    */
   public ConnectionEntryReader search(final SearchRequest request,
-                            BlockingQueue<Response> entries)
-      throws UnsupportedOperationException, IllegalStateException,
-      NullPointerException
+      BlockingQueue<Response> entries) throws UnsupportedOperationException,
+      IllegalStateException, NullPointerException
   {
-    return new ConnectionEntryReader(getAsynchronousConnection(),
-        request, entries);
+    return new ConnectionEntryReader(getAsynchronousConnection(), request,
+        entries);
   }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return connection.toString();
+  }
+
 }
diff --git a/sdk/src/org/opends/sdk/controls/package-info.java b/sdk/src/org/opends/sdk/controls/package-info.java
index 7ec769a..da216de 100755
--- a/sdk/src/org/opends/sdk/controls/package-info.java
+++ b/sdk/src/org/opends/sdk/controls/package-info.java
@@ -22,11 +22,11 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 /**
- * Classes implementing common LDAP controls.
+ * Classes and interfaces for common LDAP controls.
  */
 package org.opends.sdk.controls;
 
diff --git a/sdk/src/org/opends/sdk/package-info.java b/sdk/src/org/opends/sdk/package-info.java
index fdd33fd..6275f95 100755
--- a/sdk/src/org/opends/sdk/package-info.java
+++ b/sdk/src/org/opends/sdk/package-info.java
@@ -22,11 +22,12 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 /**
- * Core OpenDS SDK API including connections, entries, and attributes.
+ * Classes and interfaces for core types including connections, entries, and
+ * attributes.
  */
 package org.opends.sdk;
 
diff --git a/sdk/src/org/opends/sdk/requests/AbandonRequest.java b/sdk/src/org/opends/sdk/requests/AbandonRequest.java
index b8e705d..305c931 100644
--- a/sdk/src/org/opends/sdk/requests/AbandonRequest.java
+++ b/sdk/src/org/opends/sdk/requests/AbandonRequest.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk.requests;
@@ -70,23 +70,23 @@
 
 
   /**
-   * Returns the message ID of the request to be abandoned.
+   * Returns the request ID of the request to be abandoned.
    *
-   * @return The message ID of the request to be abandoned.
+   * @return The request ID of the request to be abandoned.
    */
-  int getMessageID();
+  int getRequestID();
 
 
 
   /**
-   * Sets the message ID of the request to be abandoned.
+   * Sets the request ID of the request to be abandoned.
    *
    * @param id
-   *          The message ID of the request to be abandoned.
+   *          The request ID of the request to be abandoned.
    * @return This abandon request.
    * @throws UnsupportedOperationException
-   *           If this abandon request does not permit the message ID to be set.
+   *           If this abandon request does not permit the request ID to be set.
    */
-  AbandonRequest setMessageID(int id) throws UnsupportedOperationException;
+  AbandonRequest setRequestID(int id) throws UnsupportedOperationException;
 
 }
diff --git a/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java b/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java
index 21df3a9..b09275d 100644
--- a/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk.requests;
@@ -36,26 +36,26 @@
     implements AbandonRequest
 {
 
-  private int messageID;
+  private int requestID;
 
 
 
   /**
    * Creates a new abandon request using the provided message ID.
    *
-   * @param messageID
+   * @param requestID
    *          The message ID of the request to be abandoned.
    */
-  AbandonRequestImpl(final int messageID)
+  AbandonRequestImpl(final int requestID)
   {
-    this.messageID = messageID;
+    this.requestID = requestID;
   }
 
 
 
-  public int getMessageID()
+  public int getRequestID()
   {
-    return messageID;
+    return requestID;
   }
 
 
@@ -63,10 +63,10 @@
   /**
    * {@inheritDoc}
    */
-  public AbandonRequest setMessageID(final int id)
+  public AbandonRequest setRequestID(final int id)
       throws UnsupportedOperationException
   {
-    this.messageID = id;
+    this.requestID = id;
     return this;
   }
 
@@ -79,8 +79,8 @@
   public String toString()
   {
     final StringBuilder builder = new StringBuilder();
-    builder.append("AbandonRequest(messageID=");
-    builder.append(getMessageID());
+    builder.append("AbandonRequest(requestID=");
+    builder.append(getRequestID());
     builder.append(", controls=");
     builder.append(getControls());
     builder.append(")");
diff --git a/sdk/src/org/opends/sdk/requests/CancelExtendedRequest.java b/sdk/src/org/opends/sdk/requests/CancelExtendedRequest.java
index 7bae585..e8468a2 100644
--- a/sdk/src/org/opends/sdk/requests/CancelExtendedRequest.java
+++ b/sdk/src/org/opends/sdk/requests/CancelExtendedRequest.java
@@ -93,18 +93,18 @@
 
 
   /**
-   * Returns the message ID of the request to be abandoned.
-   *
-   * @return The message ID of the request to be abandoned.
+   * {@inheritDoc}
    */
-  int getMessageID();
+  String getOID();
 
 
 
   /**
-   * {@inheritDoc}
+   * Returns the request ID of the request to be abandoned.
+   *
+   * @return The request ID of the request to be abandoned.
    */
-  String getOID();
+  int getRequestID();
 
 
 
@@ -130,14 +130,14 @@
 
 
   /**
-   * Sets the message ID of the request to be abandoned.
+   * Sets the request ID of the request to be abandoned.
    *
    * @param id
-   *          The message ID of the request to be abandoned.
+   *          The request ID of the request to be abandoned.
    * @return This abandon request.
    * @throws UnsupportedOperationException
-   *           If this abandon request does not permit the message ID to be set.
+   *           If this abandon request does not permit the request ID to be set.
    */
-  CancelExtendedRequest setMessageID(int id)
+  CancelExtendedRequest setRequestID(int id)
       throws UnsupportedOperationException;
 }
diff --git a/sdk/src/org/opends/sdk/requests/CancelExtendedRequestImpl.java b/sdk/src/org/opends/sdk/requests/CancelExtendedRequestImpl.java
index 627fa69..51e89f2 100644
--- a/sdk/src/org/opends/sdk/requests/CancelExtendedRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/CancelExtendedRequestImpl.java
@@ -40,6 +40,7 @@
 import org.opends.sdk.asn1.ASN1Reader;
 import org.opends.sdk.asn1.ASN1Writer;
 import org.opends.sdk.controls.Control;
+import org.opends.sdk.responses.AbstractExtendedResultDecoder;
 import org.opends.sdk.responses.ExtendedResult;
 import org.opends.sdk.responses.ExtendedResultDecoder;
 import org.opends.sdk.responses.Responses;
@@ -94,10 +95,10 @@
 
 
 
-  private static final class ResultDecoder implements
-      ExtendedResultDecoder<ExtendedResult>
+  private static final class ResultDecoder extends
+      AbstractExtendedResultDecoder<ExtendedResult>
   {
-    public ExtendedResult adaptExtendedErrorResult(final ResultCode resultCode,
+    public ExtendedResult newExtendedErrorResult(final ResultCode resultCode,
         final String matchedDN, final String diagnosticMessage)
     {
       return Responses.newGenericExtendedResult(resultCode).setMatchedDN(
@@ -116,7 +117,7 @@
 
 
 
-  private int messageID;
+  private int requestID;
 
   // No need to expose this.
   private static final ExtendedResultDecoder<ExtendedResult> RESULT_DECODER = new ResultDecoder();
@@ -124,9 +125,9 @@
 
 
   // Instantiation via factory.
-  CancelExtendedRequestImpl(final int messageID)
+  CancelExtendedRequestImpl(final int requestID)
   {
-    this.messageID = messageID;
+    this.requestID = requestID;
   }
 
 
@@ -134,9 +135,9 @@
   /**
    * {@inheritDoc}
    */
-  public int getMessageID()
+  public int getRequestID()
   {
-    return messageID;
+    return requestID;
   }
 
 
@@ -175,7 +176,7 @@
     try
     {
       writer.writeStartSequence();
-      writer.writeInteger(messageID);
+      writer.writeInteger(requestID);
       writer.writeEndSequence();
     }
     catch (final IOException ioe)
@@ -203,9 +204,9 @@
   /**
    * {@inheritDoc}
    */
-  public CancelExtendedRequest setMessageID(final int id)
+  public CancelExtendedRequest setRequestID(final int id)
   {
-    this.messageID = id;
+    this.requestID = id;
     return this;
   }
 
@@ -220,8 +221,8 @@
     final StringBuilder builder = new StringBuilder();
     builder.append("CancelExtendedRequest(requestName=");
     builder.append(getOID());
-    builder.append(", messageID=");
-    builder.append(messageID);
+    builder.append(", requestID=");
+    builder.append(requestID);
     builder.append(", controls=");
     builder.append(getControls());
     builder.append(")");
diff --git a/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequest.java b/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequest.java
index 53dc7e8..f5363e5 100644
--- a/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequest.java
+++ b/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequest.java
@@ -30,6 +30,7 @@
 
 
 import java.util.List;
+import java.util.Map;
 
 import org.opends.sdk.*;
 import org.opends.sdk.controls.Control;
@@ -64,68 +65,98 @@
    */
   public static final String SASL_MECHANISM_NAME = "DIGEST-MD5";
 
-
+  /**
+   * Indicates that the client will accept authentication only. More
+   * specifically, the underlying connection will not be protected using
+   * integrity protection or encryption, unless previously established using
+   * SSL/TLS. This is the default if no QOP option is present in the bind
+   * request.
+   */
+  public static final String QOP_AUTH = "auth";
 
   /**
-   * Supported quality-of-protection options.
+   * Indicates that the client will accept authentication with connection
+   * integrity protection. More specifically, the underlying connection will not
+   * be encrypted, unless previously established using SSL/TLS.
    */
-  public static enum QOPOption
-  {
-    /**
-     * Authentication only.
-     */
-    AUTH,
+  public static final String QOP_AUTH_INT = "auth-int";
 
-    /**
-     * Authentication plus integrity protection.
-     */
-    AUTH_INT,
+  /**
+   * Indicates that the client will accept authentication with connection
+   * integrity protection and encryption.
+   */
+  public static final String QOP_AUTH_CONF = "auth-conf";
 
-    /**
-     * Authentication plus integrity and confidentiality protection.
-     */
-    AUTH_CONF
-  }
+  /**
+   * Indicates that the client will accept connection encryption using the high
+   * strength triple-DES cipher.
+   */
+  public static final String CIPHER_3DES = "3des";
+
+  /**
+   * Indicates that the client will accept connection encryption using the high
+   * strength 128-bit RC4 cipher.
+   */
+  public static final String CIPHER_RC4_128 = "rc4";
+
+  /**
+   * Indicates that the client will accept connection encryption using the
+   * medium strength DES cipher.
+   */
+  public static final String CIPHER_DES = "des";
+
+  /**
+   * Indicates that the client will accept connection encryption using the
+   * medium strength 56-bit RC4 cipher.
+   */
+  public static final String CIPHER_RC4_56 = "rc4-56";
+
+  /**
+   * Indicates that the client will accept connection encryption using the low
+   * strength 40-bit RC4 cipher.
+   */
+  public static final String CIPHER_RC4_40 = "rc4-40";
+
+  /**
+   * Indicates that the client will accept connection encryption using the
+   * strongest supported cipher, as long as the cipher is considered to be high
+   * strength.
+   */
+  public static final String CIPHER_HIGH = "high";
+
+  /**
+   * Indicates that the client will accept connection encryption using the
+   * strongest supported cipher, as long as the cipher is considered to be high
+   * or medium strength.
+   */
+  public static final String CIPHER_MEDIUM = "medium";
+
+  /**
+   * Indicates that the client will accept connection encryption using the
+   * strongest supported cipher, even if the strongest cipher is considered to
+   * be medium or low strength.
+   */
+  public static final String CIPHER_LOW = "low";
 
 
 
   /**
-   * Cipher options for use with the security layer.
+   * Adds the provided additional authentication parameter to the list of
+   * parameters to be passed to the underlying mechanism implementation. This
+   * method is provided in order to allow for future extensions.
+   *
+   * @param name
+   *          The name of the additional authentication parameter.
+   * @param value
+   *          The value of the additional authentication parameter.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit additional authentication
+   *           parameters to be added.
+   * @throws NullPointerException
+   *           If {@code name} or {@code value} was {@code null}.
    */
-  public static enum CipherOption
-  {
-    /**
-     * Triple DES
-     *   The "triple DES" cipher in CBC mode with EDE with the
-     *   same key for each E stage (aka "two keys mode") for a
-     *   total key length of 112 bits.
-     * <p>
-     * RC4 128 bits
-     *   The RC4 cipher with a 128 bit key.
-     */
-    TRIPLE_DES_RC4,
-
-    /**
-     * DES
-     *   The Data Encryption Standard (DES) cipher [FIPS] in
-     *   cipher block chaining (CBC) mode with a 56 bit key.
-     * <p>
-     * RC4 56 bits
-     *   The RC4 cipher with a 56 bit key.
-     */
-    DES_RC4_56,
-
-    /**
-     * RC4 40 bits
-     *   The RC4 cipher with a 40 bit key.
-     */
-    RC4_40
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  DigestMD5SASLBindRequest addControl(Control control)
+  DigestMD5SASLBindRequest addAdditionalAuthParam(String name, String value)
       throws UnsupportedOperationException, NullPointerException;
 
 
@@ -133,11 +164,58 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  DigestMD5SASLBindRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided quality of protection (QOP) values to the ordered list of
+   * QOP values that the client is willing to accept. The order of the list
+   * specifies the preference order, high to low. Authentication will fail if no
+   * QOP values are recognized or accepted by the server.
+   * <p>
+   * By default the client will accept {@link #QOP_AUTH AUTH}.
+   *
+   * @param qopValues
+   *          The quality of protection values that the client is willing to
+   *          accept.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit QOP values to be added.
+   * @throws NullPointerException
+   *           If {@code qopValues} was {@code null}.
+   * @see #QOP_AUTH
+   * @see #QOP_AUTH_INT
+   * @see #QOP_AUTH_CONF
+   */
+  DigestMD5SASLBindRequest addQOP(String... qopValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   BindClient createBindClient(String serverName) throws ErrorResultException;
 
 
 
   /**
+   * Returns a map containing the provided additional authentication parameters
+   * to be passed to the underlying mechanism implementation. This method is
+   * provided in order to allow for future extensions.
+   *
+   * @return A map containing the provided additional authentication parameters
+   *         to be passed to the underlying mechanism implementation.
+   */
+  Map<String, String> getAdditionalAuthParams();
+
+
+
+  /**
    * Returns the authentication ID of the user. The authentication ID usually
    * has the form "dn:" immediately followed by the distinguished name of the
    * user, or "u:" followed by a user ID string, but other forms are permitted.
@@ -154,6 +232,7 @@
    *
    * @return The authentication mechanism identifier.
    */
+  @Override
   byte getAuthenticationType();
 
 
@@ -172,8 +251,26 @@
 
 
   /**
+   * Returns the cipher name or strength that the client is willing to use when
+   * connection encryption quality of protection, {@link #QOP_AUTH_CONF
+   * AUTH-CONF}, is requested.
+   * <p>
+   * By default the client will accept connection encryption using the strongest
+   * supported cipher, even if the strongest cipher is considered to be medium
+   * or low strength. This is equivalent to {@link #CIPHER_LOW}.
+   *
+   * @return The cipher that the client is willing to use if connection
+   *         encryption QOP is negotiated. May be {@code null}, indicating that
+   *         the default cipher should be used.
+   */
+  String getCipher();
+
+
+
+  /**
    * {@inheritDoc}
    */
+  @Override
   <C extends Control> C getControl(ControlDecoder<C> decoder,
       DecodeOptions options) throws NullPointerException, DecodeException;
 
@@ -182,16 +279,40 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   List<Control> getControls();
 
 
 
   /**
+   * Returns the maximum size of the receive buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * send buffer size. The default size is 65536.
+   *
+   * @return The maximum size of the receive buffer in bytes.
+   */
+  int getMaxReceiveBufferSize();
+
+
+
+  /**
+   * Returns the maximum size of the send buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * receive buffer size. The default size is 65536.
+   *
+   * @return The maximum size of the send buffer in bytes.
+   */
+  int getMaxSendBufferSize();
+
+
+
+  /**
    * Returns the name of the Directory object that the client wishes to bind as,
    * which is always the empty string for SASL authentication.
    *
    * @return The name of the Directory object that the client wishes to bind as.
    */
+  @Override
   String getName();
 
 
@@ -206,6 +327,22 @@
 
 
   /**
+   * Returns the ordered list of quality of protection (QOP) values that the
+   * client is willing to accept. The order of the list specifies the preference
+   * order, high to low. Authentication will fail if no QOP values are
+   * recognized or accepted by the server.
+   * <p>
+   * By default the client will accept {@link #QOP_AUTH AUTH}.
+   *
+   * @return The list of quality of protection values that the client is willing
+   *         to accept. The returned list may be empty indicating that the
+   *         default QOP will be accepted.
+   */
+  List<String> getQOPs();
+
+
+
+  /**
    * Returns the optional realm containing the user's account.
    *
    * @return The name of the realm containing the user's account, which may be
@@ -218,65 +355,18 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   String getSASLMechanism();
 
 
 
   /**
-   * Returns the quality-of-protection options to use.
-   * The order of the list specifies the preference order.
+   * Returns {@code true} if the server must authenticate to the client. The
+   * default is {@code false}.
    *
-   * @return The list of quality-of-protection options to use.
+   * @return {@code true} if the server must authenticate to the client.
    */
-  QOPOption[] getQOP();
-
-
-
-  /**
-   * Returns the ciphers to use with the optional security layer
-   * offered by the {@code AUTH_CONF} quality-of-protection. The order
-   * of the list specifies the preference order. When there is
-   * more than one choice for a particular option, the cipher
-   * selected depends on the availability of the ciphers in the
-   * underlying platform.
-   *
-   * @return The list of cipher options to use.
-   */
-  CipherOption[] getCipher();
-
-
-
-  /**
-   * Returns whether the server must authenticate to the client.
-   *
-   * @return {@code true} if the server must authenticate
-   *         to the client or {@code false} otherwise.
-   */
-  boolean getServerAuth();
-
-
-
-  /**
-   * Returns the maximum size of the receive buffer in bytes.
-   * The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum send
-   * buffer size.
-   *
-   * @return The maximum size of the receive buffer in bytes.
-   */
-  int getMaxReceiveBufferSize();
-
-
-
-  /**
-   * Returns the maximum size of the send buffer in bytes.
-   * The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum receive
-   * buffer size.
-   *
-   * @return The maximum size of the send buffer in bytes.
-   */
-  int getMaxSendBufferSize();
+  boolean isServerAuth();
 
 
 
@@ -291,11 +381,15 @@
    * @throws LocalizedIllegalArgumentException
    *           If {@code authenticationID} was non-empty and did not contain a
    *           valid authorization ID type.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the authentication ID to be
+   *           set.
    * @throws NullPointerException
    *           If {@code authenticationID} was {@code null}.
    */
   DigestMD5SASLBindRequest setAuthenticationID(String authenticationID)
-      throws LocalizedIllegalArgumentException, NullPointerException;
+      throws LocalizedIllegalArgumentException, UnsupportedOperationException,
+      NullPointerException;
 
 
 
@@ -312,9 +406,76 @@
    * @throws LocalizedIllegalArgumentException
    *           If {@code authorizationID} was non-empty and did not contain a
    *           valid authorization ID type.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the authorization ID to be
+   *           set.
    */
   DigestMD5SASLBindRequest setAuthorizationID(String authorizationID)
-      throws LocalizedIllegalArgumentException;
+      throws LocalizedIllegalArgumentException, UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the cipher name or strength that the client is willing to use when
+   * connection encryption quality of protection, {@link #QOP_AUTH_CONF
+   * AUTH-CONF}, is requested.
+   * <p>
+   * By default the client will accept connection encryption using the strongest
+   * supported cipher, even if the strongest cipher is considered to be medium
+   * or low strength. This is equivalent to {@link #CIPHER_LOW}.
+   *
+   * @param cipher
+   *          The cipher that the client is willing to use if connection
+   *          encryption QOP is negotiated. May be {@code null}, indicating that
+   *          the default cipher should be used.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the cipher name or strength
+   *           to be set.
+   * @see #QOP_AUTH_CONF
+   * @see #CIPHER_3DES
+   * @see #CIPHER_RC4_128
+   * @see #CIPHER_DES
+   * @see #CIPHER_RC4_56
+   * @see #CIPHER_RC4_40
+   * @see #CIPHER_HIGH
+   * @see #CIPHER_MEDIUM
+   * @see #CIPHER_LOW
+   */
+  DigestMD5SASLBindRequest setCipher(String cipher)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the maximum size of the receive buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * send buffer size. The default size is 65536.
+   *
+   * @param size
+   *          The maximum size of the receive buffer in bytes.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the buffer size to be set.
+   */
+  DigestMD5SASLBindRequest setMaxReceiveBufferSize(int size)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the maximum size of the send buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * receive buffer size. The default size is 65536.
+   *
+   * @param size
+   *          The maximum size of the send buffer in bytes.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the buffer size to be set.
+   */
+  DigestMD5SASLBindRequest setMaxSendBufferSize(int size)
+      throws UnsupportedOperationException;
 
 
 
@@ -370,64 +531,16 @@
 
 
   /**
-   * Specifies the quality-of-protection options to use.
-   * The order of the list specifies the preference order.
+   * Specifies whether or not the server must authenticate to the client. The
+   * default is {@code false}.
    *
-   * @param qopOptions The list of quality-of-protection options to
-   *                   use.
+   * @param serverAuth
+   *          {@code true} if the server must authenticate to the client or
+   *          {@code false} otherwise.
    * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit server auth to be set.
    */
-  DigestMD5SASLBindRequest setQOP(QOPOption... qopOptions);
-
-
-
-  /**
-   * Specifies the ciphers to use with the optional security layer
-   * offered by the {@code AUTH_CONF} quality-of-protection. The order
-   * of the list specifies the preference order. When there is
-   * more than one choice for a particular option, the cipher
-   * selected depends on the availability of the ciphers in the
-   * underlying platform.
-   *
-   * @param cipherOptions The list of cipher options to use.
-   * @return his bind request.
-   */
-  DigestMD5SASLBindRequest setCipher(CipherOption... cipherOptions);
-
-
-
-  /**
-   * Specifies whether the server must authenticate to the client.
-   *
-   * @param serverAuth {@code true} if the server must authenticate
-   *                   to the client or {@code false} otherwise.
-   * @return This bind request.
-   */
-  DigestMD5SASLBindRequest setServerAuth(boolean serverAuth);
-
-
-
-  /**
-   * Specifies the maximum size of the receive buffer in bytes.
-   * The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum send
-   * buffer size.
-   *
-   * @param maxBuffer The maximum size of the receive buffer in bytes.
-   * @return This bind request.
-   */
-  DigestMD5SASLBindRequest setMaxReceiveBufferSize(int maxBuffer);
-
-
-
-  /**
-   * Specifies the maximum size of the send buffer in bytes.
-   * The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum receive
-   * buffer size.
-   *
-   * @param maxBuffer The maximum size of the send buffer in bytes.
-   * @return This bind request.
-   */
-  DigestMD5SASLBindRequest setMaxSendBufferSize(int maxBuffer);
+  DigestMD5SASLBindRequest setServerAuth(boolean serverAuth)
+      throws UnsupportedOperationException;
 }
diff --git a/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequestImpl.java b/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequestImpl.java
index 24b4148..cce0537 100644
--- a/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/DigestMD5SASLBindRequestImpl.java
@@ -31,9 +31,9 @@
 
 import static com.sun.opends.sdk.messages.Messages.ERR_SASL_PROTOCOL_ERROR;
 import static com.sun.opends.sdk.util.StaticUtils.getExceptionMessage;
+import static com.sun.opends.sdk.util.StaticUtils.joinCollection;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import javax.security.auth.callback.NameCallback;
 import javax.security.auth.callback.PasswordCallback;
@@ -76,15 +76,69 @@
       this.password = initialBindRequest.getPassword();
       this.realm = initialBindRequest.getRealm();
 
+      // Create property map containing all the parameters.
       final Map<String, String> props = new HashMap<String, String>();
-      props.put(Sasl.QOP, "auth-conf,auth-int,auth");
 
+      final List<String> qopValues = initialBindRequest.getQOPs();
+      if (!qopValues.isEmpty())
+      {
+        props.put(Sasl.QOP, joinCollection(qopValues, ","));
+      }
+
+      final String cipher = initialBindRequest.getCipher();
+      if (cipher != null)
+      {
+        if (cipher.equalsIgnoreCase(CIPHER_LOW))
+        {
+          props.put(Sasl.STRENGTH, "high,medium,low");
+        }
+        else if (cipher.equalsIgnoreCase(CIPHER_MEDIUM))
+        {
+          props.put(Sasl.STRENGTH, "high,medium");
+        }
+        else if (cipher.equalsIgnoreCase(CIPHER_HIGH))
+        {
+          props.put(Sasl.STRENGTH, "high");
+        }
+        else
+        {
+          // Default strength allows all ciphers, so specifying a single cipher
+          // cannot be incompatible with the strength.
+          props.put("com.sun.security.sasl.digest.cipher", cipher);
+        }
+      }
+
+      final Boolean serverAuth = initialBindRequest.isServerAuth();
+      if (serverAuth != null)
+      {
+        props.put(Sasl.SERVER_AUTH, String.valueOf(serverAuth));
+      }
+
+      Integer size = initialBindRequest.getMaxReceiveBufferSize();
+      if (size != null)
+      {
+        props.put(Sasl.MAX_BUFFER, String.valueOf(size));
+      }
+
+      size = initialBindRequest.getMaxSendBufferSize();
+      if (size != null)
+      {
+        props.put("javax.security.sasl.sendmaxbuffer", String.valueOf(size));
+      }
+
+      for (final Map.Entry<String, String> e : initialBindRequest
+          .getAdditionalAuthParams().entrySet())
+      {
+        props.put(e.getKey(), e.getValue());
+      }
+
+      // Now create the client.
       try
       {
         saslClient = Sasl.createSaslClient(
-            new String[] { SASL_MECHANISM_NAME }, initialBindRequest
-                .getAuthorizationID(), SASL_DEFAULT_PROTOCOL, serverName,
-            props, this);
+            new String[] { SASL_MECHANISM_NAME },
+            initialBindRequest.getAuthorizationID(), SASL_DEFAULT_PROTOCOL,
+            serverName, props, this);
         if (saslClient.hasInitialResponse())
         {
           setNextSASLCredentials(saslClient.evaluateChallenge(new byte[0]));
@@ -125,17 +179,18 @@
       try
       {
         setNextSASLCredentials(saslClient.evaluateChallenge(result
-            .getServerSASLCredentials() == null ? new byte[0] :
-            result.getServerSASLCredentials().toByteArray()));
+            .getServerSASLCredentials() == null ? new byte[0] : result
+            .getServerSASLCredentials().toByteArray()));
         return saslClient.isComplete();
       }
       catch (final SaslException e)
       {
         // FIXME: I18N need to have a better error message.
         // FIXME: Is this the best result code?
-        throw ErrorResultException.wrap(Responses.newResult(
-            ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
-            "An error occurred during multi-stage authentication")
+        throw ErrorResultException.wrap(Responses
+            .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+            .setDiagnosticMessage(
+                "An error occurred during multi-stage authentication")
             .setCause(e));
       }
     }
@@ -170,9 +225,9 @@
       {
         final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(
             SASL_MECHANISM_NAME, getExceptionMessage(e));
-        throw ErrorResultException.wrap(Responses.newResult(
-            ResultCode.CLIENT_SIDE_DECODING_ERROR).setDiagnosticMessage(
-            msg.toString()).setCause(e));
+        throw ErrorResultException.wrap(Responses
+            .newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR)
+            .setDiagnosticMessage(msg.toString()).setCause(e));
       }
     }
 
@@ -190,9 +245,9 @@
       {
         final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(
             SASL_MECHANISM_NAME, getExceptionMessage(e));
-        throw ErrorResultException.wrap(Responses.newResult(
-            ResultCode.CLIENT_SIDE_ENCODING_ERROR).setDiagnosticMessage(
-            msg.toString()).setCause(e));
+        throw ErrorResultException.wrap(Responses
+            .newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR)
+            .setDiagnosticMessage(msg.toString()).setCause(e));
       }
     }
 
@@ -234,10 +289,19 @@
 
 
 
+  private final Map<String, String> additionalAuthParams = new LinkedHashMap<String, String>();
+  private final List<String> qopValues = new LinkedList<String>();
+  private String cipher = null;
+
+  // Don't use primitives for these so that we can distinguish between default
+  // settings (null) and values set by the caller.
+  private Boolean serverAuth = null;
+  private Integer maxReceiveBufferSize = null;
+  private Integer maxSendBufferSize = null;
+
   private String authenticationID;
   private String authorizationID = null;
   private ByteString password;
-
   private String realm = null;
 
 
@@ -255,6 +319,38 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public DigestMD5SASLBindRequest addAdditionalAuthParam(final String name,
+      final String value) throws UnsupportedOperationException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(name, value);
+    additionalAuthParams.put(name, value);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public DigestMD5SASLBindRequest addQOP(final String... qopValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    for (final String qopValue : qopValues)
+    {
+      this.qopValues.add(Validator.ensureNotNull(qopValue));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public BindClient createBindClient(final String serverName)
       throws ErrorResultException
   {
@@ -266,6 +362,18 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public Map<String, String> getAdditionalAuthParams()
+  {
+    return additionalAuthParams;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public String getAuthenticationID()
   {
     return authenticationID;
@@ -276,6 +384,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public String getAuthorizationID()
   {
     return authorizationID;
@@ -286,6 +395,40 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public String getCipher()
+  {
+    return cipher;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int getMaxReceiveBufferSize()
+  {
+    return maxReceiveBufferSize == null ? 65536 : maxReceiveBufferSize;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int getMaxSendBufferSize()
+  {
+    return maxSendBufferSize == null ? 65536 : maxSendBufferSize;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public ByteString getPassword()
   {
     return password;
@@ -296,6 +439,18 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public List<String> getQOPs()
+  {
+    return qopValues;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public String getRealm()
   {
     return realm;
@@ -306,6 +461,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public String getSASLMechanism()
   {
     return SASL_MECHANISM_NAME;
@@ -316,6 +472,18 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public boolean isServerAuth()
+  {
+    return serverAuth == null ? false : serverAuth;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public DigestMD5SASLBindRequest setAuthenticationID(
       final String authenticationID) throws NullPointerException
   {
@@ -329,6 +497,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public DigestMD5SASLBindRequest setAuthorizationID(
       final String authorizationID)
   {
@@ -341,6 +510,46 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public DigestMD5SASLBindRequest setCipher(final String cipher)
+      throws UnsupportedOperationException
+  {
+    this.cipher = cipher;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public DigestMD5SASLBindRequest setMaxReceiveBufferSize(final int size)
+      throws UnsupportedOperationException
+  {
+    maxReceiveBufferSize = size;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public DigestMD5SASLBindRequest setMaxSendBufferSize(final int size)
+      throws UnsupportedOperationException
+  {
+    maxSendBufferSize = size;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public DigestMD5SASLBindRequest setPassword(final ByteString password)
       throws NullPointerException
   {
@@ -354,6 +563,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public DigestMD5SASLBindRequest setPassword(final String password)
       throws NullPointerException
   {
@@ -367,6 +577,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public DigestMD5SASLBindRequest setRealm(final String realm)
   {
     this.realm = realm;
@@ -378,216 +589,11 @@
   /**
    * {@inheritDoc}
    */
-  public QOPOption[] getQOP() {
-    String value = System.getProperty(Sasl.QOP);
-    if(value == null || value.length() == 0)
-    {
-      return new QOPOption[]{QOPOption.AUTH};
-    }
-    String[] values = value.split(",");
-    QOPOption[] options = new QOPOption[values.length];
-
-    for(int i = 0; i < values.length; i++)
-    {
-      String v = values[i].trim();
-      if(v.equalsIgnoreCase("auth"))
-      {
-        options[i] = QOPOption.AUTH;
-      }
-      else if(v.equalsIgnoreCase("auth-int"))
-      {
-        options[i] = QOPOption.AUTH_INT;
-      }
-      else if(v.equalsIgnoreCase("auth-conf"))
-      {
-        options[i] = QOPOption.AUTH_CONF;
-      }
-    }
-    return options;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public CipherOption[] getCipher() {
-    String value = System.getProperty(Sasl.STRENGTH);
-    if(value == null || value.length() == 0)
-    {
-      return new CipherOption[]{CipherOption.TRIPLE_DES_RC4,
-          CipherOption.DES_RC4_56, CipherOption.RC4_40};
-    }
-    String[] values = value.split(",");
-    CipherOption[] options = new CipherOption[values.length];
-
-    for(int i = 0; i < values.length; i++)
-    {
-      String v = values[i].trim();
-      if(v.equalsIgnoreCase("high"))
-      {
-        options[i] = CipherOption.TRIPLE_DES_RC4;
-      }
-      else if(v.equalsIgnoreCase("medium"))
-      {
-        options[i] = CipherOption.DES_RC4_56;
-      }
-      else if(v.equalsIgnoreCase("low"))
-      {
-        options[i] = CipherOption.RC4_40;
-      }
-    }
-    return options;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public boolean getServerAuth() {
-    String value = System.getProperty(Sasl.SERVER_AUTH);
-    return !(value == null || value.length() == 0) &&
-        value.equalsIgnoreCase("true");
-
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public int getMaxReceiveBufferSize() {
-    String value = System.getProperty(Sasl.MAX_BUFFER);
-    if(value == null || value.length() == 0)
-    {
-      return 65536;
-    }
-    return Integer.parseInt(value);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public int getMaxSendBufferSize() {
-    String value = System.getProperty("javax.security.sasl.sendmaxbuffer");
-    if(value == null || value.length() == 0)
-    {
-      return 65536;
-    }
-    return Integer.parseInt(value);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public DigestMD5SASLBindRequest setQOP(QOPOption... qopOptions) {
-    String values = null;
-    for(QOPOption option : qopOptions)
-    {
-      String value = null;
-      if(option == QOPOption.AUTH)
-      {
-        value = "auth";
-      }
-      else if(option == QOPOption.AUTH_INT)
-      {
-        value = "auth-int";
-      }
-      else if(option == QOPOption.AUTH_CONF)
-      {
-        value = "auth-conf";
-      }
-
-      if(value != null)
-      {
-        if(values == null)
-        {
-          values = value;
-        }
-        else
-        {
-          values += (", " + value);
-        }
-      }
-    }
-
-    System.setProperty(Sasl.QOP, values);
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public DigestMD5SASLBindRequest setCipher(CipherOption... cipherOptions) {
-    String values = null;
-    for(CipherOption option : cipherOptions)
-    {
-      String value = null;
-      if(option == CipherOption.TRIPLE_DES_RC4)
-      {
-        value = "high";
-      }
-      else if(option == CipherOption.DES_RC4_56)
-      {
-        value = "medium";
-      }
-      else if(option == CipherOption.RC4_40)
-      {
-        value = "low";
-      }
-
-      if(value != null)
-      {
-        if(values == null)
-        {
-          values = value;
-        }
-        else
-        {
-          values += (", " + value);
-        }
-      }
-    }
-
-    System.setProperty(Sasl.STRENGTH, values);
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public DigestMD5SASLBindRequest setServerAuth(boolean serverAuth) {
-    System.setProperty(Sasl.SERVER_AUTH, String.valueOf(serverAuth));
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public DigestMD5SASLBindRequest setMaxReceiveBufferSize(int maxBuffer) {
-    System.setProperty(Sasl.MAX_BUFFER, String.valueOf(maxBuffer));
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public DigestMD5SASLBindRequest setMaxSendBufferSize(int maxBuffer) {
-    System.setProperty("javax.security.sasl.sendmaxbuffer",
-        String.valueOf(maxBuffer));
+  @Override
+  public DigestMD5SASLBindRequest setServerAuth(final boolean serverAuth)
+      throws UnsupportedOperationException
+  {
+    this.serverAuth = serverAuth;
     return this;
   }
 
diff --git a/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequest.java b/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequest.java
index e3d37f7..814efae 100644
--- a/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequest.java
+++ b/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequest.java
@@ -30,6 +30,7 @@
 
 
 import java.util.List;
+import java.util.Map;
 
 import javax.security.auth.Subject;
 
@@ -63,42 +64,101 @@
    */
   public static final String SASL_MECHANISM_NAME = "GSSAPI";
 
+  /**
+   * Indicates that the client will accept authentication only. More
+   * specifically, the underlying connection will not be protected using
+   * integrity protection or encryption, unless previously established using
+   * SSL/TLS. This is the default if no QOP option is present in the bind
+   * request.
+   */
+  public static final String QOP_AUTH = "auth";
+
+  /**
+   * Indicates that the client will accept authentication with connection
+   * integrity protection. More specifically, the underlying connection will not
+   * be encrypted, unless previously established using SSL/TLS.
+   */
+  public static final String QOP_AUTH_INT = "auth-int";
+
+  /**
+   * Indicates that the client will accept authentication with connection
+   * integrity protection and encryption.
+   */
+  public static final String QOP_AUTH_CONF = "auth-conf";
+
 
 
   /**
-   * Supported quality-of-protection options.
+   * Adds the provided additional authentication parameter to the list of
+   * parameters to be passed to the underlying mechanism implementation. This
+   * method is provided in order to allow for future extensions.
+   *
+   * @param name
+   *          The name of the additional authentication parameter.
+   * @param value
+   *          The value of the additional authentication parameter.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit additional authentication
+   *           parameters to be added.
+   * @throws NullPointerException
+   *           If {@code name} or {@code value} was {@code null}.
    */
-  public static enum QOPOption
-  {
-    /**
-     * Authentication only.
-     */
-    AUTH,
+  GSSAPISASLBindRequest addAdditionalAuthParam(String name, String value)
+      throws UnsupportedOperationException, NullPointerException;
 
-    /**
-     * Authentication plus integrity protection.
-     */
-    AUTH_INT,
 
-    /**
-     * Authentication plus integrity and confidentiality protection.
-     */
-    AUTH_CONF
-  }
+
+  /**
+   * Returns a map containing the provided additional authentication parameters
+   * to be passed to the underlying mechanism implementation. This method is
+   * provided in order to allow for future extensions.
+   *
+   * @return A map containing the provided additional authentication parameters
+   *         to be passed to the underlying mechanism implementation.
+   */
+  Map<String, String> getAdditionalAuthParams();
 
 
 
   /**
    * {@inheritDoc}
    */
+  @Override
   GSSAPISASLBindRequest addControl(Control control)
       throws UnsupportedOperationException, NullPointerException;
 
 
 
   /**
+   * Adds the provided quality of protection (QOP) values to the ordered list of
+   * QOP values that the client is willing to accept. The order of the list
+   * specifies the preference order, high to low. Authentication will fail if no
+   * QOP values are recognized or accepted by the server.
+   * <p>
+   * By default the client will accept {@link #QOP_AUTH AUTH}.
+   *
+   * @param qopValues
+   *          The quality of protection values that the client is willing to
+   *          accept.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit QOP values to be added.
+   * @throws NullPointerException
+   *           If {@code qopValues} was {@code null}.
+   * @see #QOP_AUTH
+   * @see #QOP_AUTH_INT
+   * @see #QOP_AUTH_CONF
+   */
+  GSSAPISASLBindRequest addQOP(String... qopValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
    * {@inheritDoc}
    */
+  @Override
   BindClient createBindClient(String serverName) throws ErrorResultException;
 
 
@@ -123,6 +183,7 @@
    *
    * @return The authentication mechanism identifier.
    */
+  @Override
   byte getAuthenticationType();
 
 
@@ -143,6 +204,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   <C extends Control> C getControl(ControlDecoder<C> decoder,
       DecodeOptions options) throws NullPointerException, DecodeException;
 
@@ -151,6 +213,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   List<Control> getControls();
 
 
@@ -168,11 +231,34 @@
 
 
   /**
+   * Returns the maximum size of the receive buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * send buffer size. The default size is 65536.
+   *
+   * @return The maximum size of the receive buffer in bytes.
+   */
+  int getMaxReceiveBufferSize();
+
+
+
+  /**
+   * Returns the maximum size of the send buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * receive buffer size. The default size is 65536.
+   *
+   * @return The maximum size of the send buffer in bytes.
+   */
+  int getMaxSendBufferSize();
+
+
+
+  /**
    * Returns the name of the Directory object that the client wishes to bind as,
    * which is always the empty string for SASL authentication.
    *
    * @return The name of the Directory object that the client wishes to bind as.
    */
+  @Override
   String getName();
 
 
@@ -189,6 +275,22 @@
 
 
   /**
+   * Returns the ordered list of quality of protection (QOP) values that the
+   * client is willing to accept. The order of the list specifies the preference
+   * order, high to low. Authentication will fail if no QOP values are
+   * recognized or accepted by the server.
+   * <p>
+   * By default the client will accept {@link #QOP_AUTH AUTH}.
+   *
+   * @return The list of quality of protection values that the client is willing
+   *         to accept. The returned list may be empty indicating that the
+   *         default QOP will be accepted.
+   */
+  List<String> getQOPs();
+
+
+
+  /**
    * Returns the optional realm containing the user's account.
    * <p>
    * <b>NOTE</b>: this will not be used if a {@code Subject} is specified.
@@ -203,6 +305,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   String getSASLMechanism();
 
 
@@ -220,48 +323,12 @@
 
 
   /**
-   * Returns the quality-of-protection options to use.
-   * The order of the list specifies the preference order.
+   * Returns {@code true} if the server must authenticate to the client. The
+   * default is {@code false}.
    *
-   * @return The list of quality-of-protection options to use.
+   * @return {@code true} if the server must authenticate to the client.
    */
-  QOPOption[] getQOP();
-
-
-
-  /**
-   * Returns whether the server must authenticate to the client.
-   * The default is {@code false}.
-   *
-   * @return {@code true} if the server must authenticate
-   *         to the client or {@code false} otherwise.
-   */
-  boolean getServerAuth();
-
-
-
-  /**
-   * Returns the maximum size of the receive buffer in bytes. The
-   * default is 65536. The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum send
-   * buffer size.
-   *
-   * @return The maximum size of the receive buffer in bytes.
-   */
-  int getMaxReceiveBufferSize();
-
-
-
-  /**
-   * Returns the maximum size of the send buffer in bytes. The
-   * default is 65536. The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum receive
-   * buffer size.
-   *
-   * @return The maximum size of the send buffer in bytes.
-   */
-  int getMaxSendBufferSize();
-
+  boolean isServerAuth();
 
 
 
@@ -326,6 +393,38 @@
 
 
   /**
+   * Sets the maximum size of the receive buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * send buffer size. The default size is 65536.
+   *
+   * @param size
+   *          The maximum size of the receive buffer in bytes.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the buffer size to be set.
+   */
+  GSSAPISASLBindRequest setMaxReceiveBufferSize(int size)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the maximum size of the send buffer in bytes. The actual maximum
+   * number of bytes will be the minimum of this number and the peer's maximum
+   * receive buffer size. The default size is 65536.
+   *
+   * @param size
+   *          The maximum size of the send buffer in bytes.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the buffer size to be set.
+   */
+  GSSAPISASLBindRequest setMaxSendBufferSize(int size)
+      throws UnsupportedOperationException;
+
+
+
+  /**
    * Sets the password of the user that the client wishes to bind as.
    * <p>
    * <b>NOTE</b>: this will not be used if a {@code Subject} is specified.
@@ -383,6 +482,22 @@
 
 
   /**
+   * Specifies whether or not the server must authenticate to the client. The
+   * default is {@code false}.
+   *
+   * @param serverAuth
+   *          {@code true} if the server must authenticate to the client or
+   *          {@code false} otherwise.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit server auth to be set.
+   */
+  GSSAPISASLBindRequest setServerAuth(boolean serverAuth)
+      throws UnsupportedOperationException;
+
+
+
+  /**
    * Sets the Kerberos subject of the user to be authenticated.
    * <p>
    * <b>NOTE</b>: if a {@code Subject} is specified then the authentication ID,
@@ -395,54 +510,4 @@
    *           If {@code subject} was {@code null}.
    */
   GSSAPISASLBindRequest setSubject(Subject subject) throws NullPointerException;
-
-
-
-  /**
-   * Specifies the quality-of-protection options to use.
-   * The order of the list specifies the preference order.
-   *
-   * @param qopOptions The list of quality-of-protection options to
-   *                   use.
-   * @return This bind request.
-   */
-  GSSAPISASLBindRequest setQOP(QOPOption... qopOptions);
-
-
-
-  /**
-   * Specifies whether the server must authenticate to the client.
-   *
-   * @param serverAuth {@code true} if the server must authenticate
-   *                   to the client or {@code false} otherwise.
-   * @return This bind request.
-   */
-  GSSAPISASLBindRequest setServerAuth(boolean serverAuth);
-
-
-
-  /**
-   * Specifies the maximum size of the receive buffer in bytes.
-   * The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum send
-   * buffer size.
-   *
-   * @param maxBuffer The maximum size of the receive buffer in bytes.
-   * @return This bind request.
-   */
-  GSSAPISASLBindRequest setMaxReceiveBufferSize(int maxBuffer);
-
-
-
-  /**
-   * Specifies the maximum size of the send buffer in bytes.
-   *  The actual maximum number of bytes will
-   * be the minimum of this number and the peer's maximum receive
-   * buffer size.
-   *
-   * @param maxBuffer The maximum size of the send buffer in bytes.
-   * @return This bind request.
-   */
-  GSSAPISASLBindRequest setMaxSendBufferSize(int maxBuffer);
-
 }
diff --git a/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequestImpl.java b/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequestImpl.java
index 62ae2e0..afee8e3 100644
--- a/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/GSSAPISASLBindRequestImpl.java
@@ -33,11 +33,11 @@
 import static com.sun.opends.sdk.messages.Messages.ERR_SASL_CONTEXT_CREATE_ERROR;
 import static com.sun.opends.sdk.messages.Messages.ERR_SASL_PROTOCOL_ERROR;
 import static com.sun.opends.sdk.util.StaticUtils.getExceptionMessage;
+import static com.sun.opends.sdk.util.StaticUtils.joinCollection;
 
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginException;
@@ -118,9 +118,9 @@
         // FIXME: Is this the best result code?
         final LocalizableMessage message = ERR_LDAPAUTH_GSSAPI_LOCAL_AUTHENTICATION_FAILED
             .get(StaticUtils.getExceptionMessage(e));
-        throw ErrorResultException.wrap(Responses.newResult(
-            ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
-            message.toString()).setCause(e));
+        throw ErrorResultException.wrap(Responses
+            .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+            .setDiagnosticMessage(message.toString()).setCause(e));
       }
       return subject;
     }
@@ -128,7 +128,6 @@
 
 
     private final SaslClient saslClient;
-    private final String serverName;
     private final String authorizationID;
     private final Subject subject;
 
@@ -137,6 +136,7 @@
     private final PrivilegedExceptionAction<Boolean> evaluateAction =
       new PrivilegedExceptionAction<Boolean>()
     {
+      @Override
       public Boolean run() throws ErrorResultException
       {
         if (lastResult.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS
@@ -152,9 +152,10 @@
           {
             // FIXME: I18N need to have a better error message.
             // FIXME: Is this the best result code?
-            throw ErrorResultException.wrap(Responses.newResult(
-                ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
-                "An error occurred during multi-stage authentication")
+            throw ErrorResultException.wrap(Responses
+                .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+                .setDiagnosticMessage(
+                    "An error occurred during multi-stage authentication")
                 .setCause(e));
           }
         }
@@ -162,37 +163,6 @@
       }
     };
 
-    private final PrivilegedExceptionAction<SaslClient> invokeAction =
-      new PrivilegedExceptionAction<SaslClient>()
-    {
-      public SaslClient run() throws ErrorResultException
-      {
-        final Map<String, String> props = new HashMap<String, String>();
-        props.put(Sasl.QOP, "auth-conf,auth-int,auth");
-
-        try
-        {
-          final SaslClient saslClient = Sasl.createSaslClient(
-              new String[] { SASL_MECHANISM_NAME }, authorizationID,
-              SASL_DEFAULT_PROTOCOL, serverName, props, Client.this);
-          if (saslClient.hasInitialResponse())
-          {
-            setNextSASLCredentials(saslClient.evaluateChallenge(new byte[0]));
-          }
-          else
-          {
-            setNextSASLCredentials((ByteString) null);
-          }
-        }
-        catch (final SaslException e)
-        {
-          throw ErrorResultException.wrap(Responses.newResult(
-              ResultCode.CLIENT_SIDE_LOCAL_ERROR).setCause(e));
-        }
-        return saslClient;
-      }
-    };
-
 
 
     private Client(final GSSAPISASLBindRequestImpl initialBindRequest,
@@ -211,11 +181,72 @@
             initialBindRequest.getPassword(), initialBindRequest.getRealm(),
             initialBindRequest.getKDCAddress());
       }
-      this.serverName = serverName;
 
       try
       {
-        this.saslClient = Subject.doAs(subject, invokeAction);
+        this.saslClient = Subject.doAs(subject,
+            new PrivilegedExceptionAction<SaslClient>()
+            {
+              @Override
+              public SaslClient run() throws ErrorResultException
+              {
+                // Create property map containing all the parameters.
+                final Map<String, String> props = new HashMap<String, String>();
+
+                final List<String> qopValues = initialBindRequest.getQOPs();
+                if (!qopValues.isEmpty())
+                {
+                  props.put(Sasl.QOP, joinCollection(qopValues, ","));
+                }
+
+                final Boolean serverAuth = initialBindRequest.isServerAuth();
+                if (serverAuth != null)
+                {
+                  props.put(Sasl.SERVER_AUTH, String.valueOf(serverAuth));
+                }
+
+                Integer size = initialBindRequest.getMaxReceiveBufferSize();
+                if (size != null)
+                {
+                  props.put(Sasl.MAX_BUFFER, String.valueOf(size));
+                }
+
+                size = initialBindRequest.getMaxSendBufferSize();
+                if (size != null)
+                {
+                  props.put("javax.security.sasl.sendmaxbuffer",
+                      String.valueOf(size));
+                }
+
+                for (final Map.Entry<String, String> e : initialBindRequest
+                    .getAdditionalAuthParams().entrySet())
+                {
+                  props.put(e.getKey(), e.getValue());
+                }
+
+                try
+                {
+                  final SaslClient saslClient = Sasl.createSaslClient(
+                      new String[] { SASL_MECHANISM_NAME }, authorizationID,
+                      SASL_DEFAULT_PROTOCOL, serverName, props, Client.this);
+                  if (saslClient.hasInitialResponse())
+                  {
+                    setNextSASLCredentials(saslClient
+                        .evaluateChallenge(new byte[0]));
+                  }
+                  else
+                  {
+                    setNextSASLCredentials((ByteString) null);
+                  }
+                }
+                catch (final SaslException e)
+                {
+                  throw ErrorResultException.wrap(Responses.newResult(
+                      ResultCode.CLIENT_SIDE_LOCAL_ERROR).setCause(e));
+                }
+                return saslClient;
+              }
+            });
       }
       catch (final PrivilegedActionException e)
       {
@@ -228,9 +259,9 @@
           // This should not happen. Must be a bug.
           final LocalizableMessage msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(
               SASL_MECHANISM_NAME, getExceptionMessage(e));
-          throw ErrorResultException.wrap(Responses.newResult(
-              ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
-              msg.toString()).setCause(e));
+          throw ErrorResultException.wrap(Responses
+              .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+              .setDiagnosticMessage(msg.toString()).setCause(e));
         }
       }
     }
@@ -272,9 +303,9 @@
           // This should not happen. Must be a bug.
           final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(
               SASL_MECHANISM_NAME, getExceptionMessage(e));
-          throw ErrorResultException.wrap(Responses.newResult(
-              ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
-              msg.toString()).setCause(e));
+          throw ErrorResultException.wrap(Responses
+              .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+              .setDiagnosticMessage(msg.toString()).setCause(e));
         }
       }
     }
@@ -309,9 +340,9 @@
       {
         final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(
             SASL_MECHANISM_NAME, getExceptionMessage(e));
-        throw ErrorResultException.wrap(Responses.newResult(
-            ResultCode.CLIENT_SIDE_DECODING_ERROR).setDiagnosticMessage(
-            msg.toString()).setCause(e));
+        throw ErrorResultException.wrap(Responses
+            .newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR)
+            .setDiagnosticMessage(msg.toString()).setCause(e));
       }
     }
 
@@ -329,9 +360,9 @@
       {
         final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(
             SASL_MECHANISM_NAME, getExceptionMessage(e));
-        throw ErrorResultException.wrap(Responses.newResult(
-            ResultCode.CLIENT_SIDE_ENCODING_ERROR).setDiagnosticMessage(
-            msg.toString()).setCause(e));
+        throw ErrorResultException.wrap(Responses
+            .newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR)
+            .setDiagnosticMessage(msg.toString()).setCause(e));
       }
     }
 
@@ -341,6 +372,7 @@
 
   // If null then authenticationID and password must be present.
   private Subject subject = null;
+
   // Ignored if subject is non-null.
   private String authenticationID = null;
   private ByteString password = null;
@@ -351,6 +383,15 @@
   // Optional authorization ID.
   private String authorizationID = null;
 
+  private final Map<String, String> additionalAuthParams = new LinkedHashMap<String, String>();
+  private final List<String> qopValues = new LinkedList<String>();
+
+  // Don't use primitives for these so that we can distinguish between default
+  // settings (null) and values set by the caller.
+  private Boolean serverAuth = null;
+  private Integer maxReceiveBufferSize = null;
+  private Integer maxSendBufferSize = null;
+
 
 
   GSSAPISASLBindRequestImpl(final String authenticationID,
@@ -374,6 +415,38 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public GSSAPISASLBindRequest addAdditionalAuthParam(final String name,
+      final String value) throws UnsupportedOperationException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(name, value);
+    additionalAuthParams.put(name, value);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public GSSAPISASLBindRequest addQOP(final String... qopValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    for (final String qopValue : qopValues)
+    {
+      this.qopValues.add(Validator.ensureNotNull(qopValue));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public BindClient createBindClient(final String serverName)
       throws ErrorResultException
   {
@@ -385,6 +458,18 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public Map<String, String> getAdditionalAuthParams()
+  {
+    return additionalAuthParams;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public String getAuthenticationID()
   {
     return authenticationID;
@@ -395,6 +480,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public String getAuthorizationID()
   {
     return authorizationID;
@@ -405,6 +491,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public String getKDCAddress()
   {
     return kdcAddress;
@@ -415,6 +502,29 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public int getMaxReceiveBufferSize()
+  {
+    return maxReceiveBufferSize == null ? 65536 : maxReceiveBufferSize;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int getMaxSendBufferSize()
+  {
+    return maxSendBufferSize == null ? 65536 : maxSendBufferSize;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public ByteString getPassword()
   {
     return password;
@@ -425,6 +535,18 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public List<String> getQOPs()
+  {
+    return qopValues;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public String getRealm()
   {
     return realm;
@@ -435,6 +557,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public String getSASLMechanism()
   {
     return SASL_MECHANISM_NAME;
@@ -445,6 +568,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public Subject getSubject()
   {
     return subject;
@@ -455,6 +579,18 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public boolean isServerAuth()
+  {
+    return serverAuth == null ? false : serverAuth;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public GSSAPISASLBindRequest setAuthenticationID(final String authenticationID)
       throws NullPointerException
   {
@@ -468,6 +604,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public GSSAPISASLBindRequest setAuthorizationID(final String authorizationID)
   {
     this.authorizationID = authorizationID;
@@ -479,6 +616,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public GSSAPISASLBindRequest setKDCAddress(final String address)
   {
     this.kdcAddress = address;
@@ -490,6 +628,33 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public GSSAPISASLBindRequest setMaxReceiveBufferSize(final int size)
+      throws UnsupportedOperationException
+  {
+    maxReceiveBufferSize = size;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public GSSAPISASLBindRequest setMaxSendBufferSize(final int size)
+      throws UnsupportedOperationException
+  {
+    maxSendBufferSize = size;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public GSSAPISASLBindRequest setPassword(final ByteString password)
       throws NullPointerException
   {
@@ -503,6 +668,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public GSSAPISASLBindRequest setPassword(final String password)
       throws NullPointerException
   {
@@ -516,6 +682,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public GSSAPISASLBindRequest setRealm(final String realm)
   {
     this.realm = realm;
@@ -527,6 +694,20 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public GSSAPISASLBindRequest setServerAuth(final boolean serverAuth)
+      throws UnsupportedOperationException
+  {
+    this.serverAuth = serverAuth;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public GSSAPISASLBindRequest setSubject(final Subject subject)
       throws NullPointerException
   {
@@ -539,150 +720,6 @@
   /**
    * {@inheritDoc}
    */
-  public QOPOption[] getQOP() {
-    String value = System.getProperty(Sasl.QOP);
-    if(value == null || value.length() == 0)
-    {
-      return new QOPOption[]{QOPOption.AUTH};
-    }
-    String[] values = value.split(",");
-    QOPOption[] options = new QOPOption[values.length];
-
-    for(int i = 0; i < values.length; i++)
-    {
-      String v = values[i].trim();
-      if(v.equalsIgnoreCase("auth"))
-      {
-        options[i] = QOPOption.AUTH;
-      }
-      else if(v.equalsIgnoreCase("auth-int"))
-      {
-        options[i] = QOPOption.AUTH_INT;
-      }
-      else if(v.equalsIgnoreCase("auth-conf"))
-      {
-        options[i] = QOPOption.AUTH_CONF;
-      }
-    }
-    return options;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public boolean getServerAuth() {
-    String value = System.getProperty(Sasl.SERVER_AUTH);
-    return !(value == null || value.length() == 0) &&
-        value.equalsIgnoreCase("true");
-
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public int getMaxReceiveBufferSize() {
-    String value = System.getProperty(Sasl.MAX_BUFFER);
-    if(value == null || value.length() == 0)
-    {
-      return 65536;
-    }
-    return Integer.parseInt(value);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public int getMaxSendBufferSize() {
-    String value = System.getProperty("javax.security.sasl.sendmaxbuffer");
-    if(value == null || value.length() == 0)
-    {
-      return 65536;
-    }
-    return Integer.parseInt(value);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public GSSAPISASLBindRequest setQOP(QOPOption... qopOptions) {
-    String values = null;
-    for(QOPOption option : qopOptions)
-    {
-      String value = null;
-      if(option == QOPOption.AUTH)
-      {
-        value = "auth";
-      }
-      else if(option == QOPOption.AUTH_INT)
-      {
-        value = "auth-int";
-      }
-      else if(option == QOPOption.AUTH_CONF)
-      {
-        value = "auth-conf";
-      }
-
-      if(value != null)
-      {
-        if(values == null)
-        {
-          values = value;
-        }
-        else
-        {
-          values += (", " + value);
-        }
-      }
-    }
-
-    System.setProperty(Sasl.QOP, values);
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public GSSAPISASLBindRequest setServerAuth(boolean serverAuth) {
-    System.setProperty(Sasl.SERVER_AUTH, String.valueOf(serverAuth));
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public GSSAPISASLBindRequest setMaxReceiveBufferSize(int maxBuffer) {
-    System.setProperty(Sasl.MAX_BUFFER, String.valueOf(maxBuffer));
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public GSSAPISASLBindRequest setMaxSendBufferSize(int maxBuffer) {
-    System.setProperty("javax.security.sasl.sendmaxbuffer",
-        String.valueOf(maxBuffer));
-    return this;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public String toString()
   {
diff --git a/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java b/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java
index cb3321d..b11f25d 100644
--- a/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java
@@ -22,22 +22,16 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk.requests;
 
 
 
-import org.opends.sdk.ByteString;
-import org.opends.sdk.DecodeException;
-import org.opends.sdk.DecodeOptions;
-import org.opends.sdk.ResultCode;
+import org.opends.sdk.*;
 import org.opends.sdk.controls.Control;
-import org.opends.sdk.responses.ExtendedResult;
-import org.opends.sdk.responses.ExtendedResultDecoder;
-import org.opends.sdk.responses.GenericExtendedResult;
-import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.*;
 
 import com.sun.opends.sdk.util.StaticUtils;
 import com.sun.opends.sdk.util.Validator;
@@ -80,11 +74,11 @@
 
 
 
-  private static final class GenericExtendedResultDecoder implements
-      ExtendedResultDecoder<GenericExtendedResult>
+  private static final class GenericExtendedResultDecoder extends
+      AbstractExtendedResultDecoder<GenericExtendedResult>
   {
 
-    public GenericExtendedResult adaptExtendedErrorResult(
+    public GenericExtendedResult newExtendedErrorResult(
         final ResultCode resultCode, final String matchedDN,
         final String diagnosticMessage)
     {
diff --git a/sdk/src/org/opends/sdk/requests/PasswordModifyExtendedRequestImpl.java b/sdk/src/org/opends/sdk/requests/PasswordModifyExtendedRequestImpl.java
index a32a7b6..ee19507 100644
--- a/sdk/src/org/opends/sdk/requests/PasswordModifyExtendedRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/PasswordModifyExtendedRequestImpl.java
@@ -39,10 +39,7 @@
 import org.opends.sdk.asn1.ASN1Reader;
 import org.opends.sdk.asn1.ASN1Writer;
 import org.opends.sdk.controls.Control;
-import org.opends.sdk.responses.ExtendedResult;
-import org.opends.sdk.responses.ExtendedResultDecoder;
-import org.opends.sdk.responses.PasswordModifyExtendedResult;
-import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.*;
 
 
 
@@ -105,10 +102,10 @@
 
 
 
-  private static final class ResultDecoder implements
-      ExtendedResultDecoder<PasswordModifyExtendedResult>
+  private static final class ResultDecoder extends
+      AbstractExtendedResultDecoder<PasswordModifyExtendedResult>
   {
-    public PasswordModifyExtendedResult adaptExtendedErrorResult(
+    public PasswordModifyExtendedResult newExtendedErrorResult(
         final ResultCode resultCode, final String matchedDN,
         final String diagnosticMessage)
     {
diff --git a/sdk/src/org/opends/sdk/requests/Request.java b/sdk/src/org/opends/sdk/requests/Request.java
index 741fe77..8243a26 100644
--- a/sdk/src/org/opends/sdk/requests/Request.java
+++ b/sdk/src/org/opends/sdk/requests/Request.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk.requests;
@@ -41,8 +41,6 @@
 /**
  * The base class of all Requests provides methods for querying and manipulating
  * the set of Controls included with a Request.
- * <p>
- * TODO: added complete description including sub-types.
  */
 public interface Request
 {
diff --git a/sdk/src/org/opends/sdk/requests/Requests.java b/sdk/src/org/opends/sdk/requests/Requests.java
index 027b1eb..eab61a4 100644
--- a/sdk/src/org/opends/sdk/requests/Requests.java
+++ b/sdk/src/org/opends/sdk/requests/Requests.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk.requests;
@@ -59,13 +59,13 @@
   /**
    * Creates a new abandon request using the provided message ID.
    *
-   * @param messageID
-   *          The message ID of the request to be abandoned.
+   * @param requestID
+   *          The request ID of the request to be abandoned.
    * @return The new abandon request.
    */
-  public static AbandonRequest newAbandonRequest(final int messageID)
+  public static AbandonRequest newAbandonRequest(final int requestID)
   {
-    return new AbandonRequestImpl(messageID);
+    return new AbandonRequestImpl(requestID);
   }
 
 
@@ -187,14 +187,14 @@
   /**
    * Creates a new cancel extended request using the provided message ID.
    *
-   * @param messageID
-   *          The message ID of the request to be abandoned.
+   * @param requestID
+   *          The request ID of the request to be abandoned.
    * @return The new cancel extended request.
    */
   public static CancelExtendedRequest newCancelExtendedRequest(
-      final int messageID)
+      final int requestID)
   {
-    return new CancelExtendedRequestImpl(messageID);
+    return new CancelExtendedRequestImpl(requestID);
   }
 
 
@@ -589,6 +589,42 @@
 
 
   /**
+   * Creates a new modify request containing a list of modifications which can
+   * be used to transform {@code fromEntry} into entry {@code toEntry}.
+   * <p>
+   * The modify request is reversible: it will contain only modifications of
+   * type {@link ModificationType#ADD ADD} and {@link ModificationType#DELETE
+   * DELETE}.
+   * <p>
+   * Finally, the modify request will use the distinguished name taken from
+   * {@code fromEntry}. Moreover, this method will not check to see if both
+   * {@code fromEntry} and {@code toEntry} have the same distinguished name.
+   * <p>
+   * This method is equivalent to:
+   *
+   * <pre>
+   * ModifyRequest request = Entries.diffEntries(fromEntry, toEntry);
+   * </pre>
+   *
+   * @param fromEntry
+   *          The source entry.
+   * @param toEntry
+   *          The destination entry.
+   * @return A modify request containing a list of modifications which can be
+   *         used to transform {@code fromEntry} into entry {@code toEntry}.
+   * @throws NullPointerException
+   *           If {@code fromEntry} or {@code toEntry} were {@code null}.
+   * @see Entries#diffEntries(Entry, Entry)
+   */
+  public static final ModifyRequest newModifyRequest(Entry fromEntry,
+      Entry toEntry) throws NullPointerException
+  {
+    return Entries.diffEntries(fromEntry, toEntry);
+  }
+
+
+
+  /**
    * Creates a new modify request using the provided distinguished name decoded
    * using the default schema.
    *
diff --git a/sdk/src/org/opends/sdk/requests/SearchRequest.java b/sdk/src/org/opends/sdk/requests/SearchRequest.java
index 38e00dc..e0e965a 100644
--- a/sdk/src/org/opends/sdk/requests/SearchRequest.java
+++ b/sdk/src/org/opends/sdk/requests/SearchRequest.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk.requests;
@@ -47,20 +47,20 @@
 public interface SearchRequest extends Request
 {
   /**
-   * Adds the provided attribute name to the list of attributes to be included
-   * with each entry that matches the search criteria. Attributes that are
-   * sub-types of listed attributes are implicitly included.
+   * Adds the provided attribute name(s) to the list of attributes to be
+   * included with each entry that matches the search criteria. Attributes that
+   * are sub-types of listed attributes are implicitly included.
    *
-   * @param attributeDescription
-   *          The name of the attribute to be included with each entry.
+   * @param attributeDescriptions
+   *          The name(s) of the attribute to be included with each entry.
    * @return This search request.
    * @throws UnsupportedOperationException
    *           If this search request does not permit attribute names to be
    *           added.
    * @throws NullPointerException
-   *           If {@code attributeDescription} was {@code null}.
+   *           If {@code attributeDescriptions} was {@code null}.
    */
-  SearchRequest addAttribute(String attributeDescription)
+  SearchRequest addAttribute(String... attributeDescriptions)
       throws UnsupportedOperationException, NullPointerException;
 
 
diff --git a/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java b/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java
index 2337021..a56b610 100644
--- a/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk.requests;
@@ -94,11 +94,13 @@
   /**
    * {@inheritDoc}
    */
-  public SearchRequest addAttribute(final String attributeDescription)
+  public SearchRequest addAttribute(final String... attributeDescriptions)
       throws NullPointerException
   {
-    Validator.ensureNotNull(attributeDescription);
-    attributes.add(attributeDescription);
+    for (String attributeDescription : attributeDescriptions)
+    {
+      attributes.add(Validator.ensureNotNull(attributeDescription));
+    }
     return this;
   }
 
diff --git a/sdk/src/org/opends/sdk/requests/StartTLSExtendedRequestImpl.java b/sdk/src/org/opends/sdk/requests/StartTLSExtendedRequestImpl.java
index 9032e55..513c75e 100644
--- a/sdk/src/org/opends/sdk/requests/StartTLSExtendedRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/StartTLSExtendedRequestImpl.java
@@ -31,15 +31,9 @@
 
 import javax.net.ssl.SSLContext;
 
-import org.opends.sdk.ByteString;
-import org.opends.sdk.DecodeException;
-import org.opends.sdk.DecodeOptions;
-import org.opends.sdk.ResultCode;
+import org.opends.sdk.*;
 import org.opends.sdk.controls.Control;
-import org.opends.sdk.responses.ExtendedResult;
-import org.opends.sdk.responses.ExtendedResultDecoder;
-import org.opends.sdk.responses.GenericExtendedResult;
-import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.*;
 
 import com.sun.opends.sdk.util.Validator;
 
@@ -72,10 +66,10 @@
 
 
 
-  private static final class ResultDecoder implements
-      ExtendedResultDecoder<ExtendedResult>
+  private static final class ResultDecoder extends
+      AbstractExtendedResultDecoder<ExtendedResult>
   {
-    public GenericExtendedResult adaptExtendedErrorResult(
+    public GenericExtendedResult newExtendedErrorResult(
         final ResultCode resultCode, final String matchedDN,
         final String diagnosticMessage)
     {
diff --git a/sdk/src/org/opends/sdk/requests/WhoAmIExtendedRequestImpl.java b/sdk/src/org/opends/sdk/requests/WhoAmIExtendedRequestImpl.java
index bc91781..d1bb727 100644
--- a/sdk/src/org/opends/sdk/requests/WhoAmIExtendedRequestImpl.java
+++ b/sdk/src/org/opends/sdk/requests/WhoAmIExtendedRequestImpl.java
@@ -31,10 +31,7 @@
 
 import org.opends.sdk.*;
 import org.opends.sdk.controls.Control;
-import org.opends.sdk.responses.ExtendedResult;
-import org.opends.sdk.responses.ExtendedResultDecoder;
-import org.opends.sdk.responses.Responses;
-import org.opends.sdk.responses.WhoAmIExtendedResult;
+import org.opends.sdk.responses.*;
 
 
 
@@ -66,10 +63,10 @@
 
 
 
-  private static final class ResultDecoder implements
-      ExtendedResultDecoder<WhoAmIExtendedResult>
+  private static final class ResultDecoder extends
+      AbstractExtendedResultDecoder<WhoAmIExtendedResult>
   {
-    public WhoAmIExtendedResult adaptExtendedErrorResult(
+    public WhoAmIExtendedResult newExtendedErrorResult(
         final ResultCode resultCode, final String matchedDN,
         final String diagnosticMessage)
     {
diff --git a/sdk/src/org/opends/sdk/responses/AbstractExtendedResultDecoder.java b/sdk/src/org/opends/sdk/responses/AbstractExtendedResultDecoder.java
new file mode 100644
index 0000000..d940ca9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractExtendedResultDecoder.java
@@ -0,0 +1,137 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.*;
+import org.opends.sdk.requests.ExtendedRequest;
+
+
+
+/**
+ * This class provides a skeletal implementation of the
+ * {@code ExtendedResultDecoder} interface, to minimize the effort required to
+ * implement this interface.
+ *
+ * @param <S>
+ *          The type of result.
+ */
+public abstract class AbstractExtendedResultDecoder<S extends ExtendedResult>
+    implements ExtendedResultDecoder<S>
+{
+  /**
+   * Creates a new abstract extended result decoder.
+   */
+  protected AbstractExtendedResultDecoder()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public S adaptDecodeException(final DecodeException exception)
+      throws NullPointerException
+  {
+    final S adaptedResult = newExtendedErrorResult(ResultCode.PROTOCOL_ERROR,
+        "", exception.getMessage());
+    adaptedResult.setCause(exception.getCause());
+    return adaptedResult;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public <R extends ExtendedResult> ResultHandler<S> adaptExtendedResultHandler(
+      final ExtendedRequest<R> request,
+      final ResultHandler<? super R> resultHandler, final DecodeOptions options)
+  {
+    return new ResultHandler<S>()
+    {
+
+      @Override
+      public void handleErrorResult(final ErrorResultException error)
+      {
+        final Result result = error.getResult();
+        final R adaptedResult = request.getResultDecoder()
+            .newExtendedErrorResult(result.getResultCode(),
+                result.getMatchedDN(), result.getDiagnosticMessage());
+        adaptedResult.setCause(result.getCause());
+        resultHandler.handleErrorResult(ErrorResultException
+            .wrap(adaptedResult));
+      }
+
+
+
+      @Override
+      public void handleResult(final S result)
+      {
+        try
+        {
+          final R adaptedResult = request.getResultDecoder()
+              .decodeExtendedResult(result, options);
+          resultHandler.handleResult(adaptedResult);
+        }
+        catch (final DecodeException e)
+        {
+          final R adaptedResult = request.getResultDecoder()
+              .adaptDecodeException(e);
+          resultHandler.handleErrorResult(ErrorResultException
+              .wrap(adaptedResult));
+        }
+      }
+
+    };
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public abstract S decodeExtendedResult(ExtendedResult result,
+      DecodeOptions options) throws DecodeException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public abstract S newExtendedErrorResult(ResultCode resultCode,
+      String matchedDN, String diagnosticMessage) throws NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/ExtendedResultDecoder.java b/sdk/src/org/opends/sdk/responses/ExtendedResultDecoder.java
index db59197..5e4c637 100644
--- a/sdk/src/org/opends/sdk/responses/ExtendedResultDecoder.java
+++ b/sdk/src/org/opends/sdk/responses/ExtendedResultDecoder.java
@@ -32,6 +32,8 @@
 import org.opends.sdk.DecodeException;
 import org.opends.sdk.DecodeOptions;
 import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.ExtendedRequest;
 
 
 
@@ -44,25 +46,46 @@
  */
 public interface ExtendedResultDecoder<S extends ExtendedResult>
 {
+
   /**
-   * Adapts the provided error result parameters to an extended operation
-   * result. This method is called when a generic failure occurs, such as a
-   * connection failure, and the error result needs to be converted to a {@code
-   * Result} of type {@code S}.
+   * Creates a new extended operation error result using the provided decoding
+   * exception. This method should be used to adapt {@code DecodeException}
+   * encountered while decoding an extended request or result. The returned
+   * error result will have the result code {@link ResultCode#PROTOCOL_ERROR}.
    *
-   * @param resultCode
-   *          The result code.
-   * @param matchedDN
-   *          The matched DN, which may be empty if none was provided.
-   * @param diagnosticMessage
-   *          The diagnostic message, which may be empty if none was provided.
-   * @return The decoded extended operation error result.
+   * @param exception
+   *          The decoding exception to be adapted.
+   * @return An extended operation error result representing the decoding
+   *         exception.
    * @throws NullPointerException
-   *           If {@code resultCode}, {@code matchedDN}, or {@code
-   *           diagnosticMessage} were {@code null}.
+   *           If {@code exception} was {@code null}.
    */
-  S adaptExtendedErrorResult(ResultCode resultCode, String matchedDN,
-      String diagnosticMessage) throws NullPointerException;
+  S adaptDecodeException(DecodeException exception) throws NullPointerException;
+
+
+
+  /**
+   * Adapts the provided extended result handler into a result handler which is
+   * compatible with this extended result decoder. Extended results handled by
+   * the returned handler will be automatically converted and passed to the
+   * provided result handler. Decoding errors encountered while decoding the
+   * extended result will be converted into protocol errors.
+   *
+   * @param <R>
+   *          The type of result handler to be adapted.
+   * @param request
+   *          The extended request whose result handler is to be adapted.
+   * @param resultHandler
+   *          The extended result handler which is to be adapted.
+   * @param options
+   *          The set of decode options which should be used when decoding the
+   *          extended operation result.
+   * @return A result handler which is compatible with this extended result
+   *         decoder.
+   */
+  <R extends ExtendedResult> ResultHandler<S> adaptExtendedResultHandler(
+      final ExtendedRequest<R> request,
+      final ResultHandler<? super R> resultHandler, DecodeOptions options);
 
 
 
@@ -86,4 +109,26 @@
   S decodeExtendedResult(ExtendedResult result, DecodeOptions options)
       throws DecodeException;
 
+
+
+  /**
+   * Creates a new extended error result using the provided result code, matched
+   * DN, and diagnostic message. This method is called when a generic failure
+   * occurs, such as a connection failure, and the error result needs to be
+   * converted to a {@code Result} of type {@code S}.
+   *
+   * @param resultCode
+   *          The result code.
+   * @param matchedDN
+   *          The matched DN, which may be empty if none was provided.
+   * @param diagnosticMessage
+   *          The diagnostic message, which may be empty if none was provided.
+   * @return The decoded extended operation error result.
+   * @throws NullPointerException
+   *           If {@code resultCode}, {@code matchedDN}, or
+   *           {@code diagnosticMessage} were {@code null}.
+   */
+  S newExtendedErrorResult(ResultCode resultCode, String matchedDN,
+      String diagnosticMessage) throws NullPointerException;
+
 }
diff --git a/sdk/src/overview.html b/sdk/src/overview.html
new file mode 100644
index 0000000..abcc487
--- /dev/null
+++ b/sdk/src/overview.html
@@ -0,0 +1,103 @@
+<html>
+<body>
+    The OpenDS SDK for Java provides a high performance easy to use
+    library of classes and interfaces for accessing and implementing
+    LDAP Directory Services as defined in <a
+      href="http://tools.ietf.org/html/rfc4510">RFC 4510</a>.
+    <br>
+    <h1>Getting started</h1>
+    The following example shows how the OpenDS SDK may be used to
+    connect to a Directory Server, authenticate, and then perform a
+    search. The search results are output as LDIF to the standard
+    output:
+    <br>
+    <table bgcolor="#cccccc" border="0" cellpadding="2" cellspacing="2"
+      width="100%">
+      <tbody>
+        <tr>
+          <pre>    // Create an LDIF writer which will write the search results to stdout.
+    final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
+    Connection connection = null;
+    try
+    {
+      // Connect and bind to the server.
+      final LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", 1389);
+
+      connection = factory.getConnection();
+      connection.bind(userName, password);
+
+      // Read the entries and output them as LDIF.
+      final ConnectionEntryReader reader = connection.search(baseDN, scope, filter, attributes);
+      while (reader.hasNext())
+      {
+        if (!reader.isReference())
+        {
+          // Got an entry.
+          final SearchResultEntry entry = reader.readEntry();
+          writer.writeComment("Search result entry: " + entry.getName().toString());
+          writer.writeEntry(entry);
+        }
+        else
+        {
+          // Got a continuation reference.
+          final SearchResultReference ref = reader.readReference();
+          writer.writeComment("Search result reference: " + ref.getURIs().toString());
+        }
+      }
+      writer.flush();
+    }
+    catch (final Exception e)
+    {
+      // Handle exceptions...
+      System.err.println(e.getMessage());
+    }
+    finally
+    {
+      if (connection != null)
+      {
+        connection.close();
+      }
+    }</pre>
+        </tr>
+      </tbody>
+    </table>
+    <br>
+    Additional examples can be found in the file examples.zip which is
+    included with this SDK.
+    <br>
+    <h1>Creating connections</h1>
+    The following classes can be used to create and manage connections to
+    LDAP Directory Servers:
+    <ul>
+      <li>{@link org.opends.sdk.LDAPConnectionFactory}</li>
+      <li>{@link org.opends.sdk.Connection}</li>
+      <li>{@link org.opends.sdk.Connections}</li>
+    </ul>
+    <br>
+    <h1>Creating requests</h1>
+    The following classes can be used to create LDAP requests:
+    <ul>
+      <li>{@link org.opends.sdk.requests.Requests}</li>
+      <li>{@link org.opends.sdk.requests.Request}</li>
+    </ul>
+    <br>
+    <h1>Using controls</h1>
+    Common LDAP control implementations can be found in
+    {@link org.opends.sdk.controls}.
+    <br>
+    <br>
+    <h1>Core types</h1>
+    The following classes and interfaces represent core types:
+    <ul>
+      <li>{@link org.opends.sdk.AttributeDescription}</li>
+      <li>{@link org.opends.sdk.Attribute}</li>
+      <li>{@link org.opends.sdk.DN}</li>
+      <li>{@link org.opends.sdk.Entry}</li>
+      <li>{@link org.opends.sdk.Filter}</li>
+    </ul>
+    <br>
+@see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 - Lightweight
+      Directory Access Protocol (LDAP): The Protocol </a>
+@see org.opends.sdk
+</body>
+</html>
diff --git a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java
index 95ba625..eac55aa 100644
--- a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferReaderTestCase.java
@@ -35,8 +35,8 @@
 import org.opends.sdk.asn1.ASN1Reader;
 import org.opends.sdk.asn1.ASN1ReaderTestCase;
 
-import com.sun.grizzly.memory.ByteBufferWrapper;
-import com.sun.grizzly.memory.DefaultMemoryManager;
+import org.glassfish.grizzly.memory.ByteBufferWrapper;
+import org.glassfish.grizzly.memory.DefaultMemoryManager;
 
 
 
diff --git a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java
index ac123cb..cc1255f 100644
--- a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/ASN1BufferWriterTestCase.java
@@ -37,8 +37,8 @@
 import org.opends.sdk.asn1.ASN1Writer;
 import org.opends.sdk.asn1.ASN1WriterTestCase;
 
-import com.sun.grizzly.Buffer;
-import com.sun.grizzly.memory.ByteBufferWrapper;
+import org.glassfish.grizzly.Buffer;
+import org.glassfish.grizzly.memory.ByteBufferWrapper;
 
 
 
diff --git a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/GlobalTransportFactoryTestCase.java b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/GlobalTransportFactoryTestCase.java
index 1608679..598cb69 100644
--- a/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/GlobalTransportFactoryTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/com/sun/opends/sdk/ldap/GlobalTransportFactoryTestCase.java
@@ -36,8 +36,8 @@
 
 import org.testng.annotations.Test;
 
-import com.sun.grizzly.TransportFactory;
-import com.sun.grizzly.nio.transport.TCPNIOTransport;
+import org.glassfish.grizzly.TransportFactory;
+import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
 
 
 
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/ConnectionFactoryTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/ConnectionFactoryTestCase.java
index a3a876f..3984c96 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/ConnectionFactoryTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/ConnectionFactoryTestCase.java
@@ -32,12 +32,15 @@
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
+import java.net.InetSocketAddress;
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 import org.opends.sdk.requests.DigestMD5SASLBindRequest;
 import org.opends.sdk.requests.Requests;
 import org.opends.sdk.requests.SearchRequest;
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 import org.testng.annotations.DataProvider;
@@ -45,6 +48,7 @@
 import javax.net.ssl.SSLContext;
 
 
+
 /**
  * Tests the connectionfactory classes.
  */
@@ -55,7 +59,7 @@
     // latch.
     private final CountDownLatch latch;
     // invalid flag.
-    private volatile boolean invalid;
+    private volatile ErrorResultException error;
 
 
 
@@ -69,7 +73,7 @@
     public void handleErrorResult(final ErrorResultException error)
     {
       // came here.
-      invalid = true;
+      this.error = error;
       latch.countDown();
     }
 
@@ -96,66 +100,125 @@
     TestCaseUtils.startServer();
   }
 
+
+
   @DataProvider(name = "connectionFactories")
-  public Object[][] getModifyDNRequests() throws Exception
+  public Object[][] getConnectyionFactories() throws Exception
   {
-    Object[][] factories = new Object[7][1];
+    Object[][] factories = new Object[21][1];
 
     // HeartBeatConnectionFactory
     // Use custom search request.
     SearchRequest request = Requests.newSearchRequest(
-        "uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT, "objectclass=*",
-        "cn");
+        "uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT,
+        "objectclass=*", "cn");
 
-    factories[0][0] = new HeartBeatConnectionFactory(
-        new LDAPConnectionFactory("localhost", TestCaseUtils.getLdapPort()),
-        1000, TimeUnit.MILLISECONDS, request);
+    factories[0][0] = new HeartBeatConnectionFactory(new LDAPConnectionFactory(
+        "localhost", TestCaseUtils.getLdapPort()), 1000, TimeUnit.MILLISECONDS,
+        request);
 
     // InternalConnectionFactory
-    factories[1][0] = Connections
-      .newInternalConnectionFactory(LDAPServer.getInstance(), null);
+    factories[1][0] = Connections.newInternalConnectionFactory(
+        LDAPServer.getInstance(), null);
 
     // AuthenticatedConnectionFactory
     factories[2][0] = new AuthenticatedConnectionFactory(
-      new LDAPConnectionFactory("localhost", TestCaseUtils.getLdapPort()),
-      Requests.newSimpleBindRequest("", ""));
+        new LDAPConnectionFactory("localhost", TestCaseUtils.getLdapPort()),
+        Requests.newSimpleBindRequest("", ""));
 
     // AuthenticatedConnectionFactory with multi-stage SASL
     factories[3][0] = new AuthenticatedConnectionFactory(
-      new LDAPConnectionFactory("localhost", TestCaseUtils.getLdapPort()),
-      Requests.newCRAMMD5SASLBindRequest("id:user",
+        new LDAPConnectionFactory("localhost", TestCaseUtils.getLdapPort()),
+        Requests.newCRAMMD5SASLBindRequest("id:user",
             ByteString.valueOf("password")));
 
     // LDAPConnectionFactory with default options
-    factories[4][0] = new LDAPConnectionFactory(
-      "localhost", TestCaseUtils.getLdapPort());
+    factories[4][0] = new LDAPConnectionFactory("localhost",
+        TestCaseUtils.getLdapPort());
 
     // LDAPConnectionFactory with startTLS
-    SSLContext sslContext = new SSLContextBuilder().
-        setTrustManager(TrustManagers.trustAll()).getSSLContext();
-    LDAPOptions options = new LDAPOptions().setSSLContext(sslContext).
-        setUseStartTLS(true).setEnabledCipherSuites(
-        new String[]{"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
-                         "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
-                         "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
-                         "SSL_DH_anon_WITH_DES_CBC_SHA",
-                         "SSL_DH_anon_WITH_RC4_128_MD5",
-                         "TLS_DH_anon_WITH_AES_128_CBC_SHA",
-                         "TLS_DH_anon_WITH_AES_256_CBC_SHA"});
-    factories[5][0] = new LDAPConnectionFactory(
-        "localhost", TestCaseUtils.getLdapPort(), options);
+    SSLContext sslContext = new SSLContextBuilder().setTrustManager(
+        TrustManagers.trustAll()).getSSLContext();
+    LDAPOptions options = new LDAPOptions()
+        .setSSLContext(sslContext)
+        .setUseStartTLS(true)
+        .setEnabledCipherSuites(
+            new String[] { "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
+                "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
+                "SSL_DH_anon_WITH_DES_CBC_SHA", "SSL_DH_anon_WITH_RC4_128_MD5",
+                "TLS_DH_anon_WITH_AES_128_CBC_SHA",
+                "TLS_DH_anon_WITH_AES_256_CBC_SHA" });
+    factories[5][0] = new LDAPConnectionFactory("localhost",
+        TestCaseUtils.getLdapPort(), options);
 
     // startTLS + SASL confidentiality
+    // Use IP address here so that DIGEST-MD5 host verification works if local
+    // host name is not localhost (e.g. on some machines it might be
+    // localhost.localdomain).
     factories[6][0] = new AuthenticatedConnectionFactory(
-        new LDAPConnectionFactory("localhost", TestCaseUtils.getLdapPort(),
-            options), Requests.newDigestMD5SASLBindRequest("id:user",
-            ByteString.valueOf("password")).setQOP(
-            DigestMD5SASLBindRequest.QOPOption.AUTH_CONF).setCipher(
-            DigestMD5SASLBindRequest.CipherOption.TRIPLE_DES_RC4));
+        new LDAPConnectionFactory(new InetSocketAddress("127.0.0.1",
+            TestCaseUtils.getLdapPort()), options), Requests
+            .newDigestMD5SASLBindRequest("id:user",
+                ByteString.valueOf("password"))
+            .addQOP(DigestMD5SASLBindRequest.QOP_AUTH_CONF)
+            .setCipher(DigestMD5SASLBindRequest.CIPHER_LOW));
+
+    // Connection pool and load balancing tests.
+    ConnectionFactory offlineServer1 = Connections.newNamedConnectionFactory(
+        new LDAPConnectionFactory("localhost", TestCaseUtils.findFreePort()),
+        "offline1");
+    ConnectionFactory offlineServer2 = Connections.newNamedConnectionFactory(
+        new LDAPConnectionFactory("localhost", TestCaseUtils.findFreePort()),
+        "offline2");
+    ConnectionFactory onlineServer = Connections.newNamedConnectionFactory(
+        new LDAPConnectionFactory("localhost", TestCaseUtils.getLdapPort()),
+        "online");
+
+    // Connection pools.
+    factories[7][0] = Connections.newConnectionPool(onlineServer, 10);
+
+    // Round robin.
+    factories[8][0] = Connections
+        .newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(Arrays.asList(
+            onlineServer, offlineServer1)));
+    factories[9][0] = factories[8][0];
+    factories[10][0] = factories[8][0];
+    factories[11][0] = Connections
+        .newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(Arrays.asList(
+            offlineServer1, onlineServer)));
+    factories[12][0] = Connections
+        .newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(Arrays.asList(
+            offlineServer1, offlineServer2, onlineServer)));
+    factories[13][0] = Connections
+        .newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(Arrays.asList(
+            Connections.newConnectionPool(offlineServer1, 10),
+            Connections.newConnectionPool(onlineServer, 10))));
+
+    // Fail-over.
+    factories[14][0] = Connections
+        .newLoadBalancer(new FailoverLoadBalancingAlgorithm(Arrays.asList(
+            onlineServer, offlineServer1)));
+    factories[15][0] = factories[14][0];
+    factories[16][0] = factories[14][0];
+    factories[17][0] = Connections
+        .newLoadBalancer(new FailoverLoadBalancingAlgorithm(Arrays.asList(
+            offlineServer1, onlineServer)));
+    factories[18][0] = Connections
+        .newLoadBalancer(new FailoverLoadBalancingAlgorithm(Arrays.asList(
+            offlineServer1, offlineServer2, onlineServer)));
+    factories[19][0] = Connections
+        .newLoadBalancer(new FailoverLoadBalancingAlgorithm(Arrays.asList(
+            Connections.newConnectionPool(offlineServer1, 10),
+            Connections.newConnectionPool(onlineServer, 10))));
+
+    factories[20][0] = Connections.newConnectionPool(onlineServer, 10);
 
     return factories;
   }
 
+
+
   /**
    * Tests the async connection in the blocking mode. This is not fully async as
    * it blocks on the future.
@@ -166,10 +229,10 @@
   public void testBlockingFutureNoHandler(ConnectionFactory factory)
       throws Exception
   {
-    final FutureResult<AsynchronousConnection> future =
-        factory.getAsynchronousConnection(null);
+    final FutureResult<AsynchronousConnection> future = factory
+        .getAsynchronousConnection(null);
     final AsynchronousConnection con = future.get();
-    // quickly check if iit is a valid connection.
+    // quickly check if it is a valid connection.
     // Don't use a result handler.
     assertNotNull(con.readRootDSE(null).get());
     con.close();
@@ -189,15 +252,14 @@
     // Use the handler to get the result asynchronously.
     final CountDownLatch latch = new CountDownLatch(1);
     final MyResultHandler handler = new MyResultHandler(latch);
-    final FutureResult<AsynchronousConnection> future =
-        factory.getAsynchronousConnection(handler);
+    final FutureResult<AsynchronousConnection> future = factory
+        .getAsynchronousConnection(handler);
     // Since we don't have anything to do, we would rather
     // be notified by the latch when the other thread calls our handler.
     latch.await(); // should do a timed wait rather?
-    if (handler.invalid)
+    if (handler.error != null)
     {
-      // There was an error.
-      throw new Exception();
+      throw handler.error;
     }
   }
 
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/DNTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/DNTestCase.java
index f4a7509..3e5d334 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/DNTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/DNTestCase.java
@@ -188,11 +188,16 @@
         { "cn=aaaa,dc=com", "cn=AAA,dc=com", 1 },
         { "cn=aaab,dc=com", "cn=aaaa,dc=com", 1 },
         { "cn=aaaa,dc=com", "cn=aaab,dc=com", -1 },
-        { "dc=aaa,dc=aaa", "dc=bbb", -1 }, { "dc=bbb,dc=aaa", "dc=bbb", -1 },
-        { "dc=ccc,dc=aaa", "dc=bbb", -1 }, { "dc=aaa,dc=bbb", "dc=bbb", 1 },
-        { "dc=bbb,dc=bbb", "dc=bbb", 1 }, { "dc=ccc,dc=bbb", "dc=bbb", 1 },
-        { "dc=aaa,dc=ccc", "dc=bbb", 1 }, { "dc=bbb,dc=ccc", "dc=bbb", 1 },
-        { "dc=ccc,dc=ccc", "dc=bbb", 1 }, { "", "dc=bbb", -1 },
+        { "dc=aaa,dc=aaa", "dc=bbb", -1 },
+        { "dc=bbb,dc=aaa", "dc=bbb", -1 },
+        { "dc=ccc,dc=aaa", "dc=bbb", -1 },
+        { "dc=aaa,dc=bbb", "dc=bbb", 1 },
+        { "dc=bbb,dc=bbb", "dc=bbb", 1 },
+        { "dc=ccc,dc=bbb", "dc=bbb", 1 },
+        { "dc=aaa,dc=ccc", "dc=bbb", 1 },
+        { "dc=bbb,dc=ccc", "dc=bbb", 1 },
+        { "dc=ccc,dc=ccc", "dc=bbb", 1 },
+        { "", "dc=bbb", -1 },
         { "dc=bbb", "", 1 } };
   }
 
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/EntriesTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/EntriesTestCase.java
new file mode 100644
index 0000000..8b0ee61
--- /dev/null
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/EntriesTestCase.java
@@ -0,0 +1,229 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Iterator;
+
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.requests.Requests;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+
+/**
+ * Test {@code Entries}.
+ */
+public final class EntriesTestCase extends SdkTestCase
+{
+  /**
+   * Creates test data for {@link #testDiffEntries}.
+   *
+   * @return The test data.
+   */
+  @DataProvider(name = "createTestDiffEntriesData")
+  public Object[][] createTestDiffEntriesData()
+  {
+    // @formatter:off
+    Entry empty = new LinkedHashMapEntry(
+        "dn: cn=test",
+        "objectClass: top",
+        "objectClass: test"
+    );
+
+    Entry from = new LinkedHashMapEntry(
+        "dn: cn=test",
+        "objectClass: top",
+        "objectClass: test",
+        "fromOnly: fromOnlyValue",
+        "bothSame: one",
+        "bothSame: two",
+        "bothSame: three",
+        "bothDifferentDeletes: common",
+        "bothDifferentDeletes: fromOnly1",
+        "bothDifferentDeletes: fromOnly2",
+        "bothDifferentAdds: common",
+        "bothDifferentAddsAndDeletes: common",
+        "bothDifferentAddsAndDeletes: fromOnly",
+        "bothDifferentReplace: fromOnly1",
+        "bothDifferentReplace: fromOnly2"
+    );
+
+    Entry to = new LinkedHashMapEntry(
+        "dn: cn=test",
+        "objectClass: top",
+        "objectClass: test",
+        "toOnly: toOnlyValue",
+        "bothSame: one",
+        "bothSame: two",
+        "bothSame: three",
+        "bothDifferentDeletes: common",
+        "bothDifferentAdds: common",
+        "bothDifferentAdds: toOnly1",
+        "bothDifferentAdds: toOnly2",
+        "bothDifferentAddsAndDeletes: common",
+        "bothDifferentAddsAndDeletes: toOnly",
+        "bothDifferentReplace: toOnly1",
+        "bothDifferentReplace: toOnly2"
+    );
+
+    ModifyRequest diffFromEmpty = Requests.newModifyRequest(
+        "dn: cn=test",
+        "changetype: modify",
+        "delete: bothDifferentAdds",
+        "bothDifferentAdds: common",
+        "-",
+        "delete: bothDifferentAddsAndDeletes",
+        "bothDifferentAddsAndDeletes: common",
+        "bothDifferentAddsAndDeletes: fromOnly",
+        "-",
+        "delete: bothDifferentDeletes",
+        "bothDifferentDeletes: common",
+        "bothDifferentDeletes: fromOnly1",
+        "bothDifferentDeletes: fromOnly2",
+        "-",
+        "delete: bothDifferentReplace",
+        "bothDifferentReplace: fromOnly1",
+        "bothDifferentReplace: fromOnly2",
+        "-",
+        "delete: bothSame",
+        "bothSame: one",
+        "bothSame: two",
+        "bothSame: three",
+        "-",
+        "delete: fromOnly",
+        "fromOnly: fromOnlyValue"
+    );
+
+    ModifyRequest diffEmptyTo = Requests.newModifyRequest(
+        "dn: cn=test",
+        "changetype: modify",
+        "add: bothDifferentAdds",
+        "bothDifferentAdds: common",
+        "bothDifferentAdds: toOnly1",
+        "bothDifferentAdds: toOnly2",
+        "-",
+        "add: bothDifferentAddsAndDeletes",
+        "bothDifferentAddsAndDeletes: common",
+        "bothDifferentAddsAndDeletes: toOnly",
+        "-",
+        "add: bothDifferentDeletes",
+        "bothDifferentDeletes: common",
+        "-",
+        "add: bothDifferentReplace",
+        "bothDifferentReplace: toOnly1",
+        "bothDifferentReplace: toOnly2",
+        "-",
+        "add: bothSame",
+        "bothSame: one",
+        "bothSame: two",
+        "bothSame: three",
+        "-",
+        "add: toOnly",
+        "toOnly: toOnlyValue"
+    );
+
+    ModifyRequest diffFromTo = Requests.newModifyRequest(
+        "dn: cn=test",
+        "changetype: modify",
+        "add: bothDifferentAdds",
+        "bothDifferentAdds: toOnly1",
+        "bothDifferentAdds: toOnly2",
+        "-",
+        "add: bothDifferentAddsAndDeletes",
+        "bothDifferentAddsAndDeletes: toOnly",
+        "-",
+        "delete: bothDifferentAddsAndDeletes",
+        "bothDifferentAddsAndDeletes: fromOnly",
+        "-",
+        "delete: bothDifferentDeletes",
+        "bothDifferentDeletes: fromOnly1",
+        "bothDifferentDeletes: fromOnly2",
+        "-",
+        "add: bothDifferentReplace",
+        "bothDifferentReplace: toOnly1",
+        "bothDifferentReplace: toOnly2",
+        "-",
+        "delete: bothDifferentReplace",
+        "bothDifferentReplace: fromOnly1",
+        "bothDifferentReplace: fromOnly2",
+        "-",
+        "delete: fromOnly",
+        "fromOnly: fromOnlyValue",
+        "-",
+        "add: toOnly",
+        "toOnly: toOnlyValue"
+    );
+
+    // From, to, diff.
+    return new Object[][]
+    {
+      { from,  empty, diffFromEmpty },
+      { empty, to,    diffEmptyTo   },
+      { from,  to,    diffFromTo    }
+    };
+
+    // @formatter:on
+  }
+
+
+
+  /**
+   * Tests {@link Entries#diffEntries(Entry, Entry)}.
+   *
+   * @param from
+   *          Source entry.
+   * @param to
+   *          Destination entry.
+   * @param expected
+   *          Expected modifications.
+   */
+  @Test(dataProvider = "createTestDiffEntriesData")
+  public void testDiffEntries(final Entry from, final Entry to,
+      final ModifyRequest expected)
+  {
+    ModifyRequest actual = Entries.diffEntries(from, to);
+
+    Assert.assertEquals(from.getName(), actual.getName());
+    Assert.assertEquals(actual.getModifications().size(), expected
+        .getModifications().size());
+    Iterator<Modification> i1 = actual.getModifications().iterator();
+    Iterator<Modification> i2 = expected.getModifications().iterator();
+    while (i1.hasNext())
+    {
+      Modification m1 = i1.next();
+      Modification m2 = i2.next();
+
+      Assert.assertEquals(m1.getModificationType(), m2.getModificationType());
+      Assert.assertEquals(m1.getAttribute(), m2.getAttribute());
+    }
+  }
+}
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/EntryTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/EntryTestCase.java
index 1303285..f1297e0 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/EntryTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/EntryTestCase.java
@@ -36,7 +36,7 @@
 
 
 /**
- * Test {@code BasicAttribute}.
+ * Test {@code Entry}.
  */
 public final class EntryTestCase extends SdkTestCase
 {
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPListenerTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPListenerTestCase.java
new file mode 100644
index 0000000..6aec25d
--- /dev/null
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPListenerTestCase.java
@@ -0,0 +1,776 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+
+/**
+ * Tests the LDAPListener class.
+ */
+public class LDAPListenerTestCase extends SdkTestCase
+{
+
+  private static class MockServerConnection implements
+      ServerConnection<Integer>
+  {
+    volatile LDAPClientContext context = null;
+    volatile boolean isConnected = false;
+    final CountDownLatch isClosed = new CountDownLatch(1);
+
+
+
+    MockServerConnection()
+    {
+      // Do nothing.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleAbandon(final Integer requestContext,
+        final AbandonRequest request) throws UnsupportedOperationException
+    {
+      // Do nothing.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleAdd(final Integer requestContext,
+        final AddRequest request,
+        final ResultHandler<? super Result> resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleBind(final Integer requestContext, final int version,
+        final BindRequest request,
+        final ResultHandler<? super BindResult> resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleCompare(final Integer requestContext,
+        final CompareRequest request,
+        final ResultHandler<? super CompareResult> resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler
+          .handleResult(Responses.newCompareResult(ResultCode.SUCCESS));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleConnectionClosed(final Integer requestContext,
+        final UnbindRequest request)
+    {
+      isClosed.countDown();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleConnectionDisconnected(final ResultCode resultCode,
+        final String message)
+    {
+      // Do nothing.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleConnectionError(final Throwable error)
+    {
+      // Do nothing.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleDelete(final Integer requestContext,
+        final DeleteRequest request,
+        final ResultHandler<? super Result> resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <R extends ExtendedResult> void handleExtendedRequest(
+        final Integer requestContext, final ExtendedRequest<R> request,
+        final ResultHandler<? super R> resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler
+          .handleErrorResult(ErrorResultException.wrap(request
+              .getResultDecoder().newExtendedErrorResult(
+                  ResultCode.PROTOCOL_ERROR, "",
+                  "Extended operation " + request.getOID() + " not supported")));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleModify(final Integer requestContext,
+        final ModifyRequest request,
+        final ResultHandler<? super Result> resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleModifyDN(final Integer requestContext,
+        final ModifyDNRequest request,
+        final ResultHandler<? super Result> resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleSearch(final Integer requestContext,
+        final SearchRequest request, final SearchResultHandler resultHandler,
+        final IntermediateResponseHandler intermediateResponseHandler)
+        throws UnsupportedOperationException
+    {
+      resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
+    }
+
+  }
+
+
+
+  private static class MockServerConnectionFactory implements
+      ServerConnectionFactory<LDAPClientContext, Integer>
+  {
+
+    private final MockServerConnection serverConnection;
+
+
+
+    private MockServerConnectionFactory(
+        final MockServerConnection serverConnection)
+    {
+      this.serverConnection = serverConnection;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ServerConnection<Integer> handleAccept(
+        final LDAPClientContext clientContext) throws ErrorResultException
+    {
+      serverConnection.context = clientContext;
+      serverConnection.isConnected = true;
+      return serverConnection;
+    }
+  }
+
+
+
+  /**
+   * Tests basic LDAP listener functionality.
+   *
+   * @throws Exception
+   *           If an unexpected exception occurred.
+   */
+  @Test
+  public void testLDAPListenerBasic() throws Exception
+  {
+    final MockServerConnection serverConnection = new MockServerConnection();
+    final MockServerConnectionFactory serverConnectionFactory = new MockServerConnectionFactory(
+        serverConnection);
+    final LDAPListener listener = new LDAPListener("localhost",
+        TestCaseUtils.findFreePort(), serverConnectionFactory);
+    try
+    {
+      // Connect and close.
+      new LDAPConnectionFactory(listener.getSocketAddress()).getConnection()
+          .close();
+
+      Assert.assertTrue(serverConnection.isConnected);
+      Assert.assertTrue(serverConnection.isClosed.await(10, TimeUnit.SECONDS));
+    }
+    finally
+    {
+      listener.close();
+    }
+  }
+
+
+
+  /**
+   * Tests LDAP listener which attempts to open a connection to a remote offline
+   * server at the point when the listener accepts the client connection.
+   *
+   * @throws Exception
+   *           If an unexpected exception occurred.
+   */
+  @Test(enabled = false)
+  public void testLDAPListenerLoadBalanceDuringHandleAccept() throws Exception
+  {
+    // Online server listener.
+    final int onlineServerPort = TestCaseUtils.findFreePort();
+    final MockServerConnection onlineServerConnection = new MockServerConnection();
+    final MockServerConnectionFactory onlineServerConnectionFactory = new MockServerConnectionFactory(
+        onlineServerConnection);
+    final LDAPListener onlineServerListener = new LDAPListener("localhost",
+        onlineServerPort, onlineServerConnectionFactory);
+
+    try
+    {
+      // Connection pool and load balancing tests.
+      final ConnectionFactory offlineServer1 = Connections
+          .newNamedConnectionFactory(new LDAPConnectionFactory("localhost",
+              TestCaseUtils.findFreePort()), "offline1");
+      final ConnectionFactory offlineServer2 = Connections
+          .newNamedConnectionFactory(new LDAPConnectionFactory("localhost",
+              TestCaseUtils.findFreePort()), "offline2");
+      final ConnectionFactory onlineServer = Connections
+          .newNamedConnectionFactory(new LDAPConnectionFactory("localhost",
+              onlineServerPort), "online");
+
+      // Round robin.
+      final ConnectionFactory loadBalancer = Connections
+          .newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(Arrays.asList(
+              Connections.newConnectionPool(offlineServer1, 10),
+              Connections.newConnectionPool(offlineServer2, 10),
+              Connections.newConnectionPool(onlineServer, 10))));
+
+      final MockServerConnection proxyServerConnection = new MockServerConnection();
+      final MockServerConnectionFactory proxyServerConnectionFactory = new MockServerConnectionFactory(
+          proxyServerConnection)
+      {
+
+        @Override
+        public ServerConnection<Integer> handleAccept(
+            final LDAPClientContext clientContext) throws ErrorResultException
+        {
+          // Get connection from load balancer, this should fail over twice
+          // before getting connection to online server.
+          try
+          {
+            loadBalancer.getConnection().close();
+          }
+          catch (final InterruptedException e)
+          {
+            // Unexpected.
+            throw ErrorResultException.newErrorResult(ResultCode.OTHER,
+                "Unexpected exception when connecting to online server", e);
+          }
+          return super.handleAccept(clientContext);
+        }
+
+      };
+
+      final LDAPListener proxyListener = new LDAPListener("localhost",
+          TestCaseUtils.findFreePort(), proxyServerConnectionFactory);
+      try
+      {
+        // Connect and close.
+        new LDAPConnectionFactory(proxyListener.getSocketAddress())
+            .getConnection().close();
+
+        // Wait for connect/close to complete.
+        proxyServerConnection.isClosed.await();
+
+        Assert.assertTrue(proxyServerConnection.isConnected);
+        Assert.assertTrue(onlineServerConnection.isConnected);
+      }
+      finally
+      {
+        proxyListener.close();
+      }
+    }
+    finally
+    {
+      onlineServerListener.close();
+    }
+  }
+
+
+
+  /**
+   * Tests LDAP listener which attempts to open a connection to a load balancing
+   * pool at the point when the listener handles a bind request.
+   *
+   * @throws Exception
+   *           If an unexpected exception occurred.
+   */
+  @Test
+  public void testLDAPListenerLoadBalanceDuringHandleBind() throws Exception
+  {
+    // Online server listener.
+    final int onlineServerPort = TestCaseUtils.findFreePort();
+    final MockServerConnection onlineServerConnection = new MockServerConnection();
+    final MockServerConnectionFactory onlineServerConnectionFactory = new MockServerConnectionFactory(
+        onlineServerConnection);
+    final LDAPListener onlineServerListener = new LDAPListener("localhost",
+        onlineServerPort, onlineServerConnectionFactory);
+
+    try
+    {
+      // Connection pool and load balancing tests.
+      final ConnectionFactory offlineServer1 = Connections
+          .newNamedConnectionFactory(new LDAPConnectionFactory("localhost",
+              TestCaseUtils.findFreePort()), "offline1");
+      final ConnectionFactory offlineServer2 = Connections
+          .newNamedConnectionFactory(new LDAPConnectionFactory("localhost",
+              TestCaseUtils.findFreePort()), "offline2");
+      final ConnectionFactory onlineServer = Connections
+          .newNamedConnectionFactory(new LDAPConnectionFactory("localhost",
+              onlineServerPort), "online");
+
+      // Round robin.
+      final ConnectionFactory loadBalancer = Connections
+          .newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(Arrays.asList(
+              Connections.newConnectionPool(offlineServer1, 10),
+              Connections.newConnectionPool(offlineServer2, 10),
+              Connections.newConnectionPool(onlineServer, 10))));
+
+      final MockServerConnection proxyServerConnection = new MockServerConnection()
+      {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleBind(final Integer requestContext, final int version,
+            final BindRequest request,
+            final ResultHandler<? super BindResult> resultHandler,
+            final IntermediateResponseHandler intermediateResponseHandler)
+            throws UnsupportedOperationException
+        {
+          // Get connection from load balancer, this should fail over twice
+          // before getting connection to online server.
+          try
+          {
+            loadBalancer.getConnection().close();
+            resultHandler.handleResult(Responses
+                .newBindResult(ResultCode.SUCCESS));
+          }
+          catch (final Exception e)
+          {
+            // Unexpected.
+            resultHandler
+                .handleErrorResult(ErrorResultException.newErrorResult(
+                    ResultCode.OTHER,
+                    "Unexpected exception when connecting to load balancer", e));
+          }
+        }
+
+      };
+      final MockServerConnectionFactory proxyServerConnectionFactory = new MockServerConnectionFactory(
+          proxyServerConnection);
+
+      final LDAPListener proxyListener = new LDAPListener("localhost",
+          TestCaseUtils.findFreePort(), proxyServerConnectionFactory);
+      try
+      {
+        // Connect, bind, and close.
+        final Connection connection = new LDAPConnectionFactory(
+            proxyListener.getSocketAddress()).getConnection();
+        try
+        {
+          connection.bind("cn=test", "password");
+        }
+        finally
+        {
+          connection.close();
+        }
+
+        // Wait for connect/close to complete.
+        proxyServerConnection.isClosed.await();
+
+        Assert.assertTrue(proxyServerConnection.isConnected);
+        Assert.assertTrue(onlineServerConnection.isConnected);
+      }
+      finally
+      {
+        proxyListener.close();
+      }
+    }
+    finally
+    {
+      onlineServerListener.close();
+    }
+  }
+
+
+
+  /**
+   * Tests LDAP listener which attempts to open a connection to a remote offline
+   * server at the point when the listener accepts the client connection.
+   *
+   * @throws Exception
+   *           If an unexpected exception occurred.
+   */
+  @Test(enabled = false)
+  public void testLDAPListenerProxyDuringHandleAccept() throws Exception
+  {
+    final MockServerConnection onlineServerConnection = new MockServerConnection();
+    final MockServerConnectionFactory onlineServerConnectionFactory = new MockServerConnectionFactory(
+        onlineServerConnection);
+    final LDAPListener onlineServerListener = new LDAPListener("localhost",
+        TestCaseUtils.findFreePort(), onlineServerConnectionFactory);
+
+    try
+    {
+      final int offlineServerPort = TestCaseUtils.findFreePort();
+
+      final MockServerConnection proxyServerConnection = new MockServerConnection();
+      final MockServerConnectionFactory proxyServerConnectionFactory = new MockServerConnectionFactory(
+          proxyServerConnection)
+      {
+
+        @Override
+        public ServerConnection<Integer> handleAccept(
+            final LDAPClientContext clientContext) throws ErrorResultException
+        {
+          // First attempt offline server.
+          LDAPConnectionFactory lcf = new LDAPConnectionFactory("localhost",
+              offlineServerPort);
+          try
+          {
+            // This is expected to fail.
+            lcf.getConnection().close();
+            throw ErrorResultException.newErrorResult(ResultCode.OTHER,
+                "Connection to offline server succeeded unexpectedly");
+          }
+          catch (final ConnectionException ce)
+          {
+            // This is expected - so go to online server.
+            try
+            {
+              lcf = new LDAPConnectionFactory(
+                  onlineServerListener.getSocketAddress());
+              lcf.getConnection().close();
+            }
+            catch (final Exception e)
+            {
+              // Unexpected.
+              throw ErrorResultException.newErrorResult(ResultCode.OTHER,
+                  "Unexpected exception when connecting to online server", e);
+            }
+          }
+          catch (final Exception e)
+          {
+            // Unexpected.
+            throw ErrorResultException.newErrorResult(ResultCode.OTHER,
+                "Unexpected exception when connecting to offline server", e);
+          }
+
+          return super.handleAccept(clientContext);
+        }
+
+      };
+      final LDAPListener proxyListener = new LDAPListener("localhost",
+          TestCaseUtils.findFreePort(), proxyServerConnectionFactory);
+      try
+      {
+        // Connect and close.
+        new LDAPConnectionFactory(proxyListener.getSocketAddress())
+            .getConnection().close();
+
+        // Wait for connect/close to complete.
+        proxyServerConnection.isClosed.await();
+
+        Assert.assertTrue(proxyServerConnection.isConnected);
+        Assert.assertTrue(onlineServerConnection.isConnected);
+      }
+      finally
+      {
+        proxyListener.close();
+      }
+    }
+    finally
+    {
+      onlineServerListener.close();
+    }
+  }
+
+
+
+  /**
+   * Tests LDAP listener which attempts to open a connection to a remote offline
+   * server at the point when the listener handles a bind request.
+   *
+   * @throws Exception
+   *           If an unexpected exception occurred.
+   */
+  @Test
+  public void testLDAPListenerProxyDuringHandleBind() throws Exception
+  {
+    final MockServerConnection onlineServerConnection = new MockServerConnection();
+    final MockServerConnectionFactory onlineServerConnectionFactory = new MockServerConnectionFactory(
+        onlineServerConnection);
+    final LDAPListener onlineServerListener = new LDAPListener("localhost",
+        TestCaseUtils.findFreePort(), onlineServerConnectionFactory);
+
+    try
+    {
+      final int offlineServerPort = TestCaseUtils.findFreePort();
+
+      final MockServerConnection proxyServerConnection = new MockServerConnection()
+      {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleBind(final Integer requestContext, final int version,
+            final BindRequest request,
+            final ResultHandler<? super BindResult> resultHandler,
+            final IntermediateResponseHandler intermediateResponseHandler)
+            throws UnsupportedOperationException
+        {
+          // First attempt offline server.
+          LDAPConnectionFactory lcf = new LDAPConnectionFactory("localhost",
+              offlineServerPort);
+          try
+          {
+            // This is expected to fail.
+            lcf.getConnection().close();
+            resultHandler.handleErrorResult(ErrorResultException
+                .newErrorResult(ResultCode.OTHER,
+                    "Connection to offline server succeeded unexpectedly"));
+          }
+          catch (final ConnectionException ce)
+          {
+            // This is expected - so go to online server.
+            try
+            {
+              lcf = new LDAPConnectionFactory(
+                  onlineServerListener.getSocketAddress());
+              lcf.getConnection().close();
+              resultHandler.handleResult(Responses
+                  .newBindResult(ResultCode.SUCCESS));
+            }
+            catch (final Exception e)
+            {
+              // Unexpected.
+              resultHandler.handleErrorResult(ErrorResultException
+                  .newErrorResult(ResultCode.OTHER,
+                      "Unexpected exception when connecting to online server",
+                      e));
+            }
+          }
+          catch (final Exception e)
+          {
+            // Unexpected.
+            resultHandler
+                .handleErrorResult(ErrorResultException
+                    .newErrorResult(
+                        ResultCode.OTHER,
+                        "Unexpected exception when connecting to offline server",
+                        e));
+          }
+        }
+
+      };
+      final MockServerConnectionFactory proxyServerConnectionFactory = new MockServerConnectionFactory(
+          proxyServerConnection);
+      final LDAPListener proxyListener = new LDAPListener("localhost",
+          TestCaseUtils.findFreePort(), proxyServerConnectionFactory);
+      try
+      {
+        // Connect, bind, and close.
+        final Connection connection = new LDAPConnectionFactory(
+            proxyListener.getSocketAddress()).getConnection();
+        try
+        {
+          connection.bind("cn=test", "password");
+        }
+        finally
+        {
+          connection.close();
+        }
+
+        // Wait for connect/close to complete.
+        proxyServerConnection.isClosed.await();
+
+        Assert.assertTrue(proxyServerConnection.isConnected);
+        Assert.assertTrue(onlineServerConnection.isConnected);
+      }
+      finally
+      {
+        proxyListener.close();
+      }
+    }
+    finally
+    {
+      onlineServerListener.close();
+    }
+  }
+
+
+
+  /**
+   * Tests server-side disconnection.
+   *
+   * @throws Exception
+   *           If an unexpected error occurred.
+   */
+  @Test
+  public void testServerDisconnect() throws Exception
+  {
+    final MockServerConnection onlineServerConnection = new MockServerConnection();
+    final MockServerConnectionFactory onlineServerConnectionFactory = new MockServerConnectionFactory(
+        onlineServerConnection);
+    final LDAPListener onlineServerListener = new LDAPListener("localhost",
+        TestCaseUtils.findFreePort(), onlineServerConnectionFactory);
+
+    final Connection connection;
+    try
+    {
+      // Connect and bind.
+      connection = new LDAPConnectionFactory(
+          onlineServerListener.getSocketAddress()).getConnection();
+      try
+      {
+        connection.bind("cn=test", "password");
+      }
+      catch (ErrorResultException e)
+      {
+        connection.close();
+        throw e;
+      }
+    }
+    finally
+    {
+      onlineServerConnection.context.disconnect();
+      onlineServerListener.close();
+    }
+
+    try
+    {
+      // Connect and bind.
+      final Connection failedConnection = new LDAPConnectionFactory(
+          onlineServerListener.getSocketAddress()).getConnection();
+      failedConnection.close();
+      connection.close();
+      Assert
+          .fail("Connection attempt to closed listener succeeded unexpectedly");
+    }
+    catch (ConnectionException e)
+    {
+      // Expected.
+    }
+
+    try
+    {
+      connection.bind("cn=test", "password");
+      Assert.fail("Bind attempt on closed connection succeeded unexpectedly");
+    }
+    catch (ErrorResultException e)
+    {
+      // Expected.
+      Assert.assertFalse(connection.isValid());
+      Assert.assertFalse(connection.isClosed());
+    }
+    finally
+    {
+      connection.close();
+      Assert.assertFalse(connection.isValid());
+      Assert.assertTrue(connection.isClosed());
+    }
+  }
+}
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPServer.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPServer.java
index c6695cc..a11c5d5 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPServer.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/LDAPServer.java
@@ -47,8 +47,8 @@
 import org.opends.sdk.requests.*;
 import org.opends.sdk.responses.*;
 
-import com.sun.grizzly.TransportFactory;
-import com.sun.grizzly.nio.transport.TCPNIOTransport;
+import org.glassfish.grizzly.TransportFactory;
+import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
 import com.sun.opends.sdk.controls.AccountUsabilityRequestControl;
 import com.sun.opends.sdk.controls.AccountUsabilityResponseControl;
 import com.sun.opends.sdk.ldap.GrizzlyLDAPListenerOptions;
@@ -138,7 +138,8 @@
 
 
 
-  private class LDAPServerConnection implements ServerConnection<Integer>
+  private class LDAPServerConnection implements
+      ServerConnection<Integer>
   {
 
     private final LDAPClientContext clientContext;
@@ -164,7 +165,8 @@
         final AbandonRequest request) throws UnsupportedOperationException
     {
       // Check if we have any concurrent operation with this message id.
-      final AbandonableRequest req = requestsInProgress.get(context);
+      final AbandonableRequest req = requestsInProgress.get(request
+          .getRequestID());
       if (req == null)
       {
         // Nothing to do here.
@@ -186,8 +188,8 @@
      * @param intermediateResponseHandler
      * @throws UnsupportedOperationException
      */
-    public void handleAdd(final Integer context, final AddRequest request,
-        final ResultHandler<? super Result> handler,
+    public void handleAdd(final Integer context,
+        final AddRequest request, final ResultHandler<? super Result> handler,
         final IntermediateResponseHandler intermediateResponseHandler)
         throws UnsupportedOperationException
     {
@@ -399,12 +401,18 @@
 
 
     /**
-     * @param context
-     * @param request
+     * {@inheritDoc}
      */
     public void handleConnectionClosed(final Integer context,
         final UnbindRequest request)
     {
+      close();
+    }
+
+
+
+    private void close()
+    {
       if (saslServer != null)
       {
         try
@@ -421,11 +429,22 @@
 
 
     /**
-     * @param error
+     * {@inheritDoc}
      */
-    public void handleConnectionException(final Throwable error)
+    public void handleConnectionDisconnected(ResultCode resultCode,
+        String message)
     {
+      close();
+    }
 
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void handleConnectionError(final Throwable error)
+    {
+      close();
     }
 
 
@@ -549,7 +568,7 @@
     {
       if (request.getOID().equals(StartTLSExtendedRequest.OID))
       {
-        final R result = request.getResultDecoder().adaptExtendedErrorResult(
+        final R result = request.getResultDecoder().newExtendedErrorResult(
             ResultCode.SUCCESS, "", "");
         resultHandler.handleResult(result);
         clientContext.startTLS(sslContext, null, sslContext.getSocketFactory()
@@ -599,14 +618,11 @@
      * @param context
      * @param request
      * @param resultHandler
-     * @param searchResulthandler
      * @param intermediateResponseHandler
      * @throws UnsupportedOperationException
      */
     public void handleSearch(final Integer context,
-        final SearchRequest request,
-        final ResultHandler<? super Result> resultHandler,
-        final SearchResultHandler searchResulthandler,
+        final SearchRequest request, final SearchResultHandler resultHandler,
         final IntermediateResponseHandler intermediateResponseHandler)
         throws UnsupportedOperationException
     {
@@ -648,7 +664,7 @@
               false, 10, false, 0));
         }
       }
-      searchResulthandler.handleEntry(e);
+      resultHandler.handleEntry(e);
       result = Responses.newResult(ResultCode.SUCCESS);
       resultHandler.handleResult(result);
       requestsInProgress.remove(context);
@@ -707,7 +723,7 @@
    * @param context
    * @return
    */
-  public ServerConnection<Integer> accept(final LDAPClientContext context)
+  public ServerConnection<Integer> handleAccept(final LDAPClientContext context)
   {
     return new LDAPServerConnection(context);
   }
@@ -759,14 +775,7 @@
     {
       return;
     }
-    try
-    {
-      listener.close();
-    }
-    catch (final IOException e)
-    {
-      e.printStackTrace();
-    }
+    listener.close();
     try
     {
       transport.stop();
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/LinkedAttributeTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/LinkedAttributeTestCase.java
index 9a84966..4ec0f6b 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/LinkedAttributeTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/LinkedAttributeTestCase.java
@@ -29,6 +29,11 @@
 
 
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
 import org.opends.sdk.schema.Schema;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -66,4 +71,416 @@
     Assert.assertTrue(attribute.remove(ByteString.valueOf("another value")));
     Assert.assertEquals(attribute.size(), 0);
   }
+
+
+
+  @Test
+  public void testAdd()
+  {
+    Attribute a = new LinkedAttribute("test");
+    Assert.assertTrue(a.add(ByteString.valueOf("value1")));
+    Assert.assertFalse(a.add(ByteString.valueOf("value1")));
+    Assert.assertTrue(a.add(ByteString.valueOf("value2")));
+    Assert.assertFalse(a.add(ByteString.valueOf("value2")));
+    Assert.assertTrue(a.add(ByteString.valueOf("value3")));
+    Assert.assertFalse(a.add(ByteString.valueOf("value3")));
+    Assert.assertEquals(a.size(), 3);
+    Iterator<ByteString> i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value3"));
+    Assert.assertFalse(i.hasNext());
+  }
+
+
+
+  @Test
+  public void testAddAll()
+  {
+    // addAll to an empty attribute.
+    Attribute a = new LinkedAttribute("test");
+    Assert.assertFalse(a.addAll(Collections.<ByteString> emptyList(), null));
+    Iterator<ByteString> i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test");
+    Assert.assertTrue(a.addAll(Arrays.asList(ByteString.valueOf("value1")),
+        null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test");
+    Assert.assertTrue(a.addAll(
+        Arrays.asList(ByteString.valueOf("value1"),
+            ByteString.valueOf("value2")), null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertFalse(i.hasNext());
+
+    // addAll to a single-valued attribute.
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertFalse(a.addAll(Collections.<ByteString> emptyList(), null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertTrue(a.addAll(Arrays.asList(ByteString.valueOf("value2")),
+        null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertTrue(a.addAll(
+        Arrays.asList(ByteString.valueOf("value2"),
+            ByteString.valueOf("value3")), null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value3"));
+    Assert.assertFalse(i.hasNext());
+
+    // addAll to a multi-valued attribute.
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"));
+    Assert.assertFalse(a.addAll(Collections.<ByteString> emptyList(), null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"));
+    Assert.assertTrue(a.addAll(Arrays.asList(ByteString.valueOf("value3")),
+        null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value3"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"));
+    Assert.assertTrue(a.addAll(
+        Arrays.asList(ByteString.valueOf("value3"),
+            ByteString.valueOf("value4")), null));
+    i = a.iterator();
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value3"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value4"));
+    Assert.assertFalse(i.hasNext());
+  }
+
+
+
+  @Test
+  public void testClear()
+  {
+    Attribute a = new LinkedAttribute("test");
+    Assert.assertTrue(a.isEmpty());
+    Assert.assertEquals(a.size(), 0);
+    a.clear();
+    Assert.assertTrue(a.isEmpty());
+    Assert.assertEquals(a.size(), 0);
+
+    a.add(ByteString.valueOf("value1"));
+    Assert.assertFalse(a.isEmpty());
+    Assert.assertEquals(a.size(), 1);
+    a.clear();
+    Assert.assertTrue(a.isEmpty());
+    Assert.assertEquals(a.size(), 0);
+
+    a.add(ByteString.valueOf("value1"));
+    a.add(ByteString.valueOf("value2"));
+    Assert.assertFalse(a.isEmpty());
+    Assert.assertEquals(a.size(), 2);
+    a.clear();
+    Assert.assertTrue(a.isEmpty());
+    Assert.assertEquals(a.size(), 0);
+
+    a.add(ByteString.valueOf("value1"));
+    a.add(ByteString.valueOf("value2"));
+    a.add(ByteString.valueOf("value3"));
+    Assert.assertFalse(a.isEmpty());
+    Assert.assertEquals(a.size(), 3);
+    a.clear();
+    Assert.assertTrue(a.isEmpty());
+    Assert.assertEquals(a.size(), 0);
+  }
+
+
+
+  @Test
+  public void testContains()
+  {
+    Attribute a = new LinkedAttribute("test");
+    Assert.assertFalse(a.contains(ByteString.valueOf("value4")));
+
+    a.add(ByteString.valueOf("value1"));
+    Assert.assertTrue(a.contains(ByteString.valueOf("value1")));
+    Assert.assertFalse(a.contains(ByteString.valueOf("value4")));
+
+    a.add(ByteString.valueOf("value2"));
+    Assert.assertTrue(a.contains(ByteString.valueOf("value1")));
+    Assert.assertTrue(a.contains(ByteString.valueOf("value2")));
+    Assert.assertFalse(a.contains(ByteString.valueOf("value4")));
+
+    a.add(ByteString.valueOf("value3"));
+    Assert.assertTrue(a.contains(ByteString.valueOf("value1")));
+    Assert.assertTrue(a.contains(ByteString.valueOf("value2")));
+    Assert.assertTrue(a.contains(ByteString.valueOf("value3")));
+    Assert.assertFalse(a.contains(ByteString.valueOf("value4")));
+  }
+
+
+
+  @Test
+  public void testContainsAll()
+  {
+    Attribute a = new LinkedAttribute("test");
+    Assert.assertTrue(a.containsAll(Collections.<ByteString> emptyList()));
+    Assert
+        .assertFalse(a.containsAll(Arrays.asList(ByteString.valueOf("value1"))));
+    Assert.assertFalse(a.containsAll(Arrays.asList(
+        ByteString.valueOf("value1"), ByteString.valueOf("value2"))));
+    Assert.assertFalse(a.containsAll(Arrays.asList(
+        ByteString.valueOf("value1"), ByteString.valueOf("value2"),
+        ByteString.valueOf("value3"))));
+
+    a.add(ByteString.valueOf("value1"));
+    Assert.assertTrue(a.containsAll(Collections.<ByteString> emptyList()));
+    Assert
+        .assertTrue(a.containsAll(Arrays.asList(ByteString.valueOf("value1"))));
+    Assert.assertFalse(a.containsAll(Arrays.asList(
+        ByteString.valueOf("value1"), ByteString.valueOf("value2"))));
+    Assert.assertFalse(a.containsAll(Arrays.asList(
+        ByteString.valueOf("value1"), ByteString.valueOf("value2"),
+        ByteString.valueOf("value3"))));
+
+    a.add(ByteString.valueOf("value2"));
+    Assert.assertTrue(a.containsAll(Collections.<ByteString> emptyList()));
+    Assert
+        .assertTrue(a.containsAll(Arrays.asList(ByteString.valueOf("value1"))));
+    Assert.assertTrue(a.containsAll(Arrays.asList(ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"))));
+    Assert.assertFalse(a.containsAll(Arrays.asList(
+        ByteString.valueOf("value1"), ByteString.valueOf("value2"),
+        ByteString.valueOf("value3"))));
+  }
+
+
+
+  @Test
+  public void testFirstValue()
+  {
+    Attribute a = new LinkedAttribute("test");
+    try
+    {
+      a.firstValue();
+      Assert.fail("Expected NoSuchElementException");
+    }
+    catch (NoSuchElementException e)
+    {
+      // Expected.
+    }
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertEquals(a.firstValue(), ByteString.valueOf("value1"));
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"));
+    Assert.assertEquals(a.firstValue(), ByteString.valueOf("value1"));
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value2"),
+        ByteString.valueOf("value1"));
+    Assert.assertEquals(a.firstValue(), ByteString.valueOf("value2"));
+  }
+
+
+
+  @Test
+  public void testGetAttributeDescription()
+  {
+    AttributeDescription ad = AttributeDescription.valueOf("test");
+    Attribute a = new LinkedAttribute(ad);
+    Assert.assertEquals(a.getAttributeDescription(), ad);
+  }
+
+
+
+  @Test
+  public void testIterator()
+  {
+    Attribute a = new LinkedAttribute("test");
+    Iterator<ByteString> i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertFalse(i.hasNext());
+  }
+
+
+
+  @Test
+  public void testRemove()
+  {
+    Attribute a = new LinkedAttribute("test");
+    Assert.assertFalse(a.remove(ByteString.valueOf("value1")));
+    Iterator<ByteString> i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertFalse(a.remove(ByteString.valueOf("value2")));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertFalse(i.hasNext());
+    Assert.assertTrue(a.remove(ByteString.valueOf("value1")));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"));
+    Assert.assertFalse(a.remove(ByteString.valueOf("value3")));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertFalse(i.hasNext());
+    Assert.assertTrue(a.remove(ByteString.valueOf("value1")));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertFalse(i.hasNext());
+    Assert.assertTrue(a.remove(ByteString.valueOf("value2")));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+  }
+
+
+
+  @Test
+  public void testRemoveAll()
+  {
+    // removeAll from an empty attribute.
+    Attribute a = new LinkedAttribute("test");
+    Assert.assertFalse(a.removeAll(Collections.<ByteString> emptyList(), null));
+    Iterator<ByteString> i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test");
+    Assert.assertFalse(a.removeAll(Arrays.asList(ByteString.valueOf("value1")),
+        null));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test");
+    Assert.assertFalse(a.removeAll(Arrays.asList(ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"))));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    // removeAll from single-valued attribute.
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertFalse(a.removeAll(Collections.<ByteString> emptyList(), null));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertTrue(a.removeAll(Arrays.asList(ByteString.valueOf("value1")),
+        null));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"));
+    Assert.assertTrue(a.removeAll(Arrays.asList(ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"))));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    // removeAll from multi-valued attribute.
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4"));
+    Assert.assertFalse(a.removeAll(Collections.<ByteString> emptyList(), null));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value1"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value3"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value4"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4"));
+    Assert.assertTrue(a.removeAll(Arrays.asList(ByteString.valueOf("value1")),
+        null));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value2"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value3"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value4"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4"));
+    Assert.assertTrue(a.removeAll(
+        Arrays.asList(ByteString.valueOf("value1"),
+            ByteString.valueOf("value2")), null));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value3"));
+    Assert.assertEquals(i.next(), ByteString.valueOf("value4"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4"));
+    Assert.assertTrue(a.removeAll(
+        Arrays.asList(ByteString.valueOf("value1"),
+            ByteString.valueOf("value2"), ByteString.valueOf("value3")), null));
+    i = a.iterator();
+    Assert.assertTrue(i.hasNext());
+    Assert.assertEquals(i.next(), ByteString.valueOf("value4"));
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4"));
+    Assert.assertTrue(a.removeAll(Arrays.asList(ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4")), null));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+
+    a = new LinkedAttribute("test", ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4"));
+    Assert.assertTrue(a.removeAll(Arrays.asList(ByteString.valueOf("value1"),
+        ByteString.valueOf("value2"), ByteString.valueOf("value3"),
+        ByteString.valueOf("value4"), ByteString.valueOf("value5")), null));
+    i = a.iterator();
+    Assert.assertFalse(i.hasNext());
+  }
 }
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/TestCaseUtils.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/TestCaseUtils.java
index f419fc3..4d8fc28 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/TestCaseUtils.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/TestCaseUtils.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.sdk;
@@ -31,6 +31,9 @@
 
 import java.io.File;
 import java.io.FileWriter;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
 
 
 
@@ -47,10 +50,10 @@
   public static final String PROPERTY_LDAP_PORT = "org.opends.server.LdapPort";
 
   /**
-   * Port number that's used by the server. Need to be used by the testcases to
+   * Port number that's used by the server. Need to be used by the test cases to
    * create connections.
    */
-  public static int port = 11389;
+  public static int port;
 
   static
   {
@@ -59,6 +62,10 @@
     {
       port = Integer.valueOf(ldapPort);
     }
+    else
+    {
+      port = findFreePort();
+    }
   }
 
 
@@ -67,6 +74,8 @@
    * Creates a temporary text file with the specified contents. It will be
    * marked for automatic deletion when the JVM exits.
    *
+   * @param lines
+   *          The file contents.
    * @return The absolute path to the file that was created.
    * @throws Exception
    *           If an unexpected problem occurs.
@@ -90,6 +99,31 @@
 
 
   /**
+   * Finds a free server socket port on the local host.
+   *
+   * @return The free port.
+   */
+  public static int findFreePort()
+  {
+    int port;
+    try
+    {
+      ServerSocket serverLdapSocket = new ServerSocket();
+      serverLdapSocket.setReuseAddress(true);
+      serverLdapSocket.bind(new InetSocketAddress("127.0.0.1", 0));
+      port = serverLdapSocket.getLocalPort();
+      serverLdapSocket.close();
+    }
+    catch (IOException e)
+    {
+      throw new RuntimeException(e);
+    }
+    return port;
+  }
+
+
+
+  /**
    * Returns an internal client connection to the running ldap server.
    *
    * @return The internal client connection.
@@ -122,10 +156,10 @@
    * Starts the test ldap server.
    *
    * @throws Exception
+   *           If an error occurs when starting the server.
    */
   public static void startServer() throws Exception
   {
-    // TODO:Try a couple of random ports before throwing exception.
     LDAPServer.getInstance().start(port);
   }
 
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/DigestMD5SASLBindRequestTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/DigestMD5SASLBindRequestTestCase.java
index 530d4ae..52629a6 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/DigestMD5SASLBindRequestTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/DigestMD5SASLBindRequestTestCase.java
@@ -35,7 +35,6 @@
 
 import java.util.Arrays;
 
-import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.assertEquals;
 
 
@@ -76,37 +75,29 @@
   @Test(dataProvider = "DigestMD5SASLBindRequests")
   public void testQOP(DigestMD5SASLBindRequest request) throws Exception
   {
-    DigestMD5SASLBindRequest.QOPOption [] options =
-        new DigestMD5SASLBindRequest.QOPOption[]{
-            DigestMD5SASLBindRequest.QOPOption.AUTH,
-            DigestMD5SASLBindRequest.QOPOption.AUTH_INT,
-            DigestMD5SASLBindRequest.QOPOption.AUTH_CONF};
-    request.setQOP(options);
-
-    DigestMD5SASLBindRequest.QOPOption [] results = request.getQOP();
-    for(int i = 0; i < options.length; i++)
-    {
-      assertEquals(options[i], results[i]);
-    }
+    String[] options = new String[] {
+        DigestMD5SASLBindRequest.QOP_AUTH,
+        DigestMD5SASLBindRequest.QOP_AUTH_INT,
+        DigestMD5SASLBindRequest.QOP_AUTH_CONF };
+    request.addQOP(options);
+    assertEquals(request.getQOPs(), Arrays.asList(options));
   }
 
   @Test(dataProvider = "DigestMD5SASLBindRequests" )
   public void testStrength(DigestMD5SASLBindRequest request) throws Exception
   {
-    DigestMD5SASLBindRequest.CipherOption[] options =
-        new DigestMD5SASLBindRequest.CipherOption[]{
-            DigestMD5SASLBindRequest.CipherOption.RC4_40,
-            DigestMD5SASLBindRequest.CipherOption.TRIPLE_DES_RC4,
-            DigestMD5SASLBindRequest.CipherOption.DES_RC4_56};
-    request.setCipher(options);
-    assertTrue(Arrays.deepEquals(options, request.getCipher()));
+    request.setCipher(DigestMD5SASLBindRequest.CIPHER_3DES);
+    assertEquals(request.getCipher(), DigestMD5SASLBindRequest.CIPHER_3DES);
+
+    request.setCipher(DigestMD5SASLBindRequest.CIPHER_MEDIUM);
+    assertEquals(request.getCipher(), DigestMD5SASLBindRequest.CIPHER_MEDIUM);
   }
 
   @Test(dataProvider = "DigestMD5SASLBindRequests")
   public void testServerAuth(DigestMD5SASLBindRequest request) throws Exception
   {
     request.setServerAuth(true);
-    assertEquals(request.getServerAuth(), true);
+    assertEquals(request.isServerAuth(), true);
   }
 
   @Test(dataProvider = "DigestMD5SASLBindRequests")
diff --git a/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/GSSAPISASLBindRequestTestCase.java b/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/GSSAPISASLBindRequestTestCase.java
index 9241213..eadef95 100644
--- a/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/GSSAPISASLBindRequestTestCase.java
+++ b/sdk/tests/unit-tests-testng/src/org/opends/sdk/requests/GSSAPISASLBindRequestTestCase.java
@@ -36,7 +36,6 @@
 import java.util.Arrays;
 
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
 
 /**
  * Tests GSSAPI SASL Bind requests.
@@ -81,20 +80,19 @@
   @Test(dataProvider = "GSSAPISASLBindRequests")
   public void testQOP(GSSAPISASLBindRequest request) throws Exception
   {
-    GSSAPISASLBindRequest.QOPOption [] options =
-        new GSSAPISASLBindRequest.QOPOption[]{
-            GSSAPISASLBindRequest.QOPOption.AUTH,
-            GSSAPISASLBindRequest.QOPOption.AUTH_INT,
-            GSSAPISASLBindRequest.QOPOption.AUTH_CONF};
-    request.setQOP(options);
-    assertTrue(Arrays.deepEquals(options, request.getQOP()));
+    String[] options = new String[] {
+        GSSAPISASLBindRequest.QOP_AUTH,
+        GSSAPISASLBindRequest.QOP_AUTH_INT,
+        GSSAPISASLBindRequest.QOP_AUTH_CONF };
+    request.addQOP(options);
+    assertEquals(request.getQOPs(), Arrays.asList(options));
   }
 
   @Test(dataProvider = "GSSAPISASLBindRequests")
   public void testServerAuth(GSSAPISASLBindRequest request) throws Exception
   {
     request.setServerAuth(true);
-    assertEquals(request.getServerAuth(), true);
+    assertEquals(request.isServerAuth(), true);
   }
 
   @Test(dataProvider = "GSSAPISASLBindRequests")

--
Gitblit v1.10.0