/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.mina.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.mina.api.IoFuture;
import org.apache.mina.api.IoFutureListener;
/**
* An abstract implementation of {@link IoFuture}. Owners of this future
* must implement {@link #cancelOwner(boolean)} to receive notifications of
* when the future should be canceled.
* <p/>
* Concrete implementations of this abstract class should consider overriding
* the two methods {@link #scheduleResult(org.apache.mina.api.IoFutureListener, Object)}
* and {@link #scheduleException(org.apache.mina.api.IoFutureListener, Throwable)}
* so that listeners are called in a separate thread. The default
* implementations may end up calling the listener in the same thread that is
* registering the listener, before the registration has completed.
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
*/
public abstract class AbstractIoFuture<V> implements IoFuture<V> {
static final Logger LOG = LoggerFactory.getLogger(AbstractIoFuture.class);
private final CountDownLatch latch = new CountDownLatch(1);
private final List<IoFutureListener<V>> listeners = new ArrayList<IoFutureListener<V>>();
private final AtomicReference<Object> result = new AtomicReference<Object>();
/**
* {@inheritDoc}
*/
@SuppressWarnings({"unchecked"})
public IoFuture<V> register(IoFutureListener<V> listener) {
LOG.debug("registering listener {}", listener);
synchronized (latch) {
if (!isDone()) {
LOG.debug("future is not done, adding listener to listener set");
listeners.add(listener);
listener = null;
}
}
if (listener != null) {
LOG.debug("future is done calling listener");
Object object = result.get();
if (object instanceof Throwable) {
scheduleException(listener, (Throwable) object);
} else {
scheduleResult(listener, (V) object);
}
}
return this;
}
/**
* {@inheritDoc}
*/
public boolean cancel(boolean mayInterruptIfRunning) {
LOG.debug("Attempting to cancel");
CancellationException ce = null;
synchronized (latch) {
if (!isCancelled() && !isDone() && cancelOwner(mayInterruptIfRunning)) {
LOG.debug("Successfully cancelled");
ce = new CancellationException();
result.set(ce);
} else {
LOG.debug("Unable to cancel");
}
latch.countDown();
}
if (ce != null) {
LOG.debug("Calling listeners");
for (IoFutureListener<V> listener : listeners) {
scheduleException(listener, ce);
}
}
return ce != null;
}
/**
* {@inheritDoc}
*/
public boolean isCancelled() {
return result.get() instanceof CancellationException;
}
/**
* {@inheritDoc}
*/
public boolean isDone() {
return latch.getCount() == 0;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({"unchecked"})
public V get() throws InterruptedException, ExecutionException {
LOG.trace("Entering wait");
latch.await();
LOG.trace("Wait completed");
if (isCancelled()) throw new CancellationException();
Object object = result.get();
if (object instanceof ExecutionException) {
throw (ExecutionException) object;
} else {
return (V) object;
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({"unchecked"})
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
LOG.trace("Entering wait");
if (!latch.await(timeout, unit)) throw new TimeoutException();
LOG.trace("Wait completed");
if (isCancelled()) throw new CancellationException();
Object object = result.get();
if (object instanceof ExecutionException) {
throw (ExecutionException) object;
} else {
return (V) object;
}
}
/**
* Notify the owner of this future that a client is attempting to cancel.
* This attempt will fail if the task has already completed, has already
* been cancelled, or could not be cancelled for some other reason. If
* successful, and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.
* <p/>
* <p>After this method returns, subsequent calls to {@link #isDone} will
* always return <tt>true</tt>. Subsequent calls to {@link #isCancelled}
* will always return <tt>true</tt> if this method returned <tt>true</tt>.
* <p/>
* <b>Note:</b> implementations must never throw an exception.
*
* @param mayInterruptIfRunning <tt>true</tt> if the owner executing this
* task should be interrupted; otherwise,
* in-progress tasks are allowed to complete
* @return <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally;
* <tt>true</tt> otherwise
*/
abstract protected boolean cancelOwner(boolean mayInterruptIfRunning);
/**
* Default implementation to call a listener's {@link IoFutureListener#completed(Object)}
* method. Owners may override this method so that the listener is called
* from a thread pool.
*
* @param listener the listener to call
* @param result the result to pass to the listener
*/
protected void scheduleResult(IoFutureListener<V> listener, V result) {
LOG.debug("Calling the default result scheduler");
try {
listener.completed(result);
} catch (Throwable t) {
LOG.warn("Listener threw an exception", t);
}
}
/**
* Default implementation to call a listener's {@link IoFutureListener#exception(Throwable)}
* method. Owners may override this method so that the listener is called
* from a thread pool.
*
* @param listener the listener to call
* @param throwable the exception to pass to the listener
*/
protected void scheduleException(IoFutureListener<V> listener, Throwable throwable) {
LOG.debug("Calling the default exception scheduler");
try {
listener.exception(throwable);
} catch (Throwable t) {
LOG.warn("Listener threw an exception", t);
}
}
/**
* Set the future result of the executing task. Any {@link IoFutureListener}s
* are notified of the
*
* @param value the value returned by the executing task.
*/
protected final void setResult(V value) {
assert !isDone();
synchronized (latch) {
result.set(value);
latch.countDown();
}
for (IoFutureListener<V> listener : listeners) {
scheduleResult(listener, value);
}
listeners.clear();
}
/**
* Set the future result as a {@link Throwable}, indicating that a
* throwable was thrown while executing the task. This value is usually
* set by the future result owner.
* <p/>
* Any {@link IoFutureListener}s are notified of the exception.
*
* @param t the throwable that was thrown while executing the task.
*/
protected final void setException(Throwable t) {
assert !isDone();
ExecutionException ee = new ExecutionException(t);
synchronized (latch) {
result.set(ee);
latch.countDown();
}
for (IoFutureListener<V> listener : listeners) {
scheduleException(listener, ee);
}
listeners.clear();
}
}