/* ========================================================================
* 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;
}
}