/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
* Portions copyright 2013 ForgeRock AS.
*/
package com.forgerock.opendj.util;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import org.forgerock.opendj.ldap.CancelledResultException;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
/**
* This class provides a skeletal implementation of the {@code FutureResult}
* interface, to minimize the effort required to implement this interface.
*
* This {@code FutureResult} implementation provides the following features:
*
* - The {@link #get} methods throw {@link ErrorResultException}s instead of
* the more generic {@code ExecutionException}s.
*
- The {@link #get} methods never throw {@code CancellationException} since
* requests in this SDK can usually be cancelled via other external means (e.g.
* the {@code Cancel} extended operation) for which there are well defined error
* results. Therefore cancellation is always signalled by throwing a
* {@link CancelledResultException} in order to be consistent with other error
* results.
*
- A {@link ResultHandler} can be provided to the constructor. The result
* handler will be invoked immediately after the result or error is received but
* before threads blocked on {@link #get} are released. More specifically,
* result handler invocation happens-before a call to {@link #get}.
* NOTE: a result handler which attempts to call {@link #get} will
* deadlock.
*
- Sub-classes may choose to implement specific cancellation cleanup by
* implementing the {@link #handleCancelRequest} method.
*
*
* @param
* The type of result returned by this future.
* @param
* The type of {@link ResultHandler} associated to this future.
*/
public class AsynchronousFutureResult> implements
FutureResult, ResultHandler {
@SuppressWarnings("serial")
private final class Sync extends AbstractQueuedSynchronizer {
// State value representing the initial state before a result has
// been received.
private static final int WAITING = 0;
// State value representing that a result has been received and is
// being processed.
private static final int PENDING = 1;
// State value representing that the request was cancelled.
private static final int CANCELLED = 2;
// State value representing that the request has failed.
private static final int FAIL = 3;
// State value representing that the request has succeeded.
private static final int SUCCESS = 4;
// These do not need to be volatile since their values are published
// by updating the state after they are set and reading the state
// immediately before they are read.
private ErrorResultException errorResult = null;
private M result = null;
/**
* Allow all threads to acquire if future has completed.
*/
@Override
protected int tryAcquireShared(final int ignore) {
return innerIsDone() ? 1 : -1;
}
/**
* Signal that the future has completed and threads waiting on get() can
* be released.
*/
@Override
protected boolean tryReleaseShared(final int finalState) {
// Ensures that errorResult/result is published.
setState(finalState);
return true;
}
boolean innerCancel(final boolean mayInterruptIfRunning) {
if (!isCancelable() || !setStatePending()) {
return false;
}
// Perform implementation defined cancellation.
ErrorResultException errorResult = handleCancelRequest(mayInterruptIfRunning);
if (errorResult == null) {
errorResult = newErrorResult(ResultCode.CLIENT_SIDE_USER_CANCELLED);
}
this.errorResult = errorResult;
try {
// Invoke error result completion handler.
if (handler != null) {
handler.handleErrorResult(errorResult);
}
} finally {
releaseShared(CANCELLED); // Publishes errorResult.
}
return true;
}
M innerGet() throws ErrorResultException, InterruptedException {
acquireSharedInterruptibly(0);
return get0();
}
M innerGet(final long nanosTimeout) throws ErrorResultException, TimeoutException,
InterruptedException {
if (!tryAcquireSharedNanos(0, nanosTimeout)) {
throw new TimeoutException();
} else {
return get0();
}
}
boolean innerIsCancelled() {
return getState() == CANCELLED;
}
boolean innerIsDone() {
return getState() > 1;
}
void innerSetErrorResult(final ErrorResultException errorResult) {
if (setStatePending()) {
this.errorResult = errorResult;
try {
// Invoke error result completion handler.
if (handler != null) {
handler.handleErrorResult(errorResult);
}
} finally {
releaseShared(FAIL); // Publishes errorResult.
}
}
}
void innerSetResult(final M result) {
if (setStatePending()) {
this.result = result;
try {
// Invoke result completion handler.
if (handler != null) {
handler.handleResult(result);
}
} finally {
releaseShared(SUCCESS); // Publishes result.
}
}
}
private M get0() throws ErrorResultException {
if (errorResult != null) {
// State must be FAILED or CANCELLED.
throw errorResult;
} else {
// State must be SUCCESS.
return result;
}
}
private boolean setStatePending() {
for (;;) {
final int s = getState();
if (s != WAITING) {
return false;
}
if (compareAndSetState(s, PENDING)) {
return true;
}
}
}
}
private final Sync sync = new Sync();
private final H handler;
private final int requestID;
/**
* Creates a new asynchronous future result with the provided result handler
* and a request ID of -1.
*
* @param handler
* A result handler which will be forwarded the result or error
* when it arrives, may be {@code null}.
*/
public AsynchronousFutureResult(final H handler) {
this(handler, -1);
}
/**
* Creates a new asynchronous future result with the provided result handler
* and request ID.
*
* @param handler
* A result handler which will be forwarded the result or error
* when it arrives, may be {@code null}.
* @param requestID
* The request ID which will be returned by the default
* implementation of {@link #getRequestID}.
*/
public AsynchronousFutureResult(final H handler, final int requestID) {
this.handler = handler;
this.requestID = requestID;
}
/**
* {@inheritDoc}
*/
@Override
public final boolean cancel(final boolean mayInterruptIfRunning) {
return sync.innerCancel(mayInterruptIfRunning);
}
/**
* {@inheritDoc}
*/
@Override
public final M get() throws ErrorResultException, InterruptedException {
return sync.innerGet();
}
/**
* {@inheritDoc}
*/
@Override
public final M get(final long timeout, final TimeUnit unit) throws ErrorResultException,
TimeoutException, InterruptedException {
return sync.innerGet(unit.toNanos(timeout));
}
/**
* Returns the result handler associated to this FutureResult.
*
* @return the result handler associated to this FutureResult.
*/
public H getResultHandler() {
return handler;
}
/**
* {@inheritDoc}
*
* The default implementation returns the request ID passed in during
* construction, or -1 if none was provided.
*/
@Override
public int getRequestID() {
return requestID;
}
/**
* Sets the error result associated with this future. If ({@code isDone() ==
* true}) then the error result will be ignored, otherwise the result
* handler will be invoked if one was provided and, on return, any threads
* waiting on {@link #get} will be released and the provided error result
* will be thrown.
*
* @param errorResult
* The error result.
*/
@Override
public final void handleErrorResult(final ErrorResultException errorResult) {
sync.innerSetErrorResult(errorResult);
}
/**
* Sets the result associated with this future. If ({@code isDone() == true}
* ) then the result will be ignored, otherwise the result handler will be
* invoked if one was provided and, on return, any threads waiting on
* {@link #get} will be released and the provided result will be returned.
*
* @param result
* The result.
*/
@Override
public final void handleResult(final M result) {
sync.innerSetResult(result);
}
/**
* {@inheritDoc}
*/
@Override
public final boolean isCancelled() {
return sync.innerIsCancelled();
}
/**
* {@inheritDoc}
*/
@Override
public final boolean isDone() {
return sync.innerIsDone();
}
/**
* Invoked when {@link #cancel} is called and {@code isDone() == false} and
* immediately before any threads waiting on {@link #get} are released.
* Implementations may choose to return a custom error result if needed or
* return {@code null} if the following default error result is acceptable:
*
*
* Result result = Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED);
*
*
* In addition, implementations may perform other cleanup, for example, by
* issuing an LDAP abandon request. The default implementation is to do
* nothing.
*
* @param mayInterruptIfRunning
* {@code true} if the thread executing executing the response
* handler should be interrupted; otherwise, in-progress response
* handlers are allowed to complete.
* @return The custom error result, or {@code null} if the default is
* acceptable.
*/
protected ErrorResultException handleCancelRequest(final boolean mayInterruptIfRunning) {
// Do nothing by default.
return null;
}
/**
* Indicates whether this future result can be canceled.
*
* @return {@code true} if this future result is cancelable or {@code false}
* otherwise.
*/
protected boolean isCancelable() {
// Return true by default.
return true;
}
/**
* Appends a string representation of this future's state to the provided
* builder.
*
* @param sb
* The string builder.
*/
protected void toString(final StringBuilder sb) {
sb.append(" state = ");
sb.append(sync);
}
}