/* ========================================================================
* Copyright (c) 2005-2016 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Xml;
using System.IO;
using System.Threading;
using System.Reflection;
using Opc.Ua.Server;
namespace Opc.Ua.Sample
{
///
/// A node manager for a variety of test data.
///
public class SampleNodeManager : INodeManager, INodeIdFactory, IDisposable
{
#region Constructors
///
/// Initializes the node manager.
///
public SampleNodeManager(IServerInternal server)
{
// save a reference to the server that owns the node manager.
m_server = server;
// create the default context.
m_systemContext = m_server.DefaultSystemContext.Copy();
m_systemContext.SystemHandle = null;
m_systemContext.NodeIdFactory = this;
// create the table of nodes.
m_predefinedNodes = new NodeIdDictionary();
m_rootNotifiers = new List();
m_sampledItems = new List();
m_minimumSamplingInterval = 100;
}
#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)
{
Utils.SilentDispose(m_samplingTimer);
m_samplingTimer = null;
foreach (NodeState node in m_predefinedNodes.Values)
{
Utils.SilentDispose(node);
}
}
}
}
#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; }
}
#endregion
#region Protected Members
///
/// The server that the node manager belongs to.
///
protected IServerInternal Server
{
get { return m_server; }
}
///
/// The default context to use.
///
protected ServerSystemContext SystemContext
{
get { return m_systemContext; }
}
///
/// 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; }
}
///
/// 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)
{
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 NodeState IsHandleInNamespace(object managerHandle)
{
NodeState source = managerHandle as NodeState;
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)
{
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)
{
instance.ReferenceTypeId = referenceTypeId;
NodeState parent = null;
if (parentId != null)
{
if (!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)
{
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;
}
#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)
{
m_namespaceUris = new List(value);
}
else
{
m_namespaceUris = new List();
}
m_namespaceIndexes = new ushort[m_namespaceUris.Count];
}
}
///
/// 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)
{
lock (Lock)
{
// add the uris to the server's namespace table and cache the indexes.
for (int ii = 0; ii < m_namespaceUris.Count; ii++)
{
m_namespaceIndexes[ii] = m_server.NamespaceUris.GetIndexOrAppend(m_namespaceUris[ii]);
}
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)
{
// 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 refernces 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 refernces 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)
{
NodeState activeNode = AddBehaviourToPredefinedNode(context, node);
m_predefinedNodes[activeNode.NodeId] = activeNode;
BaseTypeState type = activeNode as BaseTypeState;
if (type != null)
{
AddTypesToTypeTree(type);
}
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)
{
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.
}
///
/// Add the node to the set of root notifiers.
///
protected virtual void AddRootNotifier(NodeState notifier)
{
for (int ii = 0; ii < m_rootNotifiers.Count; ii++)
{
if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii]))
{
return;
}
}
m_rootNotifiers.Add(notifier);
// 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)
{
SubscribeToAllEvents(
SystemContext,
monitoredItems[ii],
true,
notifier);
}
}
}
}
///
/// Remove the node from the set of root notifiers.
///
protected virtual void RemoveRootNotifier(NodeState notifier)
{
for (int ii = 0; ii < m_rootNotifiers.Count; ii++)
{
if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii]))
{
m_rootNotifiers.RemoveAt(ii);
break;
}
}
}
///
/// Ensures that all reverse references exist.
///
/// A list of references to add to external targets.
protected virtual void AddReverseReferences(IDictionary> externalReferences)
{
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);
}
// add reference from supertype for type nodes.
/*
BaseTypeState type = source as BaseTypeState;
if (type != null && !NodeId.IsNull(type.SuperTypeId))
{
if (!IsNodeIdInNamespace(type.SuperTypeId))
{
AddExternalReference(
type.SuperTypeId,
ReferenceTypeIds.HasSubtype,
false,
type.NodeId,
externalReferences);
}
}
*/
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;
}
NodeId targetId = (NodeId)reference.TargetId;
// 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)
{
BaseTypeState type = Find(typeId) 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 (Lock)
{
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.
///
///
/// 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'.
///
protected virtual object GetManagerHandle(ISystemContext context, NodeId nodeId, IDictionary cache)
{
lock (Lock)
{
// quickly exclude nodes that not in the namespace.
if (!IsNodeIdInNamespace(nodeId))
{
return null;
}
// lookup the node.
NodeState node = null;
if (!m_predefinedNodes.TryGetValue(nodeId, out node))
{
return null;
}
return node;
}
}
///
/// 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)
{
// check for valid handle.
NodeState source = GetManagerHandle(m_systemContext, current.Key, null) as NodeState;
if (source == null)
{
return;
}
// add reference to external target.
foreach (IReference reference in current.Value)
{
source.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)
{
// check for valid handle.
NodeState source = IsHandleInNamespace(sourceHandle);
if (source == null)
{
return StatusCodes.BadNodeIdUnknown;
}
source.RemoveReference(referenceTypeId, isInverse, targetId);
if (deleteBiDirectional)
{
// check if the target is also managed by the node manager.
if (!targetId.IsAbsolute)
{
NodeState target = GetManagerHandle(m_systemContext, (NodeId)targetId, null) as NodeState;
if (target != null)
{
target.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.
NodeState target = IsHandleInNamespace(targetHandle);
if (target == null)
{
return null;
}
// validate node.
if (!ValidateNode(systemContext, target))
{
return null;
}
// read the attributes.
List