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 2015 ForgeRock AS.
024 *
025 */
026
027package org.forgerock.opendj.examples;
028
029import static org.forgerock.util.Utils.closeSilently;
030import org.forgerock.opendj.ldap.Connection;
031import org.forgerock.opendj.ldap.LDAPConnectionFactory;
032import org.forgerock.opendj.ldap.LdapException;
033import org.forgerock.opendj.ldap.ModificationType;
034import org.forgerock.opendj.ldap.ResultCode;
035import org.forgerock.opendj.ldap.RootDSE;
036import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
037import org.forgerock.opendj.ldap.requests.Requests;
038import org.forgerock.opendj.ldap.responses.BindResult;
039import org.forgerock.opendj.ldap.responses.CompareResult;
040import org.forgerock.opendj.ldap.responses.Responses;
041import org.forgerock.opendj.ldap.responses.Result;
042import org.forgerock.util.AsyncFunction;
043import org.forgerock.util.promise.ExceptionHandler;
044import org.forgerock.util.promise.Promise;
045import org.forgerock.util.promise.Promises;
046import org.forgerock.util.promise.ResultHandler;
047
048import java.util.concurrent.CountDownLatch;
049
050/**
051 * This command-line client demonstrates adding and removing a member from a
052 * (potentially large) static group using the asynchronous APIs.
053 *
054 * The client takes as arguments the host and port of the directory server, the
055 * group DN, the member DN, and whether to "add" or "del" the specified member
056 * from the group. The client uses the Permissive Modify control if it is
057 * available to avoid having to check whether the member belongs to the group or
058 * not.
059 *
060 * This client expects a group that is a <code>groupOfNames</code> such as:
061 *
062 * <pre>
063 * dn: cn=My Static Group,ou=Groups,dc=example,dc=com
064 * cn: My Static Group
065 * objectClass: groupOfNames
066 * objectClass: top
067 * ou: Groups
068 * member: uid=ahunter,ou=People,dc=example,dc=com
069 * member: uid=bjensen,ou=People,dc=example,dc=com
070 * member: uid=tmorris,ou=People,dc=example,dc=com
071 * </pre>
072 *
073 * This client connects as <code>cn=Directory Manager</code> with password
074 * <code>password</code>. Not a best practice; in real code use application
075 * specific credentials to connect, and ensure that your application has access
076 * to use the Permissive Modify control if your directory server supports it.
077 */
078public final class UpdateGroupAsync {
079    /** Connection to the LDAP server. */
080    private static Connection connection;
081    /** Result for the operation. */
082    private static int resultCode;
083    /** Count down latch to wait for modify operation to complete. */
084    private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
085
086    /**
087     * Updates the group as necessary.
088     *
089     * @param args
090     *            The command line arguments: host, port, group-dn, member-dn, (add|del)
091     */
092    public static void main(String[] args) {
093        if (args.length != 5) {
094            printUsage();
095        }
096        final String host              = args[0];
097        final int port                 = Integer.parseInt(args[1]);
098        final String groupDn           = args[2];
099        final String memberDn          = args[3];
100        final ModificationType modType = getModificationType(args[4]);
101
102        // Connect, bind, update group.
103        new LDAPConnectionFactory(host, port)
104                .getConnectionAsync()
105                .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
106                    @Override
107                    public Promise<BindResult, LdapException> apply(Connection connection)
108                            throws LdapException {
109                        UpdateGroupAsync.connection = connection;
110                        return connection.bindAsync(
111                                Requests.newSimpleBindRequest("cn=Directory Manager", "password".toCharArray()));
112                    }
113                })
114                .thenAsync(new AsyncFunction<BindResult, RootDSE, LdapException>() {
115                    @Override
116                    public Promise<RootDSE, LdapException> apply(BindResult bindResult)
117                            throws LdapException {
118                        return RootDSE.readRootDSEAsync(connection, null);
119                    }
120                })
121                .thenAsync(new AsyncFunction<RootDSE, Result, LdapException>() {
122                    @Override
123                    public Promise<Result, LdapException> apply(RootDSE rootDSE) throws LdapException {
124                        // If the directory supports the Permissive Modify request control,
125                        // then the modification type does not matter.
126                        if (rootDSE.getSupportedControls().contains(PermissiveModifyRequestControl.OID)) {
127                            log("Updating group membership.");
128                            return connection.modifyAsync(
129                                    Requests.newModifyRequest(groupDn)
130                                            .addControl(PermissiveModifyRequestControl.newControl(true))
131                                            .addModification(modType, "member", memberDn));
132                        } else {
133                            return connection
134                                    // Check whether the member is present.
135                                    .compareAsync(Requests.newCompareRequest(groupDn, "member", memberDn))
136                                    .thenAsync(new AsyncFunction<CompareResult, Result, LdapException>() {
137                                        @Override
138                                        public Promise<Result, LdapException> apply(CompareResult compareResult)
139                                                throws LdapException {
140                                            ResultCode rc = compareResult.getResultCode();
141                                            // Only add the member if missing from the group.
142                                            if (modType.equals(ModificationType.ADD)
143                                                    && rc.equals(ResultCode.COMPARE_FALSE)) {
144                                                log("Adding " + memberDn + " to " + groupDn + ".");
145                                                return connection.modifyAsync(
146                                                        Requests.newModifyRequest(groupDn)
147                                                                .addModification(modType, "member", memberDn));
148                                            // Only delete if present in the group.
149                                            } else if (modType.equals(ModificationType.DELETE)
150                                                    && rc.equals(ResultCode.COMPARE_TRUE)) {
151                                                log("Deleting " + memberDn + " from " + groupDn + ".");
152                                                return connection.modifyAsync(
153                                                        Requests.newModifyRequest(groupDn)
154                                                                .addModification(modType, "member", memberDn));
155                                            } else {
156                                                return Promises.newResultPromise(
157                                                        Responses.newResult(ResultCode.SUCCESS));
158                                            }
159                                        }
160                                    });
161                        }
162                    }
163                })
164                .thenOnResult(new ResultHandler<Result>() {
165                    @Override
166                    public void handleResult(Result result) {
167                        final String op = (modType == ModificationType.ADD) ? "added to" : "deleted from";
168                        log(memberDn + " has been " + op + " the group " + groupDn + ".");
169                        resultCode = result.getResultCode().intValue();
170                        COMPLETION_LATCH.countDown();
171                    }
172                })
173                .thenOnException(new ExceptionHandler<LdapException>() {
174                    @Override
175                    public void handleException(LdapException e) {
176                        System.err.println(e.getMessage());
177                        resultCode = e.getResult().getResultCode().intValue();
178                        COMPLETION_LATCH.countDown();
179                    }
180                });
181
182        try {
183            COMPLETION_LATCH.await();
184        } catch (InterruptedException e) {
185            System.err.println(e.getMessage());
186            System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
187            return;
188        }
189
190        closeSilently(connection);
191        System.exit(resultCode);
192    }
193
194    /**
195     * Print usage then exit.
196     */
197    private static void printUsage() {
198        System.err.println("Usage: host port group-dn member-dn {add|del}");
199        System.err.println("For example: localhost 1389 "
200                + "cn=Static,ou=Groups,dc=example,dc=com "
201                + "uid=user.5150,ou=People,dc=example,dc=com "
202                + "del");
203        System.exit(1);
204    }
205
206    /**
207     * Return the modification type for the update operation.
208     * @param operation Operation specified as an argument (add or del).
209     */
210    private static ModificationType getModificationType(String operation) {
211        final boolean isAdd = "add".equalsIgnoreCase(operation);
212        if (!isAdd && !"del".equalsIgnoreCase(operation)) {
213            printUsage();
214        }
215        return isAdd ? ModificationType.ADD : ModificationType.DELETE;
216    }
217
218    /**
219     * Log a message to System.out.
220     *
221     * @param message   The message to write to the console.
222     */
223    private static void log(final String message) {
224        System.out.println(message);
225    }
226
227    /**
228     * Constructor not used.
229     */
230    private UpdateGroupAsync() {
231        // Not used.
232    }
233}