package net.jxta.impl.endpoint.netty;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.EndpointService;
import net.jxta.endpoint.MessageSender;
import net.jxta.endpoint.Messenger;
import net.jxta.endpoint.MessengerEventListener;
import net.jxta.logging.Logging;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.util.HashedWheelTimer;
/**
* Client side of a netty based transport. Responsible for initiating outgoing connections.
*
* @author Iain McGinniss (iain.mcginniss@onedrum.com)
*/
public class NettyTransportClient implements MessageSender, TransportClientComponent {
private final class ClientConnectionRegistrationHandler implements NettyChannelRegistry {
public EndpointAddress directedAt;
public EndpointAddress logicalEndpointAddress;
public CountDownLatch latch = new CountDownLatch(1);
public void newConnection(Channel channel, EndpointAddress directedAt, EndpointAddress logicalEndpointAddress) {
this.directedAt = directedAt;
this.logicalEndpointAddress = logicalEndpointAddress;
latch.countDown();
}
}
private static final Logger LOG = Logger.getLogger(NettyTransportClient.class.getName());
private EndpointAddress localAddress;
private PeerGroupID homeGroupID;
private PeerID localPeerID;
private EndpointService endpointService;
private MessengerEventListener messageEventListener;
private AddressTranslator addrTranslator;
private ChannelGroup channels;
private HashedWheelTimer timeoutTimer;
private ChannelGroupFuture closeChannelsFuture;
private AtomicBoolean started;
private AtomicBoolean stopping;
private ChannelFactory clientFactory;
private EndpointAddress returnAddress;
public NettyTransportClient(ChannelFactory clientFactory, AddressTranslator addrTranslator, PeerGroup group, EndpointAddress returnAddress) {
this.started = new AtomicBoolean(false);
this.stopping = new AtomicBoolean(false);
this.channels = new DefaultChannelGroup();
this.addrTranslator = addrTranslator;
this.clientFactory = clientFactory;
this.returnAddress = returnAddress;
localAddress = returnAddress;
this.homeGroupID = group.getPeerGroupID();
this.localPeerID = group.getPeerID();
timeoutTimer = new HashedWheelTimer();
}
public boolean start(EndpointService endpointService) {
this.endpointService = endpointService;
messageEventListener = endpointService.addMessageTransport(this);
if(messageEventListener == null) {
return false;
}
started.set(true);
return true;
}
public void beginStop() {
if(!started.get()) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Netty transport server for protocol " + addrTranslator.getProtocolName() + " already stopped or never started!");
}
return;
}
closeChannelsFuture = channels.close();
stopping.set(true);
}
public void stop() {
if(!stopping.get()) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Netty transport server for protocol " + addrTranslator.getProtocolName() + " already stopped or never started!");
}
return;
}
closeChannelsFuture.awaitUninterruptibly();
clientFactory.releaseExternalResources();
timeoutTimer.stop();
endpointService.removeMessageTransport(this);
endpointService = null;
}
public Messenger getMessenger(EndpointAddress dest, Object hint) {
if(!started.get()) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Request to get messenger for " + dest.toString() + " when netty transport client stopped or never started");
}
return null;
}
if(Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO, "processing request to open connection to {0}", dest);
}
ClientConnectionRegistrationHandler clientRegistry = new ClientConnectionRegistrationHandler();
ClientBootstrap bootstrap = new ClientBootstrap(clientFactory);
bootstrap.setPipelineFactory(new NettyTransportChannelPipelineFactory(localPeerID, timeoutTimer, clientRegistry, addrTranslator, dest, returnAddress));
ChannelFuture connectFuture = bootstrap.connect(addrTranslator.toSocketAddress(dest));
try {
if(!connectFuture.await(5000L, TimeUnit.MILLISECONDS)) {
if(Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO, "Netty transport for protocol {0} failed to connect to {1} within acceptable time",
new Object[] { addrTranslator.getProtocolName(), dest });
}
return null;
}
} catch(InterruptedException e) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Interrupted while waiting for connection to {0} to be established", dest);
}
connectFuture.cancel();
return null;
}
if(!connectFuture.isSuccess()) {
if(Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
Throwable cause = connectFuture.getCause();
String causeString = (cause != null) ? cause.getMessage() : "cause unknown";
String message = String.format("Netty transport for protocol %s failed to connect to %s - %s", addrTranslator.getProtocolName(), dest, causeString);
LOG.log(Level.INFO, message);
}
return null;
}
boolean established = false;
try {
established = clientRegistry.latch.await(15L, TimeUnit.SECONDS);
} catch(InterruptedException e) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Interrupted while waiting for connection handover", e);
}
}
if(!established) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Connection handover timed out - either remote host was not a valid JXTA peer or did not respond on time");
}
connectFuture.getChannel().close();
return null;
}
if(Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO, "succeeded in connecting to {0}, remote peer has logical address {1}", new Object[] { dest, clientRegistry.logicalEndpointAddress });
}
channels.add(connectFuture.getChannel());
return new NettyMessenger(connectFuture.getChannel(), homeGroupID, localPeerID, clientRegistry.directedAt, clientRegistry.logicalEndpointAddress, endpointService);
}
public boolean allowsRouting() {
return true;
}
public EndpointAddress getPublicAddress() {
return localAddress;
}
public boolean isConnectionOriented() {
return true;
}
@Deprecated
public boolean ping(EndpointAddress addr) {
throw new RuntimeException("ping is deprecated, do not use!");
}
public EndpointService getEndpointService() {
return endpointService;
}
public String getProtocolName() {
return addrTranslator.getProtocolName();
}
@Deprecated
public Object transportControl(Object operation, Object value) {
throw new RuntimeException("transportControl is deprecated, do not use!");
}
}