using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Prise.Plugin; using Prise.Utils; namespace Prise.Activation { public class DefaultRemotePluginActivator : IRemotePluginActivator { private Func, IBootstrapperServiceProvider> bootstrapperServiceProviderFactory; private Func, IEnumerable, IPluginServiceProvider> pluginServiceProviderFactory; private ConcurrentBag instances; public DefaultRemotePluginActivator(Func, IBootstrapperServiceProvider> bootstrapperServiceProviderFactory, Func, IEnumerable, IPluginServiceProvider> pluginServiceProviderFactory) { this.bootstrapperServiceProviderFactory = bootstrapperServiceProviderFactory.ThrowIfNull(nameof(bootstrapperServiceProviderFactory)); this.pluginServiceProviderFactory = pluginServiceProviderFactory.ThrowIfNull(nameof(pluginServiceProviderFactory)); this.instances = new ConcurrentBag(); } private object AddToDisposables(object obj) { this.instances.Add(obj); return obj; } public virtual object CreateRemoteBootstrapper(IPluginActivationContext pluginActivationContext, IServiceCollection hostServices = null) { var bootstrapperType = pluginActivationContext.PluginBootstrapperType; var assembly = pluginActivationContext.PluginAssembly.Assembly; var bootstrapperServices = pluginActivationContext.BootstrapperServices; var contructors = bootstrapperType.GetConstructors(BindingFlags.Public | BindingFlags.Instance); var firstCtor = contructors.First(); if (!contructors.Any()) throw new PluginActivationException($"No public constructors found for remote bootstrapper {bootstrapperType.Name}"); if (firstCtor.GetParameters().Any()) throw new PluginActivationException($"Bootstrapper {bootstrapperType.Name} must contain a public parameterless constructor"); var bootstrapperInstance = assembly.CreateInstance(bootstrapperType.FullName); var serviceProvider = AddToDisposables(GetServiceProviderForBootstrapper(new ServiceCollection(), hostServices ?? new ServiceCollection())) as IServiceProvider; var bootstrapperServiceProvider = AddToDisposables(serviceProvider.GetService()) as IBootstrapperServiceProvider; bootstrapperInstance = InjectBootstrapperFieldsWithServices(bootstrapperInstance, bootstrapperServiceProvider, bootstrapperServices); return AddToDisposables(bootstrapperInstance); } public virtual object CreateRemoteInstance(IPluginActivationContext pluginActivationContext, IPluginBootstrapper bootstrapper = null, IServiceCollection hostServices = null) { var pluginType = pluginActivationContext.PluginType; var pluginAssembly = pluginActivationContext.PluginAssembly; var factoryMethod = pluginActivationContext.PluginFactoryMethod; var contructors = pluginType.GetConstructors(BindingFlags.Public | BindingFlags.Instance); if (contructors.Count() > 1) throw new PluginActivationException($"Multiple public constructors found for remote plugin {pluginType.Name}"); var serviceProvider = AddToDisposables(GetServiceProviderForPlugin(new ServiceCollection(), bootstrapper, hostServices ?? new ServiceCollection())) as IServiceProvider; if (factoryMethod != null) return AddToDisposables(factoryMethod.Invoke(null, new[] { serviceProvider })); var firstCtor = contructors.FirstOrDefault(); if (firstCtor != null && !firstCtor.GetParameters().Any()) // Empty default CTOR { var pluginServiceProvider = AddToDisposables(serviceProvider.GetService()) as IPluginServiceProvider; var remoteInstance = pluginAssembly.Assembly.CreateInstance(pluginType.FullName); remoteInstance = InjectPluginFieldsWithServices(remoteInstance, pluginServiceProvider, pluginActivationContext.PluginServices); ActivateIfNecessary(remoteInstance, pluginActivationContext); return AddToDisposables(remoteInstance); } throw new PluginActivationException($"Plugin of type {pluginType.Name} could not be activated."); } protected virtual void ActivateIfNecessary(object remoteInstance, IPluginActivationContext pluginActivationContext) { var pluginType = pluginActivationContext.PluginType; if (pluginActivationContext.PluginActivatedMethod == null) return; var remoteActivationMethod = remoteInstance.GetType().GetRuntimeMethods().FirstOrDefault(m => m.Name == pluginActivationContext.PluginActivatedMethod.Name); if (remoteActivationMethod == null) throw new PluginActivationException($"Remote activation method {pluginActivationContext.PluginActivatedMethod.Name} not found for plugin {pluginType.Name} on remote object {remoteInstance.GetType().Name}"); remoteActivationMethod.Invoke(remoteInstance, null); } protected virtual object InjectBootstrapperFieldsWithServices(object remoteInstance, IBootstrapperServiceProvider bootstrapperServiceProvider, IEnumerable bootstrapperServices) { foreach (var bootstrapperService in bootstrapperServices) { var fieldName = bootstrapperService.FieldName; var serviceInstance = bootstrapperServiceProvider.GetHostService(bootstrapperService.ServiceType); if (bootstrapperService.ProxyType == null) throw new PluginActivationException($"Field {fieldName} requires a ProxyType (BridgeType)."); var reverseProxyCtor = GetReverseProxyConstructor(bootstrapperService.ProxyType); if (reverseProxyCtor == null) throw new PluginActivationException($"ReverseProxy {bootstrapperService.ProxyType.Name} must have a single public constructor with one parameter of type object."); var reverseProxyInstance = AddToDisposables(reverseProxyCtor.Invoke(new[] { serviceInstance })); if (!TrySetField(remoteInstance, fieldName, reverseProxyInstance)) throw new PluginActivationException($"Field {bootstrapperService.FieldName} on Plugin could not be set."); } return remoteInstance; } protected virtual object InjectPluginFieldsWithServices(object remoteInstance, IPluginServiceProvider pluginServiceProvider, IEnumerable pluginServices) { foreach (var pluginService in pluginServices) { var fieldName = pluginService.FieldName; object serviceInstance = null; switch (pluginService.ProvidedBy) { case ProvidedBy.Host: serviceInstance = pluginServiceProvider.GetHostService(pluginService.ServiceType); break; case ProvidedBy.Plugin: serviceInstance = pluginServiceProvider.GetPluginService(pluginService.ServiceType); break; } if (TrySetField(remoteInstance, fieldName, serviceInstance)) continue; // Field was set successfully, continueing if (pluginService.ProxyType == null) throw new PluginActivationException($"Field {pluginService.FieldName} could not be set, please consider using a ReverseProxy."); var reverseProxyCtor = GetReverseProxyConstructor(pluginService.ProxyType); if (reverseProxyCtor == null) throw new PluginActivationException($"ReverseProxy {pluginService.ProxyType.Name} must have a single public constructor with one parameter of type object."); var reverseProxyInstance = AddToDisposables(reverseProxyCtor.Invoke(new[] { serviceInstance })); if (!TrySetField(remoteInstance, fieldName, reverseProxyInstance)) throw new PluginActivationException($"Field {pluginService.FieldName} on Plugin could not be set."); } return remoteInstance; } protected virtual IServiceProvider GetServiceProviderForBootstrapper(IServiceCollection services, IServiceCollection hostServices) { foreach (var service in hostServices) services.Add(service); services.AddScoped(sp => this.bootstrapperServiceProviderFactory( sp, hostServices.Select(d => d.ServiceType) )); return services.BuildServiceProvider(); } protected virtual IServiceProvider GetServiceProviderForPlugin(IServiceCollection services, IPluginBootstrapper bootstrapper, IServiceCollection hostServices) { // Add all the host services to the main collection foreach (var service in hostServices) services.Add(service); IServiceCollection pluginServices = new ServiceCollection(); // If a bootstrapper was provided, add the services for the plugin to a new collection if (bootstrapper != null) pluginServices = bootstrapper.Bootstrap(pluginServices); // Add all the plugin services to the main collection foreach (var service in pluginServices) services.Add(service); services.AddScoped(sp => this.pluginServiceProviderFactory( sp, hostServices.Select(d => d.ServiceType), pluginServices.Select(d => d.ServiceType) )); return services.BuildServiceProvider(); } protected virtual ConstructorInfo GetReverseProxyConstructor(Type proxyType) { return proxyType .GetConstructors(BindingFlags.Public | BindingFlags.Instance) .FirstOrDefault(c => c.GetParameters().Count() == 1 && c.GetParameters().First().ParameterType == typeof(object)); } protected virtual bool TrySetField(object remoteInstance, string fieldName, object fieldInstance) { try { remoteInstance .GetType() .GetTypeInfo() .DeclaredFields .First(f => f.Name == fieldName) .SetValue(remoteInstance, fieldInstance); return true; } catch (ArgumentException) { } return false; } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed && disposing) { foreach (var disposable in this.instances) { if (disposable as IDisposable != null) (disposable as IDisposable)?.Dispose(); } instances.Clear(); this.instances = null; } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }