/*********************************************************************/
/* */
/* AgentService Framework */
/* www.agentservice.it */
/* */
/* Copyright (C) 2007 l.i.d.o. - DIST, University of Genoa, Italy */
/* GNU Lesser General Public License */
/* */
/* This library is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU Lesser General Public */
/* License as published by the Free Software Foundation, */
/* version 2.1 of the License. */
/* */
/* This library is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */
/* Lesser General Public License for more details. */
/* */
/* You should have received a copy of the GNU Lesser General Public */
/* License along with this library; if not, write to the */
/* Free Software Foundation, Inc., 59 Temple Place - Suite 330, */
/* Boston, MA 02111-1307, USA. */
/* */
/*********************************************************************/
using System;
using System.Collections;
using System.Reflection;
using System.Threading;
using AgentService.Platform.AMS;
using AgentService.Platform.Modules;
using AgentService.Platform.Modules.Default;
using AgentService.Platform.MTS;
using System.ServiceModel;
using System.Collections.Generic;
namespace AgentService.Agent.Runtime
{
///
/// Class RemoteMsgClient. Remote implementation of the IMsgClient interface.
///
/// There some System.WriteLine calls commented out, remove
/// comments if you wanna debug the conversation handshake protocol.
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single, AddressFilterMode = AddressFilterMode.Any)]
public class RemoteMsgClient : IRemoteMsgClient
{
#region Global data
///
/// Reference to the mts agent
///
private AgentService.Platform.MTS.IMTSGateway mtsGateway;
///
/// The ImprovedMessagingModule.
///
private IImprovedMessagingModule module;
///
/// Synchronization object
///
private object synchFlag = new object();
# endregion
#region Per Agent HashTables
///
/// An hashtable containing the synchronization events for the threads that manage the
/// polling of conversation messages.
///
private Hashtable autoEventPollingQueue = new Hashtable();
///
/// An hashtable containing the synchronization events for the threads that manage the WaitForMessage.
///
private Hashtable autoEventWaitForMessage = new Hashtable();
///
/// An hash table containing the events which manage the WaitForMessage methods
/// in a conversation. There is an event for every conversation.
///
private Hashtable autoEventConversationWaitForMessage = new Hashtable();
///
/// If true polls the agent queue to look for service messages
///
private Hashtable bSnoop = new Hashtable();
///
/// If false the polling thread is working, true the polling
/// thread aborted or did not start
///
private Hashtable bPollingStopped = new Hashtable();
///
/// Collection of conversation belonging to the owner agents
///
private Hashtable conversations = new Hashtable();
///
/// Collection of active conversation identifiers
///
private Hashtable activeConversations = new Hashtable();
#endregion
///
/// Raises when a proposal for a new conversation is received
///
public event ConversationDelegate OnIncomingConversation;
public event MessageArrivedDelegate OnMessageArrive;
///
/// Constructs an ImprovedMsgClient object for the given agent.
///
/// agent identifier of the owning agent
/// reference to the mts agent
public RemoteMsgClient(AgentService.Platform.MTS.IMTSGateway mtsGateway, IImprovedMessagingModule mod)
{
this.module = mod;
this.mtsGateway = mtsGateway;
}
///
/// Constructs an ImprovedMsgClient with a given module.
///
/// The messaging module.
public RemoteMsgClient(IImprovedMessagingModule msgModule)
{
this.module = msgModule;
}
///
/// Register a new mobile agent to the Messaging service
///
/// The AID of the mobile agent
///
public bool RegisterAgent(AID aid)
{
bool bOk = true;
lock (this.synchFlag)
{
try
{
this.autoEventPollingQueue.Add(aid, new AutoResetEvent(false));
this.autoEventWaitForMessage.Add(aid, new AutoResetEvent(false));
this.bSnoop.Add(aid, true);
this.bPollingStopped.Add(aid, true);
this.autoEventConversationWaitForMessage.Add(aid, new Hashtable());
this.conversations.Add(aid, new IConversationCollection());
this.activeConversations.Add(aid, new ArrayList());
this.Activate(aid);
}
catch (Exception e)
{
bOk = false;
}
}
return bOk;
}
///
/// Deregister a remote agent from the messaging service
///
/// The AID of the remote agent
///
public bool DeRegisterAgent(AID aid)
{
bool bOk = true;
lock (this.synchFlag)
{
try
{
this.stopConversations(aid);
this.autoEventPollingQueue.Remove(aid);
this.autoEventWaitForMessage.Remove(aid);
this.bSnoop.Remove(aid);
this.bPollingStopped.Remove(aid);
this.autoEventConversationWaitForMessage.Remove(aid);
this.activeConversations.Remove(aid);
this.conversations.Remove(aid);
}
catch (Exception e)
{
bOk = false;
}
}
return true;
}
///
/// Sets the gateway.
///
/// The MTS gateway
public void SetMTSGateway(IMTSGateway gateway)
{
this.mtsGateway = gateway;
}
///
/// Callback for agent's activation, starts the conversation polling
/// thread.
///
/// aid of the agent that has been activated
public void Activate(AID sender)
{
ThreadStart starter = delegate { this.pollingQueue(sender); };
System.Threading.Thread pollingThread = new System.Threading.Thread(starter);
pollingThread.Name = String.Format("{0}#msgclient:polling", sender.Name);
pollingThread.Start();
}
#region IRuntimeBindable members
///
/// Connects the runtime component to the events
/// originated by the agent instance.
///
/// proxy to the agent instance
public void BindEvents(IAgentEventProxy proxy)
{
proxy.OnReleaseAgent += new AgentDelegate(this.stopConversations);
}
///
/// Disconnects the runtime component to the events
/// originated by the agent instance.
///
/// proxy to the agent instance
public void UnbindEvents(IAgentEventProxy proxy)
{
proxy.OnReleaseAgent -= new AgentDelegate(this.stopConversations);
}
#endregion
#region IMsgClient Members
///
/// Open a bidirectional conversation with an agent.
///
/// agent identifier of the requesting agent
/// agent identifier of the agent to talk to
/// identifier of the new conversation
public IConversation OpenConversation(AID from, AID to)
{
RemoteConversation conversation = new RemoteConversation(this, from, to);
((IConversationCollection)this.conversations[from]).Add(conversation);
while (conversation.state != ConversationState.Accepted &&
conversation.state != ConversationState.Rejected)
{
Thread.Sleep(500);
}
return conversation;
}
///
/// Open a bidirectional conversation with an agent, using an ontology.
///
/// agent identifier of the agent to talk to
/// the ontology that describes the messages.
/// Returns the identifier of the new conversation.
public IOntologicalConversation OpenConversation(AID from, AID to, Ontology.Ontology onto)
{
RemoteOntologicalConversation conversation = new RemoteOntologicalConversation(this, from, to, onto);
((IConversationCollection)this.conversations[from]).Add(conversation);
while (conversation.state != ConversationState.Accepted &&
conversation.state != ConversationState.Rejected)
{
Thread.Sleep(500);
}
return conversation;
}
///
/// Checks whether the agent's queue has messages.
///
/// true if the queue has messages
public bool HasMessages(AID aid)
{
lock (this)
{
AgentMessage msg = this.mtsGateway.GetMessage(aid, NoConvMessageFilter.Instance, false);
return msg != null;
}
}
///
/// Sends a message to an agent.
///
/// message
/// true if the message has been successfully sent
public bool SendMessage(AgentService.Platform.MTS.AgentMessage message)
{
bool b;
lock (this)
{
message.Envelope.Date = System.DateTime.Now;
System.Console.WriteLine(message.Envelope.From.GlobalName + " sto per spedire il messaggio");
b = this.mtsGateway.SendMessage(message);
System.Console.WriteLine("msg spedito da " + message.Envelope.From.GlobalName);
}
return b;
}
///
/// Receives a message and pops it from the queue.
///
/// the first message available in the queue
public AgentService.Platform.MTS.AgentMessage ReceiveMessage(AID aid)
{
lock (this)
{
Platform.MTS.AgentMessage message = null;
message = this.mtsGateway.GetMessage(aid, NoConvMessageFilter.Instance, true);
return message;
}
}
///
/// Receives a message belonging to a given conversation and pops it from the queue.
///
/// conversation identifier
/// if true removes the message from the queue
/// the first message available in the queue
public AgentService.Platform.MTS.AgentMessage GetMessage(AID aid, string convId, bool bRemove)
{
Platform.MTS.AgentMessage message = null;
if (convId.Equals("ServiceMessage") == false && convId.Equals("SimpleMessage") == false)
{
ConvMessageFilter cf = new ConvMessageFilter(convId);
message = this.mtsGateway.GetMessage(aid, cf, true);
}
return message;
}
///
/// Receives all the messages that satisfy the condition of the given filter.
///
/// Specifies the selection criteria
/// If true the call is blocking
/// A collection of filtered messages retrieved from the agent's queue
public AgentService.Platform.MTS.AgentMessageCollection ReceiveMessage(AID aid, AgentService.Platform.MTS.MessageFilter filter, bool blocking)
{
lock (this)
{
START:
AgentService.Platform.MTS.AgentMessageCollection amc = this.mtsGateway.GetMessages(aid, filter, 0, true);
if ((amc.Count == 0) && (blocking == true))
{
System.Threading.Thread.Sleep(100);
goto START;
}
return amc;
}
}
///
/// Waits until a new message arrives in the agent's queue.
///
/// The new message arrived
/// NOT ACTUALLY USED
public AgentService.Platform.MTS.AgentMessage WaitForMessage(AID aid)
{
try
{
while (true)
{
lock (this)
{
AgentMessage msg = this.mtsGateway.GetMessage(aid, NoConvMessageFilter.Instance, false);
if (msg != null)
{
return msg;
}
this.module.RegisterToIncomingMessage(this.ActivateWaitForMessage, aid);
((AutoResetEvent)this.autoEventWaitForMessage[aid]).WaitOne();
}
}
}
catch (System.Threading.ThreadAbortException)
{
// the thread has waken up because is going to be
// terminated
return null;
}
}
///
/// Looks in the agent's queue for a message.
///
/// the first message available in the queue
public AgentService.Platform.MTS.AgentMessage PeekMessage(AID aid)
{
lock (this)
{
AgentMessage msg = this.mtsGateway.GetMessage(aid, NoConvMessageFilter.Instance, false);
return msg;
}
}
///
/// Retrieves all the messages and removes them from the queue.
///
///
/// identifies the kind of messages to be retreived.
/// identifier greater than 0 returns all the messages belonging to the specified conversation
/// identifier equal to 0 returns all the service messages in the queue
/// identifier less than than 0 returns specific type of service messages
///
/// if true messages are removed from the queue
/// the collection of messages retrieved from the queue
public AgentService.Platform.MTS.AgentMessageCollection GetAllMessages(AID aid, string identifier, bool bRemove)
{
// on-line definition of a custom filter to retrie the
// desired messages
MessageFilter custom = new MessageFilter(delegate(AgentMessage msg)
{
return (((identifier.Equals("SimpleMessage") == false) && (msg.ConversationId.Equals(identifier)))
|| ((identifier.Equals("SimpleMessage")/* == false*/) && (msg.ConversationId.Equals("ServiceMessage"))));
}
);
lock (this)
{
AgentService.Platform.MTS.AgentMessageCollection filtered = this.mtsGateway.GetMessages(aid, custom, 0, bRemove);
return filtered;
}
}
///
/// Retrieves all the standard messages from the queue.
///
/// if true messages are removed from the queue
/// the collection of messages retrieved from the queue
public AgentService.Platform.MTS.AgentMessageCollection GetAllMessages(AID aid, bool bRemove)
{
lock (this)
{
AgentService.Platform.MTS.AgentMessageCollection amc = this.mtsGateway.GetMessages(aid, NoConvMessageFilter.Instance, 0, bRemove);
return amc;
}
}
///
/// Removes the given message from the queue.
///
/// message to be destroyed
/// true if message is successfully removed
public bool RemoveMessage(AID aid, AgentService.Platform.MTS.AgentMessage message)
{
lock (this)
{
return this.mtsGateway.RemoveMessage(aid, message);
}
}
#endregion
#region Helpers
///
/// Removes a conversation object from the active conversation
/// list
///
/// conversation object
public void RemoveConversation(RemoteConversation conv)
{
lock (this.synchFlag)
{
if (((ArrayList)this.activeConversations[conv.Owner]).Contains(conv.ConvId) == true)
{
((ArrayList)this.activeConversations[conv.Owner]).Remove(conv.ConvId);
}
else
{
// System.Console.WriteLine("{0} - Eccezione: tentativo di rimuovere un id non presente!",this.aid.Name);
}
}
((IConversationCollection)this.conversations[conv.Owner]).Remove(conv);
}
///
/// Removes a conversation from the active conversation list
///
/// The owner of the conversation
/// The ID of the conversation
public void RemoveConversation(AID aid, string convId)
{
lock (this.synchFlag)
{
if (((ArrayList)this.activeConversations[aid]).Contains(convId) == true)
{
((ArrayList)this.activeConversations[aid]).Remove(convId);
}
else
{
// System.Console.WriteLine("{0} - Eccezione: tentativo di rimuovere un id non presente!",this.aid.Name);
}
}
((IConversationCollection)this.conversations[aid]).Remove(convId);
}
#endregion
///
/// Stops all the active conversation of the agent.
///
/// event source
private void stopConversations(AID sender)
{
if (sender.Equals(sender))
{
// check if the polling activity
// is working, if not we do not
// have to do anything
if ((bool)this.bPollingStopped[sender] == false)
{
this.bSnoop[sender] = false;
this.ActivatePollingQueue(this, new IncomingConversationEventArgs(sender));
foreach (RemoteConversation conversation in ((IConversationCollection)this.conversations[sender]))
{
conversation.Stop();
}
while ((bool)this.bPollingStopped[sender] == false)
{
System.Threading.Thread.Sleep(10);
}
//this.StopConversationPollings();
}
}
}
///
/// Worker method that actively polls the agent message queue searching for
/// messages used to control conversation objects.
///
private void pollingQueue(AID aid)
{
this.bPollingStopped[aid] = false;
int k = 0;
string guid = "";
while ((bool)this.bSnoop[aid])
{
// System.Threading.Thread.Sleep(10);
lock (this)
{
AgentService.Platform.MTS.AgentMessageCollection amc = this.GetAllMessages(aid, "ServiceMessage", false);
foreach (Platform.MTS.AgentMessage msg in amc)
{
ConversationBody body = msg.Body as ConversationBody;
guid = body.ConvId.ToString();
switch (body.Verb)
{
case ConversationProtocol.Propose:
{
this.mtsGateway.RemoveMessage(aid, msg);
IConversation conversation = this.GetConversation(msg);
((IConversationCollection)this.conversations[aid]).Add(conversation);
ConversationArgs convArgs = new ConversationArgs(conversation);
OnIncomingConversation(this, convArgs);
} break;
case ConversationProtocol.Close:
{
this.mtsGateway.RemoveMessage(aid, msg);
ConversationBody tempBody = msg.Body as ConversationBody;
if (tempBody != null)
{
this.CloseConversation(aid, tempBody.ConvId);
}
} break;
}
}
}
this.module.RegisterToIncomingConversation(this.ActivatePollingQueue, aid);
((AutoResetEvent)this.autoEventPollingQueue[aid]).WaitOne();
}
this.bPollingStopped[aid] = true;
}
///
/// Moves a conversation id from the temporary conversations
/// list to the active conversations list. Called when a
/// match with the conversation id proposed by the peer occurs
///
/// conversation id
public void PromoteConversationId(AID aid, string convId)
{
lock (this.synchFlag)
{
((ArrayList)this.activeConversations[aid]).Add(convId);
}
}
///
/// Activates the conversation polling queue which waits for an AutoResetEvent.Set.
///
///
///
private void ActivatePollingQueue(object sender, IncomingConversationEventArgs args)
{
((AutoResetEvent)this.autoEventPollingQueue[args.aid]).Set();
}
///
/// Activates the thread that manages the WaitForMessage method.
///
///
///
private void ActivateWaitForMessage(object sender, IncomingMessageEventArgs args)
{
this.module.DeregisterToIncomingMessage(this.ActivateWaitForMessage, args.aid);
((AutoResetEvent)this.autoEventWaitForMessage[args.aid]).Set();
}
///
/// Activates the conversation WaitForMessage
///
///
///
private void ActivateConversationWaitForMessage(object sender, IncomingConversationMessageEventArgs args)
{
AutoResetEvent autoReset = ((Hashtable)this.autoEventConversationWaitForMessage[args.aid])[args.ConversationId] as AutoResetEvent;
if (autoReset != null)
{
autoReset.Set();
((Hashtable)this.autoEventConversationWaitForMessage[args.aid]).Remove(args.ConversationId);
this.module.DeregisterToIncomingConversationMessage(this.ActivateConversationWaitForMessage, args.aid);
}
}
///
/// Creates a conversation, checking if the speaking agents use an ontology; in this case
/// the method returns an OntologicalConversation, otherwise returns a Conversation object.
///
/// the system message used to negotiate the conversation
/// An OntologicalConversation or Conversation object
private IConversation GetConversation(AgentMessage msg)
{
ConversationBody body = msg.Body as ConversationBody;
IConversation conversation;
if (body.Ontology.Name == "native")
{
conversation = new RemoteConversation(this, msg);
}
else
{
Type ontologyType = null;
bool found = false;
foreach (Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetType(body.Ontology.FullTypeName) != null)
{
found = true;
ontologyType = assembly.GetType(body.Ontology.FullTypeName);
break;
}
}
if (!found) throw new Exception("Unable to find the assembly containing the ontology " + body.Ontology.Name);
PropertyInfo property = ontologyType.GetProperty("Instance");
Ontology.Ontology theOntology = (Ontology.Ontology)property.GetValue(null, null);
conversation = new RemoteOntologicalConversation((RemoteMsgClient)this, msg, theOntology);
}
return conversation;
}
///
/// Removes the identifier from the active conversation.
///
/// conversation identifier
public void CloseConversation(AID aid, string convId)
{
bool bFound = false;
lock (this.synchFlag)
{
// hack to avoid cpu starvation... set the thread priority to highest
// in this way all the other threads waiting for a new conversation id
// do not eat important cpu cycles.
ThreadPriority priority = Thread.CurrentThread.Priority;
Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest;
for (int i = 0; i < ((ArrayList)this.activeConversations[aid]).Count; i++)
{
string activeId = (string)((ArrayList)this.activeConversations[aid])[i];
if (activeId.Equals(convId))
{
((ArrayList)this.activeConversations[aid]).Remove(activeId);
bFound = true;
break;
}
}
// ok now we can slow down the thread priority since we have
// set free some conversation ids...
System.Threading.Thread.CurrentThread.Priority = priority;
if (bFound == true)
{
AID peer;
AID owner;
foreach (Runtime.RemoteConversation conv in ((IConversationCollection)this.conversations[aid]))
{
if (conv.ConvId.Equals(convId))
{
peer = conv.Peer;
owner = conv.Owner;
conv.state = ConversationState.Closing;
((IConversationCollection)this.conversations[aid]).Remove(convId);
ConversationBody closeBody = new ConversationBody(convId, peer, ConversationProtocol.Close);
//closeBody.Add(RemoteConversation.StatusCodeLabel, Conversation);
AgentMessage message = new AgentMessage(RemoteConversation.Acl, closeBody, peer);
message.Envelope.From = owner;
message.ConversationId = "ServiceMessage";
this.SendMessage(message);
break;
}
}
}
}
}
///
/// Gets all the conversations for an agent
///
/// The agent AID
/// The list of the conversations
public List GetConversationsList(AID aid)
{
List list = new List();
lock (this.synchFlag)
{
if (this.conversations.Contains(aid))
{
IConversationCollection coll = (IConversationCollection)this.conversations[aid];
for (int i = 0; i < coll.Count; i++)
{
list.Add(new RemoteConversationDescriptor(coll[i].Peer, coll[i].ConvId, coll[i].State));
}
}
}
return list;
}
///
/// Accept an incoming conversation
///
/// The conversation owner
/// The ID of the incoming conversation
public void AcceptConversation(AID aid, string convId)
{
IConversationCollection coll = ((IConversationCollection)this.conversations[aid]);
for (int i = 0; i < coll.Count; i++)
{
if (coll[i].ConvId.Equals(convId))
{
((RemoteConversation)coll[i]).Accept();
}
}
}
///
/// Reject an incoming conversation
///
/// The conversation owner
/// The ID of the incoming conversation
public void RejectConversation(AID aid, string convId)
{
IConversationCollection coll = ((IConversationCollection)this.conversations[aid]);
for (int i = 0; i < coll.Count; i++)
{
if (coll[i].ConvId.Equals(convId))
{
((RemoteConversation)coll[i]).Reject();
}
}
}
}
}