Skip to content

Commit

Permalink
refactor constructEventFilter and constructSelectClause
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Jul 15, 2023
1 parent 1cd110f commit 72dfbf8
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 23 deletions.
51 changes: 29 additions & 22 deletions packages/node-opcua-service-filter/source/tools_event_filter.ts
Expand Up @@ -2,41 +2,50 @@
* @module node-opcua-service-filter
*/
import { ObjectTypeIds } from "node-opcua-constants";
import { AttributeIds, QualifiedName, stringToQualifiedName } from "node-opcua-data-model";
import { NodeIdLike, resolveNodeId } from "node-opcua-nodeid";
import {
AttributeIds,
QualifiedName,
QualifiedNameOptions,
coerceQualifiedName,
stringToQualifiedName
} from "node-opcua-data-model";

import { ContentFilter, ContentFilterElement, ContentFilterOptions, EventFilter, FilterOperator, SimpleAttributeOperand } from "./imports";
import { ContentFilter, ContentFilterElement, ContentFilterOptions, EventFilter, SimpleAttributeOperand } from "./imports";

import { ofType } from "./make_content_filter";
function coerceQualifiedName2(a: string | QualifiedNameOptions) {
if (typeof a === "string") return coerceQualifiedName(a);
a.namespaceIndex = a.namespaceIndex || 0; // need a namespaceIndex
return coerceQualifiedName(a);
}

