// // Message.cs // // Copyright (C) 2005 Novell, Inc. // // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Reflection; using System.Text; using System.Xml.Serialization; using Beagle.Util; namespace Beagle { public abstract class Message { protected static Type[] GetTypes (Type parent_type) { ArrayList types = new ArrayList (); foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies ()) types.AddRange (ReflectionFu.ScanAssemblyForClass (ass, parent_type)); return (Type[]) types.ToArray (typeof (Type)); } } public class RequestWrapper { public RequestMessage Message; // Needed by the XmlSerializer for deserialization public RequestWrapper () { } public RequestWrapper (RequestMessage request) { this.Message = request; } } public abstract class RequestMessage : Message { private static Type[] request_types = null; private static object type_lock = new object (); private Hashtable handlers = new Hashtable (); protected ArrayList clients = new ArrayList (); //A list of clients which will receive this message protected int clients_finished; //How many clients have completed all comms [XmlIgnore] public bool Keepalive; public delegate void AsyncResponseHandler (ResponseMessage response); public delegate void Closed (); public event Closed ClosedEvent; // This is why names arguments (like in python) are a good idea. public RequestMessage (bool keepalive, string client_name) { this.Keepalive = keepalive; //FIXME: To keep the constructor compatible with the old single-client RequestMessage, //A UnixSocketClient is created and added to the clients list by default. There should be a //way to skip this if needed. this.clients.Add ( new ClientContainer (true, typeof(UnixSocketClient), client_name) ); } public RequestMessage (bool keepalive) : this (keepalive, null) { } public RequestMessage (string client_name) : this (false, client_name) { } public RequestMessage () : this (false, null) { } ~RequestMessage () { this.Close (); } public static Type[] Types { get { lock (type_lock) { if (request_types == null) request_types = GetTypes (typeof (RequestMessage)); } return request_types; } } public void Close () { lock (clients) { foreach (ClientContainer c in this.clients) { if (c.client != null) c.client.Close (); } } } public void RegisterAsyncResponseHandler (Type t, AsyncResponseHandler handler) { if (!t.IsSubclassOf (typeof (ResponseMessage))) throw new ArgumentException ("Type must be a subclass of ResponseMesage"); this.handlers [t] = handler; } public void UnregisterAsyncResponseHandler (Type t) { if (!t.IsSubclassOf (typeof (ResponseMessage))) throw new ArgumentException ("Type must be a subclass of ResponseMessage"); this.handlers.Remove (t); } private void OnClosedEvent () { if (this.ClosedEvent != null) this.ClosedEvent (); } private void OnAsyncResponse (ResponseMessage response) { AsyncResponseHandler async_response = (AsyncResponseHandler) this.handlers [response.GetType ()]; if (async_response != null) { async_response (response); } } virtual public void SendAsync () { lock (clients) { foreach (ClientContainer c in this.clients) { if (c.client != null) c.client.Close (); c.CreateClient (); c.client.AsyncResponseEvent += OnAsyncResponse; c.client.ClosedEvent += OnClosedEvent; c.client.SendAsync (this); } } } public void SendAsyncBlocking () { lock (clients) { foreach (ClientContainer c in this.clients) { c.CreateClient (); c.client.AsyncResponseEvent += OnAsyncResponse; c.client.ClosedEvent += OnClosedEvent; c.client.SendAsyncBlocking (this); } } } public ResponseMessage[] Send () { ArrayList responses = new ArrayList (); foreach (ClientContainer c in clients) { c.CreateClient (); //Logger.Log.Debug ("Sending message"); ResponseMessage resp = c.client.Send (this); //Logger.Log.Debug ("Got reply"); c.client.Close (); //Logger.Log.Debug ("Closed client"); // Add some nice syntactic sugar by throwing an // exception if the response is an error. //TODO: Maybe it's not right to throw an exception anymore (silently fail)? Or maybe throw //exceptions only for local fails? ErrorResponse err = resp as ErrorResponse; if (err != null) throw new ResponseMessageException (err); responses.Add (resp); } return (ResponseMessage []) responses.ToArray (typeof (ResponseMessage)); } } public abstract class RequestMessageExecutor { public delegate void AsyncResponse (ResponseMessage response); public event AsyncResponse AsyncResponseEvent; public abstract ResponseMessage Execute (RequestMessage req); protected void SendAsyncResponse (ResponseMessage response) { if (this.AsyncResponseEvent != null) this.AsyncResponseEvent (response); } // Really only worth overriding if the request is a keepalive public virtual void Cleanup () { } } [AttributeUsage (AttributeTargets.Class)] public class RequestMessageAttribute : Attribute { private Type message_type; public RequestMessageAttribute (Type message_type) { this.message_type = message_type; } public Type MessageType { get { return this.message_type; } } } public class ResponseWrapper { public ResponseMessage Message; // Needed by the XmlSerializer for deserialization public ResponseWrapper () { } public ResponseWrapper (ResponseMessage response) { this.Message = response; } } public abstract class ResponseMessage : Message { private static Type[] response_types = null; private static object type_lock = new object (); public static Type[] Types { get { lock (type_lock) { if (response_types == null) response_types = GetTypes (typeof (ResponseMessage)); } return response_types; } } } public class EmptyResponse : ResponseMessage { } public class ErrorResponse : ResponseMessage { public string Message; public string Details; // Needed by the XmlSerializer for deserialization public ErrorResponse () { } public ErrorResponse (Exception e) { this.Message = e.Message; this.Details = e.ToString (); } public ErrorResponse (string message) { this.Message = message; } } public class ResponseMessageException : Exception { private string details; internal ResponseMessageException (ErrorResponse response) : base (response.Message) { details = response.Details; } internal ResponseMessageException (Exception e) : base (e.Message, e) { } public override string ToString () { if (details != null) { StringBuilder sb = new StringBuilder (); sb.AppendFormat ("{0}: {1}\n", this.GetType (), this.Message); sb.Append (this.details); return sb.ToString (); } else return base.ToString (); } } internal class ClientContainer { public bool local; private System.Type clienttype; private string id; public Client client; public ClientContainer (bool local, System.Type client_type, string id) { this.local = local; this.clienttype = client_type; this.id = id; } public void CreateClient () { client = (Client) System.Activator.CreateInstance (clienttype, new object[] { id }); } } }