package net.jxta.impl.endpoint.netty;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
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.MessageReceiver;
import net.jxta.endpoint.MessengerEvent;
import net.jxta.endpoint.MessengerEventListener;
import net.jxta.exception.PeerGroupException;
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.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineCoverage;
import org.jboss.netty.channel.ChildChannelStateEvent;
import org.jboss.netty.channel.ServerChannelFactory;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
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;
/**
* The server side of a netty based transport. Responsible for accepting remote connections and making
* the endpoint service aware of these asynchronously.
*
* @author Iain McGinniss (iain.mcginniss@onedrum.com)
*/
public class NettyTransportServer implements NettyChannelRegistry, MessageReceiver, TransportServerComponent {
private static final Logger LOG = Logger.getLogger(NettyTransportServer.class.getName());
private ServerBootstrap serverBootstrap;
private Channel serverChannel;
private EndpointService endpointService;
private AtomicBoolean started = new AtomicBoolean(false);
private PeerGroupID homeGroupID;
private PeerID localPeerID;
private MessengerEventListener listener;
private List<EndpointAddress> publicAddresses;
private AddressTranslator addrTranslator;
private ChannelGroup channels;
private HashedWheelTimer timeoutTimer;
private ChannelGroupFuture closeChannelsFuture;
private List<EndpointAddress> boundAddresses;
public NettyTransportServer(ServerChannelFactory factory, AddressTranslator addrTranslator, final PeerGroup group) {
this.channels = new DefaultChannelGroup();
this.homeGroupID = group.getPeerGroupID();
this.localPeerID = group.getPeerID();
this.addrTranslator = addrTranslator;
serverBootstrap = new ServerBootstrap(factory);
serverBootstrap.setParentHandler(new ConnectionGroupAddHandler());
timeoutTimer = new HashedWheelTimer();
}
public void init(List<? extends SocketAddress> potentialBindpoints, EndpointAddress publicAddress, boolean usePublicOnly) throws PeerGroupException {
serverBootstrap.setPipelineFactory(new NettyTransportChannelPipelineFactory(localPeerID, timeoutTimer, this, addrTranslator, started, null, publicAddress));
SocketAddress chosenAddress = bindServerChannel(potentialBindpoints);
boundAddresses = Collections.unmodifiableList(addrTranslator.translateToExternalAddresses(chosenAddress));
if(serverChannel == null) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Failed to bind to any of the addresses in the configured range");
}
throw new PeerGroupException("Failed to bind to any address in the configured range");
}
if(usePublicOnly) {
if(publicAddress == null) {
if(Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Instructed to use public address only, but no public address specified! Using all bound addresses instead");
}
publicAddresses = new ArrayList<EndpointAddress>(boundAddresses);
} else {
publicAddresses = new ArrayList<EndpointAddress>(1);
publicAddresses.add(publicAddress);
}
} else {
int size = boundAddresses.size() + ((publicAddress != null) ? 1 : 0);
publicAddresses = new ArrayList<EndpointAddress>(size);
if(publicAddress != null) {
publicAddresses.add(publicAddress);
}
publicAddresses.addAll(boundAddresses);
}
}
private SocketAddress bindServerChannel(List<? extends SocketAddress> potentialBindpoints) {
for(SocketAddress nextBP : potentialBindpoints) {
try {
serverChannel = serverBootstrap.bind(nextBP);
channels.add(serverChannel);
return nextBP;
} catch(ChannelException e) {
if(Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
String failReason = (e.getCause() != null) ? e.getCause().getMessage() : e.getMessage();
LOG.log(Level.INFO, "Attempt to bind to " + nextBP + " failed (" + failReason + "), trying another address");
}
}
}
return null;
}
public boolean start(EndpointService endpointService) throws IllegalStateException {
if(started.get()) {
throw new IllegalStateException("already started");
}
this.endpointService = endpointService;
listener = endpointService.addMessageTransport(this);
if(listener == null) {
if(Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Transport registration failed for netty transport server, protocol=" + addrTranslator.getProtocolName());
}
return false;
}
started.set(true);
return true;
}
public void beginStop() {
if(!started.compareAndSet(true, false)) {
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();
}
public void stop() throws IllegalStateException {
if(closeChannelsFuture != null) {
closeChannelsFuture.awaitUninterruptibly();
}
serverChannel = null;
serverBootstrap.releaseExternalResources();
timeoutTimer.stop();
}
public void newConnection(Channel channel, EndpointAddress directedAt, EndpointAddress logicalEndpointAddress) {
// EndpointAddress localAddr = addrTranslator.toEndpointAddress(channel.getLocalAddress(), serverChannel.getLocalAddress());
NettyMessenger messenger = new NettyMessenger(channel, homeGroupID, localPeerID, directedAt, logicalEndpointAddress, endpointService);
listener.messengerReady(new MessengerEvent(this, messenger, messenger.getDestinationAddress()));
}
public Iterator<EndpointAddress> getPublicAddresses() {
return publicAddresses.iterator();
}
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");
}
@ChannelPipelineCoverage("all")
private final class ConnectionGroupAddHandler extends SimpleChannelUpstreamHandler {
@Override
public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception {
if(Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
String logMessage = String.format("Incoming connection for transport %s from %s to %s (handled by %s)",
getProtocolName(),
e.getChildChannel().getRemoteAddress(),
e.getChildChannel().getLocalAddress(),
ctx.getChannel().getLocalAddress());
LOG.log(Level.FINE, logMessage);
}
channels.add(e.getChildChannel());
super.childChannelOpen(ctx, e);
}
}
public boolean isStarted() {
return started.get();
}
/**
* @return the physically bound addresses for this transport, as opposed to those which are
* broadcasted to external peers.
*/
public List<EndpointAddress> getBoundAddresses() {
return boundAddresses;
}
}