export function constructSelectClause(arrayOfNames: string | string[]): SimpleAttributeOperand[] {
export function constructSimpleBrowsePath(a: string | string[] | (QualifiedNameOptions | string)[]): QualifiedName[] {
if (typeof a === "string") {
return constructSimpleBrowsePath(a.split("."));
}
if (Array.isArray(a)) return a.map(coerceQualifiedName2);
return [coerceQualifiedName2(a)];
}
export function constructSelectClause(
arrayOfNames: string | string[] | (QualifiedNameOptions | string)[][]
): SimpleAttributeOperand[] {
if (!Array.isArray(arrayOfNames)) {
return constructSelectClause([arrayOfNames]);
}

// replace "string" element in the form A.B.C into [ "A","B","C"]
const arrayOfNames2 = arrayOfNames.map((path) => (typeof path !== "string" ? path : path.split(".")));

const arrayOfNames3 = arrayOfNames2.map((path) => (Array.isArray(path) ? path.map(stringToQualifiedName) : path));

// replace "string" elements in arrayOfName with QualifiedName in namespace 0
const arrayOfNames4 = arrayOfNames3.map((s) => (typeof s === "string" ? stringToQualifiedName(s) : s));

// construct browse paths array
const browsePaths = arrayOfNames4.map((s) => (Array.isArray(s) ? s : [s]));
const browsePaths = arrayOfNames.map(constructSimpleBrowsePath);

// Part 4 page 127:
// In some cases the same BrowsePath will apply to multiple EventTypes. If the Client specifies the BaseEventType
// in the SimpleAttributeOperand then the Server shall evaluate the BrowsePath without considering the Type.

const isBrowsePathForConditionId = (browsePath: QualifiedName[]) =>
browsePath.length === 1 && browsePath[0].namespaceIndex === 0 && browsePath[0].name === "ConditionId";
const isBrowsePathForConditionId = (browsePath: QualifiedNameOptions[]) =>
browsePath.length === 1 && browsePath[0] && browsePath[0].namespaceIndex === 0 && browsePath[0].name === "ConditionId";

// [..]
// The SimpleAttributeOperand structure allows the Client to specify any Attribute, however, the Server is only
// required to support the Value Attribute for Variable Nodes and the NodeId Attribute for Object Nodes.
// That said, profiles defined in Part 7 may make support for additional Attributes mandatory.
const selectClauses = browsePaths.map((browsePath: QualifiedName[]) => {
const selectClauses = browsePaths.map((browsePath: QualifiedNameOptions[]) => {
if (isBrowsePathForConditionId(browsePath)) {
// special case
//
Expand Down Expand Up @@ -75,16 +84,14 @@ export function constructSelectClause(arrayOfNames: string | string[]): SimpleAt
* @example
*
* constructEventFilter(["SourceName","Message","ReceiveTime"]);
*
* constructEventFilter(["SourceName",{namespaceIndex:2 , "MyData"}]);
* constructEventFilter(["SourceName","2:MyData" ]);
*
* constructEventFilter(["SourceName" ,["EnabledState","EffectiveDisplayName"] ]);
* constructEventFilter(["SourceName" ,"EnabledState.EffectiveDisplayName" ]);
* constructEventFilter([ ["SourceName",{namespaceIndex:2 , "MyData"} ]]);
*
*/
export function constructEventFilter(
arrayOfNames: string[] | string,
arrayOfNames: (QualifiedNameOptions | string)[][] | string[] | string,
whereClause?: ContentFilterOptions | ContentFilterElement
): EventFilter {
const selectClauses = constructSelectClause(arrayOfNames);
Expand Down
107 changes: 107 additions & 0 deletions packages/node-opcua-service-filter/test/test_constructSelectClause.ts
@@ -0,0 +1,107 @@
import should from "should";
import { AttributeIds } from "node-opcua-basic-types";
import { constructSelectClause } from "..";

const doDebug = false;

describe("constructSelectClause", () => {
it("constructSelectClause form 1 - single string", () => {
const selectClause = constructSelectClause("Hello");
doDebug && console.log(selectClause.toString());

selectClause.length.should.eql(1);
selectClause[0].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[0].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[0].browsePath);
selectClause[0].browsePath?.length.should.eql(1);
selectClause[0].browsePath![0].toString().should.eql("Hello");
});
it("constructSelectClause form 2 - array with a single string", () => {
const selectClause = constructSelectClause(["Hello"]);
doDebug && console.log(selectClause.toString());
selectClause.length.should.eql(1);
selectClause[0].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[0].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[0].browsePath);
selectClause[0].browsePath?.length.should.eql(1);
selectClause[0].browsePath![0].toString().should.eql("Hello");
});
it("constructSelectClause form 3 - array with a QualifiedNameLike", () => {
const selectClause = constructSelectClause([[{ name: "Hello" }]]);
doDebug && console.log(selectClause.toString());
selectClause.length.should.eql(1);
selectClause[0].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[0].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[0].browsePath);
selectClause[0].browsePath?.length.should.eql(1);
selectClause[0].browsePath![0].toString().should.eql("Hello");
});
it("constructSelectClause form 4 - array with a array of string", () => {
const selectClause = constructSelectClause([["Hello", "2:World"]]);
doDebug && console.log(selectClause.toString());
selectClause.length.should.eql(1);
selectClause[0].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[0].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[0].browsePath);
selectClause[0].browsePath?.length.should.eql(2);
selectClause[0].browsePath![0].toString().should.eql("Hello");
selectClause[0].browsePath![1].toString().should.eql("2:World");
});
it("constructSelectClause form 5 - array with a array with a mixture of string and qualified Liked element", () => {
const selectClause = constructSelectClause([["Hello", { namespaceIndex: 2, name: "World" }]]);
doDebug && console.log(selectClause.toString());
selectClause.length.should.eql(1);
selectClause[0].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[0].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[0].browsePath);
selectClause[0].browsePath?.length.should.eql(2);
selectClause[0].browsePath![0].toString().should.eql("Hello");
selectClause[0].browsePath![1].toString().should.eql("2:World");
});
it("constructSelectClause form 6 - with an array of simple string", () => {
const selectClause = constructSelectClause(["SourceName", "Message", "ReceiveTime"]);
doDebug && console.log(selectClause.toString());
selectClause.length.should.eql(3);

selectClause[0].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[0].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[0].browsePath);
selectClause[0].browsePath?.length.should.eql(1);
selectClause[0].browsePath![0].toString().should.eql("SourceName");

selectClause[1].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[1].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[1].browsePath);
selectClause[1].browsePath?.length.should.eql(1);
selectClause[1].browsePath![0].name!.should.eql("Message");
selectClause[1].browsePath![0].namespaceIndex!.should.eql(0);

selectClause[2].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[2].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[2].browsePath);
selectClause[2].browsePath?.length.should.eql(1);
selectClause[2].browsePath![0].toString().should.eql("ReceiveTime");
});

it("constructSelectClause form 7 - with an array of composite string with namespace ", () => {
const selectClause = constructSelectClause(["SourceName", "2:EnabledState.3:EffectiveDisplayName"]);
doDebug && console.log(selectClause.toString());
selectClause.length.should.eql(2);

selectClause[0].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[0].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[0].browsePath);
selectClause[0].browsePath?.length.should.eql(1);
selectClause[0].browsePath![0].toString().should.eql("SourceName");

selectClause[1].typeDefinitionId.toString().should.eql("ns=0;i=2041"); // BaseEventType;
selectClause[1].attributeId.should.eql(AttributeIds.Value);
should.exist(selectClause[1].browsePath);
selectClause[1].browsePath?.length.should.eql(2);
selectClause[1].browsePath![0].name!.should.eql("EnabledState");
selectClause[1].browsePath![0].namespaceIndex!.should.eql(2);

selectClause[1].browsePath![1].name!.should.eql("EffectiveDisplayName");
selectClause[1].browsePath![1].namespaceIndex!.should.eql(3);
});
});
Expand Up @@ -12,7 +12,7 @@ describe("test constructEventFilter", function () {

// console.log(ef.toString());

ef.selectClauses.length.should.eql(1, "expected one elemen in the select clause : SourceName");
ef.selectClauses.length.should.eql(1, "expected one element in the select clause : SourceName");

ef.selectClauses[0].browsePath.length.should.eql(1);
ef.selectClauses[0].browsePath[0].name.should.eql("SourceName");
Expand Down

0 comments on commit 72dfbf8

Please sign in to comment.