using System.Reflection;
using Elsa.Extensions;
using Elsa.Features.Attributes;
using Elsa.Features.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
namespace Elsa.Features.Implementations;
///
public class Module : IModule
{
private record HostedServiceDescriptor(int Order, Type HostedServiceType);
private ISet _features = new HashSet();
private ISet _configuredFeatures = new HashSet();
private readonly ICollection _hostedServiceDescriptors = new List();
///
/// Constructor.
///
public Module(IServiceCollection services)
{
Services = services;
}
///
public IServiceCollection Services { get; }
///
public IDictionary Properties { get; } = new Dictionary();
///
public T Configure(Action? configure = default) where T : class, IFeature
=> Configure(module => (T)Activator.CreateInstance(typeof(T), module)!, configure);
///
public T Configure(Func factory, Action? configure = default) where T : class, IFeature
{
if (_features.FirstOrDefault(x => x is T) is not T feature)
{
feature = factory(this);
_features.Add(feature);
}
configure?.Invoke(feature);
if (!_isApplying)
return feature;
var dependencies = GetDependencyTypes(feature.GetType()).ToHashSet();
foreach (var dependency in dependencies.Select(GetOrCreateFeature))
ConfigureFeature(dependency);
ConfigureFeature(feature);
return feature;
}
///
public IModule ConfigureHostedService(int priority = 0) where T : class, IHostedService
{
_hostedServiceDescriptors.Add(new HostedServiceDescriptor(priority, typeof(T)));
return this;
}
private bool _isApplying;
///
public void Apply()
{
_isApplying = true;
//var featureTypes = _features.Select(x => x.GetType()).TSort(x => x.GetCustomAttributes().Select(dependsOn => dependsOn.Type)).ToList();
var featureTypes = GetFeatureTypes();
_features = featureTypes.Select(featureType => _features.FirstOrDefault(x => x.GetType() == featureType) ?? (IFeature)Activator.CreateInstance(featureType, this)!).ToHashSet();
// Iterate over a copy of the features to avoid concurrent modification exceptions.
foreach (var feature in _features.ToList())
{
// This will cause additional features to be added to _features.
ConfigureFeature(feature);
feature.ConfigureHostedServices();
}
foreach (var hostedServiceDescriptor in _hostedServiceDescriptors.OrderBy(x => x.Order))
Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IHostedService), hostedServiceDescriptor.HostedServiceType));
// Make sure to use the complete list of features when applying them.
foreach (var feature in _features)
feature.Apply();
}
private void ConfigureFeature(IFeature feature)
{
if(_configuredFeatures.Contains(feature))
return;
feature.Configure();
_features.Add(feature);
_configuredFeatures.Add(feature);
}
private IFeature GetOrCreateFeature(Type featureType)
{
return _features.FirstOrDefault(x => x.GetType() == featureType) ?? (IFeature)Activator.CreateInstance(featureType, this)!;
}
private ISet GetFeatureTypes()
{
var featureTypes = _features.Select(x => x.GetType()).ToHashSet();
var featureTypesWithDependencies = featureTypes.Concat(featureTypes.SelectMany(GetDependencyTypes)).ToHashSet();
return featureTypesWithDependencies.TSort(x => x.GetCustomAttributes().Select(dependsOn => dependsOn.Type)).ToHashSet();
}
// Recursively get dependency types.
private IEnumerable GetDependencyTypes(Type type)
{
var dependencies = type.GetCustomAttributes().Select(dependsOn => dependsOn.Type).ToList();
return dependencies.Concat(dependencies.SelectMany(GetDependencyTypes));
}
}