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