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.
@@ -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,13 @@
1
+ module Fbp
2
+ NOT_EQUAL_COMPARE = 0
3
+ EQUAL_COMPARE = 1
4
+ GREATER_THAN = 2
5
+ GREATER_THAN_OR_EQUAL = 3
6
+ LESS_THAN = 4
7
+ LESS_THAN_OR_EQUAL = 5
8
+ CONTAINS = 6
9
+ DOES_NOT_CONTAINS = 7
10
+ STARTS_WITH = 8
11
+ ENDS_WITH = 9
12
+ MATCHES = 10
13
+ 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