/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Threading; namespace Opc.Ua.Server.Fluent { /// /// Extension methods that register an external event source against a /// notifier node resolved by the fluent /// surface. The source's items are delivered through /// on the notifier, so monitored /// items on the notifier (or on an ancestor that the notifier is /// reachable from via inverse HasNotifier references) receive /// the events using the standard OPC UA event-dispatch path. /// /// /// /// The extensions require the manager being wired to derive from /// (the /// is attached during the manager's /// startup). Calling Publish on a builder backed by a manager /// that does not opt in throws /// with /// . /// /// /// By default each registered source is lazy: the iterator only runs /// while at least one monitored item is interested in the notifier. /// See for tuning. /// /// public static class EventNotifierBuilderExtensions { /// /// Registers as the event source for /// the resolved notifier. is invoked /// each time the source activates (lazy default activates on the /// first monitored-item subscription; eager activation under /// activates once at /// builder seal time). The factory receives the notifier node, /// the manager's , and a /// that the iterator is required /// to honor. /// /// /// Notifier node type. Constrained to /// because only object nodes carry /// the EventNotifier attribute. /// /// /// Event payload type. Must derive from /// ; the registry covariantly streams /// the items as BaseEventState through /// ReportEvent. /// /// /// is null. /// /// /// is null. /// public static INodeBuilder Publish( this INodeBuilder nodeBuilder, Func> factory, EventPublishOptions? options = null) where TNotifier : BaseObjectState where TEvent : BaseEventState { if (nodeBuilder == null) { throw new ArgumentNullException(nameof(nodeBuilder)); } if (factory == null) { throw new ArgumentNullException(nameof(factory)); } EventSourceRegistry registry = GetRegistryOrThrow(nodeBuilder); TNotifier notifier = nodeBuilder.Node; // IAsyncEnumerable is covariant on TEvent so the // cast at GetAsyncEnumerator time is allocation-free. registry.Register( notifier, (n, ctx, ct) => factory((TNotifier)n, ctx, ct), options); return nodeBuilder; } /// /// Registers the supplied as the event /// source for the resolved notifier. Each activation of the /// source calls GetAsyncEnumerator on the same /// instance — callers whose source is /// not re-iterable (e.g. a one-shot iterator) should use the /// factory overload instead. /// /// /// Notifier node type. Constrained to /// . /// /// /// Event payload type. Must derive from /// . /// /// /// is null. /// /// /// is null. /// public static INodeBuilder Publish( this INodeBuilder nodeBuilder, IAsyncEnumerable source, EventPublishOptions? options = null) where TNotifier : BaseObjectState where TEvent : BaseEventState { if (nodeBuilder == null) { throw new ArgumentNullException(nameof(nodeBuilder)); } if (source == null) { throw new ArgumentNullException(nameof(source)); } EventSourceRegistry registry = GetRegistryOrThrow(nodeBuilder); TNotifier notifier = nodeBuilder.Node; registry.Register( notifier, (_, _, _) => source, options); return nodeBuilder; } private static EventSourceRegistry GetRegistryOrThrow( INodeBuilder nodeBuilder) where TNotifier : NodeState { if (nodeBuilder.Builder is not NodeManagerBuilder concrete || concrete.EventSources == null) { string managerTypeName = nodeBuilder.Builder?.NodeManager?.GetType().FullName ?? "(unknown)"; throw ServiceResultException.Create( StatusCodes.BadConfigurationError, "Publish requires the node manager to derive from FluentNodeManagerBase. " + "Manager type '{0}' does not opt in.", managerTypeName); } return concrete.EventSources; } } }