/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.endpoint.tcp;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.Attribute;
import net.jxta.document.XMLElement;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.EndpointService;
import net.jxta.endpoint.MessageReceiver;
import net.jxta.endpoint.MessageSender;
import net.jxta.endpoint.Messenger;
import net.jxta.endpoint.MessengerEvent;
import net.jxta.endpoint.MessengerEventListener;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.ID;
import net.jxta.impl.endpoint.IPUtils;
import net.jxta.impl.endpoint.LoopbackMessenger;
import net.jxta.impl.endpoint.transportMeter.TransportBindingMeter;
import net.jxta.impl.endpoint.transportMeter.TransportMeter;
import net.jxta.impl.endpoint.transportMeter.TransportMeterBuildSettings;
import net.jxta.impl.endpoint.transportMeter.TransportServiceMonitor;
import net.jxta.impl.meter.MonitorManager;
import net.jxta.impl.peergroup.StdPeerGroup;
import net.jxta.impl.protocol.TCPAdv;
import net.jxta.impl.util.TimeUtils;
import net.jxta.logging.Logging;
import net.jxta.meter.MonitorResources;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.Module;
import net.jxta.protocol.ConfigParams;
import net.jxta.protocol.ModuleImplAdvertisement;
import net.jxta.protocol.TransportAdvertisement;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class implements the TCP Message Transport.
*
* @see net.jxta.endpoint.MessageTransport
* @see net.jxta.endpoint.MessagePropagater
* @see net.jxta.endpoint.MessageReceiver
* @see net.jxta.endpoint.MessageSender
* @see net.jxta.endpoint.EndpointService
* @see <a href="http://spec.jxta.org/v1.0/docbook/JXTAProtocols.html#trans-tcpipt">JXTA Protocols Specification : Standard JXTA Transport Bindings</a>
*/
public class TcpTransport implements Module, MessageSender, MessageReceiver {
/**
* Logger
*/
private static final Logger LOG = Logger.getLogger(TcpTransport.class.getName());
/**
* The TCP send buffer size.
* The size of the buffer used to store outgoing messages
* This should be set to the maximum message size (smaller is allowed).
*/
static final int SendBufferSize = 64 * 1024; // 64 KBytes
/**
* The TCP receive buffer size
*/
static final int RecvBufferSize = 64 * 1024; // 64 KBytes
/**
* Connection timeout
* use the same system property defined by URLconnection, otherwise default to 10 seconds.
*/
static int connectionTimeOut = 10 * (int) TimeUtils.ASECOND;
// Java's default is 50
static final int MaxAcceptCnxBacklog = 50;
private String serverName = null;
private final List<EndpointAddress> publicAddresses = new ArrayList<EndpointAddress>();
private EndpointAddress publicAddress = null;
private String interfaceAddressStr;
InetAddress usingInterface;
private int serverSocketPort;
private int restrictionPort = -1;
private IncomingUnicastServer unicastServer = null;
private boolean isClosed = false;
private long messagesSent = 0;
private long messagesReceived = 0;
private long bytesSent = 0;
private long bytesReceived = 0;
private long connectionsAccepted = 0;
PeerGroup group = null;
EndpointService endpoint = null;
Executor executor;
private String protocolName = "tcp";
private TransportMeter unicastTransportMeter;
private boolean publicAddressOnly = false;
private MessengerEventListener messengerEventListener = null;
private Thread messengerSelectorThread;
Selector messengerSelector = null;
private final Map<TcpMessenger, SocketChannel> regisMap = new ConcurrentHashMap<TcpMessenger, SocketChannel>();
private final Set<SocketChannel> unregisMap = Collections.synchronizedSet(new HashSet<SocketChannel>());
/**
* This is the thread group into which we will place all of the threads
* we create. THIS HAS NO EFFECT ON SCHEDULING. Java thread groups are
* only for organization and naming.
*/
ThreadGroup myThreadGroup = null;
/**
* The maximum number of write selectors we will maintain in our cache per
* transport instance.
*/
protected final static int MAX_WRITE_SELECTORS = 50;
/**
* A cache we maintain for selectors writing messages to the socket.
*/
private final static Stack<Selector> writeSelectorCache = new Stack<Selector>();
/**
* The number of excess write selectors believed to be in the pool.
*/
private int extraWriteSelectors = 0;
/**
* Construct a new TcpTransport instance
*/
public TcpTransport() {
// Add some selectors to the pool.
try {
for (int i = 0; i < MAX_WRITE_SELECTORS; i++) {
writeSelectorCache.add(Selector.open());
}
} catch (IOException ex) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Failed adding selector to write selector pool");
}
}
try {
String connectTOStr = System.getProperty("sun.net.client.defaultConnectTimeout");
if (connectTOStr != null) {
connectionTimeOut = Integer.parseInt(connectTOStr);
}
} catch (Exception e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Could not parse system property: sun.net.client.defaultConnectTimeout");
}
}
}
/**
* Gets the number of 'connectionsAccepted'.
*
* @return the number of 'connectionsAccepted'.
*/
public long getConnectionsAccepted() {
return connectionsAccepted;
}
/**
* increment the number of connectionsAccepted sent by 1
*/
public void incrementConnectionsAccepted() {
connectionsAccepted++;
}
/**
* increment the number of messages sent by 1
*/
public void incrementMessagesSent() {
messagesSent++;
}
/**
* increment the number of messages received by 1
*/
public void incrementMessagesReceived() {
messagesReceived++;
}
/**
* increment the number of bytes sent
*
* @param bytes the number of bytes to be added
*/
public void incrementBytesSent(long bytes) {
bytesSent += bytes;
}
/**
* increment the number of bytes received
*
* @param bytes the number of bytes to be added
*/
public void incrementBytesReceived(long bytes) {
bytesReceived += bytes;
}
/**
* Gets the number of 'messagesSent'.
*
* @return the number of 'messagesSent'.
*/
public long getMessagesSent() {
return messagesSent;
}
/**
* Gets the number of 'messagesReceived'.
*
* @return the number of 'messagesReceived'.
*/
public long getMessagesReceived() {
return messagesReceived;
}
/**
* Gets the number of 'bytesSent'.
*
* @return the number of 'bytesSent'.
*/
public long getBytesSent() {
return bytesSent;
}
/**
* Gets the number of 'bytesReceived'.
*
* @return the number of 'bytesReceived'.
*/
public long getBytesReceived() {
return bytesReceived;
}
/**
* {@inheritDoc}
*/
public boolean equals(Object target) {
if (this == target) {
return true;
}
if (null == target) {
return false;
}
if (target instanceof TcpTransport) {
TcpTransport likeMe = (TcpTransport) target;
if (!getProtocolName().equals(likeMe.getProtocolName())) {
return false;
}
Iterator<EndpointAddress> itsAddrs = likeMe.publicAddresses.iterator();
for (EndpointAddress publicAddress1 : publicAddresses) {
if (!itsAddrs.hasNext()) {
return false;
} // it has fewer than i do.
EndpointAddress mine = publicAddress1;
EndpointAddress its = itsAddrs.next();
if (!mine.equals(its)) {
// content didnt match
return false;
}
}
// ran out at the same time?
return (!itsAddrs.hasNext());
}
return false;
}
/**
* {@inheritDoc}
*/
public int hashCode() {
return getPublicAddress().hashCode();
}
/**
* {@inheritDoc}
*/
public void init(PeerGroup group, ID assignedID, Advertisement impl) throws PeerGroupException {
this.group = group;
ModuleImplAdvertisement implAdvertisement = (ModuleImplAdvertisement) impl;
this.executor = ((StdPeerGroup) group).getExecutor();
ConfigParams configAdv = group.getConfigAdvertisement();
// Get out invariable parameters from the implAdv
XMLElement param = (XMLElement) implAdvertisement.getParam();
if (param != null) {
Enumeration<XMLElement> list = param.getChildren("Proto");
if (list.hasMoreElements()) {
XMLElement pname = list.nextElement();
protocolName = pname.getTextValue();
}
}
// Get our peer-defined parameters in the configAdv
param = (XMLElement) configAdv.getServiceParam(assignedID);
if (null == param) {
throw new IllegalArgumentException(TransportAdvertisement.getAdvertisementType() + " could not be located.");
}
Enumeration<XMLElement> tcpChilds = param.getChildren(TransportAdvertisement.getAdvertisementType());
// get the TransportAdv
if (tcpChilds.hasMoreElements()) {
param = tcpChilds.nextElement();
Attribute typeAttr = param.getAttribute("type");
if (!TCPAdv.getAdvertisementType().equals(typeAttr.getValue())) {
throw new IllegalArgumentException("transport adv is not a " + TCPAdv.getAdvertisementType());
}
if (tcpChilds.hasMoreElements()) {
throw new IllegalArgumentException("Multiple transport advs detected for " + assignedID);
}
} else {
throw new IllegalArgumentException(TransportAdvertisement.getAdvertisementType() + " could not be located.");
}
Advertisement paramsAdv = null;
try {
paramsAdv = AdvertisementFactory.newAdvertisement(param);
} catch (NoSuchElementException notThere) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Could not find parameter document", notThere);
}
}
if (!(paramsAdv instanceof TCPAdv)) {
throw new IllegalArgumentException("Provided Advertisement was not a " + TCPAdv.getAdvertisementType());
}
TCPAdv adv = (TCPAdv) paramsAdv;
// determine the local interface to use. If the user specifies
// one, use that. Otherwise, use the all the available interfaces.
interfaceAddressStr = adv.getInterfaceAddress();
if (interfaceAddressStr != null) {
try {
usingInterface = InetAddress.getByName(interfaceAddressStr);
} catch (UnknownHostException failed) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Invalid address for local interface address, using default");
}
usingInterface = IPUtils.ANYADDRESS;
}
} else {
usingInterface = IPUtils.ANYADDRESS;
}
serverName = adv.getServer();
// Even when server is not enabled, we use the serverSocketPort as a
// discriminant for the simulated network partitioning, human readable
// messages, and a few things of that sort.
serverSocketPort = adv.getPort();
// should we expose other than a public address if one was specified?
publicAddressOnly = adv.getPublicAddressOnly();
// Start the servers
if (adv.isServerEnabled()) {
try {
unicastServer = new IncomingUnicastServer(this, usingInterface, serverSocketPort, adv.getStartPort(), adv.getEndPort());
} catch (IOException failed) {
throw new PeerGroupException("Failed to open server socket.", failed);
}
InetSocketAddress boundAddress = unicastServer.getLocalSocketAddress();
if(-1 != adv.getStartPort()) {
adv.setPort(boundAddress.getPort());
}
// Build the publicAddresses :
// first in the list is the "public server name". We don't try to
// resolve this since it might not be resolvable in the context we
// are running in, we just assume it's good.
if (serverName != null) {
// use speced server name.
EndpointAddress newAddr = new EndpointAddress(protocolName, serverName, null, null);
publicAddresses.add(newAddr);
}
// then add the rest of the local interfaces as appropriate. Unless
// we find an non-loopback interface, we're in local only mode.
boolean localOnly = true;
if (usingInterface.equals(IPUtils.ANYADDRESS)) {
// its wildcarded
List<EndpointAddress> wildAddrs = new ArrayList<EndpointAddress>();
for (InetAddress anAddress: IPUtils.getAllLocalAddresses()) {
String hostAddress = IPUtils.getHostAddress(anAddress);
EndpointAddress newAddr = new EndpointAddress(protocolName,
hostAddress + ":" + Integer.toString(boundAddress.getPort()), null, null);
// don't add it if its already in the list
if (!anAddress.isLoopbackAddress()) {
localOnly = false;
}
if (!publicAddresses.contains(newAddr)) {
wildAddrs.add(newAddr);
}
}
// we sort them so that later equals() will be deterministic.
// the result of IPUtils.getAllLocalAddresses() is not known to
// be sorted.
Collections.sort(wildAddrs, new Comparator<EndpointAddress>() {
public int compare(EndpointAddress one, EndpointAddress two) {
return one.toString().compareTo(two.toString());
}
public boolean equals(Object that) {
return (this == that);
}
});
// Add public addresses:
// don't add them if we have a hand-set public address and the
// publicAddressOnly property is set.
if (!(serverName != null && publicAddressOnly)) {
publicAddresses.addAll(wildAddrs);
}
} else {
// use specified interface
if (!usingInterface.isLoopbackAddress()) {
localOnly = false;
}
String hostAddress = IPUtils.getHostAddress(usingInterface);
EndpointAddress newAddr = new EndpointAddress(protocolName,
hostAddress + ":" + Integer.toString(boundAddress.getPort()), null, null);
// Add public address:
// don't add it if its already in the list
// don't add it if specified as public address and publicAddressOnly
if (!(serverName != null && publicAddressOnly)) {
if (!publicAddresses.contains(newAddr)) {
publicAddresses.add(newAddr);
}
}
}
// If the only available interface is LOOPBACK, then make sure we
// use only that (that includes resetting the outgoing/listening
// interface from ANYADDRESS to LOOPBACK).
if (localOnly) {
usingInterface = IPUtils.LOOPBACK;
publicAddresses.clear();
String hostAddress = IPUtils.getHostAddress(usingInterface);
EndpointAddress pubAddr = new EndpointAddress(protocolName,
hostAddress + ":" + Integer.toString(boundAddress.getPort()), null, null);
publicAddresses.add(pubAddr);
}
// Set the "preferred" public address. This is the address we will
// use for identifying outgoing requests.
publicAddress = publicAddresses.get(0);
} else {
// Only the outgoing interface matters.
// Verify that ANY interface does not in fact mean LOOPBACK only. If
// that's the case, we want to make that explicit, so that
// consistency checks regarding the allowed use of that interface
// work properly.
if (usingInterface.equals(IPUtils.ANYADDRESS)) {
boolean localOnly = true;
for (InetAddress anAddress : IPUtils.getAllLocalAddresses()) {
if (!anAddress.isLoopbackAddress()) {
localOnly = false;
break;
}
}
if (localOnly) {
usingInterface = IPUtils.LOOPBACK;
}
}
// The "public" address is just an internal label
// it is not usefull to anyone outside.
// IMPORTANT: we set the port to zero, to signify that this address
// is not realy usable.
String hostAddress = IPUtils.getHostAddress(usingInterface);
publicAddress = new EndpointAddress(protocolName, hostAddress + ":0", null, null);
}
// Tell tell the world about our configuration.
if (Logging.SHOW_CONFIG && LOG.isLoggable(Level.CONFIG)) {
StringBuilder configInfo = new StringBuilder("Configuring TCP Message Transport : " + assignedID);
if (implAdvertisement != null) {
configInfo.append("\n\tImplementation :");
configInfo.append("\n\t\tModule Spec ID: ").append(implAdvertisement.getModuleSpecID());
configInfo.append("\n\t\tImpl Description : ").append(implAdvertisement.getDescription());
configInfo.append("\n\t\tImpl URI : ").append(implAdvertisement.getUri());
configInfo.append("\n\t\tImpl Code : ").append(implAdvertisement.getCode());
}
configInfo.append("\n\tGroup Params:");
configInfo.append("\n\t\tGroup : ").append(group);
configInfo.append("\n\t\tPeer ID: ").append(group.getPeerID());
configInfo.append("\n\tConfiguration:");
configInfo.append("\n\t\tProtocol: ").append(protocolName);
configInfo.append("\n\t\tPublic address: ").append(serverName == null ? "(unspecified)" : serverName);
configInfo.append("\n\t\tInterface address: ").append(
interfaceAddressStr == null ? "(unspecified)" : interfaceAddressStr);
configInfo.append("\n\tConfiguration :");
configInfo.append("\n\t\tUsing Interface: ").append(usingInterface.getHostAddress());
if (null != unicastServer) {
if (-1 == unicastServer.getStartPort()) {
configInfo.append("\n\t\tUnicast Server Bind Addr: ").append(usingInterface.getHostAddress()).append(":").append(
serverSocketPort);
} else {
configInfo.append("\n\t\tUnicast Server Bind Addr: ").append(usingInterface.getHostAddress()).append(":").append(serverSocketPort).append(" [").append(unicastServer.getStartPort()).append("-").append(unicastServer.getEndPort()).append(
"]");
}
configInfo.append("\n\t\tUnicast Server Bound Addr: ").append(unicastServer.getLocalSocketAddress());
} else {
configInfo.append("\n\t\tUnicast Server : disabled");
}
configInfo.append("\n\t\tPublic Addresses: ");
configInfo.append("\n\t\t\tDefault Endpoint Addr : ").append(publicAddress);
for (EndpointAddress anAddr : publicAddresses) {
configInfo.append("\n\t\t\tEndpoint Addr : ").append(anAddr);
}
LOG.config(configInfo.toString());
}
}
/**
* {@inheritDoc}
*/
public synchronized int startApp(String[] arg) {
endpoint = group.getEndpointService();
if (null == endpoint) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Stalled until there is an endpoint service");
}
return Module.START_AGAIN_STALLED;
}
try {
messengerSelector = SelectorProvider.provider().openSelector();
} catch (IOException e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Could not create a messenger selector", e);
}
}
messengerSelectorThread = new Thread(group.getHomeThreadGroup(), new MessengerSelectorThread(), "TCP Transport MessengerSelectorThread for " + this);
messengerSelectorThread.setDaemon(true);
messengerSelectorThread.start();
// We're fully ready to function.
messengerEventListener = endpoint.addMessageTransport(this);
if (messengerEventListener == null) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Transport registration refused");
}
return -1;
}
// Cannot start before registration, we could be announcing new messengers while we
// do not exist yet ! (And get an NPE because we do not have the messenger listener set).
if (unicastServer != null) {
if (!unicastServer.start()) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Unable to start TCP Unicast Server");
}
return -1;
}
}
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
TransportServiceMonitor transportServiceMonitor = (TransportServiceMonitor) MonitorManager.getServiceMonitor(group,
MonitorResources.transportServiceMonitorClassID);
if (transportServiceMonitor != null) {
unicastTransportMeter = transportServiceMonitor.createTransportMeter("TCP", publicAddress);
}
}
isClosed = false;
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("TCP Message Transport started.");
}
return Module.START_OK;
}
/**
* {@inheritDoc}
*/
public synchronized void stopApp() {
if (isClosed) {
return;
}
isClosed = true;
if (unicastServer != null) {
unicastServer.stop();
unicastServer = null;
}
Thread temp = messengerSelectorThread;
if (null != temp) {
temp.interrupt();
try {
messengerSelector.close();
} catch (IOException failed) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "IO error occured while closing server socket", failed);
}
}
}
// Inform the pool that we don't need as many write selectors.
synchronized (writeSelectorCache) {
extraWriteSelectors += MAX_WRITE_SELECTORS;
}
endpoint.removeMessageTransport(this);
endpoint = null;
group = null;
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info(MessageFormat.format("Total bytes sent : {0}", getBytesSent()));
LOG.info(MessageFormat.format("Total Messages sent : {0}", getMessagesSent()));
LOG.info(MessageFormat.format("Total bytes received : {0}", getBytesReceived()));
LOG.info(MessageFormat.format("Total Messages received : {0}", getMessagesReceived()));
LOG.info(MessageFormat.format("Total connections accepted : {0}", getConnectionsAccepted()));
LOG.info("TCP Message Transport shut down.");
}
}
/**
* {@inheritDoc}
*/
public String getProtocolName() {
return protocolName;
}
/**
* {@inheritDoc}
*/
public EndpointAddress getPublicAddress() {
return publicAddress;
}
/**
* {@inheritDoc}
*/
public EndpointService getEndpointService() {
return endpoint;
}
/**
* {@inheritDoc}
*/
public Object transportControl(Object operation, Object Value) {
return null;
}
/**
* {@inheritDoc}
*/
public Iterator<EndpointAddress> getPublicAddresses() {
return Collections.unmodifiableList(publicAddresses).iterator();
}
/**
* {@inheritDoc}
*/
public boolean isConnectionOriented() {
return true;
}
/**
* {@inheritDoc}
*/
public boolean allowsRouting() {
return true;
}
public Messenger getMessenger(EndpointAddress dst, Object hintIgnored) {
return getMessenger(dst, hintIgnored, true);
}
/**
* Get a messenger instance for the specified destination endpoint address.
*
* @param dst destination for messages
* @param hintIgnored ignored
* @param selfDestruct indicates whether the messenger created will self
* destruct when idle
* @return Messenger instance, or null if no instance could be created
*/
public Messenger getMessenger(EndpointAddress dst, Object hintIgnored, boolean selfDestruct) {
if (!dst.getProtocolName().equalsIgnoreCase(getProtocolName())) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Cannot make messenger for protocol: " + dst.getProtocolName());
}
return null;
}
EndpointAddress plainAddr = new EndpointAddress(dst, null, null);
// If the destination is one of our addresses including loopback, we
// return a loopback messenger.
if (publicAddresses.contains(plainAddr)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("return LoopbackMessenger for addr : " + dst);
}
return new LoopbackMessenger(group, endpoint, getPublicAddress(), dst,
new EndpointAddress("jxta", group.getPeerID().getUniqueValue().toString(), null, null));
}
try {
// Right now we do not want to "announce" outgoing messengers because they get pooled and so must
// not be grabbed by a listener. If "announcing" is to be done, that should be by the endpoint
// and probably with a subtely different interface.
return new TcpMessenger(dst, this, selfDestruct);
} catch (Exception caught) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "Could not get messenger for " + dst, caught);
} else {
LOG.warning("Could not get messenger for " + dst + " : " + caught.getMessage());
}
}
if (caught instanceof RuntimeException) {
throw (RuntimeException) caught;
}
return null;
}
}
/**
* Getter for property 'restrictionPort'.
*
* @return Value for property 'restrictionPort'.
*/
int getRestrictionPort() {
return restrictionPort;
}
TransportBindingMeter getUnicastTransportBindingMeter(PeerID peerID, EndpointAddress destinationAddress) {
if (unicastTransportMeter != null) {
return unicastTransportMeter.getTransportBindingMeter(
(peerID != null) ? peerID.toString() : TransportMeter.UNKNOWN_PEER, destinationAddress);
} else {
return null;
}
}
void messengerReadyEvent(Messenger newMessenger, EndpointAddress connAddr) {
messengerEventListener.messengerReady(new MessengerEvent(this, newMessenger, connAddr));
}
/**
* Getter for property 'server'.
*
* @return Value for property 'server'.
*/
IncomingUnicastServer getServer() {
return unicastServer;
}
/**
* Get a write selector from the cache.
*
* @return A write selector.
* @throws InterruptedException If interrupted while waiting for a selector
* to become available.
*/
Selector getSelector() throws InterruptedException {
synchronized (writeSelectorCache) {
Selector selector = null;
try {
if (!writeSelectorCache.isEmpty()) {
selector = writeSelectorCache.pop();
}
} catch (EmptyStackException ese) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("No write selector available, waiting for one");
}
}
int attempts = 0;
while (selector == null && attempts < 2) {
writeSelectorCache.wait(connectionTimeOut);
try {
if (!writeSelectorCache.isEmpty()) {
selector = writeSelectorCache.pop();
}
} catch (EmptyStackException ese) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Failed to get a write selector available, waiting for one", ese);
}
}
attempts++;
}
return selector;
}
}
/**
* Return the <code>Selector</code> to the cache
*
* @param selector the selector to put back into the pool
*/
void returnSelector(Selector selector) {
synchronized (writeSelectorCache) {
if (extraWriteSelectors > 0) {
// Allow the selector to be discarded.
extraWriteSelectors--;
} else {
writeSelectorCache.push(selector);
// it does not hurt to notify, even if there are no waiters
writeSelectorCache.notify();
}
}
}
/**
* Waits for incoming data on channels and sends it to the appropriate
* messenger object.
*/
private class MessengerSelectorThread implements Runnable {
/**
* {@inheritDoc}
*/
public void run() {
try {
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("MessengerSelectorThread polling started");
}
while (!isClosed) {
try {
int selectedKeys = 0;
// Update channel registerations.
updateChannelRegisterations();
try {
// this can be interrupted through wakeup
selectedKeys = messengerSelector.select();
} catch (CancelledKeyException cke) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Key was cancelled", cke);
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(MessageFormat.format("MessengerSelector has {0} selected keys", selectedKeys));
}
if (selectedKeys == 0 && messengerSelector.selectNow() == 0) {
// We were probably just woken.
continue;
}
Set<SelectionKey> keySet = messengerSelector.selectedKeys();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(MessageFormat.format("KeySet has {0} selected keys", keySet.size()));
}
Iterator<SelectionKey> it = keySet.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// remove it from the SelectedKeys Set
it.remove();
if (key.isValid()) {
try {
if (key.isReadable() && key.channel().isOpen()) {
// ensure this channel is not selected again until the thread is done with it
// TcpMessenger is expected to reset the interestOps back to OP_READ
// Without this, expect multiple threads to execute on the same event, until
// the first thread completes reading all data available
key.interestOps(key.interestOps() & ~SelectionKey.OP_READ);
// get the messenger
TcpMessenger msgr = (TcpMessenger) key.attachment();
// process the data
try {
executor.execute(msgr);
} catch (RejectedExecutionException re) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE,
MessageFormat.format("Executor rejected task for messenger :{0}", msgr.toString()), re);
}
}
}
} catch (CancelledKeyException cce) {
//in case the key was canceled after the selection
}
} else {
// unregister it, no need to keep invalid/closed channels around
try {
key.channel().close();
} catch (IOException io) {
// avoids breaking out of the selector loop
}
key.cancel();
key = null;
}
}
} catch (ClosedSelectorException cse) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("IO Selector closed");
}
} catch (InterruptedIOException woken) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Thread inturrupted", woken);
}
} catch (IOException e1) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "An exception occurred while selecting keys", e1);
}
} catch (SecurityException e2) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "A security exception occurred while selecting keys", e2);
}
}
}
// XXX 20070205 bondolo What should we do about the channels
// that are still registered with the selector and any pending
// updates?
} catch (Throwable all) {
if (Logging.SHOW_SEVERE && Logging.SHOW_SEVERE) {
LOG.log(Level.SEVERE, "Uncaught Throwable", all);
}
} finally {
messengerSelectorThread = null;
}
}
}
/**
* Registers the channel with the Read selector and attaches the messenger to the channel
*
* @param channel the socket channel.
* @param messenger the messenger to attach to the channel.
*/
void register(SocketChannel channel, TcpMessenger messenger) {
regisMap.put(messenger, channel);
messengerSelector.wakeup();
}
/**
* Unregisters the channel with the Read selector
*
* @param channel the socket channel.
*/
void unregister(SocketChannel channel) {
unregisMap.add(channel);
messengerSelector.wakeup();
}
/**
* Registers all newly accepted and returned (by TcpMessenger) channels.
* Removes all closing TcpMessengers.
*/
private synchronized void updateChannelRegisterations() {
if (!regisMap.isEmpty() && Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(MessageFormat.format("Registering {0} channels with MessengerSelectorThread", regisMap.size()));
}
if (!regisMap.isEmpty()) {
Iterator<Map.Entry<TcpMessenger, SocketChannel>> eachMsgr = regisMap.entrySet().iterator();
while (eachMsgr.hasNext()) {
Map.Entry<TcpMessenger, SocketChannel> anEntry = eachMsgr.next();
TcpMessenger msgr = anEntry.getKey();
SocketChannel channel = anEntry.getValue();
SelectionKey key = channel.keyFor(messengerSelector);
try {
if (key == null) {
key = channel.register(messengerSelector, SelectionKey.OP_READ, msgr);
}
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(MessageFormat.format("Key interestOps on channel {0}, bit set :{1}", channel, key.interestOps()));
}
} catch (ClosedChannelException e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Failed to register Channel with messenger selector", e);
}
// it's best a new messenger is created when a new messenger is requested
msgr.close();
} catch (CancelledKeyException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Key is already cancelled, removing key from registeration map", e);
}
} catch (IllegalBlockingModeException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Invalid blocking channel mode, closing messenger", e);
}
// messenger state is unknown
msgr.close();
}
// remove it from the table
eachMsgr.remove();
}
}
// Unregister and close channels.
if (!unregisMap.isEmpty() && Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(MessageFormat.format("Unregistering {0} channels with MessengerSelectorThread", unregisMap.size()));
}
if (!unregisMap.isEmpty()) {
Iterator<SocketChannel> eachChannel;
synchronized (unregisMap) {
List<SocketChannel> allChannels = new ArrayList<SocketChannel>(unregisMap);
unregisMap.clear();
eachChannel = allChannels.iterator();
}
while (eachChannel.hasNext()) {
SocketChannel aChannel = eachChannel.next();
SelectionKey key = aChannel.keyFor(messengerSelector);
if (null != key) {
try {
key.cancel();
} catch (CancelledKeyException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Key is already cancelled, removing key from registeration map", e);
}
}
}
}
}
}
}