using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
namespace ZB.MOM.WW.OtOpcUa.Server;
///
/// Task #248 — bridges the gap surfaced by the Phase 7 live smoke (#240) where
/// DriverInstance rows in the central config DB had no path to materialise
/// as live instances in .
/// Called from OpcUaServerService.ExecuteAsync after the bootstrap loads
/// the published generation, before address-space build.
///
///
///
/// Per row: looks up the DriverType string in
/// , calls the factory with the row's
/// DriverInstanceId + DriverConfig JSON to construct an
/// , then registers via
/// which invokes InitializeAsync
/// under the host's lifecycle semantics.
///
///
/// Unknown DriverType = factory not registered = log a warning and skip.
/// Per plan decision #12 (driver isolation), failure to construct or initialize
/// one driver doesn't prevent the rest from coming up — the Server keeps serving
/// the others' subtrees + the operator can fix the misconfigured row + republish
/// to retry.
///
///
public sealed class DriverInstanceBootstrapper(
DriverFactoryRegistry factories,
DriverHost driverHost,
IServiceScopeFactory scopeFactory,
ILogger logger)
{
public async Task RegisterDriversFromGenerationAsync(long generationId, CancellationToken ct)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
var rows = await db.DriverInstances.AsNoTracking()
.Where(d => d.GenerationId == generationId && d.Enabled)
.ToListAsync(ct).ConfigureAwait(false);
var registered = 0;
var skippedUnknownType = 0;
var failedInit = 0;
foreach (var row in rows)
{
var factory = factories.TryGet(row.DriverType);
if (factory is null)
{
logger.LogWarning(
"DriverInstance {Id} skipped — DriverType '{Type}' has no registered factory (known: {Known})",
row.DriverInstanceId, row.DriverType, string.Join(",", factories.RegisteredTypes));
skippedUnknownType++;
continue;
}
try
{
var driver = factory(row.DriverInstanceId, row.DriverConfig);
await driverHost.RegisterAsync(driver, row.DriverConfig, ct).ConfigureAwait(false);
registered++;
logger.LogInformation(
"DriverInstance {Id} ({Type}) registered + initialized", row.DriverInstanceId, row.DriverType);
}
catch (Exception ex)
{
// Plan decision #12 — driver isolation. Log + continue so one bad row
// doesn't deny the OPC UA endpoint to the rest of the fleet.
logger.LogError(ex,
"DriverInstance {Id} ({Type}) failed to initialize — driver state will reflect Faulted; operator can republish to retry",
row.DriverInstanceId, row.DriverType);
failedInit++;
}
}
logger.LogInformation(
"DriverInstanceBootstrapper: gen={Gen} registered={Registered} skippedUnknownType={Skipped} failedInit={Failed}",
generationId, registered, skippedUnknownType, failedInit);
return registered;
}
}