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