/* ======================================================================== * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Text; using System.Reflection; namespace Opc.Ua.Client { /// /// A client side cache of the server's type model. /// public class NodeCache : INodeTable, ITypeTable { #region Constructors /// /// Initializes the object with default values. /// public NodeCache(Session session) { if (session == null) throw new ArgumentNullException(nameof(session)); m_session = session; m_typeTree = new TypeTable(m_session.NamespaceUris); m_nodes = new NodeTable(m_session.NamespaceUris, m_session.ServerUris, m_typeTree); } #endregion #region INodeTable Members /// public NamespaceTable NamespaceUris { get { return m_session.NamespaceUris; } } /// public StringTable ServerUris { get { return m_session.ServerUris; } } /// public ITypeTable TypeTree { get { return this; } } /// public bool Exists(ExpandedNodeId nodeId) { return Find(nodeId) != null; } /// public INode Find(ExpandedNodeId nodeId) { // check for null. if (NodeId.IsNull(nodeId)) { return null; } // check if node alredy exists. INode node = m_nodes.Find(nodeId); if (node != null) { // do not return temporary nodes created after a Browse(). if (node.GetType() != typeof(Node)) { return node; } } // fetch node from server. try { return FetchNode(nodeId); } catch (Exception e) { Utils.Trace("Could not fetch node from server: NodeId={0}, Reason='{1}'.", nodeId, e.Message); // m_nodes[nodeId] = null; return null; } } /// public INode Find( ExpandedNodeId sourceId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes, QualifiedName browseName) { // find the source. Node source = Find(sourceId) as Node; if (source == null) { return null; } // find all references. IList references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); foreach (IReference reference in references) { INode target = Find(reference.TargetId); if (target == null) { continue; } if (target.BrowseName == browseName) { return target; } } // target not found. return null; } /// public IList Find( ExpandedNodeId sourceId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes) { List hits = new List(); // find the source. Node source = Find(sourceId) as Node; if (source == null) { return hits; } // find all references. IList references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); foreach (IReference reference in references) { INode target = Find(reference.TargetId); if (target == null) { continue; } hits.Add(target); } return hits; } #endregion #region ITypeTable Methods /// /// Determines whether a node id is a known type id. /// /// The type extended identifier. /// /// true if the specified type id is known; otherwise, false. /// public bool IsKnown(ExpandedNodeId typeId) { INode type = Find(typeId); if (type == null) { return false; } return m_typeTree.IsKnown(typeId); } /// /// Determines whether a node id is a known type id. /// /// The type identifier. /// /// true if the specified type id is known; otherwise, false. /// public bool IsKnown(NodeId typeId) { INode type = Find(typeId); if (type == null) { return false; } return m_typeTree.IsKnown(typeId); } /// /// Returns the immediate supertype for the type. /// /// The extended type identifier. /// /// A type identifier of the /// public NodeId FindSuperType(ExpandedNodeId typeId) { INode type = Find(typeId); if (type == null) { return null; } return m_typeTree.FindSuperType(typeId); } /// /// Returns the immediate supertype for the type. /// /// The type identifier. /// /// The immediate supertype idnetyfier for /// public NodeId FindSuperType(NodeId typeId) { INode type = Find(typeId); if (type == null) { return null; } return m_typeTree.FindSuperType(typeId); } /// /// Returns the immediate subtypes for the type. /// /// The extended type identifier. /// /// List of type identifiers for /// public IList FindSubTypes(ExpandedNodeId typeId) { ILocalNode type = Find(typeId) as ILocalNode; if (type == null) { return new List(); } List subtypes = new List(); foreach (IReference reference in type.References.Find(ReferenceTypeIds.HasSubtype, false, true, m_typeTree)) { if (!reference.TargetId.IsAbsolute) { subtypes.Add((NodeId)reference.TargetId); } } return subtypes; } /// /// Determines whether a type is a subtype of another type. /// /// The subtype identifier. /// The supertype identifier. /// /// true if is supertype of ; otherwise, false. /// public bool IsTypeOf(ExpandedNodeId subTypeId, ExpandedNodeId superTypeId) { if (subTypeId == superTypeId) { return true; } ILocalNode subtype = Find(subTypeId) as ILocalNode; if (subtype == null) { return false; } ILocalNode supertype = subtype; while (supertype != null) { ExpandedNodeId currentId = supertype.References.FindTarget(ReferenceTypeIds.HasSubtype, true, true, m_typeTree, 0); if (currentId == superTypeId) { return true; } supertype = Find(currentId) as ILocalNode; } return false; } /// /// Determines whether a type is a subtype of another type. /// /// The subtype identifier. /// The supertype identyfier. /// /// true if is supertype of ; otherwise, false. /// public bool IsTypeOf(NodeId subTypeId, NodeId superTypeId) { if (subTypeId == superTypeId) { return true; } ILocalNode subtype = Find(subTypeId) as ILocalNode; if (subtype == null) { return false; } ILocalNode supertype = subtype; while (supertype != null) { ExpandedNodeId currentId = supertype.References.FindTarget(ReferenceTypeIds.HasSubtype, true, true, m_typeTree, 0); if (currentId == superTypeId) { return true; } supertype = Find(currentId) as ILocalNode; } return false; } /// /// Returns the qualified name for the reference type id. /// /// The reference type /// /// A name qualified with a namespace for the reference . /// public QualifiedName FindReferenceTypeName(NodeId referenceTypeId) { return m_typeTree.FindReferenceTypeName(referenceTypeId); } /// /// Returns the node identifier for the reference type with the specified browse name. /// /// Browse name of the reference. /// /// The identifier for the /// public NodeId FindReferenceType(QualifiedName browseName) { return m_typeTree.FindReferenceType(browseName); } /// /// Checks if the identifier represents a that provides encodings /// for the . /// /// The id the encoding node . /// The id of the DataType node. /// /// true if is encoding of the ; otherwise, false. /// public bool IsEncodingOf(ExpandedNodeId encodingId, ExpandedNodeId datatypeId) { ILocalNode encoding = Find(encodingId) as ILocalNode; if (encoding == null) { return false; } foreach (IReference reference in encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree)) { if (reference.TargetId == datatypeId) { return true; } } // no match. return false; } /// /// Determines if the value contained in an extension object matches the expected data type. /// /// The identifier of the expected type . /// The value. /// /// true if the value contained in an extension object matches the /// expected data type; otherwise, false. /// public bool IsEncodingFor(NodeId expectedTypeId, ExtensionObject value) { // no match on null values. if (value == null) { return false; } // check for exact match. if (expectedTypeId == value.TypeId) { return true; } // find the encoding. ILocalNode encoding = Find(value.TypeId) as ILocalNode; if (encoding == null) { return false; } // find data type. foreach (IReference reference in encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree)) { if (reference.TargetId == expectedTypeId) { return true; } } // no match. return false; } /// /// Determines if the value is an encoding of the /// /// The expected type id. /// The value. /// /// true the value is an encoding of the ; otherwise, false. /// public bool IsEncodingFor(NodeId expectedTypeId, object value) { // null actual datatype matches nothing. if (value == null) { return false; } // null expected datatype matches everything. if (NodeId.IsNull(expectedTypeId)) { return true; } // get the actual datatype. NodeId actualTypeId = TypeInfo.GetDataTypeId(value); // value is valid if the expected datatype is same as or a supertype of the actual datatype // for example: expected datatype of 'Integer' matches an actual datatype of 'UInt32'. if (IsTypeOf(actualTypeId, expectedTypeId)) { return true; } // allow matches non-structure values where the actual datatype is a supertype of the expected datatype. // for example: expected datatype of 'UtcTime' matches an actual datatype of 'DateTime'. if (actualTypeId != DataTypes.Structure) { return IsTypeOf(expectedTypeId, actualTypeId); } // for structure types must try to determine the subtype. ExtensionObject extension = value as ExtensionObject; if (extension != null) { return IsEncodingFor(expectedTypeId, extension); } // every element in an array must match. ExtensionObject[] extensions = value as ExtensionObject[]; if (extensions != null) { for (int ii = 0; ii < extensions.Length; ii++) { if (!IsEncodingFor(expectedTypeId, extensions[ii])) { return false; } } return true; } // can only get here if the value is an unrecognized data type. return false; } /// /// Returns the data type for the specified encoding. /// /// The encoding id. /// public NodeId FindDataTypeId(ExpandedNodeId encodingId) { ILocalNode encoding = Find(encodingId) as ILocalNode; if (encoding == null) { return NodeId.Null; } IList references = encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree); if (references.Count > 0) { return ExpandedNodeId.ToNodeId(references[0].TargetId, m_session.NamespaceUris); } return NodeId.Null; } /// /// Returns the data type for the specified encoding. /// /// The encoding id. /// /// The data type for the /// public NodeId FindDataTypeId(NodeId encodingId) { ILocalNode encoding = Find(encodingId) as ILocalNode; if (encoding == null) { return NodeId.Null; } IList references = encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree); if (references.Count > 0) { return ExpandedNodeId.ToNodeId(references[0].TargetId, m_session.NamespaceUris); } return NodeId.Null; } #endregion #region Public Methods /// /// Loads the UA defined types into the cache. /// /// The context. public void LoadUaDefinedTypes(ISystemContext context) { NodeStateCollection predefinedNodes = new NodeStateCollection(); var assembly = typeof(ArgumentCollection).GetTypeInfo().Assembly; predefinedNodes.LoadFromBinaryResource(context, "Opc.Ua.Stack.Generated.Opc.Ua.PredefinedNodes.uanodes", assembly, true); for (int ii = 0; ii < predefinedNodes.Count; ii++) { BaseTypeState type = predefinedNodes[ii] as BaseTypeState; if (type == null) { continue; } type.Export(context, m_nodes); } } /// /// Removes all nodes from the cache. /// public void Clear() { m_nodes.Clear(); } /// /// Fetches a node from the server and updates the cache. /// public Node FetchNode(ExpandedNodeId nodeId) { NodeId localId = ExpandedNodeId.ToNodeId(nodeId, m_session.NamespaceUris); if (localId == null) { return null; } // fetch node from server. Node source = m_session.ReadNode(localId); try { // fetch references from server. ReferenceDescriptionCollection references = m_session.FetchReferences(localId); foreach (ReferenceDescription reference in references) { // create a placeholder for the node if it does not already exist. if (!m_nodes.Exists(reference.NodeId)) { // transform absolute identifiers. if (reference.NodeId != null && reference.NodeId.IsAbsolute) { reference.NodeId = ExpandedNodeId.ToNodeId(reference.NodeId, NamespaceUris); } Node target = new Node(reference); m_nodes.Attach(target); } // add the reference. source.ReferenceTable.Add(reference.ReferenceTypeId, !reference.IsForward, reference.NodeId); } } catch (Exception e) { Utils.Trace("Could not fetch references for valid node with NodeId = {0}. Error = {1}", nodeId, e.Message); } // add to cache. m_nodes.Attach(source); return source; } /// /// Adds the supertypes of the node to the cache. /// public void FetchSuperTypes(ExpandedNodeId nodeId) { // find the target node, ILocalNode source = Find(nodeId) as ILocalNode; if (source == null) { return; } // follow the tree. ILocalNode subType = source; while (subType != null) { ILocalNode superType = null; IList references = subType.References.Find(ReferenceTypeIds.HasSubtype, true, true, this); if (references != null && references.Count > 0) { superType = Find(references[0].TargetId) as ILocalNode; } subType = superType; } } /// /// Returns the references of the specified node that meet the criteria specified. /// public IList FindReferences( ExpandedNodeId nodeId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes) { IList targets = new List(); Node source = Find(nodeId) as Node; if (source == null) { return targets; } IList references = source.ReferenceTable.Find( referenceTypeId, isInverse, includeSubtypes, m_typeTree); foreach (IReference reference in references) { INode target = Find(reference.TargetId); if (target != null) { targets.Add(target); } } return targets; } /// /// Returns a display name for a node. /// public string GetDisplayText(INode node) { // check for null. if (node == null) { return String.Empty; } // check for remote node. Node target = node as Node; if (target == null) { return node.ToString(); } string displayText = null; // use the modelling rule to determine which parent to follow. NodeId modellingRule = target.ModellingRule; foreach (IReference reference in target.ReferenceTable.Find(ReferenceTypeIds.Aggregates, true, true, m_typeTree)) { Node parent = Find(reference.TargetId) as Node; // use the first parent if modelling rule is new. if (modellingRule == Objects.ModellingRule_Mandatory) { displayText = GetDisplayText(parent); break; } // use the type node as the parent for other modelling rules. if (parent is VariableTypeNode || parent is ObjectTypeNode) { displayText = GetDisplayText(parent); break; } } // prepend the parent display name. if (displayText != null) { return Utils.Format("{0}.{1}", displayText, node); } // simply use the node name. return node.ToString(); } /// /// Returns a display name for a node. /// public string GetDisplayText(ExpandedNodeId nodeId) { if (NodeId.IsNull(nodeId)) { return String.Empty; } INode node = Find(nodeId); if (node != null) { return GetDisplayText(node); } return Utils.Format("{0}", nodeId); } /// /// Returns a display name for the target of a reference. /// public string GetDisplayText(ReferenceDescription reference) { if (reference == null || NodeId.IsNull(reference.NodeId)) { return String.Empty; } INode node = Find(reference.NodeId); if (node != null) { return GetDisplayText(node); } return reference.ToString(); } /// /// Builds the relative path from a type to a node. /// public NodeId BuildBrowsePath(ILocalNode node, IList browsePath) { NodeId typeId = null; browsePath.Add(node.BrowseName); return typeId; } #endregion #region Private Fields private Session m_session; private TypeTable m_typeTree; private NodeTable m_nodes; #endregion } }