package jadex.standalone.service;
import jadex.bridge.ContentException;
import jadex.bridge.DefaultMessageAdapter;
import jadex.bridge.IComponentIdentifier;
import jadex.bridge.IComponentManagementService;
import jadex.bridge.IContentCodec;
import jadex.bridge.IExternalAccess;
import jadex.bridge.IMessageAdapter;
import jadex.bridge.IMessageListener;
import jadex.bridge.IMessageService;
import jadex.bridge.MessageFailureException;
import jadex.bridge.MessageType;
import jadex.commons.Future;
import jadex.commons.IFuture;
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.collection.LRU;
import jadex.commons.collection.MultiCollection;
import jadex.commons.collection.SCollection;
import jadex.commons.concurrent.CollectionResultListener;
import jadex.commons.concurrent.DefaultResultListener;
import jadex.commons.concurrent.DelegationResultListener;
import jadex.commons.concurrent.IExecutable;
import jadex.commons.concurrent.IResultListener;
import jadex.commons.service.BasicService;
import jadex.commons.service.IServiceProvider;
import jadex.commons.service.SServiceProvider;
import jadex.commons.service.clock.IClockService;
import jadex.commons.service.execution.IExecutionService;
import jadex.standalone.StandaloneComponentAdapter;
import jadex.standalone.transport.ITransport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* The Message service serves several message-oriented purposes: a) sending and
* delivering messages by using transports b) management of transports
* (add/remove)
*
* The message service performs sending and delivering messages by separate actions
* that are individually executed on the execution service, i.e. they are delivered
* synchronous or asynchronous depending on the execution service mode.
*/
public class MessageService extends BasicService implements IMessageService
{
//-------- constants --------
/** The default codecs. */
protected static IContentCodec[] DEFCODECS = new IContentCodec[]
{
new jadex.base.contentcodecs.JavaXMLContentCodec(),
new jadex.base.contentcodecs.JadexXMLContentCodec(),
new jadex.base.contentcodecs.NuggetsXMLContentCodec()
};
/** No addresses constant. */
protected String LOCAL = "local";
//-------- attributes --------
/** The provider. */
protected IServiceProvider provider;
/** The transports. */
protected List transports;
/** All addresses of this platform. */
private String[] addresses;
/** The message types. */
protected Map messagetypes;
/** The deliver message action executed by platform executor. */
protected DeliverMessage delivermsg;
/** The logger. */
protected Logger logger;
/** The listeners. */
protected List listeners;
/** The cashed clock service. */
protected IClockService clockservice;
/** The cashed clock service. */
protected IComponentManagementService cms;
/** The target managers. */
protected LRU managers;
//-------- constructors --------
/**
* Constructor for Outbox.
* @param platform
*/
public MessageService(IServiceProvider provider, ITransport[] transports, MessageType[] messagetypes)
{
super(provider.getId(), IMessageService.class, null);
this.provider = provider;
this.transports = SCollection.createArrayList();
for(int i=0; i<transports.length; i++)
this.transports.add(transports[i]);
this.messagetypes = SCollection.createHashMap();
for(int i=0; i<messagetypes.length; i++)
this.messagetypes.put(messagetypes[i].getName(), messagetypes[i]);
this.delivermsg = new DeliverMessage();
this.logger = Logger.getLogger("MessageService" + this);
this.managers = new LRU(800);
}
//-------- interface methods --------
/**
* Send a message.
* @param message The native message.
*/
public IFuture sendMessage(final Map msg, final MessageType type, IComponentIdentifier sender, final ClassLoader cl)
{
final Future ret = new Future();
// IComponentIdentifier sender = adapter.getComponentIdentifier();
if(sender==null)
{
ret.setException(new RuntimeException("Sender must not be null: "+msg));
return ret;
}
final Map msgcopy = new HashMap(msg);
// Automatically add optional meta information.
String senid = type.getSenderIdentifier();
Object sen = msgcopy.get(senid);
if(sen==null)
msgcopy.put(senid, sender);
final String idid = type.getIdIdentifier();
Object id = msgcopy.get(idid);
if(id==null)
msgcopy.put(idid, SUtil.createUniqueId(sender.getLocalName()));
final String sd = type.getTimestampIdentifier();
final Object senddate = msgcopy.get(sd);
// External access of sender required for content encoding etc.
cms.getExternalAccess(sender).addResultListener(new IResultListener()
{
public void resultAvailable(Object source, Object result)
{
IExternalAccess exta = (IExternalAccess)result;
if(senddate==null)
{
// SServiceProvider.getService(container, IClockService.class).addResultListener(new DefaultResultListener()
// {
// public void resultAvailable(Object source, Object result)
// {
// if(result!=null)
// msgcopy.put(sd, ""+((IClockService)result).getTime());
msgcopy.put(sd, ""+clockservice.getTime());
doSendMessage(msg, type, exta, cl, msgcopy, ret);
// }
// });
}
else
{
doSendMessage(msg, type, exta, cl, msgcopy, ret);
}
}
public void exceptionOccurred(Object source, Exception exception)
{
ret.setException(exception);
}
});
return ret;
}
/**
* Extracted method to be callable from listener.
*/
protected void doSendMessage(Map msg, MessageType type, IExternalAccess comp, ClassLoader cl, Map msgcopy, Future ret)
{
Object tmp = msgcopy.get(type.getReceiverIdentifier());
if(tmp==null || SReflect.isIterable(tmp) && !SReflect.getIterator(tmp).hasNext())
{
ret.setException(new RuntimeException("Receivers must not be empty: "+msgcopy));
return;
}
if(SReflect.isIterable(tmp))
{
for(Iterator it=SReflect.getIterator(tmp); it.hasNext(); )
{
if(it.next()==null)
{
ret.setException(new MessageFailureException(msg, type, null, "A receiver nulls: "+msg));
return;
}
}
}
// Conversion via platform specific codecs
IContentCodec[] compcodecs = getContentCodecs(comp.getModel().getProperties());
for(Iterator it=msgcopy.keySet().iterator(); it.hasNext(); )
{
String name = (String)it.next();
Object value = msgcopy.get(name);
IContentCodec codec = type.findContentCodec(compcodecs, msg, name);
if(codec==null)
codec = type.findContentCodec(DEFCODECS, msg, name);
if(codec!=null)
{
msgcopy.put(name, codec.encode(value, cl));
}
else if(value!=null && !(value instanceof String)
&& !(name.equals(type.getSenderIdentifier()) || name.equals(type.getReceiverIdentifier())))
{
ret.setException(new ContentException("No content codec found for: "+name+", "+msgcopy));
return;
}
}
if(listeners!=null)
{
// Hack?!
IMessageAdapter msgadapter = new DefaultMessageAdapter(msgcopy, type);
for(int i=0; i<listeners.size(); i++)
{
IMessageListener lis = (IMessageListener)listeners.get(i);
lis.messageSent(msgadapter);
}
}
// Determine manager tasks
MultiCollection managers = new MultiCollection();
String recid = type.getReceiverIdentifier();
tmp = msgcopy.get(recid);
if(SReflect.isIterable(tmp))
{
for(Iterator it = SReflect.getIterator(tmp); it.hasNext(); )
{
IComponentIdentifier cid = (IComponentIdentifier)it.next();
SendManager sm = getSendManager(cid);
managers.put(sm, cid);
}
}
else
{
IComponentIdentifier cid = (IComponentIdentifier)tmp;
SendManager sm = getSendManager(cid);
managers.put(sm, cid);
}
CollectionResultListener lis = new CollectionResultListener(managers.size(), false, new DelegationResultListener(ret));
for(Iterator it=managers.keySet().iterator(); it.hasNext();)
{
SendManager tm = (SendManager)it.next();
IComponentIdentifier[] recs = (IComponentIdentifier[])managers.getCollection(tm)
.toArray(new IComponentIdentifier[0]);
ManagerSendTask task = new ManagerSendTask(msgcopy, type, recs, tm);
task.getSendManager().addMessage(task).addResultListener(lis);
}
// sendmsg.addMessage(msgcopy, type, receivers, ret);
}
/**
* Get a matching content codec.
* @param props The properties.
* @return The content codec.
*/
public static IContentCodec[] getContentCodecs(Map props)
{
List ret = null;
if(props!=null)
{
for(Iterator it=props.keySet().iterator(); ret==null && it.hasNext();)
{
String name = (String)it.next();
if(name.startsWith("contentcodec."))
{
if(ret==null)
ret = new ArrayList();
ret.add(props.get(name));
}
}
}
return ret!=null? (IContentCodec[])ret.toArray(new IContentCodec[ret.size()]): null;
}
/**
* Deliver a message to the intended components. Called from transports.
* @param message The native message.
* (Synchronized because can be called from concurrently executing transports)
*/
public void deliverMessage(Map message, String msgtype, IComponentIdentifier[] receivers)
{
IMessageListener[] lis;
synchronized(this)
{
lis = listeners==null? null: (IMessageListener[])listeners.toArray(new IMessageListener[listeners.size()]);
}
if(lis!=null)
{
// Hack?!
IMessageAdapter msg = new DefaultMessageAdapter(message, getMessageType(msgtype));
for(int i=0; i<lis.length; i++)
{
IMessageListener li = (IMessageListener)lis[i];
li.messageReceived(msg);
}
}
delivermsg.addMessage(message, msgtype, receivers);
}
/**
* Create a reply to this message event.
* @param msgeventtype The message event type.
* @return The reply event.
*/
public Map createReply(Map msg, MessageType mt)
{
Map reply = new HashMap();
MessageType.ParameterSpecification[] params = mt.getParameters();
for(int i=0; i<params.length; i++)
{
String sourcename = params[i].getSource();
if(sourcename!=null)
{
Object sourceval = msg.get(sourcename);
if(sourceval!=null)
{
reply.put(params[i].getName(), sourceval);
}
}
}
MessageType.ParameterSpecification[] paramsets = mt.getParameterSets();
for(int i=0; i<paramsets.length; i++)
{
String sourcename = paramsets[i].getSource();
if(sourcename!=null)
{
Object sourceval = msg.get(sourcename);
if(sourceval!=null)
{
List tmp = new ArrayList();
tmp.add(sourceval);
reply.put(paramsets[i].getName(), tmp);
}
}
}
return reply;
}
/**
* Adds a transport for this outbox.
* @param transport The transport.
*/
public void addTransport(ITransport transport)
{
transports.add(transport);
addresses = null;
}
/**
* Remove a transport for the outbox.
* @param transport The transport.
*/
public void removeTransport(ITransport transport)
{
transports.remove(transport);
transport.shutdown();
addresses = null;
}
/**
* Moves a transport up or down.
* @param up Move up?
* @param transport The transport to move.
*/
public synchronized void changeTransportPosition(boolean up, ITransport transport)
{
int index = transports.indexOf(transport);
if(up && index>0)
{
ITransport temptrans = (ITransport)transports.get(index - 1);
transports.set(index - 1, transport);
transports.set(index, temptrans);
}
else if(index!=-1 && index<transports.size()-1)
{
ITransport temptrans = (ITransport)transports.get(index + 1);
transports.set(index + 1, transport);
transports.set(index, temptrans);
}
else
{
throw new RuntimeException("Cannot change transport position from "
+index+(up? " up": " down"));
}
}
/**
* Get the adresses of a component.
* @return The addresses of this component.
*/
public String[] getAddresses()
{
if(addresses == null)
{
ITransport[] trans = (ITransport[])transports.toArray(new ITransport[transports.size()]);
ArrayList addrs = new ArrayList();
for(int i = 0; i < trans.length; i++)
{
String[] traddrs = trans[i].getAddresses();
for(int j = 0; traddrs!=null && j<traddrs.length; j++)
addrs.add(traddrs[j]);
}
addresses = (String[])addrs.toArray(new String[addrs.size()]);
}
return addresses;
}
/**
* Get addresses of all transports.
* @return The address schemes of all transports.
*/
public String[] getAddressSchemes()
{
ITransport[] trans = (ITransport[])transports.toArray(new ITransport[transports.size()]);
ArrayList schemes = new ArrayList();
for(int i = 0; i < trans.length; i++)
{
String scheme = trans[i].getServiceSchema();
schemes.add(scheme);
}
return (String[])schemes.toArray(new String[schemes.size()]);
}
/**
* Get the transports.
* @return The transports.
*/
public ITransport[] getTransports()
{
ITransport[] transportsArray = new ITransport[transports.size()];
return (ITransport[])transports.toArray(transportsArray);
}
/**
* Get a send target manager for addresses.
*/
public SendManager getSendManager(IComponentIdentifier cid)
{
SendManager ret = null;
String[] adrs = cid.getAddresses();
if(adrs==null || adrs.length==0)
{
ret = (SendManager)managers.get(LOCAL);
}
else
{
for(int i=0; i<adrs.length && ret==null; i++)
{
ret = (SendManager)managers.get(adrs[i]);
}
}
if(ret==null)
{
ret = new SendManager();
if(adrs==null || adrs.length==0)
{
managers.put(LOCAL, ret);
}
else
{
for(int i=0; i<adrs.length; i++)
{
managers.put(adrs[i], ret);
}
}
}
return ret;
}
//-------- IPlatformService interface --------
/**
* Start the service.
*/
public IFuture startService()
{
final Future ret = new Future();
ITransport[] tps = (ITransport[])transports.toArray(new ITransport[transports.size()]);
for(int i=0; i<tps.length; i++)
{
try
{
tps[i].start();
}
catch(Exception e)
{
System.out.println("Could not initialize transport: "+tps[i]+" reason: "+e);
transports.remove(tps[i]);
}
}
if(transports.size()==0)
{
ret.setException(new RuntimeException("MessageService has no working transport for sending messages."));
}
else
{
SServiceProvider.getService(provider, IClockService.class).addResultListener(new IResultListener()
{
public void resultAvailable(Object source, Object result)
{
clockservice = (IClockService)result;
SServiceProvider.getServiceUpwards(provider, IComponentManagementService.class).addResultListener(new IResultListener()
{
public void resultAvailable(Object source, Object result)
{
cms = (IComponentManagementService)result;
MessageService.super.startService().addResultListener(new DelegationResultListener(ret));
}
public void exceptionOccurred(Object source, Exception exception)
{
ret.setException(exception);
}
});
}
public void exceptionOccurred(Object source, Exception exception)
{
ret.setException(exception);
}
});
}
return ret;
}
/**
* Called when the platform shuts down. Do necessary cleanup here (if any).
*/
public IFuture shutdownService()
{
for(int i = 0; i < transports.size(); i++)
{
((ITransport)transports.get(i)).shutdown();
}
return super.shutdownService();
}
/**
* Get the message type.
* @param type The type name.
* @return The message type.
*/
public MessageType getMessageType(String type)
{
return (MessageType)messagetypes.get(type);
}
/**
* Add a message listener.
* @param listener The change listener.
*/
public synchronized void addMessageListener(IMessageListener listener)
{
if(listeners==null)
listeners = new ArrayList();
listeners.add(listener);
}
/**
* Remove a message listener.
* @param listener The change listener.
*/
public synchronized void removeMessageListener(IMessageListener listener)
{
listeners.remove(listener);
}
//-------- internal methods --------
/**
* Deliver a message to the receivers.
*/
protected void internalDeliverMessage(final Map msg, final String type, final IComponentIdentifier[] receivers)
{
final MessageType messagetype = getMessageType(type);
final Map decoded = new HashMap(); // Decoded messages cached by class loader to avoid decoding the same message more than once, when the same class loader is used.
SServiceProvider.getServiceUpwards(provider, IComponentManagementService.class).addResultListener(new DefaultResultListener()
{
public void resultAvailable(Object source, Object result)
{
for(int i = 0; i < receivers.length; i++)
{
// final int cnt = i;
StandaloneComponentAdapter component = (StandaloneComponentAdapter)((ComponentManagementService)cms).getComponentAdapter(receivers[i]);
if(component != null)
{
ClassLoader cl = component.getComponentInstance().getClassLoader();
Map message = (Map) decoded.get(cl);
if(message==null)
{
if(receivers.length>1)
{
message = new HashMap(msg);
decoded.put(cl, message);
}
else
{
// Skip creation of copy when only one receiver.
message = msg;
}
// Conversion via platform specific codecs
IContentCodec[] compcodecs = getContentCodecs(component.getModel().getProperties());
for(Iterator it=message.keySet().iterator(); it.hasNext(); )
{
String name = (String)it.next();
Object value = message.get(name);
IContentCodec codec = messagetype.findContentCodec(compcodecs, message, name);
if(codec==null)
codec = messagetype.findContentCodec(DEFCODECS, message, name);
if(codec!=null)
{
message.put(name, codec.decode((String)value, cl));
}
}
}
try
{
component.receiveMessage(message, messagetype);
}
catch(Exception e)
{
// logger.warning("Message could not be delivered to receiver(s): " + receivers[cnt] + ", "+ message.get(messagetype.getIdIdentifier())+", "+e);
// todo: notify sender that message could not be delivered!
// Problem: there is no connection back to the sender, so that
// the only chance is sending a separate failure message.
}
}
else
{
// logger.warning("Message could not be delivered to receiver(s): " + receivers[cnt] + ", "+ msg.get(messagetype.getIdIdentifier()));
// todo: notify sender that message could not be delivered!
// Problem: there is no connection back to the sender, so that
// the only chance is sending a separate failure message.
}
}
}
});
}
// /**
// * Send message(s) executable.
// */
// protected class SendMessage implements IExecutable
// {
// //-------- attributes --------
//
// /** The list of messages to send. */
// protected List messages;
//
// //-------- constructors --------
//
// /**
// * Create a new send message executable.
// */
// public SendMessage()
// {
// this.messages = new ArrayList();
// }
//
// //-------- methods --------
//
// /**
// * Send a message.
// */
// public boolean execute()
// {
// Object[] tmp = null;
// boolean isempty;
//
// synchronized(this)
// {
// if(!messages.isEmpty())
// tmp = (Object[])messages.remove(0);
// isempty = messages.isEmpty();
// }
//
// if(tmp!=null)
// internalSendMessage((Map)tmp[0], (MessageType)tmp[1], (IComponentIdentifier[])tmp[2], (Future)tmp[3]);
//
// return !isempty;
// }
//
// /**
// * Add a message to be sent.
// * @param message The message.
// */
// public void addMessage(Map message, MessageType type, IComponentIdentifier[] receivers, Future ret)
// {
// synchronized(this)
// {
// messages.add(new Object[]{message, type, receivers, ret});
// }
//
// SServiceProvider.getService(provider, IExecutionService.class).addResultListener(new DefaultResultListener()
// {
// public void resultAvailable(Object source, Object result)
// {
// try
// {
// ((IExecutionService)result).execute(SendMessage.this);
// }
// catch(RuntimeException e)
// {
// // ignore if execution service is shutting down.
// }
// }
// });
// }
// }
/**
* Send message(s) executable.
*/
protected class SendManager implements IExecutable
{
//-------- attributes --------
/** The list of messages to send. */
protected List messages;
//-------- constructors --------
/**
* Send manager.
*/
public SendManager()
{
this.messages = new ArrayList();
}
//-------- methods --------
/**
* Send a message.
*/
public boolean execute()
{
Object[] tmp = null;
boolean isempty;
synchronized(this)
{
if(!messages.isEmpty())
tmp = (Object[])messages.remove(0);
isempty = messages.isEmpty();
}
if(tmp!=null)
{
ManagerSendTask task = (ManagerSendTask)tmp[0];
Future ret = (Future)tmp[1];
IComponentIdentifier[] receivers = task.getReceivers();
// System.out.println("recs: "+SUtil.arrayToString(receivers)+" "+this);
ITransport[] transports = getTransports();
for(int i = 0; i < transports.length && receivers.length>0; i++)
{
try
{
// Method returns component identifiers of undelivered components
// IConnection con = transports[i].getConnection(addresses[i]);
// if(con==null)
// System.out.println("sending: "+transports[i]+", "+task.getMessage().get(task.getMessageType().getIdIdentifier())+", "+SUtil.arrayToString(receivers));
receivers = transports[i].sendMessage(task.getMessage(), task.getMessageType().getName(), receivers);
}
catch(Exception e)
{
// e.printStackTrace();
}
}
if(receivers.length > 0)
{
// logger.warning("Message could not be delivered to (all) receivers: " + SUtil.arrayToString(receivers));
ret.setException(new MessageFailureException(task.getMessage(), task.getMessageType(), receivers,
"Message could not be delivered to (all) receivers: "+ SUtil.arrayToString(receivers)+", "+SUtil.arrayToString(receivers[0].getAddresses())));
}
else
{
ret.setResult(null);
}
}
return !isempty;
}
/**
* Add a message to be sent.
* @param message The message.
*/
public IFuture addMessage(ManagerSendTask task)
{
final Future ret = new Future();
synchronized(this)
{
messages.add(new Object[]{task, ret});
}
SServiceProvider.getService(provider, IExecutionService.class).addResultListener(new DefaultResultListener()
{
public void resultAvailable(Object source, Object result)
{
try
{
((IExecutionService)result).execute(SendManager.this);
}
catch(RuntimeException e)
{
// ignore if execution service is shutting down.
}
}
});
return ret;
}
}
/**
* Deliver message(s) executable.
*/
protected class DeliverMessage implements IExecutable
{
//-------- attributes --------
/** The list of messages to send. */
protected List messages;
//-------- constructors --------
/**
* Create a new deliver message executable.
*/
public DeliverMessage()
{
this.messages = new ArrayList();
}
//-------- methods --------
/**
* Deliver the message.
*/
public boolean execute()
{
Object[] tmp = null;
boolean isempty;
synchronized(this)
{
if(!messages.isEmpty())
tmp = (Object[])messages.remove(0);
isempty = messages.isEmpty();
}
if(tmp!=null)
internalDeliverMessage((Map)tmp[0], (String)tmp[1], (IComponentIdentifier[])tmp[2]);
return !isempty;
}
/**
* Add a message to be delivered.
*/
public void addMessage(Map message, String type, IComponentIdentifier[] receivers)
{
synchronized(this)
{
messages.add(new Object[]{message, type, receivers});
}
SServiceProvider.getService(provider, IExecutionService.class).addResultListener(new DefaultResultListener()
{
public void resultAvailable(Object source, Object result)
{
try
{
((IExecutionService)result).execute(DeliverMessage.this);
}
catch(RuntimeException e)
{
// ignore if execution service is shutting down.
}
}
});
}
}
}