mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Mark Craig
04.14.2012 b35a99489a13b3ac380a8f855a1dd94225e804de
Adding content to the chapter on LDAP controls; not quite done yet
3 files modified
1079 ■■■■■ changed files
opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java 493 ●●●●● patch | view | raw | blame | history
opendj3/src/main/docbkx/dev-guide/chap-controls.xml 568 ●●●● patch | view | raw | blame | history
opendj3/src/site/resources/Example.ldif 18 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
@@ -29,17 +29,22 @@
import java.io.IOException;
import java.util.Collection;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DecodeException;
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.Filter;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.RootDSE;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchResultReferenceIOException;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.SortKey;
import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
import org.forgerock.opendj.ldap.controls.AuthorizationIdentityRequestControl;
import org.forgerock.opendj.ldap.controls.AuthorizationIdentityResponseControl;
@@ -47,13 +52,27 @@
import org.forgerock.opendj.ldap.controls.GetEffectiveRightsRequestControl;
import org.forgerock.opendj.ldap.controls.ManageDsaITRequestControl;
import org.forgerock.opendj.ldap.controls.MatchedValuesRequestControl;
import org.forgerock.opendj.ldap.controls.PasswordExpiredResponseControl;
import org.forgerock.opendj.ldap.controls.PasswordExpiringResponseControl;
import org.forgerock.opendj.ldap.controls.PasswordPolicyRequestControl;
import org.forgerock.opendj.ldap.controls.PasswordPolicyResponseControl;
import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
import org.forgerock.opendj.ldap.controls.PersistentSearchChangeType;
import org.forgerock.opendj.ldap.controls.PersistentSearchRequestControl;
import org.forgerock.opendj.ldap.controls.PostReadRequestControl;
import org.forgerock.opendj.ldap.controls.PostReadResponseControl;
import org.forgerock.opendj.ldap.controls.PreReadRequestControl;
import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
import org.forgerock.opendj.ldap.controls.ServerSideSortRequestControl;
import org.forgerock.opendj.ldap.controls.ServerSideSortResponseControl;
import org.forgerock.opendj.ldap.controls.SimplePagedResultsControl;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
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.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.opendj.ldif.ConnectionEntryReader;
@@ -104,9 +123,18 @@
            // For the EntryChangeNotificationResponseControl see
            // usePersistentSearchRequestControl()
            //useGetEffectiveRightsRequestControl(connection);
            //usePersistentSearchRequestControl(connection);
            //useManageDsaITRequestControl(connection);
            useMatchedValuesRequestControl(connection);
            //useMatchedValuesRequestControl(connection);
            //usePasswordExpiredResponseControl(connection);
            //usePasswordExpiringResponseControl(connection);
            //usePasswordPolicyRequestControl(connection);
            //usePermissiveModifyRequestControl(connection);
            //usePersistentSearchRequestControl(connection);
            //usePostReadRequestControl(connection);
            //usePreReadRequestControl(connection);
            //useProxiedAuthV2RequestControl(connection);
            //useServerSideSortRequestControl(connection);
            useSimplePagedResultsControl(connection);
            // TODO: The rest of the supported controls
        } catch (final ErrorResultException e) {
@@ -121,7 +149,8 @@
    }
    /**
     * Use the LDAP assertion control to perform a trivial modification.
     * Use the LDAP assertion control to modify Babs Jensen's description if
     * her entry does not have a description, yet.
     *
     * @param connection
     *            Active connection to LDAP server containing <a
@@ -132,25 +161,26 @@
     */
    static void useAssertionControl(Connection connection) throws ErrorResultException {
        if (isSupported(AssertionRequestControl.OID)) {
            // Modify Babs Jensen's description if her entry does not have
            // a description, yet.
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            ModifyRequest request = Requests.newModifyRequest(dn);
            request.addControl(AssertionRequestControl.newControl(true, Filter
                    .valueOf("!(description=*)")));
            request.addModification(ModificationType.ADD, "description",
                    "Created with the help of the LDAP assertion control");
            final ModifyRequest request =
                    Requests.newModifyRequest(dn)
                        .addControl(AssertionRequestControl.newControl(
                                true, Filter.valueOf("!(description=*)")))
                        .addModification(ModificationType.ADD, "description",
                                "Created using LDAP assertion control");
            connection.modify(request);
            LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
            try {
                writer.writeEntry(connection.readEntry(dn, "description"));
                writer.close();
            } catch (final IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("AssertionRequestControl not supported.");
        }
    }
@@ -166,12 +196,13 @@
     */
    static void useAuthorizationIdentityRequestControl(Connection connection) throws ErrorResultException {
        if (isSupported(AuthorizationIdentityRequestControl.OID)) {
            final String name = "uid=bjensen,ou=People,dc=example,dc=com";
            final char[] password = "hifalutin".toCharArray();
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            final char[] pwd = "hifalutin".toCharArray();
            System.out.println("Binding as " + name);
            BindRequest request = Requests.newSimpleBindRequest(name, password);
            request.addControl(AuthorizationIdentityRequestControl.newControl(true));
            System.out.println("Binding as " + dn);
            final BindRequest request =
                    Requests.newSimpleBindRequest(dn, pwd)
                        .addControl(AuthorizationIdentityRequestControl.newControl(true));
            final BindResult result = connection.bind(request);
            try {
@@ -183,6 +214,8 @@
            } catch (final DecodeException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("AuthorizationIdentityRequestControl not supported.");
        }
    }
@@ -202,12 +235,12 @@
        if (isSupported(GetEffectiveRightsRequestControl.OID)) {
            final String authDN = "uid=kvaughan,ou=People,dc=example,dc=com";
            SearchRequest request =
            final SearchRequest request =
                    Requests.newSearchRequest(
                            "dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
                            "(uid=bjensen)", "cn", "aclRights", "aclRightsInfo");
            request.addControl(
                    GetEffectiveRightsRequestControl.newControl(true, authDN, "cn"));
                            "(uid=bjensen)", "cn", "aclRights", "aclRightsInfo")
                            .addControl(GetEffectiveRightsRequestControl.newControl(
                                    true, authDN, "cn"));
            final ConnectionEntryReader reader = connection.search(request);
            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
@@ -226,6 +259,8 @@
            } catch (final IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("GetEffectiveRightsRequestControl not supported.");
        }
    }
