From 8b310cc951f4712f7b14ca523f3f856ee1774316 Mon Sep 17 00:00:00 2001
From: Mark Craig <mark.craig@forgerock.com>
Date: Mon, 07 May 2012 19:07:56 +0000
Subject: [PATCH] Part of the dev guide chapter on extended operations

---
 opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm                                          |    5 
 opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java |  203 +++++++++++++++++++++++++++++
 opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java        |   38 +++++
 opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml                                              |  154 ++++++++++++++++++----
 opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-authenticating.xml                                            |    2 
 5 files changed, 370 insertions(+), 32 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
new file mode 100644
index 0000000..b0a3be0
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
@@ -0,0 +1,203 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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 2012 ForgeRock AS
+ *
+ */
+
+package org.forgerock.opendj.examples;
+
+import java.util.Collection;
+
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.RootDSE;
+import org.forgerock.opendj.ldap.requests.PasswordModifyExtendedRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.requests.WhoAmIExtendedRequest;
+import org.forgerock.opendj.ldap.responses.PasswordModifyExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.WhoAmIExtendedResult;
+
+/**
+ * This command-line client demonstrates use of LDAP extended operations. The
+ * client takes as arguments the host and port for the directory server, and
+ * expects to find the entries and access control instructions as defined in <a
+ * href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>.
+ *
+ * This client connects as <code>cn=Directory Manager</code> with password
+ * <code>password</code>. Not a best practice; in real code use application
+ * specific credentials to connect, and ensure that your application has access
+ * to use the LDAP extended operations needed.
+ */
+public final class ExtendedOperations {
+
+    /**
+     * Connect to the server, and then try to use some LDAP extended operations.
+     *
+     * @param args
+     *            The command line arguments: host, port
+     */
+    public static void main(final String[] args) {
+        if (args.length != 2) {
+            System.err.println("Usage: host port");
+            System.err.println("For example: localhost 1389");
+            System.exit(1);
+        }
+        final String host = args[0];
+        final int port = Integer.parseInt(args[1]);
+
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+        Connection connection = null;
+
+        try {
+            connection = factory.getConnection();
+            checkSupportedExtendedOperations(connection);
+
+            final String user = "cn=Directory Manager";
+            final char[] password = "password".toCharArray();
+            connection.bind(user, password);
+
+            // Uncomment a method to run one of the examples.
+
+            // For a Cancel Extended request, see the SearchAsync example.
+            //usePasswordModifyExtendedRequest(connection);
+            // For StartTLS, see the authentication examples.
+            useWhoAmIExtendedRequest(connection);
+
+        } catch (ErrorResultException e) {
+            System.err.println(e.getMessage());
+            System.exit(e.getResult().getResultCode().intValue());
+            return;
+        } finally {
+            if (connection != null) {
+                connection.close();
+            }
+        }
+    }
+
+    /**
+     * Use the password modify extended request.
+     *
+     * @param connection
+     *            Active connection to LDAP server containing <a
+     *            href="http://opendj.forgerock.org/Example.ldif"
+     *            >Example.ldif</a> content.
+     * @throws ErrorResultException
+     *             Operation failed.
+     */
+    static void usePasswordModifyExtendedRequest(Connection connection) throws ErrorResultException {
+        if (isSupported(PasswordModifyExtendedRequest.OID)) {
+            final String userIdentity = "u:scarter";
+            final char[] oldPassword = "sprain".toCharArray();
+            final char[] newPassword = "secret12".toCharArray();
+
+            final PasswordModifyExtendedRequest request =
+                    Requests.newPasswordModifyExtendedRequest()
+                        .setUserIdentity(userIdentity)
+                        .setOldPassword(oldPassword)
+                        .setNewPassword(newPassword);
+
+            final PasswordModifyExtendedResult result =
+                    connection.extendedRequest(request);
+            if (result.isSuccess()) {
+                System.out.println("Changed password for " + userIdentity);
+            } else {
+                System.err.println(result.getDiagnosticMessage());
+            }
+        } else {
+            System.err.println("PasswordModifyExtendedRequest not supported");
+        }
+    }
+
+    /**
+     * Use the Who Am I? extended request.
+     *
+     * @param connection Active connection to LDAP server containing <a
+     *            href="http://opendj.forgerock.org/Example.ldif"
+     *            >Example.ldif</a> content.
+     * @throws ErrorResultException
+     *             Operation failed.
+     */
+    static void useWhoAmIExtendedRequest(Connection connection) throws ErrorResultException {
+        if (isSupported(WhoAmIExtendedRequest.OID)) {
+
+            final String name = "uid=bjensen,ou=People,dc=example,dc=com";
+            final char[] password = "hifalutin".toCharArray();
+
+            final Result result = connection.bind(name, password);
+            if (result.isSuccess()) {
+
+                final WhoAmIExtendedRequest request =
+                        Requests.newWhoAmIExtendedRequest();
+                final WhoAmIExtendedResult extResult =
+                        connection.extendedRequest(request);
+
+                if (extResult.isSuccess()) {
+                    System.out.println("Authz ID: "  + extResult.getAuthorizationID());
+                }
+            }
+        } else {
+            System.err.println("WhoAmIExtendedRequest not supported");
+        }
+    }
+
+    /**
+     * Controls supported by the LDAP server.
+     */
+    private static Collection<String> extendedOperations;
+
+    /**
+     * Populate the list of supported LDAP extended operation OIDs.
+     *
+     * @param connection
+     *            Active connection to the LDAP server.
+     * @throws ErrorResultException
+     *             Failed to get list of extended operations.
+     */
+    static void checkSupportedExtendedOperations(Connection connection) throws ErrorResultException {
+        extendedOperations = RootDSE.readRootDSE(connection).getSupportedExtendedOperations();
+    }
+
+    /**
+     * Check whether an extended operation is supported. Call
+     * {@code checkSupportedExtendedOperations} first.
+     *
+     * @param extendedOperation
+     *            Check support for this extended operation, provided by OID.
+     * @return True if the control is supported.
+     */
+    static boolean isSupported(final String extendedOperation) {
+        if (extendedOperations != null && !extendedOperations.isEmpty()) {
+            return extendedOperations.contains(extendedOperation);
+        }
+        return false;
+    }
+
+    /**
+     * Constructor not used.
+     */
+    private ExtendedOperations() {
+        // Not used.
+    }
+}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
index ee6afe1..fc55a86 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
@@ -33,15 +33,18 @@
 
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.FutureResult;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.ResultHandler;
 import org.forgerock.opendj.ldap.SearchResultHandler;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.CancelExtendedRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
