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-2016 ForgeRock AS.
015 */
016package org.forgerock.opendj.examples;
017
018import java.io.IOException;
019import java.util.Arrays;
020import java.util.concurrent.CountDownLatch;
021
022import org.forgerock.opendj.ldap.Connection;
023import org.forgerock.opendj.ldap.LDAPConnectionFactory;
024import org.forgerock.opendj.ldap.LdapException;
025import org.forgerock.opendj.ldap.LdapPromise;
026import org.forgerock.opendj.ldap.ResultCode;
027import org.forgerock.opendj.ldap.SearchResultHandler;
028import org.forgerock.opendj.ldap.SearchScope;
029import org.forgerock.opendj.ldap.requests.CancelExtendedRequest;
030import org.forgerock.opendj.ldap.requests.Requests;
031import org.forgerock.opendj.ldap.responses.BindResult;
032import org.forgerock.opendj.ldap.responses.ExtendedResult;
033import org.forgerock.opendj.ldap.responses.Result;
034import org.forgerock.opendj.ldap.responses.SearchResultEntry;
035import org.forgerock.opendj.ldap.responses.SearchResultReference;
036import org.forgerock.opendj.ldif.LDIFEntryWriter;
037import org.forgerock.util.AsyncFunction;
038import org.forgerock.util.promise.ExceptionHandler;
039import org.forgerock.util.promise.Promise;
040import org.forgerock.util.promise.ResultHandler;
041
042/**
043 * An example client application which searches a Directory Server using the
044 * asynchronous APIs. This example takes the following command line parameters:
045 *
046 * <pre>
047 *  {@code <host> <port> <username> <password>
048 *      <baseDN> <scope> <filter> [<attribute> <attribute> ...]}
049 * </pre>
050 */
051public final class SearchAsync {
052    // --- JCite search result handler ---
053    private static final class SearchResultHandlerImpl implements SearchResultHandler {
054        @Override
055        public synchronized boolean handleEntry(final SearchResultEntry entry) {
056            try {
057                if (entryCount < 10) {
058                    WRITER.writeComment("Search result entry: " + entry.getName());
059                    WRITER.writeEntry(entry);
060                    ++entryCount;
061                } else { // Cancel the search.
062                    CancelExtendedRequest request =
063                            Requests.newCancelExtendedRequest(requestID);
064                    connection.extendedRequestAsync(request)
065                            .thenOnResult(new ResultHandler<ExtendedResult>() {
066                                @Override
067                                public void handleResult(ExtendedResult result) {
068                                    System.err.println("Cancel request succeeded");
069                                    CANCEL_LATCH.countDown();
070                                }
071                            })
072                            .thenOnException(new ExceptionHandler<LdapException>() {
073                                @Override
074                                public void handleException(LdapException exception) {
075                                    System.err.println("Cancel request failed: "
076                                            + exception.getResult().getResultCode().intValue()
077                                            + " "
078                                            + exception.getResult().getDiagnosticMessage());
079                                    CANCEL_LATCH.countDown();
080                                }
081                            });
082                    return false;
083                }
084            } catch (final IOException e) {
085                System.err.println(e.getMessage());
086                resultCode = ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
087                COMPLETION_LATCH.countDown();
088                return false;
089            }
090            return true;
091        }
092
093        @Override
094        public synchronized boolean handleReference(final SearchResultReference reference) {
095            try {
096                WRITER.writeComment("Search result reference: " + reference.getURIs());
097            } catch (final IOException e) {
098                System.err.println(e.getMessage());
099                resultCode = ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
100                COMPLETION_LATCH.countDown();
101                return false;
102            }
103            return true;
104        }
105
106    }
107    // --- JCite search result handler ---
108
109    // --- JCite decl1 ---
110    private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
111    private static final CountDownLatch CANCEL_LATCH = new CountDownLatch(1);
112    private static final LDIFEntryWriter WRITER = new LDIFEntryWriter(System.out);
113    // --- JCite decl1 ---
114    private static String userName;
115    private static String password;
116    private static String baseDN;
117    private static SearchScope scope;
118    private static String filter;
119    private static String[] attributes;
120    private static Connection connection;
121    private static int resultCode;
122
123    // --- JCite decl2 ---
124    static int requestID;
125    static int entryCount;
126    // --- JCite decl2 ---
127
128    /**
129     * Main method.
130     *
131     * @param args
132     *            The command line arguments: host, port, username, password,
133     *            base DN, scope, filter, and zero or more attributes to be
134     *            retrieved.
135     */
136    public static void main(final String[] args) {
137        if (args.length < 7) {
138            System.err.println("Usage: host port username password baseDN scope " + "filter [attribute ...]");
139            System.exit(1);
140        }
141
142        // Parse command line arguments.
143        final String hostName = args[0];
144        final int port = Integer.parseInt(args[1]);
145        userName = args[2];
146        password = args[3];
147        baseDN = args[4];
148        final String scopeString = args[5];
149        filter = args[6];
150        if (args.length > 7) {
151            attributes = Arrays.copyOfRange(args, 7, args.length);
152        } else {
153            attributes = new String[0];
154        }
155
156        scope = SearchScope.valueOf(scopeString);
157        if (scope == null) {
158            System.err.println("Unknown scope: " + scopeString);
159            System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
160            return;
161        }
162
163        // --- Using Promises ---
164        // Initiate the asynchronous connect, bind, and search.
165        final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
166
167        factory.getConnectionAsync()
168                .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
169                    @Override
170                    public Promise<BindResult, LdapException> apply(Connection connection)
171                            throws LdapException {
172                        SearchAsync.connection = connection;
173                        return connection.bindAsync(Requests
174                                .newSimpleBindRequest(userName, password.toCharArray()));
175                    }
176                })
177                .thenAsync(new AsyncFunction<BindResult, Result, LdapException>() {
178                    @Override
179                    public Promise<Result, LdapException> apply(BindResult result)
180                            throws LdapException {
181                        LdapPromise<Result> promise = connection.searchAsync(
182                                Requests.newSearchRequest(baseDN, scope, filter, attributes),
183                                new SearchResultHandlerImpl());
184                        requestID = promise.getRequestID();
185                        return promise;
186                    }
187                })
188                .thenOnResult(new ResultHandler<Result>() {
189                    @Override
190                    public void handleResult(Result result) {
191                        resultCode = result.getResultCode().intValue();
192                        COMPLETION_LATCH.countDown();
193                    }
194                })
195                .thenOnException(new ExceptionHandler<LdapException>() {
196                    @Override
197                    public void handleException(LdapException exception) {
198                        System.err.println(exception.getMessage());
199                        resultCode = exception.getResult().getResultCode().intValue();
200                        COMPLETION_LATCH.countDown();
201                    }
202                });
203        // --- Using Promises ---
204
205        // Await completion.
206        try {
207            COMPLETION_LATCH.await();
208        } catch (final InterruptedException e) {
209            System.err.println(e.getMessage());
210            System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
211            return;
212        }
213
214        try {
215            WRITER.flush();
216        } catch (final IOException e) {
217            System.err.println(e.getMessage());
218            System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
219            return;
220        }
221
222        // Await completion of the cancel request.
223        try {
224            CANCEL_LATCH.await();
225        } catch (final InterruptedException e) {
226            System.err.println(e.getMessage());
227            System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
228            return;
229        }
230
231        if (connection != null) {
232            connection.close();
233        }
234
235        System.exit(resultCode);
236    }
237
238    private SearchAsync() {
239        // Not used.
240    }
241}