Files
Mapo-IOB-WIN/IOB-OPC-UA/Libraries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs
T
2021-03-25 18:25:25 +01:00

3319 lines
125 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2020 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;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Threading;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.Threading.Tasks;
#pragma warning disable 0618
namespace Opc.Ua.Server
{
/// <summary>
/// The default node manager for the server.
/// </summary>
/// <remarks>
/// Every Server has one instance of this NodeManager.
/// It stores objects that implement ILocalNode and indexes them by NodeId.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
public partial class CoreNodeManager : INodeManager, IDisposable
{
#region Constructors
/// <summary>
/// Initializes the object with default values.
/// </summary>
public CoreNodeManager(
IServerInternal server,
ApplicationConfiguration configuration,
ushort dynamicNamespaceIndex)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
m_server = server;
m_nodes = new NodeTable(server.NamespaceUris, server.ServerUris, server.TypeTree);
m_monitoredItems = new Dictionary<uint,MonitoredItem>();
m_defaultMinimumSamplingInterval = 1000;
m_namespaceUris = new List<string>();
m_dynamicNamespaceIndex = dynamicNamespaceIndex;
// use namespace 1 if out of range.
if (m_dynamicNamespaceIndex == 0 || m_dynamicNamespaceIndex >= server.NamespaceUris.Count)
{
m_dynamicNamespaceIndex = 1;
}
m_samplingGroupManager = new SamplingGroupManager(
server,
this,
(uint)configuration.ServerConfiguration.MaxNotificationQueueSize,
configuration.ServerConfiguration.AvailableSamplingRates);
}
#endregion
#region IDisposable Members
/// <summary>
/// Frees any unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// An overrideable version of the Dispose.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
List<INode> nodes = null;
lock(m_lock)
{
nodes = new List<INode>(m_nodes);
m_nodes.Clear();
m_monitoredItems.Clear();
}
foreach (INode node in nodes)
{
Utils.SilentDispose(node);
}
Utils.SilentDispose(m_samplingGroupManager);
}
}
#endregion
#region Public Properties
/// <summary>
/// Acquires the lock on the node manager.
/// </summary>
public object DataLock
{
get { return m_lock; }
}
#endregion
/// <summary>
/// Imports the nodes from a dictionary of NodeState objects.
/// </summary>
public void ImportNodes(ISystemContext context, IEnumerable<NodeState> predefinedNodes)
{
ImportNodes(context, predefinedNodes, false);
}
/// <summary>
/// Imports the nodes from a dictionary of NodeState objects.
/// </summary>
internal void ImportNodes(ISystemContext context, IEnumerable<NodeState> predefinedNodes, bool isInternal)
{
NodeTable nodesToExport = new NodeTable(Server.NamespaceUris, Server.ServerUris, Server.TypeTree);
foreach (NodeState node in predefinedNodes)
{
node.Export(context, nodesToExport);
}
lock (Server.CoreNodeManager.DataLock)
{
foreach (ILocalNode nodeToExport in nodesToExport)
{
Server.CoreNodeManager.AttachNode(nodeToExport, isInternal);
}
}
}
#region INodeManager Members
/// <summary cref="INodeManager.NamespaceUris" />
public IEnumerable<string> NamespaceUris
{
get
{
return m_namespaceUris;
}
}
/// <summary cref="INodeManager.CreateAddressSpace" />
/// <remarks>
/// Populates the NodeManager by loading the standard nodes from an XML file stored as an embedded resource.
/// </remarks>
public void CreateAddressSpace(IDictionary<NodeId,IList<IReference>> externalReferences)
{
// TBD
}
/// <summary cref="INodeManager.DeleteAddressSpace" />
/// <remarks>
/// Disposes all of the nodes.
/// </remarks>
public void DeleteAddressSpace()
{
List<IDisposable> nodesToDispose = new List<IDisposable>();
lock(m_lock)
{
// collect nodes to dispose.
foreach (INode node in m_nodes)
{
IDisposable disposable = node as IDisposable;
if (disposable != null)
{
nodesToDispose.Add(disposable);
}
}
m_nodes.Clear();
}
// dispose of the nodes.
foreach (IDisposable disposable in nodesToDispose)
{
try
{
disposable.Dispose();
}
catch (Exception e)
{
Utils.Trace(e, "Unexpected error disposing a Node object.");
}
}
}
/// <see cref="INodeManager.GetManagerHandle" />
public object GetManagerHandle(NodeId nodeId)
{
lock(m_lock)
{
if (NodeId.IsNull(nodeId))
{
return null;
}
return GetLocalNode(nodeId);
}
}
/// <see cref="INodeManager.TranslateBrowsePath(OperationContext,object,RelativePathElement,IList{ExpandedNodeId},IList{NodeId})" />
public void TranslateBrowsePath(
OperationContext context,
object sourceHandle,
RelativePathElement relativePath,
IList<ExpandedNodeId> targetIds,
IList<NodeId> unresolvedTargetIds)
{
if (sourceHandle == null) throw new ArgumentNullException(nameof(sourceHandle));
if (relativePath == null) throw new ArgumentNullException(nameof(relativePath));
if (targetIds == null) throw new ArgumentNullException(nameof(targetIds));
if (unresolvedTargetIds == null) throw new ArgumentNullException(nameof(unresolvedTargetIds));
// check for valid handle.
ILocalNode source = sourceHandle as ILocalNode;
if (source == null)
{
return;
}
lock(m_lock)
{
// find the references that meet the filter criteria.
IList<IReference> references = source.References.Find(
relativePath.ReferenceTypeId,
relativePath.IsInverse,
relativePath.IncludeSubtypes,
m_server.TypeTree);
// nothing more to do.
if (references == null || references.Count == 0)
{
return;
}
// find targets with matching browse names.
foreach (IReference reference in references)
{
INode target = GetLocalNode(reference.TargetId);
// target is not known to the node manager.
if (target == null)
{
// ignore unknown external references.
if (reference.TargetId.IsAbsolute)
{
continue;
}
// caller must check the browse name.
unresolvedTargetIds.Add((NodeId)reference.TargetId);
continue;
}
// check browse name.
if (target.BrowseName == relativePath.TargetName)
{
targetIds.Add(reference.TargetId);
}
}
}
}
#region Browse
/// <see cref="INodeManager.Browse" />
public void Browse(
OperationContext context,
ref ContinuationPoint continuationPoint,
IList<ReferenceDescription> references)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (continuationPoint == null) throw new ArgumentNullException(nameof(continuationPoint));
if (references == null) throw new ArgumentNullException(nameof(references));
// check for valid handle.
ILocalNode source = continuationPoint.NodeToBrowse as ILocalNode;
if (source == null)
{
throw new ServiceResultException(StatusCodes.BadNodeIdUnknown);
}
// check for view.
if (!ViewDescription.IsDefault(continuationPoint.View))
{
throw new ServiceResultException(StatusCodes.BadViewIdUnknown);
}
lock (m_lock)
{
// construct list of references.
uint maxResultsToReturn = continuationPoint.MaxResultsToReturn;
// get previous enumerator.
IEnumerator<IReference> enumerator = continuationPoint.Data as IEnumerator<IReference>;
// fetch a snapshot all references for node.
if (enumerator == null)
{
List<IReference> copy = new List<IReference>(source.References);
enumerator = copy.GetEnumerator();
enumerator.MoveNext();
}
do
{
IReference reference = enumerator.Current;
// silently ignore bad values.
if (reference == null || NodeId.IsNull(reference.ReferenceTypeId) || NodeId.IsNull(reference.TargetId))
{
continue;
}
// apply browse filters.
bool include = ApplyBrowseFilters(
reference,
continuationPoint.BrowseDirection,
continuationPoint.ReferenceTypeId,
continuationPoint.IncludeSubtypes);
if (include)
{
ReferenceDescription description = new ReferenceDescription();
description.NodeId = reference.TargetId;
description.SetReferenceType(continuationPoint.ResultMask, reference.ReferenceTypeId, !reference.IsInverse);
// only fetch the metadata if it is requested.
if (continuationPoint.TargetAttributesRequired)
{
// get the metadata for the node.
NodeMetadata metadata = GetNodeMetadata(context, GetManagerHandle(reference.TargetId), continuationPoint.ResultMask);
// update description with local node metadata.
if (metadata != null)
{
description.SetTargetAttributes(
continuationPoint.ResultMask,
metadata.NodeClass,
metadata.BrowseName,
metadata.DisplayName,
metadata.TypeDefinition);
// check node class mask.
if (!CheckNodeClassMask(continuationPoint.NodeClassMask, description.NodeClass))
{
continue;
}
}
// any target that is not remote must be owned by another node manager.
else if (!reference.TargetId.IsAbsolute)
{
description.Unfiltered = true;
}
}
// add reference to list.
references.Add(description);
// construct continuation point if max results reached.
if (maxResultsToReturn > 0 && references.Count >= maxResultsToReturn)
{
continuationPoint.Index = 0;
continuationPoint.Data = enumerator;
enumerator.MoveNext();
return;
}
}
}
while (enumerator.MoveNext());
// nothing more to browse if it exits from the loop normally.
continuationPoint.Dispose();
continuationPoint = null;
}
}
/// <summary>
/// Returns true is the target meets the filter criteria.
/// </summary>
private bool ApplyBrowseFilters(
IReference reference,
BrowseDirection browseDirection,
NodeId referenceTypeId,
bool includeSubtypes)
{
// check browse direction.
if (reference.IsInverse)
{
if (browseDirection == BrowseDirection.Forward)
{
return false;
}
}
else
{
if (browseDirection == BrowseDirection.Inverse)
{
return false;
}
}
// check reference type filter.
if (!NodeId.IsNull(referenceTypeId))
{
if (reference.ReferenceTypeId != referenceTypeId)
{
if (includeSubtypes)
{
if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId))
{
return true;
}
}
return false;
}
}
// include reference for now.
return true;
}
#endregion
/// <see cref="INodeManager.GetNodeMetadata" />
public NodeMetadata GetNodeMetadata(
OperationContext context,
object targetHandle,
BrowseResultMask resultMask)
{
if (context == null) throw new ArgumentNullException(nameof(context));
// find target.
ILocalNode target = targetHandle as ILocalNode;
if (target == null)
{
return null;
}
lock (m_lock)
{
// copy the default metadata.
NodeMetadata metadata = new NodeMetadata(target, target.NodeId);
// copy target attributes.
if ((resultMask & BrowseResultMask.NodeClass) != 0)
{
metadata.NodeClass = (NodeClass)target.NodeClass;
}
if ((resultMask & BrowseResultMask.BrowseName) != 0)
{
metadata.BrowseName = target.BrowseName;
}
if ((resultMask & BrowseResultMask.DisplayName) != 0)
{
metadata.DisplayName = target.DisplayName;
// check if the display name can be localized.
if (!String.IsNullOrEmpty(metadata.DisplayName.Key))
{
metadata.DisplayName = Server.ResourceManager.Translate(context.PreferredLocales, metadata.DisplayName);
}
}
metadata.WriteMask = target.WriteMask;
if (metadata.WriteMask != AttributeWriteMask.None)
{
DataValue value = new DataValue((uint)(int)target.UserWriteMask);
ServiceResult result = target.Read(context, Attributes.UserWriteMask, value);
if (ServiceResult.IsBad(result))
{
metadata.WriteMask = AttributeWriteMask.None;
}
else
{
metadata.WriteMask = (AttributeWriteMask)(int)((uint)(int)metadata.WriteMask & (uint)value.Value);
}
}
metadata.EventNotifier = EventNotifiers.None;
metadata.AccessLevel = AccessLevels.None;
metadata.Executable = false;
switch (target.NodeClass)
{
case NodeClass.Object:
{
metadata.EventNotifier = ((IObject)target).EventNotifier;
break;
}
case NodeClass.View:
{
metadata.EventNotifier = ((IView)target).EventNotifier;
break;
}
case NodeClass.Variable:
{
IVariable variable = (IVariable)target;
metadata.DataType = variable.DataType;
metadata.ValueRank = variable.ValueRank;
metadata.ArrayDimensions = variable.ArrayDimensions;
metadata.AccessLevel = variable.AccessLevel;
DataValue value = new DataValue(variable.UserAccessLevel);
ServiceResult result = variable.Read(context, Attributes.UserAccessLevel, value);
if (ServiceResult.IsBad(result))
{
metadata.AccessLevel = 0;
break;
}
metadata.AccessLevel = (byte)(metadata.AccessLevel & (byte)value.Value);
break;
}
case NodeClass.Method:
{
IMethod method = (IMethod)target;
metadata.Executable = method.Executable;
if (metadata.Executable)
{
DataValue value = new DataValue(method.UserExecutable);
ServiceResult result = method.Read(context, Attributes.UserExecutable, value);
if (ServiceResult.IsBad(result))
{
metadata.Executable = false;
break;
}
metadata.Executable = (bool)value.Value;
}
break;
}
}
// look up type definition.
if ((resultMask & BrowseResultMask.TypeDefinition) != 0)
{
if (target.NodeClass == NodeClass.Variable || target.NodeClass == NodeClass.Object)
{
metadata.TypeDefinition = target.TypeDefinitionId;
}
}
// Set AccessRestrictions and RolePermissions
Node node = (Node)target;
metadata.AccessRestrictions = (AccessRestrictionType)Enum.Parse(typeof(AccessRestrictionType), node.AccessRestrictions.ToString());
metadata.RolePermissions = node.RolePermissions;
metadata.UserRolePermissions = node.UserRolePermissions;
// check if NamespaceMetadata is defined for NamespaceUri
string namespaceUri = Server.NamespaceUris.GetString(target.NodeId.NamespaceIndex);
NamespaceMetadataState namespaceMetadataState = Server.NodeManager.ConfigurationNodeManager.GetNamespaceMetadataState(namespaceUri);
if (namespaceMetadataState != null)
{
metadata.DefaultAccessRestrictions = (AccessRestrictionType)Enum.ToObject(typeof(AccessRestrictionType),
namespaceMetadataState.DefaultAccessRestrictions.Value);
metadata.DefaultRolePermissions = namespaceMetadataState.DefaultRolePermissions.Value;
metadata.DefaultUserRolePermissions = namespaceMetadataState.DefaultUserRolePermissions.Value;
}
#if LEGACY_NODEMANAGER
// check if a source is defined for the node.
SourceHandle handle = target.Handle as SourceHandle;
if (handle != null)
{
// check if the metadata needs to be updated by the source.
IReadMetadataSource source = handle.Source as IReadMetadataSource;
if (source != null)
{
source.ReadMetadata(
context,
handle.Handle,
resultMask,
metadata);
}
}
#endif
// return metadata.
return metadata;
}
}
/// <summary cref="INodeManager.AddReferences" />
/// <remarks>
/// This method must not be called without first acquiring
/// </remarks>
public void AddReferences(IDictionary<NodeId,IList<IReference>> references)
{
if (references == null) throw new ArgumentNullException(nameof(references));
lock (m_lock)
{
IEnumerator<KeyValuePair<NodeId,IList<IReference>>> enumerator = references.GetEnumerator();
while (enumerator.MoveNext())
{
ILocalNode actualNode = GetLocalNode(enumerator.Current.Key) as ILocalNode;
if (actualNode != null)
{
foreach (IReference reference in enumerator.Current.Value)
{
AddReference(actualNode, reference.ReferenceTypeId, reference.IsInverse, reference.TargetId);
}
}
}
}
}
/// <see cref="INodeManager.Read" />
public void Read(
OperationContext context,
double maxAge,
IList<ReadValueId> nodesToRead,
IList<DataValue> values,
IList<ServiceResult> errors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (nodesToRead == null) throw new ArgumentNullException(nameof(nodesToRead));
if (values == null) throw new ArgumentNullException(nameof(values));
if (errors == null) throw new ArgumentNullException(nameof(errors));
lock (m_lock)
{
for (int ii = 0; ii < nodesToRead.Count; ii++)
{
ReadValueId nodeToRead = nodesToRead[ii];
// skip items that have already been processed.
if (nodeToRead.Processed)
{
continue;
}
// look up the node.
ILocalNode node = GetLocalNode(nodeToRead.NodeId) as ILocalNode;
if (node == null)
{
continue;
}
DataValue value = values[ii] = new DataValue();
value.Value = null;
value.ServerTimestamp = DateTime.UtcNow;
value.SourceTimestamp = DateTime.MinValue;
value.StatusCode = StatusCodes.BadAttributeIdInvalid;
// owned by this node manager.
nodeToRead.Processed = true;
// read the default value (also verifies that the attribute id is valid for the node).
ServiceResult error = node.Read(context, nodeToRead.AttributeId, value);
if (ServiceResult.IsBad(error))
{
errors[ii] = error;
continue;
}
// always use default value for base attributes.
bool useDefault = false;
switch (nodeToRead.AttributeId)
{
case Attributes.NodeId:
case Attributes.NodeClass:
case Attributes.BrowseName:
{
useDefault = true;
break;
}
}
if (useDefault)
{
errors[ii] = error;
continue;
}
// apply index range to value attributes.
if (nodeToRead.AttributeId == Attributes.Value)
{
object defaultValue = value.Value;
error = nodeToRead.ParsedIndexRange.ApplyRange(ref defaultValue);
if (ServiceResult.IsBad(error))
{
value.Value = null;
errors[ii] = error;
continue;
}
// apply data encoding.
if (!QualifiedName.IsNull(nodeToRead.DataEncoding))
{
error = EncodeableObject.ApplyDataEncoding(Server.MessageContext, nodeToRead.DataEncoding, ref defaultValue);
if (ServiceResult.IsBad(error))
{
value.Value = null;
errors[ii] = error;
continue;
}
}
value.Value = defaultValue;
// don't replace timestamp if it was set in the NodeSource
if (value.SourceTimestamp == DateTime.MinValue)
{
value.SourceTimestamp = DateTime.UtcNow;
}
}
}
}
}
/// <see cref="INodeManager.HistoryRead" />
public void HistoryRead(
OperationContext context,
HistoryReadDetails details,
TimestampsToReturn timestampsToReturn,
bool releaseContinuationPoints,
IList<HistoryReadValueId> nodesToRead,
IList<HistoryReadResult> results,
IList<ServiceResult> errors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (details == null) throw new ArgumentNullException(nameof(details));
if (nodesToRead == null) throw new ArgumentNullException(nameof(nodesToRead));
if (results == null) throw new ArgumentNullException(nameof(results));
if (errors == null) throw new ArgumentNullException(nameof(errors));
ReadRawModifiedDetails readRawModifiedDetails = details as ReadRawModifiedDetails;
ReadAtTimeDetails readAtTimeDetails = details as ReadAtTimeDetails;
ReadProcessedDetails readProcessedDetails = details as ReadProcessedDetails;
ReadEventDetails readEventDetails = details as ReadEventDetails;
lock (m_lock)
{
for (int ii = 0; ii < nodesToRead.Count; ii++)
{
HistoryReadValueId nodeToRead = nodesToRead[ii];
// skip items that have already been processed.
if (nodeToRead.Processed)
{
continue;
}
// look up the node.
ILocalNode node = GetLocalNode(nodeToRead.NodeId) as ILocalNode;
if (node == null)
{
continue;
}
// owned by this node manager.
nodeToRead.Processed = true;
errors[ii] = StatusCodes.BadNotReadable;
}
}
}
/// <see cref="INodeManager.Write" />
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public void Write(
OperationContext context,
IList<WriteValue> nodesToWrite,
IList<ServiceResult> errors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (nodesToWrite == null) throw new ArgumentNullException(nameof(nodesToWrite));
if (errors == null) throw new ArgumentNullException(nameof(errors));
lock (m_lock)
{
for (int ii = 0; ii < nodesToWrite.Count; ii++)
{
WriteValue nodeToWrite = nodesToWrite[ii];
// skip items that have already been processed.
if (nodeToWrite.Processed)
{
continue;
}
// look up the node.
ILocalNode node = GetLocalNode(nodeToWrite.NodeId) as ILocalNode;
if (node == null)
{
continue;
}
// owned by this node manager.
nodeToWrite.Processed = true;
if (!node.SupportsAttribute(nodeToWrite.AttributeId))
{
errors[ii] = StatusCodes.BadAttributeIdInvalid;
continue;
}
// fetch the node metadata.
NodeMetadata metadata = GetNodeMetadata(context, node, BrowseResultMask.All);
// check access.
bool writeable = true;
ServiceResult error = null;
// determine access rights.
switch (nodeToWrite.AttributeId)
{
case Attributes.NodeId:
case Attributes.NodeClass:
case Attributes.AccessLevel:
case Attributes.UserAccessLevel:
case Attributes.Executable:
case Attributes.UserExecutable:
case Attributes.EventNotifier:
{
writeable = false;
break;
}
case Attributes.Value:
{
writeable = ((metadata.AccessLevel & AccessLevels.CurrentWrite)!= 0);
break;
}
default:
{
writeable = (metadata.WriteMask & Attributes.GetMask(nodeToWrite.AttributeId)) != 0;
break;
}
}
// error if not writeable.
if (!writeable)
{
errors[ii] = StatusCodes.BadNotWritable;
continue;
}
// determine expected datatype and value rank.
NodeId expectedDatatypeId = metadata.DataType;
int expectedValueRank = metadata.ValueRank;
if (nodeToWrite.AttributeId != Attributes.Value)
{
expectedDatatypeId = Attributes.GetDataTypeId(nodeToWrite.AttributeId);
DataValue value = nodeToWrite.Value;
if (value.StatusCode != StatusCodes.Good || value.ServerTimestamp != DateTime.MinValue || value.SourceTimestamp != DateTime.MinValue)
{
errors[ii] = StatusCodes.BadWriteNotSupported;
continue;
}
expectedValueRank = ValueRanks.Scalar;
if (nodeToWrite.AttributeId == Attributes.ArrayDimensions)
{
expectedValueRank = ValueRanks.OneDimension;
}
}
// check whether value being written is an instance of the expected data type.
object valueToWrite = nodeToWrite.Value.Value;
TypeInfo typeInfo = TypeInfo.IsInstanceOfDataType(
valueToWrite,
expectedDatatypeId,
expectedValueRank,
m_server.NamespaceUris,
m_server.TypeTree);
if (typeInfo == null)
{
errors[ii] = StatusCodes.BadTypeMismatch;
continue;
}
// check index range.
if (nodeToWrite.ParsedIndexRange.Count > 0)
{
// check index range for scalars.
if (typeInfo.ValueRank < 0)
{
errors[ii] = StatusCodes.BadIndexRangeInvalid;
continue;
}
// check index range for arrays.
else
{
Array array = (Array)valueToWrite;
if (nodeToWrite.ParsedIndexRange.Count != array.Length)
{
errors[ii] = StatusCodes.BadIndexRangeInvalid;
continue;
}
}
}
// write the default value.
error = node.Write(nodeToWrite.AttributeId, nodeToWrite.Value);
if (ServiceResult.IsBad(error))
{
errors[ii] = error;
continue;
}
}
}
}
/// <see cref="INodeManager.HistoryUpdate" />
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public void HistoryUpdate(
OperationContext context,
Type detailsType,
IList<HistoryUpdateDetails> nodesToUpdate,
IList<HistoryUpdateResult> results,
IList<ServiceResult> errors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (nodesToUpdate == null) throw new ArgumentNullException(nameof(nodesToUpdate));
if (results == null) throw new ArgumentNullException(nameof(results));
if (errors == null) throw new ArgumentNullException(nameof(errors));
lock (m_lock)
{
for (int ii = 0; ii < nodesToUpdate.Count; ii++)
{
HistoryUpdateDetails nodeToUpdate = nodesToUpdate[ii];
// skip items that have already been processed.
if (nodeToUpdate.Processed)
{
continue;
}
// look up the node.
ILocalNode node = GetLocalNode(nodeToUpdate.NodeId) as ILocalNode;
if (node == null)
{
continue;
}
// owned by this node manager.
nodeToUpdate.Processed = true;
errors[ii] = StatusCodes.BadNotWritable;
}
}
}
/// <see cref="INodeManager.Call" />
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
public void Call(
OperationContext context,
IList<CallMethodRequest> methodsToCall,
IList<CallMethodResult> results,
IList<ServiceResult> errors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (methodsToCall == null) throw new ArgumentNullException(nameof(methodsToCall));
if (results == null) throw new ArgumentNullException(nameof(results));
if (errors == null) throw new ArgumentNullException(nameof(errors));
lock (m_lock)
{
for (int ii = 0; ii < methodsToCall.Count; ii++)
{
CallMethodRequest methodToCall = methodsToCall[ii];
// skip items that have already been processed.
if (methodToCall.Processed)
{
continue;
}
// look up the node.
ILocalNode node = GetLocalNode(methodToCall.ObjectId) as ILocalNode;
if (node == null)
{
continue;
}
methodToCall.Processed = true;
// look up the method.
ILocalNode method = GetLocalNode(methodToCall.MethodId) as ILocalNode;
if (method == null)
{
errors[ii] = ServiceResult.Create(StatusCodes.BadMethodInvalid, "Method is not in the address space.");
continue;
}
// check that the method is defined for the object.
if (!node.References.Exists(ReferenceTypeIds.HasComponent, false, methodToCall.MethodId, true, m_server.TypeTree))
{
errors[ii] = ServiceResult.Create(StatusCodes.BadMethodInvalid, "Method is not a component of the Object.");
continue;
}
errors[ii] = StatusCodes.BadNotImplemented;
}
}
}
/// <see cref="INodeManager.SubscribeToEvents" />
public ServiceResult SubscribeToEvents(
OperationContext context,
object sourceId,
uint subscriptionId,
IEventMonitoredItem monitoredItem,
bool unsubscribe)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (sourceId == null) throw new ArgumentNullException(nameof(sourceId));
if (monitoredItem == null) throw new ArgumentNullException(nameof(monitoredItem));
lock (m_lock)
{
// validate the node.
NodeMetadata metadata = GetNodeMetadata(context, sourceId, BrowseResultMask.NodeClass);
if (metadata == null)
{
return StatusCodes.BadNodeIdUnknown;
}
// validate the node class.
if (((metadata.NodeClass & NodeClass.Object | NodeClass.View)) == 0)
{
return StatusCodes.BadNotSupported;
}
// check that it supports events.
if ((metadata.EventNotifier & EventNotifiers.SubscribeToEvents) == 0)
{
return StatusCodes.BadNotSupported;
}
return ServiceResult.Good;
}
}
/// <see cref="INodeManager.SubscribeToAllEvents" />
public ServiceResult SubscribeToAllEvents(
OperationContext context,
uint subscriptionId,
IEventMonitoredItem monitoredItem,
bool unsubscribe)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (monitoredItem == null) throw new ArgumentNullException(nameof(monitoredItem));
return ServiceResult.Good;
}
/// <see cref="INodeManager.ConditionRefresh" />
public ServiceResult ConditionRefresh(
OperationContext context,
IList<IEventMonitoredItem> monitoredItems)
{
if (context == null) throw new ArgumentNullException(nameof(context));
return ServiceResult.Good;
}
/// <summary>
/// Creates a set of monitored items.
/// </summary>
public void CreateMonitoredItems(
OperationContext context,
uint subscriptionId,
double publishingInterval,
TimestampsToReturn timestampsToReturn,
IList<MonitoredItemCreateRequest> itemsToCreate,
IList<ServiceResult> errors,
IList<MonitoringFilterResult> filterErrors,
IList<IMonitoredItem> monitoredItems,
ref long globalIdCounter)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (itemsToCreate == null) throw new ArgumentNullException(nameof(itemsToCreate));
if (errors == null) throw new ArgumentNullException(nameof(errors));
if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems));
lock (m_lock)
{
for (int ii = 0; ii < errors.Count; ii++)
{
MonitoredItemCreateRequest itemToCreate = itemsToCreate[ii];
// skip items that have already been processed.
if (itemToCreate.Processed)
{
continue;
}
// look up the node.
ILocalNode node = this.GetLocalNode(itemToCreate.ItemToMonitor.NodeId) as ILocalNode;
if (node == null)
{
continue;
}
// owned by this node manager.
itemToCreate.Processed = true;
if (!node.SupportsAttribute(itemToCreate.ItemToMonitor.AttributeId))
{
errors[ii] = StatusCodes.BadAttributeIdInvalid;
continue;
}
// fetch the metadata for the node.
NodeMetadata metadata = GetNodeMetadata(context, node, BrowseResultMask.All);
if (itemToCreate.ItemToMonitor.AttributeId == Attributes.Value)
{
if ((metadata.AccessLevel & AccessLevels.CurrentRead) == 0)
{
errors[ii] = StatusCodes.BadNotReadable;
continue;
}
}
// check value rank against index range.
if (itemToCreate.ItemToMonitor.ParsedIndexRange != NumericRange.Empty)
{
int valueRank = metadata.ValueRank;
if (itemToCreate.ItemToMonitor.AttributeId != Attributes.Value)
{
valueRank = Attributes.GetValueRank(itemToCreate.ItemToMonitor.AttributeId);
}
if (valueRank == ValueRanks.Scalar)
{
errors[ii] = StatusCodes.BadIndexRangeInvalid;
continue;
}
}
bool rangeRequired = false;
// validate the filter against the node/attribute being monitored.
errors[ii] = ValidateFilter(
metadata,
itemToCreate.ItemToMonitor.AttributeId,
itemToCreate.RequestedParameters.Filter,
out rangeRequired);
if (ServiceResult.IsBad(errors[ii]))
{
continue;
}
// lookup EU range if required.
Range range = null;
if (rangeRequired)
{
errors[ii] = ReadEURange(context, node, out range);
if (ServiceResult.IsBad(errors[ii]))
{
continue;
}
}
// create a globally unique identifier.
uint monitoredItemId = Utils.IncrementIdentifier(ref globalIdCounter);
// limit the sampling rate for non-value attributes.
double minimumSamplingInterval = m_defaultMinimumSamplingInterval;
if (itemToCreate.ItemToMonitor.AttributeId == Attributes.Value)
{
// use the MinimumSamplingInterval attribute to limit the sampling rate for value attributes.
IVariable variableNode = node as IVariable;
if (variableNode != null)
{
minimumSamplingInterval = variableNode.MinimumSamplingInterval;
// use the default if the node does not specify one.
if (minimumSamplingInterval < 0)
{
minimumSamplingInterval = m_defaultMinimumSamplingInterval;
}
}
}
// create monitored item.
MonitoredItem monitoredItem = m_samplingGroupManager.CreateMonitoredItem(
context,
subscriptionId,
publishingInterval,
timestampsToReturn,
monitoredItemId,
node,
itemToCreate,
range,
minimumSamplingInterval);
// save monitored item.
m_monitoredItems.Add(monitoredItem.Id, monitoredItem);
// update monitored item list.
monitoredItems[ii] = monitoredItem;
// read the initial value.
DataValue initialValue = new DataValue();
initialValue.ServerTimestamp = DateTime.UtcNow;
initialValue.StatusCode = StatusCodes.BadWaitingForInitialData;
ServiceResult error = node.Read(context, itemToCreate.ItemToMonitor.AttributeId, initialValue);
if (ServiceResult.IsBad(error))
{
initialValue.Value = null;
initialValue.StatusCode = error.StatusCode;
}
monitoredItem.QueueValue(initialValue, error);
// errors updating the monitoring groups will be reported in notifications.
errors[ii] = StatusCodes.Good;
}
}
// update all groups with any new items.
m_samplingGroupManager.ApplyChanges();
}
/// <summary>
/// Modifies a set of monitored items.
/// </summary>
public void ModifyMonitoredItems(
OperationContext context,
TimestampsToReturn timestampsToReturn,
IList<IMonitoredItem> monitoredItems,
IList<MonitoredItemModifyRequest> itemsToModify,
IList<ServiceResult> errors,
IList<MonitoringFilterResult> filterErrors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems));
if (itemsToModify == null) throw new ArgumentNullException(nameof(itemsToModify));
if (errors == null) throw new ArgumentNullException(nameof(errors));
lock (m_lock)
{
for (int ii = 0; ii < errors.Count; ii++)
{
MonitoredItemModifyRequest itemToModify = itemsToModify[ii];
// skip items that have already been processed.
if (itemToModify.Processed || monitoredItems[ii] == null)
{
continue;
}
// check if the node manager created the item.
if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager))
{
continue;
}
// owned by this node manager.
itemToModify.Processed = true;
// validate monitored item.
MonitoredItem monitoredItem = null;
if (!m_monitoredItems.TryGetValue(monitoredItems[ii].Id, out monitoredItem))
{
errors[ii] = StatusCodes.BadMonitoredItemIdInvalid;
continue;
}
if (!Object.ReferenceEquals(monitoredItem, monitoredItems[ii]))
{
errors[ii] = StatusCodes.BadMonitoredItemIdInvalid;
continue;
}
// find the node being monitored.
ILocalNode node = monitoredItem.ManagerHandle as ILocalNode;
if (node == null)
{
errors[ii] = StatusCodes.BadNodeIdUnknown;
continue;
}
// fetch the metadata for the node.
NodeMetadata metadata = GetNodeMetadata(context, monitoredItem.ManagerHandle, BrowseResultMask.All);
bool rangeRequired = false;
// validate the filter against the node/attribute being monitored.
errors[ii] = ValidateFilter(
metadata,
monitoredItem.AttributeId,
itemToModify.RequestedParameters.Filter,
out rangeRequired);
if (ServiceResult.IsBad(errors[ii]))
{
continue;
}
// lookup EU range if required.
Range range = null;
if (rangeRequired)
{
// look up EU range.
errors[ii] = ReadEURange(context, node, out range);
if (ServiceResult.IsBad(errors[ii]))
{
continue;
}
}
// update sampling.
errors[ii] = m_samplingGroupManager.ModifyMonitoredItem(
context,
timestampsToReturn,
monitoredItem,
itemToModify,
range);
// state of item did not change if an error returned here.
if (ServiceResult.IsBad(errors[ii]))
{
continue;
}
// item has been modified successfully.
// errors updating the sampling groups will be reported in notifications.
errors[ii] = StatusCodes.Good;
}
}
// update all sampling groups.
m_samplingGroupManager.ApplyChanges();
}
/// <summary>
/// Deletes a set of monitored items.
/// </summary>
public void DeleteMonitoredItems(
OperationContext context,
IList<IMonitoredItem> monitoredItems,
IList<bool> processedItems,
IList<ServiceResult> errors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems));
if (errors == null) throw new ArgumentNullException(nameof(errors));
lock (m_lock)
{
for (int ii = 0; ii < errors.Count; ii++)
{
// skip items that have already been processed.
if (processedItems[ii] || monitoredItems[ii] == null)
{
continue;
}
// check if the node manager created the item.
if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager))
{
continue;
}
// owned by this node manager.
processedItems[ii] = true;
// validate monitored item.
MonitoredItem monitoredItem = null;
if (!m_monitoredItems.TryGetValue(monitoredItems[ii].Id, out monitoredItem))
{
errors[ii] = StatusCodes.BadMonitoredItemIdInvalid;
continue;
}
if (!Object.ReferenceEquals(monitoredItem, monitoredItems[ii]))
{
errors[ii] = StatusCodes.BadMonitoredItemIdInvalid;
continue;
}
// remove item.
m_samplingGroupManager.StopMonitoring(monitoredItem);
// remove association with the group.
m_monitoredItems.Remove(monitoredItem.Id);
// delete successful.
errors[ii] = StatusCodes.Good;
}
}
// remove all items from groups.
m_samplingGroupManager.ApplyChanges();
}
/// <summary>
/// Changes the monitoring mode for a set of monitored items.
/// </summary>
public void SetMonitoringMode(
OperationContext context,
MonitoringMode monitoringMode,
IList<IMonitoredItem> monitoredItems,
IList<bool> processedItems,
IList<ServiceResult> errors)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (monitoredItems == null) throw new ArgumentNullException(nameof(monitoredItems));
if (errors == null) throw new ArgumentNullException(nameof(errors));
lock (m_lock)
{
for (int ii = 0; ii < errors.Count; ii++)
{
// skip items that have already been processed.
if (processedItems[ii] || monitoredItems[ii] == null)
{
continue;
}
// check if the node manager created the item.
if (!Object.ReferenceEquals(this, monitoredItems[ii].NodeManager))
{
continue;
}
// owned by this node manager.
processedItems[ii] = true;
// validate monitored item.
MonitoredItem monitoredItem = null;
if (!m_monitoredItems.TryGetValue(monitoredItems[ii].Id, out monitoredItem))
{
errors[ii] = StatusCodes.BadMonitoredItemIdInvalid;
continue;
}
if (!Object.ReferenceEquals(monitoredItem, monitoredItems[ii]))
{
errors[ii] = StatusCodes.BadMonitoredItemIdInvalid;
continue;
}
// update monitoring mode.
MonitoringMode previousMode = monitoredItem.SetMonitoringMode(monitoringMode);
// need to provide an immediate update after enabling.
if (previousMode == MonitoringMode.Disabled && monitoringMode != MonitoringMode.Disabled)
{
DataValue initialValue = new DataValue();
initialValue.ServerTimestamp = DateTime.UtcNow;
initialValue.StatusCode = StatusCodes.BadWaitingForInitialData;
// read the initial value.
Node node = monitoredItem.ManagerHandle as Node;
if (node != null)
{
ServiceResult error = node.Read(context, monitoredItem.AttributeId, initialValue);
if (ServiceResult.IsBad(error))
{
initialValue.Value = null;
initialValue.StatusCode = error.StatusCode;
}
}
monitoredItem.QueueValue(initialValue, null);
}
// modify the item attributes.
m_samplingGroupManager.ModifyMonitoring(context, monitoredItem);
// item has been modified successfully.
// errors updating the sampling groups will be reported in notifications.
errors[ii] = StatusCodes.Good;
}
}
// update all sampling groups.
m_samplingGroupManager.ApplyChanges();
}
#endregion
#region Static Members
/// <summary>
/// Returns true if the node class matches the node class mask.
/// </summary>
public static bool CheckNodeClassMask(uint nodeClassMask, NodeClass nodeClass)
{
if (nodeClassMask != 0)
{
return ((uint)nodeClass & nodeClassMask) != 0;
}
return true;
}
#endregion
#region Protected Members
/// <summary>
/// The server that the node manager belongs to.
/// </summary>
protected IServerInternal Server
{
get { return m_server; }
}
#endregion
#region Browsing/Searching
/// <summary>
/// Returns an index for the NamespaceURI (Adds it to the server namespace table if it does not already exist).
/// </summary>
/// <remarks>
/// Returns the server's default index (1) if the namespaceUri is empty or null.
/// </remarks>
public ushort GetNamespaceIndex(string namespaceUri)
{
int namespaceIndex = 1;
if (!String.IsNullOrEmpty(namespaceUri))
{
namespaceIndex = m_server.NamespaceUris.GetIndex(namespaceUri);
if (namespaceIndex == -1)
{
namespaceIndex = m_server.NamespaceUris.Append(namespaceUri);
}
}
return (ushort)namespaceIndex;
}
/// <summary>
/// Returns all targets of the specified reference.
/// </summary>
public NodeIdCollection FindLocalNodes(NodeId sourceId, NodeId referenceTypeId, bool isInverse)
{
if (sourceId == null) throw new ArgumentNullException(nameof(sourceId));
if (referenceTypeId == null) throw new ArgumentNullException(nameof(referenceTypeId));
lock (m_lock)
{
ILocalNode source = GetManagerHandle(sourceId) as ILocalNode;
if (source == null)
{
return null;
}
NodeIdCollection targets = new NodeIdCollection();
foreach (IReference reference in source.References)
{
if (reference.IsInverse != isInverse || !m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId))
{
continue;
}
ExpandedNodeId targetId = reference.TargetId;
if (targetId.IsAbsolute)
{
continue;
}
targets.Add((NodeId)targetId);
}
return targets;
}
}
/// <summary>
/// Returns the id the first node with the specified browse name if it exists. null otherwise
/// </summary>
public NodeId FindTargetId(NodeId sourceId, NodeId referenceTypeId, bool isInverse, QualifiedName browseName)
{
if (sourceId == null) throw new ArgumentNullException(nameof(sourceId));
if (referenceTypeId == null) throw new ArgumentNullException(nameof(referenceTypeId));
lock (m_lock)
{
ILocalNode source = GetManagerHandle(sourceId) as ILocalNode;
if (source == null)
{
return null;
}
foreach (ReferenceNode reference in source.References)
{
if (reference.IsInverse != isInverse || !m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, referenceTypeId))
{
continue;
}
ExpandedNodeId targetId = reference.TargetId;
if (targetId.IsAbsolute)
{
continue;
}
ILocalNode target = GetManagerHandle((NodeId)targetId) as ILocalNode;
if (target == null)
{
continue;
}
if (QualifiedName.IsNull(browseName) || target.BrowseName == browseName)
{
return (NodeId)targetId;
}
}
return null;
}
}
/// <summary>
/// Returns the first target that matches the browse path.
/// </summary>
public NodeId Find(NodeId sourceId, string browsePath)
{
IList<NodeId> targets = TranslateBrowsePath(sourceId, browsePath);
if (targets.Count > 0)
{
return targets[0];
}
return null;
}
/// <summary>
/// Returns a list of targets the match the browse path.
/// </summary>
public IList<NodeId> TranslateBrowsePath(
OperationContext context,
NodeId sourceId,
string browsePath)
{
return TranslateBrowsePath(context, sourceId, RelativePath.Parse(browsePath, m_server.TypeTree));
}
/// <summary>
/// Returns a list of targets the match the browse path.
/// </summary>
public IList<NodeId> TranslateBrowsePath(
NodeId sourceId,
string browsePath)
{
return TranslateBrowsePath(null, sourceId, RelativePath.Parse(browsePath, m_server.TypeTree));
}
/// <summary>
/// Returns a list of targets the match the browse path.
/// </summary>
public IList<NodeId> TranslateBrowsePath(
NodeId sourceId,
RelativePath relativePath)
{
return TranslateBrowsePath(null, sourceId, relativePath);
}
/// <summary>
/// Returns a list of targets the match the browse path.
/// </summary>
public IList<NodeId> TranslateBrowsePath(
OperationContext context,
NodeId sourceId,
RelativePath relativePath)
{
List<NodeId> targets = new List<NodeId>();
if (relativePath == null || relativePath.Elements.Count == 0)
{
targets.Add(sourceId);
return targets;
}
// look up source in this node manager.
ILocalNode source = null;
lock (m_lock)
{
source = GetLocalNode(sourceId) as ILocalNode;
if (source == null)
{
return targets;
}
}
// return the set of matching targets.
return targets;
}
#endregion
#region Registering Data/Event Sources
/// <summary>
/// Registers a source for a node.
/// </summary>
/// <remarks>
/// The source could be one or more of IDataSource, IEventSource, ICallable, IHistorian or IViewManager
/// </remarks>
public void RegisterSource(NodeId nodeId, object source, object handle, bool isEventSource)
{
if (nodeId == null) throw new ArgumentNullException(nameof(nodeId));
if (source == null) throw new ArgumentNullException(nameof(source));
}
/// <summary>
/// Called when the source is no longer used.
/// </summary>
/// <remarks>
/// When a source disappears it must either delete all of its nodes from the address space
/// or unregister itself their source by calling RegisterSource with source == null.
/// After doing that the source must call this method.
/// </remarks>
public void UnregisterSource(object source)
{
}
#endregion
#region Adding/Removing Nodes
#region Apply Modelling Rules
/// <summary>
/// Applys the modelling rules to any existing instance.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public void ApplyModellingRules(
ILocalNode instance,
ILocalNode typeDefinition,
ILocalNode templateDeclaration,
ushort namespaceIndex)
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
if (typeDefinition == null) throw new ArgumentNullException(nameof(typeDefinition));
// check existing type definition.
UpdateTypeDefinition(instance, typeDefinition.NodeId);
// create list of declarations for the type definition (recursively collects definitions from supertypes).
List<DeclarationNode> declarations = new List<DeclarationNode>();
BuildDeclarationList(typeDefinition, declarations);
// add instance declaration if provided.
if (templateDeclaration != null)
{
DeclarationNode declaration = new DeclarationNode();
declaration.Node = templateDeclaration;
declaration.BrowsePath = String.Empty;
declarations.Add(declaration);
BuildDeclarationList(templateDeclaration, declarations);
}
// build list of instances to create.
List<ILocalNode> typeDefinitions = new List<ILocalNode>();
SortedDictionary<string,ILocalNode> instanceDeclarations = new SortedDictionary<string,ILocalNode>();
SortedDictionary<NodeId,ILocalNode> possibleTargets = new SortedDictionary<NodeId,ILocalNode>();
// create instances from declarations.
// subtypes appear in list last so traversing the list backwards find the overridden nodes first.
for (int ii = declarations.Count-1; ii >= 0; ii--)
{
DeclarationNode declaration = declarations[ii];
// update type definition list.
if (String.IsNullOrEmpty(declaration.BrowsePath))
{
typeDefinitions.Add(declaration.Node);
continue;
}
// skip declaration if instance already exists.
// (i.e. the declaration was overridden).
if (instanceDeclarations.ContainsKey(declaration.BrowsePath))
{
continue;
}
// update instance declaration list.
instanceDeclarations[declaration.BrowsePath] = declaration.Node;
// save the node as a possible target of references.
possibleTargets[declaration.Node.NodeId] = declaration.Node;
}
// build list of instances that already exist.
SortedDictionary<string,ILocalNode> existingInstances = new SortedDictionary<string,ILocalNode>();
BuildInstanceList(instance, String.Empty, existingInstances);
// maps the instance declaration onto an instance node.
Dictionary<NodeId,ILocalNode> instancesToCreate = new Dictionary<NodeId,ILocalNode>();
// apply modelling rules to instance declarations.
foreach (KeyValuePair<string,ILocalNode> current in instanceDeclarations)
{
string browsePath = current.Key;
ILocalNode instanceDeclaration = current.Value;
// check if the same instance has multiple browse paths to it.
ILocalNode newInstance = null;
if (instancesToCreate.TryGetValue(instanceDeclaration.NodeId, out newInstance))
{
continue;
}
// check for an existing instance.
if (existingInstances.TryGetValue(browsePath, out newInstance))
{
continue;
}
// apply modelling rule to determine whether to create a new instance.
NodeId modellingRule = instanceDeclaration.ModellingRule;
// always create a new instance if one does not already exist.
if (modellingRule == Objects.ModellingRule_Mandatory)
{
if (newInstance == null)
{
newInstance = instanceDeclaration.CreateCopy(CreateUniqueNodeId());
AddNode(newInstance);
}
}
// ignore optional instances unless one has been specified in the existing tree.
else if (modellingRule == Objects.ModellingRule_Optional)
{
if (newInstance == null)
{
continue;
}
}
// ignore any unknown modelling rules.
else
{
continue;
}
// save the mapping between the instance declaration and the new instance.
instancesToCreate[instanceDeclaration.NodeId] = newInstance;
}
// add references from type definitions to top level.
foreach (ILocalNode type in typeDefinitions)
{
foreach (IReference reference in type.References)
{
// ignore external references from type.
if (reference.TargetId.IsAbsolute)
{
continue;
}
// ignore subtype references.
if (m_nodes.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype))
{
continue;
}
// ignore targets that are not in the instance tree.
ILocalNode target = null;
if (!instancesToCreate.TryGetValue((NodeId)reference.TargetId, out target))
{
continue;
}
// add forward and backward reference.
AddReference(instance, reference.ReferenceTypeId, reference.IsInverse, target, true);
}
}
// add references between instance declarations.
foreach (ILocalNode instanceDeclaration in instanceDeclarations.Values)
{
// find the source for the references.
ILocalNode source = null;
if (!instancesToCreate.TryGetValue(instanceDeclaration.NodeId, out source))
{
continue;
}
// check if the source is a shared node.
bool sharedNode = Object.ReferenceEquals(instanceDeclaration, source);
foreach (IReference reference in instanceDeclaration.References)
{
// add external reference.
if (reference.TargetId.IsAbsolute)
{
if (!sharedNode)
{
AddReference(source, reference.ReferenceTypeId, reference.IsInverse, reference.TargetId);
}
continue;
}
// check for modelling rule.
if (reference.ReferenceTypeId == ReferenceTypeIds.HasModellingRule)
{
if (!source.References.Exists(ReferenceTypeIds.HasModellingRule, false, reference.TargetId, false, null))
{
AddReference(source, reference.ReferenceTypeId, false, reference.TargetId);
}
continue;
}
// check for type definition.
if (reference.ReferenceTypeId == ReferenceTypeIds.HasTypeDefinition)
{
if (!sharedNode)
{
UpdateTypeDefinition(source, instanceDeclaration.TypeDefinitionId);
}
continue;
}
// add targets that are not in the instance tree.
ILocalNode target = null;
if (!instancesToCreate.TryGetValue((NodeId)reference.TargetId, out target))
{
// don't update shared nodes because the reference should already exist.
if (sharedNode)
{
continue;
}
// top level references to the type definition node were already added.
if (reference.TargetId == typeDefinition.NodeId)
{
continue;
}
// see if a reference is allowed.
if (!IsExternalReferenceAllowed(reference.ReferenceTypeId))
{
continue;
}
// add one way reference.
source.References.Add(reference.ReferenceTypeId, reference.IsInverse, reference.TargetId);
continue;
}
// add forward and backward reference.
AddReference(source, reference.ReferenceTypeId, reference.IsInverse, target, true);
}
}
}
/// <summary>
/// Returns true if a one-way reference to external nodes is permitted.
/// </summary>
private bool IsExternalReferenceAllowed(NodeId referenceTypeId)
{
// always exclude hierarchial references.
if (m_nodes.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypeIds.HierarchicalReferences))
{
return false;
}
// allow one way reference to event.
if (m_nodes.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypes.GeneratesEvent))
{
return true;
}
// all other references not permitted.
return false;
}
/// <summary>
/// Updates the type definition for a node.
/// </summary>
private void UpdateTypeDefinition(ILocalNode instance, ExpandedNodeId typeDefinitionId)
{
// check existing type definition.
ExpandedNodeId existingTypeId = instance.TypeDefinitionId;
if (existingTypeId == typeDefinitionId)
{
return;
}
if (!NodeId.IsNull(existingTypeId))
{
if (m_nodes.TypeTree.IsTypeOf(existingTypeId, typeDefinitionId))
{
throw ServiceResultException.Create(
StatusCodes.BadTypeDefinitionInvalid,
"Type definition {0} is not a subtype of the existing type definition {1}.",
existingTypeId,
typeDefinitionId);
}
DeleteReference(instance, ReferenceTypeIds.HasTypeDefinition, false, existingTypeId, false);
}
AddReference(instance, ReferenceTypeIds.HasTypeDefinition, false, typeDefinitionId);
}
/// <summary>
/// A node in the type system that is used to instantiate objects or variables.
/// </summary>
private class DeclarationNode
{
public ILocalNode Node;
public string BrowsePath;
}
/// <summary>
/// Builds the list of declaration nodes for a type definition.
/// </summary>
private void BuildDeclarationList(ILocalNode typeDefinition, List<DeclarationNode> declarations)
{
if (typeDefinition == null) throw new ArgumentNullException(nameof(typeDefinition));
if (declarations == null) throw new ArgumentNullException(nameof(declarations));
// guard against loops (i.e. common grandparents).
for (int ii = 0; ii < declarations.Count; ii++)
{
if (Object.ReferenceEquals(declarations[ii].Node, typeDefinition))
{
return;
}
}
// create the root declaration for the type.
DeclarationNode declaration = new DeclarationNode();
declaration.Node = typeDefinition;
declaration.BrowsePath = String.Empty;
declarations.Add(declaration);
// follow references to supertypes first.
foreach (IReference reference in typeDefinition.References.Find(ReferenceTypeIds.HasSubtype, true, false, null))
{
ILocalNode supertype = GetLocalNode(reference.TargetId) as ILocalNode;
if (supertype == null)
{
continue;
}
BuildDeclarationList(supertype, declarations);
}
// add children of type.
BuildDeclarationList(declaration, declarations);
}
/// <summary>
/// Builds a list of declarations from the nodes aggregated by a parent.
/// </summary>
private void BuildDeclarationList(DeclarationNode parent, List<DeclarationNode> declarations)
{
if (parent == null) throw new ArgumentNullException(nameof(parent));
if (declarations == null) throw new ArgumentNullException(nameof(declarations));
// get list of children.
IList<IReference> references = parent.Node.References.Find(ReferenceTypeIds.HierarchicalReferences, false, true, m_nodes.TypeTree);
foreach (IReference reference in references)
{
// do not follow sub-type references.
if (m_nodes.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype))
{
continue;
}
// find child (ignore children that are not in the node table).
ILocalNode child = GetLocalNode(reference.TargetId) as ILocalNode;
if (child == null)
{
continue;
}
// create the declartion node.
DeclarationNode declaration = new DeclarationNode();
declaration.Node = child;
declaration.BrowsePath = Utils.Format("{0}.{1}", parent.BrowsePath, child.BrowseName);
declarations.Add(declaration);
// recursively include aggregated children.
NodeId modellingRule = child.ModellingRule;
if (modellingRule == Objects.ModellingRule_Mandatory || modellingRule == Objects.ModellingRule_Optional)
{
BuildDeclarationList(declaration, declarations);
}
}
}
/// <summary>
/// Builds a table of instances indexed by browse path from the nodes aggregated by a parent
/// </summary>
private void BuildInstanceList(ILocalNode parent, string browsePath, IDictionary<string,ILocalNode> instances)
{
if (parent == null) throw new ArgumentNullException(nameof(parent));
if (instances == null) throw new ArgumentNullException(nameof(instances));
// guard against loops.
if (instances.ContainsKey(browsePath))
{
return;
}
// index parent by browse path.
instances[browsePath] = parent;
// get list of children.
IList<IReference> references = parent.References.Find(ReferenceTypeIds.HierarchicalReferences, false, true, m_nodes.TypeTree);
foreach (IReference reference in references)
{
// find child (ignore children that are not in the node table).
ILocalNode child = GetLocalNode(reference.TargetId) as ILocalNode;
if (child == null)
{
continue;
}
// recursively include aggregated children.
BuildInstanceList(child, Utils.Format("{0}.{1}", browsePath, child.BrowseName), instances);
}
}
#endregion
/// <summary>
/// Exports a node to a nodeset.
/// </summary>
public void ExportNode(NodeId nodeId, NodeSet nodeSet)
{
lock (m_lock)
{
ILocalNode node = GetLocalNode(nodeId) as ILocalNode;
if (node == null)
{
throw ServiceResultException.Create(StatusCodes.BadNodeIdUnknown, "NodeId ({0}) does not exist.", nodeId);
}
ExportNode(node, nodeSet, (node.NodeClass & (NodeClass.Object | NodeClass.Variable)) != 0);
}
}
/// <summary>
/// Exports a node to a nodeset.
/// </summary>
public void ExportNode(ILocalNode node, NodeSet nodeSet, bool instance)
{
lock (m_lock)
{
// check if the node has already been added.
NodeId exportedId = nodeSet.Export(node.NodeId, m_nodes.NamespaceUris);
if (nodeSet.Contains(exportedId))
{
return;
}
// add to nodeset.
Node nodeToExport = nodeSet.Add(node, m_nodes.NamespaceUris, m_nodes.ServerUris);
// follow children.
foreach (ReferenceNode reference in node.References)
{
// export all references.
bool export = true;
// unless it is a subtype reference.
if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype))
{
export = false;
}
if (export)
{
nodeSet.AddReference(nodeToExport, reference, m_nodes.NamespaceUris, m_nodes.ServerUris);
}
if (reference.IsInverse || m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.HasSubtype))
{
nodeSet.AddReference(nodeToExport, reference, m_nodes.NamespaceUris, m_nodes.ServerUris);
}
if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.Aggregates))
{
if (reference.IsInverse)
{
continue;
}
ILocalNode child = GetLocalNode(reference.TargetId) as ILocalNode;
if (child != null)
{
if (instance)
{
NodeId modellingRule = child.ModellingRule;
if (modellingRule != Objects.ModellingRule_Mandatory)
{
continue;
}
}
ExportNode(child, nodeSet, instance);
}
}
}
}
}
#if XXX
/// <summary>
/// Changes the type definition for an instance.
/// </summary>
public void ChangeTypeDefinition(
NodeId instanceId,
NodeId typeDefinitionId)
{
try
{
m_lock.Enter();
// find the instance.
ILocalNode instance = GetLocalNode(instanceId) as ILocalNode;
if (instance == null)
{
throw ServiceResultException.Create(StatusCodes.BadNodeIdUnknown, "NodeId ({0}) does not exist.", instanceId);
}
// check node class.
if (instance.NodeClass != NodeClass.Object && instance.NodeClass != NodeClass.Variable)
{
throw ServiceResultException.Create(StatusCodes.BadNodeClassInvalid, "Node (NodeClass={0}) cannot have a type definition.", instance.NodeClass);
}
// get current type definition.
ExpandedNodeId existingTypeId = instance.TypeDefinitionId;
if (existingTypeId == typeDefinitionId)
{
return;
}
// can only change to a subtype of the existing type definition.
if (!m_server.TypeTree.IsTypeOf(typeDefinitionId, existingTypeId))
{
throw ServiceResultException.Create(StatusCodes.BadTypeDefinitionInvalid, "Type definition ({0}) must be a must subtype of the existing type definition ({1}).", typeDefinitionId, existingTypeId);
}
// find the type definition node.
ILocalNode typeDefinition = GetLocalNode(typeDefinitionId) as ILocalNode;
if (typeDefinition == null)
{
throw ServiceResultException.Create(StatusCodes.BadTypeDefinitionInvalid, "TypeDefinitionId ({0}) does not exist.", typeDefinitionId);
}
// apply modelling rules.
NodeFactory factory = new NodeFactory(m_nodes);
IList<ILocalNode> nodesToAdd = factory.ApplyModellingRules(instance, typeDefinition.NodeId, ref m_lastId, 1);
// add the nodes.
foreach (Node nodeToAdd in nodesToAdd)
{
AddNode(nodeToAdd);
}
}
finally
{
m_lock.Exit();
}
}
#endif
/// <summary>
/// Updates the attributes for the node.
/// </summary>
private static void UpdateAttributes(ILocalNode node, NodeAttributes attributes)
{
// DisplayName
if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.DisplayName) != 0)
{
node.DisplayName = attributes.DisplayName;
if (node.DisplayName == null)
{
node.DisplayName = new LocalizedText(node.BrowseName.Name);
}
}
else
{
node.DisplayName = new LocalizedText(node.BrowseName.Name);
}
// Description
if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.Description) != 0)
{
node.Description = attributes.Description;
}
// WriteMask
if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.WriteMask) != 0)
{
node.WriteMask = (AttributeWriteMask)attributes.WriteMask;
}
else
{
node.WriteMask = AttributeWriteMask.None;
}
// WriteMask
if (attributes != null && (attributes.SpecifiedAttributes & (uint)NodeAttributesMask.UserWriteMask) != 0)
{
node.UserWriteMask = (AttributeWriteMask)attributes.UserWriteMask;
}
else
{
node.UserWriteMask = AttributeWriteMask.None;
}
}
/// <summary>
/// Deletes a node from the address sapce.
/// </summary>
public void DeleteNode(NodeId nodeId, bool deleteChildren, bool silent)
{
if (nodeId == null) throw new ArgumentNullException(nameof(nodeId));
// find the node to delete.
ILocalNode node = GetManagerHandle(nodeId) as ILocalNode;
if (node == null)
{
if (!silent)
{
throw ServiceResultException.Create(StatusCodes.BadSourceNodeIdInvalid, "Node '{0}' does not exist.", nodeId);
}
return;
}
bool instance = (node.NodeClass & (NodeClass.Object | NodeClass.Variable)) != 0;
Dictionary<NodeId,IList<IReference>> referencesToDelete = new Dictionary<NodeId,IList<IReference>>();
if (silent)
{
try
{
DeleteNode(node, deleteChildren, instance, referencesToDelete);
}
catch (Exception e)
{
Utils.Trace(e, "Error deleting node: {0}", nodeId);
}
}
else
{
DeleteNode(node, deleteChildren, instance, referencesToDelete);
}
if (referencesToDelete.Count > 0)
{
Task.Run(() =>
{
OnDeleteReferences(referencesToDelete);
});
}
}
/// <summary>
/// Deletes a node from the address sapce.
/// </summary>
private void DeleteNode(ILocalNode node, bool deleteChildren, bool instance, Dictionary<NodeId,IList<IReference>> referencesToDelete)
{
if (node == null) throw new ArgumentNullException(nameof(node));
List<ILocalNode> nodesToDelete = new List<ILocalNode>();
List<IReference> referencesForNode = new List<IReference>();
lock (m_lock)
{
// remove the node.
m_nodes.Remove(node.NodeId);
// check need to connect subtypes to the supertype if they are being deleted.
ExpandedNodeId supertypeId = m_server.TypeTree.FindSuperType(node.NodeId);
if (!NodeId.IsNull(supertypeId))
{
m_server.TypeTree.Remove(node.NodeId);
}
// delete sources.
#if LEGACY_NODEMANAGER
DeleteRegisteredSources(node);
#endif
// remove any references to the node.
foreach (IReference reference in node.References)
{
// ignore remote references.
if (reference.TargetId.IsAbsolute)
{
continue;
}
// find the target.
ILocalNode target = GetManagerHandle(reference.TargetId) as ILocalNode;
if (target == null)
{
referencesForNode.Add(reference);
continue;
}
// delete the backward reference.
target.References.Remove(reference.ReferenceTypeId, !reference.IsInverse, node.NodeId);
// check for children that need to be deleted.
if (deleteChildren)
{
if (m_server.TypeTree.IsTypeOf(reference.ReferenceTypeId, ReferenceTypeIds.Aggregates) && !reference.IsInverse)
{
nodesToDelete.Add(target);
}
}
}
if (referencesForNode.Count > 0)
{
referencesToDelete[node.NodeId] = referencesForNode;
}
}
// delete the child nodes.
foreach (ILocalNode nodeToDelete in nodesToDelete)
{
DeleteNode(nodeToDelete, deleteChildren, instance, referencesToDelete);
}
}
/// <summary>
/// Deletes the external references to a node in a background thread.
/// </summary>
private void OnDeleteReferences(object state)
{
Dictionary<NodeId,IList<IReference>> referencesToDelete = state as Dictionary<NodeId,IList<IReference>>;
if (state == null)
{
return;
}
foreach (KeyValuePair<NodeId,IList<IReference>> current in referencesToDelete)
{
try
{
m_server.NodeManager.DeleteReferences(current.Key, current.Value);
}
catch (Exception e)
{
Utils.Trace(e, "Error deleting references for node: {0}", current.Key);
}
}
}
#region Add/Remove Node Support Functions
/// <summary>
/// Verifies that the source and the target meet the restrictions imposed by the reference type.
/// </summary>
private void ValidateReference(
ILocalNode source,
NodeId referenceTypeId,
bool isInverse,
NodeClass targetNodeClass)
{
// find reference type.
IReferenceType referenceType = GetLocalNode(referenceTypeId) as IReferenceType;
if (referenceType == null)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceTypeIdInvalid, "Reference type '{0}' does not exist.", referenceTypeId);
}
// swap the source and target for inverse references.
NodeClass sourceNodeClass = source.NodeClass;
if (isInverse)
{
sourceNodeClass = targetNodeClass;
targetNodeClass = source.NodeClass;
}
// check HasComponent references.
if (m_server.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypeIds.HasComponent))
{
if ((sourceNodeClass & (NodeClass.Object | NodeClass.Variable | NodeClass.ObjectType | NodeClass.VariableType)) == 0)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Source node cannot be used with HasComponent references.");
}
if ((targetNodeClass & (NodeClass.Object | NodeClass.Variable | NodeClass.Method)) == 0)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Target node cannot be used with HasComponent references.");
}
if (targetNodeClass == NodeClass.Variable)
{
if ((targetNodeClass & (NodeClass.Variable | NodeClass.VariableType)) == 0)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "A Variable must be a component of an Variable or VariableType.");
}
}
if (targetNodeClass == NodeClass.Method)
{
if ((sourceNodeClass & (NodeClass.Object | NodeClass.ObjectType)) == 0)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "A Method must be a component of an Object or ObjectType.");
}
}
}
// check HasProperty references.
if (m_server.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypes.HasProperty))
{
if (targetNodeClass != NodeClass.Variable)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Targets of HasProperty references must be Variables.");
}
}
// check HasSubtype references.
if (m_server.TypeTree.IsTypeOf(referenceTypeId, ReferenceTypeIds.HasSubtype))
{
if ((sourceNodeClass & (NodeClass.DataType | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType)) == 0)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "Source node cannot be used with HasSubtype references.");
}
if (targetNodeClass != sourceNodeClass)
{
throw ServiceResultException.Create(StatusCodes.BadReferenceNotAllowed, "The source and target cannot be connected by a HasSubtype reference.");
}
}
// TBD - check rules for other reference types.
}
#endregion
#endregion
#region Adding/Removing References
/// <summary>
/// Adds a reference between two existing nodes.
/// </summary>
public ServiceResult AddReference(
NodeId sourceId,
NodeId referenceTypeId,
bool isInverse,
NodeId targetId,
bool bidirectional)
{
lock (m_lock)
{
// find source.
ILocalNode source = GetManagerHandle(sourceId) as ILocalNode;
if (source == null)
{
return StatusCodes.BadParentNodeIdInvalid;
}
// add reference from target to source.
if (bidirectional)
{
// find target.
ILocalNode target = GetManagerHandle(targetId) as ILocalNode;
if (target == null)
{
return StatusCodes.BadNodeIdUnknown;
}
// ensure the reference is valid.
ValidateReference(source, referenceTypeId, isInverse, target.NodeClass);
// add reference from target to source.
AddReferenceToLocalNode(target, referenceTypeId, !isInverse, sourceId, false);
}
// add reference from source to target.
AddReferenceToLocalNode(source, referenceTypeId, isInverse, targetId, false);
return null;
}
}
/// <summary>
/// Ensures any changes to built-in nodes are reflected in the diagnostics node manager.
/// </summary>
private void AddReferenceToLocalNode(
ILocalNode source,
NodeId referenceTypeId,
bool isInverse,
ExpandedNodeId targetId,
bool isInternal)
{
source.References.Add(referenceTypeId, isInverse, targetId);
if (!isInternal && source.NodeId.NamespaceIndex == 0)
{
lock (Server.DiagnosticsNodeManager.Lock)
{
NodeState state = Server.DiagnosticsNodeManager.FindPredefinedNode(source.NodeId, null);
if (state != null)
{
INodeBrowser browser = state.CreateBrowser(
m_server.DefaultSystemContext,
null,
referenceTypeId,
true,
(isInverse) ? BrowseDirection.Inverse : BrowseDirection.Forward,
null,
null,
true);
bool found = false;
for (IReference reference = browser.Next(); reference != null; reference = browser.Next())
{
if (reference.TargetId == targetId)
{
found = true;
break;
}
}
if (!found)
{
state.AddReference(referenceTypeId, isInverse, targetId);
}
}
}
}
}
/// <summary>
/// Adds a reference between two existing nodes.
/// </summary>
public void CreateReference(
NodeId sourceId,
NodeId referenceTypeId,
bool isInverse,
NodeId targetId,
bool bidirectional)
{
lock (m_lock)
{
ServiceResult result = AddReference(sourceId, referenceTypeId, isInverse, targetId, bidirectional);
if (ServiceResult.IsBad(result))
{
throw new ServiceResultException(result);
}
}
}
/// <summary>
/// Adds a reference to the address space.
/// </summary>
private void AddReference(
ILocalNode source,
NodeId referenceTypeId,
bool isInverse,
ILocalNode target,
bool bidirectional)
{
AddReferenceToLocalNode(source, referenceTypeId, isInverse, target.NodeId, false);
if (bidirectional)
{
AddReferenceToLocalNode(target, referenceTypeId, !isInverse, source.NodeId, false);
}
}
/// <summary>
/// Adds a reference to the address space.
/// </summary>
private void AddReference(
ILocalNode source,
NodeId referenceTypeId,
bool isInverse,
ExpandedNodeId targetId)
{
AddReferenceToLocalNode(source, referenceTypeId, isInverse, targetId, false);
}
/// <summary>
/// Deletes a reference.
/// </summary>
public ServiceResult DeleteReference(
object sourceHandle,
NodeId referenceTypeId,
bool isInverse,
ExpandedNodeId targetId,
bool deleteBidirectional)
{
if (sourceHandle == null) throw new ArgumentNullException(nameof(sourceHandle));
if (referenceTypeId == null) throw new ArgumentNullException(nameof(referenceTypeId));
if (targetId == null) throw new ArgumentNullException(nameof(targetId));
lock (m_lock)
{
ILocalNode source = sourceHandle as ILocalNode;
if (source == null)
{
return StatusCodes.BadSourceNodeIdInvalid;
}
source.References.Remove(referenceTypeId, isInverse, targetId);
if (deleteBidirectional)
{
ILocalNode target = GetManagerHandle(targetId) as ILocalNode;
if (target != null)
{
target.References.Remove(referenceTypeId, !isInverse, source.NodeId);
}
}
return ServiceResult.Good;
}
}
/// <summary>
/// Deletes a reference.
/// </summary>
public void DeleteReference(
NodeId sourceId,
NodeId referenceTypeId,
bool isInverse,
ExpandedNodeId targetId,
bool deleteBidirectional)
{
ServiceResult result = DeleteReference(
GetManagerHandle(sourceId) as ILocalNode,
referenceTypeId,
isInverse,
targetId,
deleteBidirectional);
if (ServiceResult.IsBad(result))
{
throw new ServiceResultException(result);
}
}
/// <summary>
/// Adds a node to the address space.
/// </summary>
private void AddNode(ILocalNode node)
{
m_nodes.Attach(node);
}
#endregion
/// <summary>
/// Returns a node managed by the manager with the specified node id.
/// </summary>
public ILocalNode GetLocalNode(ExpandedNodeId nodeId)
{
if (nodeId == null)
{
return null;
}
// check for absolute declarations of local nodes.
if (nodeId.IsAbsolute)
{
if (nodeId.ServerIndex != 0)
{
return null;
}
int namespaceIndex = this.Server.NamespaceUris.GetIndex(nodeId.NamespaceUri);
if (namespaceIndex < 0 || nodeId.NamespaceIndex >= this.Server.NamespaceUris.Count)
{
return null;
}
return GetLocalNode(new NodeId(nodeId.Identifier, (ushort)namespaceIndex));
}
return GetLocalNode((NodeId)nodeId);
}
/// <summary>
/// Returns a node managed by the manager with the specified node id.
/// </summary>
public ILocalNode GetLocalNode(
NodeId nodeId,
NodeId referenceTypeId,
bool isInverse,
bool includeSubtypes,
QualifiedName browseName)
{
lock (m_lock)
{
return m_nodes.Find(nodeId, referenceTypeId, isInverse, includeSubtypes, browseName) as ILocalNode;
}
}
/// <summary>
/// Returns a node managed by the manager with the specified node id.
/// </summary>
public ILocalNode GetLocalNode(NodeId nodeId)
{
lock (m_lock)
{
return m_nodes.Find(nodeId) as ILocalNode;
}
}
/// <summary>
/// Returns a list of nodes which are targets of the specified references.
/// </summary>
public IList<ILocalNode> GetLocalNodes(
NodeId sourceId,
NodeId referenceTypeId,
bool isInverse,
bool includeSubtypes)
{
lock (m_lock)
{
List<ILocalNode> targets = new List<ILocalNode>();
ILocalNode source = GetLocalNode(sourceId) as ILocalNode;
if (source == null)
{
return targets;
}
foreach (IReference reference in source.References.Find(referenceTypeId, isInverse, true, m_nodes.TypeTree))
{
ILocalNode target = GetLocalNode(reference.TargetId) as ILocalNode;
if (target != null)
{
targets.Add(target);
}
}
return targets;
}
}
/// <summary>
/// Returns a node managed by the manager that has the specified browse name.
/// </summary>
public ILocalNode GetTargetNode(
NodeId sourceId,
NodeId referenceTypeId,
bool isInverse,
bool includeSubtypes,
QualifiedName browseName)
{
lock (m_lock)
{
ILocalNode source = GetLocalNode(sourceId) as ILocalNode;
if (source == null)
{
return null;
}
return GetTargetNode(source, referenceTypeId, isInverse, includeSubtypes, browseName);
}
}
/// <summary>
/// Returns a node managed by the manager that has the specified browse name.
/// </summary>
private ILocalNode GetTargetNode(
ILocalNode source,
NodeId referenceTypeId,
bool isInverse,
bool includeSubtypes,
QualifiedName browseName)
{
foreach (IReference reference in source.References.Find(referenceTypeId, isInverse, includeSubtypes, m_server.TypeTree))
{
ILocalNode target = GetLocalNode(reference.TargetId) as ILocalNode;
if (target == null)
{
continue;
}
if (QualifiedName.IsNull(browseName) || browseName == target.BrowseName)
{
return target;
}
}
return null;
}
/// <summary>
/// Attaches a node to the address space.
/// </summary>
public void AttachNode(ILocalNode node)
{
AttachNode(node, false);
}
/// <summary>
/// Attaches a node to the address space.
/// </summary>
private void AttachNode(ILocalNode node, bool isInternal)
{
if (node == null) throw new ArgumentNullException(nameof(node));
lock (m_lock)
{
// check if node exists.
if (m_nodes.Exists(node.NodeId))
{
throw ServiceResultException.Create(
StatusCodes.BadNodeIdExists,
"A node with the same node id already exists: {0}",
node.NodeId);
}
// ensure reverse references exist.
foreach (IReference reference in node.References)
{
// ignore references that are always one way.
if (reference.ReferenceTypeId == ReferenceTypeIds.HasTypeDefinition || reference.ReferenceTypeId == ReferenceTypeIds.HasModellingRule)
{
continue;
}
// find target.
ILocalNode target = GetLocalNode(reference.TargetId) as ILocalNode;
if (target != null)
{
AddReferenceToLocalNode(target, reference.ReferenceTypeId, !reference.IsInverse, node.NodeId, isInternal);
}
}
// must generate a model change event.
AddNode(node);
}
}
/// <summary>
/// Creates a unique node identifier.
/// </summary>
public NodeId CreateUniqueNodeId()
{
return CreateUniqueNodeId(m_dynamicNamespaceIndex);
}
#region Private Methods
/// <see cref="INodeManager.GetManagerHandle" />
private object GetManagerHandle(ExpandedNodeId nodeId)
{
lock (m_lock)
{
if (nodeId == null || nodeId.IsAbsolute)
{
return null;
}
return GetLocalNode(nodeId) as ILocalNode;
}
}
#if LEGACY_CORENODEMANAGER
/// <summary>
/// Checks if the operation needs to be handled by an external source.
/// </summary>
private static bool CheckSourceHandle(ILocalNode node, Type sourceType, int index, IDictionary sources)
{
// check if a source is defined for the node.
SourceHandle handle = node.Handle as SourceHandle;
if (handle == null)
{
return false;
}
// check if the source type is valid.
if (!sourceType.IsInstanceOfType(handle.Source))
{
return false;
}
// find list of handles for the source.
List<RequestHandle> handles = null;
if (!sources.Contains(handle.Source))
{
sources[handle.Source] = handles = new List<RequestHandle>();
}
else
{
handles = (List<RequestHandle>)sources[handle.Source];
}
// add node to list of values to process by the source.
handles.Add(new RequestHandle(handle.Handle, index));
return true;
}
/// <summary>
/// Recursively subscribes to events for the notifiers in the tree.
/// </summary>
private void SubscribeToEvents(
OperationContext context,
ILocalNode node,
uint subscriptionId,
IEventMonitoredItem monitoredItem,
bool unsubscribe)
{
// find handle associated with the node.
IEventSource eventSource = node as IEventSource;
SourceHandle handle = node.Handle as SourceHandle;
if (handle != null)
{
eventSource = handle.Source as IEventSource;
}
if (eventSource != null)
{
try
{
eventSource.SubscribeToEvents(context, (handle != null)?handle.Handle:null, subscriptionId, monitoredItem, unsubscribe);
}
catch (Exception e)
{
Utils.Trace(e, "Unexpected error calling SubscribeToEvents on an EventSource.");
}
}
// find the child notifiers.
IList<IReference> references = node.References.Find(ReferenceTypes.HasNotifier, false, true, m_server.TypeTree);
for (int ii = 0; ii < references.Count; ii++)
{
if (!references[ii].TargetId.IsAbsolute)
{
ILocalNode target = GetManagerHandle(references[ii].TargetId) as ILocalNode;
if (target == null)
{
continue;
}
// only object or views can produce events.
if ((target.NodeClass & (NodeClass.Object | NodeClass.View)) == 0)
{
continue;
}
SubscribeToEvents(context, target, subscriptionId, monitoredItem, unsubscribe);
}
}
}
#endif
/// <summary>
/// Reads the EU Range for a variable.
/// </summary>
private ServiceResult ReadEURange(OperationContext context, ILocalNode node, out Range range)
{
range = null;
IVariable target = GetTargetNode(node, ReferenceTypes.HasProperty, false, true, BrowseNames.EURange) as IVariable;
if (target == null)
{
return StatusCodes.BadNodeIdUnknown;
}
range = target.Value as Range;
if (range == null)
{
return StatusCodes.BadTypeMismatch;
}
return ServiceResult.Good;
}
/// <summary>
/// Validates a filter for a monitored item.
/// </summary>
private ServiceResult ValidateFilter(
NodeMetadata metadata,
uint attributeId,
ExtensionObject filter,
out bool rangeRequired)
{
rangeRequired = false;
// check filter.
DataChangeFilter datachangeFilter = null;
if (filter != null)
{
datachangeFilter = filter.Body as DataChangeFilter;
}
if (datachangeFilter != null)
{
// get the datatype of the node.
NodeId datatypeId = metadata.DataType;
// check that filter is valid.
ServiceResult error = datachangeFilter.Validate();
if (ServiceResult.IsBad(error))
{
return error;
}
// check datatype of the variable.
if (!m_server.TypeTree.IsTypeOf(datatypeId, DataTypes.Number))
{
return StatusCodes.BadDeadbandFilterInvalid;
}
// percent deadbands only allowed for analog data items.
if (datachangeFilter.DeadbandType == (uint)(int)DeadbandType.Percent)
{
ExpandedNodeId typeDefinitionId = metadata.TypeDefinition;
if (typeDefinitionId == null)
{
return StatusCodes.BadDeadbandFilterInvalid;
}
// percent deadbands only allowed for analog data items.
if (!m_server.TypeTree.IsTypeOf(typeDefinitionId, VariableTypes.AnalogItemType))
{
return StatusCodes.BadDeadbandFilterInvalid;
}
// the EURange property is required to use the filter.
rangeRequired = true;
}
}
// filter is valid
return ServiceResult.Good;
}
/// <summary>
/// Creates a new unique identifier for a node.
/// </summary>
private NodeId CreateUniqueNodeId(ushort namespaceIndex)
{
return new NodeId(Utils.IncrementIdentifier(ref m_lastId), namespaceIndex);
}
#endregion
#region Private Fields
private IServerInternal m_server;
private object m_lock = new object();
private NodeTable m_nodes;
private long m_lastId;
private SamplingGroupManager m_samplingGroupManager;
private Dictionary<uint, MonitoredItem> m_monitoredItems;
#if LEGACY_CORENODEMANAGER
private Dictionary<object,IEventSource> m_eventSources;
#endif
private double m_defaultMinimumSamplingInterval;
private List<string> m_namespaceUris;
private ushort m_dynamicNamespaceIndex;
#endregion
}
}