mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-20 00:31:32 +08:00
30ed2390a8
## What
`INodeManagerBuilder.Import(UANodeSet, INodeSetImportFactoryProvider?)`
lets a source-generated (or hand-written fluent) node manager overlay a
NodeSet2 document that is only known at runtime onto the model it was
generated from.
Every document imported during one `Configure` pass forms a single batch
that is linked **exactly once** after the pass. A node may therefore
declare a `ParentNodeId` that lives in another document of the batch, or
a node the manager already owns — which is what makes an overlay
possible. Imported nodes resolve through
`Node(...)`/`NodeFromTypeId(...)` immediately, so they can be wired in
the same pass; they are registered with the manager when
`CompleteConfigureAsync` runs.
### Public surface
- **+1 method**: `INodeManagerBuilder.Import(UANodeSet nodeSet,
INodeSetImportFactoryProvider? factoryProvider = null)`.
- **+3 contracts** in `Opc.Ua.Server.Nodes`:
`INodeSetImportFactoryProvider`, `INodeSetImportFactory`,
`NodeSetImportDiscriminator` (TypeDefinition / MethodDeclaration /
NodeId).
- In `Opc.Ua.Export`: a public `UANodeSet.Import` overload taking a
`NodeSetImportStateFactory`, and a public batch
`LinkParentChildRelationships(context, nodes,
NodeSetImportLinkOptions)`. These replace what would otherwise have been
an `InternalsVisibleTo("Opc.Ua.Server")` on `Opc.Ua.Types`.
- `NodeState.IsExplicitlyDefinedChild(context, child)` and `protected
DetachExplicitlyDefinedChild(child)`, used to tell a generated slot
apart from an ordinary child.
### Typed states, no reflection
The generator emits `{Model}NodeSetImportFactoryProvider` — one factory
per model type/declaration, each calling a concrete constructor for an
*empty* state — and makes the generated node manager implement
`INodeSetImportFactoryProvider` by delegating to it. So the builder
resolves the provider from the manager (`NodeManager as
INodeSetImportFactoryProvider`); `Import` also accepts one directly, and
the generated manager exposes `partial void
AddNodeSetImportFactories(List<INodeSetImportFactory>)` for dependency
models. An imported node of a generated type materializes as its
generated `*State` subclass with no type lookup, which keeps the path
NativeAOT-safe.
Because the factories create empty states, an imported instance carries
exactly the children its document declares.
### Reconciliation
When an imported child lands in a slot the generated parent declares
(same BrowseName), it replaces the generated placeholder: the displaced
node and every descendant the document does not carry over are removed
from the manager, and references to them are retargeted at the
replacement. Replacing a node the same `Configure` pass already wired is
rejected with `BadInvalidState` rather than silently dropping the
wiring, and sealing a builder with an unregistered import batch is
rejected too.
### Also
- `RuntimeNodeSetNodeManager` now uses the same importer: its documents
are one batch, so a node may declare a parent in another document of the
group regardless of parse order, and duplicate NodeIds across documents
are detected while parsing.
- Linking resolves an imported child's reference type from its own
inverse reference (previously always `HasComponent`/`HasProperty`).
- Method import keeps the document's `UserExecutable`; it previously
copied `Executable` into it. View import keeps `EventNotifier`.
## Tests
- `NodeSetImporterTests` (15) — discriminator selection, generic
fallbacks, field/value/reference fidelity against a generic import,
cross-document linking, typed replacement of inherited and argument
slots, deferred linking preserving an application `Handle`, duplicate
NodeIds, factory misuse.
- `NodeSetImportBuilderTests` (15) — staging and lookup before
registration, batch linking, attaching to an existing node, provider
resolution, placeholder replacement with reference retargeting,
configured-placeholder rejection, seal/completion guards.
- `NodeSetImportIntegrationTests` (5) — a generated `[NodeManager]`
overlaying its own model on a running server: typed states through the
generated provider, cross-document linking, placeholder replacement, and
browse through a session.
- `NodeManagerGeneratorTests` (+4) — the emitted provider and the
manager's delegation, including that no factory uses
`Activator.CreateInstance`/reflection.
- `NodeSetAotTests` (+1) — the public state-factory import under
NativeAOT.
Full `Opc.Ua.Server.Tests` (4946), `Opc.Ua.SourceGeneration.Core.Tests`
(3821), `Opc.Ua.SourceGeneration.Tests` (165) and `Opc.Ua.Types.Tests`
pass locally on net10.0.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
238 lines
8.8 KiB
XML
238 lines
8.8 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!--
|
|
* 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/
|
|
-->
|
|
<UANodeSet xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd"
|
|
xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd">
|
|
<NamespaceUris>
|
|
<Uri>urn:opcfoundation.org:2026-09:NodeSetImport</Uri>
|
|
</NamespaceUris>
|
|
<Models>
|
|
<Model ModelUri="urn:opcfoundation.org:2026-09:NodeSetImport"
|
|
PublicationDate="2026-09-02T00:00:00Z"
|
|
Version="1.0.0">
|
|
<RequiredModel ModelUri="http://opcfoundation.org/UA/"
|
|
PublicationDate="2025-12-25T00:00:00Z"
|
|
Version="1.05.07"/>
|
|
</Model>
|
|
</Models>
|
|
<Aliases>
|
|
<Alias Alias="Argument">i=296</Alias>
|
|
<Alias Alias="BaseDataVariableType">i=63</Alias>
|
|
<Alias Alias="BaseObjectType">i=58</Alias>
|
|
<Alias Alias="HasComponent">i=47</Alias>
|
|
<Alias Alias="HasModellingRule">i=37</Alias>
|
|
<Alias Alias="HasProperty">i=46</Alias>
|
|
<Alias Alias="HasSubtype">i=45</Alias>
|
|
<Alias Alias="HasTypeDefinition">i=40</Alias>
|
|
<Alias Alias="Int32">i=6</Alias>
|
|
<Alias Alias="Mandatory">i=78</Alias>
|
|
<Alias Alias="Organizes">i=35</Alias>
|
|
<Alias Alias="PropertyType">i=68</Alias>
|
|
</Aliases>
|
|
|
|
<UAVariableType NodeId="ns=1;i=1001"
|
|
BrowseName="1:CustomValueType"
|
|
DataType="i=6">
|
|
<DisplayName>CustomValueType</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasSubtype" IsForward="false">BaseDataVariableType</Reference>
|
|
</References>
|
|
</UAVariableType>
|
|
|
|
<UAMethod NodeId="ns=1;i=1010"
|
|
BrowseName="1:CalibrateMethodType">
|
|
<DisplayName>CalibrateMethodType</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasProperty">ns=1;i=1011</Reference>
|
|
<Reference ReferenceType="HasProperty">ns=1;i=1012</Reference>
|
|
</References>
|
|
</UAMethod>
|
|
|
|
<UAVariable NodeId="ns=1;i=1011"
|
|
BrowseName="InputArguments"
|
|
ParentNodeId="ns=1;i=1010"
|
|
DataType="i=296"
|
|
ValueRank="1"
|
|
ArrayDimensions="1">
|
|
<DisplayName>InputArguments</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasTypeDefinition">PropertyType</Reference>
|
|
<Reference ReferenceType="HasModellingRule">Mandatory</Reference>
|
|
<Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=1010</Reference>
|
|
</References>
|
|
<Value>
|
|
<uax:ListOfExtensionObject>
|
|
<uax:ExtensionObject>
|
|
<uax:TypeId>
|
|
<uax:Identifier>i=297</uax:Identifier>
|
|
</uax:TypeId>
|
|
<uax:Body>
|
|
<uax:Argument>
|
|
<uax:Name>target</uax:Name>
|
|
<uax:DataType>
|
|
<uax:Identifier>i=6</uax:Identifier>
|
|
</uax:DataType>
|
|
<uax:ValueRank>-1</uax:ValueRank>
|
|
<uax:ArrayDimensions />
|
|
<uax:Description />
|
|
</uax:Argument>
|
|
</uax:Body>
|
|
</uax:ExtensionObject>
|
|
</uax:ListOfExtensionObject>
|
|
</Value>
|
|
</UAVariable>
|
|
|
|
<UAVariable NodeId="ns=1;i=1012"
|
|
BrowseName="OutputArguments"
|
|
ParentNodeId="ns=1;i=1010"
|
|
DataType="i=296"
|
|
ValueRank="1"
|
|
ArrayDimensions="1">
|
|
<DisplayName>OutputArguments</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasTypeDefinition">PropertyType</Reference>
|
|
<Reference ReferenceType="HasModellingRule">Mandatory</Reference>
|
|
<Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=1010</Reference>
|
|
</References>
|
|
<Value>
|
|
<uax:ListOfExtensionObject>
|
|
<uax:ExtensionObject>
|
|
<uax:TypeId>
|
|
<uax:Identifier>i=297</uax:Identifier>
|
|
</uax:TypeId>
|
|
<uax:Body>
|
|
<uax:Argument>
|
|
<uax:Name>accepted</uax:Name>
|
|
<uax:DataType>
|
|
<uax:Identifier>i=6</uax:Identifier>
|
|
</uax:DataType>
|
|
<uax:ValueRank>-1</uax:ValueRank>
|
|
<uax:ArrayDimensions />
|
|
<uax:Description />
|
|
</uax:Argument>
|
|
</uax:Body>
|
|
</uax:ExtensionObject>
|
|
</uax:ListOfExtensionObject>
|
|
</Value>
|
|
</UAVariable>
|
|
|
|
<UAObjectType NodeId="ns=1;i=1000"
|
|
BrowseName="1:DeviceType">
|
|
<DisplayName>DeviceType</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasSubtype" IsForward="false">BaseObjectType</Reference>
|
|
<Reference ReferenceType="HasComponent">ns=1;i=1002</Reference>
|
|
<Reference ReferenceType="HasComponent">ns=1;i=1003</Reference>
|
|
</References>
|
|
</UAObjectType>
|
|
|
|
<UAVariable NodeId="ns=1;i=1002"
|
|
BrowseName="1:Value"
|
|
ParentNodeId="ns=1;i=1000"
|
|
DataType="i=6">
|
|
<DisplayName>Value</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasTypeDefinition">ns=1;i=1001</Reference>
|
|
<Reference ReferenceType="HasModellingRule">Mandatory</Reference>
|
|
<Reference ReferenceType="HasComponent" IsForward="false">ns=1;i=1000</Reference>
|
|
</References>
|
|
</UAVariable>
|
|
|
|
<UAMethod NodeId="ns=1;i=1003"
|
|
BrowseName="1:Calibrate"
|
|
ParentNodeId="ns=1;i=1000"
|
|
MethodDeclarationId="ns=1;i=1010">
|
|
<DisplayName>Calibrate</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasModellingRule">Mandatory</Reference>
|
|
<Reference ReferenceType="HasComponent" IsForward="false">ns=1;i=1000</Reference>
|
|
</References>
|
|
</UAMethod>
|
|
|
|
<UAObject NodeId="ns=1;i=2000"
|
|
BrowseName="1:Device"
|
|
ParentNodeId="i=85">
|
|
<DisplayName>Device</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="Organizes" IsForward="false">i=85</Reference>
|
|
<Reference ReferenceType="HasTypeDefinition">ns=1;i=1000</Reference>
|
|
<Reference ReferenceType="HasComponent">ns=1;i=2001</Reference>
|
|
<Reference ReferenceType="HasComponent">ns=1;i=2002</Reference>
|
|
</References>
|
|
</UAObject>
|
|
|
|
<UAVariable NodeId="ns=1;i=2001"
|
|
BrowseName="1:Value"
|
|
ParentNodeId="ns=1;i=2000"
|
|
DataType="i=6">
|
|
<DisplayName>Value</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasTypeDefinition">ns=1;i=1001</Reference>
|
|
<Reference ReferenceType="HasComponent" IsForward="false">ns=1;i=2000</Reference>
|
|
</References>
|
|
</UAVariable>
|
|
|
|
<UAMethod NodeId="ns=1;i=2002"
|
|
BrowseName="1:Calibrate"
|
|
ParentNodeId="ns=1;i=2000"
|
|
MethodDeclarationId="ns=1;i=1003">
|
|
<DisplayName>Calibrate</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasComponent" IsForward="false">ns=1;i=2000</Reference>
|
|
<Reference ReferenceType="HasProperty">ns=1;i=2003</Reference>
|
|
<Reference ReferenceType="HasProperty">ns=1;i=2004</Reference>
|
|
</References>
|
|
</UAMethod>
|
|
|
|
<UAVariable NodeId="ns=1;i=2003"
|
|
BrowseName="InputArguments"
|
|
ParentNodeId="ns=1;i=2002"
|
|
DataType="i=296"
|
|
ValueRank="1"
|
|
ArrayDimensions="1">
|
|
<DisplayName>InputArguments</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasTypeDefinition">PropertyType</Reference>
|
|
<Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=2002</Reference>
|
|
</References>
|
|
</UAVariable>
|
|
|
|
<UAVariable NodeId="ns=1;i=2004"
|
|
BrowseName="OutputArguments"
|
|
ParentNodeId="ns=1;i=2002"
|
|
DataType="i=296"
|
|
ValueRank="1"
|
|
ArrayDimensions="1">
|
|
<DisplayName>OutputArguments</DisplayName>
|
|
<References>
|
|
<Reference ReferenceType="HasTypeDefinition">PropertyType</Reference>
|
|
<Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=2002</Reference>
|
|
</References>
|
|
</UAVariable>
|
|
</UANodeSet> |