| 1 | // Copyright (C) 2018 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include <private/universalnode_p.h> |
| 5 | #include <private/opcuanodeidtype_p.h> |
| 6 | |
| 7 | #include <QtOpcUa/qopcuaclient.h> |
| 8 | #include <QLoggingCategory> |
| 9 | |
| 10 | QT_BEGIN_NAMESPACE |
| 11 | |
| 12 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML) |
| 13 | |
| 14 | UniversalNode::UniversalNode(QObject *parent) |
| 15 | : QObject(parent) |
| 16 | { |
| 17 | } |
| 18 | |
| 19 | UniversalNode::UniversalNode() |
| 20 | :QObject(nullptr) |
| 21 | { |
| 22 | } |
| 23 | |
| 24 | UniversalNode::UniversalNode(const QString &nodeIdentifier, QObject *parent) |
| 25 | : QObject(parent) |
| 26 | { |
| 27 | setNodeIdentifier(nodeIdentifier); |
| 28 | } |
| 29 | |
| 30 | UniversalNode::UniversalNode(const QString &namespaceName, const QString &nodeIdentifier, QObject *parent) |
| 31 | : QObject(parent) |
| 32 | { |
| 33 | setMembers(setNamespaceIndex: false, namespaceIndex: 0, setNamespaceName: true, namespaceName, setNodeIdentifier: true, nodeIdentifier); |
| 34 | } |
| 35 | |
| 36 | UniversalNode::UniversalNode(quint16 namespaceIndex, const QString &nodeIdentifier, QObject *parent) |
| 37 | : QObject(parent) |
| 38 | { |
| 39 | setMembers(setNamespaceIndex: true, namespaceIndex, setNamespaceName: false, namespaceName: QString(), setNodeIdentifier: true, nodeIdentifier); |
| 40 | } |
| 41 | |
| 42 | UniversalNode::UniversalNode(const UniversalNode &other, QObject *parent) |
| 43 | : QObject(parent) |
| 44 | { |
| 45 | setMembers(setNamespaceIndex: other.isNamespaceNameValid(), namespaceIndex: other.namespaceIndex(), |
| 46 | setNamespaceName: !other.namespaceName().isEmpty(), namespaceName: other.namespaceName(), |
| 47 | setNodeIdentifier: !other.nodeIdentifier().isEmpty(), nodeIdentifier: other.nodeIdentifier()); |
| 48 | } |
| 49 | |
| 50 | UniversalNode::UniversalNode(const OpcUaNodeIdType *other, QObject *parent) |
| 51 | : QObject(parent) |
| 52 | { |
| 53 | if (other) |
| 54 | from(*other); |
| 55 | } |
| 56 | |
| 57 | const QString &UniversalNode::namespaceName() const |
| 58 | { |
| 59 | return m_namespaceName; |
| 60 | } |
| 61 | |
| 62 | void UniversalNode::setNamespace(const QString &namespaceName) |
| 63 | { |
| 64 | bool ok; |
| 65 | int index = namespaceName.toUInt(ok: &ok); |
| 66 | |
| 67 | setMembers(setNamespaceIndex: ok, namespaceIndex: index, setNamespaceName: !ok, namespaceName, setNodeIdentifier: false, nodeIdentifier: QString()); |
| 68 | } |
| 69 | |
| 70 | quint16 UniversalNode::namespaceIndex() const |
| 71 | { |
| 72 | return m_namespaceIndex; |
| 73 | } |
| 74 | |
| 75 | void UniversalNode::setNamespace(quint16 namespaceIndex) |
| 76 | { |
| 77 | setMembers(setNamespaceIndex: true, namespaceIndex, setNamespaceName: false, namespaceName: QString(), setNodeIdentifier: false, nodeIdentifier: QString()); |
| 78 | } |
| 79 | |
| 80 | const QString &UniversalNode::nodeIdentifier() const |
| 81 | { |
| 82 | return m_nodeIdentifier; |
| 83 | } |
| 84 | |
| 85 | void UniversalNode::setNodeIdentifier(const QString &nodeIdentifier) |
| 86 | { |
| 87 | int index = 0; |
| 88 | QString name; |
| 89 | |
| 90 | if (splitNodeIdAndNamespace(nodeIdentifier, namespaceIndex: &index, identifier: &name)) { |
| 91 | setMembers(setNamespaceIndex: true, namespaceIndex: index, setNamespaceName: false, namespaceName: QString(), setNodeIdentifier: true, nodeIdentifier: name); |
| 92 | } else { |
| 93 | setMembers(setNamespaceIndex: false, namespaceIndex: 0, setNamespaceName: false, namespaceName: QString(), setNodeIdentifier: true, nodeIdentifier); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | void UniversalNode::resolveNamespace(QOpcUaClient *client) |
| 98 | { |
| 99 | if (!m_namespaceIndexValid) |
| 100 | resolveNamespaceNameToIndex(client); |
| 101 | else if (m_namespaceName.isEmpty()) |
| 102 | resolveNamespaceIndexToName(client); |
| 103 | } |
| 104 | |
| 105 | void UniversalNode::resolveNamespaceIndexToName(QOpcUaClient *client) |
| 106 | { |
| 107 | if (!m_namespaceIndexValid) { |
| 108 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Could not resolve namespace: Namespace index is not valid" ; |
| 109 | return; |
| 110 | } |
| 111 | |
| 112 | const auto namespaceArray = client->namespaceArray(); |
| 113 | |
| 114 | if (!namespaceArray.size()) { |
| 115 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Namespaces table missing, unable to resolve namespace name." ; |
| 116 | return; |
| 117 | } |
| 118 | |
| 119 | if (m_namespaceIndex >= namespaceArray.size()) { |
| 120 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Namespace index not in a valid range" ; |
| 121 | return; |
| 122 | } |
| 123 | |
| 124 | setMembers(setNamespaceIndex: true, namespaceIndex: m_namespaceIndex, setNamespaceName: true, namespaceName: namespaceArray.at(i: m_namespaceIndex), setNodeIdentifier: false, nodeIdentifier: QString()); |
| 125 | } |
| 126 | |
| 127 | void UniversalNode::resolveNamespaceNameToIndex(QOpcUaClient *client) |
| 128 | { |
| 129 | if (m_namespaceIndexValid) |
| 130 | return; // Namespace index already resolved, nothing to do |
| 131 | |
| 132 | int index = resolveNamespaceNameToIndex(namespaceName: m_namespaceName, client); |
| 133 | if (index < 0) { |
| 134 | qCWarning(QT_OPCUA_PLUGINS_QML) |
| 135 | << "Could not resolve namespace for node" |
| 136 | << (m_nodeIdentifier.isEmpty() ? QString() : (u'(' + m_nodeIdentifier + u')')); |
| 137 | return; |
| 138 | } |
| 139 | setMembers(setNamespaceIndex: true, namespaceIndex: index, setNamespaceName: true, namespaceName: m_namespaceName, setNodeIdentifier: false, nodeIdentifier: QString()); |
| 140 | } |
| 141 | |
| 142 | int UniversalNode::resolveNamespaceNameToIndex(const QString &namespaceName, QOpcUaClient *client) |
| 143 | { |
| 144 | const auto namespaceArray = client->namespaceArray(); |
| 145 | |
| 146 | if (!namespaceArray.size()) { |
| 147 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Namespaces table missing, unable to resolve namespace name." ; |
| 148 | return -1; |
| 149 | } |
| 150 | |
| 151 | if (namespaceName.isEmpty()) { |
| 152 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Could not resolve namespace: Namespace name is empty" ; |
| 153 | return -1; |
| 154 | } |
| 155 | |
| 156 | int index = namespaceArray.indexOf(str: namespaceName); |
| 157 | if (index < 0) { |
| 158 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Could not resolve namespace: Namespace" << namespaceName << "not found in" << namespaceArray; |
| 159 | return -1; |
| 160 | } |
| 161 | |
| 162 | return index; |
| 163 | } |
| 164 | |
| 165 | bool UniversalNode::isNamespaceNameValid() const |
| 166 | { |
| 167 | return !m_namespaceName.isEmpty(); |
| 168 | } |
| 169 | |
| 170 | bool UniversalNode::isNamespaceIndexValid() const |
| 171 | { |
| 172 | return m_namespaceIndexValid; |
| 173 | } |
| 174 | |
| 175 | QOpcUaQualifiedName UniversalNode::toQualifiedName() const |
| 176 | { |
| 177 | QOpcUaQualifiedName qualifiedName; |
| 178 | |
| 179 | if (!m_namespaceIndexValid || m_nodeIdentifier.isEmpty()) { |
| 180 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Insufficient information to create a QOpcUaQualifiedName" ; |
| 181 | return qualifiedName; |
| 182 | } |
| 183 | |
| 184 | qualifiedName.setNamespaceIndex(m_namespaceIndex); |
| 185 | qualifiedName.setName(m_nodeIdentifier); |
| 186 | return qualifiedName; |
| 187 | } |
| 188 | |
| 189 | QOpcUaExpandedNodeId UniversalNode::toExpandedNodeId() const |
| 190 | { |
| 191 | QOpcUaExpandedNodeId expandedNodeId; |
| 192 | |
| 193 | if (m_namespaceName.isEmpty() || m_nodeIdentifier.isEmpty()) { |
| 194 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Insufficient information to create a QOpcUaExpandedNodeId" ; |
| 195 | return expandedNodeId; |
| 196 | } |
| 197 | |
| 198 | expandedNodeId.setServerIndex(0); |
| 199 | expandedNodeId.setNamespaceUri(m_namespaceName); |
| 200 | expandedNodeId.setNodeId(m_nodeIdentifier); |
| 201 | return expandedNodeId; |
| 202 | } |
| 203 | |
| 204 | void UniversalNode::from(const QOpcUaQualifiedName &qualifiedName) |
| 205 | { |
| 206 | setMembers(setNamespaceIndex: true, namespaceIndex: qualifiedName.namespaceIndex(), setNamespaceName: false, namespaceName: QString(), setNodeIdentifier: true, nodeIdentifier: qualifiedName.name()); |
| 207 | } |
| 208 | |
| 209 | void UniversalNode::from(const QOpcUaExpandedNodeId &expandedNodeId) |
| 210 | { |
| 211 | setMembers(setNamespaceIndex: false, namespaceIndex: 0, setNamespaceName: true, namespaceName: expandedNodeId.namespaceUri(), setNodeIdentifier: true, nodeIdentifier: expandedNodeId.nodeId()); |
| 212 | } |
| 213 | |
| 214 | void UniversalNode::from(const QOpcUaBrowsePathTarget &browsePathTarget) |
| 215 | { |
| 216 | // QExpandedNodeId is too unreliable and needs some casehandling around it to get a common information |
| 217 | int index = 0; |
| 218 | QString namespaceName = browsePathTarget.targetId().namespaceUri(); |
| 219 | QString identifier; |
| 220 | bool namespaceIndexValid = splitNodeIdAndNamespace(nodeIdentifier: browsePathTarget.targetId().nodeId(), namespaceIndex: &index, identifier: &identifier); |
| 221 | |
| 222 | setMembers(setNamespaceIndex: namespaceIndexValid, namespaceIndex: index, setNamespaceName: !namespaceName.isEmpty(), namespaceName, setNodeIdentifier: true, nodeIdentifier: identifier); |
| 223 | } |
| 224 | |
| 225 | void UniversalNode::from(const OpcUaNodeIdType *other) { |
| 226 | from(*other); |
| 227 | } |
| 228 | |
| 229 | void UniversalNode::from(const OpcUaNodeIdType &other) |
| 230 | { |
| 231 | setMembers(setNamespaceIndex: other.m_universalNode.m_namespaceIndexValid, namespaceIndex: other.m_universalNode.m_namespaceIndex, |
| 232 | setNamespaceName: !other.nodeNamespace().isEmpty(), namespaceName: other.nodeNamespace(), |
| 233 | setNodeIdentifier: !other.identifier().isEmpty(), nodeIdentifier: other.identifier()); |
| 234 | } |
| 235 | |
| 236 | void UniversalNode::from(const UniversalNode &other) |
| 237 | { |
| 238 | setMembers(setNamespaceIndex: other.isNamespaceIndexValid(), namespaceIndex: other.namespaceIndex(), |
| 239 | setNamespaceName: true, namespaceName: other.namespaceName(), setNodeIdentifier: true, nodeIdentifier: other.nodeIdentifier()); |
| 240 | } |
| 241 | |
| 242 | QString UniversalNode::fullNodeId() const |
| 243 | { |
| 244 | if (!m_namespaceIndexValid || m_nodeIdentifier.isEmpty()) { |
| 245 | QString message = QStringLiteral("Unable to construct a full node id" ); |
| 246 | if (!m_nodeIdentifier.isEmpty()) |
| 247 | message += QStringLiteral(" for node " ) + m_nodeIdentifier; |
| 248 | else |
| 249 | message += QStringLiteral(" because node id string is empty." ); |
| 250 | |
| 251 | if (!m_namespaceIndexValid) |
| 252 | message += QStringLiteral("; namespace index is not valid." ); |
| 253 | qCWarning(QT_OPCUA_PLUGINS_QML) << message; |
| 254 | return QString(); |
| 255 | } |
| 256 | |
| 257 | return createNodeString(namespaceIndex: m_namespaceIndex, nodeIdentifier: m_nodeIdentifier); |
| 258 | } |
| 259 | |
| 260 | QOpcUaNode *UniversalNode::createNode(QOpcUaClient *client) |
| 261 | { |
| 262 | return client->node(nodeId: fullNodeId()); |
| 263 | } |
| 264 | |
| 265 | UniversalNode &UniversalNode::operator=(const UniversalNode &rhs) |
| 266 | { |
| 267 | m_namespaceName = rhs.m_namespaceName; |
| 268 | m_nodeIdentifier = rhs.m_nodeIdentifier; |
| 269 | m_namespaceIndex = rhs.m_namespaceIndex; |
| 270 | m_namespaceIndexValid = rhs.m_namespaceIndexValid; |
| 271 | return *this; |
| 272 | } |
| 273 | |
| 274 | /* This function sets the members to the desired values and emits changes signal only once after all variables |
| 275 | * have already been set. |
| 276 | * If the namespace index or name changes without setting the counterpart as well it will invalidate |
| 277 | * not part not being set. |
| 278 | */ |
| 279 | void UniversalNode::setMembers(bool setNamespaceIndex, quint16 namespaceIndex, |
| 280 | bool setNamespaceName, const QString &namespaceName, |
| 281 | bool setNodeIdentifier, const QString &nodeIdentifier) |
| 282 | { |
| 283 | // qCDebug(QT_OPCUA_PLUGINS_QML) << Q_FUNC_INFO << setNamespaceIndex << namespaceIndex << setNamespaceName << namespaceName << setNodeIdentifier << nodeIdentifier; |
| 284 | bool emitNamespaceIndexChanged = false; |
| 285 | bool emitNamespaceNameChanged = false; |
| 286 | bool emitNodeIdentifierChanged = false; |
| 287 | |
| 288 | if (setNamespaceIndex && (namespaceIndex != m_namespaceIndex || !m_namespaceIndexValid)) { |
| 289 | m_namespaceIndex = namespaceIndex; |
| 290 | m_namespaceIndexValid = true; |
| 291 | emitNamespaceIndexChanged = true; |
| 292 | |
| 293 | if (!setNamespaceName) // Index changed without name given: invalidate name |
| 294 | m_namespaceName.clear(); |
| 295 | } |
| 296 | |
| 297 | if (setNamespaceName && namespaceName != m_namespaceName) { |
| 298 | m_namespaceName = namespaceName; |
| 299 | emitNamespaceNameChanged = true; |
| 300 | |
| 301 | if (!setNamespaceIndex) // Name changed without index given: invalidate index |
| 302 | m_namespaceIndexValid = false; |
| 303 | } |
| 304 | |
| 305 | if (setNodeIdentifier && nodeIdentifier != m_nodeIdentifier) { |
| 306 | if (nodeIdentifier.startsWith(s: QLatin1String("ns=" ))) |
| 307 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Setting node identifier with namespace internally is not allowed." ; |
| 308 | |
| 309 | m_nodeIdentifier = nodeIdentifier; |
| 310 | emitNodeIdentifierChanged = true; |
| 311 | } |
| 312 | |
| 313 | if (emitNamespaceIndexChanged) |
| 314 | emit namespaceIndexChanged(m_namespaceIndex); |
| 315 | if (emitNamespaceNameChanged) |
| 316 | emit namespaceNameChanged(m_namespaceName); |
| 317 | if (emitNodeIdentifierChanged) |
| 318 | emit nodeIdentifierChanged(m_nodeIdentifier); |
| 319 | if (emitNamespaceIndexChanged || emitNamespaceNameChanged) |
| 320 | emit namespaceChanged(); |
| 321 | if (emitNamespaceIndexChanged || emitNamespaceNameChanged || emitNodeIdentifierChanged) { |
| 322 | emit nodeChanged(); |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | /* |
| 327 | This function splits up a node identifier into namespace index and node name. |
| 328 | Returns true if successful, otherwise false. |
| 329 | |
| 330 | When passing \nullptr as pointer argument, the assignment of results to that |
| 331 | pointer will be skipped. |
| 332 | */ |
| 333 | bool UniversalNode::splitNodeIdAndNamespace(const QString nodeIdentifier, int *namespaceIndex, QString *identifier) |
| 334 | { |
| 335 | if (nodeIdentifier.startsWith(s: QLatin1String("ns=" ))) { |
| 336 | const auto token = nodeIdentifier.split(sep: QLatin1Char(';')); |
| 337 | if (token.size() != 2) { |
| 338 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Invalid node identifier:" << nodeIdentifier; |
| 339 | return false; |
| 340 | } |
| 341 | |
| 342 | const QString ns = token[0].mid(position: 3); |
| 343 | bool ok = false; |
| 344 | if (namespaceIndex) |
| 345 | *namespaceIndex = ns.toUInt(ok: &ok); |
| 346 | if (!ok) { |
| 347 | qCWarning(QT_OPCUA_PLUGINS_QML) << "Namespace index is not a number:" << nodeIdentifier; |
| 348 | return false; |
| 349 | } |
| 350 | if (identifier) |
| 351 | *identifier = token[1]; |
| 352 | return true; |
| 353 | } |
| 354 | return false; |
| 355 | } |
| 356 | |
| 357 | bool UniversalNode::operator==(const UniversalNode &rhs) const |
| 358 | { |
| 359 | return this->m_namespaceName == rhs.m_namespaceName && |
| 360 | this->m_nodeIdentifier == rhs.m_nodeIdentifier && |
| 361 | this->m_namespaceIndex == rhs.m_namespaceIndex && |
| 362 | this->m_namespaceIndexValid == rhs.m_namespaceIndexValid; |
| 363 | } |
| 364 | |
| 365 | /* |
| 366 | Constructs a full node id string out of id and namespace. |
| 367 | A string based namespace will be preferred and resolved. |
| 368 | */ |
| 369 | QString UniversalNode::resolveNamespaceToNode(const QString &nodeId, const QString &namespaceName, QOpcUaClient *client) |
| 370 | { |
| 371 | int namespaceIndex = 0; |
| 372 | QString identifier; |
| 373 | |
| 374 | if (!splitNodeIdAndNamespace(nodeIdentifier: nodeId, namespaceIndex: &namespaceIndex, identifier: &identifier)) { |
| 375 | // Splitting of node id failed. Consider the whole node id as name. |
| 376 | identifier = nodeId; |
| 377 | } |
| 378 | |
| 379 | if (!namespaceName.isEmpty()) { |
| 380 | namespaceIndex = resolveNamespaceNameToIndex(namespaceName, client); |
| 381 | if (namespaceIndex < 0) |
| 382 | return QString(); |
| 383 | } |
| 384 | return createNodeString(namespaceIndex, nodeIdentifier: identifier); |
| 385 | } |
| 386 | |
| 387 | QT_END_NAMESPACE |
| 388 | |