@@ -270,6 +305,8 @@
            } catch (final IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("ManageDsaITRequestControl not supported.");
        }
    }
@@ -287,11 +324,11 @@
    static void useMatchedValuesRequestControl(Connection connection) throws ErrorResultException {
        if (isSupported(MatchedValuesRequestControl.OID)) {
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            SearchRequest request =
            final SearchRequest request =
                    Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT,
                            "(objectclass=*)", "cn");
            final String filter = "cn=Babs Jensen";
            request.addControl(MatchedValuesRequestControl.newControl(true, filter));
                            "(objectclass=*)", "cn")
                            .addControl(MatchedValuesRequestControl.newControl(
                                    true, "(cn=Babs Jensen)"));
            final SearchResultEntry entry = connection.searchSingleEntry(request);
            System.out.println("Reading entry with matched values request.");
@@ -302,6 +339,159 @@
            } catch (final IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("MatchedValuesRequestControl not supported.");
        }
    }
    /**
     * Check the Password Expired Response Control. To get this code to output
     * something, you must first set up an appropriate password policy and wait
     * for Barbara Jensen's password to expire.
     *
     * @param connection
     *            Active connection to LDAP server containing <a
     *            href="http://opendj.forgerock.org/Example.ldif"
     *            >Example.ldif</a> content.
     */
    static void usePasswordExpiredResponseControl(Connection connection) {
        if (isSupported(PasswordExpiredResponseControl.OID)) {
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            final char[] pwd = "hifalutin".toCharArray();
            try {
                connection.bind(dn, pwd);
            } catch (ErrorResultException e) {
                final Result result = e.getResult();
                try {
                    final PasswordExpiredResponseControl control =
                            result.getControl(PasswordExpiredResponseControl.DECODER,
                                    new DecodeOptions());
                    if (!(control == null) && control.hasValue()) {
                        System.out.println("Password expired for " + dn);
                    }
                } catch (DecodeException de) {
                    de.printStackTrace();
                }
            }
        } else {
            System.out.println("PasswordExpiredResponseControl not supported.");
        }
    }
    /**
     * Check the Password Expiring Response Control. To get this code to output
     * something, you must first set up an appropriate password policy and wait
     * for Barbara Jensen's password to get old enough that the server starts
     * warning about expiration.
     *
     * @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 usePasswordExpiringResponseControl(Connection connection)
            throws ErrorResultException {
        if (isSupported(PasswordExpiringResponseControl.OID)) {
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            final char[] pwd = "hifalutin".toCharArray();
            final BindResult result = connection.bind(dn, pwd);
            try {
                final PasswordExpiringResponseControl control =
                        result.getControl(PasswordExpiringResponseControl.DECODER,
                                new DecodeOptions());
                if (!(control == null) && control.hasValue()) {
                    System.out.println("Password for " + dn + " expires in "
                            + control.getSecondsUntilExpiration() + " seconds.");
                }
            } catch (DecodeException de) {
                de.printStackTrace();
            }
        } else {
            System.out.println("PasswordExpiringResponseControl not supported");
        }
    }
    /**
     * Use the Password Policy Request and Response Controls. To get this code
     * to output something, you must first set up an appropriate password policy
     * and wait for Barbara Jensen's password to get old enough that the server
     * starts warning about expiration, or for the password to expire.
     *
     * @param connection
     *            Active connection to LDAP server containing <a
     *            href="http://opendj.forgerock.org/Example.ldif"
     *            >Example.ldif</a> content.
     */
    static void usePasswordPolicyRequestControl(Connection connection) {
        if (isSupported(PasswordPolicyRequestControl.OID)) {
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            final char[] pwd = "hifalutin".toCharArray();
            try {
                final BindRequest request = Requests.newSimpleBindRequest(dn, pwd)
                        .addControl(PasswordPolicyRequestControl.newControl(true));
                final BindResult result = connection.bind(request);
                final PasswordPolicyResponseControl control =
                        result.getControl(PasswordPolicyResponseControl.DECODER,
                                new DecodeOptions());
                if (!(control == null) && !(control.getWarningType() == null)) {
                    System.out.println("Password policy warning "
                            + control.getWarningType().toString() + ", value "
                            + control.getWarningValue() + " for " + dn);
                }
            } catch (ErrorResultException e) {
                final Result result = e.getResult();
                try {
                    final PasswordPolicyResponseControl control =
                            result.getControl(PasswordPolicyResponseControl.DECODER,
                                    new DecodeOptions());
                    if (!(control == null)) {
                        System.out.println("Password policy error "
                                + control.getErrorType().toString() + " for " + dn);
                    }
                } catch (DecodeException de) {
                    de.printStackTrace();
                }
            } catch (DecodeException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("PasswordPolicyRequestControl not supported");
        }
    }
    /**
     * Use Permissive Modify Request Control to try to add an attribute that
     * already exists.
     *
     * @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 usePermissiveModifyRequestControl(Connection connection)
            throws ErrorResultException {
        if (isSupported(PermissiveModifyRequestControl.OID)) {
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            final ModifyRequest request =
                    Requests.newModifyRequest(dn)
                        .addControl(PermissiveModifyRequestControl.newControl(true))
                        .addModification(ModificationType.ADD, "uid", "bjensen");
            connection.modify(request);
            System.out.println("Permissive modify did not complain about "
                    + "attempt to add uid: bjensen to " + dn + ".");
        } else {
            System.out.println("PermissiveModifyRequestControl not supported");
        }
    }
@@ -322,18 +512,16 @@
     */
    static void usePersistentSearchRequestControl(Connection connection) throws ErrorResultException {
        if (isSupported(PersistentSearchRequestControl.OID)) {
            SearchRequest request =
            final SearchRequest request =
                    Requests.newSearchRequest(
                            "dc=example,dc=com",
                            SearchScope.WHOLE_SUBTREE,
                            "(objectclass=inetOrgPerson)",
                            "cn");
            request.addControl(PersistentSearchRequestControl.newControl(
                    true, true, true, // isCritical, changesOnly, returnECs
                    PersistentSearchChangeType.ADD,
                    PersistentSearchChangeType.DELETE,
                    PersistentSearchChangeType.MODIFY,
                    PersistentSearchChangeType.MODIFY_DN));
                            "dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
                            "(objectclass=inetOrgPerson)", "cn")
                            .addControl(PersistentSearchRequestControl.newControl(
                                    true, true, true, // isCritical, changesOnly, returnECs
                                    PersistentSearchChangeType.ADD,
                                    PersistentSearchChangeType.DELETE,
                                    PersistentSearchChangeType.MODIFY,
                                    PersistentSearchChangeType.MODIFY_DN));
            final ConnectionEntryReader reader = connection.search(request);
@@ -365,6 +553,241 @@
            } catch (final SearchResultReferenceIOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("PersistentSearchRequestControl not supported.");
        }
    }
    /**
     * Use Post Read Controls to get entry content after a modification.
     *
     * @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 usePostReadRequestControl(Connection connection) throws ErrorResultException {
        if (isSupported(PostReadRequestControl.OID)) {
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            final ModifyRequest request =
                    Requests.newModifyRequest(dn)
                    .addControl(PostReadRequestControl.newControl(true, "description"))
                    .addModification(ModificationType.REPLACE,
                            "description", "Using the PostReadRequestControl");
            final Result result = connection.modify(request);
            try {
                final PostReadResponseControl control =
                        result.getControl(PostReadResponseControl.DECODER,
                                new DecodeOptions());
                final Entry entry = control.getEntry();
                final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
                writer.writeEntry(entry);
                writer.close();
            } catch (DecodeException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("PostReadRequestControl not supported");
        }
    }
    /**
     * Use Pre Read Controls to get entry content before a modification.
     *
     * @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 usePreReadRequestControl(Connection connection) throws ErrorResultException {
        if (isSupported(PreReadRequestControl.OID)) {
            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
            final ModifyRequest request =
                    Requests.newModifyRequest(dn)
                    .addControl(PreReadRequestControl.newControl(true, "mail"))
                    .addModification(
                            ModificationType.REPLACE, "mail", "modified@example.com");
            final Result result = connection.modify(request);
            try {
                final PreReadResponseControl control =
                        result.getControl(PreReadResponseControl.DECODER,
                                new DecodeOptions());
                final Entry entry = control.getEntry();
                final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
                writer.writeEntry(entry);
                writer.close();
            } catch (DecodeException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("PreReadRequestControl not supported");
        }
    }
    /**
     * Use proxied authorization to modify an identity as another user.
     *
     * @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 useProxiedAuthV2RequestControl(Connection connection) throws ErrorResultException {
        if (isSupported(ProxiedAuthV2RequestControl.OID)) {
            final String bindDN = "cn=My App,ou=Apps,dc=example,dc=com";
            final String targetDn = "uid=bjensen,ou=People,dc=example,dc=com";
            final String authzId = "dn:uid=kvaughan,ou=People,dc=example,dc=com";
            final ModifyRequest request =
                    Requests.newModifyRequest(targetDn)
                    .addControl(ProxiedAuthV2RequestControl.newControl(authzId))
                    .addModification(ModificationType.REPLACE, "description",
                            "Done with proxied authz");
            connection.bind(bindDN, "password".toCharArray());
            connection.modify(request);
            final Entry entry = connection.readEntry(targetDn, "description");
            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
            try {
                writer.writeEntry(entry);
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("ProxiedAuthV2RequestControl not supported");
        }
    }
    /**
     * Use the server-side sort controls.
     *
     * @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 useServerSideSortRequestControl(Connection connection) throws ErrorResultException {
        if (isSupported(ServerSideSortRequestControl.OID)) {
            final SearchRequest request =
                    Requests.newSearchRequest("dc=example,dc=com",
                            SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn")
                            .addControl(ServerSideSortRequestControl.newControl(
                                            true, new SortKey("cn")));
            final SearchResultHandler resultHandler = new MySearchResultHandler();
            final Result result = connection.search(request, resultHandler);
            try {
                final ServerSideSortResponseControl control =
                        result.getControl(ServerSideSortResponseControl.DECODER,
                                new DecodeOptions());
                if (control != null && control.getResult() == ResultCode.SUCCESS) {
                    System.out.println("# Entries are sorted.");
                    // FIXME: But the order is backwards!
                } else {
                    System.out.println("# Entries not necessarily sorted");
                }
            } catch (DecodeException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("ServerSideSortRequestControl not supported");
        }
    }
    private static class MySearchResultHandler implements SearchResultHandler {
        @Override
        public void handleErrorResult(ErrorResultException error) {
            // Ignore.
        }
        @Override
        public void handleResult(Result result) {
            // Ignore.
        }
        @Override
        public boolean handleEntry(SearchResultEntry entry) {
            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
            try {
                writer.writeEntry(entry);
                writer.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return true;
        }
        @Override
        public boolean handleReference(SearchResultReference reference) {
            System.out.println("Got a reference: " + reference.toString());
            return false;
        }
    }
    /**
     * Use the simple paged results mechanism.
     *
     * @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 useSimplePagedResultsControl(Connection connection) throws ErrorResultException {
        if (isSupported(SimplePagedResultsControl.OID)) {
            ByteString cookie = ByteString.empty();
            SearchRequest request;
            final SearchResultHandler resultHandler = new MySearchResultHandler();
            Result result;
            int page = 1;
            do {
                System.out.println("# Simple paged results: Page " + page);
                request =
                        Requests.newSearchRequest("dc=example,dc=com",
                                SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn")
                                .addControl(SimplePagedResultsControl.newControl(
                                        true, 3, cookie));
                result = connection.search(request, resultHandler);
                try {
                    SimplePagedResultsControl control =
                            result.getControl(SimplePagedResultsControl.DECODER,
                                    new DecodeOptions());
                    cookie = control.getCookie();
                } catch (DecodeException e) {
                    e.printStackTrace();
                }
                ++page;
            } while (cookie.length() != 0);
        } else {
            System.out.println("SimplePagedResultsControl not supported");
        }
    }
opendj3/src/main/docbkx/dev-guide/chap-controls.xml
@@ -42,7 +42,7 @@
  <emphasis>request controls</emphasis>, and those sent by servers are termed
  <emphasis>response controls</emphasis>.</para>
 </section>
 <section xml:id="get-supported-controls">
  <title>Determining Supported Controls</title>
@@ -124,7 +124,7 @@
}
</programlisting>
 </section>
 <section xml:id="use-assertion-request-control">
  <title>Assertion Request Control</title>
@@ -136,31 +136,31 @@
  <programlisting language="java">
if (isSupported(AssertionRequestControl.OID)) {
    // Modify Babs Jensen's description if her entry does not have
    // a description, yet.
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    ModifyRequest request = Requests.newModifyRequest(dn);
    request.addControl(AssertionRequestControl.newControl(true, Filter
            .valueOf("!(description=*)")));
    request.addModification(ModificationType.ADD, "description",
            "Created with the help of the LDAP assertion control");
    final ModifyRequest request =
            Requests.newModifyRequest(dn)
                .addControl(AssertionRequestControl.newControl(
                        true, Filter.valueOf("!(description=*)")))
                .addModification(ModificationType.ADD, "description",
                        "Created using LDAP assertion control");
    connection.modify(request);
    LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
    final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
    try {
        writer.writeEntry(connection.readEntry(dn, "description"));
        writer.close();
    } catch (final IOException e) {
        e.printStackTrace();
    }
}</programlisting>
}
</programlisting>
  <para>OpenDJ directory server supports the LDAP assertion control:</para>
  <programlisting language="ldif">dn: uid=bjensen,ou=People,dc=example,dc=com
description: Created with the help of the LDAP assertion control</programlisting>
description: Created using LDAP assertion control</programlisting>
 </section>
 <section xml:id="use-authorization-identity-control">
@@ -173,12 +173,13 @@
  <programlisting language="java">
if (isSupported(AuthorizationIdentityRequestControl.OID)) {
    final String name = "uid=bjensen,ou=People,dc=example,dc=com";
    final char[] password = "hifalutin".toCharArray();
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    final char[] pwd = "hifalutin".toCharArray();
    System.out.println("Binding as " + name);
    BindRequest request = Requests.newSimpleBindRequest(name, password);
    request.addControl(AuthorizationIdentityRequestControl.newControl(true));
    System.out.println("Binding as " + dn);
    final BindRequest request =
            Requests.newSimpleBindRequest(dn, pwd)
                .addControl(AuthorizationIdentityRequestControl.newControl(true));
    final BindResult result = connection.bind(request);
    try {
@@ -190,7 +191,8 @@
    } catch (final DecodeException e) {
        e.printStackTrace();
    }
}</programlisting>
}
</programlisting>
  <para>OpenDJ directory server supports the LDAP Authorization Identity
  Controls:</para>
@@ -210,18 +212,16 @@
  <programlisting language="java">
if (isSupported(PersistentSearchRequestControl.OID)) {
    SearchRequest request =
    final SearchRequest request =
            Requests.newSearchRequest(
                    "dc=example,dc=com",
                    SearchScope.WHOLE_SUBTREE,
                    "(objectclass=inetOrgPerson)",
                    "cn");
    request.addControl(PersistentSearchRequestControl.newControl(
            true, true, true, // isCritical, changesOnly, returnECs
            PersistentSearchChangeType.ADD,
            PersistentSearchChangeType.DELETE,
            PersistentSearchChangeType.MODIFY,
            PersistentSearchChangeType.MODIFY_DN));
                    "dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
                    "(objectclass=inetOrgPerson)", "cn")
                    .addControl(PersistentSearchRequestControl.newControl(
                            true, true, true, // critical,changesOnly,returnECs
                            PersistentSearchChangeType.ADD,
                            PersistentSearchChangeType.DELETE,
                            PersistentSearchChangeType.MODIFY,
                            PersistentSearchChangeType.MODIFY_DN));
    final ConnectionEntryReader reader = connection.search(request);
@@ -267,8 +267,14 @@
Change type: modifyDN
Previous DN: uid=abarnes,ou=People,dc=example,dc=com
Change number: -1</programlisting>
  <para>In this case, <literal>Change number: -1</literal> because the server
  did not set a change number value. OpenDJ directory server does not set the
  change number value in the response control. If you need to track the order
  of changes with OpenDJ directory server, read the external change log instead
  of using the entry change notification response control.</para>
 </section>
 <section xml:id="use-get-effective-rights-control">
  <title>GetEffectiveRights Request Control</title>
@@ -282,12 +288,12 @@
if (isSupported(GetEffectiveRightsRequestControl.OID)) {
    final String authDN = "uid=kvaughan,ou=People,dc=example,dc=com";
    SearchRequest request =
    final SearchRequest request =
            Requests.newSearchRequest(
                    "dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
                    "(uid=bjensen)", "cn", "aclRights", "aclRightsInfo");
    request.addControl(
            GetEffectiveRightsRequestControl.newControl(true, authDN, "cn"));
                    "(uid=bjensen)", "cn", "aclRights", "aclRightsInfo")
                    .addControl(GetEffectiveRightsRequestControl.newControl(
                            true, authDN, "cn"));
    final ConnectionEntryReader reader = connection.search(request);
    final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
@@ -371,7 +377,7 @@
 ) ( reason: evaluated allow , deciding_aci: allow all Admin group)
</programlisting>
 </section>
 <section xml:id="use-managedsait-control">
  <title>ManageDsaIT Request Control</title>
@@ -417,7 +423,7 @@
  <para>OpenDJ directory server supports the ManageDsaIT Request Control.</para>
 </section>
 <section xml:id="use-matched-values-request-control">
  <title>Matched Values Request Control</title>
@@ -435,11 +441,11 @@
  <programlisting language="java">
if (isSupported(MatchedValuesRequestControl.OID)) {
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    SearchRequest request =
    final SearchRequest request =
            Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT,
                    "(objectclass=*)", "cn");
    final String filter = "cn=Babs Jensen";
    request.addControl(MatchedValuesRequestControl.newControl(true, filter));
                    "(objectclass=*)", "cn")
                    .addControl(MatchedValuesRequestControl.newControl(
                            true, "(cn=Babs Jensen)"));
    final SearchResultEntry entry = connection.searchSingleEntry(request);
    System.out.println("Reading entry with matched values request.");
@@ -461,73 +467,511 @@
cn: Babs Jensen
</programlisting>
 </section>
 <section xml:id="use-password-expired-control">
  <title>Password Expired Response Control</title>
  <para>TODO</para>
  <para>A directory server can return the Password Expired Response Control,
  described in the Internet-Draft <link xlink:show="new"
  xlink:href="http://tools.ietf.org/html/draft-vchu-ldap-pwd-policy"><citetitle
  >Password Policy for LDAP Directories</citetitle></link>, when a bind fails
  because the password has expired. In order to see this, you must configure
  the directory to expire Barbara Jensen's password.</para>
  <programlisting language="java">
if (isSupported(PasswordExpiredResponseControl.OID)) {
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    final char[] pwd = "hifalutin".toCharArray();
    try {
        connection.bind(dn, pwd);
    } catch (ErrorResultException e) {
        final Result result = e.getResult();
        try {
            final PasswordExpiredResponseControl control =
                    result.getControl(PasswordExpiredResponseControl.DECODER,
                            new DecodeOptions());
            if (!(control == null) &amp;&amp; control.hasValue()) {
                System.out.println("Password expired for " + dn);
            }
        } catch (DecodeException de) {
            de.printStackTrace();
        }
    }
}
</programlisting>
  <para>OpenDJ directory server supports the Password Expired Response Control.
  To obtain the following output from the excerpt, you can change the default
  password policy configuration to set a short maximum password age, change
  Barbara Jensen's password, and wait for it to expire.</para>
  <programlisting
  >Password expired for uid=bjensen,ou=People,dc=example,dc=com</programlisting>
 </section>
 <section xml:id="use-password-expiring-control">
  <title>Password Expiring Response Control</title>
  <para>TODO</para>
  <para>The Password Expiring Response Control, described in the Internet-Draft
  <link xlink:href="http://tools.ietf.org/html/draft-vchu-ldap-pwd-policy"
  xlink:show="new" ><citetitle>Password Policy for LDAP
  Directories</citetitle></link>, warns your application during a bind
  that the password used will soon expire.</para>
  <programlisting language="java">
if (isSupported(PasswordExpiringResponseControl.OID)) {
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    final char[] pwd = "hifalutin".toCharArray();
    final BindResult result = connection.bind(dn, pwd);
    try {
        final PasswordExpiringResponseControl control =
                result.getControl(PasswordExpiringResponseControl.DECODER,
                        new DecodeOptions());
        if (!(control == null) &amp;&amp; control.hasValue()) {
            System.out.println("Password for " + dn + " expires in "
                    + control.getSecondsUntilExpiration() + " seconds.");
        }
    } catch (DecodeException de) {
        de.printStackTrace();
    }
}
</programlisting>
  <para>OpenDJ directory server supports the Password Expiring Response Control.
  To obtain the following output from the excerpt, you can change the default
  password policy configuration to set a maximum password age and a warning
  interval, change Barbara Jensen's password, and wait until you enter the
  warning interval before password expiration.</para>
  <programlisting>Password for uid=bjensen,ou=People,dc=example,dc=com
 expires in 237 seconds.</programlisting>
 </section>
 
 <section xml:id="use-password-policy-controls">
  <title>Password Policy Controls</title>
  <para>TODO</para>
  <para>The Behera Internet-Draft, <link xlink:show="new"
  xlink:href="http://tools.ietf.org/html/draft-behera-ldap-password-policy"
  ><citetitle>Password Policy for LDAP Directories</citetitle></link>, describes
  Password Policy Request and Response Controls. You send the request control
  with a request to let the directory server know that your application can
  handle the response control. The directory server sends the response control
  on applicable operations to communicate warnings and errors.</para>
  <programlisting language="java">
if (isSupported(PasswordPolicyRequestControl.OID)) {
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    final char[] pwd = "hifalutin".toCharArray();
    try {
        final BindRequest request = Requests.newSimpleBindRequest(dn, pwd)
                .addControl(PasswordPolicyRequestControl.newControl(true));
        final BindResult result = connection.bind(request);
        final PasswordPolicyResponseControl control =
                result.getControl(PasswordPolicyResponseControl.DECODER,
                        new DecodeOptions());
        if (!(control == null) &amp;&amp; !(control.getWarningType() == null)) {
            System.out.println("Password policy warning "
                    + control.getWarningType().toString() + ", value "
                    + control.getWarningValue() + " for " + dn);
        }
    } catch (ErrorResultException e) {
        final Result result = e.getResult();
        try {
            final PasswordPolicyResponseControl control =
                    result.getControl(PasswordPolicyResponseControl.DECODER,
                            new DecodeOptions());
            if (!(control == null)) {
                System.out.println("Password policy error "
                        + control.getErrorType().toString() + " for " + dn);
            }
        } catch (DecodeException de) {
            de.printStackTrace();
        }
    } catch (DecodeException e) {
        e.printStackTrace();
    }
}
</programlisting>
  <para>OpenDJ directory server supports the Password Policy Controls. To obtain
  the output from the excerpt, you can change the default password policy
  configuration to set a maximum password age and a warning interval, change
  Barbara Jensen's password, and then run the example during the warning
  interval and after the password has expired.</para>
  <para>For a warning:</para>
  <programlisting>Password policy warning timeBeforeExpiration, value 237 for
 uid=bjensen,ou=People,dc=example,dc=com</programlisting>
  <para>For an error:</para>
  <programlisting>Password policy error passwordExpired for
 uid=bjensen,ou=People,dc=example,dc=com</programlisting>
 </section>
 <section xml:id="use-permissive-modify-request-control">
  <title>Permissive Modify Request Control</title>
  <para>TODO</para>
  <para>Microsoft defined a Permissive Modify Request Control that relaxes
  some constraints when your application performs a modify operation and
  tries to <literal>add</literal> an attribute that already exists, or to
  <literal>delete</literal> an attribute that does not exist.</para>
  <programlisting language="java">
if (isSupported(PermissiveModifyRequestControl.OID)) {
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    final ModifyRequest request =
            Requests.newModifyRequest(dn)
                .addControl(PermissiveModifyRequestControl.newControl(true))
                .addModification(ModificationType.ADD, "uid", "bjensen");
    connection.modify(request);
    System.out.println("Permissive modify did not complain about "
            + "attempt to add uid: bjensen to " + dn + ".");
}
</programlisting>
  <para>OpenDJ directory server supports the Permissive Modify Request
  Control:</para>
  <programlisting>Permissive modify did not complain about attempt to add
 uid: bjensen to uid=bjensen,ou=People,dc=example,dc=com.</programlisting>
 </section>
 <section xml:id="use-persistent-search-request-control">
  <title>Persistent Search Request Control</title>
  <para>See <xref linkend="use-entry-change-notification-control" />.</para>
 </section>
 <section xml:id="use-post-read-control">
  <title>Post-Read Controls</title>
  <para>TODO</para>
  <para>RFC 4527, <link xlink:href="http://tools.ietf.org/html/rfc4527"
  xlink:show="new"><citetitle>LDAP Read Entry Controls</citetitle></link>,
  describes the post-read controls that let your application get the content
  of an entry immediately after modifications are applied.</para>
  <programlisting language="java">
if (isSupported(PostReadRequestControl.OID)) {
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    final ModifyRequest request =
            Requests.newModifyRequest(dn)
            .addControl(PostReadRequestControl.newControl(true, "description"))
            .addModification(ModificationType.REPLACE,
                    "description", "Using the PostReadRequestControl");
    final Result result = connection.modify(request);
    try {
        final PostReadResponseControl control =
                result.getControl(PostReadResponseControl.DECODER,
                        new DecodeOptions());
        final Entry entry = control.getEntry();
        final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
        writer.writeEntry(entry);
        writer.close();
    } catch (DecodeException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
</programlisting>
  <para>OpenDJ directory server supports these controls:</para>
  <programlisting language="ldif">dn: uid=bjensen,ou=People,dc=example,dc=com
description: Using the PostReadRequestControl</programlisting>
 </section>
 <section xml:id="use-pre-read-control">
  <title>Pre-Read Controls</title>
  <para>TODO</para>
  <para>RFC 4527, <link xlink:href="http://tools.ietf.org/html/rfc4527"
  xlink:show="new"><citetitle>LDAP Read Entry Controls</citetitle></link>,
  describes the pre-read controls that let your application get the content
  of an entry immediately before modifications are applied.</para>
  <programlisting language="java">
if (isSupported(PreReadRequestControl.OID)) {
    final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
    final ModifyRequest request =
            Requests.newModifyRequest(dn)
            .addControl(PreReadRequestControl.newControl(true, "mail"))
            .addModification(
                    ModificationType.REPLACE, "mail", "modified@example.com");
    final Result result = connection.modify(request);
    try {
        final PreReadResponseControl control =
                result.getControl(PreReadResponseControl.DECODER,
                        new DecodeOptions());
        final Entry entry = control.getEntry();
        final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
        writer.writeEntry(entry);
        writer.close();
    } catch (DecodeException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
</programlisting>
  <para>OpenDJ directory server supports these controls:</para>
  <programlisting language="ldif">dn: uid=bjensen,ou=People,dc=example,dc=com
mail: bjensen@example.com</programlisting>
 </section>
 <section xml:id="use-proxy-authz-control">
  <title>Proxied Authorization Request Controls</title>
  <para>TODO</para>
  <para>Proxied authorization provides a standard control as defined in
  <link xlink:href="http://tools.ietf.org/html/rfc4370" xlink:show="new">RFC
  4370</link> (and an earlier Internet-Draft) for binding with the user
  credentials of a proxy, who carries out LDAP operations on behalf of other
  users. You might use proxied authorization, for example, to have your
  application bind with its credentials, and then carry out operations as the
  users who login to the application.</para>
  <programlisting language="java">
if (isSupported(ProxiedAuthV2RequestControl.OID)) {
    final String bindDN = "cn=My App,ou=Apps,dc=example,dc=com";
    final String targetDn = "uid=bjensen,ou=People,dc=example,dc=com";
    final String authzId = "dn:uid=kvaughan,ou=People,dc=example,dc=com";
    final ModifyRequest request =
            Requests.newModifyRequest(targetDn)
            .addControl(ProxiedAuthV2RequestControl.newControl(authzId))
            .addModification(ModificationType.REPLACE, "description",
                    "Done with proxied authz");
    connection.bind(bindDN, "password".toCharArray());
    connection.modify(request);
    final Entry entry = connection.readEntry(targetDn, "description");
    final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
    try {
        writer.writeEntry(entry);
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}</programlisting>
  <para>OpenDJ supports proxied authorization, and the example works with the
  sample data:</para>
  <programlisting language="ldif">dn: uid=bjensen,ou=People,dc=example,dc=com
description: Done with proxied authz</programlisting>
 </section>
 <section xml:id="use-server-side-sort-control">
  <title>Server-Side Sort Controls</title>
  <para>TODO</para>
  <para>The server-side sort controls are described in RFC 2891, <link
  xlink:show="new" xlink:href="http://tools.ietf.org/html/rfc2891"><citetitle
  >LDAP Control Extension for Server Side Sorting of Search
  Results</citetitle></link>. If possible, sort on the client side instead to
  reduce load on the server. If not, then you can request a server-side
  sort.</para>
  <programlisting language="java">
static void useServerSideSortRequestControl(Connection connection)
        throws ErrorResultException {
    if (isSupported(ServerSideSortRequestControl.OID)) {
        final SearchRequest request =
                Requests.newSearchRequest("dc=example,dc=com",
                        SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn")
                        .addControl(ServerSideSortRequestControl.newControl(
                                        true, new SortKey("cn")));
        final SearchResultHandler resultHandler = new MySearchResultHandler();
        final Result result = connection.search(request, resultHandler);
        try {
            final ServerSideSortResponseControl control =
                    result.getControl(ServerSideSortResponseControl.DECODER,
                            new DecodeOptions());
            if (control != null &amp;&amp; control.getResult() == ResultCode.SUCCESS) {
                System.out.println("# Entries are sorted.");
                // FIXME: But the order is backwards!
            } else {
                System.out.println("# Entries not necessarily sorted");
            }
        } catch (DecodeException e) {
            e.printStackTrace();
        }
    } else {
        System.out.println("ServerSideSortRequestControl not supported");
    }
}
private static class MySearchResultHandler implements SearchResultHandler {
    @Override
    public void handleErrorResult(ErrorResultException error) {
        // Ignore.
    }
    @Override
    public void handleResult(Result result) {
        // Ignore.
    }
    @Override
    public boolean handleEntry(SearchResultEntry entry) {
        final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
        try {
            writer.writeEntry(entry);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }
    @Override
    public boolean handleReference(SearchResultReference reference) {
        System.out.println("Got a reference: " + reference.toString());
        return false;
    }
}
</programlisting>
  <para>OpenDJ directory server supports server-side sorting:</para>
  <programlisting language="ldif">dn: uid=ajensen,ou=People,dc=example,dc=com
cn: Allison Jensen
dn: uid=bjensen,ou=People,dc=example,dc=com
cn: Barbara Jensen
cn: Babs Jensen
dn: uid=bjense2,ou=People,dc=example,dc=com
cn: Bjorn Jensen
dn: uid=gjensen,ou=People,dc=example,dc=com
cn: Gern Jensen
dn: uid=jjensen,ou=People,dc=example,dc=com
cn: Jody Jensen
dn: uid=kjensen,ou=People,dc=example,dc=com
cn: Kurt Jensen
dn: uid=rjense2,ou=People,dc=example,dc=com
cn: Randy Jensen
dn: uid=rjensen,ou=People,dc=example,dc=com
cn: Richard Jensen
dn: uid=tjensen,ou=People,dc=example,dc=com
cn: Ted Jensen
# Entries are sorted.</programlisting>
 </section>
 <section xml:id="use-simple-paged-results-control">
  <title>Simple Paged Results Control</title>
  <para>TODO</para>
  <para>RFC 2696, <link xlink:href="http://tools.ietf.org/html/rfc2696"
  xlink:show="new"><citetitle>LDAP Control Extension for Simple Paged Results
  Manipulation</citetitle></link>, defines a control for simple paging of
  search results that works with a cookie mechanism.</para>
  <programlisting language="java">
if (isSupported(SimplePagedResultsControl.OID)) {
    ByteString cookie = ByteString.empty();
    SearchRequest request;
    final SearchResultHandler resultHandler = new MySearchResultHandler();
    Result result;
    int page = 1;
    do {
        System.out.println("# Simple paged results: Page " + page);
        request =
                Requests.newSearchRequest("dc=example,dc=com",
                        SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn")
                        .addControl(SimplePagedResultsControl.newControl(
                                true, 3, cookie));
        result = connection.search(request, resultHandler);
        try {
            SimplePagedResultsControl control =
                    result.getControl(SimplePagedResultsControl.DECODER,
                            new DecodeOptions());
            cookie = control.getCookie();
        } catch (DecodeException e) {
            e.printStackTrace();
        }
        ++page;
    } while (cookie.length() != 0);
}
</programlisting>
  <para>OpenDJ directory server supports getting simple paged results:</para>
  <programlisting language="ldif"># Simple paged results: Page 1
dn: uid=ajensen,ou=People,dc=example,dc=com
cn: Allison Jensen
dn: uid=bjense2,ou=People,dc=example,dc=com
cn: Bjorn Jensen
dn: uid=bjensen,ou=People,dc=example,dc=com
cn: Barbara Jensen
cn: Babs Jensen
# Simple paged results: Page 2
dn: uid=gjensen,ou=People,dc=example,dc=com
cn: Gern Jensen
dn: uid=jjensen,ou=People,dc=example,dc=com
cn: Jody Jensen
dn: uid=kjensen,ou=People,dc=example,dc=com
cn: Kurt Jensen
# Simple paged results: Page 3
dn: uid=rjense2,ou=People,dc=example,dc=com
cn: Randy Jensen
dn: uid=rjensen,ou=People,dc=example,dc=com
cn: Richard Jensen
dn: uid=tjensen,ou=People,dc=example,dc=com
cn: Ted Jensen
</programlisting>
 </section>
 <section xml:id="use-subentry-request-control">
  <title>Sub-entries Request Control</title>
  <para>TODO</para>
 </section>
 <section xml:id="use-subtree-delete-control">
  <title>Subtree Delete Request Control</title>
  <para>TODO</para>
 </section>
 <section xml:id="use-vlv-control">
  <title>Virtual List View Controls</title>
  <para>TODO</para>
 </section>
 <section xml:id="custom-control">
  <title>Custom Controls</title>
  <para>TODO</para>
opendj3/src/site/resources/Example.ldif
@@ -61,6 +61,9 @@
aci: (target="ldap:///dc=example,dc=com") (targetattr =
 "*")(version 3.0; acl "allow all Admin group"; allow(all) groupdn =
 "ldap:///cn=Directory Administrators,ou=Groups,dc=example,dc=com";)
aci: (target="ldap:///dc=example,dc=com") (targetattr ="*
 ")(version 3.0; acl "Allow apps proxied auth"; allow(all, proxy
 )(userdn = "ldap:///cn=*,ou=Apps,dc=example,dc=com");)
dn: ou=Company Servers,dc=example,dc=com
objectClass: organizationalUnit
@@ -3756,3 +3759,18 @@
objectClass: top
ref: ldap:///ou=People,dc=example,dc=com
dn: ou=Apps,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Apps
dn: cn=My App,ou=Apps,dc=example,dc=com
cn: My App
objectClass: person
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: top
sn: App
userPassword: password
ds-privilege-name: proxied-auth