JD2022-TU1/main/tools/framework/JD.TeaBoxAddinSystem/Addins/Internal/AddinSessionService.cs

797 lines
27 KiB
C#

using System;
using System.Collections.Generic;
using JD.Collections;
using JD.Collections.Extensions;
using JD.Kernel.StaticHelpers;
using JD.LogService;
using JD.TeaBoxAddinSystem.Addins.Description;
using JD.TeaBoxAddinSystem.Addins.Description.Conditions;
using JD.TeaBoxAddinSystem.Addins.Description.FileReferences;
namespace JD.TeaBoxAddinSystem.Addins.Internal
{
/// <summary>
/// Responsible for managing the current session addins.
/// </summary>
internal class AddinSessionService
{
#region Inner Types
private delegate void TypeSetReferenceValidationDelegate(string reference);
#endregion
#region Variables
private readonly Dictionary<string, Addin> m_loadedAddins = new Dictionary<string, Addin>(); // Addin full id -> addin
private readonly Dictionary<string, ExtensionTypeSet> m_extensionTypeSets = new Dictionary<string, ExtensionTypeSet>(); // Type name -> type
private readonly Dictionary<string, ConditionType> m_conditionTypes = new Dictionary<string, ConditionType>(); // Type name -> type
private readonly Dictionary<string, ExtensionPoint> m_extensionPoints = new Dictionary<string, ExtensionPoint>(); // Path -> extension point
private readonly MultiDictionary<string, RuntimeAssembly> m_loadedAssemblies = new MultiDictionary<string, RuntimeAssembly>(StringComparer.OrdinalIgnoreCase); // Assembly Name -> Loaded assembly. Needed for correct deserialization in framework (assembly resolving).
private readonly DependencyManager m_dependencies = new DependencyManager();
private readonly ConditionManager m_conditionManager = new ConditionManager();
private readonly ChangedHandlerManager m_handlerManager = new ChangedHandlerManager();
private readonly TreeNode m_extensionTree = new TreeNode();
#endregion
#region Properties
/// <summary>
/// Loaded addins.
/// </summary>
public ICollection<Addin> LoadedAddins
{
get
{
return m_loadedAddins.Values;
}
}
/// <summary>
/// Loaded extension type sets.
/// </summary>
public IReadOnlyDictionary<string, ExtensionTypeSet> ExtensionTypeSets
{
get
{
return m_extensionTypeSets.AsReadOnly();
}
}
/// <summary>
/// Loaded condition types.
/// </summary>
public IReadOnlyDictionary<string, ConditionType> ConditionTypes
{
get
{
return m_conditionTypes.AsReadOnly();
}
}
/// <summary>
/// Loaded extension points.
/// </summary>
public IReadOnlyDictionary<string, ExtensionPoint> ExtensionPoints
{
get
{
return m_extensionPoints.AsReadOnly();
}
}
/// <summary>
/// Extension tree.
/// </summary>
public TreeNode ExtensionTree
{
get
{
return m_extensionTree;
}
}
internal ConditionManager ConditionManager
{
get
{
return m_conditionManager;
}
}
internal ChangedHandlerManager ChangedHandlerManager
{
get
{
return m_handlerManager;
}
}
#endregion
#region Methods
#region General Methods
public bool IsAddinLoaded(Addin addin)
{
if (addin == null)
{
return false;
}
Addin loadedAddin;
return m_loadedAddins.TryGetValue(addin.FullId, out loadedAddin) && loadedAddin == addin;
}
public bool IsModuleLoaded(Module module)
{
if (module == null || module.Addin == null)
{
return false;
}
if (!IsAddinLoaded(module.Addin))
{
return false;
}
if (module.IsMainModule)
{
return true;
}
foreach(AddinReference dependency in module.Dependencies)
{
if (!ResolveDependency(dependency))
{
return false;
}
}
return true;
}
/// <summary>
/// Registers the given <paramref name="handler"/> on the <see cref="ExtensionNode"/> at the given <paramref name="path"/>.
/// </summary>
/// <param name="path"></param>
/// <param name="handler"></param>
public void AddExtensionChangedHandler(string path, EventHandler<ExtensionNodeChangedEventArgs> handler)
{
Throw.IfEmpty(path, "path");
Throw.IfNull(handler, "handler");
path = ExtensionPoint.NormalizePath(path);
ChangedHandlerManager.Add(path, handler);
}
/// <summary>
/// Unregisters the given <paramref name="handler"/> on the <see cref="ExtensionNode"/> at the given <paramref name="path"/>.
/// </summary>
/// <param name="path"></param>
/// <param name="handler"></param>
public void RemoveExtensionChangedHandler(string path, EventHandler<ExtensionNodeChangedEventArgs> handler)
{
Throw.IfEmpty(path, "path");
Throw.IfNull(handler, "handler");
path = ExtensionPoint.NormalizePath(path);
ChangedHandlerManager.Remove(path, handler);
}
internal ExtensionType FindExtensionType(ExtensionTypeSet typeSet, string typeName)
{
System.Diagnostics.Debug.Assert(typeSet != null);
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(typeName));
// Check in immediate types
if (typeSet.ExtensionTypes.Contains(typeName))
{
return typeSet.ExtensionTypes[typeName];
}
// Check references.
foreach (string reference in typeSet.ExtensionTypeSets)
{
if (ExtensionTypeSets.ContainsKey(reference))
{
// Recurse
return FindExtensionType(ExtensionTypeSets[reference], typeName);
}
}
return null;
}
internal ConditionType FindConditionType(string typeName)
{
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(typeName));
// Check in immediate types
ConditionType conditionType;
ConditionTypes.TryGetValue(typeName, out conditionType);
return conditionType;
}
internal IEnumerable<Module> GetLoadedDependencies(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
foreach (AddinReference dependency in module.Dependencies)
{
Addin addin;
if (m_loadedAddins.TryGetValue(dependency.Name, out addin))
{
yield return addin.MainModule;
}
}
}
internal System.Reflection.Assembly GetAssembly(string assemblyName)
{
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(assemblyName));
string simpleAssemblyName = assemblyName.ToLowerInvariant();
int commaIndex = simpleAssemblyName.IndexOf(',');
if (commaIndex > 0)
{
simpleAssemblyName = simpleAssemblyName.Substring(0, commaIndex);
}
foreach (RuntimeAssembly runtimeAssembly in m_loadedAssemblies.Values)
{
if (runtimeAssembly.AssemblyName.ToLowerInvariant().Contains(simpleAssemblyName))
{
System.Reflection.Assembly assembly = runtimeAssembly.Assembly;
if (assembly != null && string.Equals(assembly.FullName, assemblyName, StringComparison.OrdinalIgnoreCase))
{
return assembly;
}
}
}
return null;
}
#endregion
#region Loading
public bool LoadAddin(Addin addin)
{
Throw.IfNull(addin, "addin");
if (IsAddinLoaded(addin))
{
return true;
}
if (m_loadedAddins.ContainsKey(addin.FullId))
{
throw new InvalidOperationException($"Another addin with the id {addin.FullId} is already loaded.");
}
// We need to proceed in two passes to be "error-safe", i.e. in case of an error, the session service state must be reverted to what it was before the call.
// So we verify everything first and then proceed with the actual operation.
// Verification step
if (!ValidateAddin(addin))
{
return false;
}
// Actual loading starts here
if (!LoadAddinImpl(addin))
{
return false;
}
PostLoad(addin);
return true;
}
private bool ValidateAddin(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
bool encounteredError = false;
// Check strong dependencies
foreach (AddinReference dependency in addin.MainModule.Dependencies)
{
if (!ResolveDependency(dependency))
{
AddinManager.Log(new Error(LogCategory.Error, "Cannot load addin {0} because it has an unresolved dependency: {1}", addin.FullId, dependency));
encounteredError = true;
}
}
if (encounteredError)
{
return false;
}
encounteredError |= CheckNoExistingElement("extension type set", m_extensionTypeSets, addin.ExtensionTypeSets, delegate(ExtensionTypeSet typeSet)
{
System.Diagnostics.Debug.Assert(typeSet != null);
return typeSet.Name;
});
encounteredError |= CheckNoExistingElement("condition type", m_conditionTypes, addin.ConditionTypes, delegate(ConditionType conditionType)
{
System.Diagnostics.Debug.Assert(conditionType != null);
return conditionType.Name;
});
encounteredError |= CheckNoExistingElement("extension point", m_extensionPoints, addin.ExtensionPoints, delegate(ExtensionPoint extensionPoint)
{
System.Diagnostics.Debug.Assert(extensionPoint != null);
return extensionPoint.Path;
});
return !encounteredError;
}
private static bool CheckNoExistingElement<T>(string elementType, IDictionary<string, T> dictionary, IEnumerable<T> elements, Converter<T, string> converter) where T : AddinObject
{
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(elementType));
System.Diagnostics.Debug.Assert(dictionary != null);
System.Diagnostics.Debug.Assert(elements != null);
System.Diagnostics.Debug.Assert(converter != null);
bool encounteredError = false;
foreach (T element in elements)
{
string key = converter(element);
T existingElement;
if (dictionary.TryGetValue(key, out existingElement))
{
LogNodeTypeError(elementType, key, element, existingElement);
encounteredError = true;
}
}
return encounteredError;
}
private static void LogNodeTypeError(string nodeType, string name, AddinObject element, AddinObject existingElement)
{
element.LogError("Cannot load addin {0} because an {1} named {2} is already provided by the addin {3} at {4}",
element.Addin.FullId, nodeType, name, existingElement.Addin.FullId, existingElement.FileLocation);
}
private bool ResolveDependency(AddinReference reference)
{
System.Diagnostics.Debug.Assert(reference != null);
// Check if an addin is loaded with the correct name
Addin loadedAddin;
if (!m_loadedAddins.TryGetValue(reference.Name, out loadedAddin))
{
return false;
}
// Check version
return reference.Validate(loadedAddin.Manifest.Version);
}
private bool LoadAddinImpl(Addin addin)
{
// Add extension type sets
foreach (ExtensionTypeSet typeSet in addin.ExtensionTypeSets)
{
InsertExtensionTypeSet(typeSet);
}
// Add condition types
foreach (ConditionType conditionType in addin.ConditionTypes)
{
InsertConditionType(conditionType);
}
// Add extension points
foreach (ExtensionPoint extensionPoint in addin.ExtensionPoints)
{
InsertExtensionPoint(extensionPoint);
}
// Load modules
LoadModules(addin);
m_loadedAddins.Add(addin.FullId, addin);
return true;
}
private void PostLoad(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
// Validate global extension type set references (they are validated in post-load because they might refer to other type sets that were just loaded)
foreach (ExtensionTypeSet typeSet in addin.ExtensionTypeSets)
{
ValidateExtensionTypeSetReferences(addin, typeSet, delegate(string reference)
{
typeSet.LogWarning("Extension type set {0} in addin {1} references an invalid type set {2}", typeSet.Name, addin.FullId, reference);
});
}
// Validate extension type set references on extension points
foreach (ExtensionPoint point in addin.ExtensionPoints)
{
ValidateExtensionTypeSetReferences(addin, point.ExtensionTypeSet, delegate(string reference)
{
point.LogWarning("Extension point {0} in addin {1} references an invalid type set {2}", point.Path, addin.FullId, reference);
});
}
// Active optional dependent modules
ActivateOptionalModules(addin);
// For each loaded extension point, trigger changed event
foreach (ExtensionPoint point in addin.ExtensionPoints)
{
System.Diagnostics.Debug.Assert(ExtensionTree[point.Path] != null);
ChangedHandlerManager.OnChanged(new ExtensionNodeChangedEventArgs(ExtensionTree[point.Path]));
}
}
private void ValidateExtensionTypeSetReferences(Addin addin, ExtensionTypeSet typeSet, TypeSetReferenceValidationDelegate validationDelegate)
{
System.Diagnostics.Debug.Assert(addin != null);
System.Diagnostics.Debug.Assert(typeSet != null);
System.Diagnostics.Debug.Assert(validationDelegate != null);
foreach (string reference in typeSet.ExtensionTypeSets)
{
if (!ExtensionTypeSets.ContainsKey(reference))
{
validationDelegate(reference);
}
}
}
private void LoadModules(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
LoadModule(addin.MainModule);
foreach (Module module in addin.Modules)
{
LoadModule(module);
}
}
private void ActivateOptionalModules(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
foreach (Module module in m_dependencies.GetWeakDependencies(addin.FullId))
{
ActivateModule(module);
}
}
private void LoadModule(Module module)
{
// Register module
m_dependencies.RegisterModule(module);
ActivateModule(module);
}
private void ActivateModule(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
// Resolve dependencies
bool resolved = true;
foreach (AddinReference dependency in module.Dependencies)
{
if (!ResolveDependency(dependency))
{
System.Diagnostics.Debug.Assert(!module.IsMainModule);
System.Diagnostics.Debug.Assert(module.Addin != null);
AddinManager.Log(new Message("Will not load optional module in addin {0} because its dependency {1} cannot be resolved", module.Addin.FullId, dependency) { Importance = MessageImportance.Low });
resolved = false;
}
}
if (!resolved)
{
return;
}
// Register assemblies
foreach (AssemblyFileReference assemblyReference in module.Assemblies)
{
m_loadedAssemblies.Add(assemblyReference.Runtime.AssemblyName, assemblyReference.Runtime);
}
// Load extensions
foreach (Extension extension in module.Extensions)
{
LoadExtension(extension);
}
}
private void LoadExtension(Extension extension)
{
System.Diagnostics.Debug.Assert(extension != null);
// Find the path in the tree
TreeNode node = ExtensionTree[extension.Path];
if (node == null || node.AllowedTypes == null)
{
extension.LogWarning("Cannot load extension {0} because the path is not extensible", extension.Path);
return;
}
var extensionPoint = node.ExtensionPoint;
if (extensionPoint != null && extensionPoint.IsDeprecated)
{
extension.LogWarning("Extension {0} is deprecated: {1}", extension.Path, extensionPoint.DeprecationMessage);
}
node.LoadExtension(extension);
}
private void InsertExtensionTypeSet(ExtensionTypeSet typeSet)
{
System.Diagnostics.Debug.Assert(typeSet != null);
System.Diagnostics.Debug.Assert(!m_extensionTypeSets.ContainsKey(typeSet.Name));
m_extensionTypeSets.Add(typeSet.Name, typeSet);
}
private void InsertConditionType(ConditionType conditionType)
{
System.Diagnostics.Debug.Assert(conditionType != null);
System.Diagnostics.Debug.Assert(!m_conditionTypes.ContainsKey(conditionType.Name));
m_conditionTypes.Add(conditionType.Name, conditionType);
}
private void InsertExtensionPoint(ExtensionPoint extensionPoint)
{
System.Diagnostics.Debug.Assert(extensionPoint != null);
System.Diagnostics.Debug.Assert(!m_extensionPoints.ContainsKey(extensionPoint.Path));
m_extensionPoints.Add(extensionPoint.Path, extensionPoint);
TreeNode extensionPathNode = ExtensionTree.GetNode(extensionPoint.Path, true);
System.Diagnostics.Debug.Assert(extensionPathNode.ExtensionPoint == null);
extensionPathNode.AllowedTypes = extensionPoint;
}
#endregion
#region Unloading
public bool UnloadAddin(Addin addin)
{
Throw.IfNull(addin, "addin");
// Make sure we can unload the addin
if (!ValidateUnloadAddin(addin))
{
return false;
}
return UnloadAddinImpl(addin);
}
private bool ValidateUnloadAddin(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
if (!IsAddinLoaded(addin))
{
return false;
}
// Check dependent assemblies.
if (m_dependencies.HasStrongDependencies(addin.FullId))
{
AddinManager.Log(new Error(LogCategory.Error, "Cannot unload addin {0} because there are addins that directly depend on it", addin.FullId));
return false;
}
return true;
}
private bool UnloadAddinImpl(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
// Deactivate dependent modules
DeactivateOptionalModules(addin);
// Unload modules
UnloadModules(addin);
// Remove extension points
foreach (ExtensionPoint extensionPoint in addin.ExtensionPoints)
{
System.Diagnostics.Debug.Assert(m_extensionPoints.ContainsKey(extensionPoint.Path) && m_extensionPoints[extensionPoint.Path] == extensionPoint);
m_extensionPoints.Remove(extensionPoint.Path);
RemoveExtensionPoint(extensionPoint);
}
// Remove condition types
foreach (ConditionType conditionType in addin.ConditionTypes)
{
System.Diagnostics.Debug.Assert(m_conditionTypes.ContainsKey(conditionType.Name) && m_conditionTypes[conditionType.Name] == conditionType);
m_conditionTypes.Remove(conditionType.Name);
}
// Remove extension type sets
foreach (ExtensionTypeSet typeSet in addin.ExtensionTypeSets)
{
System.Diagnostics.Debug.Assert(m_extensionTypeSets.ContainsKey(typeSet.Name) && m_extensionTypeSets[typeSet.Name] == typeSet);
m_extensionTypeSets.Remove(typeSet.Name);
}
System.Diagnostics.Debug.Assert(m_loadedAddins.ContainsKey(addin.FullId));
System.Diagnostics.Debug.Assert(m_loadedAddins[addin.FullId] == addin);
m_loadedAddins.Remove(addin.FullId);
// For each unloaded extension point, trigger an OnChanged event after unloading.
foreach (ExtensionPoint point in addin.ExtensionPoints)
{
ChangedHandlerManager.OnChanged(new ExtensionNodeChangedEventArgs(point.Path));
}
return true;
}
private void UnloadModules(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
foreach (Module module in addin.Modules)
{
UnloadModule(module);
}
UnloadModule(addin.MainModule);
}
private void DeactivateOptionalModules(Addin addin)
{
System.Diagnostics.Debug.Assert(addin != null);
foreach (Module module in m_dependencies.GetWeakDependencies(addin.FullId))
{
DeactivateModule(module);
}
}
private void UnloadModule(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
DeactivateModule(module);
// Unregister module
m_dependencies.UnregisterModule(module);
}
private void DeactivateModule(Module module)
{
System.Diagnostics.Debug.Assert(module != null);
// Unload extensions
foreach (Extension extension in module.Extensions)
{
UnloadExtension(extension);
}
// Unregister assemblies
foreach (AssemblyFileReference assemblyReference in module.Assemblies)
{
m_loadedAssemblies.Remove(assemblyReference.Runtime.AssemblyName, assemblyReference.Runtime);
}
}
private void UnloadExtension(Extension extension)
{
System.Diagnostics.Debug.Assert(extension != null);
// Find the path in the tree
TreeNode node = ExtensionTree[extension.Path];
if (node != null) // It's possible this extension could not be loaded for some reason.
{
node.UnloadExtension(extension);
}
}
public void UnloadAllAddins(bool destroyHandlers)
{
/*// Big, complicated loop to unload addins correctly.
int lastCount = LoadedAddins.Count;
while (LoadedAddins.Count > 0)
{
foreach (Addin addin in new List<Addin>(LoadedAddins))
{
if (GetDependentAddins(addin).Count == 0)
{
UnloadAddin(addin);
}
}
if (LoadedAddins.Count == lastCount)
{
throw new InvalidProgramException("Circular addin dependency detected");
}
lastCount = LoadedAddins.Count;
}*/
// Trigger a changed event for all unloaded extension points.
List<string> removedExtensionPointPaths = new List<string>();
if (!destroyHandlers)
{
foreach (ExtensionPoint point in m_extensionPoints.Values)
{
System.Diagnostics.Debug.Assert(ExtensionTree[point.Path] != null);
System.Diagnostics.Debug.Assert(ExtensionTree[point.Path].ExtensionPoint != null);
removedExtensionPointPaths.Add(point.Path);
}
}
m_loadedAddins.Clear();
m_extensionPoints.Clear();
m_extensionTypeSets.Clear();
m_conditionTypes.Clear();
m_extensionTree.Clear();
m_dependencies.Clear();
m_conditionManager.Clear();
foreach (string extensionPointPath in removedExtensionPointPaths)
{
ChangedHandlerManager.OnChanged(new ExtensionNodeChangedEventArgs(extensionPointPath));
}
if (destroyHandlers)
{
ChangedHandlerManager.Clear();
}
System.Diagnostics.Debug.Assert(m_loadedAddins.Count == 0);
}
private void RemoveExtensionPoint(ExtensionPoint extensionPoint)
{
TreeNode extensionNode = ExtensionTree[extensionPoint.Path];
System.Diagnostics.Debug.Assert(extensionNode != null);
System.Diagnostics.Debug.Assert(extensionNode.ExtensionPoint == extensionPoint);
extensionNode.AllowedTypes = null;
RemoveTreeNodeIfPossible(extensionNode);
}
private void RemoveTreeNodeIfPossible(TreeNode node)
{
System.Diagnostics.Debug.Assert(node != null);
if (node.ChildNodes.Count > 0 || node.Parent == null)
{
return;
}
TreeNode parent = node.Parent;
parent.ChildNodesInternal.Remove(node);
RemoveTreeNodeIfPossible(parent);
}
#endregion
#endregion
}
}