using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Core.Hosting; /// /// Process-singleton registry of factories keyed by /// DriverInstance.DriverType string. Each driver project ships a DI /// extension (e.g. services.AddGalaxyProxyDriverFactory()) that registers /// its factory at startup; the bootstrapper looks up the factory by /// DriverInstance.DriverType + invokes it with the row's /// DriverInstanceId + DriverConfig JSON. /// /// /// Closes the gap surfaced by task #240 live smoke — DriverInstance rows in /// the central config DB had no path to materialise as registered /// instances. The factory registry is the seam. /// public sealed class DriverFactoryRegistry { private readonly Dictionary> _factories = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _tiers = new(StringComparer.OrdinalIgnoreCase); private readonly object _lock = new(); /// /// Register a factory for . Throws if a factory is /// already registered for that type — drivers are singletons by type-name in /// this process. /// /// Matches DriverInstance.DriverType. /// /// Receives (driverInstanceId, driverConfigJson); returns a new /// . Must NOT call /// itself — the bootstrapper calls it via /// so the host's per-driver retry semantics apply uniformly. /// /// /// Stability tier per docs/v2/driver-stability.md. Defaults to /// (in-process, lowest recycle footprint); Tier C /// drivers SHOULD pass this explicitly so the scheduled-recycle wiring in /// DriverInstanceBootstrapper can skip in-process drivers by tier check /// rather than by driver-type allow-list. /// public void Register(string driverType, Func factory, DriverTier tier = DriverTier.A) { ArgumentException.ThrowIfNullOrWhiteSpace(driverType); ArgumentNullException.ThrowIfNull(factory); lock (_lock) { if (_factories.ContainsKey(driverType)) throw new InvalidOperationException( $"DriverType '{driverType}' factory already registered for this process"); _factories[driverType] = factory; _tiers[driverType] = tier; } } /// /// Try to look up the factory for . Returns null /// if no driver assembly registered one — bootstrapper logs + skips so a /// missing-assembly deployment doesn't take down the whole server. /// public Func? TryGet(string driverType) { ArgumentException.ThrowIfNullOrWhiteSpace(driverType); lock (_lock) return _factories.GetValueOrDefault(driverType); } /// /// Look up the tier recorded alongside the factory. Returns /// for unknown driver types — a missing registration is already a skipped-bootstrap /// case upstream; we don't double-surface that failure here. /// public DriverTier GetTier(string driverType) { ArgumentException.ThrowIfNullOrWhiteSpace(driverType); lock (_lock) return _tiers.GetValueOrDefault(driverType, DriverTier.A); } public IReadOnlyCollection RegisteredTypes { get { lock (_lock) return [.. _factories.Keys]; } } }