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 }