From 1638dfb988360db91384e62f41f845cc39f86c9c Mon Sep 17 00:00:00 2001
From: Mark Craig <mark.craig@forgerock.com>
Date: Fri, 31 Jan 2014 13:39:01 +0000
Subject: [PATCH] CR-2917 fix for OPENDJ-1257: Update dev guide to reflect SDK support for Active Directory Change Notifications

---
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java          |   93 +++++++++++++
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java |  176 +++++++++++++++++++++++++
 opendj-sdk/src/main/docbkx/dev-guide/chap-controls.xml                                                 |  124 +++++++----------
 opendj-sdk/src/site/resources/Example.ldif                                                             |    3 
 opendj-sdk/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm                                         |    6 
 5 files changed, 327 insertions(+), 75 deletions(-)

diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
index 824fac8..1bae0e9 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
@@ -20,7 +20,7 @@
  *
  * CDDL HEADER END
  *
- *      Copyright 2012-2013 ForgeRock AS
+ *      Copyright 2012-2014 ForgeRock AS
  *
  */
 
@@ -45,6 +45,7 @@
 import org.forgerock.opendj.ldap.SearchResultReferenceIOException;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.SortKey;
+import org.forgerock.opendj.ldap.controls.ADNotificationRequestControl;
 import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
 import org.forgerock.opendj.ldap.controls.AuthorizationIdentityRequestControl;
 import org.forgerock.opendj.ldap.controls.AuthorizationIdentityResponseControl;
@@ -123,6 +124,7 @@
 
             // Uncomment a method to run one of the examples.
 
+            //useADNotificationRequestControl(connection);
             //useAssertionControl(connection);
             useAuthorizationIdentityRequestControl(connection);
             // For the EntryChangeNotificationResponseControl see
