/* ======================================================================== * 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 Opc.Ua.Server.AliasNames; namespace Opc.Ua.Server { /// /// OPC UA Part 17 (Alias Names) integration for the standard /// well-known nodes loaded by DiagnosticsNodeManager from /// Opc.Ua.NodeSet2.xml. /// /// /// The standard NodeSet ships three AliasNameCategoryType /// instances under the Server object (Part 17 §9): /// /// Aliases (i=23470) — with mandatory /// FindAlias (i=23476) and a LastChange (i=32852) /// property; /// TagVariables (i=23479) — with mandatory /// FindAlias (i=23485); /// Topics (i=23488) — with mandatory /// FindAlias (i=23494). /// /// None of the optional Part 17 children /// (FindAliasVerbose/AddAliasesToCategory/DeleteAliasesFromCategory) /// are instantiated by the standard NodeSet on these well-known nodes, /// so this binder only wires FindAlias and (for /// Aliases) LastChange. Optional methods on /// application-defined categories are wired by /// instead. /// public partial class DiagnosticsNodeManager { /// /// Resolves the server's /// (via the optional /// interface) and wires the standard well-known /// Aliases/TagVariables/Topics FindAlias /// methods (plus Aliases.LastChange) to dispatch through /// it. The wiring is "live" — the registry is queried at each /// call rather than snapshotted at load time — so stores /// registered after this point are also reachable. /// private void WireStandardAliasMethods() { IAliasNameStoreRegistry? registry = (Server as IAliasNameStoreRegistryProvider)?.AliasNameStoreRegistry; if (registry == null) { return; } m_aliasRegistry = registry; m_aliasRegistry.Changed += OnAliasRegistryChanged; WireStandardCategory(ObjectIds.Aliases, includeLastChange: true); WireStandardCategory(ObjectIds.TagVariables, includeLastChange: false); WireStandardCategory(ObjectIds.Topics, includeLastChange: false); } private void WireStandardCategory(NodeId categoryId, bool includeLastChange) { AliasNameCategoryState? category = FindPredefinedNode(categoryId); if (category == null) { return; } category.FindAlias?.OnCallAsync = (ctx, method, objId, pattern, refType, ct) => AliasNameMethodDispatcher.FindAliasAsync( m_aliasRegistry!, Server.TypeTree, objId.IsNull ? categoryId : objId, pattern, refType, ct); if (includeLastChange && category.LastChange != null) { uint? seed = null; foreach (IAliasNameStore store in m_aliasRegistry!.Stores) { uint? v = store.GetLastChange(categoryId); if (v.HasValue) { seed = v; break; } } if (seed.HasValue) { category.LastChange.Value = seed.Value; category.LastChange.ClearChangeMasks(SystemContext, false); } m_aliasesLastChangeNode = category.LastChange; } } private void OnAliasRegistryChanged(object? sender, AliasStoreChangedEventArgs e) { // Only the standard Aliases (i=23470) node carries a LastChange // property in the shipped NodeSet; mirror its store value. if (m_aliasesLastChangeNode != null && e.CategoryId == ObjectIds.Aliases) { m_aliasesLastChangeNode.Value = e.LastChange; m_aliasesLastChangeNode.ClearChangeMasks(SystemContext, false); } } /// /// Detaches the handler. /// Invoked from Dispose(bool) so the registry — which /// outlives this manager when other node managers share the same /// server — does not hold a stale reference into the disposed /// . /// private void UnwireStandardAliasMethods() { IAliasNameStoreRegistry? registry = m_aliasRegistry; if (registry != null) { registry.Changed -= OnAliasRegistryChanged; m_aliasRegistry = null; } m_aliasesLastChangeNode = null; } private IAliasNameStoreRegistry? m_aliasRegistry; private PropertyState? m_aliasesLastChangeNode; } }