/*********************************************************************/ /* */ /* 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(); } } } } }