/* ======================================================================== * 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.Text; using System.Collections.Generic; using System.Reflection; using System.Threading; using Opc.Ua; using Opc.Ua.Server; namespace Opc.Ua.Server { /// /// A sample implementation of the INodeManager interface. /// /// /// This node manager is a base class used in multiple samples. It implements the INodeManager /// interface and allows sub-classes to override only the methods that they need. This example /// is not part of the SDK because most real implementations of a INodeManager will need to /// modify the behavior of the base class. /// public class CustomNodeManager2 : INodeManager2, INodeIdFactory, IDisposable { #region Constructors /// /// Initializes the node manager. /// protected CustomNodeManager2( IServerInternal server, params string[] namespaceUris) : this(server, (ApplicationConfiguration)null, namespaceUris) { } /// /// Initializes the node manager. /// protected CustomNodeManager2( IServerInternal server, ApplicationConfiguration configuration, params string[] namespaceUris) { // set defaults. m_maxQueueSize = 1000; if (configuration != null) { if (configuration.ServerConfiguration != null) { m_maxQueueSize = (uint)configuration.ServerConfiguration.MaxNotificationQueueSize; } } // save a reference to the UA server instance that owns the node manager. m_server = server; // all operations require information about the system m_systemContext = m_server.DefaultSystemContext.Copy(); // the node id factory assigns new node ids to new nodes. // the strategy used by a NodeManager depends on what kind of information it provides. m_systemContext.NodeIdFactory = this; // create the table of namespaces that are used by the NodeManager. m_namespaceUris = namespaceUris; // add the uris to the server's namespace table and cache the indexes. if (namespaceUris != null) { m_namespaceIndexes = new ushort[m_namespaceUris.Length]; for (int ii = 0; ii < m_namespaceUris.Length; ii++) { m_namespaceIndexes[ii] = m_server.NamespaceUris.GetIndexOrAppend(m_namespaceUris[ii]); } } // create the table of monitored items. // these are items created by clients when they subscribe to data or events. m_monitoredItems = new Dictionary(); // create the table of monitored nodes. // these are created by the node manager whenever a client subscribe to an attribute of the node. m_monitoredNodes = new Dictionary(); } #endregion #region IDisposable Members /// /// Frees any unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// An overrideable version of the Dispose. /// protected virtual void Dispose(bool disposing) { if (disposing) { lock (m_lock) { if (m_predefinedNodes != null) { foreach (NodeState node in m_predefinedNodes.Values) { Utils.SilentDispose(node); } m_predefinedNodes.Clear(); } } } } #endregion #region INodeIdFactory Members /// /// Creates the NodeId for the specified node. /// /// The context. /// The node. /// The new NodeId. public virtual NodeId New(ISystemContext context, NodeState node) { return node.NodeId; } #endregion #region Public Properties /// /// Acquires the lock on the node manager. /// public object Lock { get { return m_lock; } } /// /// Gets the server that the node manager belongs to. /// public IServerInternal Server { get { return m_server; } } /// /// The default context to use. /// public ServerSystemContext SystemContext { get { return m_systemContext; } } /// /// Gets the default index for the node manager's namespace. /// public ushort NamespaceIndex { get { return m_namespaceIndexes[0]; } } /// /// Gets the namespace indexes owned by the node manager. /// /// The namespace indexes. public ushort[] NamespaceIndexes { get { return m_namespaceIndexes; } } /// /// Gets or sets the maximum size of a monitored item queue. /// /// The maximum size of a monitored item queue. public uint MaxQueueSize { get { return m_maxQueueSize; } set { m_maxQueueSize = value; } } /// /// The root for the alias assigned to the node manager. /// public string AliasRoot { get { return m_aliasRoot; } set { m_aliasRoot = value; } } #endregion #region Protected Members /// /// The predefined nodes managed by the node manager. /// protected NodeIdDictionary PredefinedNodes { get { return m_predefinedNodes; } } /// /// The root notifiers for the node manager. /// protected List RootNotifiers { get { return m_rootNotifiers; } } /// /// Gets the table of monitored items. /// protected Dictionary MonitoredItems { get { return m_monitoredItems; } } /// /// Gets the table of nodes being monitored. /// protected Dictionary MonitoredNodes { get { return m_monitoredNodes; } } /// /// Sets the namespaces supported by the NodeManager. /// /// The namespace uris. protected void SetNamespaces(params string[] namespaceUris) { // create the table of namespaces that are used by the NodeManager. m_namespaceUris = namespaceUris; // add the uris to the server's namespace table and cache the indexes. m_namespaceIndexes = new ushort[m_namespaceUris.Length]; for (int ii = 0; ii < m_namespaceUris.Length; ii++) { m_namespaceIndexes[ii] = m_server.NamespaceUris.GetIndexOrAppend(m_namespaceUris[ii]); } } /// /// Sets the namespace indexes supported by the NodeManager. /// protected void SetNamespaceIndexes(ushort[] namespaceIndexes) { m_namespaceIndexes = namespaceIndexes; m_namespaceUris = new string[namespaceIndexes.Length]; for (int ii = 0; ii < namespaceIndexes.Length; ii++) { m_namespaceUris[ii] = m_server.NamespaceUris.GetString(namespaceIndexes[ii]); } } /// /// Returns true if the namespace for the node id is one of the namespaces managed by the node manager. /// /// The node id to check. /// True if the namespace is one of the nodes. protected virtual bool IsNodeIdInNamespace(NodeId nodeId) { // nulls are never a valid node. if (NodeId.IsNull(nodeId)) { return false; } // quickly exclude nodes that not in the namespace. for (int ii = 0; ii < m_namespaceIndexes.Length; ii++) { if (nodeId.NamespaceIndex == m_namespaceIndexes[ii]) { return true; } } return false; } /// /// Returns the node if the handle refers to a node managed by this manager. /// /// The handle to check. /// Non-null if the handle belongs to the node manager. protected virtual NodeHandle IsHandleInNamespace(object managerHandle) { NodeHandle source = managerHandle as NodeHandle; if (source == null) { return null; } if (!IsNodeIdInNamespace(source.NodeId)) { return null; } return source; } /// /// Returns the state object for the specified node if it exists. /// public NodeState Find(NodeId nodeId) { lock (Lock) { if (PredefinedNodes == null) { return null; } NodeState node = null; if (!PredefinedNodes.TryGetValue(nodeId, out node)) { return null; } return node; } } /// /// Creates a new instance and assigns unique identifiers to all children. /// /// The operation context. /// An optional parent identifier. /// The reference type from the parent. /// The browse name. /// The instance to create. /// The new node id. public NodeId CreateNode( ServerSystemContext context, NodeId parentId, NodeId referenceTypeId, QualifiedName browseName, BaseInstanceState instance) { ServerSystemContext contextToUse = (ServerSystemContext)m_systemContext.Copy(context); lock (Lock) { if (m_predefinedNodes == null) { m_predefinedNodes = new NodeIdDictionary(); } instance.ReferenceTypeId = referenceTypeId; NodeState parent = null; if (parentId != null) { if (!m_predefinedNodes.TryGetValue(parentId, out parent)) { throw ServiceResultException.Create( StatusCodes.BadNodeIdUnknown, "Cannot find parent with id: {0}", parentId); } parent.AddChild(instance); } instance.Create(contextToUse, null, browseName, null, true); AddPredefinedNode(contextToUse, instance); return instance.NodeId; } } /// /// Deletes a node and all of its children. /// public bool DeleteNode( ServerSystemContext context, NodeId nodeId) { ServerSystemContext contextToUse = m_systemContext.Copy(context); bool found = false; List referencesToRemove = new List(); lock (Lock) { if (m_predefinedNodes == null) { return false; } NodeState node = null; if (PredefinedNodes.TryGetValue(nodeId, out node)) { RemovePredefinedNode(contextToUse, node, referencesToRemove); found = true; } RemoveRootNotifier(node); } // must release the lock before removing cross references to other node managers. if (referencesToRemove.Count > 0) { Server.NodeManager.RemoveReferences(referencesToRemove); } return found; } /// /// Searches the node id in all node managers /// /// /// public NodeState FindNodeInAddressSpace(NodeId nodeId) { if (nodeId == null) { return null; } // search node id in all node managers foreach (INodeManager nodeManager in Server.NodeManager.NodeManagers) { NodeHandle handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; if (handle == null) { continue; } return handle.Node; } return null; } #endregion #region INodeManager Members /// /// Returns the namespaces used by the node manager. /// /// /// All NodeIds exposed by the node manager must be qualified by a namespace URI. This property /// returns the URIs used by the node manager. In this example all NodeIds use a single URI. /// public virtual IEnumerable NamespaceUris { get { return m_namespaceUris; } protected set { if (value == null) throw new ArgumentNullException(nameof(value)); List namespaceUris = new List(value); SetNamespaces(namespaceUris.ToArray()); } } /// /// Does any initialization required before the address space can be used. /// /// /// The externalReferences is an out parameter that allows the node manager to link to nodes /// in other node managers. For example, the 'Objects' node is managed by the CoreNodeManager and /// should have a reference to the root folder node(s) exposed by this node manager. /// public virtual void CreateAddressSpace(IDictionary> externalReferences) { LoadPredefinedNodes(m_systemContext, externalReferences); } #region CreateAddressSpace Support Functions /// /// Loads a node set from a file or resource and addes them to the set of predefined nodes. /// public virtual void LoadPredefinedNodes( ISystemContext context, Assembly assembly, string resourcePath, IDictionary> externalReferences) { if (m_predefinedNodes == null) { m_predefinedNodes = new NodeIdDictionary(); } // load the predefined nodes from an XML document. NodeStateCollection predefinedNodes = new NodeStateCollection(); predefinedNodes.LoadFromResource(context, resourcePath, assembly, true); // add the predefined nodes to the node manager. for (int ii = 0; ii < predefinedNodes.Count; ii++) { AddPredefinedNode(context, predefinedNodes[ii]); } // ensure the reverse references exist. AddReverseReferences(externalReferences); } /// /// Loads a node set from a file or resource and addes them to the set of predefined nodes. /// protected virtual NodeStateCollection LoadPredefinedNodes(ISystemContext context) { return new NodeStateCollection(); } /// /// Loads a node set from a file or resource and addes them to the set of predefined nodes. /// protected virtual void LoadPredefinedNodes( ISystemContext context, IDictionary> externalReferences) { // load the predefined nodes from an XML document. NodeStateCollection predefinedNodes = LoadPredefinedNodes(context); // add the predefined nodes to the node manager. for (int ii = 0; ii < predefinedNodes.Count; ii++) { AddPredefinedNode(context, predefinedNodes[ii]); } // ensure the reverse references exist. AddReverseReferences(externalReferences); } /// /// Replaces the generic node with a node specific to the model. /// protected virtual NodeState AddBehaviourToPredefinedNode(ISystemContext context, NodeState predefinedNode) { BaseObjectState passiveNode = predefinedNode as BaseObjectState; if (passiveNode == null) { return predefinedNode; } return predefinedNode; } /// /// Recursively indexes the node and its children. /// protected virtual void AddPredefinedNode(ISystemContext context, NodeState node) { if (m_predefinedNodes == null) { m_predefinedNodes = new NodeIdDictionary(); } NodeState activeNode = AddBehaviourToPredefinedNode(context, node); m_predefinedNodes[activeNode.NodeId] = activeNode; BaseTypeState type = activeNode as BaseTypeState; if (type != null) { AddTypesToTypeTree(type); } // update the root notifiers. if (m_rootNotifiers != null) { for (int ii = 0; ii < m_rootNotifiers.Count; ii++) { if (m_rootNotifiers[ii].NodeId == activeNode.NodeId) { m_rootNotifiers[ii] = activeNode; // need to prevent recursion with the server object. if (activeNode.NodeId != ObjectIds.Server) { activeNode.OnReportEvent = OnReportEvent; if (!activeNode.ReferenceExists(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server)) { activeNode.AddReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); } } break; } } } List children = new List(); activeNode.GetChildren(context, children); for (int ii = 0; ii < children.Count; ii++) { AddPredefinedNode(context, children[ii]); } } /// /// Recursively indexes the node and its children. /// protected virtual void RemovePredefinedNode( ISystemContext context, NodeState node, List referencesToRemove) { if (m_predefinedNodes == null) { return; } m_predefinedNodes.Remove(node.NodeId); node.UpdateChangeMasks(NodeStateChangeMasks.Deleted); node.ClearChangeMasks(context, false); OnNodeRemoved(node); // remove from the parent. BaseInstanceState instance = node as BaseInstanceState; if (instance != null && instance.Parent != null) { instance.Parent.RemoveChild(instance); } // remove children. List children = new List(); node.GetChildren(context, children); for (int ii = 0; ii < children.Count; ii++) { node.RemoveChild(children[ii]); } for (int ii = 0; ii < children.Count; ii++) { RemovePredefinedNode(context, children[ii], referencesToRemove); } // remove from type table. BaseTypeState type = node as BaseTypeState; if (type != null) { m_server.TypeTree.Remove(type.NodeId); } // remove inverse references. List references = new List(); node.GetReferences(context, references); for (int ii = 0; ii < references.Count; ii++) { IReference reference = references[ii]; if (reference.TargetId.IsAbsolute) { continue; } LocalReference referenceToRemove = new LocalReference( (NodeId)reference.TargetId, reference.ReferenceTypeId, reference.IsInverse, node.NodeId); referencesToRemove.Add(referenceToRemove); } } /// /// Called after a node has been deleted. /// protected virtual void OnNodeRemoved(NodeState node) { // overridden by the sub-class. } /// /// Ensures that all reverse references exist. /// /// A list of references to add to external targets. protected virtual void AddReverseReferences(IDictionary> externalReferences) { if (m_predefinedNodes == null) { return; } foreach (NodeState source in m_predefinedNodes.Values) { // assign a default value to any variable value. BaseVariableState variable = source as BaseVariableState; if (variable != null && variable.Value == null) { variable.Value = TypeInfo.GetDefaultValue(variable.DataType, variable.ValueRank, Server.TypeTree); } IList references = new List(); source.GetReferences(SystemContext, references); for (int ii = 0; ii < references.Count; ii++) { IReference reference = references[ii]; // nothing to do with external nodes. if (reference.TargetId == null || reference.TargetId.IsAbsolute) { continue; } // no need to add HasSubtype references since these are handled via the type table. if (reference.ReferenceTypeId == ReferenceTypeIds.HasSubtype) { continue; } NodeId targetId = (NodeId)reference.TargetId; // check for data type encoding references. if (reference.IsInverse && reference.ReferenceTypeId == ReferenceTypeIds.HasEncoding) { Server.TypeTree.AddEncoding(targetId, source.NodeId); } // add inverse reference to internal targets. NodeState target = null; if (m_predefinedNodes.TryGetValue(targetId, out target)) { if (!target.ReferenceExists(reference.ReferenceTypeId, !reference.IsInverse, source.NodeId)) { target.AddReference(reference.ReferenceTypeId, !reference.IsInverse, source.NodeId); } continue; } // check for inverse references to external notifiers. if (reference.IsInverse && reference.ReferenceTypeId == ReferenceTypeIds.HasNotifier) { AddRootNotifier(source); } // nothing more to do for references to nodes managed by this manager. if (IsNodeIdInNamespace(targetId)) { continue; } // add external reference. AddExternalReference( targetId, reference.ReferenceTypeId, !reference.IsInverse, source.NodeId, externalReferences); } } } /// /// Adds an external reference to the dictionary. /// protected void AddExternalReference( NodeId sourceId, NodeId referenceTypeId, bool isInverse, NodeId targetId, IDictionary> externalReferences) { // get list of references to external nodes. IList referencesToAdd = null; if (!externalReferences.TryGetValue(sourceId, out referencesToAdd)) { externalReferences[sourceId] = referencesToAdd = new List(); } // add reserve reference from external node. ReferenceNode referenceToAdd = new ReferenceNode(); referenceToAdd.ReferenceTypeId = referenceTypeId; referenceToAdd.IsInverse = isInverse; referenceToAdd.TargetId = targetId; referencesToAdd.Add(referenceToAdd); } /// /// Recursively adds the types to the type tree. /// protected void AddTypesToTypeTree(BaseTypeState type) { if (!NodeId.IsNull(type.SuperTypeId)) { if (!Server.TypeTree.IsKnown(type.SuperTypeId)) { AddTypesToTypeTree(type.SuperTypeId); } } if (type.NodeClass != NodeClass.ReferenceType) { Server.TypeTree.AddSubtype(type.NodeId, type.SuperTypeId); } else { Server.TypeTree.AddReferenceSubtype(type.NodeId, type.SuperTypeId, type.BrowseName); } } /// /// Recursively adds the types to the type tree. /// protected void AddTypesToTypeTree(NodeId typeId) { NodeState node = null; if (!PredefinedNodes.TryGetValue(typeId, out node)) { return; } BaseTypeState type = node as BaseTypeState; if (type == null) { return; } AddTypesToTypeTree(type); } /// /// Finds the specified and checks if it is of the expected type. /// /// Returns null if not found or not of the correct type. public NodeState FindPredefinedNode(NodeId nodeId, Type expectedType) { if (nodeId == null) { return null; } NodeState node = null; if (!PredefinedNodes.TryGetValue(nodeId, out node)) { return null; } if (expectedType != null) { if (!expectedType.IsInstanceOfType(node)) { return null; } } return node; } #endregion /// /// Frees any resources allocated for the address space. /// public virtual void DeleteAddressSpace() { lock (m_lock) { if (m_predefinedNodes != null) { foreach (NodeState node in m_predefinedNodes.Values) { Utils.SilentDispose(node); } m_predefinedNodes.Clear(); } } } /// /// Returns a unique handle for the node. /// /// /// This must efficiently determine whether the node belongs to the node manager. If it does belong to /// NodeManager it should return a handle that does not require the NodeId to be validated again when /// the handle is passed into other methods such as 'Read' or 'Write'. /// public virtual object GetManagerHandle(NodeId nodeId) { lock (Lock) { return GetManagerHandle(m_systemContext, nodeId, null); } } /// /// Returns a unique handle for the node. /// protected virtual NodeHandle GetManagerHandle(ServerSystemContext context, NodeId nodeId, IDictionary cache) { if (!IsNodeIdInNamespace(nodeId)) { return null; } if (m_predefinedNodes != null) { NodeState node = null; if (m_predefinedNodes.TryGetValue(nodeId, out node)) { NodeHandle handle = new NodeHandle(); handle.NodeId = nodeId; handle.Node = node; handle.Validated = true; return handle; } } return null; } /// /// This method is used to add bi-directional references to nodes from other node managers. /// /// /// The additional references are optional, however, the NodeManager should support them. /// public virtual void AddReferences(IDictionary> references) { lock (Lock) { foreach (KeyValuePair> current in references) { // get the handle. NodeHandle source = GetManagerHandle(m_systemContext, current.Key, null); // only support external references to nodes that are stored in memory. if (source == null || !source.Validated || source.Node == null) { continue; } // add reference to external target. foreach (IReference reference in current.Value) { if (!source.Node.ReferenceExists(reference.ReferenceTypeId, reference.IsInverse, reference.TargetId)) { source.Node.AddReference(reference.ReferenceTypeId, reference.IsInverse, reference.TargetId); } } } } } /// /// This method is used to delete bi-directional references to nodes from other node managers. /// public virtual ServiceResult DeleteReference( object sourceHandle, NodeId referenceTypeId, bool isInverse, ExpandedNodeId targetId, bool deleteBiDirectional) { lock (Lock) { // get the handle. NodeHandle source = IsHandleInNamespace(sourceHandle); if (source == null) { return StatusCodes.BadNodeIdUnknown; } // only support external references to nodes that are stored in memory. if (!source.Validated || source.Node == null) { return StatusCodes.BadNotSupported; } // only support references to Source Areas. source.Node.RemoveReference(referenceTypeId, isInverse, targetId); if (deleteBiDirectional) { // check if the target is also managed by this node manager. if (!targetId.IsAbsolute) { NodeHandle target = GetManagerHandle(m_systemContext, (NodeId)targetId, null); if (target != null && target.Validated && target.Node != null) { target.Node.RemoveReference(referenceTypeId, !isInverse, source.NodeId); } } } return ServiceResult.Good; } } /// /// Returns the basic metadata for the node. Returns null if the node does not exist. /// /// /// This method validates any placeholder handle. /// public virtual NodeMetadata GetNodeMetadata( OperationContext context, object targetHandle, BrowseResultMask resultMask) { ServerSystemContext systemContext = m_systemContext.Copy(context); lock (Lock) { // check for valid handle. NodeHandle handle = IsHandleInNamespace(targetHandle); if (handle == null) { return null; } // validate node. NodeState target = ValidateNode(systemContext, handle, null); if (target == null) { return null; } // read the attributes. List values = target.ReadAttributes( systemContext, Attributes.WriteMask, Attributes.UserWriteMask, Attributes.DataType, Attributes.ValueRank, Attributes.ArrayDimensions, Attributes.AccessLevel, Attributes.UserAccessLevel, Attributes.EventNotifier, Attributes.Executable, Attributes.UserExecutable, Attributes.AccessRestrictions, Attributes.RolePermissions, Attributes.UserRolePermissions); // construct the meta-data object. NodeMetadata metadata = new NodeMetadata(target, target.NodeId); metadata.NodeClass = target.NodeClass; metadata.BrowseName = target.BrowseName; metadata.DisplayName = target.DisplayName; if (values[0] != null && values[1] != null) { metadata.WriteMask = (AttributeWriteMask)(((uint)values[0]) & ((uint)values[1])); } metadata.DataType = (NodeId)values[2]; if (values[3] != null) { metadata.ValueRank = (int)values[3]; } metadata.ArrayDimensions = (IList)values[4]; if (values[5] != null && values[6] != null) { metadata.AccessLevel = (byte)(((byte)values[5]) & ((byte)values[6])); } if (values[7] != null) { metadata.EventNotifier = (byte)values[7]; } if (values[8] != null && values[9] != null) { metadata.Executable = (((bool)values[8]) && ((bool)values[9])); } if (values[10] != null) { metadata.AccessRestrictions = (AccessRestrictionType)Enum.ToObject(typeof(AccessRestrictionType), values[10]); } if (values[11] != null) { metadata.RolePermissions = new RolePermissionTypeCollection(ExtensionObject.ToList(values[11])); } if (values[12] != null) { metadata.UserRolePermissions = new RolePermissionTypeCollection(ExtensionObject.ToList(values[12])); } // 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) { List namespaceMetadataValues; if (namespaceMetadataState.DefaultAccessRestrictions != null) { // get DefaultAccessRestrictions for Namespace namespaceMetadataValues = namespaceMetadataState.DefaultAccessRestrictions.ReadAttributes(systemContext, Attributes.Value); if (namespaceMetadataValues[0] != null) { metadata.DefaultAccessRestrictions = (AccessRestrictionType)Enum.ToObject(typeof(AccessRestrictionType), namespaceMetadataValues[0]); } } if (namespaceMetadataState.DefaultRolePermissions != null) { // get DefaultRolePermissions for Namespace namespaceMetadataValues = namespaceMetadataState.DefaultRolePermissions.ReadAttributes(systemContext, Attributes.Value); if (namespaceMetadataValues[0] != null) { metadata.DefaultRolePermissions = new RolePermissionTypeCollection(ExtensionObject.ToList(namespaceMetadataValues[0])); } } if (namespaceMetadataState.DefaultUserRolePermissions != null) { // get DefaultUserRolePermissions for Namespace namespaceMetadataValues = namespaceMetadataState.DefaultUserRolePermissions.ReadAttributes(systemContext, Attributes.Value); if (namespaceMetadataValues[0] != null) { metadata.DefaultUserRolePermissions = new RolePermissionTypeCollection(ExtensionObject.ToList(namespaceMetadataValues[0])); } } } // get instance references. BaseInstanceState instance = target as BaseInstanceState; if (instance != null) { metadata.TypeDefinition = instance.TypeDefinitionId; metadata.ModellingRule = instance.ModellingRuleId; } // fill in the common attributes. return metadata; } } /// /// Browses the references from a node managed by the node manager. /// /// /// The continuation point is created for every browse operation and contains the browse parameters. /// The node manager can store its state information in the Data and Index properties. /// public virtual void Browse( OperationContext context, ref ContinuationPoint continuationPoint, IList references) { if (continuationPoint == null) throw new ArgumentNullException(nameof(continuationPoint)); if (references == null) throw new ArgumentNullException(nameof(references)); ServerSystemContext systemContext = m_systemContext.Copy(context); // check for valid view. ValidateViewDescription(systemContext, continuationPoint.View); INodeBrowser browser = null; lock (Lock) { // check for valid handle. NodeHandle handle = IsHandleInNamespace(continuationPoint.NodeToBrowse); if (handle == null) { throw new ServiceResultException(StatusCodes.BadNodeIdUnknown); } // validate node. NodeState source = ValidateNode(systemContext, handle, null); if (source == null) { throw new ServiceResultException(StatusCodes.BadNodeIdUnknown); } // check if node is in the view. if (!IsNodeInView(systemContext, continuationPoint, source)) { throw new ServiceResultException(StatusCodes.BadNodeNotInView); } // check for previous continuation point. browser = continuationPoint.Data as INodeBrowser; // fetch list of references. if (browser == null) { // create a new browser. continuationPoint.Data = browser = source.CreateBrowser( systemContext, continuationPoint.View, continuationPoint.ReferenceTypeId, continuationPoint.IncludeSubtypes, continuationPoint.BrowseDirection, null, null, false); } } // prevent multiple access the browser object. lock (browser) { // apply filters to references. Dictionary cache = new Dictionary(); for (IReference reference = browser.Next(); reference != null; reference = browser.Next()) { // validate Browse permission ServiceResult serviceResult = ValidateRolePermissions(context, ExpandedNodeId.ToNodeId(reference.TargetId, Server.NamespaceUris), PermissionType.Browse); if (ServiceResult.IsBad(serviceResult)) { // ignore reference continue; } // create the type definition reference. ReferenceDescription description = GetReferenceDescription(systemContext, cache, reference, continuationPoint); if (description == null) { continue; } // check if limit reached. if (continuationPoint.MaxResultsToReturn != 0 && references.Count >= continuationPoint.MaxResultsToReturn) { browser.Push(reference); return; } references.Add(description); } // release the continuation point if all done. continuationPoint.Dispose(); continuationPoint = null; } } #region Browse Support Functions /// /// Validates the view description passed to a browse request (throws on error). /// protected virtual void ValidateViewDescription(ServerSystemContext context, ViewDescription view) { if (ViewDescription.IsDefault(view)) { return; } ViewState node = (ViewState)FindPredefinedNode(view.ViewId, typeof(ViewState)); if (node == null) { throw new ServiceResultException(StatusCodes.BadViewIdUnknown); } if (view.Timestamp != DateTime.MinValue) { throw new ServiceResultException(StatusCodes.BadViewTimestampInvalid); } if (view.ViewVersion != 0) { throw new ServiceResultException(StatusCodes.BadViewVersionInvalid); } } /// /// Checks if the node is in the view. /// protected virtual bool IsNodeInView(ServerSystemContext context, ContinuationPoint continuationPoint, NodeState node) { if (continuationPoint == null || ViewDescription.IsDefault(continuationPoint.View)) { return true; } return IsNodeInView(context, continuationPoint.View.ViewId, node); } /// /// Checks if the node is in the view. /// protected virtual bool IsNodeInView(ServerSystemContext context, NodeId viewId, NodeState node) { ViewState view = (ViewState)FindPredefinedNode(viewId, typeof(ViewState)); if (view != null) { return true; } return false; } /// /// Checks if the reference is in the view. /// protected virtual bool IsReferenceInView(ServerSystemContext context, ContinuationPoint continuationPoint, IReference reference) { return true; } /// /// Returns the references for the node that meets the criteria specified. /// protected virtual ReferenceDescription GetReferenceDescription( ServerSystemContext context, Dictionary cache, IReference reference, ContinuationPoint continuationPoint) { ServerSystemContext systemContext = m_systemContext.Copy(context); // create the type definition reference. ReferenceDescription description = new ReferenceDescription(); description.NodeId = reference.TargetId; description.SetReferenceType(continuationPoint.ResultMask, reference.ReferenceTypeId, !reference.IsInverse); // check if reference is in the view. if (!IsReferenceInView(context, continuationPoint, reference)) { return null; } // do not cache target parameters for remote nodes. if (reference.TargetId.IsAbsolute) { // only return remote references if no node class filter is specified. if (continuationPoint.NodeClassMask != 0) { return null; } return description; } NodeState target = null; // check for local reference. NodeStateReference referenceInfo = reference as NodeStateReference; if (referenceInfo != null) { target = referenceInfo.Target; } // check for internal reference. if (target == null) { NodeHandle handle = GetManagerHandle(context, (NodeId)reference.TargetId, null) as NodeHandle; if (handle != null) { target = ValidateNode(context, handle, null); } } // the target may be a reference to a node in another node manager. In these cases // the target attributes must be fetched by the caller. The Unfiltered flag tells the // caller to do that. if (target == null) { description.Unfiltered = true; return description; } // apply node class filter. if (continuationPoint.NodeClassMask != 0 && ((continuationPoint.NodeClassMask & (uint)target.NodeClass) == 0)) { return null; } // check if target is in the view. if (!IsNodeInView(context, continuationPoint, target)) { return null; } // look up the type definition. NodeId typeDefinition = null; BaseInstanceState instance = target as BaseInstanceState; if (instance != null) { typeDefinition = instance.TypeDefinitionId; } // set target attributes. description.SetTargetAttributes( continuationPoint.ResultMask, target.NodeClass, target.BrowseName, target.DisplayName, typeDefinition); return description; } #endregion /// /// Returns the target of the specified browse path fragment(s). /// /// /// If reference exists but the node manager does not know the browse name it must /// return the NodeId as an unresolvedTargetIds. The caller will try to check the /// browse name. /// public virtual void TranslateBrowsePath( OperationContext context, object sourceHandle, RelativePathElement relativePath, IList targetIds, IList unresolvedTargetIds) { ServerSystemContext systemContext = m_systemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); lock (Lock) { // check for valid handle. NodeHandle handle = IsHandleInNamespace(sourceHandle); if (handle == null) { return; } // validate node. NodeState source = ValidateNode(systemContext, handle, operationCache); if (source == null) { return; } // get list of references that relative path. INodeBrowser browser = source.CreateBrowser( systemContext, null, relativePath.ReferenceTypeId, relativePath.IncludeSubtypes, (relativePath.IsInverse) ? BrowseDirection.Inverse : BrowseDirection.Forward, relativePath.TargetName, null, false); // check the browse names. try { for (IReference reference = browser.Next(); reference != null; reference = browser.Next()) { // ignore unknown external references. if (reference.TargetId.IsAbsolute) { continue; } NodeState target = null; // check for local reference. NodeStateReference referenceInfo = reference as NodeStateReference; if (referenceInfo != null) { target = referenceInfo.Target; } if (target == null) { NodeId targetId = (NodeId)reference.TargetId; // the target may be a reference to a node in another node manager. if (!IsNodeIdInNamespace(targetId)) { unresolvedTargetIds.Add((NodeId)reference.TargetId); continue; } // look up the target manually. NodeHandle targetHandle = GetManagerHandle(systemContext, targetId, operationCache); if (targetHandle == null) { continue; } // validate target. target = ValidateNode(systemContext, targetHandle, operationCache); if (target == null) { continue; } } // check browse name. if (target.BrowseName == relativePath.TargetName) { if (!targetIds.Contains(reference.TargetId)) { targetIds.Add(reference.TargetId); } } } } finally { browser.Dispose(); } } } /// /// Reads the value for the specified attribute. /// public virtual void Read( OperationContext context, double maxAge, IList nodesToRead, IList values, IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); List nodesToValidate = new List(); lock (Lock) { for (int ii = 0; ii < nodesToRead.Count; ii++) { ReadValueId nodeToRead = nodesToRead[ii]; // skip items that have already been processed. if (nodeToRead.Processed) { continue; } // check for valid handle. NodeHandle handle = GetManagerHandle(systemContext, nodeToRead.NodeId, operationCache); if (handle == null) { continue; } // owned by this node manager. nodeToRead.Processed = true; // create an initial value. DataValue value = values[ii] = new DataValue(); value.Value = null; value.ServerTimestamp = DateTime.UtcNow; value.SourceTimestamp = DateTime.MinValue; value.StatusCode = StatusCodes.Good; // check if the node is a area in memory. if (handle.Node == null) { errors[ii] = StatusCodes.BadNodeIdUnknown; // must validate node in a separate operation handle.Index = ii; nodesToValidate.Add(handle); continue; } // read the attribute value. errors[ii] = handle.Node.ReadAttribute( systemContext, nodeToRead.AttributeId, nodeToRead.ParsedIndexRange, nodeToRead.DataEncoding, value); } // check for nothing to do. if (nodesToValidate.Count == 0) { return; } } // validates the nodes (reads values from the underlying data source if required). Read( systemContext, nodesToRead, values, errors, nodesToValidate, operationCache); } #region Read Support Functions /// /// Finds a node in the dynamic cache. /// /// The current context. /// The node handle. /// The cache to search. /// The node if found. Null otherwise. protected virtual NodeState FindNodeInCache( ServerSystemContext context, NodeHandle handle, IDictionary cache) { NodeState target = null; // not valid if no root. if (handle == null) { return null; } // check if previously validated. if (handle.Validated) { return handle.Node; } // construct id for root node. NodeId rootId = handle.RootId; if (cache != null) { // lookup component in local cache for request. if (cache.TryGetValue(handle.NodeId, out target)) { return target; } // lookup root in local cache for request. if (!String.IsNullOrEmpty(handle.ComponentPath)) { if (cache.TryGetValue(rootId, out target)) { target = target.FindChildBySymbolicName(context, handle.ComponentPath); // component exists. if (target != null) { return target; } } } } // lookup component in shared cache. target = LookupNodeInComponentCache(context, handle); if (target != null) { return target; } return null; } /// /// Marks the handle as validated and saves the node in the dynamic cache. /// protected virtual NodeState ValidationComplete( ServerSystemContext context, NodeHandle handle, NodeState node, IDictionary cache) { handle.Node = node; handle.Validated = true; if (cache != null && handle != null) { cache[handle.NodeId] = node; } return node; } /// /// Verifies that the specified node exists. /// protected virtual NodeState ValidateNode( ServerSystemContext context, NodeHandle handle, IDictionary cache) { // lookup in cache. NodeState target = FindNodeInCache(context, handle, cache); if (target != null) { handle.Node = target; handle.Validated = true; return handle.Node; } // return default. return handle.Node; } /// /// Validates the nodes and reads the values from the underlying source. /// /// The context. /// The nodes to read. /// The values. /// The errors. /// The nodes to validate. /// The cache. protected virtual void Read( ServerSystemContext context, IList nodesToRead, IList values, IList errors, List nodesToValidate, IDictionary cache) { for (int ii = 0; ii < nodesToValidate.Count; ii++) { NodeHandle handle = nodesToValidate[ii]; lock (Lock) { // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } ReadValueId nodeToRead = nodesToRead[handle.Index]; DataValue value = values[handle.Index]; // update the attribute value. errors[handle.Index] = source.ReadAttribute( context, nodeToRead.AttributeId, nodeToRead.ParsedIndexRange, nodeToRead.DataEncoding, value); } } } #endregion /// /// Writes the value for the specified attributes. /// public virtual void Write( OperationContext context, IList nodesToWrite, IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); List nodesToValidate = new List(); lock (Lock) { for (int ii = 0; ii < nodesToWrite.Count; ii++) { WriteValue nodeToWrite = nodesToWrite[ii]; // skip items that have already been processed. if (nodeToWrite.Processed) { continue; } // check for valid handle. NodeHandle handle = GetManagerHandle(systemContext, nodeToWrite.NodeId, operationCache); if (handle == null) { continue; } // owned by this node manager. nodeToWrite.Processed = true; // index range is not supported. if (nodeToWrite.AttributeId != Attributes.Value) { if (!String.IsNullOrEmpty(nodeToWrite.IndexRange)) { errors[ii] = StatusCodes.BadWriteNotSupported; continue; } } // check if the node is a area in memory. if (handle.Node == null) { errors[ii] = StatusCodes.BadNodeIdUnknown; // must validate node in a separate operation. handle.Index = ii; nodesToValidate.Add(handle); continue; } // check if the node is AnalogItem and the value is outside the InstrumentRange. AnalogItemState analogItemState = handle.Node as AnalogItemState; if (analogItemState != null && analogItemState.InstrumentRange != null) { try { double newValue = System.Convert.ToDouble(nodeToWrite.Value.Value); if (newValue > analogItemState.InstrumentRange.Value.High || newValue < analogItemState.InstrumentRange.Value.Low) { errors[ii] = StatusCodes.BadOutOfRange; continue; } } catch { //skip the InstrumentRange check if the transformation isn't possible. } } Utils.TraceDebug("WRITE: Value={0} Range={1}", nodeToWrite.Value.WrappedValue, nodeToWrite.IndexRange); PropertyState propertyState = handle.Node as PropertyState; object previousPropertyValue = null; if (propertyState != null) { ExtensionObject extension = propertyState.Value as ExtensionObject; if (extension != null) { previousPropertyValue = extension.Body; } else { previousPropertyValue = propertyState.Value; } } // write the attribute value. errors[ii] = handle.Node.WriteAttribute( systemContext, nodeToWrite.AttributeId, nodeToWrite.ParsedIndexRange, nodeToWrite.Value); if (!ServiceResult.IsGood(errors[ii])) { continue; } if (propertyState != null) { object propertyValue; ExtensionObject extension = nodeToWrite.Value.Value as ExtensionObject; if (extension != null) { propertyValue = extension.Body; } else { propertyValue = nodeToWrite.Value.Value; } CheckIfSemanticsHaveChanged(systemContext, propertyState, propertyValue, previousPropertyValue); } // updates to source finished - report changes to monitored items. handle.Node.ClearChangeMasks(systemContext, false); } // check for nothing to do. if (nodesToValidate.Count == 0) { return; } } // validates the nodes and writes the value to the underlying system. Write( systemContext, nodesToWrite, errors, nodesToValidate, operationCache); } private void CheckIfSemanticsHaveChanged(ServerSystemContext systemContext, PropertyState property, object newPropertyValue, object previousPropertyValue) { // check if the changed property is one that can trigger semantic changes string propertyName = property.BrowseName.Name; if (propertyName != BrowseNames.EURange && propertyName != BrowseNames.InstrumentRange && propertyName != BrowseNames.EngineeringUnits && propertyName != BrowseNames.Title && propertyName != BrowseNames.AxisDefinition && propertyName != BrowseNames.FalseState && propertyName != BrowseNames.TrueState && propertyName != BrowseNames.EnumStrings && propertyName != BrowseNames.XAxisDefinition && propertyName != BrowseNames.YAxisDefinition && propertyName != BrowseNames.ZAxisDefinition) { return; } //look for the Parent and its monitoring items foreach (var monitoredNode in m_monitoredNodes.Values) { var propertyState = monitoredNode.Node.FindChild(systemContext, property.BrowseName); if (propertyState != null && property != null && propertyState.NodeId == property.NodeId && !Utils.IsEqual(newPropertyValue, previousPropertyValue)) { foreach (var monitoredItem in monitoredNode.DataChangeMonitoredItems) { if (monitoredItem.AttributeId == Attributes.Value) { NodeState node = monitoredNode.Node; if ((node is AnalogItemState && (propertyName == BrowseNames.EURange || propertyName == BrowseNames.EngineeringUnits)) || (node is TwoStateDiscreteState && (propertyName == BrowseNames.FalseState || propertyName == BrowseNames.TrueState)) || (node is MultiStateDiscreteState && (propertyName == BrowseNames.EnumStrings)) || (node is ArrayItemState && (propertyName == BrowseNames.InstrumentRange || propertyName == BrowseNames.EURange || propertyName == BrowseNames.EngineeringUnits || propertyName == BrowseNames.Title)) || ((node is YArrayItemState || node is XYArrayItemState) && (propertyName == BrowseNames.InstrumentRange || propertyName == BrowseNames.EURange || propertyName == BrowseNames.EngineeringUnits || propertyName == BrowseNames.Title || propertyName == BrowseNames.XAxisDefinition)) || (node is ImageItemState && (propertyName == BrowseNames.InstrumentRange || propertyName == BrowseNames.EURange || propertyName == BrowseNames.EngineeringUnits || propertyName == BrowseNames.Title || propertyName == BrowseNames.XAxisDefinition || propertyName == BrowseNames.YAxisDefinition)) || (node is CubeItemState && (propertyName == BrowseNames.InstrumentRange || propertyName == BrowseNames.EURange || propertyName == BrowseNames.EngineeringUnits || propertyName == BrowseNames.Title || propertyName == BrowseNames.XAxisDefinition || propertyName == BrowseNames.YAxisDefinition || propertyName == BrowseNames.ZAxisDefinition)) || (node is NDimensionArrayItemState && (propertyName == BrowseNames.InstrumentRange || propertyName == BrowseNames.EURange || propertyName == BrowseNames.EngineeringUnits || propertyName == BrowseNames.Title || propertyName == BrowseNames.AxisDefinition))) { monitoredItem.SetSemanticsChanged(); DataValue value = new DataValue(); value.ServerTimestamp = DateTime.UtcNow; monitoredNode.Node.ReadAttribute(systemContext, Attributes.Value, monitoredItem.IndexRange, null, value); monitoredItem.QueueValue(value, ServiceResult.Good, true); } } } } } } #region Write Support Functions /// /// Validates the nodes and writes the value to the underlying system. /// /// The context. /// The nodes to write. /// The errors. /// The nodes to validate. /// The cache. protected virtual void Write( ServerSystemContext context, IList nodesToWrite, IList errors, List nodesToValidate, IDictionary cache) { // validates the nodes (reads values from the underlying data source if required). for (int ii = 0; ii < nodesToValidate.Count; ii++) { NodeHandle handle = nodesToValidate[ii]; lock (Lock) { // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } WriteValue nodeToWrite = nodesToWrite[handle.Index]; // write the attribute value. errors[handle.Index] = source.WriteAttribute( context, nodeToWrite.AttributeId, nodeToWrite.ParsedIndexRange, nodeToWrite.Value); // updates to source finished - report changes to monitored items. source.ClearChangeMasks(context, false); } } } #endregion /// /// Reads the history for the specified nodes. /// public virtual void HistoryRead( OperationContext context, HistoryReadDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, IList nodesToRead, IList results, IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); List nodesToProcess = new List(); lock (Lock) { for (int ii = 0; ii < nodesToRead.Count; ii++) { HistoryReadValueId nodeToRead = nodesToRead[ii]; // skip items that have already been processed. if (nodeToRead.Processed) { continue; } // check for valid handle. NodeHandle handle = GetManagerHandle(systemContext, nodeToRead.NodeId, operationCache); if (handle == null) { continue; } // owned by this node manager. nodeToRead.Processed = true; // create an initial result. HistoryReadResult result = results[ii] = new HistoryReadResult(); result.HistoryData = null; result.ContinuationPoint = null; result.StatusCode = StatusCodes.Good; // check if the node is a area in memory. if (handle.Node == null) { errors[ii] = StatusCodes.BadNodeIdUnknown; // must validate node in a seperate operation handle.Index = ii; nodesToProcess.Add(handle); continue; } errors[ii] = StatusCodes.BadHistoryOperationUnsupported; // check for data history variable. BaseVariableState variable = handle.Node as BaseVariableState; if (variable != null) { if ((variable.AccessLevel & AccessLevels.HistoryRead) != 0) { handle.Index = ii; nodesToProcess.Add(handle); continue; } } // check for event history object. BaseObjectState notifier = handle.Node as BaseObjectState; if (notifier != null) { if ((notifier.EventNotifier & EventNotifiers.HistoryRead) != 0) { handle.Index = ii; nodesToProcess.Add(handle); continue; } } } // check for nothing to do. if (nodesToProcess.Count == 0) { return; } } // validates the nodes (reads values from the underlying data source if required). HistoryRead( systemContext, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors, nodesToProcess, operationCache); } #region HistoryRead Support Functions /// /// Releases the continuation points. /// protected virtual void HistoryReleaseContinuationPoints( ServerSystemContext context, IList nodesToRead, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadContinuationPointInvalid; } } /// /// Reads raw history data. /// protected virtual void HistoryReadRawModified( ServerSystemContext context, ReadRawModifiedDetails details, TimestampsToReturn timestampsToReturn, IList nodesToRead, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Reads processed history data. /// protected virtual void HistoryReadProcessed( ServerSystemContext context, ReadProcessedDetails details, TimestampsToReturn timestampsToReturn, IList nodesToRead, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Reads history data at specified times. /// protected virtual void HistoryReadAtTime( ServerSystemContext context, ReadAtTimeDetails details, TimestampsToReturn timestampsToReturn, IList nodesToRead, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Reads history events. /// protected virtual void HistoryReadEvents( ServerSystemContext context, ReadEventDetails details, TimestampsToReturn timestampsToReturn, IList nodesToRead, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Validates the nodes and reads the values from the underlying source. /// protected virtual void HistoryRead( ServerSystemContext context, HistoryReadDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, IList nodesToRead, IList results, IList errors, List nodesToProcess, IDictionary cache) { // check if continuation points are being released. if (releaseContinuationPoints) { HistoryReleaseContinuationPoints( context, nodesToRead, errors, nodesToProcess, cache); return; } // check timestamps to return. if (timestampsToReturn < TimestampsToReturn.Source || timestampsToReturn > TimestampsToReturn.Neither) { throw new ServiceResultException(StatusCodes.BadTimestampsToReturnInvalid); } // handle raw data request. ReadRawModifiedDetails readRawModifiedDetails = details as ReadRawModifiedDetails; if (readRawModifiedDetails != null) { // at least one must be provided. if (readRawModifiedDetails.StartTime == DateTime.MinValue && readRawModifiedDetails.EndTime == DateTime.MinValue) { throw new ServiceResultException(StatusCodes.BadInvalidTimestampArgument); } // if one is null the num values must be provided. if (readRawModifiedDetails.StartTime == DateTime.MinValue || readRawModifiedDetails.EndTime == DateTime.MinValue) { if (readRawModifiedDetails.NumValuesPerNode == 0) { throw new ServiceResultException(StatusCodes.BadInvalidTimestampArgument); } } HistoryReadRawModified( context, readRawModifiedDetails, timestampsToReturn, nodesToRead, results, errors, nodesToProcess, cache); return; } // handle processed data request. ReadProcessedDetails readProcessedDetails = details as ReadProcessedDetails; if (readProcessedDetails != null) { // check the list of aggregates. if (readProcessedDetails.AggregateType == null || readProcessedDetails.AggregateType.Count != nodesToRead.Count) { throw new ServiceResultException(StatusCodes.BadAggregateListMismatch); } // check start/end time. if (readProcessedDetails.StartTime == DateTime.MinValue || readProcessedDetails.EndTime == DateTime.MinValue) { throw new ServiceResultException(StatusCodes.BadInvalidTimestampArgument); } HistoryReadProcessed( context, readProcessedDetails, timestampsToReturn, nodesToRead, results, errors, nodesToProcess, cache); return; } // handle raw data at time request. ReadAtTimeDetails readAtTimeDetails = details as ReadAtTimeDetails; if (readAtTimeDetails != null) { HistoryReadAtTime( context, readAtTimeDetails, timestampsToReturn, nodesToRead, results, errors, nodesToProcess, cache); return; } // handle read events request. ReadEventDetails readEventDetails = details as ReadEventDetails; if (readEventDetails != null) { // check start/end time and max values. if (readEventDetails.NumValuesPerNode == 0) { if (readEventDetails.StartTime == DateTime.MinValue || readEventDetails.EndTime == DateTime.MinValue) { throw new ServiceResultException(StatusCodes.BadInvalidTimestampArgument); } } else { if (readEventDetails.StartTime == DateTime.MinValue && readEventDetails.EndTime == DateTime.MinValue) { throw new ServiceResultException(StatusCodes.BadInvalidTimestampArgument); } } // validate the event filter. EventFilter.Result result = readEventDetails.Filter.Validate(new FilterContext(m_server.NamespaceUris, m_server.TypeTree, context)); if (ServiceResult.IsBad(result.Status)) { throw new ServiceResultException(result.Status); } // read the event history. HistoryReadEvents( context, readEventDetails, timestampsToReturn, nodesToRead, results, errors, nodesToProcess, cache); return; } } #endregion /// /// Updates the history for the specified nodes. /// public virtual void HistoryUpdate( OperationContext context, Type detailsType, IList nodesToUpdate, IList results, IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); List nodesToProcess = new List(); lock (Lock) { for (int ii = 0; ii < nodesToUpdate.Count; ii++) { HistoryUpdateDetails nodeToUpdate = nodesToUpdate[ii]; // skip items that have already been processed. if (nodeToUpdate.Processed) { continue; } // check for valid handle. NodeHandle handle = GetManagerHandle(systemContext, nodeToUpdate.NodeId, operationCache); if (handle == null) { continue; } // owned by this node manager. nodeToUpdate.Processed = true; // create an initial result. HistoryUpdateResult result = results[ii] = new HistoryUpdateResult(); result.StatusCode = StatusCodes.Good; // check if the node is a area in memory. if (handle.Node == null) { errors[ii] = StatusCodes.BadNodeIdUnknown; // must validate node in a seperate operation handle.Index = ii; nodesToProcess.Add(handle); continue; } errors[ii] = StatusCodes.BadHistoryOperationUnsupported; // check for data history variable. BaseVariableState variable = handle.Node as BaseVariableState; if (variable != null) { if ((variable.AccessLevel & AccessLevels.HistoryWrite) != 0) { handle.Index = ii; nodesToProcess.Add(handle); continue; } } // check for event history object. BaseObjectState notifier = handle.Node as BaseObjectState; if (notifier != null) { if ((notifier.EventNotifier & EventNotifiers.HistoryWrite) != 0) { handle.Index = ii; nodesToProcess.Add(handle); continue; } } } // check for nothing to do. if (nodesToProcess.Count == 0) { return; } } // validates the nodes and updates. HistoryUpdate( systemContext, detailsType, nodesToUpdate, results, errors, nodesToProcess, operationCache); } #region HistoryUpdate Support Functions /// /// Validates the nodes and updates the history. /// protected virtual void HistoryUpdate( ServerSystemContext context, Type detailsType, IList nodesToUpdate, IList results, IList errors, List nodesToProcess, IDictionary cache) { // handle update data request. if (detailsType == typeof(UpdateDataDetails)) { UpdateDataDetails[] details = new UpdateDataDetails[nodesToUpdate.Count]; for (int ii = 0; ii < details.Length; ii++) { details[ii] = (UpdateDataDetails)nodesToUpdate[ii]; } HistoryUpdateData( context, details, results, errors, nodesToProcess, cache); return; } // handle update structure data request. if (detailsType == typeof(UpdateStructureDataDetails)) { UpdateStructureDataDetails[] details = new UpdateStructureDataDetails[nodesToUpdate.Count]; for (int ii = 0; ii < details.Length; ii++) { details[ii] = (UpdateStructureDataDetails)nodesToUpdate[ii]; } HistoryUpdateStructureData( context, details, results, errors, nodesToProcess, cache); return; } // handle update events request. if (detailsType == typeof(UpdateEventDetails)) { UpdateEventDetails[] details = new UpdateEventDetails[nodesToUpdate.Count]; for (int ii = 0; ii < details.Length; ii++) { details[ii] = (UpdateEventDetails)nodesToUpdate[ii]; } HistoryUpdateEvents( context, details, results, errors, nodesToProcess, cache); return; } // handle delete raw data request. if (detailsType == typeof(DeleteRawModifiedDetails)) { DeleteRawModifiedDetails[] details = new DeleteRawModifiedDetails[nodesToUpdate.Count]; for (int ii = 0; ii < details.Length; ii++) { details[ii] = (DeleteRawModifiedDetails)nodesToUpdate[ii]; } HistoryDeleteRawModified( context, details, results, errors, nodesToProcess, cache); return; } // handle delete at time request. if (detailsType == typeof(DeleteAtTimeDetails)) { DeleteAtTimeDetails[] details = new DeleteAtTimeDetails[nodesToUpdate.Count]; for (int ii = 0; ii < details.Length; ii++) { details[ii] = (DeleteAtTimeDetails)nodesToUpdate[ii]; } HistoryDeleteAtTime( context, details, results, errors, nodesToProcess, cache); return; } // handle delete at time request. if (detailsType == typeof(DeleteEventDetails)) { DeleteEventDetails[] details = new DeleteEventDetails[nodesToUpdate.Count]; for (int ii = 0; ii < details.Length; ii++) { details[ii] = (DeleteEventDetails)nodesToUpdate[ii]; } HistoryDeleteEvents( context, details, results, errors, nodesToProcess, cache); return; } } /// /// Updates the data history for one or more nodes. /// protected virtual void HistoryUpdateData( ServerSystemContext context, IList nodesToUpdate, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Updates the structured data history for one or more nodes. /// protected virtual void HistoryUpdateStructureData( ServerSystemContext context, IList nodesToUpdate, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Updates the event history for one or more nodes. /// protected virtual void HistoryUpdateEvents( ServerSystemContext context, IList nodesToUpdate, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Deletes the data history for one or more nodes. /// protected virtual void HistoryDeleteRawModified( ServerSystemContext context, IList nodesToUpdate, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Deletes the data history for one or more nodes. /// protected virtual void HistoryDeleteAtTime( ServerSystemContext context, IList nodesToUpdate, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } /// /// Deletes the event history for one or more nodes. /// protected virtual void HistoryDeleteEvents( ServerSystemContext context, IList nodesToUpdate, IList results, IList errors, List nodesToProcess, IDictionary cache) { for (int ii = 0; ii < nodesToProcess.Count; ii++) { NodeHandle handle = nodesToProcess[ii]; // validate node. NodeState source = ValidateNode(context, handle, cache); if (source == null) { continue; } errors[handle.Index] = StatusCodes.BadHistoryOperationUnsupported; } } #endregion /// /// Calls a method on the specified nodes. /// public virtual void Call( OperationContext context, IList methodsToCall, IList results, IList errors) { ServerSystemContext systemContext = SystemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); for (int ii = 0; ii < methodsToCall.Count; ii++) { CallMethodRequest methodToCall = methodsToCall[ii]; // skip items that have already been processed. if (methodToCall.Processed) { continue; } MethodState method = null; lock (Lock) { // check for valid handle. NodeHandle handle = GetManagerHandle(systemContext, methodToCall.ObjectId, operationCache); if (handle == null) { continue; } // owned by this node manager. methodToCall.Processed = true; // validate the source node. NodeState source = ValidateNode(systemContext, handle, operationCache); if (source == null) { errors[ii] = StatusCodes.BadNodeIdUnknown; continue; } // find the method. method = source.FindMethod(systemContext, methodToCall.MethodId); if (method == null) { // check for loose coupling. if (source.ReferenceExists(ReferenceTypeIds.HasComponent, false, methodToCall.MethodId)) { method = (MethodState)FindPredefinedNode(methodToCall.MethodId, typeof(MethodState)); } if (method == null) { errors[ii] = StatusCodes.BadMethodInvalid; continue; } } } // call the method. CallMethodResult result = results[ii] = new CallMethodResult(); errors[ii] = Call( systemContext, methodToCall, method, result); } } /// /// Calls a method on an object. /// protected virtual ServiceResult Call( ISystemContext context, CallMethodRequest methodToCall, MethodState method, CallMethodResult result) { ServerSystemContext systemContext = context as ServerSystemContext; List argumentErrors = new List(); VariantCollection outputArguments = new VariantCollection(); ServiceResult error = method.Call( context, methodToCall.ObjectId, methodToCall.InputArguments, argumentErrors, outputArguments); if (ServiceResult.IsBad(error)) { return error; } // check for argument errors. bool argumentsValid = true; for (int jj = 0; jj < argumentErrors.Count; jj++) { ServiceResult argumentError = argumentErrors[jj]; if (argumentError != null) { result.InputArgumentResults.Add(argumentError.StatusCode); if (ServiceResult.IsBad(argumentError)) { argumentsValid = false; } } else { result.InputArgumentResults.Add(StatusCodes.Good); } // only fill in diagnostic info if it is requested. if (systemContext.OperationContext != null) { if ((systemContext.OperationContext.DiagnosticsMask & DiagnosticsMasks.OperationAll) != 0) { if (ServiceResult.IsBad(argumentError)) { argumentsValid = false; result.InputArgumentDiagnosticInfos.Add(new DiagnosticInfo(argumentError, systemContext.OperationContext.DiagnosticsMask, false, systemContext.OperationContext.StringTable)); } else { result.InputArgumentDiagnosticInfos.Add(null); } } } } // check for validation errors. if (!argumentsValid) { result.StatusCode = StatusCodes.BadInvalidArgument; return result.StatusCode; } // do not return diagnostics if there are no errors. result.InputArgumentDiagnosticInfos.Clear(); // return output arguments. result.OutputArguments = outputArguments; return ServiceResult.Good; } /// /// Subscribes or unsubscribes to events produced by the specified source. /// /// /// This method is called when a event subscription is created or deletes. The node manager /// must start/stop reporting events for the specified object and all objects below it in /// the notifier hierarchy. /// public virtual ServiceResult SubscribeToEvents( OperationContext context, object sourceId, uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe) { ServerSystemContext systemContext = SystemContext.Copy(context); lock (Lock) { // check for valid handle. NodeHandle handle = IsHandleInNamespace(sourceId); if (handle == null) { return StatusCodes.BadNodeIdInvalid; } // check for valid node. NodeState source = ValidateNode(systemContext, handle, null); if (source == null) { return StatusCodes.BadNodeIdUnknown; } // subscribe to events. return SubscribeToEvents(systemContext, source, monitoredItem, unsubscribe); } } /// /// Subscribes or unsubscribes to events produced by all event sources. /// /// /// This method is called when a event subscription is created or deleted. The node /// manager must start/stop reporting events for all objects that it manages. /// public virtual ServiceResult SubscribeToAllEvents( OperationContext context, uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe) { ServerSystemContext systemContext = SystemContext.Copy(context); lock (Lock) { // A client has subscribed to the Server object which means all events produced // by this manager must be reported. This is done by incrementing the monitoring // reference count for all root notifiers. if (m_rootNotifiers != null) { for (int ii = 0; ii < m_rootNotifiers.Count; ii++) { SubscribeToEvents(systemContext, m_rootNotifiers[ii], monitoredItem, unsubscribe); } } return ServiceResult.Good; } } #region SubscribeToEvents Support Functions /// /// Adds a root notifier. /// /// The notifier. /// /// A root notifier is a notifier owned by the NodeManager that is not the target of a /// HasNotifier reference. These nodes need to be linked directly to the Server object. /// protected virtual void AddRootNotifier(NodeState notifier) { if (m_rootNotifiers == null) { m_rootNotifiers = new List(); } bool mustAdd = true; for (int ii = 0; ii < m_rootNotifiers.Count; ii++) { if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii])) { return; } if (m_rootNotifiers[ii].NodeId == notifier.NodeId) { m_rootNotifiers[ii] = notifier; mustAdd = false; break; } } if (mustAdd) { m_rootNotifiers.Add(notifier); } // need to prevent recursion with the server object. if (notifier.NodeId != ObjectIds.Server) { notifier.OnReportEvent = OnReportEvent; if (!notifier.ReferenceExists(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server)) { notifier.AddReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); } } // subscribe to existing events. if (m_server.EventManager != null) { IList monitoredItems = m_server.EventManager.GetMonitoredItems(); for (int ii = 0; ii < monitoredItems.Count; ii++) { if (monitoredItems[ii].MonitoringAllEvents) { SubscribeToEvents( SystemContext, notifier, monitoredItems[ii], true); } } } } /// /// Removes a root notifier previously added with AddRootNotifier. /// /// The notifier. protected virtual void RemoveRootNotifier(NodeState notifier) { if (m_rootNotifiers != null) { for (int ii = 0; ii < m_rootNotifiers.Count; ii++) { if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii])) { notifier.OnReportEvent = null; notifier.RemoveReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); m_rootNotifiers.RemoveAt(ii); break; } } } } /// /// Reports an event for a root notifier. /// protected virtual void OnReportEvent( ISystemContext context, NodeState node, IFilterTarget e) { Server.ReportEvent(context, e); } /// /// Subscribes to events. /// /// The context. /// The source. /// The monitored item. /// if set to true [unsubscribe]. /// Any error code. protected virtual ServiceResult SubscribeToEvents( ServerSystemContext context, NodeState source, IEventMonitoredItem monitoredItem, bool unsubscribe) { MonitoredNode2 monitoredNode = null; // handle unsubscribe. if (unsubscribe) { // check for existing monitored node. if (!MonitoredNodes.TryGetValue(source.NodeId, out monitoredNode)) { return StatusCodes.BadNodeIdUnknown; } monitoredNode.Remove(monitoredItem); // check if node is no longer being monitored. if (!monitoredNode.HasMonitoredItems) { MonitoredNodes.Remove(source.NodeId); } // update flag. source.SetAreEventsMonitored(context, !unsubscribe, true); // call subclass. OnSubscribeToEvents(context, monitoredNode, unsubscribe); // all done. return ServiceResult.Good; } // only objects or views can be subscribed to. BaseObjectState instance = source as BaseObjectState; if (instance == null || (instance.EventNotifier & EventNotifiers.SubscribeToEvents) == 0) { ViewState view = source as ViewState; if (view == null || (view.EventNotifier & EventNotifiers.SubscribeToEvents) == 0) { return StatusCodes.BadNotSupported; } } // check for existing monitored node. if (!MonitoredNodes.TryGetValue(source.NodeId, out monitoredNode)) { MonitoredNodes[source.NodeId] = monitoredNode = new MonitoredNode2(this, source); } // this links the node to specified monitored item and ensures all events // reported by the node are added to the monitored item's queue. monitoredNode.Add(monitoredItem); // This call recursively updates a reference count all nodes in the notifier // hierarchy below the area. Sources with a reference count of 0 do not have // any active subscriptions so they do not need to report events. source.SetAreEventsMonitored(context, !unsubscribe, true); // signal update. OnSubscribeToEvents(context, monitoredNode, unsubscribe); // all done. return ServiceResult.Good; } /// /// Called after subscribing/unsubscribing to events. /// /// The context. /// The monitored node. /// if set to true unsubscribing. protected virtual void OnSubscribeToEvents( ServerSystemContext context, MonitoredNode2 monitoredNode, bool unsubscribe) { // defined by the sub-class } #endregion /// /// Tells the node manager to refresh any conditions associated with the specified monitored items. /// /// /// This method is called when the condition refresh method is called for a subscription. /// The node manager must create a refresh event for each condition monitored by the subscription. /// public virtual ServiceResult ConditionRefresh( OperationContext context, IList monitoredItems) { ServerSystemContext systemContext = SystemContext.Copy(context); for (int ii = 0; ii < monitoredItems.Count; ii++) { // the IEventMonitoredItem should always be MonitoredItems since they are created by the MasterNodeManager. MonitoredItem monitoredItem = monitoredItems[ii] as MonitoredItem; if (monitoredItem == null) { continue; } List events = new List(); List nodesToRefresh = new List(); lock (Lock) { // check for server subscription. if (monitoredItem.NodeId == ObjectIds.Server) { if (m_rootNotifiers != null) { nodesToRefresh.AddRange(m_rootNotifiers); } } else { // check for existing monitored node. MonitoredNode2 monitoredNode = null; if (!MonitoredNodes.TryGetValue(monitoredItem.NodeId, out monitoredNode)) { continue; } // get the refresh events. nodesToRefresh.Add(monitoredNode.Node); } } // block and wait for the refresh. for (int jj = 0; jj < nodesToRefresh.Count; jj++) { nodesToRefresh[jj].ConditionRefresh(systemContext, events, true); } // queue the events. for (int jj = 0; jj < events.Count; jj++) { monitoredItem.QueueEvent(events[jj]); } } // all done. return ServiceResult.Good; } /// /// Creates a new set of monitored items for a set of variables. /// /// /// This method only handles data change subscriptions. Event subscriptions are created by the SDK. /// public virtual void CreateMonitoredItems( OperationContext context, uint subscriptionId, double publishingInterval, TimestampsToReturn timestampsToReturn, IList itemsToCreate, IList errors, IList filterResults, IList monitoredItems, ref long globalIdCounter) { ServerSystemContext systemContext = m_systemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); List nodesToValidate = new List(); List createdItems = new List(); lock (Lock) { for (int ii = 0; ii < itemsToCreate.Count; ii++) { MonitoredItemCreateRequest itemToCreate = itemsToCreate[ii]; // skip items that have already been processed. if (itemToCreate.Processed) { continue; } ReadValueId itemToMonitor = itemToCreate.ItemToMonitor; // check for valid handle. NodeHandle handle = GetManagerHandle(systemContext, itemToMonitor.NodeId, operationCache); if (handle == null) { continue; } // owned by this node manager. itemToCreate.Processed = true; // must validate node in a seperate operation. errors[ii] = StatusCodes.BadNodeIdUnknown; handle.Index = ii; nodesToValidate.Add(handle); } // check for nothing to do. if (nodesToValidate.Count == 0) { return; } } // validates the nodes (reads values from the underlying data source if required). for (int ii = 0; ii < nodesToValidate.Count; ii++) { NodeHandle handle = nodesToValidate[ii]; MonitoringFilterResult filterResult = null; IMonitoredItem monitoredItem = null; lock (Lock) { // validate node. NodeState source = ValidateNode(systemContext, handle, operationCache); if (source == null) { continue; } MonitoredItemCreateRequest itemToCreate = itemsToCreate[handle.Index]; // create monitored item. errors[handle.Index] = CreateMonitoredItem( systemContext, handle, subscriptionId, publishingInterval, context.DiagnosticsMask, timestampsToReturn, itemToCreate, ref globalIdCounter, out filterResult, out monitoredItem); } // save any filter error details. filterResults[handle.Index] = filterResult; if (ServiceResult.IsBad(errors[handle.Index])) { continue; } // save the monitored item. monitoredItems[handle.Index] = monitoredItem; createdItems.Add(monitoredItem); } // do any post processing. OnCreateMonitoredItemsComplete(systemContext, createdItems); } #region CreateMonitoredItem Support Functions /// /// Called when a batch of monitored items has been created. /// protected virtual void OnCreateMonitoredItemsComplete(ServerSystemContext context, IList monitoredItems) { // defined by the sub-class } /// /// Creates a new set of monitored items for a set of variables. /// /// /// This method only handles data change subscriptions. Event subscriptions are created by the SDK. /// protected virtual ServiceResult CreateMonitoredItem( ServerSystemContext context, NodeHandle handle, uint subscriptionId, double publishingInterval, DiagnosticsMasks diagnosticsMasks, TimestampsToReturn timestampsToReturn, MonitoredItemCreateRequest itemToCreate, ref long globalIdCounter, out MonitoringFilterResult filterResult, out IMonitoredItem monitoredItem) { filterResult = null; monitoredItem = null; // validate parameters. MonitoringParameters parameters = itemToCreate.RequestedParameters; // validate attribute. if (!Attributes.IsValid(handle.Node.NodeClass, itemToCreate.ItemToMonitor.AttributeId)) { return StatusCodes.BadAttributeIdInvalid; } NodeState cachedNode = AddNodeToComponentCache(context, handle, handle.Node); // check if the node is already being monitored. MonitoredNode2 monitoredNode = null; if (!m_monitoredNodes.TryGetValue(handle.Node.NodeId, out monitoredNode)) { m_monitoredNodes[handle.Node.NodeId] = monitoredNode = new MonitoredNode2(this, cachedNode); } handle.Node = monitoredNode.Node; handle.MonitoredNode = monitoredNode; // create a globally unique identifier. uint monitoredItemId = Utils.IncrementIdentifier(ref globalIdCounter); // determine the sampling interval. double samplingInterval = itemToCreate.RequestedParameters.SamplingInterval; if (samplingInterval < 0) { samplingInterval = publishingInterval; } // ensure minimum sampling interval is not exceeded. if (itemToCreate.ItemToMonitor.AttributeId == Attributes.Value) { BaseVariableState variable = handle.Node as BaseVariableState; if (variable != null && samplingInterval < variable.MinimumSamplingInterval) { samplingInterval = variable.MinimumSamplingInterval; } } // put a large upper limit on sampling. if (samplingInterval == Double.MaxValue) { samplingInterval = 365 * 24 * 3600 * 1000.0; } // put an upper limit on queue size. uint queueSize = itemToCreate.RequestedParameters.QueueSize; if (queueSize > m_maxQueueSize) { queueSize = m_maxQueueSize; } // validate the monitoring filter. Range euRange = null; MonitoringFilter filterToUse = null; ServiceResult error = ValidateMonitoringFilter( context, handle, itemToCreate.ItemToMonitor.AttributeId, samplingInterval, queueSize, parameters.Filter, out filterToUse, out euRange, out filterResult); if (ServiceResult.IsBad(error)) { return error; } // create the item. MonitoredItem datachangeItem = new MonitoredItem( Server, this, handle, subscriptionId, monitoredItemId, context.OperationContext.Session, itemToCreate.ItemToMonitor, diagnosticsMasks, timestampsToReturn, itemToCreate.MonitoringMode, itemToCreate.RequestedParameters.ClientHandle, filterToUse, filterToUse, euRange, samplingInterval, queueSize, itemToCreate.RequestedParameters.DiscardOldest, 0); // report the initial value. ReadInitialValue(context, handle, datachangeItem); // update monitored item list. monitoredItem = datachangeItem; // save the monitored item. m_monitoredItems.Add(monitoredItemId, datachangeItem); monitoredNode.Add(datachangeItem); // report change. OnMonitoredItemCreated(context, handle, datachangeItem); return error; } /// /// Reads the initial value for a monitored item. /// /// The context. /// The item handle. /// The monitored item. protected virtual void ReadInitialValue( ServerSystemContext context, NodeHandle handle, MonitoredItem monitoredItem) { DataValue initialValue = new DataValue(); initialValue.Value = null; initialValue.ServerTimestamp = DateTime.UtcNow; initialValue.SourceTimestamp = DateTime.MinValue; initialValue.StatusCode = StatusCodes.BadWaitingForInitialData; ServiceResult error = handle.Node.ReadAttribute( context, monitoredItem.AttributeId, monitoredItem.IndexRange, monitoredItem.DataEncoding, initialValue); monitoredItem.QueueValue(initialValue, error); } /// /// Called after creating a MonitoredItem. /// /// The context. /// The handle for the node. /// The monitored item. protected virtual void OnMonitoredItemCreated( ServerSystemContext context, NodeHandle handle, MonitoredItem monitoredItem) { // overridden by the sub-class. } /// /// Validates Role permissions for the specified NodeId /// /// /// /// /// public ServiceResult ValidateRolePermissions(OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission) { if (operationContext.Session == null || requestedPermission == PermissionType.None) { // no permission is required hence the validation passes. return StatusCodes.Good; } INodeManager nodeManager = null; object nodeHandle = Server.NodeManager.GetManagerHandle(nodeId, out nodeManager); if (nodeHandle == null || nodeManager == null) { // ignore unknown nodes. return StatusCodes.Good; } NodeMetadata nodeMetadata = nodeManager.GetNodeMetadata(operationContext, nodeHandle, BrowseResultMask.All); return MasterNodeManager.ValidateRolePermissions(operationContext, nodeMetadata, requestedPermission); } /// /// Validates the monitoring filter specified by the client. /// protected virtual StatusCode ValidateMonitoringFilter( ServerSystemContext context, NodeHandle handle, uint attributeId, double samplingInterval, uint queueSize, ExtensionObject filter, out MonitoringFilter filterToUse, out Range range, out MonitoringFilterResult result) { range = null; filterToUse = null; result = null; // nothing to do if the filter is not specified. if (ExtensionObject.IsNull(filter)) { return StatusCodes.Good; } // extension objects wrap any data structure. must check that the client provided the correct structure. DataChangeFilter deadbandFilter = ExtensionObject.ToEncodeable(filter) as DataChangeFilter; if (deadbandFilter == null) { AggregateFilter aggregateFilter = ExtensionObject.ToEncodeable(filter) as AggregateFilter; if (aggregateFilter == null || attributeId != Attributes.Value) { return StatusCodes.BadFilterNotAllowed; } if (!Server.AggregateManager.IsSupported(aggregateFilter.AggregateType)) { return StatusCodes.BadAggregateNotSupported; } ServerAggregateFilter revisedFilter = new ServerAggregateFilter(); revisedFilter.AggregateType = aggregateFilter.AggregateType; revisedFilter.StartTime = aggregateFilter.StartTime; revisedFilter.ProcessingInterval = aggregateFilter.ProcessingInterval; revisedFilter.AggregateConfiguration = aggregateFilter.AggregateConfiguration; revisedFilter.Stepped = false; StatusCode error = ReviseAggregateFilter(context, handle, samplingInterval, queueSize, revisedFilter); if (StatusCode.IsBad(error)) { return error; } AggregateFilterResult aggregateFilterResult = new AggregateFilterResult(); aggregateFilterResult.RevisedProcessingInterval = aggregateFilter.ProcessingInterval; aggregateFilterResult.RevisedStartTime = aggregateFilter.StartTime; aggregateFilterResult.RevisedAggregateConfiguration = aggregateFilter.AggregateConfiguration; filterToUse = revisedFilter; result = aggregateFilterResult; return StatusCodes.Good; } // deadband filters only allowed for variable values. if (attributeId != Attributes.Value) { return StatusCodes.BadFilterNotAllowed; } BaseVariableState variable = handle.Node as BaseVariableState; if (variable == null) { return StatusCodes.BadFilterNotAllowed; } // check for status filter. if (deadbandFilter.DeadbandType == (uint)DeadbandType.None) { filterToUse = deadbandFilter; return StatusCodes.Good; } // deadband filters can only be used for numeric values. if (!Server.TypeTree.IsTypeOf(variable.DataType, DataTypeIds.Number)) { return StatusCodes.BadFilterNotAllowed; } // nothing more to do for absolute filters. if (deadbandFilter.DeadbandType == (uint)DeadbandType.Absolute) { filterToUse = deadbandFilter; return StatusCodes.Good; } // need to look up the EU range if a percent filter is requested. if (deadbandFilter.DeadbandType == (uint)DeadbandType.Percent) { PropertyState property = handle.Node.FindChild(context, Opc.Ua.BrowseNames.EURange) as PropertyState; if (property == null) { return StatusCodes.BadMonitoredItemFilterUnsupported; } range = property.Value as Range; if (range == null) { return StatusCodes.BadMonitoredItemFilterUnsupported; } filterToUse = deadbandFilter; return StatusCodes.Good; } // no other type of filter supported. return StatusCodes.BadFilterNotAllowed; } /// /// Revises an aggregate filter (may require knowledge of the variable being used). /// /// The context. /// The handle. /// The sampling interval for the monitored item. /// The queue size for the monitored item. /// The filter to revise. /// Good if the protected virtual StatusCode ReviseAggregateFilter( ServerSystemContext context, NodeHandle handle, double samplingInterval, uint queueSize, ServerAggregateFilter filterToUse) { if (filterToUse.ProcessingInterval < samplingInterval) { filterToUse.ProcessingInterval = samplingInterval; } if (filterToUse.ProcessingInterval < Server.AggregateManager.MinimumProcessingInterval) { filterToUse.ProcessingInterval = Server.AggregateManager.MinimumProcessingInterval; } DateTime earliestStartTime = DateTime.UtcNow.AddMilliseconds(-(queueSize - 1) * filterToUse.ProcessingInterval); if (earliestStartTime > filterToUse.StartTime) { filterToUse.StartTime = earliestStartTime; } if (filterToUse.AggregateConfiguration.UseServerCapabilitiesDefaults) { filterToUse.AggregateConfiguration = Server.AggregateManager.GetDefaultConfiguration(null); } return StatusCodes.Good; } #endregion /// /// Modifies the parameters for a set of monitored items. /// public virtual void ModifyMonitoredItems( OperationContext context, TimestampsToReturn timestampsToReturn, IList monitoredItems, IList itemsToModify, IList errors, IList filterResults) { ServerSystemContext systemContext = m_systemContext.Copy(context); List modifiedItems = new List(); lock (Lock) { for (int ii = 0; ii < monitoredItems.Count; ii++) { MonitoredItemModifyRequest itemToModify = itemsToModify[ii]; // skip items that have already been processed. if (itemToModify.Processed || monitoredItems[ii] == null) { continue; } // check handle. NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); if (handle == null) { continue; } // owned by this node manager. itemToModify.Processed = true; // modify the monitored item. MonitoringFilterResult filterResult = null; errors[ii] = ModifyMonitoredItem( systemContext, context.DiagnosticsMask, timestampsToReturn, monitoredItems[ii], itemToModify, handle, out filterResult); // save any filter error details. filterResults[ii] = filterResult; // save the modified item. if (ServiceResult.IsGood(errors[ii])) { modifiedItems.Add(monitoredItems[ii]); } } } // do any post processing. OnModifyMonitoredItemsComplete(systemContext, modifiedItems); } #region ModifyMonitoredItem Support Functions /// /// Called when a batch of monitored items has been modified. /// protected virtual void OnModifyMonitoredItemsComplete(ServerSystemContext context, IList monitoredItems) { // defined by the sub-class } /// /// Modifies the parameters for a monitored item. /// protected virtual ServiceResult ModifyMonitoredItem( ServerSystemContext context, DiagnosticsMasks diagnosticsMasks, TimestampsToReturn timestampsToReturn, IMonitoredItem monitoredItem, MonitoredItemModifyRequest itemToModify, NodeHandle handle, out MonitoringFilterResult filterResult) { filterResult = null; // check for valid monitored item. MonitoredItem datachangeItem = monitoredItem as MonitoredItem; // validate parameters. MonitoringParameters parameters = itemToModify.RequestedParameters; double previousSamplingInterval = datachangeItem.SamplingInterval; // check if the variable needs to be sampled. double samplingInterval = itemToModify.RequestedParameters.SamplingInterval; if (samplingInterval < 0) { samplingInterval = previousSamplingInterval; } // ensure minimum sampling interval is not exceeded. if (datachangeItem.AttributeId == Attributes.Value) { BaseVariableState variable = handle.Node as BaseVariableState; if (variable != null && samplingInterval < variable.MinimumSamplingInterval) { samplingInterval = variable.MinimumSamplingInterval; } } // put a large upper limit on sampling. if (samplingInterval == Double.MaxValue) { samplingInterval = 365 * 24 * 3600 * 1000.0; } // put an upper limit on queue size. uint queueSize = itemToModify.RequestedParameters.QueueSize; if (queueSize > m_maxQueueSize) { queueSize = m_maxQueueSize; } // validate the monitoring filter. Range euRange = null; MonitoringFilter filterToUse = null; ServiceResult error = ValidateMonitoringFilter( context, handle, datachangeItem.AttributeId, samplingInterval, queueSize, parameters.Filter, out filterToUse, out euRange, out filterResult); if (ServiceResult.IsBad(error)) { return error; } // modify the monitored item parameters. error = datachangeItem.ModifyAttributes( diagnosticsMasks, timestampsToReturn, itemToModify.RequestedParameters.ClientHandle, filterToUse, filterToUse, euRange, samplingInterval, queueSize, itemToModify.RequestedParameters.DiscardOldest); // report change. if (ServiceResult.IsGood(error)) { OnMonitoredItemModified(context, handle, datachangeItem); } return error; } /// /// Called after modifying a MonitoredItem. /// /// The context. /// The handle for the node. /// The monitored item. protected virtual void OnMonitoredItemModified( ServerSystemContext context, NodeHandle handle, MonitoredItem monitoredItem) { // overridden by the sub-class. } #endregion /// /// Deletes a set of monitored items. /// public virtual void DeleteMonitoredItems( OperationContext context, IList monitoredItems, IList processedItems, IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); List deletedItems = new List(); lock (Lock) { for (int ii = 0; ii < monitoredItems.Count; ii++) { // skip items that have already been processed. if (processedItems[ii] || monitoredItems[ii] == null) { continue; } // check handle. NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); if (handle == null) { continue; } // owned by this node manager. processedItems[ii] = true; errors[ii] = DeleteMonitoredItem( systemContext, monitoredItems[ii], handle); // save the modified item. if (ServiceResult.IsGood(errors[ii])) { deletedItems.Add(monitoredItems[ii]); RemoveNodeFromComponentCache(systemContext, handle); } } } // do any post processing. OnDeleteMonitoredItemsComplete(systemContext, deletedItems); } #region DeleteMonitoredItems Support Functions /// /// Called when a batch of monitored items has been modified. /// protected virtual void OnDeleteMonitoredItemsComplete(ServerSystemContext context, IList monitoredItems) { // defined by the sub-class } /// /// Deletes a monitored item. /// protected virtual ServiceResult DeleteMonitoredItem( ServerSystemContext context, IMonitoredItem monitoredItem, NodeHandle handle) { // check for valid monitored item. MonitoredItem datachangeItem = monitoredItem as MonitoredItem; // check if the node is already being monitored. MonitoredNode2 monitoredNode = null; if (m_monitoredNodes.TryGetValue(handle.NodeId, out monitoredNode)) { monitoredNode.Remove(datachangeItem); // check if node is no longer being monitored. if (!monitoredNode.HasMonitoredItems) { MonitoredNodes.Remove(handle.NodeId); } } // remove the monitored item. m_monitoredItems.Remove(monitoredItem.Id); // report change. OnMonitoredItemDeleted(context, handle, datachangeItem); return ServiceResult.Good; } /// /// Called after deleting a MonitoredItem. /// /// The context. /// The handle for the node. /// The monitored item. protected virtual void OnMonitoredItemDeleted( ServerSystemContext context, NodeHandle handle, MonitoredItem monitoredItem) { // overridden by the sub-class. } #endregion /// /// Changes the monitoring mode for a set of monitored items. /// /// The context. /// The monitoring mode. /// The set of monitoring items to update. /// Flags indicating which items have been processed. /// Any errors. public virtual void SetMonitoringMode( OperationContext context, MonitoringMode monitoringMode, IList monitoredItems, IList processedItems, IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); List changedItems = new List(); lock (Lock) { for (int ii = 0; ii < monitoredItems.Count; ii++) { // skip items that have already been processed. if (processedItems[ii] || monitoredItems[ii] == null) { continue; } // check handle. NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); if (handle == null) { continue; } // indicate whether it was processed or not. processedItems[ii] = true; // update monitoring mode. errors[ii] = SetMonitoringMode( systemContext, monitoredItems[ii], monitoringMode, handle); // save the modified item. if (ServiceResult.IsGood(errors[ii])) { changedItems.Add(monitoredItems[ii]); } } } // do any post processing. OnSetMonitoringModeComplete(systemContext, changedItems); } #region SetMonitoringMode Support Functions /// /// Called when a batch of monitored items has their monitoring mode changed. /// protected virtual void OnSetMonitoringModeComplete(ServerSystemContext context, IList monitoredItems) { // defined by the sub-class } /// /// Changes the monitoring mode for an item. /// protected virtual ServiceResult SetMonitoringMode( ServerSystemContext context, IMonitoredItem monitoredItem, MonitoringMode monitoringMode, NodeHandle handle) { // check for valid monitored item. MonitoredItem datachangeItem = monitoredItem as MonitoredItem; // update monitoring mode. MonitoringMode previousMode = datachangeItem.SetMonitoringMode(monitoringMode); // must send the latest value after enabling a disabled item. if (monitoringMode == MonitoringMode.Reporting && previousMode == MonitoringMode.Disabled) { handle.MonitoredNode.QueueValue(context, handle.Node, datachangeItem); } // report change. if (previousMode != monitoringMode) { OnMonitoringModeChanged( context, handle, datachangeItem, previousMode, monitoringMode); } return ServiceResult.Good; } /// /// Called after changing the MonitoringMode for a MonitoredItem. /// /// The context. /// The handle for the node. /// The monitored item. /// The previous monitoring mode. /// The current monitoring mode. protected virtual void OnMonitoringModeChanged( ServerSystemContext context, NodeHandle handle, MonitoredItem monitoredItem, MonitoringMode previousMode, MonitoringMode monitoringMode) { // overridden by the sub-class. } #endregion #endregion #region INodeManager2 Members /// /// Called when a session is closed. /// public virtual void SessionClosing(OperationContext context, NodeId sessionId, bool deleteSubscriptions) { } /// /// Returns true if a node is in a view. /// public virtual bool IsNodeInView(OperationContext context, NodeId viewId, object nodeHandle) { NodeHandle handle = nodeHandle as NodeHandle; if (handle == null) { return false; } if (handle.Node != null) { return IsNodeInView(context, viewId, handle.Node); } return false; } #endregion #region ComponentCache Functions /// /// Stores a reference count for entries in the component cache. /// private class CacheEntry { public int RefCount; public NodeState Entry; } /// /// Looks up a component in cache. /// protected NodeState LookupNodeInComponentCache(ISystemContext context, NodeHandle handle) { lock (Lock) { if (m_componentCache == null) { return null; } CacheEntry entry = null; if (!String.IsNullOrEmpty(handle.ComponentPath)) { if (m_componentCache.TryGetValue(handle.RootId, out entry)) { return entry.Entry.FindChildBySymbolicName(context, handle.ComponentPath); } } else { if (m_componentCache.TryGetValue(handle.NodeId, out entry)) { return entry.Entry; } } return null; } } /// /// Removes a reference to a component in thecache. /// protected void RemoveNodeFromComponentCache(ISystemContext context, NodeHandle handle) { lock (Lock) { if (handle == null) { return; } if (m_componentCache != null) { NodeId nodeId = handle.NodeId; if (!String.IsNullOrEmpty(handle.ComponentPath)) { nodeId = handle.RootId; } CacheEntry entry = null; if (m_componentCache.TryGetValue(nodeId, out entry)) { entry.RefCount--; if (entry.RefCount == 0) { m_componentCache.Remove(nodeId); } } } } } /// /// Adds a node to the component cache. /// protected NodeState AddNodeToComponentCache(ISystemContext context, NodeHandle handle, NodeState node) { lock (Lock) { if (handle == null) { return node; } if (m_componentCache == null) { m_componentCache = new Dictionary(); } // check if a component is actually specified. if (!String.IsNullOrEmpty(handle.ComponentPath)) { CacheEntry entry = null; if (m_componentCache.TryGetValue(handle.RootId, out entry)) { entry.RefCount++; if (!String.IsNullOrEmpty(handle.ComponentPath)) { return entry.Entry.FindChildBySymbolicName(context, handle.ComponentPath); } return entry.Entry; } NodeState root = node.GetHierarchyRoot(); if (root != null) { entry = new CacheEntry(); entry.RefCount = 1; entry.Entry = root; m_componentCache.Add(handle.RootId, entry); } } // simply add the node to the cache. else { CacheEntry entry = null; if (m_componentCache.TryGetValue(handle.NodeId, out entry)) { entry.RefCount++; return entry.Entry; } entry = new CacheEntry(); entry.RefCount = 1; entry.Entry = node; m_componentCache.Add(handle.NodeId, entry); } return node; } } #endregion #region Private Fields private object m_lock = new object(); private IServerInternal m_server; private ServerSystemContext m_systemContext; private string[] m_namespaceUris; private ushort[] m_namespaceIndexes; private Dictionary m_monitoredItems; private Dictionary m_monitoredNodes; private Dictionary m_componentCache; private NodeIdDictionary m_predefinedNodes; private List m_rootNotifiers; private uint m_maxQueueSize; private string m_aliasRoot; #endregion } }