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