package jadex.standalone.transport.niotcpmtp;
import jadex.bridge.ComponentIdentifier;
import jadex.bridge.IComponentIdentifier;
import jadex.bridge.IMessageService;
import jadex.commons.SUtil;
import jadex.commons.collection.ILRUEntryCleaner;
import jadex.commons.collection.LRU;
import jadex.commons.collection.MultiCollection;
import jadex.commons.collection.SCollection;
import jadex.commons.concurrent.DefaultResultListener;
import jadex.commons.service.IServiceProvider;
import jadex.commons.service.SServiceProvider;
import jadex.commons.service.clock.IClockService;
import jadex.commons.service.clock.ITimedObject;
import jadex.commons.service.clock.ITimer;
import jadex.commons.service.library.ILibraryService;
import jadex.commons.service.threadpool.IThreadPoolService;
import jadex.standalone.transport.ITransport;
import jadex.standalone.transport.MessageEnvelope;
import jadex.standalone.transport.codecs.CodecFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
/**
* The tcp transport for sending messages over
* tcp/ip connections. Initiates one receiving
* tcp/ip port under the specified settings and
* opens outgoing connections for all remote
* platforms on demand.
*
* For the receiving side a separate listener
* thread is necessary as it must be continuously
* listened for incoming transmission requests.
*/
public class NIOTCPTransport implements ITransport
{
//-------- constants --------
/** The schema name. */
public static final String SCHEMA = "nio-mtp://";
/** How long to keep connections alive (5 min). */
protected static final int MAX_KEEPALIVE = 300000;
/** The prolog size. */
protected static final int PROLOG_SIZE = 5;
/** Maximum number of outgoing connections */
protected static final int MAX_CONNECTIONS = 20;
/** Default port. */
protected static final int DEFAULT_PORT = 8765;
//-------- attributes --------
/** The platform. */
protected IServiceProvider container;
/** The addresses. */
protected String[] addresses;
/** The port. */
protected int port;
/** The server socket for receiving messages. */
protected ServerSocketChannel ssc;
/** The selector for fetching new incoming requests. */
protected Selector selector;
/** The opened connections for addresses. (aid address -> connection). */
protected Map connections;
/** The codec factory. */
protected CodecFactory codecfac;
/** The logger. */
protected Logger logger;
/** The library service. */
protected ILibraryService libservice;
//-------- constructors --------
/**
* Init the transport.
* @param platform The platform.
* @param settings The settings.
*/
public NIOTCPTransport(final IServiceProvider container, int port)
{
this.logger = Logger.getLogger("NIOTCPTransport" + this);
this.codecfac = new CodecFactory();
this.container = container;
this.port = port;
// Set up sending side.
this.connections = SCollection.createLRU(MAX_CONNECTIONS);
((LRU)this.connections).setCleaner(new ILRUEntryCleaner()
{
public void cleanupEldestEntry(Entry eldest)
{
Object con = eldest.getValue();
if(con instanceof NIOTCPOutputConnection)
{
((NIOTCPOutputConnection)con).close();
}
}
});
}
/**
* Start the transport.
*/
public void start()
{
try
{
// Set up receiver side.
// If port==0 -> any free port
this.ssc = ServerSocketChannel.open();
this.ssc.configureBlocking(false);
ServerSocket serversocket = ssc.socket();
serversocket.bind(new InetSocketAddress(port));
this.port = serversocket.getLocalPort();
this.selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
// Determine all transport addresses.
InetAddress iaddr = InetAddress.getLocalHost();
String lhostname = iaddr.getHostName().toLowerCase();
InetAddress[] laddrs = InetAddress.getAllByName(lhostname);
Set addrs = new HashSet();
addrs.add(getAddress(iaddr.getHostAddress(), this.port));
// Get the ip addresses
for(int i=0; i<laddrs.length; i++)
{
String hostname = laddrs[i].getHostName().toLowerCase();
String ip_addr = laddrs[i].getHostAddress();
addrs.add(getAddress(ip_addr, this.port));
if(!ip_addr.equals(hostname))
{
// We have a fully qualified domain name.
addrs.add(getAddress(hostname, this.port));
}
}
addresses = (String[])addrs.toArray(new String[addrs.size()]);
// Start receiver thread.
SServiceProvider.getService(container, ILibraryService.class).addResultListener(new DefaultResultListener()
{
public void resultAvailable(Object source, Object result)
{
libservice = (ILibraryService)result;
SServiceProvider.getService(container, IThreadPoolService.class).addResultListener(new DefaultResultListener()
{
public void resultAvailable(Object source, Object result)
{
IThreadPoolService tp = (IThreadPoolService)result;
tp.execute(new Runnable()
{
public void run()
{
while(ssc.isOpen())
{
// This is a blocking call that only returns when traffic occurs.
Iterator it = null;
try
{
selector.select();
it = selector.selectedKeys().iterator();
}
catch(IOException e)
{
// logger.warning("NIOTCP selector error.");
//e.printStackTrace();
}
while(it!=null && it.hasNext())
{
// Get the selection key
final SelectionKey key = (SelectionKey)it.next();
// Remove it from the list to indicate that it is being processed
it.remove();
if(key.isValid() && key.isAcceptable())
{
try
{
// Returns only null if no connection request is available.
SocketChannel sc = ssc.accept();
if(sc!=null)
{
sc.configureBlocking(false);
// ClassLoader cl = ((ILibraryService)container.getService(ILibraryService.class)).getClassLoader();
sc.register(selector, SelectionKey.OP_READ, new NIOTCPInputConnection(sc, codecfac, libservice.getClassLoader()));
}
}
catch(IOException e)
{
// logger.warning("NIOTCP connection error on receiver side.");
//e.printStackTrace();
key.cancel();
}
}
else if(key.isValid() && key.isReadable())
{
final NIOTCPInputConnection con = (NIOTCPInputConnection)key.attachment();
SServiceProvider.getService(container, IMessageService.class).addResultListener(new DefaultResultListener()
{
public void resultAvailable(Object source, Object result)
{
try
{
IMessageService ms = (IMessageService)result;
for(MessageEnvelope msg=con.read(); msg!=null; msg=con.read())
{
ms.deliverMessage(msg.getMessage(), msg.getTypeName(), msg.getReceivers());
}
}
catch(IOException e)
{
// logger.warning("NIOTCP receiving error while reading data.");
// e.printStackTrace();
con.close();
key.cancel();
}
}
});
}
else
{
key.cancel();
}
}
}
// logger.info("TCPNIO receiver closed.");
}
});
}
});
}
});
//platform.getLogger().info("Local address: "+getServiceSchema()+lhostname+":"+listen_port);
}
catch(Exception e)
{
//e.printStackTrace();
throw new RuntimeException("Transport initialization error: "+e.getMessage());
}
}
/**
* Perform cleanup operations (if any).
*/
public void shutdown()
{
try{this.ssc.close();}catch(Exception e){}
connections = null; // Help gc
}
//-------- methods --------
/**
* Send a message.
* @param message The message to send.
*/
// public ComponentIdentifier[] sendMessage(IMessageEnvelope message)
public IComponentIdentifier[] sendMessage(Map message, String msgtype, IComponentIdentifier[] receivers)
{
// Fetch all receivers
IComponentIdentifier[] recstodel = receivers;
// IComponentIdentifier[] recstodel = message.getReceivers();
List undelivered = SUtil.arrayToList(recstodel);
// Find receivers with same address and send only once for
// them as message is delivered to all
// address -> (aid1, aid2, ...)
MultiCollection adrsets = new MultiCollection(SCollection.createHashMap(), HashSet.class);
for(int i=0; i<recstodel.length; i++)
{
String[] addrs = recstodel[i].getAddresses();
for(int j=0; j<addrs.length; j++)
{
adrsets.put(addrs[j], recstodel[i]);
}
}
// Iterate over all different addresses and try to send
// to missing and appropriate receivers
String[] addrs = (String[])adrsets.getKeys(String.class);
// if(addrs.length>1)
// System.out.println("here: "+SUtil.arrayToString(addrs));
for(int i=0; i<addrs.length && undelivered.size()>0; i++)
{
try
{
boolean fresh = false;
// Is the cached connection is dead the call will
// cause a IOException been thrown
NIOTCPOutputConnection con = getConnection(addrs[i]);
if(con==null)
{
fresh = true;
con = createConnection(addrs[i]);
}
if(con!=null)
{
Set aidset = (Set)adrsets.get(addrs[i]);
aidset.retainAll(undelivered);
// ComponentIdentifier[] aids = (ComponentIdentifier[])aidset.toArray(new ComponentIdentifier[aidset.size()]);
// message.setReceivers(aids);
// The send process must be performed once or twice
// as there is no possibility to check if the cached connection
// is still connected to the other end. This can only be
// checked by the write operation.
while(true)
{
try
{
con.send(new MessageEnvelope(message, aidset, msgtype));
undelivered.removeAll(aidset);
break;
}
catch(IOException e)
{
removeConnection(addrs[i]);
if(!fresh)
{
fresh = true;
con = createConnection(addrs[i]);
if(con==null)
break;
}
else
{
// logger.warning("Send connection closed: "+addrs[i]);
break;
}
}
}
}
}
catch(IOException e)
{
// logger.warning("Address unreachable: "+addrs[i]);
}
}
return (ComponentIdentifier[])undelivered.toArray(new ComponentIdentifier[undelivered.size()]);
}
/**
* Returns the prefix of this transport
* @return Transport prefix.
*/
public String getServiceSchema()
{
return SCHEMA;
}
/**
* Get the adresses of this transport.
* @return An array of strings representing the addresses
* of this message transport mechanism.
*/
public String[] getAddresses()
{
return addresses;
}
//-------- helper methods --------
/**
* Get the address of this transport.
* @param hostname The hostname.
* @param port The port.
* @return <scheme>:<hostname>:<port>
*/
protected String getAddress(String hostname, int port)
{
return getServiceSchema()+hostname+":"+port;
}
/**
* Get the cached connection.
* @param address The address.
* @return The cached connection.
*/
protected NIOTCPOutputConnection getConnection(String address) throws IOException
{
address = address.toLowerCase();
Object ret = connections.get(address);
if(ret instanceof NIOTCPDeadConnection)
{
NIOTCPDeadConnection dead = (NIOTCPDeadConnection)ret;
// Reset connection if connection should be retried.
if(dead.shouldRetry())
{
connections.remove(address);
ret = null;
}
else
{
throw new IOException("Dead connection.");
}
}
return (NIOTCPOutputConnection)ret;
}
/**
* Remove a cached connection.
* @param address The address.
*/
protected void removeConnection(String address)
{
if(connections!=null)
{
address = address.toLowerCase();
Object con = connections.remove(address);
if(con instanceof NIOTCPOutputConnection)
((NIOTCPOutputConnection)con).close();
}
}
/**
* Create a outgoing connection.
* @param address The connection address.
* @return the connection to this address
*/
protected NIOTCPOutputConnection createConnection(String address)
{
address = address.toLowerCase();
NIOTCPOutputConnection ret = null;
if(address.startsWith(getServiceSchema()))
{
// Parse the address
try
{
int schemalen = getServiceSchema().length();
int div = address.indexOf(':', schemalen);
String hostname;
int iport;
if(div>0)
{
hostname = address.substring(schemalen, div);
iport = Integer.parseInt(address.substring(div+1));
}
else
{
hostname = address.substring(schemalen);
iport = DEFAULT_PORT;
}
// ClassLoader cl = ((ILibraryService)container.getService(ILibraryService.class)).getClassLoader()
ret = new NIOTCPOutputConnection(InetAddress.getByName(hostname), iport, codecfac, new Cleaner(address), libservice.getClassLoader());
connections.put(address, ret);
}
catch(Exception e)
{
connections.put(address, new NIOTCPDeadConnection());
// logger.warning("Could not establish connection to: "+address);
//e.printStackTrace();
}
}
return ret;
}
/**
* Class for cleaning output connections after
* max keep alive time has been reached.
*/
protected class Cleaner implements ITimedObject
{
//-------- attributes --------
/** The address of the connection. */
protected String address;
/** The timer. */
protected ITimer timer;
//-------- constructors --------
/**
* Cleaner for a specified output connection.
* @param address The address.
*/
public Cleaner(String address)
{
this.address = address;
}
//-------- methods --------
/**
* Called when timepoint was reached.
*/
public void timeEventOccurred(long currenttime)
{
//System.out.println("Timeout reached for: "+address);
removeConnection(address);
}
/**
* Refresh the timeout.
*/
public void refresh()
{
//platform.getTimerService().addEntry(this, System.currentTimeMillis()+MAX_KEEPALIVE);
/*if(timer!=null)
timer.cancel();
timer = platform.getClock().createTimer(System.currentTimeMillis()+MAX_KEEPALIVE, this);*/
SServiceProvider.getService(container, IClockService.class).addResultListener(new DefaultResultListener()
{
public void resultAvailable(Object source, Object result)
{
IClockService clock = (IClockService)result;
long time = clock.getTime()+MAX_KEEPALIVE;
if(timer==null)
timer = clock.createTimer(time, Cleaner.this);
else
timer.setNotificationTime(time);
}
});
}
/**
* Remove this cleaner.
*/
public void remove()
{
//platform.getTimerService().removeEntry(this);
if(timer!=null)
timer.cancel();
}
}
}