#region License Information
/* HeuristicLab
* Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
namespace HeuristicLab.PluginInfrastructure.Manager {
// must extend MarshalByRefObject because of event passing between Loader and PluginManager (each in it's own AppDomain)
///
/// Class to manage different plugins.
///
public sealed class PluginManager : MarshalByRefObject {
public event EventHandler PluginLoaded;
public event EventHandler PluginUnloaded;
public event EventHandler Initializing;
public event EventHandler Initialized;
public event EventHandler ApplicationStarting;
public event EventHandler ApplicationStarted;
private string pluginDir;
private List plugins;
///
/// Gets all installed plugins.
///
public IEnumerable Plugins {
get { return plugins; }
}
private List applications;
///
/// Gets all installed applications.
///
public IEnumerable Applications {
get { return applications; }
}
private object locker = new object();
private bool initialized;
public PluginManager(string pluginDir) {
this.pluginDir = pluginDir;
plugins = new List();
applications = new List();
initialized = false;
}
///
/// Determines installed plugins and checks if all plugins are loadable.
///
public void DiscoverAndCheckPlugins() {
OnInitializing(PluginInfrastructureEventArgs.Empty);
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = pluginDir;
AppDomain pluginDomain = null;
try {
pluginDomain = AppDomain.CreateDomain("plugin domain", null, setup);
Type pluginValidatorType = typeof(PluginValidator);
PluginValidator remoteValidator = (PluginValidator)pluginDomain.CreateInstanceAndUnwrap(pluginValidatorType.Assembly.FullName, pluginValidatorType.FullName, true, BindingFlags.NonPublic | BindingFlags.Instance, null, null, null, null);
remoteValidator.PluginDir = pluginDir;
// forward all events from the remoteValidator to listeners
remoteValidator.PluginLoaded +=
delegate(object sender, PluginInfrastructureEventArgs e) {
OnPluginLoaded(e);
};
// get list of plugins and applications from the validator
plugins.Clear(); applications.Clear();
plugins.AddRange(remoteValidator.Plugins);
applications.AddRange(remoteValidator.Applications);
}
finally {
// discard the AppDomain that was used for plugin discovery
AppDomain.Unload(pluginDomain);
// unload all plugins
foreach (var pluginDescription in plugins.Where(x => x.PluginState == PluginState.Loaded)) {
pluginDescription.Unload();
OnPluginUnloaded(new PluginInfrastructureEventArgs(pluginDescription));
}
initialized = true;
OnInitialized(PluginInfrastructureEventArgs.Empty);
}
}
///
/// Starts an application in a separate AppDomain.
/// Loads all enabled plugins and starts the application via an ApplicationManager instance activated in the new AppDomain.
///
/// application to run
public void Run(ApplicationDescription appInfo, ICommandLineArgument[] args) {
if (!initialized) throw new InvalidOperationException("PluginManager is not initialized. DiscoverAndCheckPlugins() must be called before Run()");
// create a separate AppDomain for the application
// initialize the static ApplicationManager in the AppDomain
// and remotely tell it to start the application
OnApplicationStarting(new PluginInfrastructureEventArgs(appInfo));
AppDomain applicationDomain = null;
try {
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.PrivateBinPath = pluginDir;
applicationDomain = AppDomain.CreateDomain(AppDomain.CurrentDomain.FriendlyName, null, setup);
Type applicationManagerType = typeof(DefaultApplicationManager);
DefaultApplicationManager applicationManager =
(DefaultApplicationManager)applicationDomain.CreateInstanceAndUnwrap(applicationManagerType.Assembly.FullName, applicationManagerType.FullName, true, BindingFlags.NonPublic | BindingFlags.Instance, null, null, null, null);
applicationManager.PluginLoaded += applicationManager_PluginLoaded;
applicationManager.PluginUnloaded += applicationManager_PluginUnloaded;
applicationManager.PrepareApplicationDomain(applications, plugins);
OnApplicationStarted(new PluginInfrastructureEventArgs(appInfo));
applicationManager.Run(appInfo, args);
}
finally {
// make sure domain is unloaded in all cases
AppDomain.Unload(applicationDomain);
}
}
private void applicationManager_PluginUnloaded(object sender, PluginInfrastructureEventArgs e) {
// unload the matching plugin description (
PluginDescription desc = (PluginDescription)e.Entity;
// access to plugin descriptions has to be synchronized because multiple applications
// can be started or stopped at the same time
lock (locker) {
// also unload the matching plugin description in this AppDomain
plugins.First(x => x.Equals(desc)).Unload();
}
OnPluginUnloaded(e);
}
private void applicationManager_PluginLoaded(object sender, PluginInfrastructureEventArgs e) {
// load the matching plugin description (
PluginDescription desc = (PluginDescription)e.Entity;
// access to plugin descriptions has to be synchronized because multiple applications
// can be started or stopped at the same time
lock (locker) {
// also load the matching plugin description in this AppDomain
plugins.First(x => x.Equals(desc)).Load();
}
OnPluginLoaded(e);
}
#region event raising methods
private void OnPluginLoaded(PluginInfrastructureEventArgs e) {
if (PluginLoaded != null) {
PluginLoaded(this, e);
}
}
private void OnPluginUnloaded(PluginInfrastructureEventArgs e) {
if (PluginUnloaded != null) {
PluginUnloaded(this, e);
}
}
private void OnInitializing(PluginInfrastructureEventArgs e) {
if (Initializing != null) {
Initializing(this, e);
}
}
private void OnInitialized(PluginInfrastructureEventArgs e) {
if (Initialized != null) {
Initialized(this, e);
}
}
private void OnApplicationStarting(PluginInfrastructureEventArgs e) {
if (ApplicationStarting != null) {
ApplicationStarting(this, e);
}
}
private void OnApplicationStarted(PluginInfrastructureEventArgs e) {
if (ApplicationStarted != null) {
ApplicationStarted(this, e);
}
}
#endregion
// infinite lease time
///
/// Make sure that the plugin manager is never disposed (necessary for cross-app-domain events)
///
/// null.
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService() {
return null;
}
}
}