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.
Files changed (45) hide show
  1. package/dist/check_event_clause.d.ts +17 -0
  2. package/dist/check_event_clause.js +52 -0
  3. package/dist/check_event_clause.js.map +1 -0
  4. package/dist/check_where_clause.d.ts +3 -0
  5. package/dist/check_where_clause.js +250 -0
  6. package/dist/check_where_clause.js.map +1 -0
  7. package/dist/extract_event_field.d.ts +9 -0
  8. package/dist/extract_event_field.js +53 -0
  9. package/dist/extract_event_field.js.map +1 -0
  10. package/dist/filter_context.d.ts +11 -0
  11. package/dist/filter_context.js +3 -0
  12. package/dist/filter_context.js.map +1 -0
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.js +8 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/make_content_filter.d.ts +22 -0
  17. package/dist/make_content_filter.js +121 -0
  18. package/dist/make_content_filter.js.map +1 -0
  19. package/dist/on_address_space/extract_event_fields.d.ts +10 -0
  20. package/dist/on_address_space/extract_event_fields.js +18 -0
  21. package/dist/on_address_space/extract_event_fields.js.map +1 -0
  22. package/dist/on_address_space/filter_context_on_address_space.d.ts +19 -0
  23. package/dist/on_address_space/filter_context_on_address_space.js +109 -0
  24. package/dist/on_address_space/filter_context_on_address_space.js.map +1 -0
  25. package/dist/on_address_space/index.d.ts +2 -0
  26. package/dist/on_address_space/index.js +19 -0
  27. package/dist/on_address_space/index.js.map +1 -0
  28. package/dist/resolve_operand.d.ts +4 -0
  29. package/dist/resolve_operand.js +45 -0
  30. package/dist/resolve_operand.js.map +1 -0
  31. package/dist/tools_event_filter.d.ts +4 -16
  32. package/dist/tools_event_filter.js +53 -121
  33. package/dist/tools_event_filter.js.map +1 -1
  34. package/package.json +13 -9
  35. package/source/check_event_clause.ts +54 -0
  36. package/source/check_where_clause.ts +287 -0
  37. package/source/extract_event_field.ts +55 -0
  38. package/source/filter_context.ts +15 -0
  39. package/source/index.ts +8 -0
  40. package/source/make_content_filter.ts +132 -0
  41. package/source/on_address_space/extract_event_fields.ts +24 -0
  42. package/source/on_address_space/filter_context_on_address_space.ts +134 -0
  43. package/source/on_address_space/index.ts +3 -0
  44. package/source/resolve_operand.ts +45 -0
  45. 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
+ }
@@ -0,0 +1,3 @@
1
+
2
+ export * from "./filter_context_on_address_space";
3
+ export * from "./extract_event_fields";