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 | |
12 | QT_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 | */ |
29 | const int maxRecursionDepth = 50; |
30 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML) |
31 | |
32 | OpcUaPathResolver::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 | |
42 | OpcUaPathResolver::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 | |
52 | OpcUaPathResolver::~OpcUaPathResolver() |
53 | { |
54 | if (m_node) { |
55 | m_node->deleteLater(); |
56 | m_node = nullptr; |
57 | } |
58 | } |
59 | |
60 | void 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 | |
98 | void 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 | |
138 | void 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 | |
202 | QT_END_NAMESPACE |
203 | |