/**
* Copyright 2007-2015, Kaazing Corporation. All rights reserved.
*
* Licensed 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.kaazing.k3po.driver.internal.netty.channel;
import static java.util.Collections.unmodifiableCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelFutureProgressListener;
import org.jboss.netty.channel.DefaultChannelFuture;
/**
* A {@link ChannelFuture} of {@link ChannelFuture}s. It is useful when you want
* to get notified when all {@link ChannelFuture}s are complete.
*
* Note that this future is complete if and only if its containing futures are
* complete Setting this future to complete does NOT result in containing
* futures getting completed.
*
* This future is not cancelable. If one of the futures in which this future is
* composed of is canceled. The CompositeChannelFuture2 will be considered
* failed.
*
* @param <E> the type of the child futures.
*/
public class CompositeChannelFuture<E extends ChannelFuture> extends DefaultChannelFuture {
private final NotifyingListener listener = new NotifyingListener();
private final AtomicInteger unnotified = new AtomicInteger();
private volatile boolean constructionFinished;
private final Collection<E> kids;
private volatile int successCount;
private volatile int failedCount;
private volatile int cancelledCount;
private final boolean failFast;
public CompositeChannelFuture(Channel channel, Collection<E> kids) {
this(channel, kids, false);
}
// If we fail fast it means the composite is set to failure as soon as we see a single future fail
public CompositeChannelFuture(Channel channel, Collection<E> kids, boolean failFast) {
super(channel, false);
this.failFast = failFast;
this.kids = unmodifiableCollection(new ArrayList<>(kids));
for (E k : kids) {
unnotified.incrementAndGet();
k.addListener(listener);
}
/*
* Note that a composite with no children will be automatically set to
* success
*/
constructionFinished = true;
if (unnotified.get() == 0) {
setSuccess();
}
}
@Override
public Throwable getCause() {
Throwable t = super.getCause();
if (t != null) {
return t;
}
Iterator<E> i = kids.iterator();
while (i.hasNext()) {
E future = i.next();
t = future.getCause();
if (t != null) {
/*
* If we found one then the listener hasn't been notified yet
*/
if (failFast) {
setFailure(t);
}
return t;
}
}
return null;
}
private interface CompositeTrue {
boolean isTrue(ChannelFuture f);
}
@Override
public boolean isSuccess() {
if (super.isSuccess()) {
return true;
}
boolean result = this.allTrue(new CompositeTrue() {
@Override
public boolean isTrue(ChannelFuture f) {
return f.isSuccess();
}
});
/*
* If true we know we are done and the listener just hasn't been
* notified yet to set this.setSuccess(). So we do this now. But it may
* have since been marked success sine we last check so make sure we
* still return true.
*/
// return result ? (super.setSuccess() || true) : false;
return result;
}
@Override
public boolean isDone() {
if (super.isDone()) {
return true;
}
return this.allTrue(new CompositeTrue() {
@Override
public boolean isTrue(ChannelFuture f) {
return f.isDone();
}
});
}
@Override
public String toString() {
StringBuffer s = new StringBuffer(super.toString());
s.append(" (");
s.append(futureStatus(this));
s.append(", " + kids.size() + " kids)");
for (E kid : kids) {
s.append("\n ");
s.append(kid.toString());
s.append(" (");
s.append(futureStatus(kid));
s.append(")");
}
return s.toString();
}
private static String futureStatus(ChannelFuture future) {
return future.isSuccess() ? "success"
: future.isCancelled() ? "cancelled"
: future.getCause() == null ? "incomplete"
: "failed - " + future.getCause();
}
private boolean allTrue(CompositeTrue predicate) {
/* An empty list should evaluate to false. Always. */
if (kids.isEmpty()) {
return false;
}
Iterator<E> i = kids.iterator();
while (i.hasNext()) {
E future = i.next();
if (!predicate.isTrue(future)) {
return false;
}
}
return true;
}
private class NotifyingListener implements ChannelFutureListener, ChannelFutureProgressListener {
@Override
public void operationProgressed(ChannelFuture future, long amount, long current, long total) throws Exception {
if (constructionFinished) {
setProgress(amount, current, total);
}
}
@Override
public void operationComplete(final ChannelFuture future) {
boolean isSuccess = future.isSuccess();
boolean isCancelled = future.isCancelled();
boolean failed = false;
/* We need to synchronize here due to the addChildren method */
synchronized (CompositeChannelFuture.this) {
if (CompositeChannelFuture.super.isDone()) {
// Then we must have failed fast.
return;
}
int currentUnnotified = unnotified.decrementAndGet();
if (isSuccess) {
successCount++;
} else if (isCancelled) {
cancelledCount++;
} else {
failed = true;
failedCount++;
}
// callSetDone = successCount + failureCount == futures.size();
if (currentUnnotified == 0 && constructionFinished) {
final int totalKids = kids.size();
if (totalKids == successCount) {
setSuccess();
} else if (totalKids == cancelledCount) {
if (!cancel()) {
if (!isCancelled()) {
// Then the composite was non-cancellable. Set to success
setSuccess();
}
}
} else {
for (E f : kids) {
Throwable t = f.getCause();
if (t != null) {
setFailure(t);
return;
}
}
}
} else if (failed && failFast && constructionFinished) {
setFailure(future.getCause());
}
}
}
}
}