fbp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/fbp.rb +464 -0
- data/lib/fbp/aggregator-node.rb +31 -0
- data/lib/fbp/assign-node.rb +41 -0
- data/lib/fbp/concatenate-node.rb +66 -0
- data/lib/fbp/constants.rb +13 -0
- data/lib/fbp/counter-node.rb +50 -0
- data/lib/fbp/decode-node.rb +34 -0
- data/lib/fbp/encode-node.rb +31 -0
- data/lib/fbp/flow-node.rb +369 -0
- data/lib/fbp/fpb-thread-pool.rb +91 -0
- data/lib/fbp/selector-node.rb +131 -0
- data/lib/fbp/sort-node.rb +68 -0
- data/lib/fbp/text_file_reader_node.rb +80 -0
- data/lib/fbp/text_file_writer_node.rb +65 -0
- data/lib/fbp/version.rb +3 -0
- data/lib/thread-pool/thread-pool.rb +150 -0
- metadata +91 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Fbp
|
2
|
+
=begin
|
3
|
+
=== Description
|
4
|
+
The Aggregator node puts itself into a transaction which will cause all of
|
5
|
+
the incoming IPs to be aggregated until the upstream node has completed then
|
6
|
+
the aggregated IPs will be sent on to the down stream node(s)
|
7
|
+
=end
|
8
|
+
class Aggregator_node < Node
|
9
|
+
=begin rdoc
|
10
|
+
When a Aggregator_node is created, it is immediately put into a transaction.
|
11
|
+
This allows the Aggregator_node to aggregate all of the IPs that come to it
|
12
|
+
until the up stream node completes its work
|
13
|
+
=end
|
14
|
+
def initialize()
|
15
|
+
super()
|
16
|
+
write_to_input({:begin_transaction => true})
|
17
|
+
end
|
18
|
+
|
19
|
+
=begin rdoc
|
20
|
+
Once the up stream node completes. The transaction will end and
|
21
|
+
all of the IPs that had been accumulated during the transaction will
|
22
|
+
be sent to the down stream node.
|
23
|
+
=end
|
24
|
+
def do_node_work(args)
|
25
|
+
args.delete :completed
|
26
|
+
write_to_output(args)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Fbp
|
2
|
+
=begin rdoc
|
3
|
+
== Description
|
4
|
+
The Assign_node will take its incoming IPs and add items to the hash
|
5
|
+
before passing it one to its down stream node(s)
|
6
|
+
|
7
|
+
== Discussion
|
8
|
+
The Assign_node takes an optional IIP of the form <tt>{:additional_args =>{}}</tt>
|
9
|
+
|
10
|
+
The hash contains the items that will be added to every incoming
|
11
|
+
IP sent to this node. The IIP is optional with this node. If no
|
12
|
+
additional arguments are given to this node then it will simply write all
|
13
|
+
of its in coming IPs to its output.
|
14
|
+
=end
|
15
|
+
class Assign_node < Node
|
16
|
+
|
17
|
+
=begin rdoc
|
18
|
+
When creating a new Assign_node instance one can optionally provide a hash
|
19
|
+
containing the items to be added to every in coming IP
|
20
|
+
=end
|
21
|
+
def initialize(additional_args = nil)
|
22
|
+
super()
|
23
|
+
@options[:additional_args] = additional_args if !additional_args.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
=begin rdoc
|
27
|
+
If an IIP has set the :additional_args key in options, then those items
|
28
|
+
will be appended to every incoming IP. If the :additional_args key has
|
29
|
+
not been set then the IP will be passed downstream untouched.
|
30
|
+
|
31
|
+
If the IP has an :ips key then each of those IP in the array of IPs
|
32
|
+
will be modified as described above.
|
33
|
+
=end
|
34
|
+
def do_node_work(args)
|
35
|
+
ips = args.has_key? :ips ? args[:ips] : [args]
|
36
|
+
ips.each {|ip| ip.merge!(@options[:additional_args]) if @options.has_key? :additional_args}
|
37
|
+
super(args)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
module Fbp
|
3
|
+
|
4
|
+
=begin
|
5
|
+
=== Description
|
6
|
+
The Concatenate_node takes a list of keys as it IIP and as IPs are sent
|
7
|
+
to this node, it will check to see if th IP has any of the keys specified
|
8
|
+
in the IIP. If there is a match then the IP is merged into a single IP.
|
9
|
+
Once all of the keys that are specified in the IIP are in the merged IP,
|
10
|
+
it is sent to the down stream node.
|
11
|
+
|
12
|
+
An example of the usage of this node would be if the down stream node
|
13
|
+
was an encryption node. The encryption node might require an IIP with
|
14
|
+
keys of key_data, salt, mode, and padding. This concatenate node
|
15
|
+
could be used to gather all of that dat into an IIP to send to the
|
16
|
+
encryption node.
|
17
|
+
=end
|
18
|
+
class Concatenate_node < Node
|
19
|
+
|
20
|
+
=begin rdoc
|
21
|
+
When creating a new Concatenate_node instance one can provide an array of
|
22
|
+
keys that will be used to determine if all of the necessary data has
|
23
|
+
been cached before creating a new IP to send down stream.
|
24
|
+
=end
|
25
|
+
def initialize(keys = nil)
|
26
|
+
super()
|
27
|
+
@options[:keys] = keys if !keys.nil?
|
28
|
+
@output_ip = Hash.new
|
29
|
+
end
|
30
|
+
|
31
|
+
=begin rdoc
|
32
|
+
This is called to see if this node has been parameterized with the
|
33
|
+
required keys needed for this node to do its work
|
34
|
+
=end
|
35
|
+
def is_ready_to_run?
|
36
|
+
@options.has_key? :keys
|
37
|
+
end
|
38
|
+
|
39
|
+
=begin rdoc
|
40
|
+
This will do the work of determining if all of the required data
|
41
|
+
has been acquired before creating an IP to send down stream.
|
42
|
+
=end
|
43
|
+
def do_node_work(args)
|
44
|
+
# Merge the IP into Concatenate_node the output hash
|
45
|
+
ips = nil
|
46
|
+
|
47
|
+
if args.has_key? :ips
|
48
|
+
ips = args[:ips]
|
49
|
+
temp = args.clone
|
50
|
+
temp.delete(:ips)
|
51
|
+
ips << temp if !temp.empty?
|
52
|
+
else
|
53
|
+
ips = [args]
|
54
|
+
end
|
55
|
+
|
56
|
+
ips.each {|ip| @output_ip.merge!(ip)}
|
57
|
+
# Check to see if the output_ip hash has all of the required keys.
|
58
|
+
# If it does, then write the output_ip hash to the output queue
|
59
|
+
if @options[:keys] == (@options[:keys] & @output_ip.keys)
|
60
|
+
write_to_output(@output_ip)
|
61
|
+
@output_ip.clear
|
62
|
+
end
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Fbp
|
2
|
+
=begin
|
3
|
+
=== Description
|
4
|
+
The counter node counts either incoming IPs or IPs that
|
5
|
+
contain a certain key and then adds that count data to
|
6
|
+
the incoming IPs
|
7
|
+
=end
|
8
|
+
class Counter_node < Node
|
9
|
+
|
10
|
+
=begin rdoc
|
11
|
+
When creating a new Counter_node instance one can optionally provide a
|
12
|
+
symbol which would be used to match a key in an incoming IP. The IP
|
13
|
+
would then only be counted if the IP contained that key.
|
14
|
+
=end
|
15
|
+
def initialize(key = nil)
|
16
|
+
super()
|
17
|
+
@options[:key] = key if !key.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
=begin rdoc
|
21
|
+
When an IP is received if a key has been set then the IP will be
|
22
|
+
checked to see if it contains that key. If so then it is counted.
|
23
|
+
|
24
|
+
If the incoming IP has the :ips key then the nested IPs will be
|
25
|
+
search for the matching key and counted if found.
|
26
|
+
|
27
|
+
In either a single IP or an IP with the :ips key is received and
|
28
|
+
no key has been set, then all IPs will be counted.
|
29
|
+
=end
|
30
|
+
def do_node_work(args)
|
31
|
+
counter = 0
|
32
|
+
ips = args.has_key? :ips ? args[:ips] : [args]
|
33
|
+
|
34
|
+
key = nil
|
35
|
+
key = @options[:key] if @options.has_key?(:key)
|
36
|
+
|
37
|
+
ips.each do |ip|
|
38
|
+
if key.nil?
|
39
|
+
counter += 1
|
40
|
+
else
|
41
|
+
counter += 1 if ip.has_key?(key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
args[:counter] = counter
|
46
|
+
super(args)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Fbp
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
=== Description
|
7
|
+
The Decode node takes it incoming IP that is an JSON encoded IP and decodes it
|
8
|
+
into a Ruby hash to be sent on to the downstream node.
|
9
|
+
|
10
|
+
This node would be used when receiving an IP from another machine or another process
|
11
|
+
on the same machine.
|
12
|
+
=end
|
13
|
+
class Decode_node < Node
|
14
|
+
|
15
|
+
=begin rdoc
|
16
|
+
When an IP comes in it will be checked for the :json key. If is exists
|
17
|
+
then the data will be extracted and the reconstituted data will be sent
|
18
|
+
to the down stream node. If the IP does not have the :json key it will
|
19
|
+
be sent to the down stream node as is.
|
20
|
+
=end
|
21
|
+
def do_node_work(args)
|
22
|
+
return super(args) if !args.has_key?(:json)
|
23
|
+
decoded_obj = nil
|
24
|
+
begin
|
25
|
+
decoded_obj = JSON.parse(args)
|
26
|
+
rescue
|
27
|
+
decoded_obj = nil
|
28
|
+
end
|
29
|
+
super(decoded_obj)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Fbp
|
4
|
+
=begin
|
5
|
+
=== Description
|
6
|
+
The Encode node takes it incoming IP and encodes the IP into a JSON string. This data
|
7
|
+
is then added to the incoming IP and sent to the downstream node.
|
8
|
+
|
9
|
+
This node would be used when sending IPs to another machine or another process
|
10
|
+
on the same machine.
|
11
|
+
=end
|
12
|
+
class Encode_node < Node
|
13
|
+
|
14
|
+
=begin rdoc
|
15
|
+
When an IP comes in it will be converted into a JSON text object.
|
16
|
+
That text object will then be append to the incoming IP with the
|
17
|
+
:json key and sent to the down stream node.
|
18
|
+
=end
|
19
|
+
def do_node_work(args)
|
20
|
+
encoded_obj = nil
|
21
|
+
begin
|
22
|
+
encoded_obj = args.to_json
|
23
|
+
rescue
|
24
|
+
encoded_obj = nil
|
25
|
+
end
|
26
|
+
args.merge!({:json => encoded_obj}) if !encoded_obj.nil?
|
27
|
+
super(args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,369 @@
|
|
1
|
+
|
2
|
+
module Fbp
|
3
|
+
private
|
4
|
+
def self.add_node_to_flow_desc(node, flow_desc) #:nodoc:
|
5
|
+
return if node.nil? || flow_desc.nil?
|
6
|
+
|
7
|
+
class_symbol = node.class.name.to_sym
|
8
|
+
|
9
|
+
# Add the input node first
|
10
|
+
node_record = {:node => class_symbol, :options => node.options, :object_id => node.object_id, :input => nil}
|
11
|
+
flow_desc << node_record
|
12
|
+
|
13
|
+
# Recursively add all the nodes in the network of nodes
|
14
|
+
node.output.each_value do |channel|
|
15
|
+
next if channel.empty?
|
16
|
+
channel.each {|a_node| add_node_to_flow_desc(a_node, flow_desc)}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def self.set_input_array(node, lookup_data, flow_desc) #:nodoc:
|
22
|
+
return if node.nil? || lookup_data.nil? || flow_desc.nil?
|
23
|
+
node_index = lookup_data[node.object_id]
|
24
|
+
|
25
|
+
node.output.each_key do |key|
|
26
|
+
node_array = node.output[key]
|
27
|
+
next if node_array.nil?
|
28
|
+
node_array.each do |n|
|
29
|
+
n_index = lookup_data[n.object_id]
|
30
|
+
fr = flow_desc[n_index]
|
31
|
+
input_array = fr[:input]
|
32
|
+
input_array = Array.new if input_array.nil?
|
33
|
+
input_array << {node_index => key}
|
34
|
+
fr[:input] = input_array
|
35
|
+
set_input_array(n, lookup_data, flow_desc)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
=begin rdoc
|
41
|
+
Given a Fbp node that is the beginning of a network of Fbp nodes, the Fbp::make_flow_description
|
42
|
+
function will create a Flow description record.
|
43
|
+
|
44
|
+
== Discussion
|
45
|
+
<b>Flow Description Record</b>
|
46
|
+
[{:node => symbol_of_the_Fbp_node_class_name, :options =>{class_specific_hash}, :input => nil || [{index => output_channel_name_symbol}]}]
|
47
|
+
|
48
|
+
+:node+ This is a symbol that is the name of the Fbp node to be instantiated.
|
49
|
+
|
50
|
+
+:options+ This is a hash which is the IIP needed to parameterize the newly instantiated node.
|
51
|
+
|
52
|
+
+:input+ This is either nil or an array of hashes. Each hash is keyed by the index in the array of nodes that are described by the Flow Description Record with the value being the symbol name of the output channel from which to get the input for the node.
|
53
|
+
=end
|
54
|
+
public
|
55
|
+
def self.make_flow_description(node)
|
56
|
+
return nil if node.nil?
|
57
|
+
flow_desc = Array.new
|
58
|
+
|
59
|
+
# Add the nodes to flow_desc
|
60
|
+
add_node_to_flow_desc(node, flow_desc)
|
61
|
+
|
62
|
+
# Make a hash keyed by an object_id with the value of the index of the object in the flow_desc array
|
63
|
+
lookup_data = Hash.new
|
64
|
+
flow_desc.each_index do |idx|
|
65
|
+
lookup_data[flow_desc[idx][:object_id]] = idx
|
66
|
+
end
|
67
|
+
|
68
|
+
set_input_array(node, lookup_data, flow_desc)
|
69
|
+
flow_desc
|
70
|
+
end
|
71
|
+
|
72
|
+
=begin rdoc
|
73
|
+
Given a Flow Description Record, and a full path to a file, write out the
|
74
|
+
Flow Description Record to the file system.
|
75
|
+
|
76
|
+
See the make_flow_description documentation for a discussion of what
|
77
|
+
a Flow Description Record.
|
78
|
+
=end
|
79
|
+
public
|
80
|
+
def self.write_flow_desc_to_file(flow_desc, file_path)
|
81
|
+
return false if flow_desc.nil? || file_path.nil?
|
82
|
+
|
83
|
+
flow_data = Marshal.dump(flow_desc)
|
84
|
+
|
85
|
+
result = false
|
86
|
+
begin
|
87
|
+
File.open(file_path, 'w') {|f| f.write(flow_data)}
|
88
|
+
result = true
|
89
|
+
rescue
|
90
|
+
result = false
|
91
|
+
end
|
92
|
+
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
=begin rdoc
|
97
|
+
Given a full path to a file that contains A previously written out
|
98
|
+
Flow Description Record, this function will read in that file and
|
99
|
+
create a Flow Description Record from the data.
|
100
|
+
|
101
|
+
See the make_flow_description documentation for a discussion of what
|
102
|
+
a Flow Description Record.
|
103
|
+
=end
|
104
|
+
public
|
105
|
+
def self.read_flow_desc_from_file(file_path)
|
106
|
+
return nil if file_path.nil?
|
107
|
+
encoded_data = nil
|
108
|
+
|
109
|
+
begin
|
110
|
+
File.open(file_path, 'r') {|f| encoded_data = f.read()}
|
111
|
+
rescue
|
112
|
+
encoded_data = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
return nil if encoded_data.nil?
|
116
|
+
|
117
|
+
Marshal.load(encoded_data)
|
118
|
+
end
|
119
|
+
|
120
|
+
=begin rdoc
|
121
|
+
== Description
|
122
|
+
The flow node provides the ability of taking a network of Fbp nodes and wrap that network in a
|
123
|
+
single node. This is done by creating a Flow description record. A Flow description record
|
124
|
+
is a data structure that describes an entire network of Fbp noes. Given a Flow description record
|
125
|
+
the Flow node class will create the entire network and then wrap that network so that writing to the
|
126
|
+
input of a Flow node instance will forward that input to the first node in the created network.
|
127
|
+
The output of the created network will be forwarded to the output of the Flow node instance.
|
128
|
+
|
129
|
+
There are module functions associated with the Flow node class. These functions provide the ability
|
130
|
+
to take a Fbp network and create a Flow description record that describes the network. There are
|
131
|
+
also functions that take a Flow description record and write them to the file system and read a
|
132
|
+
file with a Flow description record and re-create the Flow description record.
|
133
|
+
|
134
|
+
== Discussion
|
135
|
+
A Flow description record is an Array of Hashes with the following keys
|
136
|
+
|
137
|
+
<b>Flow Description Record</b>
|
138
|
+
[{:node => symbol_of_the_Fbp_node_class_name, :options =>{class_specific_hash}, :input => nil || [{index => output_channel_name_symbol}]}]
|
139
|
+
|
140
|
+
+:node+ This is a symbol that is the name of the Fbp node to be instantiated.
|
141
|
+
|
142
|
+
+:options+ This is a hash which is the IIP needed to parameterize the newly instantiated node.
|
143
|
+
|
144
|
+
+:input+ This is either nil or an array of hashes. Each hash is keyed by the index in the array of nodes that are described by the Flow Description Record with the value being the symbol name of the output channel from which to get the input for the node.
|
145
|
+
|
146
|
+
=== Example Flow Description Record
|
147
|
+
The following is a simple network that will read in a file and split the file into two based upon a selector.
|
148
|
+
The two outputs are sorted using the Sort Node and the output of the two different data streams and then writen
|
149
|
+
out to different two files.
|
150
|
+
|
151
|
+
======================================================
|
152
|
+
| {:selectors=>[{:data=>{:comparison=>Fbp::CONTAINS, |
|
153
|
+
============== | {:comparison=>Fbp::CONTAINS, :value=>"Magic", |
|
154
|
+
| ~/input.txt |=== | :match=>:magic, :reject=>:output}}] |
|
155
|
+
============== | ======================================================
|
156
|
+
| (IIP) | (IIP)
|
157
|
+
\/ \/
|
158
|
+
============================== ======================
|
159
|
+
| | output to input | |
|
160
|
+
| Fbp::Test_file_reader_node |==================> | Fbp::Selector_node |
|
161
|
+
| | | |
|
162
|
+
============================== ======================
|
163
|
+
| |
|
164
|
+
output to input | |
|
165
|
+
===================================================== |
|
166
|
+
| magic to input |
|
167
|
+
| =========================================================
|
168
|
+
| |
|
169
|
+
| |
|
170
|
+
| |
|
171
|
+
| | ======================= ================================================================
|
172
|
+
| | | {:sort_keys=>:data} | | {:file_name=>"/Users/local/magic.txt", :file_open_mode=>"w"} |
|
173
|
+
| | ======================= ================================================================
|
174
|
+
| | | (IIP) | (IIP)
|
175
|
+
| | \/ \/
|
176
|
+
| | =================== =============================
|
177
|
+
| ==> | "Fbp::Sort_node |=================>| Fbp::Text_file_writer_node |===> nil
|
178
|
+
| =================== =============================
|
179
|
+
|
|
180
|
+
| ======================= =================================================================
|
181
|
+
| | {:sort_keys=>:data} | | {:file_name=>"/Users/local/muggle.txt", :file_open_mode=>"w"} |
|
182
|
+
| ======================= =================================================================
|
183
|
+
| | (IIP) | (IIP)
|
184
|
+
| \/ \/
|
185
|
+
| =================== ==============================
|
186
|
+
====> | "Fbp::Sort_node |================>| Fbp::Text_file_writer_node | ===> nil
|
187
|
+
=================== ==============================
|
188
|
+
|
189
|
+
The beginning node in this network is the Fbp::Test_file_reader_node node. If that node is used to call
|
190
|
+
The Fbp::make_flow_description function, the following will be returned.
|
191
|
+
|
192
|
+
[
|
193
|
+
{:node=>:"Fbp::Test_file_reader_node",
|
194
|
+
:options=>{:file_name=>"/Users/local/input.txt"},
|
195
|
+
:input=>nil},
|
196
|
+
|
197
|
+
{:node=>:"Fbp::Selector_node",
|
198
|
+
:options=>{:selectors=>[{:data=>{:comparison=>6, :value=>"Magic", :match=>:magic, :reject=>:output}}]},
|
199
|
+
:input=>[{0=>:output}]},
|
200
|
+
|
201
|
+
{:node=>:"Fbp::Sort_node",
|
202
|
+
:options=>{:sort_keys=>:data},
|
203
|
+
:input=>[{1=>:output}]},
|
204
|
+
|
205
|
+
{:node=>:"Fbp::Text_file_writer_node",
|
206
|
+
:options=>{:file_name=>"/Users/local/muggle.txt", :file_open_mode=>"w"},
|
207
|
+
:input=>[{2=>:output}]},
|
208
|
+
|
209
|
+
{:node=>:"Fbp::Sort_node",
|
210
|
+
:options=>{:sort_keys=>:data},
|
211
|
+
:input=>[{1=>:magic}]},
|
212
|
+
|
213
|
+
{:node=>:"Fbp::Text_file_writer_node",
|
214
|
+
:options=>{:file_name=>"/Users/local/magic.txt", :file_open_mode=>"w"},
|
215
|
+
:input=>[{4=>:output}]}
|
216
|
+
]
|
217
|
+
|
218
|
+
There are some important things to note about this Flow Description Record.
|
219
|
+
Each node is represented by a Hash. That hash has three items as noted
|
220
|
+
previously. The order of the hashes in the array is important. It allows
|
221
|
+
the :input key to be based upon the index of the node from which the
|
222
|
+
referring node will receive its input.
|
223
|
+
|
224
|
+
For example the Fbp::Selector_node which is the second node in the array
|
225
|
+
will receive its input from the Fbp::Test_file_reader_node node which is
|
226
|
+
the first node in the array. This is denoted by the :input => [{0=>:output}]
|
227
|
+
key value pair hash that defines the Fbp::Selector_node. This reads as
|
228
|
+
<em>The input of the Fbp::Selector_node comes form the node defined in
|
229
|
+
the zeroth position of the Flow Description Record and takes its input
|
230
|
+
from the output channel of that node</em>
|
231
|
+
|
232
|
+
=== Future Development
|
233
|
+
Many FBP solutions have a visual configuration tool to form networks
|
234
|
+
from existing nodes. The Flow node could be used in conjunction with
|
235
|
+
a drawing tool to allow for non-programmer development of networks.
|
236
|
+
This is an area that should be pursued.
|
237
|
+
=end
|
238
|
+
class Flow_node < Node
|
239
|
+
|
240
|
+
=begin rdoc
|
241
|
+
When creating a new Flow_node instance one can optionally provide a
|
242
|
+
flow description record.
|
243
|
+
=end
|
244
|
+
def initialize(flow_desc = nil)
|
245
|
+
super()
|
246
|
+
@options[:flow_desc] = flow_desc
|
247
|
+
@flow_created = false
|
248
|
+
@node_objects = Array.new
|
249
|
+
end
|
250
|
+
|
251
|
+
=begin rdoc
|
252
|
+
Checks to see if this Flow_node instance has a flow description record.
|
253
|
+
If the does have a flow description record then true will be returned
|
254
|
+
otherwise false. A Flow_node instance will not execute until it has a
|
255
|
+
low description record.
|
256
|
+
=end
|
257
|
+
def is_ready_to_run?
|
258
|
+
@options.has_key? :flow_desc
|
259
|
+
end
|
260
|
+
|
261
|
+
=begin rdoc
|
262
|
+
This will write to the first object in the network being serviced by this
|
263
|
+
Flow_node instance.
|
264
|
+
|
265
|
+
=== Discussion
|
266
|
+
Should a way be provided to set which node in the network is the 'first'
|
267
|
+
node?
|
268
|
+
=end
|
269
|
+
def write_to_input(obj)
|
270
|
+
node = @node_objects.first
|
271
|
+
node.input_queue << obj if !node.nil? && !obj.nil?
|
272
|
+
end
|
273
|
+
|
274
|
+
=begin rdoc
|
275
|
+
This will forward the register request to the last node in the network
|
276
|
+
being serviced by this Flow_node instance.
|
277
|
+
|
278
|
+
=== Discussion
|
279
|
+
Should a way be provided to set which node in the network is the 'last'
|
280
|
+
node?
|
281
|
+
=end
|
282
|
+
def register_for_output_from_node(node, output_channel = :output)
|
283
|
+
last_name = @node_objects.last
|
284
|
+
last_name.register_for_output_from_node(node, output_channel) if !last_name.nil? && node.nil?
|
285
|
+
end
|
286
|
+
|
287
|
+
=begin rdoc
|
288
|
+
This will forward the wait_until_completed method to the last node in the network
|
289
|
+
being serviced by this Flow_node instance.
|
290
|
+
|
291
|
+
=== Discussion
|
292
|
+
Should a way be provided to set which node in the network is the 'last'
|
293
|
+
node?
|
294
|
+
=end
|
295
|
+
def wait_until_completed
|
296
|
+
node = @node_objects.last
|
297
|
+
node.wait_until_completed if !node.nil?
|
298
|
+
end
|
299
|
+
|
300
|
+
=begin rdoc
|
301
|
+
This will forward the execute method to the first node in the network
|
302
|
+
being serviced by this Flow_node instance.
|
303
|
+
|
304
|
+
=== Discussion
|
305
|
+
Should a way be provided to set which node in the network is the 'first'
|
306
|
+
node?
|
307
|
+
=end
|
308
|
+
def execute
|
309
|
+
make_flow if !@flow_created
|
310
|
+
node = @node_objects.first
|
311
|
+
node.execute if !node.nil?
|
312
|
+
end
|
313
|
+
|
314
|
+
=begin rdoc
|
315
|
+
This will forward the do_node_work method to the first node in the network
|
316
|
+
being serviced by this Flow_node instance.
|
317
|
+
|
318
|
+
=== Discussion
|
319
|
+
Should a way be provided to set which node in the network is the 'first'
|
320
|
+
node?
|
321
|
+
=end
|
322
|
+
def do_node_work(args)
|
323
|
+
node = @node_objects.first
|
324
|
+
node.do_node_work(args) if !node.nil?
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
def make_flow #:nodoc:
|
329
|
+
return true if @flow_created
|
330
|
+
|
331
|
+
flow_desc = @options[:flow_desc]
|
332
|
+
return false if !flow_desc.respond_to?(:each_index)
|
333
|
+
|
334
|
+
# This needs to be done in two passes. One to create the
|
335
|
+
# Node objects and then to connect them. The order in the
|
336
|
+
# flow_desc determines how the Node object are to be connected
|
337
|
+
# so create an array to hold the newly created object in the same
|
338
|
+
# order as they are defined
|
339
|
+
|
340
|
+
# Pass one
|
341
|
+
flow_desc.each do |node_desc|
|
342
|
+
node_object = Fbp.create(node_desc[:node].to_s)
|
343
|
+
return false if node_object.nil?
|
344
|
+
node_object.merge_options!(node_desc[:options]) if !node_desc[:options].nil?
|
345
|
+
@node_objects << node_object
|
346
|
+
end
|
347
|
+
|
348
|
+
# Pass two
|
349
|
+
flow_desc.each_index do |idx|
|
350
|
+
node_desc = flow_desc[idx]
|
351
|
+
this_node = @node_objects[idx]
|
352
|
+
next if node_desc.nil? || this_node.nil?
|
353
|
+
input_desc = node_desc[:input]
|
354
|
+
next if input_desc.nil?
|
355
|
+
|
356
|
+
input_desc.each do |input_hash|
|
357
|
+
next if input_hash.nil? || !input_hash.respond_to?(:each_pair)
|
358
|
+
input_hash.each_pair do |index, output_name|
|
359
|
+
output_node = @node_objects[index]
|
360
|
+
next if output_node.nil?
|
361
|
+
this_node.register_for_output_from_node(output_node, output_name)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
@flow_created = true
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
end
|