/* ======================================================================== * 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/ * ======================================================================*/ namespace Opc.Ua.SourceGeneration { /// /// Templates emitted by the . /// /// /// All emitted code uses fully-qualified type names so the consuming /// project only needs to reference Opc.Ua.Server (which itself /// transitively brings in Opc.Ua.Core). /// internal static class NodeManagerTemplates { /// /// Top-level template for the partial NodeManager class. /// public static readonly TemplateString File = TemplateString.Parse( $$""" {{Tokens.CodeHeader}} // The namespace-taking constructor is emitted for every manager, // but the user's partial decides whether the class is sealed and // the generator cannot see that. In a sealed manager the protected // constructor is merely unreachable, not wrong. #pragma warning disable CS0628 // New protected member in sealed type namespace {{Tokens.NamespacePrefix}} { /// /// Source-generated node manager for the /// {{Tokens.NamespaceUri}} namespace. /// /// /// Implement partial void Configure(INodeManagerBuilder builder) /// in a sibling partial to wire per-node callbacks using the /// fluent API in Opc.Ua.Server.Fluent. Wiring that has to /// await — materialising instances, reading a store — goes into /// an override of /// ConfigureAsync(INodeManagerBuilder, CancellationToken), /// which runs first so Configure sees the nodes it created. /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{Tokens.Tool}}", "{{Tokens.Version}}")] public partial class {{Tokens.NodeManagerClassName}} : global::Opc.Ua.Server.Fluent.FluentNodeManagerBase, global::Opc.Ua.Server.Nodes.INodeSetImportFactoryProvider { private global::Opc.Ua.Server.Fluent.NodeManagerBuilder? __m_builder; /// /// The namespace URIs this manager owns, in the order they /// are reported to the master node manager. The first entry /// becomes NamespaceIndexes[0] and therefore the /// manager's own NamespaceIndex: the namespace an /// unqualified browse path resolves in. Where runtime /// NodeIds are minted is the NodeId factory's to decide, /// not this order's. Pass a different array to the /// protected constructor to change the set or the order. /// public static string[] DefaultNamespaceUris() { return new string[] { {{Tokens.NamespaceUri}}{{Tokens.AdditionalNamespaceUris}} }; } {{Tokens.NodeManagerDefaultConstructor}} /// /// Initializes a new /// that owns . Chain to this /// from a constructor of your own to take collaborators the /// generated signature does not carry, to change which /// namespaces the manager owns, or to change their order. /// /// The hosting server. /// The application configuration. /// /// The namespace URIs to report, in order. null adopts /// . /// protected {{Tokens.NodeManagerClassName}}( global::Opc.Ua.Server.IServerInternal server, global::Opc.Ua.ApplicationConfiguration configuration, string[]? namespaceUris) : base( server, configuration, global::Opc.Ua.TelemetryExtensions.CreateLogger<{{Tokens.NodeManagerClassName}}>( server.Telemetry), namespaceUris ?? DefaultNamespaceUris()) { } /// /// User extensibility hook. Implement in a sibling /// partial to wire callbacks via the fluent builder. /// Override /// FluentNodeManagerBase.ConfigureAsync instead when /// the wiring has to await. /// partial void Configure(global::Opc.Ua.Server.Fluent.INodeManagerBuilder builder); /// /// Source-generated typed counterpart of /// . /// Implement in a sibling partial to wire callbacks via the /// strongly-typed model-traversal surface generated for this manager. /// partial void Configure(I{{Tokens.NodeManagerClassName}}Builder builder); /// /// Supplies the concrete states used when a NodeSet2 /// document imported through /// INodeManagerBuilder.Import declares a node of /// this model. /// public global::Opc.Ua.ArrayOf< global::Opc.Ua.Server.Nodes.INodeSetImportFactory> GetNodeSetImportFactories() { var __factories = new global::System.Collections.Generic.List< global::Opc.Ua.Server.Nodes.INodeSetImportFactory>(); global::Opc.Ua.ArrayOf< global::Opc.Ua.Server.Nodes.INodeSetImportFactory> __generatedFactories = global::{{Tokens.Prefix}}. {{Tokens.NodeSetImportFactoryProviderClassName}}.Instance. GetNodeSetImportFactories(); for (int __index = 0; __index < __generatedFactories.Count; __index++) { __factories.Add(__generatedFactories[__index]); } AddNodeSetImportFactories(__factories); return new global::Opc.Ua.ArrayOf< global::Opc.Ua.Server.Nodes.INodeSetImportFactory>( __factories.ToArray()); } /// /// Adds import factories supplied by dependency models or /// by the application. Implement in a sibling /// partial. /// partial void AddNodeSetImportFactories( global::System.Collections.Generic.List< global::Opc.Ua.Server.Nodes.INodeSetImportFactory> factories); /// protected override global::System.Threading.Tasks.ValueTask LoadPredefinedNodesAsync( global::Opc.Ua.ISystemContext context, global::System.Threading.CancellationToken cancellationToken = default) { return new global::System.Threading.Tasks.ValueTask( global::{{Tokens.Prefix}}.{{Tokens.Namespace}}Extensions.Add{{Tokens.Namespace}}( new global::Opc.Ua.NodeStateCollection(), context)); } /// public override async global::System.Threading.Tasks.ValueTask CreateAddressSpaceAsync( global::System.Collections.Generic.IDictionary> externalReferences, global::System.Threading.CancellationToken cancellationToken = default) { await LoadPredefinedNodesAsync(SystemContext, externalReferences, cancellationToken).ConfigureAwait(false); ushort __nsIndex = Server.NamespaceUris.GetIndexOrAppend({{Tokens.NamespaceUri}}); __m_builder = new global::Opc.Ua.Server.Fluent.NodeManagerBuilder( SystemContext, this, __nsIndex, __FindRootByBrowseName, __FindRootByNodeId, __FindByTypeDefinitionId, __FindByDataTypeId); // Attach the FluentNodeManagerBase event-source registry // to the builder so Publish(...) extensions can resolve // it before the user's Configure partial(s) run. AttachToBuilder(__m_builder); // The awaitable wiring seam. Runs before the synchronous // Configure partial(s) so instances it materialises are in // the address space by the time they wire callbacks. await ConfigureAsync(__m_builder, cancellationToken).ConfigureAwait(false); Configure(__m_builder); Configure(new {{Tokens.NodeManagerClassName}}TypedBuilder(__m_builder)); // Register nodes created by the Configure partial(s) // through the builder's Add* methods before the // reverse-reference pass runs, so their references to // externally owned nodes are mirrored too. await RegisterAuthoredNodesAsync(__m_builder, cancellationToken).ConfigureAwait(false); // Mirror references from configure-created nodes to // nodes owned by other node managers (e.g. the Objects // folder) into the externalReferences dictionary. await CompleteConfigureAsync(externalReferences, cancellationToken).ConfigureAwait(false); // Seals the builder, replays NotifyNodeAdded for every // predefined node so per-node lifecycle hooks fire // deterministically, and only then completes the // registrations Configure could not await (root // notifiers) and starts the simulations, so no // simulated value change can precede the OnNodeAdded // handler of its own node. await SealConfigurationAsync(__m_builder, cancellationToken).ConfigureAwait(false); } /// protected override async global::System.Threading.Tasks.ValueTask AddPredefinedNodeAsync( global::Opc.Ua.ISystemContext context, global::Opc.Ua.NodeState node, global::System.Threading.CancellationToken cancellationToken = default) { await base.AddPredefinedNodeAsync(context, node, cancellationToken).ConfigureAwait(false); if (__m_builder is { } __b) { __b.Dispatcher.NotifyNodeAdded(context, node); } } /// protected override async global::System.Threading.Tasks.ValueTask RemovePredefinedNodeAsync( global::Opc.Ua.ISystemContext context, global::Opc.Ua.NodeState node, global::System.Collections.Generic.List referencesToRemove, global::System.Threading.CancellationToken cancellationToken = default) { if (__m_builder is { } __b) { __b.Dispatcher.NotifyNodeRemoved(context, node); } await base.RemovePredefinedNodeAsync(context, node, referencesToRemove, cancellationToken).ConfigureAwait(false); } private global::Opc.Ua.NodeState __FindRootByBrowseName(global::Opc.Ua.QualifiedName browseName) { if (browseName == null) { return null; } foreach (global::Opc.Ua.NodeState __node in PredefinedNodes.Values) { if (__node.BrowseName == browseName) { return __node; } } return null; } private global::Opc.Ua.NodeState __FindRootByNodeId(global::Opc.Ua.NodeId nodeId) { if (nodeId.IsNull) { return null; } return PredefinedNodes.TryGetValue(nodeId, out global::Opc.Ua.NodeState __node) ? __node : null; } private global::System.Collections.Generic.IReadOnlyList __FindByTypeDefinitionId( global::Opc.Ua.NodeId typeDefinitionId) { if (typeDefinitionId == null || typeDefinitionId.IsNull) { return global::System.Array.Empty(); } var __matches = new global::System.Collections.Generic.List(); var __queue = new global::System.Collections.Generic.Queue(); var __seen = new global::System.Collections.Generic.HashSet(); var __scratch = new global::System.Collections.Generic.List(); foreach (global::Opc.Ua.NodeState __root in PredefinedNodes.Values) { if (__root != null && __seen.Add(__root)) { __queue.Enqueue(__root); } } while (__queue.Count > 0) { global::Opc.Ua.NodeState __current = __queue.Dequeue(); if (__current is global::Opc.Ua.BaseInstanceState __instance && __instance.TypeDefinitionId == typeDefinitionId) { __matches.Add(__current); } __scratch.Clear(); __current.GetChildren(SystemContext, __scratch); for (int __i = 0; __i < __scratch.Count; __i++) { global::Opc.Ua.BaseInstanceState __child = __scratch[__i]; if (__child != null && __seen.Add(__child)) { __queue.Enqueue(__child); } } } return __matches; } private global::Opc.Ua.ArrayOf __FindByDataTypeId( global::Opc.Ua.NodeId dataTypeId) { if (dataTypeId == null || dataTypeId.IsNull) { return []; } var __matches = new global::System.Collections.Generic.List(); var __queue = new global::System.Collections.Generic.Queue(); var __seen = new global::System.Collections.Generic.HashSet(); var __scratch = new global::System.Collections.Generic.List(); foreach (global::Opc.Ua.NodeState __root in PredefinedNodes.Values) { if (__root != null && __seen.Add(__root)) { __queue.Enqueue(__root); } } while (__queue.Count > 0) { global::Opc.Ua.NodeState __current = __queue.Dequeue(); if (__current is global::Opc.Ua.BaseVariableState __variable && __variable.DataType == dataTypeId) { __matches.Add(__current); } __scratch.Clear(); __current.GetChildren(SystemContext, __scratch); for (int __i = 0; __i < __scratch.Count; __i++) { global::Opc.Ua.BaseInstanceState __child = __scratch[__i]; if (__child != null && __seen.Add(__child)) { __queue.Enqueue(__child); } } } return new global::Opc.Ua.ArrayOf( __matches.ToArray()); } } } """); /// /// The public (server, configuration) constructor. Emitted /// unless the binding sets GenerateDefaultConstructor=false, /// which managers do when they cannot be built from those two /// arguments alone. /// public static readonly TemplateString DefaultConstructor = TemplateString.Parse( $$""" /// /// Initializes a new /// owning . /// /// The hosting server. /// The application configuration. public {{Tokens.NodeManagerClassName}}( global::Opc.Ua.Server.IServerInternal server, global::Opc.Ua.ApplicationConfiguration configuration) : this(server, configuration, null) { } """); /// /// Top-level template for the generated factory class. /// public static readonly TemplateString FactoryFile = TemplateString.Parse( $$""" {{Tokens.CodeHeader}} namespace {{Tokens.NamespacePrefix}} { /// /// Source-generated /// for the {{Tokens.NamespaceUri}} namespace. /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{Tokens.Tool}}", "{{Tokens.Version}}")] public partial class {{Tokens.NodeManagerFactoryClassName}} : global::Opc.Ua.Server.IAsyncNodeManagerFactory { /// public virtual global::Opc.Ua.ArrayOf NamespacesUris => new global::Opc.Ua.ArrayOf(new string[] { {{Tokens.NamespaceUri}}{{Tokens.AdditionalNamespaceUris}} }); /// public virtual global::System.Threading.Tasks.ValueTask CreateAsync( global::Opc.Ua.Server.IServerInternal server, global::Opc.Ua.ApplicationConfiguration configuration, global::System.Threading.CancellationToken cancellationToken = default) { return new global::System.Threading.Tasks.ValueTask( new {{Tokens.NodeManagerClassName}}(server, configuration)); } } } """); } }