< Summary

Information
Class: Elsa.Workflows.ActivityRegistry
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs
Line coverage
93%
Covered lines: 150
Uncovered lines: 11
Coverable lines: 161
Total lines: 390
Line coverage: 93.1%
Branch coverage
94%
Covered branches: 83
Total branches: 88
Branch coverage: 94.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Add(...)100%11100%
Remove(...)100%22100%
ListAll()100%22100%
ListByProvider(...)83.33%66100%
Find(...)100%66100%
Find(...)100%66100%
Find(...)100%44100%
FindMany(...)0%620%
Register(...)100%11100%
RegisterAsync()100%22100%
RegisterAsync()100%22100%
GetDescriptorsAsync(...)100%11100%
RefreshDescriptorsAsync()100%22100%
RefreshDescriptorsAsync()100%88100%
EnsureDescriptorsAsync()100%66100%
Add(...)83.33%6685.71%
Clear()100%11100%
ClearProvider(...)100%1010100%
ClearTenant(...)100%210%
GetOrCreateRegistry(...)100%66100%
GetOrCreateProviderDescriptors(...)100%11100%
UpdateLatestDescriptor(...)100%22100%
RemoveDescriptor(...)83.33%6680%
RecomputeLatestDescriptor(...)100%1010100%
NormalizeTenantIdForGrouping(...)100%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Diagnostics.CodeAnalysis;
 3using Elsa.Common.Multitenancy;
 4using Elsa.Workflows.Helpers;
 5using Elsa.Workflows.Models;
 6using Microsoft.Extensions.Logging;
 7
 8namespace Elsa.Workflows;
 9
 10/// <inheritdoc />
 67411public class ActivityRegistry(IActivityDescriber activityDescriber, IEnumerable<IActivityDescriptorModifier> modifiers, 
 12{
 13    // Legacy support for manually registered activities
 67414    private readonly ISet<ActivityDescriptor> _manualActivityDescriptors = new HashSet<ActivityDescriptor>();
 15
 16    // Per-tenant activity descriptors (workflow-as-activities, tenant-specific providers, etc.)
 67417    private readonly ConcurrentDictionary<string, TenantRegistryData> _tenantRegistries = new();
 18
 19    // Tenant-agnostic activity descriptors (built-in activities, manually registered, etc.)
 67420    private readonly TenantRegistryData _agnosticRegistry = new();
 21
 22    // Tracks tenant-agnostic providers initialized for this registry instance.
 67423    private readonly ConcurrentDictionary<Type, byte> _initializedProviders = new();
 67424    private readonly ConcurrentDictionary<Type, SemaphoreSlim> _providerInitializationLocks = new();
 25
 26    /// <inheritdoc />
 27    public void Add(Type providerType, ActivityDescriptor descriptor)
 28    {
 107529        var registry = GetOrCreateRegistry(descriptor.TenantId);
 107530        var providerDescriptors = GetOrCreateProviderDescriptors(registry, providerType);
 107531        Add(descriptor, registry, providerDescriptors);
 107532    }
 33
 34    /// <inheritdoc />
 35    public void Remove(Type providerType, ActivityDescriptor descriptor)
 36    {
 94537        var registry = GetOrCreateRegistry(descriptor.TenantId);
 94538        if (registry.ProvidedActivityDescriptors.TryGetValue(providerType, out var providerDescriptors))
 39        {
 94540            providerDescriptors.Remove(descriptor);
 94541            RemoveDescriptor(registry, descriptor);
 42        }
 94543    }
 44
 45    /// <inheritdoc />
 46    public IEnumerable<ActivityDescriptor> ListAll()
 47    {
 1811548        var currentTenantId = tenantAccessor.TenantId;
 49
 50        // Get descriptors from current tenant's registry
 1811551        var tenantDescriptors = _tenantRegistries.TryGetValue(currentTenantId, out var tenantRegistry)
 1811552            ? tenantRegistry.ActivityDescriptors.Values
 1811553            : Enumerable.Empty<ActivityDescriptor>();
 54
 55        // Get descriptors from agnostic registry
 1811556        var agnosticDescriptors = _agnosticRegistry.ActivityDescriptors.Values;
 57
 1811558        return tenantDescriptors.Concat(agnosticDescriptors);
 59    }
 60
 61    /// <inheritdoc />
 62    public IEnumerable<ActivityDescriptor> ListByProvider(Type providerType)
 63    {
 148664        var currentTenantId = tenantAccessor.TenantId;
 65
 66        // Get descriptors from current tenant's registry
 148667        var tenantDescriptors = _tenantRegistries.TryGetValue(currentTenantId, out var tenantRegistry) &&
 148668                                tenantRegistry.ProvidedActivityDescriptors.TryGetValue(providerType, out var tenantProvi
 148669            ? tenantProviderDescriptors
 148670            : Enumerable.Empty<ActivityDescriptor>();
 71
 72        // Get descriptors from agnostic registry
 148673        var agnosticDescriptors = _agnosticRegistry.ProvidedActivityDescriptors.TryGetValue(providerType, out var agnost
 148674            ? agnosticProviderDescriptors
 148675            : Enumerable.Empty<ActivityDescriptor>();
 76
 148677        return tenantDescriptors.Concat(agnosticDescriptors);
 78    }
 79
 80    /// <inheritdoc />
 81    public ActivityDescriptor? Find(string type)
 82    {
 61783        var currentTenantId = tenantAccessor.TenantId;
 84
 85        // Always prefer tenant-specific descriptors over tenant-agnostic ones
 86        // Get highest version from current tenant's registry
 61787        if (_tenantRegistries.TryGetValue(currentTenantId, out var tenantRegistry))
 88        {
 20089            if (tenantRegistry.LatestActivityDescriptors.TryGetValue(type, out var tenantDescriptor))
 6890                return tenantDescriptor;
 91        }
 92
 93        // Fall back to agnostic registry only if no tenant-specific descriptor exists
 54994        return _agnosticRegistry.LatestActivityDescriptors.TryGetValue(type, out var agnosticDescriptor)
 54995            ? agnosticDescriptor
 54996            : null;
 97    }
 98
 99    /// <inheritdoc />
 100    public ActivityDescriptor? Find(string type, int version)
 101    {
 64654102        var currentTenantId = tenantAccessor.TenantId;
 103
 104        // Check current tenant's registry first
 64654105        if (_tenantRegistries.TryGetValue(currentTenantId, out var tenantRegistry) &&
 64654106            tenantRegistry.ActivityDescriptors.TryGetValue((type, version), out var tenantDescriptor))
 107        {
 2792108            return tenantDescriptor;
 109        }
 110
 111        // Fall back to agnostic registry
 61862112        return _agnosticRegistry.ActivityDescriptors.TryGetValue((type, version), out var agnosticDescriptor)
 61862113            ? agnosticDescriptor
 61862114            : null;
 115    }
 116
 117    /// <inheritdoc />
 118    public ActivityDescriptor? Find(Func<ActivityDescriptor, bool> predicate)
 119    {
 651120        var currentTenantId = tenantAccessor.TenantId;
 121
 122        // Check current tenant's registry first
 651123        if (_tenantRegistries.TryGetValue(currentTenantId, out var tenantRegistry))
 124        {
 480125            var tenantMatch = tenantRegistry.ActivityDescriptors.Values.FirstOrDefault(predicate);
 943126            if (tenantMatch != null) return tenantMatch;
 127        }
 128
 129        // Fall back to agnostic registry
 188130        return _agnosticRegistry.ActivityDescriptors.Values.FirstOrDefault(predicate);
 131    }
 132
 133    /// <inheritdoc />
 134    public IEnumerable<ActivityDescriptor> FindMany(Func<ActivityDescriptor, bool> predicate)
 135    {
 0136        var currentTenantId = tenantAccessor.TenantId;
 137
 138        // Get descriptors from current tenant's registry
 0139        var tenantDescriptors = _tenantRegistries.TryGetValue(currentTenantId, out var tenantRegistry)
 0140            ? tenantRegistry.ActivityDescriptors.Values.Where(predicate)
 0141            : Enumerable.Empty<ActivityDescriptor>();
 142
 143        // Get descriptors from agnostic registry
 0144        var agnosticDescriptors = _agnosticRegistry.ActivityDescriptors.Values.Where(predicate);
 145
 0146        return tenantDescriptors.Concat(agnosticDescriptors);
 147    }
 148
 149    /// <inheritdoc />
 150    public void Register(ActivityDescriptor descriptor)
 151    {
 47152        var registry = GetOrCreateRegistry(descriptor.TenantId);
 47153        var providerDescriptors = GetOrCreateProviderDescriptors(registry, GetType());
 47154        Add(descriptor, registry, providerDescriptors);
 47155    }
 156
 157    /// <inheritdoc />
 158    public async Task RegisterAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type a
 159    {
 18111160        var activityTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 161
 162        // Check if already registered in any registry
 992484163        if (ListAll().Any(x => x.TypeName == activityTypeName))
 16825164            return;
 165
 1286166        var activityDescriptor = await activityDescriber.DescribeActivityAsync(activityType, cancellationToken);
 167
 1286168        var registry = GetOrCreateRegistry(activityDescriptor.TenantId);
 1286169        Add(activityDescriptor, registry, _manualActivityDescriptors);
 18111170    }
 171
 172    /// <inheritdoc />
 173    public async Task RegisterAsync(IEnumerable<Type> activityTypes, CancellationToken cancellationToken = default)
 174    {
 44730175        foreach (var activityType in activityTypes)
 17579176            await RegisterAsync(activityType, cancellationToken);
 4786177    }
 178
 179    /// <inheritdoc />
 1180    public ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
 181
 182    /// <inheritdoc />
 183    public async Task RefreshDescriptorsAsync(IEnumerable<IActivityProvider> activityProviders, CancellationToken cancel
 184    {
 1936185        foreach (var activityProvider in activityProviders)
 74186            await RefreshDescriptorsAsync(activityProvider, cancellationToken);
 894187    }
 188
 189    public async Task RefreshDescriptorsAsync(IActivityProvider activityProvider, CancellationToken cancellationToken = 
 190    {
 637191        var providerType = activityProvider.GetType();
 192
 193        // Get new descriptors from provider
 637194        var descriptors = (await activityProvider.GetDescriptorsAsync(cancellationToken)).ToList();
 195
 196        // Group descriptors by normalized tenant ID
 197        // Normalize null to "*" so both map to the same agnostic group, avoiding redundant processing
 8070198        var descriptorsByTenant = descriptors.GroupBy(d => NormalizeTenantIdForGrouping(d.TenantId));
 199
 1724200        foreach (var group in descriptorsByTenant)
 201        {
 227202            var tenantId = group.Key;
 227203            var registry = GetOrCreateRegistry(tenantId);
 204
 205            // Remove old descriptors for this provider from this tenant's registry
 227206            if (registry.ProvidedActivityDescriptors.TryGetValue(providerType, out var oldDescriptors))
 207            {
 2730208                foreach (var oldDescriptor in oldDescriptors.ToList())
 209                {
 1291210                    RemoveDescriptor(registry, oldDescriptor);
 211                }
 212            }
 213
 214            // Add new descriptors for this tenant
 227215            var providerDescriptors = new List<ActivityDescriptor>();
 15324216            foreach (var descriptor in group)
 217            {
 7435218                Add(descriptor, registry, providerDescriptors);
 219            }
 220
 221            // Update the provider's descriptor list in this registry
 227222            registry.ProvidedActivityDescriptors[providerType] = providerDescriptors;
 223        }
 635224    }
 225
 226    /// <inheritdoc />
 227    public async Task EnsureDescriptorsAsync(IActivityProvider activityProvider, CancellationToken cancellationToken = d
 228    {
 884229        if (activityProvider is not ITenantAgnosticActivityProvider)
 230        {
 290231            await RefreshDescriptorsAsync(activityProvider, cancellationToken);
 290232            return;
 233        }
 234
 594235        var providerType = activityProvider.GetType();
 594236        if (_initializedProviders.ContainsKey(providerType))
 319237            return;
 238
 539239        var initializationLock = _providerInitializationLocks.GetOrAdd(providerType, _ => new(1, 1));
 275240        await initializationLock.WaitAsync(cancellationToken);
 241
 242        try
 243        {
 275244            if (_initializedProviders.ContainsKey(providerType))
 7245                return;
 246
 268247            await RefreshDescriptorsAsync(activityProvider, cancellationToken);
 266248            _initializedProviders.TryAdd(providerType, 0);
 266249        }
 250        finally
 251        {
 275252            initializationLock.Release();
 253        }
 882254    }
 255
 256    private void Add(ActivityDescriptor? descriptor, TenantRegistryData registry, ICollection<ActivityDescriptor> provid
 257    {
 9843258        if (descriptor is null)
 259        {
 0260            logger.LogError("Unable to add a null descriptor");
 0261            return;
 262        }
 263
 61410264        foreach (var modifier in modifiers)
 20862265            modifier.Modify(descriptor);
 266
 9843267        var activityDescriptors = registry.ActivityDescriptors;
 9843268        var descriptorKey = (descriptor.TypeName, descriptor.Version);
 269
 270        // If the descriptor already exists, replace it. But log a warning.
 9843271        if (activityDescriptors.TryGetValue(descriptorKey, out var existingDescriptor))
 272        {
 273            // Remove the existing descriptor from the providerDescriptors collection.
 132274            providerDescriptors.Remove(existingDescriptor);
 275
 276            // Log a warning.
 132277            logger.LogWarning("Activity descriptor {ActivityType} v{ActivityVersion} was already registered for tenant {
 278        }
 279
 9843280        activityDescriptors[descriptorKey] = descriptor;
 9843281        UpdateLatestDescriptor(registry, descriptor);
 9843282        providerDescriptors.Add(descriptor);
 9843283    }
 284
 285    /// <inheritdoc />
 286    public void Clear()
 287    {
 3288        _manualActivityDescriptors.Clear();
 3289        _tenantRegistries.Clear();
 3290        _agnosticRegistry.ActivityDescriptors.Clear();
 3291        _agnosticRegistry.LatestActivityDescriptors.Clear();
 3292        _agnosticRegistry.ProvidedActivityDescriptors.Clear();
 3293        _initializedProviders.Clear();
 3294    }
 295
 296    /// <inheritdoc />
 297    public void ClearProvider(Type providerType)
 298    {
 3299        _initializedProviders.TryRemove(providerType, out _);
 3300        var currentTenantId = tenantAccessor.TenantId;
 301
 302        // Clear from current tenant's registry
 3303        if (_tenantRegistries.TryGetValue(currentTenantId, out var tenantRegistry)
 3304            && tenantRegistry.ProvidedActivityDescriptors.TryGetValue(providerType, out var descriptors))
 305        {
 10306            foreach (var descriptor in descriptors.ToList())
 3307                RemoveDescriptor(tenantRegistry, descriptor);
 308
 2309            tenantRegistry.ProvidedActivityDescriptors.TryRemove(providerType, out _);
 310        }
 311
 312        // Clear from agnostic registry
 3313        if (_agnosticRegistry.ProvidedActivityDescriptors.TryGetValue(providerType, out var agnosticDescriptors))
 314        {
 4315            foreach (var descriptor in agnosticDescriptors.ToList())
 1316                RemoveDescriptor(_agnosticRegistry, descriptor);
 317
 1318            _agnosticRegistry.ProvidedActivityDescriptors.TryRemove(providerType, out _);
 319        }
 3320    }
 321
 322    /// <summary>
 323    /// Clears all activity descriptors for a specific tenant. Useful when a tenant is deactivated.
 324    /// </summary>
 325    internal void ClearTenant(string tenantId)
 326    {
 0327        _tenantRegistries.TryRemove(tenantId, out _);
 0328    }
 329
 330    private TenantRegistryData GetOrCreateRegistry(string? tenantId)
 331    {
 332        // Null or agnostic tenant ID goes to agnostic registry
 3580333        if (tenantId is null or Tenant.AgnosticTenantId)
 2254334            return _agnosticRegistry;
 335
 336        // Get or create tenant-specific registry
 1345337        return _tenantRegistries.GetOrAdd(tenantId, _ => new());
 338    }
 339
 340    private ICollection<ActivityDescriptor> GetOrCreateProviderDescriptors(TenantRegistryData registry, Type providerTyp
 341    {
 1156342        return registry.ProvidedActivityDescriptors.GetOrAdd(providerType, _ => new List<ActivityDescriptor>());
 343    }
 344
 345    private static void UpdateLatestDescriptor(TenantRegistryData registry, ActivityDescriptor descriptor)
 346    {
 9843347        registry.LatestActivityDescriptors.AddOrUpdate(
 9843348            descriptor.TypeName,
 9843349            descriptor,
 10075350            (_, latestDescriptor) => descriptor.Version >= latestDescriptor.Version ? descriptor : latestDescriptor);
 9843351    }
 352
 353    private static void RemoveDescriptor(TenantRegistryData registry, ActivityDescriptor descriptor)
 354    {
 2240355        if (!registry.ActivityDescriptors.TryRemove((descriptor.TypeName, descriptor.Version), out var removedDescriptor
 0356            return;
 357
 2240358        if (registry.LatestActivityDescriptors.TryGetValue(removedDescriptor.TypeName, out var latestDescriptor) && late
 2156359            RecomputeLatestDescriptor(registry, removedDescriptor.TypeName);
 2240360    }
 361
 362    private static void RecomputeLatestDescriptor(TenantRegistryData registry, string typeName)
 363    {
 2156364        ActivityDescriptor? latestDescriptor = null;
 127896365        foreach (var descriptor in registry.ActivityDescriptors.Values)
 366        {
 61792367            if (descriptor.TypeName != typeName)
 368                continue;
 369
 4370            if (latestDescriptor == null || descriptor.Version > latestDescriptor.Version)
 3371                latestDescriptor = descriptor;
 372        }
 373
 2156374        if (latestDescriptor == null)
 2153375            registry.LatestActivityDescriptors.TryRemove(typeName, out _);
 376        else
 3377            registry.LatestActivityDescriptors[typeName] = latestDescriptor;
 3378    }
 379
 380    /// <summary>
 381    /// Normalizes tenant ID for grouping purposes.
 382    /// Converts null to "*" so that both null and "*" descriptors are grouped together,
 383    /// avoiding redundant processing of the agnostic registry.
 384    /// </summary>
 385    private static string? NormalizeTenantIdForGrouping(string? tenantId)
 386    {
 387        // Normalize null to "*" so both map to the same group
 7435388        return tenantId ?? Tenant.AgnosticTenantId;
 389    }
 390}

Methods/Properties

.ctor(Elsa.Workflows.IActivityDescriber,System.Collections.Generic.IEnumerable`1<Elsa.Workflows.IActivityDescriptorModifier>,Elsa.Common.Multitenancy.ITenantAccessor,Microsoft.Extensions.Logging.ILogger`1<Elsa.Workflows.ActivityRegistry>)
Add(System.Type,Elsa.Workflows.Models.ActivityDescriptor)
Remove(System.Type,Elsa.Workflows.Models.ActivityDescriptor)
ListAll()
ListByProvider(System.Type)
Find(System.String)
Find(System.String,System.Int32)
Find(System.Func`2<Elsa.Workflows.Models.ActivityDescriptor,System.Boolean>)
FindMany(System.Func`2<Elsa.Workflows.Models.ActivityDescriptor,System.Boolean>)
Register(Elsa.Workflows.Models.ActivityDescriptor)
RegisterAsync()
RegisterAsync()
GetDescriptorsAsync(System.Threading.CancellationToken)
RefreshDescriptorsAsync()
RefreshDescriptorsAsync()
EnsureDescriptorsAsync()
Add(Elsa.Workflows.Models.ActivityDescriptor,Elsa.Workflows.Models.TenantRegistryData,System.Collections.Generic.ICollection`1<Elsa.Workflows.Models.ActivityDescriptor>)
Clear()
ClearProvider(System.Type)
ClearTenant(System.String)
GetOrCreateRegistry(System.String)
GetOrCreateProviderDescriptors(Elsa.Workflows.Models.TenantRegistryData,System.Type)
UpdateLatestDescriptor(Elsa.Workflows.Models.TenantRegistryData,Elsa.Workflows.Models.ActivityDescriptor)
RemoveDescriptor(Elsa.Workflows.Models.TenantRegistryData,Elsa.Workflows.Models.ActivityDescriptor)
RecomputeLatestDescriptor(Elsa.Workflows.Models.TenantRegistryData,System.String)
NormalizeTenantIdForGrouping(System.String)