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