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 2014-2016 ForgeRock AS.
015 */
016package org.forgerock.opendj.ldap.spi;
017
018import static org.forgerock.opendj.ldap.LdapException.newLdapException;
019import static org.forgerock.opendj.ldap.responses.Responses.newCompareResult;
020import static org.forgerock.opendj.ldap.responses.Responses.newResult;
021
022import org.forgerock.opendj.ldap.Connection;
023import org.forgerock.opendj.ldap.IntermediateResponseHandler;
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.requests.BindClient;
029import org.forgerock.opendj.ldap.requests.BindRequest;
030import org.forgerock.opendj.ldap.requests.CompareRequest;
031import org.forgerock.opendj.ldap.requests.ExtendedRequest;
032import org.forgerock.opendj.ldap.requests.Request;
033import org.forgerock.opendj.ldap.requests.Requests;
034import org.forgerock.opendj.ldap.requests.SearchRequest;
035import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
036import org.forgerock.opendj.ldap.responses.BindResult;
037import org.forgerock.opendj.ldap.responses.CompareResult;
038import org.forgerock.opendj.ldap.responses.ExtendedResult;
039import org.forgerock.opendj.ldap.responses.Result;
040import org.forgerock.util.promise.Promise;
041import org.forgerock.util.promise.PromiseImpl;
042import org.forgerock.util.promise.Promises;
043
044/** Utility methods for creating and composing {@link LdapPromise}s. */
045public final class LdapPromises {
046    private LdapPromises() {
047    }
048
049    /**
050     * Converts a {@link Promise} to a {@link LdapPromise}.
051     *
052     * @param <R>
053     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
054     *         side-effects).
055     * @param wrappedPromise
056     *         The {@link Promise} to wrap.
057     * @return A {@link LdapPromise} representing the same asynchronous task as the {@link Promise} provided.
058     */
059    public static <R> LdapPromise<R> asPromise(Promise<R, LdapException> wrappedPromise) {
060        return wrap(wrappedPromise, -1);
061    }
062
063    /**
064     * Creates a new bind {@link BindResultLdapPromiseImpl}.
065     *
066     * @param requestID
067     *         Identifier of the request.
068     * @param request
069     *         The bind request sent to server.
070     * @param bindClient
071     *         Client that binds to the server.
072     * @param intermediateResponseHandler
073     *         Handler that consumes intermediate responses from extended operations.
074     * @return The new {@link BindResultLdapPromiseImpl}.
075     */
076    public static BindResultLdapPromiseImpl newBindLdapPromise(
077            final int requestID,
078            final BindRequest request,
079            final BindClient bindClient,
080            final IntermediateResponseHandler intermediateResponseHandler) {
081        return new BindResultLdapPromiseImpl(LdapPromises.<BindResult>newInnerBindOrStartTLSPromise(),
082                                             requestID,
083                                             request,
084                                             bindClient,
085                                             intermediateResponseHandler);
086    }
087
088    /**
089     * Creates a new compare {@link ResultLdapPromiseImpl}.
090     *
091     * @param requestID
092     *         Identifier of the request.
093     * @param request
094     *         The compare request sent to the server.
095     * @param intermediateResponseHandler
096     *         Handler that consumes intermediate responses from extended operations.
097     * @param connection
098     *         The connection to directory server.
099     * @return The new {@link ResultLdapPromiseImpl}.
100     */
101    public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(
102            final int requestID,
103            final CompareRequest request,
104            final IntermediateResponseHandler intermediateResponseHandler,
105            final Connection connection) {
106        final PromiseImpl<CompareResult, LdapException> innerPromise = newInnerPromise(connection, requestID);
107        return newCompareLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
108    }
109
110    /**
111     * Creates a new compare {@link ResultLdapPromiseImpl}.
112     *
113     * @param requestID
114     *         Identifier of the request.
115     * @param request
116     *         The compare request sent to the server.
117     * @param intermediateResponseHandler
118     *         Handler that consumes intermediate responses from extended operations.
119     * @param connection
120     *         The connection to directory server.
121     * @return The new {@link ResultLdapPromiseImpl}.
122     */
123    public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(
124            final int requestID,
125            final CompareRequest request,
126            final IntermediateResponseHandler intermediateResponseHandler,
127            final LDAPConnectionImpl connection) {
128        final PromiseImpl<CompareResult, LdapException> innerPromise = newInnerPromise(connection, requestID);
129        return newCompareLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
130    }
131
132    private static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(
133            final PromiseImpl<CompareResult, LdapException> innerPromise,
134            final int requestID,
135            final CompareRequest request,
136            final IntermediateResponseHandler intermediateResponseHandler) {
137        return new ResultLdapPromiseImpl<CompareRequest, CompareResult>(innerPromise,
138                                                                        requestID,
139                                                                        request,
140                                                                        intermediateResponseHandler) {
141            @Override
142            protected CompareResult newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) {
143                return newCompareResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause);
144            }
145        };
146    }
147
148    /**
149     * Creates a new extended {@link ExtendedResultLdapPromiseImpl}.
150     *
151     * @param <S>
152     *         The type of result returned by this promise.
153     * @param requestID
154     *         Identifier of the request.
155     * @param request
156     *         The extended request sent to the server.
157     * @param intermediateResponseHandler
158     *         Handler that consumes intermediate responses from extended operations.
159     * @param connection
160     *         The connection to directory server.
161     * @return The new {@link ExtendedResultLdapPromiseImpl}.
162     */
163    public static <S extends ExtendedResult> ExtendedResultLdapPromiseImpl<S> newExtendedLdapPromise(
164            final int requestID,
165            final ExtendedRequest<S> request,
166            final IntermediateResponseHandler intermediateResponseHandler,
167            final Connection connection) {
168        final PromiseImpl<S, LdapException> innerPromise;
169        if (!StartTLSExtendedRequest.OID.equals(request.getOID())) {
170            innerPromise = newInnerBindOrStartTLSPromise();
171        } else {
172            innerPromise = newInnerPromise(connection, requestID);
173        }
174        return new ExtendedResultLdapPromiseImpl<>(innerPromise, requestID, request, intermediateResponseHandler);
175    }
176
177    /**
178     * Creates a new extended {@link ExtendedResultLdapPromiseImpl}.
179     *
180     * @param <S>
181     *         The type of result returned by this promise.
182     * @param requestID
183     *         Identifier of the request.
184     * @param request
185     *         The extended request sent to the server.
186     * @param intermediateResponseHandler
187     *         Handler that consumes intermediate responses from extended operations.
188     * @param connection
189     *         The connection to directory server.
190     * @return The new {@link ExtendedResultLdapPromiseImpl}.
191     */
192    public static <S extends ExtendedResult> ExtendedResultLdapPromiseImpl<S> newExtendedLdapPromise(
193            final int requestID,
194            final ExtendedRequest<S> request,
195            final IntermediateResponseHandler intermediateResponseHandler,
196            final LDAPConnectionImpl connection) {
197        final PromiseImpl<S, LdapException> innerPromise;
198        if (!StartTLSExtendedRequest.OID.equals(request.getOID())) {
199            innerPromise = newInnerBindOrStartTLSPromise();
200        } else {
201            innerPromise = newInnerPromise(connection, requestID);
202        }
203        return new ExtendedResultLdapPromiseImpl<>(innerPromise, requestID, request, intermediateResponseHandler);
204    }
205
206    /**
207     * Creates a new {@link ResultLdapPromiseImpl} to handle  a standard request (add, delete, modify and modidyDN).
208     *
209     * @param <R>
210     *         The type of the task's request.
211     * @param requestID
212     *         Identifier of the request.
213     * @param request
214     *         The request sent to the server.
215     * @param intermediateResponseHandler
216     *         Handler that consumes intermediate responses from extended operations.
217     * @param connection
218     *         The connection to directory server.
219     * @return The new {@link ResultLdapPromiseImpl}.
220     */
221    public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(
222            final int requestID,
223            final R request,
224            final IntermediateResponseHandler intermediateResponseHandler,
225            final Connection connection) {
226        final PromiseImpl<Result, LdapException> innerPromise = newInnerPromise(connection, requestID);
227        return newResultLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
228    }
229
230    /**
231     * Creates a new {@link ResultLdapPromiseImpl} to handle  a standard request (add, delete, modify and modidyDN).
232     *
233     * @param <R>
234     *         The type of the task's request.
235     * @param requestID
236     *         Identifier of the request.
237     * @param request
238     *         The request sent to the server.
239     * @param intermediateResponseHandler
240     *         Handler that consumes intermediate responses from extended operations.
241     * @param connection
242     *         The connection to directory server.
243     * @return The new {@link ResultLdapPromiseImpl}.
244     */
245    public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(
246            final int requestID,
247            final R request,
248            final IntermediateResponseHandler intermediateResponseHandler,
249            final LDAPConnectionImpl connection) {
250        final PromiseImpl<Result, LdapException> innerPromise = newInnerPromise(connection, requestID);
251        return newResultLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
252    }
253
254    private static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(
255            final PromiseImpl<Result, LdapException> innerPromise,
256            final int requestID,
257            final R request,
258            final IntermediateResponseHandler intermediateResponseHandler) {
259        return new ResultLdapPromiseImpl<R, Result>(innerPromise, requestID, request, intermediateResponseHandler) {
260            @Override
261            protected Result newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) {
262                return newResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause);
263            }
264        };
265    }
266
267    /**
268     * Creates a new search {@link SearchResultLdapPromiseImpl}.
269     *
270     * @param requestID
271     *         Identifier of the request.
272     * @param request
273     *         The search request sent to the server.
274     * @param resultHandler
275     *         Handler that consumes search result.
276     * @param intermediateResponseHandler
277     *         Handler that consumes intermediate responses from extended operations.
278     * @param connection
279     *         The connection to directory server.
280     * @return The new {@link SearchResultLdapPromiseImpl}.
281     */
282    public static SearchResultLdapPromiseImpl newSearchLdapPromise(
283            final int requestID,
284            final SearchRequest request,
285            final SearchResultHandler resultHandler,
286            final IntermediateResponseHandler intermediateResponseHandler,
287            final Connection connection) {
288        return new SearchResultLdapPromiseImpl(newInnerPromise(connection, requestID),
289                                               requestID,
290                                               request,
291                                               resultHandler,
292                                               intermediateResponseHandler);
293    }
294
295    /**
296     * Creates a new search {@link SearchResultLdapPromiseImpl}.
297     *
298     * @param requestID
299     *         Identifier of the request.
300     * @param request
301     *         The search request sent to the server.
302     * @param resultHandler
303     *         Handler that consumes search result.
304     * @param intermediateResponseHandler
305     *         Handler that consumes intermediate responses from extended operations.
306     * @param connection
307     *         The connection to directory server.
308     * @return The new {@link SearchResultLdapPromiseImpl}.
309     */
310    public static SearchResultLdapPromiseImpl newSearchLdapPromise(
311            final int requestID,
312            final SearchRequest request,
313            final SearchResultHandler resultHandler,
314            final IntermediateResponseHandler intermediateResponseHandler,
315            final LDAPConnectionImpl connection) {
316        return new SearchResultLdapPromiseImpl(newInnerPromise(connection, requestID),
317                                               requestID,
318                                               request,
319                                               resultHandler,
320                                               intermediateResponseHandler);
321    }
322
323    /**
324     * Returns a {@link LdapPromise} representing an asynchronous task which has already failed with the provided
325     * error.
326     *
327     * @param <R>
328     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
329     *         side-effects).
330     * @param <E>
331     *         The type of the exception thrown by the task if it fails.
332     * @param error
333     *         The exception indicating why the asynchronous task has failed.
334     * @return A {@link LdapPromise} representing an asynchronous task which has already failed with the provided error.
335     */
336    public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error) {
337        return wrap(Promises.<R, LdapException>newExceptionPromise(error), -1);
338    }
339
340    /**
341     * Returns a {@link LdapPromise} representing an asynchronous task, identified by the provided requestID, which has
342     * already failed with the provided error.
343     *
344     * @param <R>
345     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
346     *         side-effects).
347     * @param <E>
348     *         The type of the exception thrown by the task if it fails.
349     * @param error
350     *         The exception indicating why the asynchronous task has failed.
351     * @param requestID
352     *         The request ID of the failed task.
353     * @return A {@link LdapPromise} representing an asynchronous task which has already failed with the provided error.
354     */
355    public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error, int requestID) {
356        return wrap(Promises.<R, LdapException>newExceptionPromise(error), requestID);
357    }
358
359    /**
360     * Returns a {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided
361     * result. Attempts to get the result will immediately return the result.
362     *
363     * @param <R>
364     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
365     *         side-effects).
366     * @param result
367     *         The result of the asynchronous task.
368     * @return A {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided
369     * result.
370     */
371    public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result) {
372        return wrap(Promises.<R, LdapException>newResultPromise(result), -1);
373    }
374
375    /**
376     * Returns a {@link LdapPromise} representing an asynchronous task, identified by the provided requestID, which has
377     * already succeeded with the provided result. Attempts to get the result will immediately return the result.
378     *
379     * @param <R>
380     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
381     *         side-effects).
382     * @param result
383     *         The result of the asynchronous task.
384     * @param requestID
385     *         The request ID of the succeeded task.
386     * @return A {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided
387     * result.
388     */
389    public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result, int requestID) {
390        return wrap(Promises.<R, LdapException>newResultPromise(result), requestID);
391    }
392
393    private static <S extends Result> PromiseImpl<S, LdapException> newInnerBindOrStartTLSPromise() {
394        return new PromiseImpl<S, LdapException>() {
395            @Override
396            protected LdapException tryCancel(boolean mayInterruptIfRunning) {
397                /*
398                 * No other operations can be performed while a bind or StartTLS is active. Therefore it is not
399                 * possible to cancel bind or StartTLS requests, since doing so will leave the connection in a
400                 * state which prevents other operations from being performed.
401                 */
402                return null;
403            }
404        };
405    }
406
407    private static <S extends Result> PromiseImpl<S, LdapException> newInnerPromise(
408            final Connection connection, final int requestID) {
409        return new PromiseImpl<S, LdapException>() {
410            @Override
411            protected final LdapException tryCancel(final boolean mayInterruptIfRunning) {
412                /*
413                 * This will abandon the request, but will also recursively cancel this future. There is no risk of
414                 * an infinite loop because the state of this future has already been changed.
415                 */
416                connection.abandonAsync(Requests.newAbandonRequest(requestID));
417                return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED);
418            }
419        };
420    }
421
422    private static <S extends Result> PromiseImpl<S, LdapException> newInnerPromise(
423            final LDAPConnectionImpl connection, final int requestID) {
424        return new PromiseImpl<S, LdapException>() {
425            @Override
426            protected final LdapException tryCancel(final boolean mayInterruptIfRunning) {
427                /*
428                 * This will abandon the request, but will also recursively cancel this future. There is no risk of
429                 * an infinite loop because the state of this future has already been changed.
430                 */
431                connection.abandonAsync(Requests.newAbandonRequest(requestID));
432                return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED);
433            }
434        };
435    }
436
437    static <R> LdapPromise<R> wrap(Promise<R, LdapException> wrappedPromise, int requestID) {
438        return new LdapPromiseWrapper<>(wrappedPromise, requestID);
439    }
440}