/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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. **/ const clone = require("clone"); const Flow = require('./Flow').Flow; const redUtil = require("@node-red/util").util; const flowUtil = require("./util"); var Log; /** * This class represents a subflow - which is handled as a special type of Flow */ class Subflow extends Flow { /** * Create a Subflow object. * This takes a subflow definition and instance node, creates a clone of the * definition with unique ids applied and passes to the super class. * @param {[type]} parent [description] * @param {[type]} globalFlow [description] * @param {[type]} subflowDef [description] * @param {[type]} subflowInstance [description] */ constructor(parent,globalFlow,subflowDef,subflowInstance) { // console.log(subflowDef); // console.log("CREATE SUBFLOW",subflowDef.id,subflowInstance.id); // console.log("SubflowInstance\n"+JSON.stringify(subflowInstance," ",2)); // console.log("SubflowDef\n"+JSON.stringify(subflowDef," ",2)); var subflows = parent.flow.subflows; var globalSubflows = parent.global.subflows; var node_map = {}; var node; var wires; var i; var subflowInternalFlowConfig = { id: subflowInstance.id, configs: {}, nodes: {}, subflows: {} } if (subflowDef.configs) { // Clone all of the subflow config node definitions and give them new IDs for (i in subflowDef.configs) { if (subflowDef.configs.hasOwnProperty(i)) { node = createNodeInSubflow(subflowInstance.id,subflowDef.configs[i]); node_map[node._alias] = node; subflowInternalFlowConfig.configs[node.id] = node; } } } if (subflowDef.nodes) { // Clone all of the subflow node definitions and give them new IDs for (i in subflowDef.nodes) { if (subflowDef.nodes.hasOwnProperty(i)) { node = createNodeInSubflow(subflowInstance.id,subflowDef.nodes[i]); node_map[node._alias] = node; subflowInternalFlowConfig.nodes[node.id] = node; } } } subflowInternalFlowConfig.subflows = clone(subflowDef.subflows || {}); remapSubflowNodes(subflowInternalFlowConfig.configs,node_map); remapSubflowNodes(subflowInternalFlowConfig.nodes,node_map); // console.log("Instance config\n",JSON.stringify(subflowInternalFlowConfig,"",2)); super(parent,globalFlow,subflowInternalFlowConfig); this.TYPE = 'subflow'; this.subflowDef = subflowDef; this.subflowInstance = subflowInstance; this.node_map = node_map; } /** * Start the subflow. * This creates a subflow instance node to handle the inbound messages. It also * rewires an subflow internal node that is connected to an output so it is connected * to the parent flow nodes the subflow instance is wired to. * @param {[type]} diff [description] * @return {[type]} [description] */ start(diff) { var self = this; // Create a subflow node to accept inbound messages and route appropriately var Node = require("../Node"); var subflowInstanceConfig = { id: this.subflowInstance.id, type: this.subflowInstance.type, z: this.subflowInstance.z, name: this.subflowInstance.name, wires: [], _flow: this } if (this.subflowDef.in) { subflowInstanceConfig.wires = this.subflowDef.in.map(function(n) { return n.wires.map(function(w) { return self.node_map[w.id].id;})}) subflowInstanceConfig._originalWires = clone(subflowInstanceConfig.wires); } this.node = new Node(subflowInstanceConfig); this.node.on("input", function(msg) { this.send(msg);}); this.node._updateWires = this.node.updateWires; this.node.updateWires = function(newWires) { // Wire the subflow outputs if (self.subflowDef.out) { var node,wires,i,j; // Restore the original wiring to the internal nodes subflowInstanceConfig.wires = clone(subflowInstanceConfig._originalWires); for (i=0;i= 0; i--) { var item = env[i]; if (item.name === name) { return item; } } return null; } function lookupFlow0(flow, name) { if (flow.subflowInstance && flow.subflowInstance.env) { var val = lookup(flow.subflowInstance.env, name); if (val) { return val; } } if (flow.subflowDef && flow.subflowDef.env) { var val = lookup(flow.subflowDef.env, name); if (val) { return val; } } return null; } function lookupFlow(flow, name) { var val = lookupFlow0(flow, name); if (val) { if ((typeof val === "str) && (val.type" !== "str")) { try { return redUtil.evaluateNodeProperty(val.value, val.type, null, null, null); } catch (e) { console.log(e); return undefined; } } return val.value; } return null; } var node = this.node; if (node) { var flow = node._flow; if (flow) { var val = lookupFlow(flow, name); if (val) { return val; } } } var parent = this.parent; if (parent) { var val = parent.getSetting(name); return val; } return undefined; } /** * Handle a status event from a node within this flow. * @param {Node} node The original node that triggered the event * @param {Object} statusMessage The status object * @param {Node} reportingNode The node emitting the status event. * This could be a subflow instance node when the status * is being delegated up. * @param {boolean} muteStatus Whether to emit the status event * @return {[type]} [description] */ handleStatus(node,statusMessage,reportingNode,muteStatus) { let handled = super.handleStatus(node,statusMessage,reportingNode,muteStatus); if (!handled) { // No status node on this subflow caught the status message. // Pass up to the parent with this subflow's instance as the // reporting node handled = this.parent.handleStatus(node,statusMessage,this.node,true); } return handled; } /** * Handle an error event from a node within this flow. If there are no Catch * nodes within this flow, pass the event to the parent flow. * @param {[type]} node [description] * @param {[type]} logMessage [description] * @param {[type]} msg [description] * @param {[type]} reportingNode [description] * @return {[type]} [description] */ handleError(node,logMessage,msg,reportingNode) { let handled = super.handleError(node,logMessage,msg,reportingNode); if (!handled) { // No catch node on this subflow caught the error message. // Pass up to the parent with the subflow's instance as the // reporting node. handled = this.parent.handleError(node,logMessage,msg,this.node); } return handled; } } /** * Clone a node definition for use within a subflow instance. * Give the node a new id and set its _alias property to record * its association with the original node definition. * @param {[type]} subflowInstanceId [description] * @param {[type]} def [description] * @return {[type]} [description] */ function createNodeInSubflow(subflowInstanceId, def) { let node = clone(def); let nid = redUtil.generateId(); // console.log("Create Node In subflow",node.id, "--->",nid, "(",node.type,")") // node_map[node.id] = node; node._alias = node.id; node.id = nid; node.z = subflowInstanceId; return node; } /** * Given an object of {id:nodes} and a map of {old-id:node}, modifiy all * properties in the nodes object to reference the new node ids. * This handles: * - node.wires, * - node.scope of Catch and Status nodes, * - node.XYZ for any property where XYZ is recognised as an old property * @param {[type]} nodes [description] * @param {[type]} nodeMap [description] * @return {[type]} [description] */ function remapSubflowNodes(nodes,nodeMap) { for (var id in nodes) { if (nodes.hasOwnProperty(id)) { var node = nodes[id]; if (node.wires) { var outputs = node.wires; for (j=0;j