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.Entries;
032import org.forgerock.opendj.ldap.Entry;
033import org.forgerock.opendj.ldap.LDAPConnectionFactory;
034import org.forgerock.opendj.ldap.LdapException;
035import org.forgerock.opendj.ldap.LinkedHashMapEntry;
036import org.forgerock.opendj.ldap.ResultCode;
037import org.forgerock.opendj.ldap.TreeMapEntry;
038import org.forgerock.opendj.ldap.requests.ModifyRequest;
039import org.forgerock.opendj.ldap.requests.Requests;
040import org.forgerock.opendj.ldap.responses.BindResult;
041import org.forgerock.opendj.ldap.responses.Result;
042import org.forgerock.opendj.ldif.LDIFEntryWriter;
043import org.forgerock.util.AsyncFunction;
044import org.forgerock.util.promise.ExceptionHandler;
045import org.forgerock.util.promise.Promise;
046import org.forgerock.util.promise.ResultHandler;
047
048import java.io.IOException;
049import java.util.concurrent.CountDownLatch;
050
051/**
052 * A command-line client that creates, updates, renames, and deletes a
053 * short-lived entry in order to demonstrate LDAP write operations
054 * using the asynchronous APIs.
055 * <br>
056 * The client takes as arguments the host and port for the directory server,
057 * and expects to find the entries and access control instructions as defined in
058 * <a href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>.
059 *
060 * <ul>
061 * <li>host - host name of the directory server</li>
062 * <li>port - port number of the directory server</li>
063 * </ul>
064 *
065 * All arguments are required.
066 */
067public final class ShortLifeAsync {
068    /** The short-lived entry. */
069    private static Entry entry;
070    /** Writer for displaying LDIF to System.out. */
071    private static LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
072    /** Connection to the LDAP server. */
073    private static Connection connection;
074    /** Result for the operation. */
075    private static int resultCode;
076    /** Count down latch to wait for modify operation to complete. */
077    private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
078
079    /**
080     * Adds, modifies, renames, and deletes an entry.
081     *
082     * @param args
083     *            The command line arguments: host, port
084     */
085    public static void main(final String[] args) {
086        if (args.length != 2) {
087            System.err.println("Usage: host port");
088            System.err.println("For example: localhost 1389");
089            System.exit(1);
090        }
091        final String host = args[0];
092        final int    port = Integer.parseInt(args[1]);
093
094        // User credentials of a "Directory Administrators" group member.
095        // Kirsten Vaughan is authorized to create, update, and delete entries.
096        //
097        // Alternatively, prompt an administrator user for credentials,
098        // or get the application its own account with access to update data.
099        final String adminDn  = "uid=kvaughan,ou=people,dc=example,dc=com";
100        final char[] adminPwd = "bribery".toCharArray();
101
102        // Prepare an entry to add to the directory.
103        final String entryDn = "cn=Bob,ou=People,dc=example,dc=com";
104        entry = new LinkedHashMapEntry(entryDn)
105            .addAttribute("cn", "Bob")
106            .addAttribute("objectclass", "top")
107            .addAttribute("objectclass", "person")
108            .addAttribute("objectclass", "organizationalPerson")
109            .addAttribute("objectclass", "inetOrgPerson")
110            .addAttribute("mail", "subgenius@example.com")
111            .addAttribute("sn", "Dobbs");
112
113        new LDAPConnectionFactory(host, port)
114                .getConnectionAsync()
115                .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
116                    @Override
117                    public Promise<BindResult, LdapException> apply(Connection connection)
118                            throws LdapException {
119                        ShortLifeAsync.connection = connection;
120                        return connection.bindAsync(
121                                Requests.newSimpleBindRequest(adminDn, adminPwd));
122                    }
123                })
124                .thenAsync(new AsyncFunction<BindResult, Result, LdapException>() {
125                    @Override
126                    public Promise<Result, LdapException> apply(BindResult bindResult)
127                            throws LdapException {
128                        log("Adding the entry...");
129                        log(entry);
130                        return connection.addAsync(Requests.newAddRequest(entry));
131                    }
132                })
133                .thenAsync(new AsyncFunction<Result, Result, LdapException>() {
134                    @Override
135                    public Promise<Result, LdapException> apply(Result result)
136                            throws LdapException {
137                        Entry old = TreeMapEntry.deepCopyOfEntry(entry);
138                        entry = entry
139                                .replaceAttribute("mail", "spammer@example.com")
140                                .addAttribute("description", "Good user gone bad");
141                        log("Updating mail address, adding description...");
142                        log(entry);
143                        ModifyRequest request = Entries.diffEntries(old, entry);
144                        return connection.modifyAsync(request);
145                    }
146                })
147                .thenAsync(new AsyncFunction<Result, Result, LdapException>() {
148                    @Override
149                    public Promise<Result, LdapException> apply(Result result)
150                            throws LdapException {
151                        entry = entry.setName("cn=Renamed,ou=People,dc=example,dc=com");
152                        log("Renaming the entry...");
153                        log(entry);
154                        return connection.modifyDNAsync(
155                                Requests.newModifyDNRequest(entryDn, "cn=Renamed"));
156                    }
157                })
158                .thenAsync(new AsyncFunction<Result, Result, LdapException>() {
159                    @Override
160                    public Promise<Result, LdapException> apply(Result result)
161                            throws LdapException {
162                        final String newDn = entryDn.replace("Bob", "Renamed");
163                        log("Deleting " + newDn + "...");
164                        return connection.deleteAsync(
165                                Requests.newDeleteRequest(newDn));
166                    }
167                })
168                .thenOnResult(new ResultHandler<Result>() {
169                    @Override
170                    public void handleResult(Result result) {
171                        resultCode = result.getResultCode().intValue();
172                        log("... done.");
173                        COMPLETION_LATCH.countDown();
174                    }
175                })
176                .thenOnException(new ExceptionHandler<LdapException>() {
177                    @Override
178                    public void handleException(LdapException e) {
179                        System.err.println(e.getMessage());
180                        resultCode = e.getResult().getResultCode().intValue();
181                        COMPLETION_LATCH.countDown();
182                    }
183                });
184
185        try {
186            COMPLETION_LATCH.await();
187        }  catch (InterruptedException e) {
188            System.err.println(e.getMessage());
189            System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
190            return;
191        }
192
193        closeSilently(connection);
194        System.exit(resultCode);
195    }
196
197    /**
198     * Log a message to System.out.
199     *
200     * @param message   The message to write to the console.
201     */
202    private static void log(final String message) {
203        System.out.println(message);
204    }
205
206    /**
207     * Log an entry in LDIF form.
208     *
209     * @param entry     The entry to log.
210     */
211    private static void log(final Entry entry) {
212        try {
213            writer.writeEntry(entry);
214            writer.flush();
215        } catch (IOException e) {
216            System.err.println(e.getMessage());
217            System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
218        }
219    }
220
221    /**
222     * Constructor not used.
223     */
224    private ShortLifeAsync() {
225        // Not used.
226    }
227}