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 | |