/* ========================================================================
* Copyright (c) 2005-2026 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.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua;
using Opc.Ua.Export;
using Opc.Ua.Server;
using Opc.Ua.Server.Fluent;
using Quickstarts.ReferenceServer;
namespace NodeSetImportModel
{
///
/// A source-generated node manager for the NodeSet import test model
/// which overlays a NodeSet2 document on top of its generated nodes.
///
///
/// The class is declared in the generated model namespace so the emitted
/// partial can reach the model's node states, and the generator makes it
/// implement INodeSetImportFactoryProvider, so the overlay is
/// imported into this model's typed states.
///
[NodeManager(NamespaceUri = NodeSetImportTestModel.NamespaceUri)]
public sealed partial class NodeSetImportOverlayNodeManager
{
///
/// The Device instance the overlay document adds.
///
public DeviceState OverlayDevice { get; private set; }
///
/// The Value variable the overlay document supplies for the generated
/// Device instance, replacing its generated child.
///
public CustomValueState ReplacedDeviceValue { get; private set; }
///
/// The node the generated Device.Value child had before the
/// overlay replaced it.
///
public NodeId DisplacedDeviceValueId { get; private set; }
partial void Configure(INodeManagerBuilder builder)
{
ushort namespaceIndex = builder.Context.NamespaceUris.GetIndexOrAppend(
NodeSetImportTestModel.NamespaceUri);
DisplacedDeviceValueId = new NodeId(
NodeSetImportTestModel.GeneratedDeviceValueIdentifier,
namespaceIndex);
// Two documents of one batch: the second one declares a parent
// which only exists in the first one.
builder.Import(NodeSetImportTestModel.ReadOverlayDevice());
builder.Import(NodeSetImportTestModel.ReadOverlayDeviceChildren());
OverlayDevice = builder
.Node(
new NodeId(
NodeSetImportTestModel.OverlayDeviceIdentifier,
namespaceIndex))
.Node;
ReplacedDeviceValue = builder
.Node(
new NodeId(
NodeSetImportTestModel.ReplacedDeviceValueIdentifier,
namespaceIndex))
.Node;
}
}
///
/// Captures the node manager instance the server creates so the tests can
/// assert against its address space.
///
public sealed class NodeSetImportOverlayNodeManagerCapturingFactory :
NodeSetImportOverlayNodeManagerFactory
{
///
/// The manager created for the running server.
///
public NodeSetImportOverlayNodeManager Manager { get; private set; } = null!;
///
public override ValueTask CreateAsync(
IServerInternal server,
ApplicationConfiguration configuration,
CancellationToken cancellationToken = default)
{
var manager = new NodeSetImportOverlayNodeManager(server, configuration);
Manager = manager;
return new ValueTask(manager);
}
}
///
/// Constants and overlay documents shared by the NodeSet import tests.
///
public static class NodeSetImportTestModel
{
///
/// The model namespace generated from
/// Fluent/Assets/NodeSetImport.NodeSet2.xml.
///
public const string NamespaceUri = "urn:opcfoundation.org:2026-09:NodeSetImport";
///
/// The DeviceType instance the model itself declares.
///
public const uint GeneratedDeviceIdentifier = 2000u;
///
/// The generated Device.Value child the overlay displaces.
///
public const uint GeneratedDeviceValueIdentifier = 2001u;
///
/// The DeviceType instance the overlay adds.
///
public const uint OverlayDeviceIdentifier = 3000u;
///
/// The Calibrate method the overlay adds to
/// from a second document.
///
public const uint OverlayDeviceCalibrateIdentifier = 3001u;
///
/// The Value variable the overlay supplies for the generated
/// Device instance.
///
public const uint ReplacedDeviceValueIdentifier = 3300u;
///
/// A document adding one more instance of the model's DeviceType, plus
/// a Value variable which replaces the generated child of the model's
/// own Device instance.
///
public static UANodeSet ReadOverlayDevice()
{
return Read(
"""
OverlayDevice
i=85
ns=1;i=1000
Value
ns=1;i=1001
ns=1;i=2000
""");
}
///
/// A second document whose nodes hang off the instance declared by
/// .
///
public static UANodeSet ReadOverlayDeviceChildren()
{
return Read(
"""
Calibrate
ns=1;i=3000
""");
}
private static UANodeSet Read(string nodes)
{
string xml =
"\r\n" +
"\r\n" +
" \r\n" +
" " + NamespaceUri + "\r\n" +
" \r\n" +
nodes + "\r\n" +
"";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
return UANodeSet.Read(stream)!;
}
}
///
/// A reference server hosting the generated node manager which overlays a
/// NodeSet2 document onto its own model.
///
public sealed class NodeSetImportServer : ReferenceServer
{
///
/// Initializes the server and registers the overlay node manager.
///
public NodeSetImportServer(ITelemetryContext telemetry)
: base(telemetry)
{
NodeManagerFactory = new NodeSetImportOverlayNodeManagerCapturingFactory();
AddNodeManager(NodeManagerFactory);
}
///
/// The factory which captured the created node manager.
///
public NodeSetImportOverlayNodeManagerCapturingFactory NodeManagerFactory { get; }
}
}