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 *
024 *      Copyright 2014 ForgeRock AS.
025 */
026
027package org.forgerock.opendj.ldap.spi;
028
029import org.forgerock.opendj.ldap.Connection;
030import org.forgerock.opendj.ldap.IntermediateResponseHandler;
031import org.forgerock.opendj.ldap.LdapException;
032import org.forgerock.opendj.ldap.LdapPromise;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.requests.Request;
035import org.forgerock.opendj.ldap.requests.Requests;
036import org.forgerock.opendj.ldap.responses.IntermediateResponse;
037import org.forgerock.opendj.ldap.responses.Result;
038import org.forgerock.util.promise.Promise;
039import org.forgerock.util.promise.PromiseImpl;
040import org.forgerock.util.promise.Promises;
041
042import static org.forgerock.opendj.ldap.LdapException.*;
043
044/**
045 * This class provides an implementation of the {@link LdapPromise}.
046 *
047 * @param <R>
048 *            The type of the associated {@link Request}.
049 * @param <S>
050 *            The type of result returned by this promise.
051 * @see Promise
052 * @see Promises
053 * @see LdapPromise
054 */
055public abstract class ResultLdapPromiseImpl<R extends Request, S extends Result> extends LdapPromiseImpl<S>
056        implements LdapPromise<S>, IntermediateResponseHandler {
057    private final R request;
058    private IntermediateResponseHandler intermediateResponseHandler;
059    private volatile long timestamp;
060
061    ResultLdapPromiseImpl(final int requestID, final R request,
062            final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) {
063        this(new PromiseImpl<S, LdapException>() {
064            @Override
065            protected final LdapException tryCancel(final boolean mayInterruptIfRunning) {
066                /*
067                 * This will abandon the request, but will also recursively cancel this
068                 * future. There is no risk of an infinite loop because the state of
069                 * this future has already been changed.
070                 */
071                connection.abandonAsync(Requests.newAbandonRequest(requestID));
072                return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED);
073            }
074        }, requestID, request, intermediateResponseHandler, connection);
075    }
076
077    ResultLdapPromiseImpl(final PromiseImpl<S, LdapException> impl, final int requestID, final R request,
078            final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) {
079        super(impl, requestID);
080        this.request = request;
081        this.intermediateResponseHandler = intermediateResponseHandler;
082        this.timestamp = System.currentTimeMillis();
083    }
084
085    /** {@inheritDoc} */
086    @Override
087    public final boolean handleIntermediateResponse(final IntermediateResponse response) {
088        // FIXME: there's a potential race condition here - the promise could
089        // get cancelled between the isDone() call and the handler
090        // invocation. We'd need to add support for intermediate handlers in
091        // the synchronizer.
092        if (!isDone()) {
093            updateTimestamp();
094            if (intermediateResponseHandler != null
095                    && !intermediateResponseHandler.handleIntermediateResponse(response)) {
096                intermediateResponseHandler = null;
097            }
098        }
099        return true;
100    }
101
102    /**
103     * Returns {@code true} if this promise represents the result of a bind or
104     * StartTLS request. The default implementation is to return {@code false}.
105     *
106     * @return {@code true} if this promise represents the result of a bind or
107     *         StartTLS request.
108     */
109    public boolean isBindOrStartTLS() {
110        return false;
111    }
112
113    /**
114     * Returns a string representation of this promise's state.
115     *
116     * @return String representation of this promise's state.
117     */
118    public String toString() {
119        final StringBuilder sb = new StringBuilder();
120        sb.append("( requestID = ");
121        sb.append(getRequestID());
122        sb.append(" timestamp = ");
123        sb.append(timestamp);
124        sb.append(" request = ");
125        sb.append(getRequest());
126        sb.append(" )");
127        return sb.toString();
128    }
129
130    /**
131     * Sets the result associated to this promise as an error result.
132     *
133     * @param result
134     *            result of an operation
135     */
136    public final void adaptErrorResult(final Result result) {
137        final S errorResult = newErrorResult(result.getResultCode(), result.getDiagnosticMessage(), result.getCause());
138        setResultOrError(errorResult);
139    }
140
141    /**
142     * Returns the creation time of this promise.
143     *
144     * @return the timestamp indicating creation time of this promise
145     */
146    public final long getTimestamp() {
147        return timestamp;
148    }
149
150    abstract S newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause);
151
152    /**
153     * Sets the result associated to this promise.
154     *
155     * @param result
156     *            the result of operation
157     */
158    public final void setResultOrError(final S result) {
159        if (result.getResultCode().isExceptional()) {
160            getWrappedPromise().handleException(newLdapException(result));
161        } else {
162            getWrappedPromise().handleResult(result);
163        }
164    }
165
166    /**
167     * Returns the attached request.
168     *
169     * @return The request.
170     */
171    public R getRequest() {
172        return request;
173    }
174
175    final void updateTimestamp() {
176        timestamp = System.currentTimeMillis();
177    }
178
179    /**
180     * Returns {@code true} if this request should be canceled once the timeout
181     * period expires. The default implementation is to return {@code true}
182     * which will be appropriate for nearly all requests, the one obvious
183     * exception being persistent searches.
184     *
185     * @return {@code true} if this request should be canceled once the timeout
186     *         period expires.
187     */
188    public boolean checkForTimeout() {
189        return true;
190    }
191}