using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; using ZB.MOM.WW.OtOpcUa.Core.Hosting; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy; /// /// Static factory registration helper for . Mirrors /// GalaxyProxyDriverFactoryExtensions / ModbusDriverFactoryExtensions. /// Server's Program.cs calls once at startup; the driver /// bootstrap pipeline materialises DriverInstance rows whose DriverType matches /// into live instances. /// /// /// The driver-type name "GalaxyMxGateway" is intentionally distinct from the /// legacy proxy's "Galaxy" so both factories can be registered simultaneously /// during parity testing (Phase 5). PR 4.W will add a server-side Galaxy:Backend /// switch that materialises a Galaxy DriverInstance under one or the other type name. /// public static class GalaxyDriverFactoryExtensions { public const string DriverTypeName = "GalaxyMxGateway"; public static void Register(DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null) { ArgumentNullException.ThrowIfNull(registry); registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory)); } /// Convenience for tests + standalone callers. public static GalaxyDriver CreateInstance(string driverInstanceId, string driverConfigJson) => CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null); public static GalaxyDriver CreateInstance( string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory) { ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId); ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson); var dto = JsonSerializer.Deserialize(driverConfigJson, JsonOptions) ?? throw new InvalidOperationException( $"Galaxy driver config for '{driverInstanceId}' deserialised to null"); var options = new GalaxyDriverOptions( Gateway: new GalaxyGatewayOptions( Endpoint: dto.Gateway?.Endpoint ?? throw new InvalidOperationException( $"Galaxy driver '{driverInstanceId}' missing required Gateway.Endpoint"), ApiKeySecretRef: dto.Gateway.ApiKeySecretRef ?? throw new InvalidOperationException( $"Galaxy driver '{driverInstanceId}' missing required Gateway.ApiKeySecretRef"), UseTls: dto.Gateway.UseTls ?? true, CaCertificatePath: dto.Gateway.CaCertificatePath, ConnectTimeoutSeconds: dto.Gateway.ConnectTimeoutSeconds ?? 10, DefaultCallTimeoutSeconds: dto.Gateway.DefaultCallTimeoutSeconds ?? 30, StreamTimeoutSeconds: dto.Gateway.StreamTimeoutSeconds ?? 0), MxAccess: new GalaxyMxAccessOptions( ClientName: dto.MxAccess?.ClientName ?? throw new InvalidOperationException( $"Galaxy driver '{driverInstanceId}' missing required MxAccess.ClientName"), PublishingIntervalMs: dto.MxAccess.PublishingIntervalMs ?? 1000, WriteUserId: dto.MxAccess.WriteUserId ?? 0, EventPumpChannelCapacity: dto.MxAccess.EventPumpChannelCapacity ?? 50_000), Repository: new GalaxyRepositoryOptions( DiscoverPageSize: dto.Repository?.DiscoverPageSize ?? 5000, WatchDeployEvents: dto.Repository?.WatchDeployEvents ?? true), Reconnect: new GalaxyReconnectOptions( InitialBackoffMs: dto.Reconnect?.InitialBackoffMs ?? 500, MaxBackoffMs: dto.Reconnect?.MaxBackoffMs ?? 30_000, ReplayOnSessionLost: dto.Reconnect?.ReplayOnSessionLost ?? true)); return new GalaxyDriver(driverInstanceId, options, loggerFactory?.CreateLogger()); } private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true, ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; internal sealed class GalaxyDriverConfigDto { public GatewayDto? Gateway { get; init; } public MxAccessDto? MxAccess { get; init; } public RepositoryDto? Repository { get; init; } public ReconnectDto? Reconnect { get; init; } } internal sealed class GatewayDto { public string? Endpoint { get; init; } public string? ApiKeySecretRef { get; init; } public bool? UseTls { get; init; } public string? CaCertificatePath { get; init; } public int? ConnectTimeoutSeconds { get; init; } public int? DefaultCallTimeoutSeconds { get; init; } public int? StreamTimeoutSeconds { get; init; } } internal sealed class MxAccessDto { public string? ClientName { get; init; } public int? PublishingIntervalMs { get; init; } public int? WriteUserId { get; init; } public int? EventPumpChannelCapacity { get; init; } } internal sealed class RepositoryDto { public int? DiscoverPageSize { get; init; } public bool? WatchDeployEvents { get; init; } } internal sealed class ReconnectDto { public int? InitialBackoffMs { get; init; } public int? MaxBackoffMs { get; init; } public bool? ReplayOnSessionLost { get; init; } } }