/*
* Copyright 2013 Thomas Bocek
*
* 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 net.tomp2p.connection;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GenericFutureListener;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceArray;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.Cancel;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FutureForkJoin;
import net.tomp2p.futures.FuturePing;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message;
import net.tomp2p.message.Message.Type;
import net.tomp2p.message.TomP2PCumulationTCP;
import net.tomp2p.message.TomP2POutbound;
import net.tomp2p.message.TomP2PSinglePacketUDP;
import net.tomp2p.p2p.builder.PingBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress;
import net.tomp2p.peers.PeerStatusListener;
import net.tomp2p.rpc.RPC;
import net.tomp2p.rpc.RPC.Commands;
import net.tomp2p.utils.Pair;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The class that sends out messages.
*
* @author Thomas Bocek
*
*/
public class Sender {
private static final Logger LOG = LoggerFactory.getLogger(Sender.class);
private final List<PeerStatusListener> peerStatusListeners;
private final ChannelClientConfiguration channelClientConfiguration;
private final Dispatcher dispatcher;
private final SendBehavior sendBehavior;
private final Random random;
// this map caches all messages which are meant to be sent by a reverse
// connection setup
private final ConcurrentHashMap<Integer, FutureResponse> cachedRequests = new ConcurrentHashMap<Integer, FutureResponse>();
private PingBuilderFactory pingBuilderFactory;
/**
* Creates a new sender with the listeners for offline peers.
*
* @param peerStatusListeners
* The listener for offline peers
* @param channelClientConfiguration
* The configuration used to get the signature factory
* @param dispatcher
* @param concurrentHashMap
*/
public Sender(final Number160 peerId, final List<PeerStatusListener> peerStatusListeners,
final ChannelClientConfiguration channelClientConfiguration, Dispatcher dispatcher, SendBehavior sendBehavior) {
this.peerStatusListeners = peerStatusListeners;
this.channelClientConfiguration = channelClientConfiguration;
this.dispatcher = dispatcher;
this.sendBehavior = sendBehavior;
this.random = new Random(peerId.hashCode());
}
public ChannelClientConfiguration channelClientConfiguration() {
return channelClientConfiguration;
}
public PingBuilderFactory pingBuilderFactory() {
return pingBuilderFactory;
}
public Sender pingBuilderFactory(PingBuilderFactory pingBuilderFactory) {
this.pingBuilderFactory = pingBuilderFactory;
return this;
}
/**
* Send a message via TCP.
*
* @param handler
* The handler to deal with a reply message
* @param futureResponse
* The future to set the response
* @param message
* The message to send
* @param channelCreator
* The channel creator for the UPD channel
* @param idleTCPSeconds
* The idle time of a message until we fail
* @param connectTimeoutMillis
* The idle we set for the connection setup
*/
public void sendTCP(final SimpleChannelInboundHandler<Message> handler, final FutureResponse futureResponse,
final Message message, final ChannelCreator channelCreator, final int idleTCPSeconds,
final int connectTimeoutMillis, final PeerConnection peerConnection) {
// no need to continue if we already finished
if (futureResponse.isCompleted()) {
return;
}
removePeerIfFailed(futureResponse, message);
final ChannelFuture channelFuture;
if (peerConnection != null && peerConnection.channelFuture() != null
&& peerConnection.channelFuture().channel().isActive()) {
channelFuture = sendTCPPeerConnection(peerConnection, handler, channelCreator, futureResponse);
afterConnect(futureResponse, message, channelFuture, handler == null);
} else if (channelCreator != null) {
final TimeoutFactory timeoutHandler = createTimeoutHandler(futureResponse, idleTCPSeconds, handler == null);
switch (sendBehavior.tcpSendBehavior(message)) {
case DIRECT:
connectAndSend(handler, futureResponse, channelCreator, connectTimeoutMillis, peerConnection, timeoutHandler, message);
break;
case RCON:
handleRcon(handler, futureResponse, message, channelCreator, connectTimeoutMillis, peerConnection, timeoutHandler);
break;
case RELAY:
handleRelay(handler, futureResponse, message, channelCreator, idleTCPSeconds, connectTimeoutMillis, peerConnection,
timeoutHandler);
break;
default:
throw new IllegalArgumentException("Illegal sending behavior");
}
}
}
/**
* This method initiates the reverse connection setup (or short: rconSetup).
* It creates a new Message and sends it via relay to the unreachable peer
* which then connects to this peer again. After the connectMessage from the
* unreachable peer this peer will send the original Message and its content
* directly.
*
* @param handler
* @param futureResponse
* @param message
* @param channelCreator
* @param connectTimeoutMillis
* @param peerConnection
* @param timeoutHandler
*/
private void handleRcon(final SimpleChannelInboundHandler<Message> handler, final FutureResponse futureResponse, final Message message,
final ChannelCreator channelCreator, final int connectTimeoutMillis, final PeerConnection peerConnection,
final TimeoutFactory timeoutHandler) {
message.keepAlive(true);
LOG.debug("initiate reverse connection setup to peer with peerAddress {}", message.recipient());
Message rconMessage = createRconMessage(message);
// cache the original message until the connection is established
cachedRequests.put(message.messageId(), futureResponse);
// wait for response (whether the reverse connection setup was successful)
final FutureResponse rconResponse = new FutureResponse(rconMessage);
SimpleChannelInboundHandler<Message> rconInboundHandler = new SimpleChannelInboundHandler<Message>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
if(msg.command() == Commands.RCON.getNr() && msg.type() == Type.OK) {
LOG.debug("Successfully set up the reverse connection to peer {}", message.recipient().peerId());
rconResponse.response(msg);
} else {
LOG.debug("Could not acquire a reverse connection to peer {}", message.recipient().peerId());
rconResponse.failed("Could not acquire a reverse connection");
futureResponse.failed(rconResponse.failedReason());
}
}
};
// send reverse connection request instead of normal message
sendTCP(rconInboundHandler, rconResponse, rconMessage, channelCreator, connectTimeoutMillis, connectTimeoutMillis, peerConnection);
}
/**
* This method makes a copy of the original Message and prepares it for
* sending it to the relay.
*
* @param message
* @return rconMessage
*/
private static Message createRconMessage(final Message message) {
// get Relay InetAddress from unreachable peer
Object[] relayInetAdresses = message.recipient().peerSocketAddresses().toArray();
PeerSocketAddress socketAddress = null;
if (relayInetAdresses.length > 0) {
// we should be fair and choose one of the relays randomly
socketAddress = (PeerSocketAddress) relayInetAdresses[Utils.randomPositiveInt(relayInetAdresses.length)];
} else {
throw new IllegalArgumentException(
"There are no PeerSocketAdresses available for this relayed Peer. This should not be possible!");
}
// we need to make a copy of the original message
Message rconMessage = new Message();
rconMessage.sender(message.sender());
rconMessage.version(message.version());
// store the message id in the payload to get the cached message later
rconMessage.intValue(message.messageId());
// the message must have set the keepAlive Flag true. If not, the relay
// peer will close the PeerConnection to the unreachable peer.
rconMessage.keepAlive(true);
// making the message ready to send
PeerAddress recipient = message.recipient().changeAddress(socketAddress.inetAddress())
.changePorts(socketAddress.tcpPort(), socketAddress.udpPort()).changeRelayed(false);
rconMessage.recipient(recipient);
rconMessage.command(RPC.Commands.RCON.getNr());
rconMessage.type(Message.Type.REQUEST_1);
return rconMessage;
}
/**
* This method is extracted by @author jonaswagner to ensure that no
* duplicate code exist.
*
* @param handler
* @param futureResponse
* @param channelCreator
* @param connectTimeoutMillis
* @param peerConnection
* @param timeoutHandler
* @param message
*/
private void connectAndSend(final SimpleChannelInboundHandler<Message> handler,
final FutureResponse futureResponse, final ChannelCreator channelCreator, final int connectTimeoutMillis,
final PeerConnection peerConnection, final TimeoutFactory timeoutHandler, final Message message) {
InetSocketAddress recipient = message.recipient().createSocketTCP();
final ChannelFuture channelFuture = sendTCPCreateChannel(recipient, channelCreator, peerConnection, handler,
timeoutHandler, connectTimeoutMillis, futureResponse);
afterConnect(futureResponse, message, channelFuture, handler == null);
}
/**
* Both peers are relayed, thus sending directly or over reverse connection
* is not possible. Send the message to one of the receiver's relays.
*
*
* @param handler
* @param futureResponse
* @param message
* @param channelCreator
* @param idleTCPSeconds
* @param connectTimeoutMillis
* @param peerConnection
* @param timeoutHandler
*/
private void handleRelay(final SimpleChannelInboundHandler<Message> handler, final FutureResponse futureResponse,
final Message message, final ChannelCreator channelCreator, final int idleTCPSeconds,
final int connectTimeoutMillis, final PeerConnection peerConnection, final TimeoutFactory timeoutHandler) {
FutureDone<PeerSocketAddress> futurePing = pingFirst(message.recipient().peerSocketAddresses());
futurePing.addListener(new BaseFutureAdapter<FutureDone<PeerSocketAddress>>() {
@Override
public void operationComplete(final FutureDone<PeerSocketAddress> futureDone) throws Exception {
if (futureDone.isSuccess()) {
InetSocketAddress recipient = PeerSocketAddress.createSocketTCP(futureDone.object());
ChannelFuture channelFuture = sendTCPCreateChannel(recipient, channelCreator, peerConnection,
handler, timeoutHandler, connectTimeoutMillis, futureResponse);
afterConnect(futureResponse, message, channelFuture, handler == null);
futureResponse.addListener(new BaseFutureAdapter<FutureResponse>() {
@Override
public void operationComplete(FutureResponse future) throws Exception {
if (future.isFailed()) {
if (future.responseMessage() != null && future.responseMessage().type() != Message.Type.DENIED) {
// remove the failed relay and try again
clearInactivePeerSocketAddress(futureDone);
sendTCP(handler, futureResponse, message, channelCreator, idleTCPSeconds,
connectTimeoutMillis, peerConnection);
}
}
}
private void clearInactivePeerSocketAddress(final FutureDone<PeerSocketAddress> futureDone) {
Collection<PeerSocketAddress> tmp = new ArrayList<PeerSocketAddress>();
for (PeerSocketAddress psa : message.recipient().peerSocketAddresses()) {
if (psa != null) {
if (!psa.equals(futureDone.object())) {
tmp.add(psa);
}
}
}
message.peerSocketAddresses(tmp);
}
});
} else {
futureResponse.failed("no relay could be contacted", futureDone);
}
}
});
}
/**
* Ping all relays of the receiver. The first one answering is picked as the responsible relay for this message.
*
* @param peerSocketAddresses a collection of relay addresses
* @return
*/
private FutureDone<PeerSocketAddress> pingFirst(Collection<PeerSocketAddress> peerSocketAddresses) {
final FutureDone<PeerSocketAddress> futureDone = new FutureDone<PeerSocketAddress>();
FuturePing[] forks = new FuturePing[peerSocketAddresses.size()];
int index = 0;
for (PeerSocketAddress psa : peerSocketAddresses) {
if (psa != null) {
InetSocketAddress inetSocketAddress = PeerSocketAddress.createSocketUDP(psa);
PingBuilder pingBuilder = pingBuilderFactory.create();
forks[index++] = pingBuilder.inetAddress(inetSocketAddress.getAddress())
.port(inetSocketAddress.getPort()).start();
}
}
FutureForkJoin<FuturePing> ffk = new FutureForkJoin<FuturePing>(1, true, new AtomicReferenceArray<FuturePing>(
forks));
ffk.addListener(new BaseFutureAdapter<FutureForkJoin<FuturePing>>() {
@Override
public void operationComplete(FutureForkJoin<FuturePing> future) throws Exception {
if (future.isSuccess()) {
futureDone.done(future.first().remotePeer().peerSocketAddress());
} else {
futureDone.failed(future);
}
}
});
return futureDone;
}
private ChannelFuture sendTCPCreateChannel(InetSocketAddress recipient, ChannelCreator channelCreator,
PeerConnection peerConnection, ChannelHandler handler, TimeoutFactory timeoutHandler,
int connectTimeoutMillis, FutureResponse futureResponse) {
final Map<String, Pair<EventExecutorGroup, ChannelHandler>> handlers;
if (timeoutHandler != null) {
handlers = new LinkedHashMap<String, Pair<EventExecutorGroup, ChannelHandler>>();
handlers.put("timeout0",
new Pair<EventExecutorGroup, ChannelHandler>(null, timeoutHandler.idleStateHandlerTomP2P()));
handlers.put("timeout1", new Pair<EventExecutorGroup, ChannelHandler>(null, timeoutHandler.timeHandler()));
} else {
handlers = new LinkedHashMap<String, Pair<EventExecutorGroup, ChannelHandler>>();
}
handlers.put("decoder", new Pair<EventExecutorGroup, ChannelHandler>(null, new TomP2PCumulationTCP(
channelClientConfiguration.signatureFactory())));
handlers.put("encoder", new Pair<EventExecutorGroup, ChannelHandler>(null, new TomP2POutbound(false,
channelClientConfiguration.signatureFactory())));
if (peerConnection != null) {
// we expect replies on this connection
handlers.put("dispatcher", new Pair<EventExecutorGroup, ChannelHandler>(null, dispatcher));
}
if (timeoutHandler != null) {
handlers.put("handler", new Pair<EventExecutorGroup, ChannelHandler>(null, handler));
}
HeartBeat heartBeat = null;
if (peerConnection != null) {
heartBeat = new HeartBeat(peerConnection.heartBeatMillis(), TimeUnit.MILLISECONDS, pingBuilderFactory);
handlers.put("heartbeat", new Pair<EventExecutorGroup, ChannelHandler>(null, heartBeat));
}
InetSocketAddress reflectedRecipient = Utils.natReflection(recipient, false, dispatcher.peerBean().serverPeerAddress());
ChannelFuture channelFuture = channelCreator.createTCP(reflectedRecipient, connectTimeoutMillis, handlers, futureResponse);
if (peerConnection != null && channelFuture != null) {
peerConnection.channelFuture(channelFuture);
heartBeat.peerConnection(peerConnection);
}
return channelFuture;
}
private ChannelFuture sendTCPPeerConnection(PeerConnection peerConnection, ChannelHandler handler,
final ChannelCreator channelCreator, final FutureResponse futureResponse) {
// if the channel gets closed, the future should get notified
ChannelFuture channelFuture = peerConnection.channelFuture();
// channelCreator can be null if we don't need to create any channels
if (channelCreator != null) {
channelCreator.setupCloseListener(channelFuture, futureResponse);
}
ChannelPipeline pipeline = channelFuture.channel().pipeline();
// we need to replace the handler if this comes from the peer that
// create a peerConnection, otherwise we
// need to add a handler
addOrReplace(pipeline, "dispatcher", "handler", handler);
// uncomment this if the recipient should also heartbeat
// addIfAbsent(pipeline, "handler", "heartbeat",
// new HeartBeat(2, pingBuilder).peerConnection(peerConnection));
return channelFuture;
}
// private boolean addIfAbsent(ChannelPipeline pipeline, String before,
// String name,
// ChannelHandler channelHandler) {
// List<String> names = pipeline.names();
// if (names.contains(name)) {
// return false;
// } else {
// if (before == null) {
// pipeline.addFirst(name, channelHandler);
// } else {
// pipeline.addBefore(before, name, channelHandler);
// }
// return true;
// }
// }
private boolean addOrReplace(ChannelPipeline pipeline, String before, String name, ChannelHandler channelHandler) {
List<String> names = pipeline.names();
if (names.contains(name)) {
pipeline.replace(name, name, channelHandler);
return false;
} else {
if (before == null) {
pipeline.addFirst(name, channelHandler);
} else {
pipeline.addBefore(before, name, channelHandler);
}
return true;
}
}
/**
* Send a message via UDP.
*
* @param handler
* The handler to deal with a reply message
* @param futureResponse
* The future to set the response
* @param message
* The message to send
* @param channelCreator
* The channel creator for the UPD channel
* @param idleUDPSeconds
* The idle time of a message until we fail
* @param broadcast
* True to send via layer 2 broadcast
*/
// TODO: if message.getRecipient() is me, than call dispatcher directly
// without sending over Internet.
public void sendUDP(final SimpleChannelInboundHandler<Message> handler, final FutureResponse futureResponse,
final Message message, final ChannelCreator channelCreator, final int idleUDPSeconds,
final boolean broadcast) {
// no need to continue if we already finished
if (futureResponse.isCompleted()) {
return;
}
removePeerIfFailed(futureResponse, message);
boolean isFireAndForget = handler == null;
final Map<String, Pair<EventExecutorGroup, ChannelHandler>> handlers;
if (isFireAndForget) {
final int nrTCPHandlers = 3; // 2 / 0.75
handlers = new LinkedHashMap<String, Pair<EventExecutorGroup, ChannelHandler>>(nrTCPHandlers);
} else {
final int nrTCPHandlers = 7; // 5 / 0.75
handlers = new LinkedHashMap<String, Pair<EventExecutorGroup, ChannelHandler>>(nrTCPHandlers);
final TimeoutFactory timeoutHandler = createTimeoutHandler(futureResponse, idleUDPSeconds, isFireAndForget);
handlers.put("timeout0",
new Pair<EventExecutorGroup, ChannelHandler>(null, timeoutHandler.idleStateHandlerTomP2P()));
handlers.put("timeout1", new Pair<EventExecutorGroup, ChannelHandler>(null, timeoutHandler.timeHandler()));
}
handlers.put("decoder", new Pair<EventExecutorGroup, ChannelHandler>(null, new TomP2PSinglePacketUDP(
channelClientConfiguration.signatureFactory())));
handlers.put("encoder", new Pair<EventExecutorGroup, ChannelHandler>(null, new TomP2POutbound(false,
channelClientConfiguration.signatureFactory())));
if (!isFireAndForget) {
handlers.put("handler", new Pair<EventExecutorGroup, ChannelHandler>(null, handler));
}
try {
final ChannelFuture channelFuture;
switch (sendBehavior.udpSendBehavior(message)) {
case DIRECT:
channelFuture = channelCreator.createUDP(broadcast, handlers, futureResponse);
break;
case RELAY:
List<PeerSocketAddress> psa = new ArrayList<>(message.recipient().peerSocketAddresses());
LOG.debug("send neighbor request to random relay peer {}", psa);
if (psa.size() > 0) {
PeerSocketAddress ps = psa.get(random.nextInt(psa.size()));
message.recipientRelay(message.recipient().changePeerSocketAddress(ps).changeRelayed(true));
channelFuture = channelCreator.createUDP(broadcast, handlers, futureResponse);
} else {
futureResponse.failed("Peer is relayed, but no relay given");
return;
}
break;
default:
throw new IllegalArgumentException("UDP messages are not allowed to send over RCON");
}
afterConnect(futureResponse, message, channelFuture, handler == null);
} catch (UnsupportedOperationException e) {
LOG.warn(e.getMessage());
futureResponse.failed(e);
}
}
/**
* Create a timeout handler or null if its a fire and forget. In this case
* we don't expect a reply and we don't need a timeout.
*
* @param futureResponse
* The future to set the response
* @param idleMillis
* The timeout
* @param fireAndForget
* True, if we don't expect a message
* @return The timeout creator that will create timeout handlers
*/
private TimeoutFactory createTimeoutHandler(final FutureResponse futureResponse, final int idleMillis,
final boolean fireAndForget) {
return fireAndForget ? null : new TimeoutFactory(futureResponse, idleMillis, peerStatusListeners, "Sender");
}
/**
* After connecting, we check if the connect was successful.
*
* @param futureResponse
* The future to set the response
* @param message
* The message to send
* @param channelFuture
* the future of the connect
* @param fireAndForget
* True, if we don't expect a message
*/
private void afterConnect(final FutureResponse futureResponse, final Message message,
final ChannelFuture channelFuture, final boolean fireAndForget) {
if (channelFuture == null) {
futureResponse.failed("could not create a " + (message.isUdp() ? "UDP" : "TCP") + " channel");
return;
}
LOG.debug("about to connect to {} with channel {}, ff={}", message.recipient(), channelFuture.channel(), fireAndForget);
final Cancel connectCancel = createCancel(channelFuture);
futureResponse.addCancel(connectCancel);
channelFuture.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(final ChannelFuture future) throws Exception {
futureResponse.removeCancel(connectCancel);
if (future.isSuccess()) {
futureResponse.progressHandler(new ProgresHandler() {
@Override
public void progres() {
final ChannelFuture writeFuture = future.channel().writeAndFlush(message);
afterSend(writeFuture, futureResponse, fireAndForget);
}
});
// this needs to be called first before all other progress
futureResponse.progressFirst();
} else {
LOG.debug("Channel creation failed", future.cause());
futureResponse.failed("Channel creation failed " + future.channel() + "/" + future.cause());
// may have been closed by the other side,
// or it may have been canceled from this side
if (!(future.cause() instanceof CancellationException)
&& !(future.cause() instanceof ClosedChannelException)
&& !(future.cause() instanceof ConnectException)) {
LOG.warn("Channel creation failed to {} for {}", future.channel(), message);
}
}
}
});
}
/**
* After sending, we check if the write was successful or if it was a fire
* and forget.
*
* @param writeFuture
* The future of the write operation. Can be UDP or TCP
* @param futureResponse
* The future to set the response
* @param fireAndForget
* True, if we don't expect a message
*/
private void afterSend(final ChannelFuture writeFuture, final FutureResponse futureResponse,
final boolean fireAndForget) {
final Cancel writeCancel = createCancel(writeFuture);
writeFuture.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(final ChannelFuture future) throws Exception {
futureResponse.removeCancel(writeCancel);
if (!future.isSuccess()) {
futureResponse.failedLater(future.cause());
reportFailed(futureResponse, future.channel().close());
LOG.warn("Failed to write channel the request {} {}", futureResponse.request(), future.cause());
}
if (fireAndForget) {
futureResponse.responseLater(null);
LOG.debug("fire and forget, close channel now {}, {}", futureResponse.request(), future.channel());
reportMessage(futureResponse, future.channel().close());
}
}
});
}
/**
* Report a failure after the channel was closed.
*
* @param futureResponse
* The future to set the response
* @param close
* The close future
*/
private void reportFailed(final FutureResponse futureResponse, final ChannelFuture close) {
close.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(final ChannelFuture arg0) throws Exception {
futureResponse.responseNow();
}
});
}
/**
* Report a successful response after the channel was closed.
*
* @param futureResponse
* The future to set the response
* @param close
* The close future
*/
private void reportMessage(final FutureResponse futureResponse, final ChannelFuture close) {
close.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(final ChannelFuture arg0) throws Exception {
futureResponse.responseNow();
}
});
}
/**
* @param channelFuture
* The channel future that can be canceled
* @return Create a cancel class for the channel future
*/
private static Cancel createCancel(final ChannelFuture channelFuture) {
return new Cancel() {
@Override
public void cancel() {
channelFuture.cancel(true);
}
};
}
private void removePeerIfFailed(final FutureResponse futureResponse, final Message message) {
futureResponse.addListener(new BaseFutureAdapter<FutureResponse>() {
@Override
public void operationComplete(FutureResponse future) throws Exception {
if (future.isFailed()) {
if (message.recipient().isRelayed()) {
// TODO: make the relay go away if failed
} else {
synchronized (peerStatusListeners) {
for (PeerStatusListener peerStatusListener : peerStatusListeners) {
peerStatusListener.peerFailed(message.recipient(), new PeerException(future));
}
}
}
}
}
});
}
/**
* Get currently cached requests. They are cached because for example the receiver is behind a NAT. Instead
* of sending the message directly, a reverse connection is set up beforehand. After a successful connection
* establishment, the cached messages are sent through the direct channel.
*/
public ConcurrentHashMap<Integer, FutureResponse> cachedRequests() {
return cachedRequests;
}
}