7
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
using System.Collections;
34
using System.Reflection;
36
using Mono.Addins.Description;
37
using Mono.Addins.Database;
38
using Mono.Addins.Localization;
39
using System.Collections.Generic;
47
/// This class allows hosting several independent add-in engines in a single application domain.
48
/// In general, applications use the AddinManager class to query and manage extensions. This class is static,
49
/// so the API is easily accessible. However, some kind applications may need to use several isolated
50
/// add-in engines, and in this case the AddinManager class can't be used, because it is bound to a single
51
/// add-in engine. Those applications can instead create several instances of the AddinEngine class. Each
52
/// add-in engine can be independently initialized with different add-in registries and extension models.
54
public class AddinEngine: ExtensionContext
57
string startupDirectory;
58
AddinRegistry registry;
59
IAddinInstaller installer;
61
bool checkAssemblyLoadConflicts;
62
Hashtable loadedAddins = new Hashtable ();
63
Dictionary<string,ExtensionNodeSet> nodeSets = new Dictionary<string, ExtensionNodeSet> ();
64
Hashtable autoExtensionTypes = new Hashtable ();
65
Hashtable loadedAssemblies = new Hashtable ();
66
AddinLocalizer defaultLocalizer;
67
IProgressStatus defaultProgressStatus = new ConsoleProgressStatus (false);
70
/// Raised when there is an error while loading an add-in
72
public static event AddinErrorEventHandler AddinLoadError;
75
/// Raised when an add-in is loaded
77
public static event AddinEventHandler AddinLoaded;
80
/// Raised when an add-in is unloaded
82
public static event AddinEventHandler AddinUnloaded;
85
/// Initializes a new instance of the <see cref="Mono.Addins.AddinEngine"/> class.
92
/// Initializes the add-in engine
94
/// <param name="configDir">
95
/// Location of the add-in registry.
97
/// <remarks>The add-in engine needs to be initialized before doing any add-in operation.
98
/// When initialized with this method, it will look for add-in in the add-in registry
99
/// located in the specified path.
101
public void Initialize (string configDir)
106
Assembly asm = Assembly.GetEntryAssembly ();
107
if (asm == null) asm = Assembly.GetCallingAssembly ();
108
Initialize (asm, configDir, null, null);
112
/// Initializes the add-in engine.
114
/// <param name='configDir'>
115
/// Location of the add-in registry.
117
/// <param name='addinsDir'>
118
/// Add-ins directory. If the path is relative, it is considered to be relative
119
/// to the configDir directory.
122
/// The add-in engine needs to be initialized before doing any add-in operation.
123
/// Configuration information about the add-in registry will be stored in the
124
/// provided location. The add-in engine will look for add-ins in the provided
125
/// 'addinsDir' directory.
127
/// When specifying a path, it is possible to use a special folder name as root.
128
/// For example: [Personal]/.config/MyApp. In this case, [Personal] will be replaced
129
/// by the location of the Environment.SpecialFolder.Personal folder. Any value
130
/// of the Environment.SpecialFolder enumeration can be used (always between square
133
public void Initialize (string configDir, string addinsDir)
138
Assembly asm = Assembly.GetEntryAssembly ();
139
if (asm == null) asm = Assembly.GetCallingAssembly ();
140
Initialize (asm, configDir, addinsDir, null);
144
/// Initializes the add-in engine.
146
/// <param name='configDir'>
147
/// Location of the add-in registry.
149
/// <param name='addinsDir'>
150
/// Add-ins directory. If the path is relative, it is considered to be relative
151
/// to the configDir directory.
153
/// <param name='databaseDir'>
154
/// Location of the add-in database. If the path is relative, it is considered to be relative
155
/// to the configDir directory.
158
/// The add-in engine needs to be initialized before doing any add-in operation.
159
/// Configuration information about the add-in registry will be stored in the
160
/// provided location. The add-in engine will look for add-ins in the provided
161
/// 'addinsDir' directory. Cached information about add-ins will be stored in
162
/// the 'databaseDir' directory.
164
/// When specifying a path, it is possible to use a special folder name as root.
165
/// For example: [Personal]/.config/MyApp. In this case, [Personal] will be replaced
166
/// by the location of the Environment.SpecialFolder.Personal folder. Any value
167
/// of the Environment.SpecialFolder enumeration can be used (always between square
170
public void Initialize (string configDir, string addinsDir, string databaseDir)
175
Assembly asm = Assembly.GetEntryAssembly ();
176
if (asm == null) asm = Assembly.GetCallingAssembly ();
177
Initialize (asm, configDir, addinsDir, databaseDir);
180
internal void Initialize (Assembly startupAsm, string configDir, string addinsDir, string databaseDir)
187
string asmFile = new Uri (startupAsm.CodeBase).LocalPath;
188
startupDirectory = System.IO.Path.GetDirectoryName (asmFile);
190
string customDir = Environment.GetEnvironmentVariable ("MONO_ADDINS_REGISTRY");
191
if (customDir != null && customDir.Length > 0)
192
configDir = customDir;
194
if (configDir == null || configDir.Length == 0)
195
registry = AddinRegistry.GetGlobalRegistry (this, startupDirectory);
197
registry = new AddinRegistry (this, configDir, startupDirectory, addinsDir, databaseDir);
199
if (registry.CreateHostAddinsFile (asmFile) || registry.UnknownDomain)
200
registry.Update (new ConsoleProgressStatus (false));
205
OnAssemblyLoaded (null, null);
206
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler (OnAssemblyLoaded);
210
/// Finalizes the add-in engine.
212
public void Shutdown ()
215
AppDomain.CurrentDomain.AssemblyLoad -= new AssemblyLoadEventHandler (OnAssemblyLoaded);
216
loadedAddins.Clear ();
217
loadedAssemblies.Clear ();
220
startupDirectory = null;
225
/// Sets the default localizer to be used for this add-in engine
227
/// <param name="localizer">
228
/// The add-in localizer
230
public void InitializeDefaultLocalizer (IAddinLocalizer localizer)
233
if (localizer != null)
234
defaultLocalizer = new AddinLocalizer (localizer);
236
defaultLocalizer = null;
239
internal string StartupDirectory {
240
get { return startupDirectory; }
244
/// Gets whether the add-in engine has been initialized.
246
public bool IsInitialized {
247
get { return initialized; }
251
/// Gets the default add-in installer
254
/// The default installer is used by the CheckInstalled method to request
255
/// the installation of missing add-ins.
257
public IAddinInstaller DefaultInstaller {
258
get { return installer; }
259
set { installer = value; }
263
/// Gets the default localizer for this add-in engine
265
public AddinLocalizer DefaultLocalizer {
268
if (defaultLocalizer != null)
269
return defaultLocalizer;
271
return NullLocalizer.Instance;
275
internal ExtensionContext DefaultContext {
280
/// Gets the localizer for the add-in that is invoking this property
282
public AddinLocalizer CurrentLocalizer {
285
Assembly asm = Assembly.GetCallingAssembly ();
286
RuntimeAddin addin = GetAddinForAssembly (asm);
288
return addin.Localizer;
290
return DefaultLocalizer;
295
/// Gets a reference to the RuntimeAddin object for the add-in that is invoking this property
297
public RuntimeAddin CurrentAddin {
300
Assembly asm = Assembly.GetCallingAssembly ();
301
return GetAddinForAssembly (asm);
306
/// Gets the add-in registry bound to this add-in engine
308
public AddinRegistry Registry {
315
internal RuntimeAddin GetAddinForAssembly (Assembly asm)
317
return (RuntimeAddin) loadedAssemblies [asm];
321
/// Checks if the provided add-ins are installed, and requests the installation of those
324
/// <param name="message">
325
/// Message to show to the user when new add-ins have to be installed.
327
/// <param name="addinIds">
328
/// List of IDs of the add-ins to be checked.
331
/// This method checks if the specified add-ins are installed.
332
/// If some of the add-ins are not installed, it will use
333
/// the installer assigned to the DefaultAddinInstaller property
334
/// to install them. If the installation fails, or if DefaultAddinInstaller
335
/// is not set, an exception will be thrown.
337
public void CheckInstalled (string message, params string[] addinIds)
339
ArrayList notInstalled = new ArrayList ();
340
foreach (string id in addinIds) {
341
Addin addin = Registry.GetAddin (id, false);
343
// The add-in is already installed
344
// If the add-in is disabled, enable it now
346
addin.Enabled = true;
348
notInstalled.Add (id);
351
if (notInstalled.Count == 0)
353
if (installer == null)
354
throw new InvalidOperationException ("Add-in installer not set");
356
// Install the add-ins
357
installer.InstallAddins (Registry, message, (string[]) notInstalled.ToArray (typeof(string)));
360
// Enables or disables conflict checking while loading assemblies.
361
// Disabling makes loading faster, but less safe.
362
internal bool CheckAssemblyLoadConflicts {
363
get { return checkAssemblyLoadConflicts; }
364
set { checkAssemblyLoadConflicts = value; }
368
/// Checks if an add-in has been loaded.
370
/// <param name="id">
371
/// Full identifier of the add-in.
374
/// True if the add-in is loaded.
376
public bool IsAddinLoaded (string id)
379
return loadedAddins.Contains (Addin.GetIdName (id));
382
internal RuntimeAddin GetAddin (string id)
384
return (RuntimeAddin) loadedAddins [Addin.GetIdName (id)];
387
internal void ActivateAddin (string id)
389
ActivateAddinExtensions (id);
392
internal void UnloadAddin (string id)
394
RemoveAddinExtensions (id);
396
RuntimeAddin addin = GetAddin (id);
398
addin.UnloadExtensions ();
399
loadedAddins.Remove (Addin.GetIdName (id));
400
if (addin.AssembliesLoaded) {
401
foreach (Assembly asm in addin.Assemblies)
402
loadedAssemblies.Remove (asm);
404
ReportAddinUnload (id);
409
/// Forces the loading of an add-in.
411
/// <param name="statusMonitor">
412
/// Status monitor to keep track of the loading process.
414
/// <param name="id">
415
/// Full identifier of the add-in to load.
418
/// This method loads all assemblies that belong to an add-in in memory.
419
/// All add-ins on which the specified add-in depends will also be loaded.
420
/// Notice that in general add-ins don't need to be explicitely loaded using
421
/// this method, since the add-in engine will load them on demand.
423
public void LoadAddin (IProgressStatus statusMonitor, string id)
426
LoadAddin (statusMonitor, id, true);
429
internal bool LoadAddin (IProgressStatus statusMonitor, string id, bool throwExceptions)
432
if (IsAddinLoaded (id))
435
if (!Registry.IsAddinEnabled (id)) {
436
string msg = GettextCatalog.GetString ("Disabled add-ins can't be loaded.");
437
ReportError (msg, id, null, false);
439
throw new InvalidOperationException (msg);
443
ArrayList addins = new ArrayList ();
444
Stack depCheck = new Stack ();
445
ResolveLoadDependencies (addins, depCheck, id, false);
448
if (statusMonitor != null)
449
statusMonitor.SetMessage ("Loading Addins");
451
for (int n=0; n<addins.Count; n++) {
453
if (statusMonitor != null)
454
statusMonitor.SetProgress ((double) n / (double)addins.Count);
456
Addin iad = (Addin) addins [n];
457
if (IsAddinLoaded (iad.Id))
460
if (statusMonitor != null)
461
statusMonitor.SetMessage (string.Format(GettextCatalog.GetString("Loading {0} add-in"), iad.Id));
463
if (!InsertAddin (statusMonitor, iad))
468
catch (Exception ex) {
469
ReportError ("Add-in could not be loaded: " + ex.Message, id, ex, false);
470
if (statusMonitor != null)
471
statusMonitor.ReportError ("Add-in '" + id + "' could not be loaded.", ex);
478
internal override void ResetCachedData ()
480
foreach (RuntimeAddin ad in loadedAddins.Values)
481
ad.Addin.ResetCachedData ();
482
base.ResetCachedData ();
485
bool InsertAddin (IProgressStatus statusMonitor, Addin iad)
488
RuntimeAddin p = new RuntimeAddin (this);
490
// Read the config file and load the add-in assemblies
491
AddinDescription description = p.Load (iad);
493
// Register the add-in
494
loadedAddins [Addin.GetIdName (p.Id)] = p;
496
if (!AddinDatabase.RunningSetupProcess) {
497
// Load the extension points and other addin data
499
foreach (ExtensionNodeSet rel in description.ExtensionNodeSets) {
500
RegisterNodeSet (iad.Id, rel);
503
foreach (ConditionTypeDescription cond in description.ConditionTypes) {
504
Type ctype = p.GetType (cond.TypeName, true);
505
RegisterCondition (cond.Id, ctype);
509
foreach (ExtensionPoint ep in description.ExtensionPoints)
510
InsertExtensionPoint (p, ep);
513
NotifyAddinLoaded (p);
514
ReportAddinLoad (p.Id);
517
catch (Exception ex) {
518
ReportError ("Add-in could not be loaded", iad.Id, ex, false);
519
if (statusMonitor != null)
520
statusMonitor.ReportError ("Add-in '" + iad.Id + "' could not be loaded.", ex);
525
internal void RegisterAssemblies (RuntimeAddin addin)
527
foreach (Assembly asm in addin.Assemblies)
528
loadedAssemblies [asm] = addin;
531
internal void InsertExtensionPoint (RuntimeAddin addin, ExtensionPoint ep)
533
CreateExtensionPoint (ep);
534
foreach (ExtensionNodeType nt in ep.NodeSet.NodeTypes) {
535
if (nt.ObjectTypeName.Length > 0) {
536
Type ntype = addin.GetType (nt.ObjectTypeName, true);
537
RegisterAutoTypeExtensionPoint (ntype, ep.Path);
542
bool ResolveLoadDependencies (ArrayList addins, Stack depCheck, string id, bool optional)
544
if (IsAddinLoaded (id))
547
if (depCheck.Contains (id))
548
throw new InvalidOperationException ("A cyclic addin dependency has been detected.");
552
Addin iad = Registry.GetAddin (id);
553
if (iad == null || !iad.Enabled) {
556
else if (iad != null && !iad.Enabled)
557
throw new MissingDependencyException (GettextCatalog.GetString ("The required addin '{0}' is disabled.", id));
559
throw new MissingDependencyException (GettextCatalog.GetString ("The required addin '{0}' is not installed.", id));
562
// If this addin has already been requested, bring it to the head
563
// of the list, so it is loaded earlier than before.
567
foreach (Dependency dep in iad.AddinInfo.Dependencies) {
568
AddinDependency adep = dep as AddinDependency;
571
string adepid = Addin.GetFullId (iad.AddinInfo.Namespace, adep.AddinId, adep.Version);
572
ResolveLoadDependencies (addins, depCheck, adepid, false);
573
} catch (MissingDependencyException) {
582
if (iad.AddinInfo.OptionalDependencies != null) {
583
foreach (Dependency dep in iad.AddinInfo.OptionalDependencies) {
584
AddinDependency adep = dep as AddinDependency;
586
string adepid = Addin.GetFullId (iad.Namespace, adep.AddinId, adep.Version);
587
if (!ResolveLoadDependencies (addins, depCheck, adepid, true))
597
internal void RegisterNodeSet (string addinId, ExtensionNodeSet nset)
599
nset.SourceAddinId = addinId;
600
nodeSets [nset.Id] = nset;
603
internal void UnregisterAddinNodeSets (string addinId)
605
foreach (var nset in nodeSets.Values.Where (n => n.SourceAddinId == addinId).ToArray ())
606
nodeSets.Remove (nset.Id);
609
internal string GetNodeTypeAddin (ExtensionNodeSet nset, string type, string callingAddinId)
611
ExtensionNodeType nt = FindType (nset, type, callingAddinId);
618
internal ExtensionNodeType FindType (ExtensionNodeSet nset, string name, string callingAddinId)
623
foreach (ExtensionNodeType nt in nset.NodeTypes) {
628
foreach (string ns in nset.NodeSets) {
629
ExtensionNodeSet regSet;
630
if (!nodeSets.TryGetValue (ns, out regSet)) {
631
ReportError ("Unknown node set: " + ns, callingAddinId, null, false);
634
ExtensionNodeType nt = FindType (regSet, name, callingAddinId);
641
internal void RegisterAutoTypeExtensionPoint (Type type, string path)
643
autoExtensionTypes [type] = path;
646
internal void UnregisterAutoTypeExtensionPoint (Type type, string path)
648
autoExtensionTypes.Remove (type);
651
internal string GetAutoTypeExtensionPoint (Type type)
653
return autoExtensionTypes [type] as string;
656
void OnAssemblyLoaded (object s, AssemblyLoadEventArgs a)
659
CheckHostAssembly (a.LoadedAssembly);
662
internal void ActivateRoots ()
664
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
665
CheckHostAssembly (asm);
668
void CheckHostAssembly (Assembly asm)
670
if (AddinDatabase.RunningSetupProcess || asm is System.Reflection.Emit.AssemblyBuilder || asm.IsDynamic)
674
codeBase = asm.CodeBase;
679
if (!Uri.TryCreate (codeBase, UriKind.Absolute, out u))
681
string asmFile = u.LocalPath;
682
Addin ainfo = Registry.GetAddinForHostAssembly (asmFile);
683
if (ainfo != null && !IsAddinLoaded (ainfo.Id)) {
684
AddinDescription adesc = null;
686
adesc = ainfo.Description;
687
} catch (Exception ex) {
688
defaultProgressStatus.ReportError ("Add-in description could not be loaded.", ex);
690
if (adesc == null || adesc.FilesChanged ()) {
691
// If the add-in has changed, update the add-in database.
692
// We do it here because once loaded, add-in roots can't be
693
// reloaded like regular add-ins.
694
Registry.Update (null);
695
ainfo = Registry.GetAddinForHostAssembly (asmFile);
699
LoadAddin (null, ainfo.Id, false);
704
/// Creates a new extension context.
707
/// The new extension context.
710
/// Extension contexts can be used to query the extension model using particular condition values.
712
public ExtensionContext CreateExtensionContext ()
715
return CreateChildContext ();
718
internal void CheckInitialized ()
721
throw new InvalidOperationException ("Add-in engine not initialized.");
724
internal void ReportError (string message, string addinId, Exception exception, bool fatal)
726
if (AddinLoadError != null)
727
AddinLoadError (null, new AddinErrorEventArgs (message, addinId, exception));
729
Console.WriteLine (message);
730
if (exception != null)
731
Console.WriteLine (exception);
735
internal void ReportAddinLoad (string id)
737
if (AddinLoaded != null) {
739
AddinLoaded (null, new AddinEventArgs (id));
741
// Ignore subscriber exceptions
746
internal void ReportAddinUnload (string id)
748
if (AddinUnloaded != null) {
750
AddinUnloaded (null, new AddinEventArgs (id));
752
// Ignore subscriber exceptions