/*********************************************************************/ /* */ /* 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 AgentService.Agent; using AgentService.Ontology; using AgentService.Platform.AMS; using AgentService.Platform.MTS; using System.Threading; namespace AgentService.Agent.Runtime { /// /// Class ImprovedConversation. Improved implementation of the IConversation interface /// [Serializable()] public class ImprovedConversation: IConversation { /// /// Item name used to transfer the status code item. /// protected static readonly string StatusCodeLabel = "code"; /// /// agent communication language for agent service conversation /// public static readonly string Acl = "ags:conversation"; /// /// reference to the client of the messaging system. /// private ImprovedMsgClient msgClient; /// /// Owner's aid of the conversation object /// private AID owner; /// /// AID of the other agent that participates in the conversation /// private AID peer; /// /// Gets the AID of the other agent that participates in the conversation /// public AID Peer { get { return this.peer; } } //Added by Andrea Passadore (20/03/2007): it is used in the new costructors managing the ontologies. /// /// Owner's aid of the conversation object /// public AID Owner { get { return this.owner; } } /// /// state of the conversation /// internal ConversationState state; /// /// Gets the state of the conversation /// public ConversationState State { get { return this.state; } } /// /// conversation status code /// internal ConversationStatusCode statusCode = ConversationStatusCode.Ok; /// /// Gets the status code of the conversation /// public ConversationStatusCode StatusCode { get { return this.statusCode; } } /// /// Unique conversation identifier /// private string convId; /// /// Gets the conversation identifier /// public string ConvId { get { return this.convId; } } /// /// Flag used only for optimization, if true /// the conversation has been removed from the /// conversation collection of the message client /// and we do not need to call RemoveConversation /// again /// private bool bRemoved = false; /// /// If true conversation object executes the thread /// to contract for the conversation identifier /// private bool bRunHandShake = true; /// /// Message body of the incoming conversation /// private ConversationBody body; /// /// Default envelope for all the messages belonging /// to this conversation /// private MessageEnvelope envelope; /// /// Attempts counter /// private int attempts; /// /// Number of max attempts used by the conversation /// object to complete the handshake process /// private int maxAttempts = 5; /// /// Gets, sets the number of max attempts to complete /// the handshake process /// public int MaxAttempts { get { return this.maxAttempts; } set { if (value > 0) { this.maxAttempts = value; } } } //Added by Andrea Passadore (20/03/2007): it manages ontological conversation. /// /// Constructs a Conversation object to open a channel with a given agent /// by using the given message client. Used by the message client when from /// whithin a behaviour a new conversation is requested. /// /// reference to the message client /// aid of the agent holding the conversation /// aid of the agent to talk with /// the ontology descriptor internal ImprovedConversation(ImprovedMsgClient msgClient, AID owner, AID peer, OntologyTypeDescriptor ontology) { if (msgClient == null) throw new ArgumentNullException("msgClient", "null reference to the message client?"); this.msgClient = msgClient; if (owner == null) throw new ArgumentNullException("owner", "null aid?"); this.owner = owner; if (peer == null) throw new ArgumentNullException("peer", "null aid?"); this.peer = peer; this.state = ConversationState.Requesting; this.attempts = 0; this.convId = Guid.NewGuid().ToString(); ConversationBody body = new ConversationBody(this.convId, this.owner); body.Ontology = ontology; AgentMessage request = new AgentMessage(Conversation.Acl, body, this.peer); request.ConversationId = "ServiceMessage"; this.msgClient.SendMessage(request); System.Threading.Thread convThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.handleHandShake)); convThread.Name = String.Format("{0}#conv:handshake", System.Threading.Thread.CurrentThread.Name); convThread.Priority = System.Threading.ThreadPriority.Highest; convThread.Start(); } //Modded by Andrea Passadore (20/03/2007): the code has been moved in Conversation(ImprovedMsgClient msgClient, AID owner, AID peer, OntologyTypeDescriptor ontology) /// /// Constructs a Conversation object to open a channel with a given agent /// by using the given message client. Used by the message client when from /// whithin a behaviour a new conversation is requested. /// /// reference to the message client /// aid of the agent holding the conversation /// aid of the agent to talk with internal ImprovedConversation(ImprovedMsgClient msgClient, AID owner, AID peer) : this(msgClient, owner, peer, new OntologyTypeDescriptor()) { } //Added by Andrea Passadore (20/03/2007): it manages ontological conversation. /// /// Constructs a Conversation object to open a channel with a given agent /// by using the given message client. Used by the message client when an /// incoming conversation request is in the agent message queue. /// /// reference to the message client /// message containing an incoming conversation request /// the ontology descriptor internal ImprovedConversation(ImprovedMsgClient msgClient, AgentMessage message, OntologyTypeDescriptor ontology) { if (msgClient == null) throw new ArgumentNullException("msgClient", "null reference to the message client?"); this.msgClient = msgClient; if (message == null) throw new ArgumentNullException("message", "null message?"); this.owner = message.Envelope.To[0]; this.peer = message.Envelope.From; this.state = ConversationState.Incoming; this.body = message.Body as ConversationBody; this.attempts = 0; this.convId = ((ConversationBody)message.Body).ConvId; } //Modded by Andrea Passadore (20/03/2007): the code has been moved in Conversation(ImprovedMsgClient msgClient, AgentMessage message, OntologyTypeDescriptor ontology) /// /// Constructs a Conversation object to open a channel with a given agent /// by using the given message client. Used by the message client when an /// incoming conversation request is in the agent message queue. /// /// reference to the message client /// message containing an incoming conversation request internal ImprovedConversation(ImprovedMsgClient msgClient, AgentMessage message) : this(msgClient, message, new OntologyTypeDescriptor()) { } #region IConversation Members /// /// Waits for the acknowledge for a limited period of time. /// /// Period of time to wait for the acknowledge, if the /// time span is equal to zero, the method will wait till the conversation is /// accepted or rejected /// true if the acknowledge for a message has been received. public bool WaitForAck(TimeSpan timeout) { // TODO: Add Conversation.WaitForAck implementation int total = timeout.Milliseconds; int step = total / 10; bool bRunning = true; while (bRunning) { switch(this.state) { case ConversationState.Accepted: case ConversationState.Active: return true; case ConversationState.Rejected: case ConversationState.Closed: return false; case ConversationState.Closing: return true; default: { if (total > 0) bRunning = (step-- > 0); } break; } System.Threading.Thread.Sleep(0); } return ((this.state == ConversationState.Accepted) || (this.state == ConversationState.Active)); } /// /// Accepts the incoming conversation that is defined by this object. /// public void Accept() { if (this.state == ConversationState.Incoming) { body.Verb = ConversationProtocol.Reply; AgentMessage reply = new AgentMessage(Conversation.Acl, body, this.peer); reply.ConversationId = "ServiceMessage"; this.msgClient.SendMessage(reply); this.state = ConversationState.Accepted; msgClient.PromoteConversationId(convId); } else { this.state = ConversationState.Invalid; } } /// /// Rejects the incoming conversation that is defined by this object. /// public void Reject() { this.Reject(ConversationStatusCode.Other); } /// /// Rejects the incoming conversation represented by this instance /// and specifies additional information for the rejection. /// /// additional informatio for the rejection public void Reject(ConversationStatusCode code) { if (this.state == ConversationState.Incoming) { this.body.Verb = ConversationProtocol.Deny; this.body.Add(ImprovedConversation.StatusCodeLabel, code); this.state = ConversationState.Rejected; this.msgClient.RemoveConversation(this); this.bRemoved = true; AgentMessage msg = new AgentMessage(Conversation.Acl, body, this.peer); msg.ConversationId = "ServiceMessage"; this.msgClient.SendMessage(msg); } else { this.state = ConversationState.Invalid; // TODO: throw exception? } } /// /// Changes the status of the conversation on the peer side. /// /// conversation status code public void Signal(ConversationStatusCode code) { if (this.state == ConversationState.Active) { ConversationBody convBody = new ConversationBody(this.convId, this.owner, ConversationProtocol.Signal); convBody.Verb = ConversationProtocol.Signal; convBody.Add(ImprovedConversation.StatusCodeLabel, code); AgentMessage msg = new AgentMessage(Conversation.Acl, convBody, this.peer); //msg.ConversationId = this.guid; this.msgClient.SendMessage(msg); } } /// /// Looks in the agent message queue for messages belonging to this /// conversation object. /// /// true if there are messages belonging to this conversation object public bool HasMessages() { // TODO: Add Conversation.HasMessages implementation if ((this.state == ConversationState.Accepted) || (this.state == ConversationState.Active) || (this.state == ConversationState.Closing)) { // checks for the first message in the queue belonging to // this conversation, if the message is null there no messages // in the queue belonging to this conversation return (this.msgClient.GetMessage(this.convId, false) != null); } else if (this.state == ConversationState.Requesting) { // just do nothing since the conversation has not been // set up yet return false; } else if (this.state == ConversationState.Closed) { if (bRemoved == false) { this.msgClient.RemoveConversation(this); this.bRemoved = true; } this.state = ConversationState.Invalid; return false; } return false; } /// /// Sends a message whithin this conversation object. /// /// content of the message /// true if the message has been properly sent. public bool SendMessage(AgentService.Platform.MTS.MessageBody content) { if ((this.state == ConversationState.Accepted) || (this.state == ConversationState.Active)) { if (this.envelope == null) { this.envelope = new MessageEnvelope(); this.envelope.To.RemoveAll(); this.envelope.To.Add(this.peer); } AgentMessage message = new AgentMessage(this.envelope,content,this.convId); bool bOk = this.msgClient.SendMessage(message); if (bOk == true) { this.state = ConversationState.Active; } return bOk; } else if ( (this.state == ConversationState.Requesting) || (this.state == ConversationState.Incoming) ) { // just do nothing since the conversation has not been // set up yet return false; } else { this.state = ConversationState.Invalid; return false; } } /// /// Receives the first message belonging to this conversation object. /// /// the message received public AgentService.Platform.MTS.AgentMessage ReceiveMessage() { AgentMessage message = null; if ((this.state == ConversationState.Accepted) || (this.state == ConversationState.Active) || (this.state == ConversationState.Closing) ) { message = this.msgClient.GetMessage(this.convId, true); if (message != null) { if (this.state == ConversationState.Accepted) { this.state = ConversationState.Active; } if (this.handleSignal(message.Body) == true) { message = null; } } } else if (this.state != ConversationState.Requesting) { this.state = ConversationState.Invalid; } return message; } /// /// Looks in the agent message queue for messages belonging to this /// conversation object and returns the first of them whitout popping /// it from the queue. /// /// the first message on the agent message queue belonging to this conversation public AgentService.Platform.MTS.AgentMessage PeekMessage() { AgentMessage message = null; if ((this.state == ConversationState.Accepted) || (this.state == ConversationState.Active)) { message = this.msgClient.GetMessage(this.convId,false); if(message != null) { this.state = ConversationState.Active; } if (this.handleSignal(message.Body) == true) { return null; } } else if ( (this.state != ConversationState.Requesting) && (this.state != ConversationState.Requesting) ) { this.state = ConversationState.Invalid; } return message; } /// /// Waits for a message belonging to this conversation object /// /// /// an agent message object, null if the conversation is in /// the Closed or Invalid state /// public AgentService.Platform.MTS.AgentMessage WaitForMessage() { if ((this.state == ConversationState.Accepted) || (this.state == ConversationState.Active)) { return this.doWaitForMessage(); } else if (this.state == ConversationState.Requesting) { do { // go to sleep for one second System.Threading.Thread.Sleep(1500); } while (this.state == ConversationState.Requesting); return this.doWaitForMessage(); } else { this.state = ConversationState.Invalid; return null; } } /// /// Helper for the WaitForMessage method, actually waits for /// an incoming conversation message /// e /// /// an agent message object, null if the conversation is in /// the Closed or Invalid state /// private AgentMessage doWaitForMessage() { AgentMessage message = null; do { if ((this.state == ConversationState.Closed) || (this.state == ConversationState.Invalid)) { if (this.bRemoved == false) { this.msgClient.RemoveConversation(this); this.bRemoved = true; } return null; } AgentMessageCollection messages = this.msgClient.GetAllMessages(this.convId,false); if(messages.Count != 0) { message = messages[0] as AgentMessage; this.msgClient.RemoveMessage(message); if (message != null) { this.handleSignal(message.Body); } this.state = ConversationState.Active; break; } AutoResetEvent autoReset = this.msgClient.SubscribeToWaitForMessageEvent(this.convId); autoReset.WaitOne(); } while (message == null); return message; } /// /// Closes this conversation object. /// public void Close() { this.Close(ConversationStatusCode.Ok); } /// /// Closes the conversation upon /// a specific event described by the /// status code. /// /// conversation status code public void Close(ConversationStatusCode code) { lock (this) { if ((this.state != ConversationState.Closing) && (this.state != ConversationState.Invalid) && (this.state != ConversationState.Rejected)) { ConversationBody closeBody = new ConversationBody(this.convId, this.peer, ConversationProtocol.Close); closeBody.Add(ImprovedConversation.StatusCodeLabel, code); AgentMessage message = new AgentMessage(ImprovedConversation.Acl, closeBody, this.peer); message.ConversationId = "ServiceMessage"; this.msgClient.SendMessage(message); } } if (this.bRemoved == false) { this.msgClient.CloseConversation(this.convId); this.bRemoved = true; } this.state = ConversationState.Closed; } #endregion #region helpers /// /// Processes the message body in order to /// look for signals. /// /// this method can be used in the /// future for extending the protocol /// /// private bool handleSignal(MessageBody body) { ConversationBody cb = body as ConversationBody; if (cb != null) { if (cb.Verb == ConversationProtocol.Signal) { try { object item = cb[ImprovedConversation.StatusCodeLabel]; this.statusCode = (ConversationStatusCode) item; } catch (InvalidCastException ex) { // TODO: Review the specific code set this.statusCode = ConversationStatusCode.InternalError; } } return true; } else { return false; } } /// /// Contracts the conversation identifier to start the communication with the peer /// private void handleHandShake() { //Note if the peer do not reply the cycle is infinite. //int tempId = this.convId; while(this.bRunHandShake) { AgentMessageCollection messages = this.msgClient.GetAllMessages("ServiceMessage",false); foreach(AgentMessage message in messages) { // if the message is a service message and it comes from the peer agent.... if (message.Envelope.From.Equals(this.peer)) { // does it speak my language? ConversationBody body = message.Body as ConversationBody; if (body != null) { // ..yes if ((body.ConvId.Equals(this.convId)) && (body.Initiator.Name.Equals(this.owner.Name))) { this.msgClient.RemoveMessage(message); if (body.Verb == ConversationProtocol.Deny) { lock (this) { this.state = ConversationState.Rejected; this.statusCode = (ConversationStatusCode)body[ImprovedConversation.StatusCodeLabel]; this.msgClient.RemoveConversation(this); return; } } else { if (body.Verb == ConversationProtocol.Reply) { lock (this) { this.state = ConversationState.Accepted; this.msgClient.PromoteConversationId(body.ConvId); return; } } else { if (body.Verb == ConversationProtocol.Close) { this.msgClient.RemoveMessage(message); this.msgClient.RemoveConversation(this); } } } } else { // ..no, just skip } } } } System.Threading.Thread.Sleep(1000); } } /// /// Stops the handleHandShake thread and sets the state of the conversation to Closed /// internal void Stop() { this.bRunHandShake = false; this.state = ConversationState.Closed; } #endregion } }