mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-27 13:10:37 +08:00
# Description Full-repo `dotnet format` sweep across all 73 projects in `UA.slnx` — no behavioral changes.
431 lines
16 KiB
C#
431 lines
16 KiB
C#
/* ========================================================================
|
|
* 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;
|
|
|
|
namespace Opc.Ua.Server.Fluent
|
|
{
|
|
/// <summary>
|
|
/// Default <see cref="INodeBuilder"/> implementation. Wires variable,
|
|
/// method, event, and condition-refresh callbacks directly onto the
|
|
/// resolved <see cref="NodeState"/>; routes manager-level callbacks
|
|
/// (history, monitored-item, lifecycle) through the parent
|
|
/// <see cref="NodeManagerBuilder"/>'s dispatcher.
|
|
/// </summary>
|
|
internal class NodeBuilder : INodeBuilder
|
|
{
|
|
public NodeBuilder(NodeManagerBuilder parent, NodeState node)
|
|
{
|
|
m_parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
|
Node = node ?? throw new ArgumentNullException(nameof(node));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public NodeState Node { get; }
|
|
|
|
/// <inheritdoc/>
|
|
public INodeManagerBuilder Builder => m_parent;
|
|
|
|
/// <summary>
|
|
/// Explicit INodeStateBuilder bridging: the Core marker uses
|
|
/// INodeStateBuilder return types; the server-side INodeBuilder
|
|
/// narrows those to INodeBuilder. Forward the marker methods to
|
|
/// the typed implementations so the same builder satisfies both
|
|
/// shapes without forcing every caller through a cast.
|
|
/// </summary>
|
|
/// <param name="browseName"></param>
|
|
/// <returns></returns>
|
|
INodeStateBuilder INodeStateBuilder.Child(QualifiedName browseName)
|
|
{
|
|
return Child(browseName);
|
|
}
|
|
|
|
INodeStateBuilder<TState> INodeStateBuilder.Child<TState>(QualifiedName browseName)
|
|
{
|
|
return Child<TState>(browseName);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder<TState> As<TState>() where TState : NodeState
|
|
{
|
|
if (Node is not TState typed)
|
|
{
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadTypeMismatch,
|
|
"Node '{0}' is of type {1}, which is not assignable to {2}.",
|
|
Node.BrowseName,
|
|
Node.GetType().Name,
|
|
typeof(TState).Name);
|
|
}
|
|
|
|
return new NodeBuilder<TState>(m_parent, typed);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnRead(NodeValueEventHandler handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnRead");
|
|
ThrowIfSlotOccupied(v.OnReadValue, "OnRead");
|
|
v.OnReadValue = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnRead(NodeValueSimpleEventHandler handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnSimpleRead");
|
|
ThrowIfSlotOccupied(v.OnSimpleReadValue, "OnSimpleRead");
|
|
v.OnSimpleReadValue = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnWrite(NodeValueEventHandler handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnWrite");
|
|
ThrowIfSlotOccupied(v.OnWriteValue, "OnWrite");
|
|
v.OnWriteValue = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnWrite(NodeValueSimpleEventHandler handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnSimpleWrite");
|
|
ThrowIfSlotOccupied(v.OnSimpleWriteValue, "OnSimpleWrite");
|
|
v.OnSimpleWriteValue = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnRead(NodeValueEventHandlerAsync handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnReadAsync");
|
|
ThrowIfSlotOccupied(v.OnReadValueAsync, "OnReadAsync");
|
|
v.OnReadValueAsync = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnRead(NodeValueSimpleEventHandlerAsync handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnSimpleReadAsync");
|
|
ThrowIfSlotOccupied(v.OnSimpleReadValueAsync, "OnSimpleReadAsync");
|
|
v.OnSimpleReadValueAsync = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnWrite(NodeValueWriteEventHandlerAsync handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnWriteAsync");
|
|
ThrowIfSlotOccupied(v.OnWriteValueAsync, "OnWriteAsync");
|
|
v.OnWriteValueAsync = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnWrite(NodeValueSimpleWriteEventHandlerAsync handler)
|
|
{
|
|
BaseVariableState v = RequireVariable("OnSimpleWriteAsync");
|
|
ThrowIfSlotOccupied(v.OnSimpleWriteValueAsync, "OnSimpleWriteAsync");
|
|
v.OnSimpleWriteValueAsync = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnCall(GenericMethodCalledEventHandler2 handler)
|
|
{
|
|
MethodState m = RequireMethod("OnCall");
|
|
ThrowIfSlotOccupied(m.OnCallMethod2, "OnCall");
|
|
m.OnCallMethod2 = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnCall(GenericMethodCalledEventHandler2Async handler)
|
|
{
|
|
MethodState m = RequireMethod("OnCallAsync");
|
|
ThrowIfSlotOccupied(m.OnCallMethod2Async, "OnCallAsync");
|
|
m.OnCallMethod2Async = handler ?? throw new ArgumentNullException(nameof(handler));
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnNodeAdded(NodeLifecycleHandler handler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(handler));
|
|
}
|
|
m_parent.RegisterNodeAdded(Node, handler);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnNodeRemoved(NodeLifecycleHandler handler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(handler));
|
|
}
|
|
m_parent.RegisterNodeRemoved(Node, handler);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnHistoryRead(HistoryReadHandler handler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(handler));
|
|
}
|
|
m_parent.RegisterHistoryRead(Node, handler);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnHistoryUpdate(HistoryUpdateHandler handler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(handler));
|
|
}
|
|
m_parent.RegisterHistoryUpdate(Node, handler);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnConditionRefresh(ConditionRefreshHandler handler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(handler));
|
|
}
|
|
ThrowIfSlotOccupied(Node.OnConditionRefresh, "OnConditionRefresh");
|
|
// Direct wire — signature matches NodeStateConditionRefreshEventHandler.
|
|
Node.OnConditionRefresh = (ctx, n, evts) => handler(ctx, n, evts);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnMonitoredItemCreated(MonitoredItemCreatedHandler handler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(handler));
|
|
}
|
|
m_parent.RegisterMonitoredItemCreated(Node, handler);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder OnEvent(EventNotificationHandler handler)
|
|
{
|
|
if (handler == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(handler));
|
|
}
|
|
// OnReportEvent is also used by CustomNodeManager2 for root notifier
|
|
// wiring — replacing it would silently break server event propagation.
|
|
ThrowIfSlotOccupied(Node.OnReportEvent, "OnEvent (NodeState.OnReportEvent)");
|
|
Node.OnReportEvent = (ctx, n, e) => handler(ctx, n, e);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder AllowMultipleEventConsumers(bool enable = true)
|
|
{
|
|
m_parent.RegisterMultiConsumerNode(Node, enable);
|
|
return this;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder Child(QualifiedName browseName)
|
|
{
|
|
NodeState child = ResolveChild(browseName);
|
|
return new NodeBuilder(m_parent, child);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public INodeBuilder<TState> Child<TState>(QualifiedName browseName)
|
|
where TState : NodeState
|
|
{
|
|
NodeState child = ResolveChild(browseName);
|
|
if (child is not TState typed)
|
|
{
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadTypeMismatch,
|
|
"Child '{0}' under '{1}' is of type {2}, which is not assignable to {3}.",
|
|
browseName,
|
|
Node.BrowseName,
|
|
child.GetType().Name,
|
|
typeof(TState).Name);
|
|
}
|
|
return new NodeBuilder<TState>(m_parent, typed);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IVariableBuilder<TValue> Variable<TValue>(QualifiedName browseName)
|
|
{
|
|
NodeState child = ResolveChild(browseName);
|
|
return m_parent.ToVariableBuilder<TValue>(
|
|
child,
|
|
CoreUtils.Format("{0}/{1}", Node.BrowseName, browseName));
|
|
}
|
|
|
|
private BaseInstanceState ResolveChild(QualifiedName browseName)
|
|
{
|
|
if (browseName.IsNull)
|
|
{
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadBrowseNameInvalid,
|
|
"Browse name is null or empty.");
|
|
}
|
|
|
|
// Try exact-namespace match first.
|
|
if (Node.FindChild(m_parent.Context, browseName) is BaseInstanceState child)
|
|
{
|
|
return child;
|
|
}
|
|
// Cross-namespace fallback: when no exact-namespace match,
|
|
// scan children by local name only. Required so generator-
|
|
// emitted typed accessors can pass an unqualified
|
|
// QualifiedName(name) and have it resolve through namespace
|
|
// transitions induced by inherited type children (e.g.
|
|
// PumpType inheriting Operational from MachineComponentType).
|
|
// Ambiguity (more than one child with the same local name
|
|
// in different namespaces) is reported as
|
|
// BadBrowseNameDuplicated so the caller is forced to pass
|
|
// the disambiguating namespace index explicitly.
|
|
var siblings = new System.Collections.Generic.List<BaseInstanceState>();
|
|
Node.GetChildren(m_parent.Context, siblings);
|
|
BaseInstanceState? match = null;
|
|
for (int i = 0; i < siblings.Count; i++)
|
|
{
|
|
BaseInstanceState sibling = siblings[i];
|
|
if (sibling.BrowseName.IsNull ||
|
|
!string.Equals(
|
|
sibling.BrowseName.Name, browseName.Name,
|
|
StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
if (match != null)
|
|
{
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadBrowseNameDuplicated,
|
|
"Child '{0}' under '{1}' is ambiguous (matches " +
|
|
"multiple namespaces; supply the namespace index explicitly).",
|
|
browseName, Node.BrowseName);
|
|
}
|
|
match = sibling;
|
|
}
|
|
if (match != null)
|
|
{
|
|
return match;
|
|
}
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadNodeIdUnknown,
|
|
"Child '{0}' not found under '{1}' (id '{2}').",
|
|
browseName,
|
|
Node.BrowseName,
|
|
Node.NodeId);
|
|
}
|
|
|
|
private BaseVariableState RequireVariable(string what)
|
|
{
|
|
if (Node is not BaseVariableState v)
|
|
{
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadInvalidArgument,
|
|
"Cannot wire {0} on node '{1}': not a BaseVariableState (actual: {2}).",
|
|
what,
|
|
Node.BrowseName,
|
|
Node.GetType().Name);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
private MethodState RequireMethod(string what)
|
|
{
|
|
if (Node is not MethodState m)
|
|
{
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadInvalidArgument,
|
|
"Cannot wire {0} on node '{1}': not a MethodState (actual: {2}).",
|
|
what,
|
|
Node.BrowseName,
|
|
Node.GetType().Name);
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
private void ThrowIfSlotOccupied(Delegate? existing, string what)
|
|
{
|
|
if (existing != null)
|
|
{
|
|
throw ServiceResultException.Create(
|
|
StatusCodes.BadConfigurationError,
|
|
"Node '{0}' (id '{1}') already has a {2} handler assigned.",
|
|
Node.BrowseName,
|
|
Node.NodeId,
|
|
what);
|
|
}
|
|
}
|
|
|
|
private readonly NodeManagerBuilder m_parent;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Strongly-typed <see cref="INodeBuilder{TState}"/> wrapper. Inherits
|
|
/// all <c>On*</c> behavior from the non-generic
|
|
/// <see cref="NodeBuilder"/>; the only added value is a typed
|
|
/// <see cref="Node"/> property.
|
|
/// </summary>
|
|
/// <typeparam name="TState">
|
|
/// The concrete <see cref="NodeState"/> derivative the resolved node
|
|
/// is narrowed to.
|
|
/// </typeparam>
|
|
internal class NodeBuilder<TState> : NodeBuilder, INodeBuilder<TState>
|
|
where TState : NodeState
|
|
{
|
|
public NodeBuilder(NodeManagerBuilder parent, TState node)
|
|
: base(parent, node)
|
|
{
|
|
Node = node;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public new TState Node { get; }
|
|
}
|
|
}
|