/*********************************************************************/ /* */ /* 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.IO; using System.Reflection; using System.Collections; using AgentService.Agent; using AgentService.Platform; using AgentService.Platform.Modules; namespace AgentService.Platform.Modules.Default { /// /// Class StorageInfo. Holds installation information for the storage module. /// [Serializable] public class StorageInfo : InstallationInfo { /// /// base directory for the storage /// public string baseDir; /// /// Constructs a StorageInfo object. /// /// base directory for the storage public StorageInfo(string baseDirectory) { this.baseDir = baseDirectory; } } /// /// Class DefaultStorageModule. Default implementation of the IStorageModule interface. /// public class DefaultStorageModule : AgentService.Platform.Modules.IStorageModule { /// /// synchronization handle(used for /// concurrent accesses to the services /// of the storage). /// private object syncRoot = new object(); /// /// Synchronization flag, used to resync /// the Mapping instance with the physical /// storage. /// private bool bNeedSync; /// /// Type information about the knowledge, /// behaviour, and agent template classes /// mantained in the storage. /// private Mappings mappings; /// /// Last error occurred in the module /// private AgentService.Platform.Modules.ModuleError lastError; /// /// Platform accessible data /// private AgentService.Platform.IPlatformContext hapContext; /// /// Directory hosting the repository /// private string baseDirectory; /// /// Configuration file /// private string configFile; /// /// Token owned by the module to deregister an event sink previously registered /// private int sinkToken; /// /// Constructs a default storage module /// public DefaultStorageModule() { } #region IPlatformModule Members /// /// name of the module /// private string name = "Default Storage Module"; /// /// Gets the name of the module /// public string Name { get { return this.name; } } /// /// description of the module /// private string description = "Provides a default implementation of the starage subsystem that relies on a specified portion of the file system"; /// /// Gets a summary description of the module /// public string Description { get { return this.description; } } /// /// Gets the module category /// public AgentService.Platform.Modules.ModuleCategory Category { get { return AgentService.Platform.Modules.ModuleCategory.Storage; } } /// /// Gets a bool value indicating if the module is /// able to perform the main functionality of the /// belonging category. If the bool value is false /// the maodule is not a main module. This means that /// in the case of the Messaging Category the module /// can be a filter. If the bool value is true insted /// the module can perform all the base functionalities /// required by the messaging subsystem /// public bool IsMain { get { return true; } } /// /// Checks wheter the module supports features defined in a given interface. /// If the these features are supported the module reference can be subject to /// cast against the requested interface. /// /// full name of the interface that /// true if the specified interface is supported public bool CanSupport(string capabiltyInterface) { return false; } /// /// Returns the last error occurred in the module, null if there /// are no errors to return /// /// Reference to the error. public ModuleError GetLastError() { return this.lastError; } /// /// Attaches the module to the platform. Called by the platform /// when the module is loaded. /// /// reference to the instance of the hosting platform /// configuration file /// returns true is the operation has been successfulland with no errors public bool Attach(IPlatformContext hostingPlatform, string configFile) { // retrieves base directory from the configuration file this.hapContext = hostingPlatform; try { this.configFile = configFile; this.ReadData(); } catch(System.Exception ex) { this.lastError = new ModuleError(this,"Could not read configuration file",ex); return false; } // registers the event sink for the module AgentService.Platform.PlatformEventSinkHandler handler = new PlatformEventSinkHandler(this.DispatchSinkEvent); AgentService.Platform.SinkStatusCodes code = this.hapContext.RegisterEventSink(StorageSinkEvent.GetDescriptor(),handler,out this.sinkToken); if (code != AgentService.Platform.SinkStatusCodes.SinkOk) { this.lastError = new ModuleError(this,"Error registering storage event sink with code [{0}]" + code.ToString()); } AppDomain.CurrentDomain.AppendPrivatePath(this.baseDirectory); return true; } /// /// Detaches the module from the platform Called by the platform when /// the module is unloaded /// /// true if the operation has been successful public bool Detach() { AgentService.Platform.SinkStatusCodes code = this.hapContext.DeregisterEventSink(StorageSinkEvent.GetDescriptor(),this.sinkToken); if (code != AgentService.Platform.SinkStatusCodes.SinkOk) { this.lastError = new ModuleError(this,"Error deregistering storage event sink with code [{0}]" + code.ToString()); } try { lock(this.syncRoot) { this.Synchronize(); this.WriteData(); return true; } } catch(System.IO.IOException ioex) { this.lastError = new ModuleError(this, "An I/O error occurred while detaching th module", ioex); return false; } } /// /// Installs a the module in the platform. Called by the pltaform when the module is /// installed. /// /// platform accessible data /// configuration file /// installation info object used to store install information /// true if the installation process has been successful public bool Install(IPlatformContext hostingPlatform, string configFile, ref InstallationInfo instInfo) { this.hapContext = hostingPlatform; try { this.mappings = new Mappings(); this.baseDirectory = System.IO.Path.Combine(hostingPlatform.PlatformBaseDir, @"storage"); this.configFile = configFile; this.WriteData(); System.IO.Directory.CreateDirectory(this.baseDirectory); instInfo = new StorageInfo(this.baseDirectory); return true; } catch(System.IO.IOException ioex) { this.lastError = new ModuleError(this, "Unexpected IO error", ioex); return false; } } /// /// Uninstall the module. Called by the platform when the module is removed permanently /// from the platform /// /// installation info object used to install the module /// true if the uninstall process has been successfull public bool UnInstall(InstallationInfo instInfo) { StorageInfo stInfo = instInfo as StorageInfo; if (stInfo != null) { this.baseDirectory = stInfo.baseDir; System.IO.Directory.Delete(this.baseDirectory); return true; } return false; } #endregion #region IStorageModule Members public bool RemoveAssembly(string name) { try { System.IO.File.Delete(System.IO.Path.Combine(this.baseDirectory,name)); this.lastError = null; this.bNeedSync = true; return true; } catch(System.IO.IOException ioe) { this.lastError = new ModuleError(this,"cannot remove assembly " + name,ioe); return false; } } public bool AddAssembly(string path, bool bReplaceIfExists) { string assembly = System.IO.Path.GetFileName(path); try { string dest = System.IO.Path.Combine(this.baseDirectory, assembly); if ((System.IO.File.Exists(dest) == true) && (bReplaceIfExists == false)) { this.lastError = new ModuleError(this, String.Format("Assembly {0} already exists in the storage",dest)); return false; } System.IO.File.Copy(path,dest,bReplaceIfExists); this.lastError = null; this.bNeedSync = true; return true; } catch(System.IO.IOException ioe) { this.lastError = new ModuleError(this,String.Format("cannot add assembly {0} to repository",assembly),ioe); return false; } } /// /// Executes a copy of an assembly mantained in the storage. /// /// assembly file name /// destination path /// true if the copy is successfull public bool CopyAssembly(string fileName, string newPath) { try { string source = Path.Combine(this.baseDirectory, fileName); FileStream fsSource = new FileStream(source,FileMode.Open, FileAccess.Read); FileStream fsDest = new FileStream(newPath,FileMode.Create, FileAccess.Write); byte[] buffer = new byte[1024]; int offset = 0; int count = 1024; do { count = fsSource.Read(buffer, offset, count); if (count > 0) { fsDest.Write(buffer,offset,count); offset += count; if (offset == buffer.Length) { count = 1024; offset = 0; } else { count = buffer.Length - offset; } } } while(count > 0); fsSource.Close(); fsDest.Close(); return true; } catch(ArgumentNullException ioex) { this.lastError = new ModuleError(this,"Invalid parameter", ioex); return false; } catch(ArgumentException ioex) { this.lastError = new ModuleError(this,"Invalid parameter", ioex); return false; } } public bool ClearStorage() { try { lock(this.syncRoot) { System.IO.Directory.Delete(this.baseDirectory); System.IO.Directory.CreateDirectory(this.baseDirectory); this.lastError = null; this.mappings = new Mappings(); return true; } } catch(System.IO.IOException ioe) { this.lastError = new ModuleError(this,"cannot remove all the assemblies in repository",ioe); return false; } } public TypeDescriptor GetAssemblyFor(string type) { lock(this.syncRoot) { this.Synchronize(); TypeDescriptor td = this.mappings.TypeMap[type] as TypeDescriptor; if (td == null) { this.lastError = new ModuleError(this,string.Format("AgentTemplate,Knowledge or Behaviour {0} does not exists in the repository.", type)); return null; } // return System.IO.Path.Combine(this.baseDirectory,assembly); return td; } } public System.Reflection.AssemblyName[] GetDependentAssemblies(string assemblyName) { try { string assemblyPath = System.IO.Path.Combine(this.baseDirectory, assemblyName); System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(assemblyPath); this.lastError = null; return assembly.GetReferencedAssemblies(); } catch(System.IO.IOException ioe) { this.lastError = new ModuleError(this,"cannot gets all the referenced assemblies",ioe); return null; } } /// /// Synchronizes the storage phisical content with repository of AgentTemplate types. /// Note: Call after AddAssembly, RemoveAssembly /// public bool Synchronize() { if (this.bNeedSync) { lock(this.syncRoot) { // retrieve all dll files from the base directory string pattern = "*.dll"; string[] dlls = System.IO.Directory.GetFiles(this.baseDirectory, pattern); ArrayList alTemplates = new ArrayList(); ArrayList alKnowledges = new ArrayList(); ArrayList alBehaviours = new ArrayList(); ArrayList al = new ArrayList(); this.mappings.TypeMap.Clear(); foreach(string dll in dlls) { try { System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(dll); Type[] types = assembly.GetExportedTypes(); foreach(Type type in types) { byte[] pkt = type.Assembly.GetName().GetPublicKeyToken(); TypeDescriptor td = new TypeDescriptor(type.FullName, type.Assembly.GetName().Name, pkt); if (type.IsSubclassOf(typeof(AgentService.Agent.AgentTemplate)) == true) { alTemplates.Add(td); } if (type.IsSubclassOf(typeof(AgentService.Agent.Behaviour.Behaviour)) == true) { alKnowledges.Add(td); } if (type.IsSubclassOf(typeof(AgentService.Agent.Knowledge.Knowledge)) == true) { alBehaviours.Add(td); } this.mappings.TypeMap[td.FullTypeName] = td; } al.Add(assembly.GetName()); } catch(System.TypeLoadException tex) { } catch(System.BadImageFormatException imgex) { // just skip, it could be a not managed dll } } this.mappings.Templates = (TypeDescriptor[]) alTemplates.ToArray(typeof(TypeDescriptor)); this.mappings.Knowledges = (TypeDescriptor[]) alKnowledges.ToArray(typeof(TypeDescriptor)); this.mappings.Behaviours = (TypeDescriptor[]) alBehaviours.ToArray(typeof(TypeDescriptor)); this.mappings.Assemblies = (AssemblyName[]) al.ToArray(typeof(AssemblyName)); this.bNeedSync = false; } } return true; } /// /// returns all the assemblies that the platform uses /// public System.Reflection.AssemblyName[] GetStoredAssemblies() { lock(this.syncRoot) { this.Synchronize(); return this.mappings.Assemblies; } } /// /// Gets all the templates stored in the assembly loaded into the platform /// public TypeDescriptor[] GetStoredAgentTemplates() { lock(this.syncRoot) { this.Synchronize(); TypeDescriptor[] templates = new TypeDescriptor[this.mappings.Templates.Length]; for(int ndx = 0; ndx < this.mappings.Templates.Length; ndx++) { templates[ndx] = this.mappings.Templates[ndx].Clone() as TypeDescriptor; } return templates; } } /// /// Gets all the Behaviour classes used by agents into the platform /// public TypeDescriptor[] GetStoredBehaviours() { lock(this.syncRoot) { this.Synchronize(); TypeDescriptor[] behaviours = new TypeDescriptor[this.mappings.Behaviours.Length]; for(int ndx = 0; ndx < this.mappings.Behaviours.Length; ndx++) { behaviours[ndx] = this.mappings.Behaviours[ndx].Clone() as TypeDescriptor; } return behaviours; } } /// /// Gets all the Knowledge classes used by agents into the platform /// public TypeDescriptor[] GetStoredKnowledges() { lock(this.syncRoot) { this.Synchronize(); TypeDescriptor[] knowledges = new TypeDescriptor[this.mappings.Knowledges.Length]; for(int ndx = 0; ndx < this.mappings.Knowledges.Length; ndx++) { knowledges[ndx] = this.mappings.Knowledges[ndx].Clone() as TypeDescriptor; } return knowledges; } } #endregion #region Worker methods /// /// Deserialize the Mappings instance from the given string. /// /// string version of the Mappings instance /// deserialized Mappings instance private Mappings DeserializeMappings(string strMappings) { string[] bytes = strMappings.Split('.'); byte[] buffer = new byte[bytes.Length]; for(int ndx=0; ndx /// Serialize the hastable into a string /// /// serialized version of the mapping object into a string private string SerializeMappings() { System.IO.MemoryStream ms = new System.IO.MemoryStream(); System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); bf.Serialize(ms,this.mappings); System.Text.StringBuilder sb =new System.Text.StringBuilder(); byte[] bytes = ms.GetBuffer(); for(int ndx = 0; ndx < bytes.Length; ndx++ ) { sb.Append(bytes[ndx]); if (ndx < bytes.Length - 1) sb.Append("."); } return sb.ToString(); } /// /// Writes the module data to the /// configuration file. /// public void WriteData() { System.IO.StreamWriter writer = new System.IO.StreamWriter(this.configFile); writer.WriteLine(this.baseDirectory); // we do not lock the instance since while // the module is being installed there is // no risk of race conditions on the mappings // instance. writer.WriteLine(this.SerializeMappings()); writer.WriteLine(this.bNeedSync); writer.Close(); } /// /// Reads the module data from /// the configuration file. /// public void ReadData() { System.IO.StreamReader reader = new System.IO.StreamReader(this.configFile); this.baseDirectory = reader.ReadLine(); string strMappings = reader.ReadLine(); this.bNeedSync = bool.Parse(reader.ReadLine()); // we do not need to lock the object // here because while the module is // being attached there is no risk of // race conditions this.mappings = this.DeserializeMappings(strMappings); reader.Close(); } /// /// Handles all the events issued to the module. /// /// sender of the event /// event arguments private void DispatchSinkEvent(object sender, PlatformSinkEventArgs sinkEvent) { StorageSinkEvent storageEvent = sinkEvent.Event as StorageSinkEvent; if (storageEvent == null) { storageEvent.Result = false; this.lastError = new ModuleError(this,"Invalid Sink event"); return; } switch(storageEvent.Action) { case StorageActions.AddAssembly : { this.AddAssembly(storageEvent.Param, false); // CHANGE: 4/2/2006: added the syncronization of // the storage lock(this.syncRoot) { this.Synchronize(); } storageEvent.Result = this.lastError == null; } break; case StorageActions.CopyAssembly : { this.CopyAssembly(storageEvent.Param, storageEvent.Param2); storageEvent.Result = this.lastError == null; } break; case StorageActions.RemoveAssembly : { this.RemoveAssembly(storageEvent.Param); storageEvent.Result = this.lastError == null; } break; case StorageActions.QueryType : { TypeDescriptor foundInGAC = this.IsAssemblyInGAC(storageEvent.Param); if (foundInGAC == null) { TypeDescriptor td = this.GetAssemblyFor(storageEvent.Param); if (td == null) { storageEvent.Types = null; } else { storageEvent.Types = new TypeDescriptor[1] { td }; } } else { storageEvent.Types = new TypeDescriptor[1] {foundInGAC}; } storageEvent.Result = this.lastError == null; } break; case StorageActions.QueryAssemblies : { storageEvent.Assemblies = this.GetStoredAssemblies(); storageEvent.Result = this.lastError == null; } break; case StorageActions.QueryTemplates : { storageEvent.Types = this.GetStoredAgentTemplates(); storageEvent.Result = this.lastError == null; } break; case StorageActions.QueryKnowledges : { storageEvent.Types = this.GetStoredKnowledges(); storageEvent.Result = this.lastError == null; } break; case StorageActions.QueryBehaviours : { storageEvent.Types = this.GetStoredBehaviours(); storageEvent.Result = this.lastError == null; } break; } } /// /// Checks if the type belongs to an assembly loaded from the global /// assembly cache /// /// Type to check /// True if found /// For now this method only check if the type to check belongs /// to the mscrlib assembly private TypeDescriptor IsAssemblyInGAC (string typeToCheck) { try { Type type = System.Reflection.Assembly.GetAssembly(typeof(string)).GetType(typeToCheck,true); return new TypeDescriptor(type); } catch (System.Exception) { return null; } } #endregion } /// /// Class Mappings. Holds the references to the array /// of TypeDescriptor objects used to mantain the /// information about the mapping. /// [Serializable] public class Mappings { /// /// Type information about the agent /// templates stored in the storage. /// public TypeDescriptor[] Templates; /// /// Type information about the knowledge /// types stored in the sotrage. /// public TypeDescriptor[] Knowledges; /// /// Type information about the behaviour /// types stored in the storage. /// public TypeDescriptor[] Behaviours; /// /// List of managed assemblie hold in /// the storage. /// public AssemblyName[] Assemblies; /// /// Hashtable containing the mappings /// between the name of the type and /// its descriptor. /// public Hashtable TypeMap; /// /// Constructs a Mappings instance. /// public Mappings() { this.Templates = new TypeDescriptor[0]; this.Knowledges = new TypeDescriptor[0]; this.Behaviours = new TypeDescriptor[0]; this.Assemblies = new AssemblyName[0]; this.TypeMap = new Hashtable(); } } }