1. Path Pattern
Any node in the MTree (Metadata Tree) is uniquely identified by a full path from the root node to that node.
When using paths for node operations, a path pattern can be constructed by using wildcards * and **. A path pattern can represent a set of nodes matching the pattern. Where * denotes a node at one level in the MTree and ** denotes a node at one or more levels.
For example, given time series:
root.sg.d1.s1 root.sg.d1.s2 root.sg.d2.s2 root.sg.group1.d3.s2
Match the corresponding time series by the given path pattern:
.d1.s1 matches .d1.s1
.d1.* matches .d1.s1、.d1.s2
.*.s2 matches .d1.s2、.d2.s2
.**.s2 matches .d1.s2、.d2.s2、.group1.d3.s2
.d2.** matches .d2.s2、.group1.d3.s2
2. Overview of the design
Design path pattern parsing process and the related MTree traversal mechanism to avoid duplicate code
Provide an extensible code framework for different result set queries, and extend the result set processing by overwrite.
3. Logic of Traversal Process
Traverser based on path pattern is the process of comparing node names in MTree with targetName in a given pattern level by level.
In the case of a match between two names, the traverser process will continue; in the case of a mismatch, the nodes in the subsequent levels of the MTree and the pattern will not be compared.
Depending on the targetName, each comparison will be one of the following three cases.
targetName is **, then all children nodes of the current MNode will be processed, because ** may match one or more layers, so the nodes in the subsequent levels of MTree may still match the current **.
targetName is not ** but contains *, then all the children nodes in the current MNode that match the targetName will be processed
targetName does not contain * or **, then the children specified by targetName in the current MNode will be processed.
For the node matching the targetName, a customizable processing interface is provided, with the following two matching cases
- internal match: root.sg internal match root.sg.(pattern). A single path search process, the intermediate nodes of the path in some scenarios also need to be processed
- full match: root.sg.d full match root.sg.**(pattern). A single path full match node will be processed by a custom method
Parameter description:
- global parameters
- nodes: the targetName list obtained by cutting the given path pattern by separator
- isPrefixMatch: whether the current task is a prefix match, default is false, i.e. full path match
- Recursive parameters
- node: the current matched MTree node
- idx: the position of the targetName in the path pattern matched by the current node
- multiLevelWildcard: whether the node corresponding to the current node is `**` in the path pattern
- level: the level of the current node, root is level==0
/** * The recursive method for MTree traversal. If the node matches nodes[idx], then do some * operation and traverse the children with nodes[idx+1]. * * @param node current node that match the targetName in given path * @param idx the index of targetName in given path * @param level the level of current node in MTree * @throws MetadataException some result process may throw MetadataException */ protected void traverse(IMNode node, int idx, int level) throws MetadataException { if (processMatchedMNode(node, idx, level)) { return; } if (idx >= nodes.length - 1) { if (nodes[nodes.length - 1].equals(MULTI_LEVEL_PATH_WILDCARD) || isPrefixMatch) { processMultiLevelWildcard(node, idx, level); } return; } if (node.isMeasurement()) { return; } String targetName = nodes[idx + 1]; if (MULTI_LEVEL_PATH_WILDCARD.equals(targetName)) { processMultiLevelWildcard(node, idx, level); } else if (targetName.contains(ONE_LEVEL_PATH_WILDCARD)) { processOneLevelWildcard(node, idx, level); } else { processNameMatch(node, idx, level); } } /** * process curNode that matches the targetName during traversal. there are two cases: 1. internal * match: root.sg internal match root.sg.**(pattern) 2. full match: root.sg.d full match * root.sg.**(pattern) Both of them are default abstract and should be implemented according * concrete tasks. * * @return whether this branch of recursive traversal should stop; if true, stop */ private boolean processMatchedMNode(IMNode node, int idx, int level) throws MetadataException { if (idx < nodes.length - 1) { return processInternalMatchedMNode(node, idx, level); } else { return processFullMatchedMNode(node, idx, level); } }
3. Detailed Design
4.1 Class Diagram
4.2. Implementation
Class name | Parent | Responsibility | Related upper-level business |
MNodeLevelCounter | CounterTraverser | MNode level count | count node |
StorageGroupCounter | CounterTraverser | Storage group count | count storage group |
EntityCounter | CounterTraverser | Entity count | count device |
MeasurementCounter | CounterTraverser | Measurement count | count timeseries |
4.3 Extension
The main framework of Traverser is as follows
Method name | Responsibility |
protected void traverse(IMNode node, int idx, int level) throws MetadataException | Define the traversal logic of MTree |
protected void processMultiLevelWildcard(IMNode node, int idx, int level) throws MetadataException | Define how to handle when targetName is ** |
protected void processOneLevelWildcard(IMNode node, int idx, int level) throws MetadataException | Define how to handle when targetName is * |
protected void processNameMatch(IMNode node, int idx, int level) throws MetadataException | Define how to handle the case where the targetName does not contain a wildcard |
If you need to obtain different result sets by traversing MTree, you can extend it by overloading the following functions in Traverser.
Method name | Responsibility |
protected abstract boolean processInternalMatchedMNode(IMNode node, int idx, int level) | internal match: root.sg internal match root.sg.**(pattern) |
protected abstract boolean processFullMatchedMNode(IMNode node, int idx, int level) | full match: root.sg.d full match root.sg.**(pattern) |