001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2012-2016 ForgeRock AS.
015 */
016
017package org.forgerock.opendj.examples;
018
019import java.io.IOException;
020import java.util.Collection;
021
022import com.forgerock.opendj.ldap.controls.AffinityControl;
023import org.forgerock.opendj.ldap.ByteString;
024import org.forgerock.opendj.ldap.Connection;
025import org.forgerock.opendj.ldap.DecodeException;
026import org.forgerock.opendj.ldap.DecodeOptions;
027import org.forgerock.opendj.ldap.Entry;
028import org.forgerock.opendj.ldap.LdapException;
029import org.forgerock.opendj.ldap.Filter;
030import org.forgerock.opendj.ldap.LDAPConnectionFactory;
031import org.forgerock.opendj.ldap.ModificationType;
032import org.forgerock.opendj.ldap.ResultCode;
033import org.forgerock.opendj.ldap.RootDSE;
034import org.forgerock.opendj.ldap.SearchResultHandler;
035import org.forgerock.opendj.ldap.SearchResultReferenceIOException;
036import org.forgerock.opendj.ldap.SearchScope;
037import org.forgerock.opendj.ldap.SortKey;
038import org.forgerock.opendj.ldap.controls.ADNotificationRequestControl;
039import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
040import org.forgerock.opendj.ldap.controls.AuthorizationIdentityRequestControl;
041import org.forgerock.opendj.ldap.controls.AuthorizationIdentityResponseControl;
042import org.forgerock.opendj.ldap.controls.EntryChangeNotificationResponseControl;
043import org.forgerock.opendj.ldap.controls.GetEffectiveRightsRequestControl;
044import org.forgerock.opendj.ldap.controls.ManageDsaITRequestControl;
045import org.forgerock.opendj.ldap.controls.MatchedValuesRequestControl;
046import org.forgerock.opendj.ldap.controls.PasswordExpiredResponseControl;
047import org.forgerock.opendj.ldap.controls.PasswordExpiringResponseControl;
048import org.forgerock.opendj.ldap.controls.PasswordPolicyRequestControl;
049import org.forgerock.opendj.ldap.controls.PasswordPolicyResponseControl;
050import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
051import org.forgerock.opendj.ldap.controls.PersistentSearchChangeType;
052import org.forgerock.opendj.ldap.controls.PersistentSearchRequestControl;
053import org.forgerock.opendj.ldap.controls.PostReadRequestControl;
054import org.forgerock.opendj.ldap.controls.PostReadResponseControl;
055import org.forgerock.opendj.ldap.controls.PreReadRequestControl;
056import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
057import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
058import org.forgerock.opendj.ldap.controls.ServerSideSortRequestControl;
059import org.forgerock.opendj.ldap.controls.ServerSideSortResponseControl;
060import org.forgerock.opendj.ldap.controls.SimplePagedResultsControl;
061import org.forgerock.opendj.ldap.controls.SubentriesRequestControl;
062import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
063import org.forgerock.opendj.ldap.controls.VirtualListViewRequestControl;
064import org.forgerock.opendj.ldap.controls.VirtualListViewResponseControl;
065import org.forgerock.opendj.ldap.requests.BindRequest;
066import org.forgerock.opendj.ldap.requests.DeleteRequest;
067import org.forgerock.opendj.ldap.requests.ModifyRequest;
068import org.forgerock.opendj.ldap.requests.Requests;
069import org.forgerock.opendj.ldap.requests.SearchRequest;
070import org.forgerock.opendj.ldap.responses.BindResult;
071import org.forgerock.opendj.ldap.responses.Result;
072import org.forgerock.opendj.ldap.responses.SearchResultEntry;
073import org.forgerock.opendj.ldap.responses.SearchResultReference;
074import org.forgerock.opendj.ldif.ConnectionEntryReader;
075import org.forgerock.opendj.ldif.LDIFEntryWriter;
076
077/**
078 * This command-line client demonstrates use of LDAP controls. The client takes
079 * as arguments the host and port for the directory server, and expects to find
080 * the entries and access control instructions as defined in <a
081 * href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>.
082 *
083 * This client connects as <code>cn=Directory Manager</code> with password
084 * <code>password</code>. Not a best practice; in real code use application
085 * specific credentials to connect, and ensure that your application has access
086 * to use the LDAP controls needed.
087 */
088public final class Controls {
089
090    /**
091     * Connect to the server, and then try to use some LDAP controls.
092     *
093     * @param args
094     *            The command line arguments: host, port
095     */
096    public static void main(final String[] args) {
097        if (args.length != 2) {
098            System.err.println("Usage: host port");
099            System.err.println("For example: localhost 1389");
100            System.exit(1);
101        }
102        final String host = args[0];
103        final int port = Integer.parseInt(args[1]);
104
105        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
106        try (Connection connection = factory.getConnection()) {
107            checkSupportedControls(connection);
108
109            final String user = "cn=Directory Manager";
110            final char[] password = "password".toCharArray();
111            connection.bind(user, password);
112
113            // Uncomment a method to run one of the examples.
114
115            //useAffinityControl(connection);
116            //useADNotificationRequestControl(connection);
117            //useAssertionControl(connection);
118            useAuthorizationIdentityRequestControl(connection);
119            // For the EntryChangeNotificationResponseControl see
120            // usePersistentSearchRequestControl()
121            //useGetEffectiveRightsRequestControl(connection);
122            //useManageDsaITRequestControl(connection);
123            //useMatchedValuesRequestControl(connection);
124            //usePasswordExpiredResponseControl(connection);
125            //usePasswordExpiringResponseControl(connection);
126            //usePasswordPolicyRequestControl(connection);
127            //usePermissiveModifyRequestControl(connection);
128            //usePersistentSearchRequestControl(connection);
129            //usePostReadRequestControl(connection);
130            //usePreReadRequestControl(connection);
131            //useProxiedAuthV2RequestControl(connection);
132            //useServerSideSortRequestControl(connection);
133            //useSimplePagedResultsControl(connection);
134            //useSubentriesRequestControl(connection);
135            //useSubtreeDeleteRequestControl(connection);
136            //useVirtualListViewRequestControl(connection);
137
138        } catch (final LdapException e) {
139            System.err.println(e.getMessage());
140            System.exit(e.getResult().getResultCode().intValue());
141            return;
142        }
143    }
144
145    /**
146     * Use the OpenDJ affinity control to bypass load balancing.
147     * <br>
148     * In other words, each request with a control having the same value
149     * is sent to the same LDAP server.
150     *
151     * @param connection Active connection to the directory server.
152     * @throws LdapException Operation failed.
153     */
154    static void useAffinityControl(Connection connection) throws LdapException {
155        // --- JCite affinity ---
156        if (isSupported(AffinityControl.OID)) {
157            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
158
159            // Get an affinity control with a random value.
160            final AffinityControl control = AffinityControl.newControl(true);
161
162            final ModifyRequest modification =
163                    Requests.newModifyRequest(dn)
164                            .addControl(control)
165                            .addModification(ModificationType.ADD, "description",
166                                    "Added with an Affinity control");
167            connection.modify(modification);
168
169            final SearchRequest read =
170                    Requests.newSearchRequest(dn,
171                            SearchScope.BASE_OBJECT, "(&)", "description")
172                            .addControl(control);
173
174            try (final ConnectionEntryReader reader = connection.search(read);
175                 final LDIFEntryWriter writer = new LDIFEntryWriter(System.out)) {
176                while (reader.hasNext()) {
177                    writer.writeEntry(reader.readEntry());
178                }
179            } catch (final IOException e) {
180                System.err.println(e.getMessage());
181                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
182            }
183        } else {
184            System.err.println("AffinityControl not supported.");
185        }
186        // --- JCite affinity ---
187    }
188
189    /**
190     * Use the <a
191     * href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms676877(v=vs.85).aspx"
192     * >Microsoft LDAP Notification control</a>
193     * to register a change notification request for a search
194     * on Microsoft Active Directory.
195     * <p/>
196     * This client binds to Active Directory as
197     * {@code cn=Administrator,cn=users,dc=example,dc=com}
198     * with password {@code password},
199     * and expects entries under {@code dc=example,dc=com}.
200     *
201     * @param connection Active connection to Active Directory server.
202     * @throws LdapException Operation failed.
203     */
204    static void useADNotificationRequestControl(Connection connection) throws LdapException {
205
206        // --- JCite ADNotification ---
207        final String user = "cn=Administrator,cn=users,dc=example,dc=com";
208        final char[] password = "password".toCharArray();
209        connection.bind(user, password);
210
211        final String[] attributes = {"cn",
212            ADNotificationRequestControl.IS_DELETED_ATTR,
213            ADNotificationRequestControl.WHEN_CHANGED_ATTR,
214            ADNotificationRequestControl.WHEN_CREATED_ATTR};
215
216        SearchRequest request =
217                Requests.newSearchRequest("dc=example,dc=com",
218                        SearchScope.WHOLE_SUBTREE, "(objectclass=*)", attributes)
219                        .addControl(ADNotificationRequestControl.newControl(true));
220
221        ConnectionEntryReader reader = connection.search(request);
222
223        try {
224            while (reader.hasNext()) {
225                if (!reader.isReference()) {
226                    SearchResultEntry entry = reader.readEntry(); // Updated entry
227                    final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
228
229                    Boolean isDeleted = entry.parseAttribute(
230                            ADNotificationRequestControl.IS_DELETED_ATTR
231                    ).asBoolean();
232                    if (isDeleted != null && isDeleted) {
233                        // Handle entry deletion
234                        writer.writeComment("Deleted entry: " + entry.getName());
235                        writer.writeEntry(entry);
236                        writer.flush();
237                    }
238                    String whenCreated = entry.parseAttribute(
239                            ADNotificationRequestControl.WHEN_CREATED_ATTR)
240                            .asString();
241                    String whenChanged = entry.parseAttribute(
242                            ADNotificationRequestControl.WHEN_CHANGED_ATTR)
243                            .asString();
244                    if (whenCreated != null && whenChanged != null) {
245                        if (whenCreated.equals(whenChanged)) {
246                            // Handle entry addition
247                            writer.writeComment("Added entry: " + entry.getName());
248                            writer.writeEntry(entry);
249                            writer.flush();
250                        } else {
251                            // Handle entry modification
252                            writer.writeComment("Modified entry: " + entry.getName());
253                            writer.writeEntry(entry);
254                            writer.flush();
255                        }
256                    }
257                } else {
258                    reader.readReference(); // Read and ignore reference
259                }
260            }
261        } catch (final LdapException e) {
262            System.err.println(e.getMessage());
263            System.exit(e.getResult().getResultCode().intValue());
264        } catch (final SearchResultReferenceIOException e) {
265            System.err.println("Got search reference(s): " + e.getReference().getURIs());
266        } catch (final IOException e) {
267            System.err.println(e.getMessage());
268            System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
269        }
270        // --- JCite ADNotification ---
271    }
272
273    /**
274     * Use the LDAP assertion control to modify Babs Jensen's description if
275     * her entry does not have a description, yet.
276     *
277     * @param connection
278     *            Active connection to LDAP server containing <a
279     *            href="http://opendj.forgerock.org/Example.ldif"
280     *            >Example.ldif</a> content.
281     * @throws LdapException
282     *             Operation failed.
283     */
284    static void useAssertionControl(Connection connection) throws LdapException {
285        // --- JCite assertion ---
286        if (isSupported(AssertionRequestControl.OID)) {
287            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
288
289            final ModifyRequest request =
290                    Requests.newModifyRequest(dn)
291                        .addControl(AssertionRequestControl.newControl(
292                                true, Filter.valueOf("!(description=*)")))
293                        .addModification(ModificationType.ADD, "description",
294                                "Created using LDAP assertion control");
295
296            connection.modify(request);
297
298            try (final LDIFEntryWriter writer = new LDIFEntryWriter(System.out)) {
299                writer.writeEntry(connection.readEntry(dn, "description"));
300            } catch (final IOException e) {
301                System.err.println(e.getMessage());
302                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
303            }
304        } else {
305            System.err.println("AssertionRequestControl not supported.");
306        }
307        // --- JCite assertion ---
308    }
309
310    /**
311     * Use the LDAP Authorization Identity Controls to get the authorization ID.
312     *
313     * @param connection
314     *            Active connection to LDAP server containing <a
315     *            href="http://opendj.forgerock.org/Example.ldif"
316     *            >Example.ldif</a> content.
317     * @throws LdapException
318     *             Operation failed.
319     */
320    static void useAuthorizationIdentityRequestControl(Connection connection) throws LdapException {
321        // --- JCite authzid ---
322        if (isSupported(AuthorizationIdentityRequestControl.OID)) {
323            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
324            final char[] pwd = "hifalutin".toCharArray();
325
326            System.out.println("Binding as " + dn);
327            final BindRequest request =
328                    Requests.newSimpleBindRequest(dn, pwd)
329                        .addControl(AuthorizationIdentityRequestControl.newControl(true));
330
331            final BindResult result = connection.bind(request);
332            try {
333                final AuthorizationIdentityResponseControl control =
334                        result.getControl(AuthorizationIdentityResponseControl.DECODER,
335                                new DecodeOptions());
336                System.out.println("Authorization ID returned: "
337                                + control.getAuthorizationID());
338            } catch (final DecodeException e) {
339                System.err.println(e.getMessage());
340                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
341            }
342        } else {
343            System.err.println("AuthorizationIdentityRequestControl not supported.");
344        }
345        // --- JCite authzid ---
346    }
347
348    /**
349     * Use the GetEffectiveRights Request Control to determine what sort of
350     * access a user has to particular attributes on an entry.
351     *
352     * @param connection
353     *            Active connection to LDAP server containing <a
354     *            href="http://opendj.forgerock.org/Example.ldif"
355     *            >Example.ldif</a> content.
356     * @throws LdapException
357     *             Operation failed.
358     */
359    static void useGetEffectiveRightsRequestControl(Connection connection) throws LdapException {
360        // --- JCite effective rights ---
361        if (isSupported(GetEffectiveRightsRequestControl.OID)) {
362            final String authDN = "uid=kvaughan,ou=People,dc=example,dc=com";
363
364            final SearchRequest request =
365                    Requests.newSearchRequest(
366                            "dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
367                            "(uid=bjensen)", "cn", "aclRights", "aclRightsInfo")
368                            .addControl(GetEffectiveRightsRequestControl.newControl(
369                                    true, authDN, "cn"));
370
371            final ConnectionEntryReader reader = connection.search(request);
372            try (final LDIFEntryWriter writer = new LDIFEntryWriter(System.out)) {
373                while (reader.hasNext()) {
374                    if (!reader.isReference()) {
375                        final SearchResultEntry entry = reader.readEntry();
376                        writer.writeEntry(entry);
377                    }
378                }
379            } catch (final LdapException e) {
380                System.err.println(e.getMessage());
381                System.exit(e.getResult().getResultCode().intValue());
382            } catch (final SearchResultReferenceIOException e) {
383                System.err.println("Got search reference(s): " + e.getReference().getURIs());
384            } catch (final IOException e) {
385                System.err.println(e.getMessage());
386                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
387            }
388        } else {
389            System.err.println("GetEffectiveRightsRequestControl not supported.");
390        }
391        // --- JCite effective rights ---
392    }
393
394    /**
395     * Use the ManageDsaIT Request Control to show the difference between a
396     * referral accessed with and without use of the control.
397     *
398     * @param connection
399     *            Active connection to LDAP server containing <a
400     *            href="http://opendj.forgerock.org/Example.ldif"
401     *            >Example.ldif</a> content.
402     * @throws LdapException
403     *             Operation failed.
404     */
405    static void useManageDsaITRequestControl(Connection connection) throws LdapException {
406        // --- JCite manage DsaIT ---
407        if (isSupported(ManageDsaITRequestControl.OID)) {
408            final String dn = "dc=ref,dc=com";
409
410            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
411            try {
412                System.out.println("Referral without the ManageDsaIT control.");
413                SearchRequest request = Requests.newSearchRequest(dn,
414                        SearchScope.SUBORDINATES, "(objectclass=*)", "");
415                final ConnectionEntryReader reader = connection.search(request);
416                while (reader.hasNext()) {
417                    if (reader.isReference()) {
418                        final SearchResultReference ref = reader.readReference();
419                        System.out.println("Reference: " + ref.getURIs());
420                    }
421                }
422
423                System.out.println("Referral with the ManageDsaIT control.");
424                request.addControl(ManageDsaITRequestControl.newControl(true));
425                final SearchResultEntry entry = connection.searchSingleEntry(request);
426                writer.writeEntry(entry);
427                writer.close();
428            } catch (final LdapException e) {
429                System.err.println(e.getMessage());
430                System.exit(e.getResult().getResultCode().intValue());
431            } catch (final SearchResultReferenceIOException e) {
432                System.err.println("Got search reference(s): " + e.getReference().getURIs());
433            } catch (final IOException e) {
434                System.err.println(e.getMessage());
435                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
436            }
437        } else {
438            System.err.println("ManageDsaITRequestControl not supported.");
439        }
440        // --- JCite manage DsaIT ---
441    }
442
443    /**
444     * Use the Matched Values Request Control to show read only one attribute
445     * value.
446     *
447     * @param connection
448     *            Active connection to LDAP server containing <a
449     *            href="http://opendj.forgerock.org/Example.ldif"
450     *            >Example.ldif</a> content.
451     * @throws LdapException
452     *             Operation failed.
453     */
454    static void useMatchedValuesRequestControl(Connection connection) throws LdapException {
455        // --- JCite matched values ---
456        if (isSupported(MatchedValuesRequestControl.OID)) {
457            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
458            final SearchRequest request =
459                    Requests.newSearchRequest(dn, SearchScope.BASE_OBJECT,
460                            "(objectclass=*)", "cn")
461                            .addControl(MatchedValuesRequestControl.newControl(
462                                    true, "(cn=Babs Jensen)"));
463
464            final SearchResultEntry entry = connection.searchSingleEntry(request);
465            System.out.println("Reading entry with matched values request.");
466            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
467            try {
468                writer.writeEntry(entry);
469                writer.close();
470            } catch (final IOException e) {
471                System.err.println(e.getMessage());
472                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
473            }
474        } else {
475            System.err.println("MatchedValuesRequestControl not supported.");
476        }
477        // --- JCite matched values ---
478    }
479
480    /**
481     * Check the Password Expired Response Control. To get this code to output
482     * something, you must first set up an appropriate password policy and wait
483     * for Barbara Jensen's password to expire.
484     *
485     * @param connection
486     *            Active connection to LDAP server containing <a
487     *            href="http://opendj.forgerock.org/Example.ldif"
488     *            >Example.ldif</a> content.
489     */
490    static void usePasswordExpiredResponseControl(Connection connection) {
491        // --- JCite password expired ---
492        if (isSupported(PasswordExpiredResponseControl.OID)) {
493            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
494            final char[] pwd = "hifalutin".toCharArray();
495
496            try {
497                connection.bind(dn, pwd);
498            } catch (final LdapException e) {
499                final Result result = e.getResult();
500                try {
501                    final PasswordExpiredResponseControl control =
502                            result.getControl(PasswordExpiredResponseControl.DECODER,
503                                    new DecodeOptions());
504                    if (control != null && control.hasValue()) {
505                        System.out.println("Password expired for " + dn);
506                    }
507                } catch (final DecodeException de) {
508                    System.err.println(de.getMessage());
509                    System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
510                }
511            }
512        } else {
513            System.err.println("PasswordExpiredResponseControl not supported.");
514        }
515        // --- JCite password expired ---
516    }
517
518    /**
519     * Check the Password Expiring Response Control. To get this code to output
520     * something, you must first set up an appropriate password policy and wait
521     * for Barbara Jensen's password to get old enough that the server starts
522     * warning about expiration.
523     *
524     * @param connection
525     *            Active connection to LDAP server containing <a
526     *            href="http://opendj.forgerock.org/Example.ldif"
527     *            >Example.ldif</a> content.
528     * @throws LdapException
529     *             Operation failed.
530     */
531    static void usePasswordExpiringResponseControl(Connection connection) throws LdapException {
532        // --- JCite password expiring ---
533        if (isSupported(PasswordExpiringResponseControl.OID)) {
534            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
535            final char[] pwd = "hifalutin".toCharArray();
536
537            final BindResult result = connection.bind(dn, pwd);
538            try {
539                final PasswordExpiringResponseControl control =
540                        result.getControl(PasswordExpiringResponseControl.DECODER,
541                                new DecodeOptions());
542                if (control != null && control.hasValue()) {
543                    System.out.println("Password for " + dn + " expires in "
544                            + control.getSecondsUntilExpiration() + " seconds.");
545                }
546            } catch (final DecodeException de) {
547                System.err.println(de.getMessage());
548                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
549            }
550        } else {
551            System.err.println("PasswordExpiringResponseControl not supported");
552        }
553        // --- JCite password expiring ---
554    }
555
556    /**
557     * Use the Password Policy Request and Response Controls. To get this code
558     * to output something, you must first set up an appropriate password policy
559     * and wait for Barbara Jensen's password to get old enough that the server
560     * starts warning about expiration, or for the password to expire.
561     *
562     * @param connection
563     *            Active connection to LDAP server containing <a
564     *            href="http://opendj.forgerock.org/Example.ldif"
565     *            >Example.ldif</a> content.
566     */
567    static void usePasswordPolicyRequestControl(Connection connection) {
568        // --- JCite password policy ---
569        if (isSupported(PasswordPolicyRequestControl.OID)) {
570            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
571            final char[] pwd = "hifalutin".toCharArray();
572
573            try {
574                final BindRequest request = Requests.newSimpleBindRequest(dn, pwd)
575                        .addControl(PasswordPolicyRequestControl.newControl(true));
576
577                final BindResult result = connection.bind(request);
578
579                final PasswordPolicyResponseControl control =
580                        result.getControl(PasswordPolicyResponseControl.DECODER,
581                                new DecodeOptions());
582                if (control != null && control.getWarningType() != null) {
583                    System.out.println("Password policy warning "
584                            + control.getWarningType() + ", value "
585                            + control.getWarningValue() + " for " + dn);
586                }
587            } catch (final LdapException e) {
588                final Result result = e.getResult();
589                try {
590                    final PasswordPolicyResponseControl control =
591                            result.getControl(PasswordPolicyResponseControl.DECODER,
592                                    new DecodeOptions());
593                    if (control != null) {
594                        System.out.println("Password policy error "
595                                + control.getErrorType() + " for " + dn);
596                    }
597                } catch (final DecodeException de) {
598                    System.err.println(de.getMessage());
599                    System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
600                }
601            } catch (final DecodeException e) {
602                System.err.println(e.getMessage());
603                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
604            }
605        } else {
606            System.err.println("PasswordPolicyRequestControl not supported");
607        }
608        // --- JCite password policy ---
609    }
610
611    /**
612     * Use Permissive Modify Request Control to try to add an attribute that
613     * already exists.
614     *
615     * @param connection
616     *            Active connection to LDAP server containing <a
617     *            href="http://opendj.forgerock.org/Example.ldif"
618     *            >Example.ldif</a> content.
619     * @throws LdapException
620     *             Operation failed.
621     */
622    static void usePermissiveModifyRequestControl(Connection connection) throws LdapException {
623        // --- JCite permissive modify ---
624        if (isSupported(PermissiveModifyRequestControl.OID)) {
625            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
626
627            final ModifyRequest request =
628                    Requests.newModifyRequest(dn)
629                        .addControl(PermissiveModifyRequestControl.newControl(true))
630                        .addModification(ModificationType.ADD, "uid", "bjensen");
631
632            connection.modify(request);
633            System.out.println("Permissive modify did not complain about "
634                    + "attempt to add uid: bjensen to " + dn + ".");
635        } else {
636            System.err.println("PermissiveModifyRequestControl not supported");
637        }
638        // --- JCite permissive modify ---
639    }
640
641    /**
642     * Use the LDAP PersistentSearchRequestControl to set up a persistent
643     * search. Also use the Entry Change Notification Response Control to get
644     * details about why an entry was returned for a persistent search.
645     *
646     * After you set this up, use another application to make changes to user
647     * entries under dc=example,dc=com.
648     *
649     * @param connection
650     *            Active connection to LDAP server containing <a
651     *            href="http://opendj.forgerock.org/Example.ldif"
652     *            >Example.ldif</a> content.
653     * @throws LdapException
654     *             Operation failed.
655     */
656    static void usePersistentSearchRequestControl(Connection connection) throws LdapException {
657        // --- JCite psearch ---
658        if (isSupported(PersistentSearchRequestControl.OID)) {
659            final SearchRequest request =
660                    Requests.newSearchRequest(
661                            "dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
662                            "(objectclass=inetOrgPerson)", "cn")
663                            .addControl(PersistentSearchRequestControl.newControl(
664                                    true, true, true, // isCritical, changesOnly, returnECs
665                                    PersistentSearchChangeType.ADD,
666                                    PersistentSearchChangeType.DELETE,
667                                    PersistentSearchChangeType.MODIFY,
668                                    PersistentSearchChangeType.MODIFY_DN));
669
670            final ConnectionEntryReader reader = connection.search(request);
671
672            try {
673                while (reader.hasNext()) {
674                    if (!reader.isReference()) {
675                        final SearchResultEntry entry = reader.readEntry();
676                        System.out.println("Entry changed: " + entry.getName());
677
678                        final EntryChangeNotificationResponseControl control =
679                                entry.getControl(
680                                        EntryChangeNotificationResponseControl.DECODER,
681                                        new DecodeOptions());
682
683                        final PersistentSearchChangeType type = control.getChangeType();
684                        System.out.println("Change type: " + type);
685                        if (type.equals(PersistentSearchChangeType.MODIFY_DN)) {
686                            System.out.println("Previous DN: " + control.getPreviousName());
687                        }
688                        System.out.println("Change number: " + control.getChangeNumber());
689                        System.out.println(); // Add a blank line.
690                    }
691                }
692            } catch (final DecodeException e) {
693                System.err.println(e.getMessage());
694                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
695            } catch (final LdapException e) {
696                System.err.println(e.getMessage());
697                System.exit(e.getResult().getResultCode().intValue());
698            } catch (final SearchResultReferenceIOException e) {
699                System.err.println("Got search reference(s): " + e.getReference().getURIs());
700            }
701        } else {
702            System.err.println("PersistentSearchRequestControl not supported.");
703        }
704        // --- JCite psearch ---
705    }
706
707
708    /**
709     * Use Post Read Controls to get entry content after a modification.
710     *
711     * @param connection
712     *            Active connection to LDAP server containing <a
713     *            href="http://opendj.forgerock.org/Example.ldif"
714     *            >Example.ldif</a> content.
715     * @throws LdapException
716     *             Operation failed.
717     */
718    static void usePostReadRequestControl(Connection connection) throws LdapException {
719        // --- JCite post read ---
720        if (isSupported(PostReadRequestControl.OID)) {
721            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
722
723            final ModifyRequest request =
724                    Requests.newModifyRequest(dn)
725                    .addControl(PostReadRequestControl.newControl(true, "description"))
726                    .addModification(ModificationType.REPLACE,
727                            "description", "Using the PostReadRequestControl");
728
729            final Result result = connection.modify(request);
730            try {
731                final PostReadResponseControl control =
732                        result.getControl(PostReadResponseControl.DECODER,
733                                new DecodeOptions());
734                final Entry entry = control.getEntry();
735
736                final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
737                writer.writeEntry(entry);
738                writer.close();
739            } catch (final DecodeException e) {
740                System.err.println(e.getMessage());
741                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
742            } catch (final IOException e) {
743                System.err.println(e.getMessage());
744                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
745            }
746        } else {
747            System.err.println("PostReadRequestControl not supported");
748        }
749        // --- JCite post read ---
750    }
751
752    /**
753     * Use Pre Read Controls to get entry content before a modification.
754     *
755     * @param connection
756     *            Active connection to LDAP server containing <a
757     *            href="http://opendj.forgerock.org/Example.ldif"
758     *            >Example.ldif</a> content.
759     * @throws LdapException
760     *             Operation failed.
761     */
762    static void usePreReadRequestControl(Connection connection) throws LdapException {
763        // --- JCite pre read ---
764        if (isSupported(PreReadRequestControl.OID)) {
765            final String dn = "uid=bjensen,ou=People,dc=example,dc=com";
766
767            final ModifyRequest request =
768                    Requests.newModifyRequest(dn)
769                    .addControl(PreReadRequestControl.newControl(true, "mail"))
770                    .addModification(
771                            ModificationType.REPLACE, "mail", "modified@example.com");
772
773            final Result result = connection.modify(request);
774            try {
775                final PreReadResponseControl control =
776                        result.getControl(PreReadResponseControl.DECODER,
777                                new DecodeOptions());
778                final Entry entry = control.getEntry();
779
780                final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
781                writer.writeEntry(entry);
782                writer.close();
783            } catch (final DecodeException e) {
784                System.err.println(e.getMessage());
785                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
786            } catch (final IOException e) {
787                System.err.println(e.getMessage());
788                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
789            }
790        } else {
791            System.err.println("PreReadRequestControl not supported");
792        }
793        // --- JCite pre read ---
794    }
795
796    /**
797     * Use proxied authorization to modify an identity as another user.
798     *
799     * @param connection
800     *            Active connection to LDAP server containing <a
801     *            href="http://opendj.forgerock.org/Example.ldif"
802     *            >Example.ldif</a> content.
803     * @throws LdapException
804     *             Operation failed.
805     */
806    static void useProxiedAuthV2RequestControl(Connection connection) throws LdapException {
807        // --- JCite proxied authzv2 ---
808        if (isSupported(ProxiedAuthV2RequestControl.OID)) {
809            final String bindDN = "cn=My App,ou=Apps,dc=example,dc=com";
810            final String targetDn = "uid=bjensen,ou=People,dc=example,dc=com";
811            final String authzId = "dn:uid=kvaughan,ou=People,dc=example,dc=com";
812
813            final ModifyRequest request =
814                    Requests.newModifyRequest(targetDn)
815                    .addControl(ProxiedAuthV2RequestControl.newControl(authzId))
816                    .addModification(ModificationType.REPLACE, "description",
817                            "Done with proxied authz");
818
819            connection.bind(bindDN, "password".toCharArray());
820            connection.modify(request);
821            final Entry entry = connection.readEntry(targetDn, "description");
822
823            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
824            try {
825                writer.writeEntry(entry);
826                writer.close();
827            } catch (final IOException e) {
828                System.err.println(e.getMessage());
829                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
830            }
831        } else {
832            System.err.println("ProxiedAuthV2RequestControl not supported");
833        }
834        // --- JCite proxied authzv2 ---
835    }
836
837    /**
838     * Use the server-side sort controls.
839     *
840     * @param connection
841     *            Active connection to LDAP server containing <a
842     *            href="http://opendj.forgerock.org/Example.ldif"
843     *            >Example.ldif</a> content.
844     * @throws LdapException
845     *             Operation failed.
846     */
847    // --- JCite server-side sort ---
848    static void useServerSideSortRequestControl(Connection connection) throws LdapException {
849        if (isSupported(ServerSideSortRequestControl.OID)) {
850            final SearchRequest request =
851                    Requests.newSearchRequest("ou=People,dc=example,dc=com",
852                            SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn")
853                            .addControl(ServerSideSortRequestControl.newControl(
854                                            true, new SortKey("cn")));
855
856            final SearchResultHandler resultHandler = new MySearchResultHandler();
857            final Result result = connection.search(request, resultHandler);
858
859            try {
860                final ServerSideSortResponseControl control =
861                        result.getControl(ServerSideSortResponseControl.DECODER,
862                                new DecodeOptions());
863                if (control != null && control.getResult() == ResultCode.SUCCESS) {
864                    System.out.println("# Entries are sorted.");
865                } else {
866                    System.out.println("# Entries not necessarily sorted");
867                }
868            } catch (final DecodeException e) {
869                System.err.println(e.getMessage());
870                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
871            }
872        } else {
873            System.err.println("ServerSideSortRequestControl not supported");
874        }
875    }
876
877    private static class MySearchResultHandler implements SearchResultHandler {
878
879        @Override
880        public boolean handleEntry(SearchResultEntry entry) {
881            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
882            try {
883                writer.writeEntry(entry);
884                writer.flush();
885            } catch (final IOException e) {
886                System.err.println(e.getMessage());
887                return false;
888            }
889            return true;
890        }
891
892        @Override
893        public boolean handleReference(SearchResultReference reference) {
894            System.out.println("Got a reference: " + reference);
895            return false;
896        }
897    }
898    // --- JCite server-side sort ---
899
900    /**
901     * Use the simple paged results mechanism.
902     *
903     * @param connection
904     *            Active connection to LDAP server containing <a
905     *            href="http://opendj.forgerock.org/Example.ldif"
906     *            >Example.ldif</a> content.
907     * @throws LdapException
908     *             Operation failed.
909     */
910    static void useSimplePagedResultsControl(Connection connection) throws LdapException {
911        // --- JCite simple paged results ---
912        if (isSupported(SimplePagedResultsControl.OID)) {
913            ByteString cookie = ByteString.empty();
914            SearchRequest request;
915            final SearchResultHandler resultHandler = new MySearchResultHandler();
916            Result result;
917
918            int page = 1;
919            do {
920                System.out.println("# Simple paged results: Page " + page);
921
922                request =
923                        Requests.newSearchRequest("dc=example,dc=com",
924                                SearchScope.WHOLE_SUBTREE, "(sn=Jensen)", "cn")
925                                .addControl(SimplePagedResultsControl.newControl(
926                                        true, 3, cookie));
927
928                result = connection.search(request, resultHandler);
929                try {
930                    SimplePagedResultsControl control =
931                            result.getControl(SimplePagedResultsControl.DECODER,
932                                    new DecodeOptions());
933                    cookie = control.getCookie();
934                } catch (final DecodeException e) {
935                    System.err.println(e.getMessage());
936                    System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
937                }
938
939                ++page;
940            } while (cookie.length() != 0);
941        } else {
942            System.err.println("SimplePagedResultsControl not supported");
943        }
944        // --- JCite simple paged results ---
945    }
946
947    /**
948     * Use the subentries request control.
949     *
950     * @param connection
951     *            Active connection to LDAP server containing <a
952     *            href="http://opendj.forgerock.org/Example.ldif"
953     *            >Example.ldif</a> content.
954     * @throws LdapException
955     *             Operation failed.
956     */
957    static void useSubentriesRequestControl(Connection connection) throws LdapException {
958        // --- JCite subentries ---
959        if (isSupported(SubentriesRequestControl.OID)) {
960            final SearchRequest request =
961                    Requests.newSearchRequest("dc=example,dc=com",
962                                SearchScope.WHOLE_SUBTREE,
963                                "cn=*Class of Service", "cn", "subtreeSpecification")
964                            .addControl(SubentriesRequestControl.newControl(
965                                true, true));
966
967            final ConnectionEntryReader reader = connection.search(request);
968            final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
969            try {
970                while (reader.hasNext()) {
971                    if (reader.isEntry()) {
972                        final SearchResultEntry entry = reader.readEntry();
973                        writer.writeEntry(entry);
974                    }
975                }
976                writer.close();
977            } catch (final LdapException e) {
978                System.err.println(e.getMessage());
979                System.exit(e.getResult().getResultCode().intValue());
980            } catch (final SearchResultReferenceIOException e) {
981                System.err.println("Got search reference(s): " + e.getReference().getURIs());
982            } catch (final IOException e) {
983                System.err.println(e.getMessage());
984                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
985            }
986        } else {
987            System.err.println("SubentriesRequestControl not supported");
988        }
989        // --- JCite subentries ---
990    }
991
992    /**
993     * Use the subtree delete control.
994     *
995     * @param connection
996     *            Active connection to LDAP server containing <a
997     *            href="http://opendj.forgerock.org/Example.ldif"
998     *            >Example.ldif</a> content.
999     * @throws LdapException
1000     *             Operation failed.
1001     */
1002    static void useSubtreeDeleteRequestControl(Connection connection) throws LdapException {
1003        // --- JCite tree delete ---
1004        if (isSupported(SubtreeDeleteRequestControl.OID)) {
1005
1006            final String dn = "ou=Apps,dc=example,dc=com";
1007            final DeleteRequest request =
1008                    Requests.newDeleteRequest(dn)
1009                            .addControl(SubtreeDeleteRequestControl.newControl(true));
1010
1011            final Result result = connection.delete(request);
1012            if (result.isSuccess()) {
1013                System.out.println("Successfully deleted " + dn
1014                        + " and all entries below.");
1015            } else {
1016                System.err.println("Result: " + result.getDiagnosticMessage());
1017            }
1018        } else {
1019            System.err.println("SubtreeDeleteRequestControl not supported");
1020        }
1021        // --- JCite tree delete ---
1022    }
1023
1024    /**
1025     * Use the virtual list view controls. In order to set up OpenDJ directory
1026     * server to produce the following output with the example code, use OpenDJ
1027     * Control Panel &gt; Manage Indexes &gt; New VLV Index... to set up a
1028     * virtual list view index for people by last name, using the filter
1029     * {@code (|(givenName=*)(sn=*))}, and sorting first by surname, {@code sn},
1030     * in ascending order, then by given name also in ascending order
1031     *
1032     * @param connection
1033     *            Active connection to LDAP server containing <a
1034     *            href="http://opendj.forgerock.org/Example.ldif"
1035     *            >Example.ldif</a> content.
1036     * @throws LdapException
1037     *             Operation failed.
1038     */
1039    static void useVirtualListViewRequestControl(Connection connection) throws LdapException {
1040        // --- JCite vlv ---
1041        if (isSupported(VirtualListViewRequestControl.OID)) {
1042            ByteString contextID = ByteString.empty();
1043
1044            // Add a window of 2 entries on either side of the first sn=Jensen entry.
1045            final SearchRequest request =
1046                    Requests.newSearchRequest("ou=People,dc=example,dc=com",
1047                            SearchScope.WHOLE_SUBTREE, "(sn=*)", "sn", "givenName")
1048                            .addControl(ServerSideSortRequestControl.newControl(
1049                                    true, new SortKey("sn")))
1050                            .addControl(
1051                                    VirtualListViewRequestControl.newAssertionControl(
1052                                            true,
1053                                            ByteString.valueOfUtf8("Jensen"),
1054                                            2, 2, contextID));
1055
1056            final SearchResultHandler resultHandler = new MySearchResultHandler();
1057            final Result result = connection.search(request, resultHandler);
1058
1059            try {
1060                final ServerSideSortResponseControl sssControl =
1061                        result.getControl(ServerSideSortResponseControl.DECODER,
1062                                new DecodeOptions());
1063                if (sssControl != null && sssControl.getResult() == ResultCode.SUCCESS) {
1064                    System.out.println("# Entries are sorted.");
1065                } else {
1066                    System.out.println("# Entries not necessarily sorted");
1067                }
1068
1069                final VirtualListViewResponseControl vlvControl =
1070                        result.getControl(VirtualListViewResponseControl.DECODER,
1071                                new DecodeOptions());
1072                System.out.println("# Position in list: "
1073                        + vlvControl.getTargetPosition() + "/"
1074                        + vlvControl.getContentCount());
1075            } catch (final DecodeException e) {
1076                System.err.println(e.getMessage());
1077                System.exit(ResultCode.CLIENT_SIDE_DECODING_ERROR.intValue());
1078            }
1079        } else {
1080            System.err.println("VirtualListViewRequestControl not supported");
1081        }
1082        // --- JCite vlv ---
1083    }
1084
1085    // --- JCite check support ---
1086    /**
1087     * Controls supported by the LDAP server.
1088     */
1089    private static Collection<String> controls;
1090
1091    /**
1092     * Populate the list of supported LDAP control OIDs.
1093     *
1094     * @param connection
1095     *            Active connection to the LDAP server.
1096     * @throws LdapException
1097     *             Failed to get list of controls.
1098     */
1099    static void checkSupportedControls(Connection connection) throws LdapException {
1100        controls = RootDSE.readRootDSE(connection).getSupportedControls();
1101    }
1102
1103    /**
1104     * Check whether a control is supported. Call {@code checkSupportedControls}
1105     * first.
1106     *
1107     * @param control
1108     *            Check support for this control, provided by OID.
1109     * @return True if the control is supported.
1110     */
1111    static boolean isSupported(final String control) {
1112        return controls != null && controls.contains(control);
1113    }
1114    // --- JCite check support ---
1115
1116    /**
1117     * Constructor not used.
1118     */
1119    private Controls() {
1120        // Not used.
1121    }
1122}