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/opcuapathresolver_p.h>
5#include <private/opcuarelativenodeid_p.h>
6#include <private/opcuarelativenodepath_p.h>
7
8#include <QOpcUaClient>
9#include <QMetaEnum>
10#include <QLoggingCategory>
11
12QT_BEGIN_NAMESPACE
13
14/*!
15 \class OpcUaPathResolver
16 \inqmlmodule QtOpcUa
17 \internal
18 \brief This class resolves relative nodes.
19
20 This class is used to resolve relative node IDs. It will emit \c resolvedNode
21 with the result and delete itself afterwards.
22 In case of errors the resolved node is empty and the error message is set.
23
24 This class is capable of resolving cascaded relative nodes by recursively instantiating
25 further resolvers. The maximum recursion depth is 50.
26
27 \sa RelativeNodeId, Node
28*/
29const int maxRecursionDepth = 50;
30Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML)
31
32OpcUaPathResolver::OpcUaPathResolver(OpcUaRelativeNodeId *relativeNode, QOpcUaClient *client, QObject *target)
33 : QObject(target)
34 , m_level(0)
35 , m_relativeNode(relativeNode)
36 , m_target(target)
37 , m_client(client)
38 , m_node(nullptr)
39{
40}
41
42OpcUaPathResolver::OpcUaPathResolver(int level, OpcUaRelativeNodeId *relativeNode, QOpcUaClient *client, QObject *target)
43 : QObject(target)
44 , m_level(level)
45 , m_relativeNode(relativeNode)
46 , m_target(target)
47 , m_client(client)
48 , m_node(nullptr)
49{
50}
51
52OpcUaPathResolver::~OpcUaPathResolver()
53{
54 if (m_node) {
55 m_node->deleteLater();
56 m_node = nullptr;
57 }
58}
59
60void OpcUaPathResolver::startResolving()
61{
62 if (!m_relativeNode || !m_client || !m_target) {
63 emit resolvedNode(node: UniversalNode(), errorMessage: QLatin1String("Member has been deleted"));
64 deleteLater();
65 return;
66 }
67
68 auto startNode = m_relativeNode->startNode();
69 if (!startNode) {
70 emit resolvedNode(node: UniversalNode(), errorMessage: QLatin1String("Aborted resolving because start node not present"));
71 deleteLater();
72 return;
73 }
74
75 if (m_relativeNode->pathCount() == 0) {
76 emit resolvedNode(node: UniversalNode(), errorMessage: QLatin1String("Skipping to resolve relative node with empty path"));
77 deleteLater();
78 return;
79 }
80
81 if (qobject_cast<const OpcUaRelativeNodeId *>(object: startNode)) {
82 // Trigger recursive resolving
83 if (m_level >= maxRecursionDepth) {
84 emit resolvedNode(node: UniversalNode(), errorMessage: QLatin1String("Maximum recursion depth reached during node resolution"));
85 deleteLater();
86 return;
87 }
88 auto node = qobject_cast<OpcUaRelativeNodeId *>(object: startNode);
89 auto resolver = new OpcUaPathResolver(m_level + 1, node, m_client, this);
90 connect(sender: resolver, signal: &OpcUaPathResolver::resolvedNode, context: this, slot: &OpcUaPathResolver::startNodeResolved);
91 resolver->startResolving();
92 return;
93 } else {
94 startNodeResolved(startNode, errorMessage: QString());
95 }
96}
97
98void OpcUaPathResolver::startNodeResolved(UniversalNode startNode, const QString &errorMessage)
99{
100 if (!m_relativeNode || !m_client || !m_target) {
101 emit resolvedNode(node: UniversalNode(), errorMessage: QLatin1String("Member has been deleted"));
102 deleteLater();
103 return;
104 }
105
106 if (sender())
107 sender()->deleteLater();
108
109 if (!errorMessage.isEmpty()) {
110 emit resolvedNode(node: startNode, errorMessage);
111 deleteLater();
112 return;
113 }
114
115 startNode.resolveNamespace(client: m_client);
116 m_node = m_client->node(nodeId: startNode.fullNodeId());
117 if (!m_node) {
118 emit resolvedNode(node: startNode, QStringLiteral("Could not create node from '%1'")
119 .arg(a: startNode.fullNodeId()));
120 deleteLater();
121 return;
122 }
123
124 // construct path vector
125 QList<QOpcUaRelativePathElement> path;
126 for (int i = 0; i < m_relativeNode->pathCount(); ++i)
127 path.append(t: m_relativeNode->path(i)->toRelativePathElement(client: m_client));
128
129 qCDebug(QT_OPCUA_PLUGINS_QML) << "Starting browse on" << m_node->nodeId();
130 connect(sender: m_node, signal: &QOpcUaNode::resolveBrowsePathFinished, context: this, slot: &OpcUaPathResolver::browsePathFinished);
131 if (!m_node->resolveBrowsePath(path)) {
132 emit resolvedNode(node: UniversalNode(), QStringLiteral("Failed to start browse"));
133 deleteLater();
134 return;
135 }
136}
137
138void OpcUaPathResolver::browsePathFinished(QList<QOpcUaBrowsePathTarget> results, QList<QOpcUaRelativePathElement> path, QOpcUa::UaStatusCode status)
139{
140 Q_UNUSED(path);
141 UniversalNode nodeToUse;
142
143 if (status != QOpcUa::Good) {
144 const QString name = QString::fromUtf8(
145 utf8: QMetaEnum::fromType<QOpcUa::UaStatusCode>().valueToKey(value: status));
146 emit resolvedNode(node: UniversalNode(),
147 QStringLiteral("Resolving browse path return error code %1").arg(a: name));
148 deleteLater();
149 return;
150 }
151
152 if (results.size() == 0) {
153 emit resolvedNode(node: UniversalNode(),
154 QStringLiteral("Relative path could not be resolved: Results are empty"));
155 deleteLater();
156 return;
157 } else if (results.size() == 1) {
158 if (results.at(i: 0).targetId().serverIndex() > 0) {
159 emit resolvedNode(node: UniversalNode(),
160 QStringLiteral("Relative path could not be resolved: "
161 "Resulting node is located on a remote server"));
162 deleteLater();
163 return;
164 }
165 nodeToUse.from(browsePathTarget: results.at(i: 0));
166
167 } else { // greater than one
168 UniversalNode tmp;
169 QString message = QStringLiteral("No resolved node found");
170
171 for (const auto &result : results) {
172 if (result.isFullyResolved()) {
173 if (result.targetId().serverIndex() > 0) {
174 message = QStringLiteral("Relative path could not be resolved: "
175 "Resulting node is located on a remote server");
176 continue;
177 }
178 if (!tmp.nodeIdentifier().isEmpty()) {
179 emit resolvedNode(node: UniversalNode(), errorMessage: QLatin1String("There are multiple resolved nodes"));
180 deleteLater();
181 return;
182 }
183 tmp.from(browsePathTarget: result);
184 }
185 }
186
187 if (!tmp.nodeIdentifier().isEmpty()) {
188 nodeToUse = tmp;
189 } else {
190 emit resolvedNode(node: UniversalNode(), errorMessage: message);
191 deleteLater();
192 return;
193 }
194 }
195
196 nodeToUse.resolveNamespace(client: m_client);
197 qCDebug(QT_OPCUA_PLUGINS_QML) << "Relative node fully resolved to:" << nodeToUse.fullNodeId();
198 emit resolvedNode(node: nodeToUse, errorMessage: QString());
199 deleteLater();
200}
201
202QT_END_NAMESPACE
203

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