in src/Azure.IIoT.OpcUa.Publisher/src/Services/NodeServices.cs [259:444]
public async Task<NodeMetadataResponseModel> GetMetadataAsync(
T endpoint, NodeMetadataRequestModel request, CancellationToken ct)
{
ArgumentNullException.ThrowIfNull(request);
if (string.IsNullOrEmpty(request.NodeId) &&
(request.BrowsePath == null || request.BrowsePath.Count == 0))
{
throw new ArgumentException("Node id or browse path missing", nameof(request));
}
using var trace = _activitySource.StartActivity("GetMetadata");
return await _client.ExecuteAsync(endpoint, async context =>
{
var nodeId = await context.Session.ResolveNodeIdAsync(request.Header, request.NodeId,
request.BrowsePath, nameof(request.BrowsePath), _timeProvider,
context.Ct).ConfigureAwait(false);
if (NodeId.IsNull(nodeId))
{
throw new ArgumentException("Node id missing", nameof(request));
}
var (node, errorInfo) = await context.Session.ReadNodeAsync(
request.Header.ToRequestHeader(_timeProvider), nodeId,
request.Header.GetNamespaceFormat(_options), ct: context.Ct).ConfigureAwait(false);
if (errorInfo != null || node.NodeClass == null)
{
return new NodeMetadataResponseModel
{
ErrorInfo = errorInfo ?? ((StatusCode)StatusCodes.BadNodeIdUnknown)
.CreateResultModel()
};
}
VariableMetadataModel? variableMetadata = null;
MethodMetadataModel? methodMetadata = null;
DataTypeMetadataModel? dataTypeMetadata = null;
var typeId = nodeId;
if (node.NodeClass == NodeClass.Method)
{
(methodMetadata, errorInfo) = await context.Session.GetMethodMetadataAsync(
request.Header.ToRequestHeader(_timeProvider), nodeId,
request.Header.GetNamespaceFormat(_options), context.Ct).ConfigureAwait(false);
if (errorInfo != null)
{
return new NodeMetadataResponseModel { ErrorInfo = errorInfo };
}
}
else if (node.NodeClass == NodeClass.DataType)
{
// TODO
dataTypeMetadata = null;
}
else if (node.NodeClass is NodeClass.Variable or NodeClass.Object)
{
// Get type definition
var (references, ei) = await context.Session.FindAsync(
request.Header.ToRequestHeader(_timeProvider), nodeId.YieldReturn(),
ReferenceTypeIds.HasTypeDefinition, maxResults: 1, ct: context.Ct).ConfigureAwait(false);
typeId = references.FirstOrDefault(r => r.ErrorInfo == null).Node;
if (NodeId.IsNull(typeId))
{
typeId = nodeId;
}
if (node.NodeClass == NodeClass.Variable)
{
(variableMetadata, errorInfo) = await context.Session.GetVariableMetadataAsync(
request.Header.ToRequestHeader(_timeProvider), nodeId,
request.Header.GetNamespaceFormat(_options), context.Ct).ConfigureAwait(false);
if (errorInfo != null)
{
return new NodeMetadataResponseModel { ErrorInfo = errorInfo };
}
}
}
var type = node;
if (typeId != nodeId)
{
(type, errorInfo) = await context.Session.ReadNodeAsync(
request.Header.ToRequestHeader(_timeProvider), typeId,
request.Header.GetNamespaceFormat(_options), ct: context.Ct).ConfigureAwait(false);
if (errorInfo != null || type.NodeClass == null)
{
return new NodeMetadataResponseModel
{
ErrorInfo = errorInfo ?? ((StatusCode)StatusCodes.BadTypeDefinitionInvalid)
.CreateResultModel()
};
}
}
// process the types starting from the top of the tree.
var map = new Dictionary<ImmutableRelativePath, InstanceDeclarationModel>();
var declarations = new List<InstanceDeclarationModel>();
var hierarchy = new List<(NodeId, ReferenceDescription)>();
await context.Session.CollectTypeHierarchyAsync(request.Header.ToRequestHeader(_timeProvider),
typeId, hierarchy, context.Ct).ConfigureAwait(false);
hierarchy.Reverse(); // Start from Root super type
foreach (var (subType, superType) in hierarchy)
{
errorInfo = await context.Session.CollectInstanceDeclarationsAsync(
request.Header.ToRequestHeader(_timeProvider), (NodeId)superType.NodeId,
null, declarations, map, request.Header.GetNamespaceFormat(_options),
ct: context.Ct).ConfigureAwait(false);
if (errorInfo != null)
{
break;
}
}
if (errorInfo == null)
{
// collect the fields for the selected type.
errorInfo = await context.Session.CollectInstanceDeclarationsAsync(
request.Header.ToRequestHeader(_timeProvider), typeId, null,
declarations, map, request.Header.GetNamespaceFormat(_options),
ct: context.Ct).ConfigureAwait(false);
}
return new NodeMetadataResponseModel
{
ErrorInfo = errorInfo,
Description = node.Description,
DisplayName = node.DisplayName,
NodeClass = node.NodeClass.Value,
NodeId = node.NodeId,
MethodMetadata = methodMetadata,
VariableMetadata = variableMetadata,
DataTypeMetadata = dataTypeMetadata,
TypeDefinition = errorInfo != null ? null : new TypeDefinitionModel
{
TypeDefinitionId = type.NodeId,
DisplayName = type.DisplayName,
BrowseName = type.BrowseName,
Description = type.Description,
TypeHierarchy = hierarchy.ConvertAll(e => new NodeModel
{
NodeId = request.Header.AsString(e.Item2.NodeId,
context.Session.MessageContext, _options),
DisplayName = e.Item2.DisplayName.AsString(),
BrowseName = request.Header.AsString(e.Item2.BrowseName,
context.Session.MessageContext, _options),
NodeClass = e.Item2.NodeClass.ToServiceType()
}),
NodeType = GetNodeType(hierarchy
.Select(r => (NodeId)r.Item2.NodeId)
.Append(typeId)
.ToList()),
Declarations = declarations
}
};
}, request.Header, ct).ConfigureAwait(false);
static NodeType GetNodeType(List<NodeId> hierarchy)
{
if (hierarchy.Count > 1)
{
if (hierarchy[1] == ObjectTypeIds.BaseEventType)
{
return NodeType.Event;
}
if (hierarchy[1] == ObjectTypeIds.BaseInterfaceType)
{
return NodeType.Interface;
}
if (hierarchy[1] == VariableTypeIds.PropertyType)
{
return NodeType.Property;
}
if (hierarchy[1] == VariableTypeIds.BaseDataVariableType)
{
return NodeType.DataVariable;
}
}
if (hierarchy.Count > 0)
{
if (hierarchy[0] == VariableTypeIds.BaseVariableType)
{
return NodeType.Variable;
}
if (hierarchy[0] == ObjectTypeIds.BaseObjectType)
{
return NodeType.Object;
}
}
return NodeType.Unknown;
}
}