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
10QT_BEGIN_NAMESPACE
11
12Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML)
13
14UniversalNode::UniversalNode(QObject *parent)
15 : QObject(parent)
16{
17}
18
19UniversalNode::UniversalNode()
20 :QObject(nullptr)
21{
22}
23
24UniversalNode::UniversalNode(const QString &nodeIdentifier, QObject *parent)
25 : QObject(parent)
26{
27 setNodeIdentifier(nodeIdentifier);
28}
29
30UniversalNode::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
36UniversalNode::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
42UniversalNode::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
50UniversalNode::UniversalNode(const OpcUaNodeIdType *other, QObject *parent)
51 : QObject(parent)
52{
53 if (other)
54 from(*other);
55}
56
57const QString &UniversalNode::namespaceName() const
58{
59 return m_namespaceName;
60}
61
62void 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
70quint16 UniversalNode::namespaceIndex() const
71{
72 return m_namespaceIndex;
73}
74
75void UniversalNode::setNamespace(quint16 namespaceIndex)
76{
77 setMembers(setNamespaceIndex: true, namespaceIndex, setNamespaceName: false, namespaceName: QString(), setNodeIdentifier: false, nodeIdentifier: QString());
78}
79
80const QString &UniversalNode::nodeIdentifier() const
81{
82 return m_nodeIdentifier;
83}
84
85void 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
97void UniversalNode::resolveNamespace(QOpcUaClient *client)
98{
99 if (!m_namespaceIndexValid)
100 resolveNamespaceNameToIndex(client);
101 else if (m_namespaceName.isEmpty())
102 resolveNamespaceIndexToName(client);
103}
104
105void 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
127void 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
142int 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
165bool UniversalNode::isNamespaceNameValid() const
166{
167 return !m_namespaceName.isEmpty();
168}
169
170bool UniversalNode::isNamespaceIndexValid() const
171{
172 return m_namespaceIndexValid;
173}
174
175QOpcUaQualifiedName 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
189QOpcUaExpandedNodeId 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
204void UniversalNode::from(const QOpcUaQualifiedName &qualifiedName)
205{
206 setMembers(setNamespaceIndex: true, namespaceIndex: qualifiedName.namespaceIndex(), setNamespaceName: false, namespaceName: QString(), setNodeIdentifier: true, nodeIdentifier: qualifiedName.name());
207}
208
209void UniversalNode::from(const QOpcUaExpandedNodeId &expandedNodeId)
210{
211 setMembers(setNamespaceIndex: false, namespaceIndex: 0, setNamespaceName: true, namespaceName: expandedNodeId.namespaceUri(), setNodeIdentifier: true, nodeIdentifier: expandedNodeId.nodeId());
212}
213
214void 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
225void UniversalNode::from(const OpcUaNodeIdType *other) {
226 from(*other);
227}
228
229void 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
236void 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
242QString 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
260QOpcUaNode *UniversalNode::createNode(QOpcUaClient *client)
261{
262 return client->node(nodeId: fullNodeId());
263}
264
265UniversalNode &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 */
279void 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 */
333bool 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
357bool 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*/
369QString 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
387QT_END_NAMESPACE
388

source code of qtopcua/src/declarative_opcua/universalnode.cpp