node-opcua-service-filter 2.74.0 → 2.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/check_event_clause.d.ts +17 -0
- package/dist/check_event_clause.js +52 -0
- package/dist/check_event_clause.js.map +1 -0
- package/dist/check_where_clause.d.ts +3 -0
- package/dist/check_where_clause.js +250 -0
- package/dist/check_where_clause.js.map +1 -0
- package/dist/extract_event_field.d.ts +9 -0
- package/dist/extract_event_field.js +53 -0
- package/dist/extract_event_field.js.map +1 -0
- package/dist/filter_context.d.ts +11 -0
- package/dist/filter_context.js +3 -0
- package/dist/filter_context.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/make_content_filter.d.ts +22 -0
- package/dist/make_content_filter.js +121 -0
- package/dist/make_content_filter.js.map +1 -0
- package/dist/on_address_space/extract_event_fields.d.ts +10 -0
- package/dist/on_address_space/extract_event_fields.js +18 -0
- package/dist/on_address_space/extract_event_fields.js.map +1 -0
- package/dist/on_address_space/filter_context_on_address_space.d.ts +19 -0
- package/dist/on_address_space/filter_context_on_address_space.js +109 -0
- package/dist/on_address_space/filter_context_on_address_space.js.map +1 -0
- package/dist/on_address_space/index.d.ts +2 -0
- package/dist/on_address_space/index.js +19 -0
- package/dist/on_address_space/index.js.map +1 -0
- package/dist/resolve_operand.d.ts +4 -0
- package/dist/resolve_operand.js +45 -0
- package/dist/resolve_operand.js.map +1 -0
- package/dist/tools_event_filter.d.ts +4 -16
- package/dist/tools_event_filter.js +53 -121
- package/dist/tools_event_filter.js.map +1 -1
- package/package.json +13 -9
- package/source/check_event_clause.ts +54 -0
- package/source/check_where_clause.ts +287 -0
- package/source/extract_event_field.ts +55 -0
- package/source/filter_context.ts +15 -0
- package/source/index.ts +8 -0
- package/source/make_content_filter.ts +132 -0
- package/source/on_address_space/extract_event_fields.ts +24 -0
- package/source/on_address_space/filter_context_on_address_space.ts +134 -0
- package/source/on_address_space/index.ts +3 -0
- package/source/resolve_operand.ts +45 -0
- package/source/tools_event_filter.ts +61 -129
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContentFilter,
|
|
3
|
+
FilterOperator,
|
|
4
|
+
LiteralOperand,
|
|
5
|
+
SimpleAttributeOperand,
|
|
6
|
+
FilterOperand,
|
|
7
|
+
AttributeOperand,
|
|
8
|
+
ElementOperand
|
|
9
|
+
} from "node-opcua-types";
|
|
10
|
+
import { ExtensionObject } from "node-opcua-extension-object";
|
|
11
|
+
import { DataType, Variant } from "node-opcua-variant";
|
|
12
|
+
import { NodeClass } from "node-opcua-data-model";
|
|
13
|
+
import { NodeId, sameNodeId } from "node-opcua-nodeid";
|
|
14
|
+
import { make_warningLog } from "node-opcua-debug";
|
|
15
|
+
//
|
|
16
|
+
import { FilterContext } from "./filter_context";
|
|
17
|
+
import { resolveOperand } from "./resolve_operand";
|
|
18
|
+
|
|
19
|
+
const warningLog = make_warningLog("Filter");
|
|
20
|
+
|
|
21
|
+
function _coerceToBoolean(value: Variant | number | string | null | boolean): boolean {
|
|
22
|
+
if (value instanceof Variant) {
|
|
23
|
+
return _coerceToBoolean(value.value);
|
|
24
|
+
}
|
|
25
|
+
return !!value;
|
|
26
|
+
}
|
|
27
|
+
function _coerceToNumber(value: Variant | number | string | null | boolean): number {
|
|
28
|
+
if (value instanceof Variant) {
|
|
29
|
+
return _coerceToNumber(value.value);
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "string") {
|
|
32
|
+
return parseInt(value, 10);
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "boolean") {
|
|
35
|
+
return value ? 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "number") {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function evaluateOperand<T>(
|
|
44
|
+
filterContext: FilterContext,
|
|
45
|
+
filter: ContentFilter,
|
|
46
|
+
operand: FilterOperand,
|
|
47
|
+
coerce: (value: any) => T
|
|
48
|
+
): T {
|
|
49
|
+
if (operand instanceof AttributeOperand) {
|
|
50
|
+
return coerce(resolveOperand(filterContext, operand));
|
|
51
|
+
} else if (operand instanceof SimpleAttributeOperand) {
|
|
52
|
+
return coerce(resolveOperand(filterContext, operand));
|
|
53
|
+
} else if (operand instanceof LiteralOperand) {
|
|
54
|
+
return coerce(operand.value);
|
|
55
|
+
} else if (operand instanceof ElementOperand) {
|
|
56
|
+
const index = operand.index;
|
|
57
|
+
return coerce(checkFilterAtIndex(filterContext, filter, index));
|
|
58
|
+
}
|
|
59
|
+
// istanbul ignore
|
|
60
|
+
return coerce(null);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checkOfType(filterContext: FilterContext, ofType: ExtensionObject | null): boolean {
|
|
64
|
+
// istanbul ignore next
|
|
65
|
+
if (!ofType || !(ofType instanceof LiteralOperand)) {
|
|
66
|
+
warningLog("checkOfType : unsupported case ! ofType is not a LiteralOperand , ofType = ", ofType?.toString());
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
// istanbul ignore next
|
|
70
|
+
if (ofType.value.dataType !== DataType.NodeId) {
|
|
71
|
+
warningLog("invalid operand type (expecting NodeId); got " + DataType[ofType.value.dataType]);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ofTypeNode: NodeId = ofType.value.value;
|
|
76
|
+
// istanbul ignore next
|
|
77
|
+
if (!ofTypeNode) {
|
|
78
|
+
return false; // the ofType node is not known, we don't know what to do
|
|
79
|
+
}
|
|
80
|
+
const ofTypeNodeNodeClass = filterContext.getNodeClass(ofTypeNode);
|
|
81
|
+
|
|
82
|
+
// istanbul ignore next
|
|
83
|
+
if (
|
|
84
|
+
ofTypeNodeNodeClass !== NodeClass.ObjectType &&
|
|
85
|
+
ofTypeNodeNodeClass !== NodeClass.VariableType &&
|
|
86
|
+
ofTypeNodeNodeClass !== NodeClass.DataType &&
|
|
87
|
+
ofTypeNodeNodeClass !== NodeClass.ReferenceType
|
|
88
|
+
) {
|
|
89
|
+
warningLog("operand should be a ObjectType " + ofTypeNode.toString());
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!filterContext.eventSource || filterContext.eventSource.isEmpty()) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let sourceTypeDefinition = filterContext.eventSource;
|
|
98
|
+
const sourceNodeClass = filterContext.getNodeClass(filterContext.eventSource);
|
|
99
|
+
if (sourceNodeClass === NodeClass.Object || sourceNodeClass === NodeClass.Variable) {
|
|
100
|
+
sourceTypeDefinition = filterContext.getTypeDefinition(filterContext.eventSource)!;
|
|
101
|
+
if (!sourceTypeDefinition) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return filterContext.isSubtypeOf(sourceTypeDefinition, ofTypeNode);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function checkNot(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
109
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToBoolean);
|
|
110
|
+
return !operandA;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function checkOr(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
114
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToBoolean);
|
|
115
|
+
const operandB = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToBoolean);
|
|
116
|
+
return operandA || operandB;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
*
|
|
120
|
+
* TRUE if operand[0] and operand[1] are TRUE.
|
|
121
|
+
* The following restrictions apply to the operands:
|
|
122
|
+
* [0]: Any operand that resolves to a Boolean.
|
|
123
|
+
*
|
|
124
|
+
* [1]: Any operand that resolves to a Boolean.
|
|
125
|
+
* If any operand cannot be resolved to a Boolean it is considered a NULL. See below for a discussion on the handling of NULL.
|
|
126
|
+
*/
|
|
127
|
+
function checkAnd(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
128
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToBoolean);
|
|
129
|
+
const operandB = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToBoolean);
|
|
130
|
+
return operandA && operandB;
|
|
131
|
+
}
|
|
132
|
+
function checkLessThan(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
133
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToNumber);
|
|
134
|
+
const operandB = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToNumber);
|
|
135
|
+
return operandA < operandB;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function checkLessThanOrEqual(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
139
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToNumber);
|
|
140
|
+
const operandB = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToNumber);
|
|
141
|
+
return operandA <= operandB;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkGreaterThan(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
145
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToNumber);
|
|
146
|
+
const operandB = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToNumber);
|
|
147
|
+
return operandA > operandB;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function checkGreaterThanOrEqual(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
151
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToNumber);
|
|
152
|
+
const operandB = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToNumber);
|
|
153
|
+
return operandA >= operandB;
|
|
154
|
+
}
|
|
155
|
+
function checkEquals(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
156
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToNumber);
|
|
157
|
+
const operandB = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToNumber);
|
|
158
|
+
return operandA === operandB;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
*
|
|
162
|
+
* TRUE if operand[0] is greater or equal to operand[1] and less than or equal to operand[2].
|
|
163
|
+
* The following restrictions apply to the operands:
|
|
164
|
+
* [0]: Any operand that resolves to an ordered value.
|
|
165
|
+
* [1]: Any operand that resolves to an ordered value.
|
|
166
|
+
* [2]: Any operand that resolves to an ordered value.
|
|
167
|
+
* If the operands are of different types, the system shall perform any implicit conversion to match
|
|
168
|
+
* all operands to a common type. If no implicit conversion is available and the operands are of different
|
|
169
|
+
* types, the particular result is FALSE. See the discussion on data type precedence in Table 123
|
|
170
|
+
* for more information how to convert operands of different types.
|
|
171
|
+
*/
|
|
172
|
+
function checkBetween(filterContext: FilterContext, filter: ContentFilter, filteredOperands: FilterOperand[]): boolean {
|
|
173
|
+
const operandA = evaluateOperand(filterContext, filter, filteredOperands[0], _coerceToNumber);
|
|
174
|
+
const operandLow = evaluateOperand(filterContext, filter, filteredOperands[1], _coerceToNumber);
|
|
175
|
+
const operandHigh = evaluateOperand(filterContext, filter, filteredOperands[2], _coerceToNumber);
|
|
176
|
+
return operandA >= operandLow && operandA <= operandHigh;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
*
|
|
181
|
+
* InList
|
|
182
|
+
* TRUE if operand[0] is equal to one or more of the remaining operands.
|
|
183
|
+
* The Equals Operator is evaluated for operand[0] and each remaining operand in the
|
|
184
|
+
* list. If any Equals evaluation is TRUE, InList returns TRUE
|
|
185
|
+
x*/
|
|
186
|
+
function checkInList(context: FilterContext, filterOperands: FilterOperand[]): boolean {
|
|
187
|
+
const operand0 = filterOperands[0];
|
|
188
|
+
|
|
189
|
+
// istanbul ignore next
|
|
190
|
+
if (!(operand0 instanceof SimpleAttributeOperand)) {
|
|
191
|
+
// unsupported case
|
|
192
|
+
warningLog("FilterOperator.InList operand0 is not a SimpleAttributeOperand " + operand0.constructor.name);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const value = resolveOperand(context, operand0);
|
|
196
|
+
|
|
197
|
+
// istanbul ignore next
|
|
198
|
+
if (value.dataType !== DataType.NodeId) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const nodeId: NodeId | null = value.value as NodeId;
|
|
203
|
+
|
|
204
|
+
// istanbul ignore next
|
|
205
|
+
if (!nodeId) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function _is(nodeId1: NodeId, operandX: LiteralOperand): boolean {
|
|
210
|
+
if (operandX.value.dataType !== DataType.NodeId) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const nodeId2 = operandX.value.value as NodeId;
|
|
214
|
+
const nodeClass = context.getNodeClass(nodeId2);
|
|
215
|
+
if (nodeClass === NodeClass.Unspecified) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
return sameNodeId(nodeId1, nodeId2);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (let i = 1; i < filterOperands.length; i++) {
|
|
222
|
+
const filterOperand = filterOperands[i];
|
|
223
|
+
if (filterOperand instanceof LiteralOperand && _is(nodeId, filterOperand)) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// eslint-disable-next-line complexity
|
|
231
|
+
function checkFilterAtIndex(filterContext: FilterContext, filter: ContentFilter, index: number): boolean {
|
|
232
|
+
if (!filter.elements || filter.elements.length === 0) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
const element = filter.elements[index];
|
|
236
|
+
|
|
237
|
+
// istanbul ignore next
|
|
238
|
+
if (!element) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
const filterOperands = (element.filterOperands as FilterOperand[] | null) || [];
|
|
242
|
+
|
|
243
|
+
switch (element.filterOperator) {
|
|
244
|
+
case FilterOperator.Equals:
|
|
245
|
+
return checkEquals(filterContext, filter, filterOperands);
|
|
246
|
+
case FilterOperator.LessThan:
|
|
247
|
+
return checkLessThan(filterContext, filter, filterOperands);
|
|
248
|
+
case FilterOperator.LessThanOrEqual:
|
|
249
|
+
return checkLessThanOrEqual(filterContext, filter, filterOperands);
|
|
250
|
+
case FilterOperator.GreaterThan:
|
|
251
|
+
return checkGreaterThan(filterContext, filter, filterOperands);
|
|
252
|
+
case FilterOperator.GreaterThanOrEqual:
|
|
253
|
+
return checkGreaterThanOrEqual(filterContext, filter, filterOperands);
|
|
254
|
+
case FilterOperator.Between:
|
|
255
|
+
return checkBetween(filterContext, filter, filterOperands);
|
|
256
|
+
|
|
257
|
+
case FilterOperator.And:
|
|
258
|
+
return checkAnd(filterContext, filter, filterOperands);
|
|
259
|
+
case FilterOperator.Or:
|
|
260
|
+
return checkOr(filterContext, filter, filterOperands);
|
|
261
|
+
case FilterOperator.Not:
|
|
262
|
+
return checkNot(filterContext, filter, filterOperands);
|
|
263
|
+
|
|
264
|
+
case FilterOperator.OfType:
|
|
265
|
+
return checkOfType(filterContext, element.filterOperands![0]);
|
|
266
|
+
case FilterOperator.InList:
|
|
267
|
+
return checkInList(filterContext, filterOperands);
|
|
268
|
+
|
|
269
|
+
case FilterOperator.RelatedTo:
|
|
270
|
+
case FilterOperator.Like:
|
|
271
|
+
case FilterOperator.BitwiseAnd:
|
|
272
|
+
case FilterOperator.BitwiseOr:
|
|
273
|
+
case FilterOperator.Cast:
|
|
274
|
+
case FilterOperator.InView:
|
|
275
|
+
case FilterOperator.IsNull:
|
|
276
|
+
default:
|
|
277
|
+
// from Spec OPC Unified Architecture, Part 4 133 Release 1.04
|
|
278
|
+
// Any basic FilterOperator in Table 119 may be used in the whereClause, however, only the
|
|
279
|
+
// OfType_14 FilterOperator from Table 120 is permitted.
|
|
280
|
+
warningLog(`checkFilter: operator ${FilterOperator[element.filterOperator]} is currently not supported in filter`);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function checkFilter(filterContext: FilterContext, contentFilter: ContentFilter): boolean {
|
|
286
|
+
return checkFilterAtIndex(filterContext, contentFilter, 0);
|
|
287
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { assert } from "node-opcua-assert";
|
|
2
|
+
import { AttributeIds } from "node-opcua-data-model";
|
|
3
|
+
import { make_warningLog } from "node-opcua-debug";
|
|
4
|
+
import { NodeId, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
|
|
5
|
+
import { SimpleAttributeOperand } from "node-opcua-types";
|
|
6
|
+
import { DataType, Variant } from "node-opcua-variant";
|
|
7
|
+
import { FilterContext } from "./filter_context";
|
|
8
|
+
import { resolveOperand } from "./resolve_operand";
|
|
9
|
+
|
|
10
|
+
const warningLog = make_warningLog("FILTER");
|
|
11
|
+
const conditionTypeNodeId = resolveNodeId("ConditionType");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* extract a eventField from a event node, matching the given selectClause
|
|
16
|
+
*/
|
|
17
|
+
export function extractEventField(context: FilterContext, operand: SimpleAttributeOperand): Variant {
|
|
18
|
+
assert(operand instanceof SimpleAttributeOperand);
|
|
19
|
+
|
|
20
|
+
operand.browsePath = operand.browsePath || [];
|
|
21
|
+
|
|
22
|
+
if (operand.browsePath.length === 0 && operand.attributeId === AttributeIds.NodeId) {
|
|
23
|
+
// "ns=0;i=2782" => ConditionType
|
|
24
|
+
// "ns=0;i=2041" => BaseEventType
|
|
25
|
+
if (!sameNodeId(operand.typeDefinitionId, conditionTypeNodeId)) {
|
|
26
|
+
// not a ConditionType
|
|
27
|
+
// but could be on of its derived type. for instance ns=0;i=2881 => AcknowledgeableConditionType
|
|
28
|
+
if (!context.isSubtypeOf(operand.typeDefinitionId, conditionTypeNodeId)) {
|
|
29
|
+
warningLog(" ", operand.typeDefinitionId.toString());
|
|
30
|
+
warningLog("this case is not handled yet : selectClause.typeDefinitionId = " + operand.typeDefinitionId.toString());
|
|
31
|
+
warningLog(operand.toString());
|
|
32
|
+
return new Variant({ dataType: DataType.NodeId, value: context.eventSource });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const eventSourceTypeDefinition = context.getTypeDefinition(context.eventSource);
|
|
37
|
+
if (!eventSourceTypeDefinition) {
|
|
38
|
+
// eventSource is a EventType class
|
|
39
|
+
return new Variant();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!context.isSubtypeOf(eventSourceTypeDefinition, conditionTypeNodeId)) {
|
|
43
|
+
return new Variant();
|
|
44
|
+
}
|
|
45
|
+
// Yeh : our EventType is a Condition Type !
|
|
46
|
+
return new Variant({ dataType: DataType.NodeId, value: context.eventSource });
|
|
47
|
+
}
|
|
48
|
+
return resolveOperand(context, operand);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function extractEventFieldsBase(context: FilterContext, selectClauses: SimpleAttributeOperand[]): Variant[] {
|
|
52
|
+
assert(Array.isArray(selectClauses));
|
|
53
|
+
assert(selectClauses.length === 0 || selectClauses[0] instanceof SimpleAttributeOperand);
|
|
54
|
+
return selectClauses.map(extractEventField.bind(null, context));
|
|
55
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NodeId, NodeIdLike } from "node-opcua-nodeid";
|
|
2
|
+
import { constructBrowsePathFromQualifiedName } from "node-opcua-service-translate-browse-path";
|
|
3
|
+
import { AttributeOperand, BrowsePath, NodeClass, SimpleAttributeOperand } from "node-opcua-types";
|
|
4
|
+
import { DataType, Variant } from "node-opcua-variant";
|
|
5
|
+
|
|
6
|
+
export interface FilterContext {
|
|
7
|
+
readonly eventSource: NodeId;
|
|
8
|
+
isSubtypeOf(nodeId: NodeId, baseType: NodeId): boolean;
|
|
9
|
+
getTypeDefinition(nodeId: NodeId): NodeId | null;
|
|
10
|
+
// readOperand(operand: SimpleAttributeOperand | AttributeOperand): Variant;
|
|
11
|
+
readNodeValue(nodeId: NodeIdLike): Variant;
|
|
12
|
+
getNodeClass(nodeId: NodeId): NodeClass;
|
|
13
|
+
browsePath(browsePath: BrowsePath): NodeId | null;
|
|
14
|
+
}
|
|
15
|
+
|
package/source/index.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module node-opcua-service-filter
|
|
3
3
|
*/
|
|
4
|
+
export * from "./check_event_clause";
|
|
5
|
+
export * from "./check_where_clause";
|
|
6
|
+
export * from "./extract_event_field";
|
|
7
|
+
export * from "./filter_context";
|
|
4
8
|
export * from "./imports";
|
|
9
|
+
export * from "./make_content_filter";
|
|
10
|
+
export * from "./on_address_space/extract_event_fields";
|
|
11
|
+
export * from "./resolve_operand";
|
|
5
12
|
export * from "./tools_event_filter";
|
|
13
|
+
export * from "./on_address_space/filter_context_on_address_space";
|
|
6
14
|
|
|
7
15
|
// The SimpleAttributeOperand is a simplified form of the AttributeOperand and all of the rules that
|
|
8
16
|
// apply to the AttributeOperand also apply to the SimpleAttributeOperand. The examples provided in
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-service-filter
|
|
3
|
+
*/
|
|
4
|
+
import { DataType } from "node-opcua-basic-types";
|
|
5
|
+
import { ObjectTypeIds } from "node-opcua-constants";
|
|
6
|
+
import { AttributeIds, coerceQualifiedName, QualifiedName, stringToQualifiedName } from "node-opcua-data-model";
|
|
7
|
+
import { NodeIdLike, resolveNodeId } from "node-opcua-nodeid";
|
|
8
|
+
import { ContentFilterElementResult } from "node-opcua-types";
|
|
9
|
+
import { Variant } from "node-opcua-variant";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
AttributeOperand,
|
|
13
|
+
ContentFilter,
|
|
14
|
+
ContentFilterElement,
|
|
15
|
+
ContentFilterElementOptions,
|
|
16
|
+
ContentFilterOptions,
|
|
17
|
+
ElementOperand,
|
|
18
|
+
EventFilter,
|
|
19
|
+
FilterOperator,
|
|
20
|
+
LiteralOperand,
|
|
21
|
+
SimpleAttributeOperand
|
|
22
|
+
} from "./imports";
|
|
23
|
+
|
|
24
|
+
export function ofType(nodeId: NodeIdLike): ContentFilterElement {
|
|
25
|
+
const element: ContentFilterElement = new ContentFilterElement({
|
|
26
|
+
filterOperator: FilterOperator.OfType,
|
|
27
|
+
filterOperands: [
|
|
28
|
+
new LiteralOperand({
|
|
29
|
+
value: {
|
|
30
|
+
dataType: DataType.NodeId,
|
|
31
|
+
value: resolveNodeId(nodeId)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
return element;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function l(dataType: DataType, value: any): LiteralOperand {
|
|
40
|
+
switch (dataType) {
|
|
41
|
+
case DataType.NodeId:
|
|
42
|
+
value = resolveNodeId(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return new LiteralOperand({ value: new Variant({ dataType, value }) });
|
|
46
|
+
}
|
|
47
|
+
export function n(n: NodeIdLike): LiteralOperand {
|
|
48
|
+
return l(DataType.NodeId, n);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function s(attributeId: AttributeIds, path: string): SimpleAttributeOperand {
|
|
52
|
+
return new SimpleAttributeOperand({
|
|
53
|
+
attributeId: attributeId,
|
|
54
|
+
browsePath: path.split(".").map(coerceQualifiedName)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type A = LiteralOperand | SimpleAttributeOperand | AttributeOperand | ContentFilterElement;
|
|
59
|
+
|
|
60
|
+
export function or(a: A, b: A): ContentFilterElement {
|
|
61
|
+
return new ContentFilterElement({
|
|
62
|
+
filterOperands: [a, b],
|
|
63
|
+
filterOperator: FilterOperator.Or
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function and(a: A, b: A): ContentFilterElement {
|
|
67
|
+
return new ContentFilterElement({
|
|
68
|
+
filterOperands: [a, b],
|
|
69
|
+
filterOperator: FilterOperator.And
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export function lessThan(a: A, b: A): ContentFilterElement {
|
|
73
|
+
return new ContentFilterElement({
|
|
74
|
+
filterOperands: [a, b],
|
|
75
|
+
filterOperator: FilterOperator.LessThan
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function LessThanOrEqual(a: A, b: A): ContentFilterElement {
|
|
79
|
+
return new ContentFilterElement({
|
|
80
|
+
filterOperands: [a, b],
|
|
81
|
+
filterOperator: FilterOperator.LessThanOrEqual
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function greaterThanOrEqual(a: A, b: A): ContentFilterElement {
|
|
86
|
+
return new ContentFilterElement({
|
|
87
|
+
filterOperands: [a, b],
|
|
88
|
+
filterOperator: FilterOperator.GreaterThanOrEqual
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function greaterThan(a: A, b: A): ContentFilterElement {
|
|
93
|
+
return new ContentFilterElement({
|
|
94
|
+
filterOperands: [a, b],
|
|
95
|
+
filterOperator: FilterOperator.GreaterThan
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function inList(a: A, l: A[]) {
|
|
100
|
+
return new ContentFilterElement({
|
|
101
|
+
filterOperands: [a, ...l],
|
|
102
|
+
filterOperator: FilterOperator.InList
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function makeContentFilterElements(o: ContentFilterElement): ContentFilterElement[] {
|
|
107
|
+
const elements: ContentFilterElement[] = [];
|
|
108
|
+
|
|
109
|
+
function pushElement(element: ContentFilterElement) {
|
|
110
|
+
elements.push(element);
|
|
111
|
+
const thisIndex = elements.length - 1;
|
|
112
|
+
if (element.filterOperands) {
|
|
113
|
+
for (let i = 0; i < element.filterOperands.length; i++) {
|
|
114
|
+
const op = element.filterOperands[i];
|
|
115
|
+
if (op instanceof ContentFilterElement) {
|
|
116
|
+
const index = pushElement(op);
|
|
117
|
+
element.filterOperands![i] = new ElementOperand({ index });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return thisIndex;
|
|
122
|
+
}
|
|
123
|
+
pushElement(o);
|
|
124
|
+
|
|
125
|
+
return elements;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function makeContentFilter(o: ContentFilterElementOptions): ContentFilter {
|
|
129
|
+
return new ContentFilter({
|
|
130
|
+
elements: makeContentFilterElements(o instanceof ContentFilterElement ? o : new ContentFilterElement(o))
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IEventData,
|
|
3
|
+
ISessionContext} from "node-opcua-address-space-base";
|
|
4
|
+
import { SimpleAttributeOperand } from "node-opcua-types";
|
|
5
|
+
import { Variant } from "node-opcua-variant";
|
|
6
|
+
//
|
|
7
|
+
import { extractEventFieldsBase } from "../extract_event_field";
|
|
8
|
+
import { FilterContextOnAddressSpace } from "./filter_context_on_address_space";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @method extractEventFields
|
|
13
|
+
* extract a array of eventFields from a event node, matching the selectClauses
|
|
14
|
+
* @param selectClauses
|
|
15
|
+
* @param eventData : a pseudo Node that provides a browse Method and a readValue(nodeId)
|
|
16
|
+
*/
|
|
17
|
+
export function extractEventFields(
|
|
18
|
+
sessionContext: ISessionContext,
|
|
19
|
+
selectClauses: SimpleAttributeOperand[],
|
|
20
|
+
eventData: IEventData
|
|
21
|
+
): Variant[] {
|
|
22
|
+
const context = new FilterContextOnAddressSpace(sessionContext, eventData);
|
|
23
|
+
return extractEventFieldsBase(context, selectClauses);
|
|
24
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseNode,
|
|
3
|
+
IAddressSpace,
|
|
4
|
+
IEventData,
|
|
5
|
+
ISessionContext,
|
|
6
|
+
UADataType,
|
|
7
|
+
UAObject,
|
|
8
|
+
UAObjectType,
|
|
9
|
+
UAReferenceType,
|
|
10
|
+
UAVariable,
|
|
11
|
+
UAVariableType
|
|
12
|
+
} from "node-opcua-address-space-base";
|
|
13
|
+
import { AttributeIds, NodeClass } from "node-opcua-data-model";
|
|
14
|
+
import { make_warningLog } from "node-opcua-debug";
|
|
15
|
+
import { NodeId, sameNodeId, NodeIdLike, coerceNodeId } from "node-opcua-nodeid";
|
|
16
|
+
import { StatusCodes } from "node-opcua-status-code";
|
|
17
|
+
import { BrowsePath } from "node-opcua-types";
|
|
18
|
+
import { Variant, DataType } from "node-opcua-variant";
|
|
19
|
+
import { DataValue } from "node-opcua-data-value";
|
|
20
|
+
//
|
|
21
|
+
import { FilterContext } from "../filter_context";
|
|
22
|
+
|
|
23
|
+
const warningLog = make_warningLog("Filter");
|
|
24
|
+
|
|
25
|
+
export class FilterContextOnAddressSpace implements FilterContext {
|
|
26
|
+
public eventSource: NodeId;
|
|
27
|
+
|
|
28
|
+
constructor(private sessionContext: ISessionContext, private eventData: IEventData) {
|
|
29
|
+
this.eventSource = this.eventData.$eventDataSource?.nodeId || NodeId.nullNodeId;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getNodeClass(nodeId: NodeId): NodeClass {
|
|
33
|
+
const addressSpace = this.getAddressSpace();
|
|
34
|
+
const node = addressSpace.findNode(nodeId);
|
|
35
|
+
return node ? node.nodeClass : NodeClass.Unspecified;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isSubtypeOf(nodeId: NodeId, baseType: NodeId): boolean {
|
|
39
|
+
const addressSpace = this.getAddressSpace();
|
|
40
|
+
const node = addressSpace.findNode(nodeId);
|
|
41
|
+
const baseTypeNode = addressSpace.findNode(baseType);
|
|
42
|
+
if (!node || !baseTypeNode) {
|
|
43
|
+
warningLog("invalid node - must be specifed ");
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (node.nodeClass === NodeClass.ObjectType && baseTypeNode.nodeClass === NodeClass.ObjectType) {
|
|
47
|
+
return (node as UAObjectType).isSupertypeOf(baseTypeNode as UAObjectType);
|
|
48
|
+
}
|
|
49
|
+
if (node.nodeClass === NodeClass.VariableType && baseTypeNode.nodeClass === NodeClass.VariableType) {
|
|
50
|
+
return (node as UAVariableType).isSupertypeOf(baseTypeNode as UAVariableType);
|
|
51
|
+
}
|
|
52
|
+
if (node.nodeClass === NodeClass.ReferenceType && baseTypeNode.nodeClass === NodeClass.ReferenceType) {
|
|
53
|
+
return (node as UAReferenceType).isSupertypeOf(baseTypeNode as UAReferenceType);
|
|
54
|
+
}
|
|
55
|
+
if (node.nodeClass === NodeClass.DataType && baseTypeNode.nodeClass === NodeClass.DataType) {
|
|
56
|
+
return (node as UADataType).isSupertypeOf(baseTypeNode as UADataType);
|
|
57
|
+
}
|
|
58
|
+
if (node.nodeClass === NodeClass.Object && baseTypeNode.nodeClass === NodeClass.ObjectType) {
|
|
59
|
+
const obj = node as UAObject;
|
|
60
|
+
return obj.typeDefinitionObj.isSupertypeOf(baseTypeNode as UAObjectType);
|
|
61
|
+
}
|
|
62
|
+
if (node.nodeClass === NodeClass.Variable && baseTypeNode.nodeClass !== NodeClass.VariableType) {
|
|
63
|
+
const obj = node as UAVariable;
|
|
64
|
+
return obj.typeDefinitionObj.isSupertypeOf(baseTypeNode as UAVariableType);
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getTypeDefinition(nodeId: NodeId): NodeId | null {
|
|
70
|
+
const addressSpace = this.getAddressSpace();
|
|
71
|
+
const node = addressSpace.findNode(nodeId);
|
|
72
|
+
if (!node) return null;
|
|
73
|
+
if (node.nodeClass === NodeClass.Object || node.nodeClass === NodeClass.Variable) {
|
|
74
|
+
return (node as UAObject | UAVariable).typeDefinition;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
readNodeValue(nodeId: NodeIdLike): Variant {
|
|
80
|
+
nodeId = coerceNodeId(nodeId);
|
|
81
|
+
|
|
82
|
+
// use cache/snapshot if available
|
|
83
|
+
const value = this.eventData._readValue(nodeId);
|
|
84
|
+
if (value) {
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const addressSpace = this.getAddressSpace();
|
|
89
|
+
const node = addressSpace.findNode(nodeId);
|
|
90
|
+
if (!node || node.nodeClass !== NodeClass.Variable) {
|
|
91
|
+
return new Variant();
|
|
92
|
+
}
|
|
93
|
+
return prepare((node as UAVariable).readValue(this.sessionContext));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private getAddressSpace(): IAddressSpace {
|
|
97
|
+
return this.eventData.$eventDataSource!.addressSpace;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public browsePath(browsePath: BrowsePath): NodeId | null {
|
|
101
|
+
|
|
102
|
+
// delegate to eventData if appropriate
|
|
103
|
+
if (sameNodeId(browsePath.startingNode, this.eventSource)) {
|
|
104
|
+
const browseResult = this.eventData._browse(browsePath);
|
|
105
|
+
if (
|
|
106
|
+
browseResult &&
|
|
107
|
+
browseResult.statusCode === StatusCodes.Good &&
|
|
108
|
+
browseResult.targets &&
|
|
109
|
+
browseResult.targets.length === 1
|
|
110
|
+
) {
|
|
111
|
+
return browseResult.targets![0].targetId;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// fallback to addressSpace otherwise
|
|
116
|
+
const addressSpace = this.getAddressSpace();
|
|
117
|
+
const browseResult = addressSpace.browsePath(browsePath);
|
|
118
|
+
if (browseResult.statusCode !== StatusCodes.Good || !browseResult.targets || browseResult.targets.length !== 1) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return browseResult.targets[0].targetId;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setEventSource(eventSource: BaseNode | null) {
|
|
125
|
+
this.eventSource = eventSource ? eventSource.nodeId : NodeId.nullNodeId;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function prepare(dataValue: DataValue): Variant {
|
|
130
|
+
if (dataValue.statusCode === StatusCodes.Good) {
|
|
131
|
+
return dataValue.value;
|
|
132
|
+
}
|
|
133
|
+
return new Variant({ dataType: DataType.StatusCode, value: dataValue.statusCode });
|
|
134
|
+
}
|