View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2012-2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.opendj.examples;
18  
19  import java.io.IOException;
20  import java.util.Collection;
21  
22  import com.forgerock.opendj.ldap.controls.AffinityControl;
23  import org.forgerock.opendj.ldap.ByteString;
24  import org.forgerock.opendj.ldap.Connection;
25  import org.forgerock.opendj.ldap.DecodeException;
26  import org.forgerock.opendj.ldap.DecodeOptions;
27  import org.forgerock.opendj.ldap.Entry;
28  import org.forgerock.opendj.ldap.LdapException;
29  import org.forgerock.opendj.ldap.Filter;
30  import org.forgerock.opendj.ldap.LDAPConnectionFactory;
31  import org.forgerock.opendj.ldap.ModificationType;
32  import org.forgerock.opendj.ldap.ResultCode;
33  import org.forgerock.opendj.ldap.RootDSE;
34  import org.forgerock.opendj.ldap.SearchResultHandler;
35  import org.forgerock.opendj.ldap.SearchResultReferenceIOException;
36  import org.forgerock.opendj.ldap.SearchScope;
37  import org.forgerock.opendj.ldap.SortKey;
38  import org.forgerock.opendj.ldap.controls.ADNotificationRequestControl;
39  import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
40  import org.forgerock.opendj.ldap.controls.AuthorizationIdentityRequestControl;
41  import org.forgerock.opendj.ldap.controls.AuthorizationIdentityResponseControl;
42  import org.forgerock.opendj.ldap.controls.EntryChangeNotificationResponseControl;
43  import org.forgerock.opendj.ldap.controls.GetEffectiveRightsRequestControl;
44  import org.forgerock.opendj.ldap.controls.ManageDsaITRequestControl;
45  import org.forgerock.opendj.ldap.controls.MatchedValuesRequestControl;
46  import org.forgerock.opendj.ldap.controls.PasswordExpiredResponseControl;
47  import org.forgerock.opendj.ldap.controls.PasswordExpiringResponseControl;
48  import org.forgerock.opendj.ldap.controls.PasswordPolicyRequestControl;
49  import org.forgerock.opendj.ldap.controls.PasswordPolicyResponseControl;
50  import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
51  import org.forgerock.opendj.ldap.controls.PersistentSearchChangeType;
52  import org.forgerock.opendj.ldap.controls.PersistentSearchRequestControl;
53  import org.forgerock.opendj.ldap.controls.PostReadRequestControl;
54  import org.forgerock.opendj.ldap.controls.PostReadResponseControl;
55  import org.forgerock.opendj.ldap.controls.PreReadRequestControl;
56  import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
57  import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
58  import org.forgerock.opendj.ldap.controls.ServerSideSortRequestControl;
59  import org.forgerock.opendj.ldap.controls.ServerSideSortResponseControl;
60  import org.forgerock.opendj.ldap.controls.SimplePagedResultsControl;
61  import org.forgerock.opendj.ldap.controls.SubentriesRequestControl;
62  import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
63  import org.forgerock.opendj.ldap.controls.VirtualListViewRequestControl;
64  import org.forgerock.opendj.ldap.controls.VirtualListViewResponseControl;
65  import org.forgerock.opendj.ldap.requests.BindRequest;
66  import org.forgerock.opendj.ldap.requests.DeleteRequest;
67  import org.forgerock.opendj.ldap.requests.ModifyRequest;
68  import org.forgerock.opendj.ldap.requests.Requests;
69  import org.forgerock.opendj.ldap.requests.SearchRequest;
70  import org.forgerock.opendj.ldap.responses.BindResult;
71  import org.forgerock.opendj.ldap.responses.Result;
72  import org.forgerock.opendj.ldap.responses.SearchResultEntry;
73  import org.forgerock.opendj.ldap.responses.SearchResultReference;
74  import org.forgerock.opendj.ldif.ConnectionEntryReader;
75  import org.forgerock.opendj.ldif.LDIFEntryWriter;
76  
77  /**
78   * This command-line client demonstrates use of LDAP controls. The client takes
79   * as arguments the host and port for the directory server, and expects to find
80   * the entries and access control instructions as defined in <a
81   * href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>.
82   *
83   * This client connects as <code>cn=Directory Manager</code> with password
84   * <code>password</code>. Not a best practice; in real code use application
85   * specific credentials to connect, and ensure that your application has access
86   * to use the LDAP controls needed.
87   */
88  public final class Controls {
89  
90      /**
91       * Connect to the server, and then try to use some LDAP controls.
92       *
93       * @param args
94       *            The command line arguments: host, port
95       */
96      public static void main(final String[] args) {
97          if (args.length != 2) {
98              System.err.println("Usage: host port");
99              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 }