diff --git a/nodes/core/core/58-debug.js b/nodes/core/core/58-debug.js index a88c6a026..77fc62b0a 100644 --- a/nodes/core/core/58-debug.js +++ b/nodes/core/core/58-debug.js @@ -149,7 +149,7 @@ DebugNode.logHandler.on("log",function(msg) { DebugNode.send(msg); } }); -RED.nodes.addLogHandler(DebugNode.logHandler); +RED.log.addHandler(DebugNode.logHandler); RED.httpAdmin.post("/debug/:id/:state", function(req,res) { var node = RED.nodes.getNode(req.params.id); diff --git a/package.json b/package.json index 57116a4de..a00f55841 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "grunt-cli": "0.1.13", "grunt-simple-mocha": "0.4.0", "mocha": "1.18.2", - "should": "3.3.1" + "should": "3.3.1", + "sinon": "1.9.1" }, "engines": { "node": ">=0.8" diff --git a/red/log.js b/red/log.js new file mode 100644 index 000000000..add74321b --- /dev/null +++ b/red/log.js @@ -0,0 +1,39 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var EventEmitter = require("events").EventEmitter; + +var logHandlers = []; + +var ConsoleLogHandler = new EventEmitter(); +ConsoleLogHandler.on("log",function(msg) { + util.log("["+msg.level+"] ["+msg.type+":"+(msg.name||msg.id)+"] "+msg.msg); +}); + +var log = module.exports = { + addHandler: function(func) { + + }, + + log: function(msg) { + for (var i in logHandlers) { + logHandlers[i].emit("log",msg); + } + } +} + +log.addHandler(ConsoleLogHandler); diff --git a/red/nodes.js b/red/nodes.js deleted file mode 100644 index be1cf9118..000000000 --- a/red/nodes.js +++ /dev/null @@ -1,536 +0,0 @@ -/** - * Copyright 2013 IBM Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ -var util = require("util"); -var EventEmitter = require("events").EventEmitter; -var fs = require("fs"); -var path = require("path"); -var clone = require("clone"); -var when = require("when"); -var whenNode = require('when/node'); - -var events = require("./events"); -var storage = null; -var settings = null; - -var registry = (function() { - var nodes = {}; - var logHandlers = []; - var obj = { - add: function(n) { - nodes[n.id] = n; - n.on("log",function(msg) { - for (var i in logHandlers) { - logHandlers[i].emit("log",msg); - } - }); - }, - get: function(i) { - return nodes[i]; - }, - clear: function() { - events.emit("nodes-stopping"); - for (var n in nodes) { - nodes[n].close(); - } - events.emit("nodes-stopped"); - nodes = {}; - }, - each: function(cb) { - for (var n in nodes) { - cb(nodes[n]); - } - }, - addLogHandler: function(handler) { - logHandlers.push(handler); - } - } - return obj; -})(); - -var ConsoleLogHandler = new EventEmitter(); -ConsoleLogHandler.on("log",function(msg) { - util.log("["+msg.level+"] ["+msg.type+":"+(msg.name||msg.id)+"] "+msg.msg); -}); - -registry.addLogHandler(ConsoleLogHandler); - -var node_type_registry = (function() { - var node_types = {}; - var node_configs = []; - var obj = { - register: function(type,node) { - util.inherits(node, Node); - node_types[type] = node; - events.emit("type-registered",type); - }, - registerConfig: function(config) { - node_configs.push(config); - }, - get: function(type) { - return node_types[type]; - }, - getNodeConfigs: function() { - var result = ""; - for (var i=0;i 0) { - var i = missingTypes.indexOf(type); - if (i != -1) { - missingTypes.splice(i,1); - util.log("[red] Missing type registered: "+type); - } - if (missingTypes.length == 0) { - parseConfig(); - } - } -}); - -function getNode(nid) { - return registry.get(nid); -} - -function stopFlows() { - if (activeConfig&&activeConfig.length > 0) { - util.log("[red] Stopping flows"); - } - registry.clear(); -} - -function setConfig(conf) { - stopFlows(); - activeConfig = conf; - - if (!storage) { - // Do this lazily to ensure the storage provider as been initialised - storage = require("./storage"); - } - storage.getCredentials().then(function(creds) { - credentials = creds; - parseConfig(); - }).otherwise(function(err) { - util.log("[red] Error loading credentials : "+err); - }); -} - -function getConfig() { - return activeConfig; -} - -var parseConfig = function() { - missingTypes = []; - for (var i in activeConfig) { - var type = activeConfig[i].type; - // TODO: remove workspace in next release+1 - if (type != "workspace" && type != "tab") { - var nt = node_type_registry.get(type); - if (!nt && missingTypes.indexOf(type) == -1) { - missingTypes.push(type); - } - } - }; - if (missingTypes.length > 0) { - util.log("[red] Waiting for missing types to be registered:"); - for (var i in missingTypes) { - util.log("[red] - "+missingTypes[i]); - } - return; - } - - util.log("[red] Starting flows"); - events.emit("nodes-starting"); - for (var i in activeConfig) { - var nn = null; - // TODO: remove workspace in next release+1 - if (activeConfig[i].type != "workspace" && activeConfig[i].type != "tab") { - var nt = node_type_registry.get(activeConfig[i].type); - if (nt) { - try { - nn = new nt(activeConfig[i]); - } - catch (err) { - util.log("[red] "+activeConfig[i].type+" : "+err); - } - } - // console.log(nn); - if (nn == null) { - util.log("[red] unknown type: "+activeConfig[i].type); - } - } - } - // Clean up any orphaned credentials - var deletedCredentials = false; - for (var c in credentials) { - var n = registry.get(c); - if (!n) { - deletedCredentials = true; - delete credentials[c]; - } - } - if (deletedCredentials) { - storage.saveCredentials(credentials); - } - events.emit("nodes-started"); -} - - -module.exports = { - Node:Node, - addCredentials: addCredentials, - getCredentials: getCredentials, - deleteCredentials: deleteCredentials, - createNode: createNode, - registerType: node_type_registry.register, - getType: node_type_registry.get, - getNodeConfigs: node_type_registry.getNodeConfigs, - addLogHandler: registry.addLogHandler, - load: load, - getNode: getNode, - stopFlows: stopFlows, - setConfig: setConfig, - getConfig: getConfig -} - diff --git a/red/nodes/Node.js b/red/nodes/Node.js new file mode 100644 index 000000000..a21e8d8fa --- /dev/null +++ b/red/nodes/Node.js @@ -0,0 +1,119 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var EventEmitter = require("events").EventEmitter; +var clone = require("clone"); + +var flows = require("./flows"); + + + +function Node(n) { + this.id = n.id; + flows.add(this); + this.type = n.type; + if (n.name) { + this.name = n.name; + } + this.wires = n.wires||[]; +} +util.inherits(Node,EventEmitter); + +Node.prototype.close = function() { + // called when a node is removed + this.emit("close"); +} + + +Node.prototype.send = function(msg) { + // instanceof doesn't work for some reason here + if (msg == null) { + msg = []; + } else if (!util.isArray(msg)) { + msg = [msg]; + } + for (var i in this.wires) { + var wires = this.wires[i]; + if (i < msg.length) { + if (msg[i] != null) { + var msgs = msg[i]; + if (!util.isArray(msg[i])) { + msgs = [msg[i]]; + } + //if (wires.length == 1) { + // // Single recipient, don't need to clone the message + // var node = flows.get(wires[0]); + // if (node) { + // for (var k in msgs) { + // var mm = msgs[k]; + // node.receive(mm); + // } + // } + //} else { + // Multiple recipients, must send message copies + for (var j in wires) { + var node = flows.get(wires[j]); + if (node) { + for (var k in msgs) { + var mm = msgs[k]; + // Temporary fix for #97 + // TODO: remove this http-node-specific fix somehow + var req = mm.req; + var res = mm.res; + delete mm.req; + delete mm.res; + var m = clone(mm); + if (req) { + m.req = req; + mm.req = req; + } + if (res) { + m.res = res; + mm.res = res; + } + node.receive(m); + } + } + } + //} + } + } + } +} + +Node.prototype.receive = function(msg) { + this.emit("input",msg); +} + +Node.prototype.log = function(msg) { + var o = {level:'log',id:this.id, type:this.type, msg:msg}; + if (this.name) o.name = this.name; + this.emit("log",o); +} +Node.prototype.warn = function(msg) { + var o = {level:'warn',id:this.id, type:this.type, msg:msg}; + if (this.name) o.name = this.name; + this.emit("log",o); +} +Node.prototype.error = function(msg) { + var o = {level:'error',id:this.id, type:this.type, msg:msg}; + if (this.name) o.name = this.name; + this.emit("log",o); +} + + +module.exports = Node; diff --git a/red/nodes/credentials.js b/red/nodes/credentials.js new file mode 100644 index 000000000..848334d37 --- /dev/null +++ b/red/nodes/credentials.js @@ -0,0 +1,62 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var credentials = {}; +var storage = null; + +module.exports = { + init: function(_storage) { + storage = _storage; + }, + load: function() { + return storage.getCredentials().then(function(creds) { + credentials = creds; + }).otherwise(function(err) { + util.log("[red] Error loading credentials : "+err); + }); + }, + add: function(id,creds) { + credentials[id] = creds; + storage.saveCredentials(credentials); + }, + + get: function(id) { + return credentials[id]; + }, + + delete: function(id) { + delete credentials[id]; + storage.saveCredentials(credentials); + }, + + clean: function(getNode) { + var deletedCredentials = false; + for (var c in credentials) { + var n = getNode(c); + console.log(c,n) + if (!n) { + deletedCredentials = true; + delete credentials[c]; + } + } + if (deletedCredentials) { + storage.saveCredentials(credentials); + } + + } +} diff --git a/red/nodes/flows.js b/red/nodes/flows.js new file mode 100644 index 000000000..583566345 --- /dev/null +++ b/red/nodes/flows.js @@ -0,0 +1,150 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var when = require("when"); + +var typeRegistry = require("./registry"); +var credentials = require("./credentials"); +var log = require("../log"); +var events = require("../events"); + +var storage = null; + +var nodes = {}; +var activeConfig = []; +var missingTypes = []; + +events.on('type-registered',function(type) { + if (missingTypes.length > 0) { + var i = missingTypes.indexOf(type); + if (i != -1) { + missingTypes.splice(i,1); + util.log("[red] Missing type registered: "+type); + } + if (missingTypes.length == 0) { + parseConfig(); + } + } +}); + + +var parseConfig = function() { + missingTypes = []; + for (var i in activeConfig) { + var type = activeConfig[i].type; + // TODO: remove workspace in next release+1 + if (type != "workspace" && type != "tab") { + var nt = typeRegistry.get(type); + if (!nt && missingTypes.indexOf(type) == -1) { + missingTypes.push(type); + } + } + }; + if (missingTypes.length > 0) { + util.log("[red] Waiting for missing types to be registered:"); + for (var i in missingTypes) { + util.log("[red] - "+missingTypes[i]); + } + return; + } + + util.log("[red] Starting flows"); + events.emit("nodes-starting"); + for (var i in activeConfig) { + var nn = null; + // TODO: remove workspace in next release+1 + if (activeConfig[i].type != "workspace" && activeConfig[i].type != "tab") { + var nt = typeRegistry.get(activeConfig[i].type); + if (nt) { + try { + nn = new nt(activeConfig[i]); + } + catch (err) { + util.log("[red] "+activeConfig[i].type+" : "+err); + } + } + // console.log(nn); + if (nn == null) { + util.log("[red] unknown type: "+activeConfig[i].type); + } + } + } + // Clean up any orphaned credentials + credentials.clean(flowNodes.get); + events.emit("nodes-started"); +} + + +function stopFlows() { + if (activeConfig&&activeConfig.length > 0) { + util.log("[red] Stopping flows"); + } + flowNodes.clear(); +} + +var flowNodes = module.exports = { + init: function(_storage) { + storage = _storage; + }, + load: function() { + return storage.getFlows().then(function(flows) { + return credentials.load().then(function() { + activeConfig = flows; + if (activeConfig && activeConfig.length > 0) { + parseConfig(); + } + }); + }).otherwise(function(err) { + util.log("[red] Error loading flows : "+err); + }); + }, + add: function(n) { + nodes[n.id] = n; + n.on("log",log.log); + }, + get: function(i) { + return nodes[i]; + }, + clear: function() { + events.emit("nodes-stopping"); + for (var n in nodes) { + nodes[n].close(); + } + events.emit("nodes-stopped"); + nodes = {}; + }, + each: function(cb) { + for (var n in nodes) { + cb(nodes[n]); + } + }, + addLogHandler: function(handler) { + logHandlers.push(handler); + }, + + getFlows: function() { + return activeConfig; + }, + setFlows: function(conf) { + return storage.saveFlows(conf).then(function() { + stopFlows(); + activeConfig = conf; + parseConfig(); + }) + }, + stopFlows: stopFlows +} diff --git a/red/nodes/index.js b/red/nodes/index.js new file mode 100644 index 000000000..1960f5b70 --- /dev/null +++ b/red/nodes/index.js @@ -0,0 +1,50 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +var registry = require("./registry"); +var credentials = require("./credentials"); +var flows = require("./flows"); +var Node = require("./Node"); + + +function createNode(node,def) { + Node.call(node,def); +} + +function init(_settings,storage) { + credentials.init(storage); + flows.init(storage); + registry.init(_settings); +} + + +module.exports = { + init: init, + load: registry.load, + addCredentials: credentials.add, + getCredentials: credentials.get, + deleteCredentials: credentials.delete, + createNode: createNode, + registerType: registry.registerType, + getType: registry.get, + getNodeConfigs: registry.getNodeConfigs, + getNode: flows.get, + + loadFlows: flows.load, + stopFlows: flows.stopFlows, + setFlows: flows.setFlows, + getFlows: flows.getFlows +} + diff --git a/red/nodes/registry.js b/red/nodes/registry.js new file mode 100644 index 000000000..c467c1833 --- /dev/null +++ b/red/nodes/registry.js @@ -0,0 +1,257 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var when = require("when"); +var whenNode = require('when/node'); +var fs = require("fs"); +var path = require("path"); +var events = require("../events"); + +var Node; +var settings; + +var node_types = {}; +var node_configs = []; + +function loadTemplate(templateFilename) { + return when.promise(function(resolve,reject) { + whenNode.call(fs.readFile,templateFilename,'utf8').done(function(content) { + typeRegistry.registerConfig(content); + resolve(); + }, function(err) { + reject("missing template file"); + }); + + }); +} + +function loadNode(nodeDir, nodeFn) { + return when.promise(function(resolve,reject) { + if (settings.nodesExcludes) { + for (var i=0;i 0) { util.log("------------------------------------------"); if (settings.verbose) { @@ -84,13 +84,8 @@ function start() { util.log("------------------------------------------"); } defer.resolve(); - storage.getFlows().then(function(flows) { - if (flows.length > 0) { - redNodes.setConfig(flows); - } - }).otherwise(function(err) { - util.log("[red] Error loading flows : "+err); - }); + + redNodes.loadFlows(); }); }); diff --git a/test/credentials_spec.js b/test/credentials_spec.js new file mode 100644 index 000000000..8a34c2b09 --- /dev/null +++ b/test/credentials_spec.js @@ -0,0 +1,95 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var sinon = require("sinon"); +var when = require("when"); + +var credentials = require("../red/nodes/credentials"); + +describe('Credentials', function() { + + it('loads from storage',function(done) { + + var storage = { + getCredentials: function() { + console.log("ONE"); + return when.promise(function(resolve,reject) { + resolve({"a":{"b":1,"c":2}}); + }); + } + }; + + credentials.init(storage); + + credentials.load().then(function() { + + credentials.get("a").should.have.property('b',1); + credentials.get("a").should.have.property('c',2); + + done(); + }); + }); + + + it('saves to storage', function(done) { + var storage = { + getCredentials: function() { + return when.promise(function(resolve,reject) { + resolve({"a":{"b":1,"c":2}}); + }); + }, + saveCredentials: function(creds) { + return when(true); + } + }; + sinon.spy(storage,"saveCredentials"); + credentials.init(storage); + credentials.load().then(function() { + should.not.exist(credentials.get("b")) + credentials.add('b',{"d":3}); + storage.saveCredentials.callCount.should.be.exactly(1); + credentials.get("b").should.have.property('d',3); + done(); + }); + }); + + it('deletes from storage', function(done) { + var storage = { + getCredentials: function() { + return when.promise(function(resolve,reject) { + resolve({"a":{"b":1,"c":2}}); + }); + }, + saveCredentials: function(creds) { + return when(true); + } + }; + sinon.spy(storage,"saveCredentials"); + credentials.init(storage); + credentials.load().then(function() { + should.exist(credentials.get("a")) + credentials.delete('a'); + storage.saveCredentials.callCount.should.be.exactly(1); + should.not.exist(credentials.get("a")); + done(); + }); + + }); + +}) + + diff --git a/test/node_registry_spec.js b/test/node_registry_spec.js index a8dfb426f..c83ac15e8 100644 --- a/test/node_registry_spec.js +++ b/test/node_registry_spec.js @@ -1,7 +1,23 @@ -var should = require("should"); -var RedNodes = require("../red/nodes.js"); +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ -var RedNode = RedNodes.Node; +var should = require("should"); +var RedNodes = require("../red/nodes"); + +var RedNode = require("../red/nodes/Node"); describe('NodeRegistry', function() { it('automatically registers new nodes',function() { diff --git a/test/node_spec.js b/test/node_spec.js index 2758fb446..f72cc08af 100644 --- a/test/node_spec.js +++ b/test/node_spec.js @@ -1,7 +1,21 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + var should = require("should"); -var RedNodes = require("../red/nodes.js"); - -var RedNode = RedNodes.Node; +var RedNode = require("../red/nodes/Node"); describe('Node', function() { describe('#constructor',function() {