@@ -156,6 +158,95 @@
     }
 
     /**
+     * Use the <a
+     * href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms676877(v=vs.85).aspx"
+     * >Microsoft LDAP Notification control</a>
+     * to register a change notification request for a search
+     * on Microsoft Active Directory.
+     * <p/>
+     * This client binds to Active Directory as
+     * {@code cn=Administrator,cn=users,dc=example,dc=com}
+     * with password {@code password},
+     * and expects entries under {@code dc=example,dc=com}.
+     *
+     * @param connection Active connection to Active Directory server.
+     * @throws ErrorResultException Operation failed.
+     */
+    static void useADNotificationRequestControl(Connection connection)
+            throws ErrorResultException {
+
+        // --- JCite ADNotification ---
+        final String user = "cn=Administrator,cn=users,dc=example,dc=com";
+        final char[] password = "password".toCharArray();
+        connection.bind(user, password);
+
+        final String[] attributes = {"cn",
+            ADNotificationRequestControl.IS_DELETED_ATTR,
+            ADNotificationRequestControl.WHEN_CHANGED_ATTR,
+            ADNotificationRequestControl.WHEN_CREATED_ATTR};
+
+        SearchRequest request =
+                Requests.newSearchRequest("dc=example,dc=com",
+                        SearchScope.WHOLE_SUBTREE, "(objectclass=*)", attributes)
+                        .addControl(ADNotificationRequestControl.newControl(true));
+
+        ConnectionEntryReader reader = connection.search(request);
+
+        try {
+            while (reader.hasNext()) {
+                if (!reader.isReference()) {
+                    SearchResultEntry entry = reader.readEntry(); // Updated entry
+                    final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
+
+                    Boolean isDeleted = entry.parseAttribute(
+                            ADNotificationRequestControl.IS_DELETED_ATTR
+                    ).asBoolean();
+                    if (isDeleted != null && isDeleted) {
+                        // Handle entry deletion
+                        writer.writeComment("Deleted entry: "
+                                + entry.getName().toString());
+                        writer.writeEntry(entry);
+                        writer.flush();
+                    }
+                    String whenCreated = entry.parseAttribute(
+                            ADNotificationRequestControl.WHEN_CREATED_ATTR)
+                            .asString();
+                    String whenChanged = entry.parseAttribute(
+                            ADNotificationRequestControl.WHEN_CHANGED_ATTR)
+                            .asString();
+                    if (whenCreated != null && whenChanged != null) {
+                        if (whenCreated.equals(whenChanged)) {
+                            // Handle entry addition
+                            writer.writeComment("Added entry: "
+                                    + entry.getName().toString());
+                            writer.writeEntry(entry);
+                            writer.flush();
+                        } else {
+                            // Handle entry modification
+                            writer.writeComment("Modified entry: "
+                                    + entry.getName().toString());
+                            writer.writeEntry(entry);
+                            writer.flush();
+                        }
+                    }
+                } else {
+                    reader.readReference(); // Read and ignore reference
+                }
+            }
+        } catch (final ErrorResultIOException e) {
+            System.err.println(e.getMessage());
+            System.exit(e.getCause().getResult().getResultCode().intValue());
+        } catch (final SearchResultReferenceIOException e) {
+            System.err.println("Got search reference(s): " + e.getReference()
+                    .getURIs().toString());
+        } catch (final IOException e) {
+            System.err.println(e.getMessage());
+            System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
+        }
+        // --- JCite ADNotification ---
+    }
+
+    /**
      * Use the LDAP assertion control to modify Babs Jensen's description if
      * her entry does not have a description, yet.
      *
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
new file mode 100644
index 0000000..a575fe5
--- /dev/null
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
@@ -0,0 +1,176 @@
+/*
+ * 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 2009-2010 Sun Microsystems, Inc.
+ *      Portions copyright 2011-2014 ForgeRock AS
+ */
+
+package org.forgerock.opendj.examples;
+
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.ErrorResultIOException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.ModificationType;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.controls.GenericControl;
+import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldif.LDIFEntryWriter;
+
+import java.io.IOException;
+
+/**
+ * An example client application which uses
+ * {@link org.forgerock.opendj.ldap.controls.GenericControl} to pass the
+ * pre-read request control from <a href="http://tools.ietf.org/html/rfc4527"
+ * >RFC 4527 - Lightweight Directory Access Protocol (LDAP) Read Entry Controls</a>.
+ *
+ * <p>This example takes the following command line parameters:
+ *
+ * <pre>
+ *  &lt;host> &lt;port> &lt;username> &lt;password> &lt;userDN>
+ * </pre>
+ *
+ * <p>This example modifies the description attribute of an entry that
+ * you specify in the &lt;userDN> command line parameter.
+ */
+public final class UseGenericControl {
+    /**
+     * Main method.
+     *
+     * @param args The command line arguments: host, port, username, password,
+     *             base DN, where the base DN is the root of a naming context.
+     */
+    public static void main(final String[] args) {
+        if (args.length < 5) {
+            System.err.println("Usage: host port username password userDN");
+            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 userDN = args[4];
+
+        // --- JCite ---
+        // Create an LDIF writer to write entries 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;
+
+        // Prepare the value for the GenericControl.
+
+        // http://tools.ietf.org/html/rfc4527#section-3.1 says:
+        // "The Pre-Read request control is a LDAP Control [RFC4511] whose
+        // controlType is 1.3.6.1.1.13.1 and whose controlValue is a BER-encoded
+        // AttributeSelection [RFC4511], as extended by [RFC3673]."
+
+        ByteStringBuilder builder = new ByteStringBuilder();
+        ASN1Writer asn1Writer = ASN1.getWriter(builder);
+        try {
+            asn1Writer.writeStartSequence();
+            asn1Writer.writeOctetString("description");
+            asn1Writer.writeEndSequence();
+            asn1Writer.flush();
+            asn1Writer.close();
+        } catch (Exception e) {
+            System.out.println("Failed to prepare control value: "
+                    + e.getCause());
+            System.exit(-1);
+        }
+
+        try {
+            connection = factory.getConnection();
+            connection.bind(userName, password.toCharArray());
+
+            // Modify the user description.
+            final ModifyRequest request =
+                    Requests
+                            .newModifyRequest(userDN)
+                            .addModification(ModificationType.REPLACE,
+                                    "description", "A new description")
+                            .addControl(
+                                    GenericControl
+                                            .newControl(
+                                                    "1.3.6.1.1.13.1",
+                                                    true,
+                                                    builder.toByteString()));
+            final Result result = connection.modify(request);
+
+            // Display the description before and after the modification.
+            if (result.isSuccess()) {
+                final PreReadResponseControl control = result.getControl(
+                        PreReadResponseControl.DECODER, new DecodeOptions()
+                );
+                final Entry unmodifiedEntry = control.getEntry();
+                writer.writeComment("Before modification");
+                writer.writeEntry(unmodifiedEntry);
+                writer.flush();
+
+                final SearchResultEntry modifiedEntry =
+                        connection.searchSingleEntry(
+                                userDN,
+                                SearchScope.BASE_OBJECT,
+                                "(objectclass=*)",
+                                "description");
+                writer.writeComment("After modification");
+                writer.writeEntry(modifiedEntry);
+                writer.flush();
+            }
+
+        } catch (final ErrorResultException e) {
+            System.err.println(e.getMessage());
+            System.exit(e.getResult().getResultCode().intValue());
+        } catch (final ErrorResultIOException e) {
+            System.err.println(e.getMessage());
+            System.exit(e.getCause().getResult().getResultCode().intValue());
+        } catch (final IOException e) {
+            System.err.println(e.getMessage());
+            System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
+        } finally {
+            if (connection != null) {
+                connection.close();
+            }
+        }
+        // --- JCite ---
+    }
+
+    private UseGenericControl() {
+        // Not used.
+    }
+}
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm b/opendj-sdk/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
index ab21b51..e4145f8 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
@@ -20,7 +20,7 @@
   !
   ! CCPL HEADER END
   !
-  !      Copyright 2011-2013 ForgeRock AS
+  !      Copyright 2011-2014 ForgeRock AS
   !    
 -->
 <document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -103,6 +103,10 @@
           - illustrates how to add or remove a member from a static group
         </li>
         <li>
+          <a href="xref/org/forgerock/opendj/examples/UseGenericControl.html">Use <code>GenericControl</code></a>
+          - illustrates how to use <code>GenericControl</code> to add a pre-read request control
+        </li>
+        <li>
           <a href="xref/org/forgerock/opendj/examples/GetADChangeNotifications.html">Use <code>GenericControl</code></a>
           - illustrates how to use <code>GenericControl</code> to get change notifications from Active Directory
         </li>
diff --git a/opendj-sdk/src/main/docbkx/dev-guide/chap-controls.xml b/opendj-sdk/src/main/docbkx/dev-guide/chap-controls.xml
index a3d8b62..5aed0fe 100644
--- a/opendj-sdk/src/main/docbkx/dev-guide/chap-controls.xml
+++ b/opendj-sdk/src/main/docbkx/dev-guide/chap-controls.xml
@@ -20,15 +20,15 @@
   !
   ! CCPL HEADER END
   !
-  !      Copyright 2011-2013 ForgeRock AS
+  !      Copyright 2011-2014 ForgeRock AS
   !    
 -->
 <chapter xml:id='chap-controls'
  xmlns='http://docbook.org/ns/docbook' version='5.0' xml:lang='en'
  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
- xsi:schemaLocation='http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd'
- xmlns:xlink='http://www.w3.org/1999/xlink'
- xmlns:xinclude='http://www.w3.org/2001/XInclude'>
+ xsi:schemaLocation='http://docbook.org/ns/docbook
+                     http://docbook.org/xml/5.0/xsd/docbook.xsd'
+ xmlns:xlink='http://www.w3.org/1999/xlink'>
  <title>Working With Controls</title>
  <indexterm>
   <primary>Controls</primary>
@@ -350,6 +350,33 @@
 </programlisting>
  </section>
 
+ <section xml:id="use-ad-notification-request-control">
+  <title>Microsoft LDAP Server Notification Control</title>
+  <indexterm>
+   <primary>Controls</primary>
+   <secondary>Microsoft LDAP Server Notification Control</secondary>
+  </indexterm>
+
+  <para>
+   The Microsoft <link xlink:show="new"
+   xlink:href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa366983(v=vs.85).aspx"
+   >LDAP Server Notification Control</link>
+   with OID <literal>1.2.840.113556.1.4.528</literal>
+   can be used to register a change notification request
+   for a search on Microsoft Active Directory.
+  </para>
+
+  <programlisting language="java"
+  >[jcp:org.forgerock.opendj.examples.Controls:--- JCite ADNotification ---]</programlisting>
+
+  <para>
+   When you run the search against Active Directory
+   and then create, update, and delete a new user
+   Active Directory notifies you of changes to directory data.
+  </para>
+
+ </section>
+
  <section xml:id="use-password-expired-control">
   <title>Password Expired Response Control</title>
   <indexterm>
@@ -837,79 +864,32 @@
   <literal>GenericControl</literal> class when adding the control to your
   request.</para>
 
-  <para>For example, the Microsoft <link xlink:show="new"
-  xlink:href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa366983(v=vs.85).aspx"
-  >LDAP Server Notification Control</link> with OID
-  <literal>1.2.840.113556.1.4.528</literal> can be used to register a change
-  notification request for a search on Microsoft Active Directory. You can use
-  a <literal>GenericControl.newControl()</literal> static method to add the
-  request control to your search.</para>
+  <para>
+   The following example uses a <literal>GenericControl</literal>
+   to add a pre-read request control when replacing the description
+   on a user's entry.
+   OpenDJ LDAP SDK already implements the pre-read request control,
+   as shown in <xref linkend="use-pre-read-control" />.
+   The example is of interest mainly because it shows
+   that the values that you pass when using a <literal>GenericControl</literal>
+   must be prepared as indicated in the specification of the control.
+  </para>
 
   <programlisting language="java"
-  >[jcp:org.forgerock.opendj.examples.GetADChangeNotifications:--- JCite ---]</programlisting>
+  >[jcp:org.forgerock.opendj.examples.UseGenericControl:--- JCite ---]</programlisting>
 
-  <para>When you run the search against Active Directory and then create,
-  update, and delete a new user, in this example
-  <literal>CN=New User,CN=Users,DC=ad,DC=example,DC=com</literal>, Active
-  Directory notifies you of changes to directory data.</para>
+  <para>
+   When you run this example against a user entry in OpenDJ directory server,
+   you see something like the following result.
+  </para>
 
-  <programlisting language="ldif"
-  ># Search result entry: CN=RID Set,CN=WIN2008R2641,OU=Domain Controllers,
- DC=ad,DC=example,DC=com
-dn: CN=RID Set,CN=WIN2008R2641,OU=Domain Controllers,DC=ad,DC=example,DC=com
-objectClass: top
-objectClass: rIDSet
-objectGUID:: 178zQQic3EOoBOB1j2QVgQ==
-uSNChanged: 12446
+  <programlisting language="ldif"># Before modification
+dn: uid=bjensen,ou=People,dc=example,dc=com
+description: Original description
 
-# Search result entry: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-dn: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-objectClass: top
-objectClass: person
-objectClass: organizationalPerson
-objectClass: user
-objectGUID:: 7XE/OoJdFEqAegwAi2eNlA==
-uSNChanged: 12753
-
-# Search result entry: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-dn: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-objectClass: top
-objectClass: person
-objectClass: organizationalPerson
-objectClass: user
-objectGUID:: 7XE/OoJdFEqAegwAi2eNlA==
-uSNChanged: 12755
-
-# Search result entry: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-dn: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-objectClass: top
-objectClass: person
-objectClass: organizationalPerson
-objectClass: user
-objectGUID:: 7XE/OoJdFEqAegwAi2eNlA==
-uSNChanged: 12757
-
-# Search result entry: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-dn: CN=New User,CN=Users,DC=ad,DC=example,DC=com
-objectClass: top
-objectClass: person
-objectClass: organizationalPerson
-objectClass: user
-objectGUID:: 7XE/OoJdFEqAegwAi2eNlA==
-uSNChanged: 12758
-
-# Search result entry: CN=New User\0ADEL:3a3f71ed-5d82-4a14-807a-0c008b678d94,
-# CN=Deleted Objects,DC=ad,DC=example,DC=com
-dn: CN=New User\0ADEL:3a3f71ed-5d82-4a14-807a-0c008b678d94,CN=Deleted Objects,
- DC=ad,DC=example,DC=com
-objectClass: top
-objectClass: person
-objectClass: organizationalPerson
-objectClass: user
-objectGUID:: 7XE/OoJdFEqAegwAi2eNlA==
-isDeleted: TRUE
-uSNChanged: 12759
-</programlisting>
+# After modification
+dn: uid=bjensen,ou=People,dc=example,dc=com
+description: A new description</programlisting>
 
   <para>The <literal>GenericControl</literal> class is useful with controls that
   do not require you to encode complex request values, or decode complex
diff --git a/opendj-sdk/src/site/resources/Example.ldif b/opendj-sdk/src/site/resources/Example.ldif
index cbac340..0c459b7 100644
--- a/opendj-sdk/src/site/resources/Example.ldif
+++ b/opendj-sdk/src/site/resources/Example.ldif
@@ -20,7 +20,7 @@
 # CDDL HEADER END
 #
 #      Copyright 2006-2008 Sun Microsystems, Inc.
-#      Portions Copyright 2012-2013 ForgeRock AS
+#      Portions Copyright 2012-2014 ForgeRock AS
 #
 #
 # dc=com sample LDIF file
@@ -682,6 +682,7 @@
 preferredLanguage: en, ko;q=0.8
 uidNumber: 1076
 gidNumber: 1000
+description: Original description
 
 dn: uid=bmaddox,ou=People,dc=example,dc=com
 objectClass: person

--
Gitblit v1.10.0