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 2016 ForgeRock AS.
15   */
16  package org.forgerock.opendj.examples;
17  
18  import static org.forgerock.util.Utils.closeSilently;
19  import org.forgerock.opendj.ldap.Connection;
20  import org.forgerock.opendj.ldap.LDAPConnectionFactory;
21  import org.forgerock.opendj.ldap.LdapException;
22  import org.forgerock.opendj.ldap.ModificationType;
23  import org.forgerock.opendj.ldap.ResultCode;
24  import org.forgerock.opendj.ldap.RootDSE;
25  import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
26  import org.forgerock.opendj.ldap.requests.Requests;
27  import org.forgerock.opendj.ldap.responses.BindResult;
28  import org.forgerock.opendj.ldap.responses.CompareResult;
29  import org.forgerock.opendj.ldap.responses.Responses;
30  import org.forgerock.opendj.ldap.responses.Result;
31  import org.forgerock.util.AsyncFunction;
32  import org.forgerock.util.promise.ExceptionHandler;
33  import org.forgerock.util.promise.Promise;
34  import org.forgerock.util.promise.Promises;
35  import org.forgerock.util.promise.ResultHandler;
36  
37  import java.util.concurrent.CountDownLatch;
38  
39  /**
40   * This command-line client demonstrates adding and removing a member from a
41   * (potentially large) static group using the asynchronous APIs.
42   *
43   * The client takes as arguments the host and port of the directory server, the
44   * group DN, the member DN, and whether to "add" or "del" the specified member
45   * from the group. The client uses the Permissive Modify control if it is
46   * available to avoid having to check whether the member belongs to the group or
47   * not.
48   *
49   * This client expects a group that is a <code>groupOfNames</code> such as:
50   *
51   * <pre>
52   * dn: cn=My Static Group,ou=Groups,dc=example,dc=com
53   * cn: My Static Group
54   * objectClass: groupOfNames
55   * objectClass: top
56   * ou: Groups
57   * member: uid=ahunter,ou=People,dc=example,dc=com
58   * member: uid=bjensen,ou=People,dc=example,dc=com
59   * member: uid=tmorris,ou=People,dc=example,dc=com
60   * </pre>
61   *
62   * This client connects as <code>cn=Directory Manager</code> with password
63   * <code>password</code>. Not a best practice; in real code use application
64   * specific credentials to connect, and ensure that your application has access
65   * to use the Permissive Modify control if your directory server supports it.
66   */
67  public final class UpdateGroupAsync {
68      /** Connection to the LDAP server. */
69      private static Connection connection;
70      /** Result for the operation. */
71      private static int resultCode;
72      /** Count down latch to wait for modify operation to complete. */
73      private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
74  
75      /**
76       * Updates the group as necessary.
77       *
78       * @param args
79       *            The command line arguments: host, port, group-dn, member-dn, (add|del)
80       */
81      public static void main(String[] args) {
82          if (args.length != 5) {
83              printUsage();
84          }
85          final String host              = args[0];
86          final int port                 = Integer.parseInt(args[1]);
87          final String groupDn           = args[2];
88          final String memberDn          = args[3];
89          final ModificationType modType = getModificationType(args[4]);
90  
91          // Connect, bind, update group.
92          new LDAPConnectionFactory(host, port)
93                  .getConnectionAsync()
94                  .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
95                      @Override
96                      public Promise<BindResult, LdapException> apply(Connection connection)
97                              throws LdapException {
98                          UpdateGroupAsync.connection = connection;
99                          return connection.bindAsync(
100                                 Requests.newSimpleBindRequest("cn=Directory Manager", "password".toCharArray()));
101                     }
102                 })
103                 .thenAsync(new AsyncFunction<BindResult, RootDSE, LdapException>() {
104                     @Override
105                     public Promise<RootDSE, LdapException> apply(BindResult bindResult)
106                             throws LdapException {
107                         return RootDSE.readRootDSEAsync(connection);
108                     }
109                 })
110                 .thenAsync(new AsyncFunction<RootDSE, Result, LdapException>() {
111                     @Override
112                     public Promise<Result, LdapException> apply(RootDSE rootDSE) throws LdapException {
113                         // If the directory supports the Permissive Modify request control,
114                         // then the modification type does not matter.
115                         if (rootDSE.getSupportedControls().contains(PermissiveModifyRequestControl.OID)) {
116                             log("Updating group membership.");
117                             return connection.modifyAsync(
118                                     Requests.newModifyRequest(groupDn)
119                                             .addControl(PermissiveModifyRequestControl.newControl(true))
120                                             .addModification(modType, "member", memberDn));
121                         } else {
122                             return connection
123                                     // Check whether the member is present.
124                                     .compareAsync(Requests.newCompareRequest(groupDn, "member", memberDn))
125                                     .thenAsync(new AsyncFunction<CompareResult, Result, LdapException>() {
126                                         @Override
127                                         public Promise<Result, LdapException> apply(CompareResult compareResult)
128                                                 throws LdapException {
129                                             ResultCode rc = compareResult.getResultCode();
130                                             // Only add the member if missing from the group.
131                                             if (modType.equals(ModificationType.ADD)
132                                                     && rc.equals(ResultCode.COMPARE_FALSE)) {
133                                                 log("Adding " + memberDn + " to " + groupDn + ".");
134                                                 return connection.modifyAsync(
135                                                         Requests.newModifyRequest(groupDn)
136                                                                 .addModification(modType, "member", memberDn));
137                                             // Only delete if present in the group.
138                                             } else if (modType.equals(ModificationType.DELETE)
139                                                     && rc.equals(ResultCode.COMPARE_TRUE)) {
140                                                 log("Deleting " + memberDn + " from " + groupDn + ".");
141                                                 return connection.modifyAsync(
142                                                         Requests.newModifyRequest(groupDn)
143                                                                 .addModification(modType, "member", memberDn));
144                                             } else {
145                                                 return Promises.newResultPromise(
146                                                         Responses.newResult(ResultCode.SUCCESS));
147                                             }
148                                         }
149                                     });
150                         }
151                     }
152                 })
153                 .thenOnResult(new ResultHandler<Result>() {
154                     @Override
155                     public void handleResult(Result result) {
156                         final String op = (modType == ModificationType.ADD) ? "added to" : "deleted from";
157                         log(memberDn + " has been " + op + " the group " + groupDn + ".");
158                         resultCode = result.getResultCode().intValue();
159                         COMPLETION_LATCH.countDown();
160                     }
161                 })
162                 .thenOnException(new ExceptionHandler<LdapException>() {
163                     @Override
164                     public void handleException(LdapException e) {
165                         System.err.println(e.getMessage());
166                         resultCode = e.getResult().getResultCode().intValue();
167                         COMPLETION_LATCH.countDown();
168                     }
169                 });
170 
171         try {
172             COMPLETION_LATCH.await();
173         } catch (InterruptedException e) {
174             System.err.println(e.getMessage());
175             System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
176             return;
177         }
178 
179         closeSilently(connection);
180         System.exit(resultCode);
181     }
182 
183     /** Print usage then exit. */
184     private static void printUsage() {
185         System.err.println("Usage: host port group-dn member-dn {add|del}");
186         System.err.println("For example: localhost 1389 "
187                 + "cn=Static,ou=Groups,dc=example,dc=com "
188                 + "uid=user.5150,ou=People,dc=example,dc=com "
189                 + "del");
190         System.exit(1);
191     }
192 
193     /**
194      * Return the modification type for the update operation.
195      * @param operation Operation specified as an argument (add or del).
196      */
197     private static ModificationType getModificationType(String operation) {
198         final boolean isAdd = "add".equalsIgnoreCase(operation);
199         if (!isAdd && !"del".equalsIgnoreCase(operation)) {
200             printUsage();
201         }
202         return isAdd ? ModificationType.ADD : ModificationType.DELETE;
203     }
204 
205     /**
206      * Log a message to System.out.
207      *
208      * @param message   The message to write to the console.
209      */
210     private static void log(final String message) {
211         System.out.println(message);
212     }
213 
214     /** Constructor not used. */
215     private UpdateGroupAsync() {
216         // Not used.
217     }
218 }