@@ -77,7 +80,9 @@
             // Bind succeeded: initiate search.
             final SearchRequest request =
                     Requests.newSearchRequest(baseDN, scope, filter, attributes);
-            connection.searchAsync(request, null, new SearchResultHandlerImpl());
+            final FutureResult<Result> futureResult =
+                    connection.searchAsync(request, null, new SearchResultHandlerImpl());
+            requestID = futureResult.getRequestID();
         }
 
     }
@@ -117,8 +122,15 @@
         @Override
         public synchronized boolean handleEntry(final SearchResultEntry entry) {
             try {
-                WRITER.writeComment("Search result entry: " + entry.getName().toString());
-                WRITER.writeEntry(entry);
+                if (entryCount < 10) {
+                    WRITER.writeComment("Search result entry: " + entry.getName().toString());
+                    WRITER.writeEntry(entry);
+                    ++entryCount;
+                } else { // Cancel the search.
+                    CancelExtendedRequest request = Requests.newCancelExtendedRequest(requestID);
+                    connection.extendedRequestAsync(request, null, new CancelResultHandlerImpl());
+                    return false;
+                }
             } catch (final IOException e) {
                 System.err.println(e.getMessage());
                 resultCode = ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
@@ -176,6 +188,26 @@
     private static Connection connection = null;
     private static int resultCode = 0;
 
+    static int requestID;
+    static int entryCount = 0;
+
+    private static final class CancelResultHandlerImpl implements ResultHandler<ExtendedResult> {
+
+        @Override
+        public void handleErrorResult(final ErrorResultException error) {
+            System.err.println(error.getMessage());
+            resultCode = error.getResult().getResultCode().intValue();
+            COMPLETION_LATCH.countDown();
+        }
+
+        @Override
+        public void handleResult(final ExtendedResult result) {
+            resultCode = result.getResultCode().intValue();
+            COMPLETION_LATCH.countDown();
+        }
+
+    }
+
     /**
      * Main method.
      *
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
index 9129372..f89acd3 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
@@ -85,6 +85,11 @@
           <a href="xref/org/forgerock/opendj/examples/Controls.html">Use LDAP Controls</a>
           - illustrates how to use supported LDAP controls
         </li>
+        <li>
+          <a href="xref/org/forgerock/opendj/examples/ExtendedOperations.html">Use
+          LDAP Extended Operations</a> - illustrates how to use supported LDAP extended
+          operations
+        </li>
       </ul>
     </section>
     <section name="Get the OpenDJ LDAP SDK Examples">
diff --git a/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-authenticating.xml b/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-authenticating.xml
index 94f4f41..ebe02f2 100644
--- a/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-authenticating.xml
+++ b/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-authenticating.xml
@@ -103,7 +103,7 @@
  </section>
  
  <section xml:id="simple-auth-with-starttls-or-ssl">
-  <title>Start TLS and SSL Authentication</title>
+  <title>Start TLS &amp; SSL Authentication</title>
   
   <para>Simple authentication involves sending a user name and password to
   the directory server. To avoid sending the user name and password in
diff --git a/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml b/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml
index 979db6c..0de550c 100644
--- a/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml
+++ b/opendj-sdk/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml
@@ -64,54 +64,152 @@
 supportedExtension: 1.3.6.1.4.1.4203.1.11.3
 supportedExtension: 1.3.6.1.4.1.1466.20037</screen>
 
-  <para>The following excerpt shows the Java equivalent of the preceding
-  command.</para>
+  <para>The following excerpt shows code to check for supported extended
+  operations.</para>
 
   <programlisting language="java">
-final LDAPConnectionFactory factory = new LDAPConnectionFactory(
-  host, port);
-Connection connection = null;
+/**
+ * Controls supported by the LDAP server.
+ */
+private static Collection&lt;String&gt; extendedOperations;
 
-try
-{
-  connection = factory.getConnection();
+/**
+ * Populate the list of supported LDAP extended operation OIDs.
+ * 
+ * @param connection
+ *            Active connection to the LDAP server.
+ * @throws ErrorResultException
+ *             Failed to get list of extended operations.
+ */
+static void checkSupportedExtendedOperations(Connection connection)
+        throws ErrorResultException {
+    extendedOperations = RootDSE.readRootDSE(connection)
+            .getSupportedExtendedOperations();
+}
 
-  // Perform an anonymous search on the root DSE.
-  final SearchResultEntry entry = connection.searchSingleEntry(
-      "",                         // DN is "" for root DSE.
-      SearchScope.BASE_OBJECT,    // Read only the root DSE.
-      "objectclass=*",            // Every object matches this filter.
-      "supportedExtension");      // Check supported extended operations.
-
-  final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
-  writer.writeComment("Supported extended ops for server " + host + ":" + port);
-  if (entry != null) writer.writeEntry(entry);
-  writer.flush();
-}</programlisting>
+/**
+ * Check whether an extended operation is supported. Call
+ * {@code checkSupportedExtendedOperations} first.
+ * 
+ * @param extendedOperation
+ *            Check support for this extended operation, provided by OID.
+ * @return True if the control is supported.
+ */
+static boolean isSupported(final String extendedOperation) {
+    if (extendedOperations != null &amp;&amp; !extendedOperations.isEmpty()) {
+        return extendedOperations.contains(extendedOperation);
+    }
+    return false;
+}
+</programlisting>
  </section>
  
  <section xml:id="use-cancel-extended-operation">
   <title>Cancel Extended Operation</title>
-  <para>TODO</para>
+
+  <para>RFC 3909, <link xlink:href="http://tools.ietf.org/html/rfc3909"
+  xlink:show="new"><citetitle>LDAP Cancel Operation</citetitle></link>, defines
+  an extended operation that lets you cancel an operation in progress and get
+  an indication of the outcome.</para>
+
+  <para>This cancel extended requests uses the request ID of operation you
+  want to cancel, and so therefore works with asynchronous searches and
+  updates.</para>
+
+  <programlisting language="java">
+TODO
+</programlisting>
+
+  <para>OpenDJ directory server supports the cancel operation.</para>
+
+  <programlisting>TODO</programlisting>
  </section>
- 
+
  <section xml:id="use-password-modify-extended-operation">
   <title>Password Modify Extended Operation</title>
-  <para>TODO</para>
+
+  <para>RFC 3062, <link xlink:href="http://tools.ietf.org/html/rfc3062"
+  xlink:show="new"><citetitle>LDAP Password Modify Extended
+  Operation</citetitle></link>, defines an extended operation for modifying
+  user passwords that does not depend on the authentication identity, nor on
+  the way passwords are stored.</para>
+
+  <programlisting language="java">
+if (isSupported(PasswordModifyExtendedRequest.OID)) {
+    final String userIdentity = "u:scarter";
+    final char[] oldPassword = "sprain".toCharArray();
+    final char[] newPassword = "secret12".toCharArray();
+
+    final PasswordModifyExtendedRequest request =
+            Requests.newPasswordModifyExtendedRequest()
+                .setUserIdentity(userIdentity)
+                .setOldPassword(oldPassword)
+                .setNewPassword(newPassword);
+
+    final PasswordModifyExtendedResult result =
+            connection.extendedRequest(request);
+    if (result.isSuccess()) {
+        System.out.println("Changed password for " + userIdentity);
+    } else {
+        System.err.println(result.getDiagnosticMessage());
+    }
+}
+</programlisting>
+
+  <para>OpenDJ directory server supports the password modify operation.</para>
+
+  <programlisting>Changed password for u:scarter</programlisting>
  </section>
- 
+
  <section xml:id="use-starttls-extended-operation">
   <title>Start TLS Extended Operation</title>
-  <para>TODO</para>
+
+  <para>Use Start TLS when setting up your connection to protect what your
+  application sends to and receives from the directory server. For an example,
+  read the section on <link
+  xlink:href="dev-guide#simple-auth-with-starttls-or-ssl"
+  xlink:role="http://docbook.org/xlink/role/olink"><citetitle>Start TLS &amp;
+  SSL Authentication</citetitle></link>.</para>
  </section>
- 
+
  <section xml:id="use-who-am-i-extended-operation">
   <title>Who am I? Extended Operation</title>
-  <para>TODO</para>
+
+  <para>RFC 4532, <link xlink:href="http://tools.ietf.org/html/rfc4532"
+  xlink:show="new"><citetitle>LDAP "Who am I?" Operation</citetitle></link>,
+  defines an extended operation that lets your application determine the
+  current authorization ID.</para>
+
+  <programlisting language="java">
+if (isSupported(WhoAmIExtendedRequest.OID)) {
+
+    final String name = "uid=bjensen,ou=People,dc=example,dc=com";
+    final char[] password = "hifalutin".toCharArray();
+
+    final Result result = connection.bind(name, password);
+    if (result.isSuccess()) {
+
+        final WhoAmIExtendedRequest request =
+                Requests.newWhoAmIExtendedRequest();
+        final WhoAmIExtendedResult extResult =
+                connection.extendedRequest(request);
+
+        if (extResult.isSuccess()) {
+            System.out.println("Authz ID: "  + extResult.getAuthorizationID());
+        }
+    }
+}
+</programlisting>
+
+  <para>OpenDJ directory server supports the "Who am I?" operation.</para>
+
+  <programlisting
+  >Authz ID: dn:uid=bjensen,ou=People,dc=example,dc=com</programlisting>
  </section>
- 
+
  <section xml:id="custom-extended-operation">
   <title>Custom Extended Operations</title>
+
   <para>TODO</para>
  </section>
 </chapter>

--
Gitblit v1.10.0