/*
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
|
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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.
|
*/
|
|
package com.sun.opends.sdk.util;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
|
|
import org.opends.sdk.*;
|
import org.opends.sdk.responses.Responses;
|
import org.opends.sdk.responses.Result;
|
|
|
|
/**
|
* This class provides a skeletal implementation of the {@code FutureResult}
|
* interface, to minimize the effort required to implement this interface.
|
* <p>
|
* This {@code FutureResult} implementation provides the following features:
|
* <ul>
|
* <li>The {@link #get} methods throw {@link ErrorResultException}s instead of
|
* the more generic {@code ExecutionException}s.
|
* <li>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.
|
* <li>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 <i>happens-before</i> a call to {@link #get}.
|
* <b>NOTE:</b> a result handler which attempts to call {@link #get} will
|
* deadlock.
|
* <li>Sub-classes may choose to implement specific cancellation cleanup by
|
* implementing the {@link #handleCancelRequest} method.
|
* </ul>
|
*
|
* @param <M>
|
* The type of result returned by this completion future.
|
*/
|
public abstract class AbstractFutureResult<M> implements FutureResult<M>,
|
ResultHandler<M>
|
{
|
@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)
|
{
|
final Result result = Responses
|
.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED);
|
errorResult = ErrorResultException.wrap(result);
|
}
|
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 ResultHandler<? super M> handler;
|
|
|
|
/**
|
* Creates a new abstract future result with the provided result handler.
|
*
|
* @param handler
|
* A result handler which will be forwarded the result or error when
|
* it arrives.
|
*/
|
protected AbstractFutureResult(final ResultHandler<? super M> handler)
|
{
|
this.handler = handler;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public final boolean cancel(final boolean mayInterruptIfRunning)
|
{
|
return sync.innerCancel(mayInterruptIfRunning);
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public final M get() throws ErrorResultException, InterruptedException
|
{
|
return sync.innerGet();
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public final M get(final long timeout, final TimeUnit unit)
|
throws ErrorResultException, TimeoutException, InterruptedException
|
{
|
return sync.innerGet(unit.toNanos(timeout));
|
}
|
|
|
|
/**
|
* 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.
|
*/
|
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.
|
*/
|
public final void handleResult(final M result)
|
{
|
sync.innerSetResult(result);
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public final boolean isCancelled()
|
{
|
return sync.innerIsCancelled();
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
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:
|
*
|
* <pre>
|
* Result result = Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED);
|
* </pre>
|
*
|
* 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);
|
}
|
|
}
|