Skip to content

Commit e46b89a

Browse files
authored
Add additional functions to TwoPhaseCore. Closes #1706 (#1707)
* Add additional functions to TwoPhaseCore. Closes #1706 * Added tests for TwoPhaseCore
1 parent 07c3873 commit e46b89a

File tree

4 files changed

+371
-55
lines changed

4 files changed

+371
-55
lines changed

src/plugins/TwoPhaseCommit/CreatedNode.js

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,87 @@
22
define([
33
], function(
44
) {
5-
CreatedNode.CREATE_PREFIX = 'created_node_';
65
let counter = 0;
7-
function CreatedNode(base, parent) {
8-
this.id = CreatedNode.CREATE_PREFIX + (++counter);
9-
this.base = base;
10-
this.parent = parent;
11-
this._nodeId = null;
6+
class CreatedNode {
7+
constructor(base, parent) {
8+
this.id = CreatedNode.CREATE_PREFIX + (++counter);
9+
this.base = base;
10+
this.parent = parent;
11+
this._nodeId = null;
12+
}
13+
14+
static async getGMENode (rootNode, core, node) {
15+
return !(node instanceof CreatedNode) ?
16+
await core.loadByPath(rootNode, core.getPath(node)) :
17+
await node.toGMENode(rootNode, core);
18+
}
19+
20+
async toGMENode (rootNode, core) {
21+
if (!this._nodeId) {
22+
const parent = await CreatedNode.getGMENode(rootNode, core, this.parent);
23+
const base = await CreatedNode.getGMENode(rootNode, core, this.base);
24+
const node = core.createNode({base, parent});
25+
this._nodeId = core.getPath(node);
26+
return node;
27+
}
28+
return core.loadByPath(rootNode, this._nodeId);
29+
}
30+
31+
async getInheritedChildren (core) {
32+
if (this.base instanceof CreatedNode) {
33+
return this.base.getInheritedChildren(core);
34+
} else {
35+
return (await core.loadChildren(this.base))
36+
.map(node => new InheritedNode(node, this));
37+
}
38+
}
39+
40+
static isCreateId (id) {
41+
return (typeof id === 'string') && (id.indexOf(CreatedNode.CREATE_PREFIX) === 0);
42+
}
1243
}
44+
CreatedNode.CREATE_PREFIX = 'created_node_';
45+
46+
class InheritedNode extends CreatedNode {
47+
async toGMENode (rootNode, core) {
48+
if (!this._nodeId) {
49+
const parent = await CreatedNode.getGMENode(rootNode, core, this.parent);
50+
const base = await CreatedNode.getGMENode(rootNode, core, this.base);
51+
const children = await core.loadChildren(parent);
52+
const basePath = core.getPath(base);
53+
const node = children
54+
.find(node => core.getPath(core.getBase(node)) === basePath);
1355

14-
CreatedNode.getGMENode = async function(rootNode, core, node) {
15-
return !(node instanceof CreatedNode) ?
16-
await core.loadByPath(rootNode, core.getPath(node)) :
17-
await node.toGMENode(rootNode, core);
18-
};
56+
this._nodeId = core.getPath(node);
57+
return node;
58+
}
59+
return core.loadByPath(rootNode, this._nodeId);
60+
}
61+
}
1962

20-
CreatedNode.prototype.toGMENode = async function(rootNode, core) {
21-
if (!this._nodeId) {
22-
const parent = await CreatedNode.getGMENode(rootNode, core, this.parent);
23-
const base = await CreatedNode.getGMENode(rootNode, core, this.base);
24-
const node = core.createNode({base, parent});
25-
this._nodeId = core.getPath(node);
26-
return node;
63+
class CopiedNode extends CreatedNode {
64+
constructor(original, parent) {
65+
super(original, parent);
66+
this.parent = parent;
67+
this.original = original;
2768
}
28-
return core.loadByPath(rootNode, this._nodeId);
29-
};
3069

31-
CreatedNode.isCreateId = function (id) {
32-
return (typeof id === 'string') && (id.indexOf(CreatedNode.CREATE_PREFIX) === 0);
33-
};
70+
async toGMENode (rootNode, core) {
71+
if (!this._nodeId) {
72+
const parent = await CreatedNode.getGMENode(rootNode, core, this.parent);
73+
const original = await CreatedNode.getGMENode(rootNode, core, this.original);
74+
const node = core.copyNode(original, parent);
75+
this._nodeId = core.getPath(node);
76+
return node;
77+
}
78+
return core.loadByPath(rootNode, this._nodeId);
79+
}
80+
81+
async getInheritedChildren (/*core*/) {
82+
throw new Error('Cannot get children of copied node');
83+
}
84+
}
3485

86+
CreatedNode.CopiedNode = CopiedNode;
3587
return CreatedNode;
3688
});

src/plugins/TwoPhaseCommit/TwoPhaseCore.js

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ define([
33
'./StagedChanges',
44
'./CreatedNode',
55
'common/util/assert',
6+
'underscore',
67
], function(
78
StagedChanges,
89
CreatedNode,
910
assert,
11+
_,
1012
) {
1113
function TwoPhaseCore(logger, core) {
1214
this.logger = logger;
@@ -30,19 +32,110 @@ define([
3032

3133
passToCore('persist');
3234
passToCore('loadRoot');
33-
passToCore('loadByPath');
3435
passToCore('loadSubTree');
35-
passToCore('getPointerPath');
3636
passToCore('getParent');
3737
passToCore('getNamespace');
38-
passToCore('getMetaType');
3938
passToCore('getChildrenMeta');
4039
passToCore('getChildrenPaths');
4140
passToCore('addMember');
4241
passToCore('isMetaNode');
4342
passToCore('getOwnRegistry');
4443
passToCore('getOwnAttribute');
4544
passToCore('getAttributeNames');
45+
passToCore('getValidAttributeNames');
46+
passToCore('getValidPointerNames');
47+
48+
TwoPhaseCore.prototype.loadByPath = async function (node, id) {
49+
ensureNode(node, 'loadByPath');
50+
if (CreatedNode.isCreateId(id)) {
51+
const changesets = this.queuedChanges.concat(this);
52+
for (let i = 0; i < changesets.length; i++) {
53+
const createdNodes = changesets[i].createdNodes;
54+
const node = createdNodes.find(node => node.id === id);
55+
if (node) {
56+
return node;
57+
}
58+
}
59+
} else {
60+
return this.core.loadByPath(node, id);
61+
}
62+
};
63+
64+
TwoPhaseCore.prototype.getMetaType = function (node) {
65+
ensureNode(node, 'getMetaType');
66+
while (node instanceof CreatedNode) {
67+
node = node.base;
68+
}
69+
return this.core.getMetaType(node);
70+
};
71+
72+
TwoPhaseCore.prototype.getOwnAttributeNames = function (node) {
73+
ensureNode(node, 'getOwnAttributeNames');
74+
const isNewNode = node instanceof CreatedNode;
75+
let names = [];
76+
if (!isNewNode) {
77+
names = this.core.getOwnAttributeNames(node);
78+
}
79+
80+
function updateAttributeNames(changes={attr:{}}, names) {
81+
const [setAttrs, delAttrs] = Object.entries(changes.attr)
82+
.reduce((setAndDel, attr) => {
83+
const [sets, dels] = setAndDel;
84+
const [name, value] = attr;
85+
if (value === null) {
86+
dels.push(name);
87+
} else {
88+
sets.push(name);
89+
}
90+
return setAndDel;
91+
}, [[], []]);
92+
names = _.union(names, setAttrs);
93+
names = _.without(names, ...delAttrs);
94+
return names;
95+
}
96+
97+
this._forAllNodeChanges(
98+
node,
99+
changes => names = updateAttributeNames(changes, names)
100+
);
101+
102+
return names;
103+
};
104+
105+
TwoPhaseCore.prototype.getOwnPointerNames = function (node) {
106+
ensureNode(node, 'getOwnPointerNames');
107+
const isNewNode = node instanceof CreatedNode;
108+
let names = [];
109+
if (!isNewNode) {
110+
names = this.core.getOwnPointerNames(node);
111+
}
112+
113+
function updatePointerNames(changes={ptr:{}}, names) {
114+
const ptrNames = Object.keys(changes.ptr);
115+
return _.union(names, ptrNames);
116+
}
117+
118+
this._forAllNodeChanges(
119+
node,
120+
changes => names = updatePointerNames(changes, names)
121+
);
122+
123+
return names;
124+
};
125+
126+
TwoPhaseCore.prototype._forAllNodeChanges = function (node, fn) {
127+
const nodeId = this.getPath(node);
128+
for (let i = 0; i < this.queuedChanges.length; i++) {
129+
const changes = this.queuedChanges[i].getNodeEdits(nodeId);
130+
if (changes) {
131+
fn(changes);
132+
}
133+
}
134+
const changes = this.getChangesForNode(node);
135+
if (changes) {
136+
fn(changes);
137+
}
138+
};
46139

47140
TwoPhaseCore.prototype.getBase = function (node) {
48141
ensureNode(node, 'getBase');
@@ -100,6 +193,17 @@ define([
100193
return this.changes[nodeId];
101194
};
102195

196+
TwoPhaseCore.prototype.copyNode = function (node, parent) {
197+
assert(node, 'Cannot copy invalid node');
198+
assert(parent, 'Cannot copy node without parent');
199+
ensureNode(node, 'copyNode');
200+
ensureNode(parent, 'copyNode');
201+
202+
const newNode = new CreatedNode(node, parent);
203+
this.createdNodes.push(newNode);
204+
return newNode;
205+
};
206+
103207
TwoPhaseCore.prototype.createNode = function (desc) {
104208
const {parent, base} = desc;
105209
assert(parent, 'Cannot create node without parent');
@@ -128,7 +232,9 @@ define([
128232
.reduce((l1, l2) => l1.concat(l2));
129233

130234
let children = allCreatedNodes.filter(node => getId(node.parent) === nodeId);
131-
if (!(node instanceof CreatedNode)) {
235+
if (node instanceof CreatedNode) {
236+
children = children.concat(await node.getInheritedChildren(this.core));
237+
} else {
132238
children = children.concat(await this.core.loadChildren(node));
133239
}
134240
return children;
@@ -151,6 +257,28 @@ define([
151257
changes.attr[attr] = value;
152258
};
153259

260+
TwoPhaseCore.prototype.getPointerPath = function (node, name) {
261+
ensureNode(node, 'getPointerPath');
262+
let path = null;
263+
if (!(node instanceof CreatedNode)) {
264+
path = this.core.getPointerPath(node, name);
265+
} else if (name === 'base') {
266+
path = this.getPath(node.base);
267+
}
268+
269+
this._forAllNodeChanges(
270+
node,
271+
changes => {
272+
if (changes.ptr.hasOwnProperty(name)) {
273+
const target = changes.ptr[name];
274+
path = target && this.getPath(target);
275+
}
276+
}
277+
);
278+
279+
return path;
280+
};
281+
154282
TwoPhaseCore.prototype.getAttribute = function (node, attr) {
155283
ensureNode(node, 'getAttribute');
156284
var nodeId;

test/unit/plugins/TwoPhaseCommit/TwoPhaseCommit.spec.js

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -547,32 +547,4 @@ describe('TwoPhaseCommit', function() {
547547
assert(!childNames.includes('goodbye'), 'Included node created after save.');
548548
});
549549
});
550-
551-
describe('argument validation', function() {
552-
const methods = [
553-
'getPath',
554-
'getBase',
555-
'setAttribute',
556-
'isTypeOf',
557-
'setPointer',
558-
'deleteNode',
559-
'delAttribute',
560-
];
561-
562-
methods.forEach(method => {
563-
it(`should check node types on ${method}`, function() {
564-
const invalidNode = {relid: 'h'};
565-
assert.throws(() => plugin.core[method](invalidNode));
566-
});
567-
});
568-
569-
it('should check node types on loadChildren', async function() {
570-
const invalidNode = {relid: 'h'};
571-
try {
572-
await plugin.core.loadChildren(invalidNode);
573-
throw new Error('Did not throw exception.');
574-
} catch (err) {
575-
}
576-
});
577-
});
578550
});

0 commit comments

Comments
 (0)