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 { /// /// Responsible for managing the current session addins. /// internal class AddinSessionService { #region Inner Types private delegate void TypeSetReferenceValidationDelegate(string reference); #endregion #region Variables private readonly Dictionary m_loadedAddins = new Dictionary(); // Addin full id -> addin private readonly Dictionary m_extensionTypeSets = new Dictionary(); // Type name -> type private readonly Dictionary m_conditionTypes = new Dictionary(); // Type name -> type private readonly Dictionary m_extensionPoints = new Dictionary(); // Path -> extension point private readonly MultiDictionary m_loadedAssemblies = new MultiDictionary(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 /// /// Loaded addins. /// public ICollection LoadedAddins { get { return m_loadedAddins.Values; } } /// /// Loaded extension type sets. /// public IReadOnlyDictionary ExtensionTypeSets { get { return m_extensionTypeSets.AsReadOnly(); } } /// /// Loaded condition types. /// public IReadOnlyDictionary ConditionTypes { get { return m_conditionTypes.AsReadOnly(); } } /// /// Loaded extension points. /// public IReadOnlyDictionary ExtensionPoints { get { return m_extensionPoints.AsReadOnly(); } } /// /// Extension tree. /// 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; } /// /// Registers the given on the at the given . /// /// /// public void AddExtensionChangedHandler(string path, EventHandler handler) { Throw.IfEmpty(path, "path"); Throw.IfNull(handler, "handler"); path = ExtensionPoint.NormalizePath(path); ChangedHandlerManager.Add(path, handler); } /// /// Unregisters the given on the at the given . /// /// /// public void RemoveExtensionChangedHandler(string path, EventHandler 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 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(string elementType, IDictionary dictionary, IEnumerable elements, Converter 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(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 removedExtensionPointPaths = new List(); 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 } }