weel 1.0.3

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.
Files changed (41) hide show
  1. data/AUTHORS +2 -0
  2. data/COPYING +504 -0
  3. data/FEATURES +20 -0
  4. data/INSTALL +7 -0
  5. data/README +4 -0
  6. data/Rakefile +15 -0
  7. data/example/SimpleHandlerWrapper.rb +68 -0
  8. data/example/SimpleWorkflow.rb +15 -0
  9. data/example/runme.rb +12 -0
  10. data/lib/weel.rb +749 -0
  11. data/test/ContinueTest.rb +25 -0
  12. data/test/TestHandlerWrapper.rb +120 -0
  13. data/test/TestWorkflow.rb +35 -0
  14. data/test/basic/tc_choose.rb +82 -0
  15. data/test/basic/tc_codereplace.rb +38 -0
  16. data/test/basic/tc_data.rb +32 -0
  17. data/test/basic/tc_endpoint.rb +40 -0
  18. data/test/basic/tc_handler.rb +19 -0
  19. data/test/basic/tc_parallel.rb +115 -0
  20. data/test/basic/tc_search.rb +37 -0
  21. data/test/basic/tc_state.rb +17 -0
  22. data/test/basic/tc_wf_control.rb +68 -0
  23. data/test/complex/tc_generalsynchonizingmerge_loopsearch.rb +113 -0
  24. data/test/complex/tc_parallel_stop.rb +32 -0
  25. data/test/wfp_adv_branching/tc_generalizedjoin.rb +11 -0
  26. data/test/wfp_adv_branching/tc_generalsynchronizingmerge.rb +45 -0
  27. data/test/wfp_adv_branching/tc_localsynchronizingmerge.rb +37 -0
  28. data/test/wfp_adv_branching/tc_multichoice_structuredsynchronizingmerge.rb +47 -0
  29. data/test/wfp_adv_branching/tc_multimerge.rb +11 -0
  30. data/test/wfp_adv_branching/tc_structured_discriminator.rb +28 -0
  31. data/test/wfp_adv_branching/tc_structured_partial_join.rb +41 -0
  32. data/test/wfp_adv_branching/tc_threadmerge.rb +11 -0
  33. data/test/wfp_adv_branching/tc_threadsplit.rb +11 -0
  34. data/test/wfp_basic/tc_exclusivechoice_simplemerge.rb +22 -0
  35. data/test/wfp_basic/tc_parallelsplit_synchronization.rb +26 -0
  36. data/test/wfp_basic/tc_sequence.rb +16 -0
  37. data/test/wfp_iteration/tc_structuredloop.rb +65 -0
  38. data/test/wfp_state_based/tc_deferredchoice.rb +38 -0
  39. data/test/wfp_state_based/tc_interleavedparallelrouting.rb +30 -0
  40. data/weel.gemspec +25 -0
  41. metadata +127 -0
@@ -0,0 +1,20 @@
1
+ WEEL Features
2
+ * Reuses an existing virtual machine for executing control flow
3
+ * Has a core size of ~ 700 LOC.
4
+ * Uses ~ 2 MiB of RAM per instance.
5
+ * Can utilize multiple threads per instance.
6
+ * Cold starts an instance in less than 2ms.
7
+ * Provides a DSL for defining processes.
8
+ * It provides better coverage of workflow patterns compared to Oracle BPELPM,
9
+ jBOSS jBPM, and Apache Ode.
10
+ * Supports BPEL and other languages through transformation to a directly
11
+ executable DSL.
12
+ * Pluggable event & protocol backend for activities.
13
+
14
+ WEEL Applied Benefits
15
+ * Repair & adpatation - change description, change thread of control.
16
+ * Stop & restart process instances.
17
+ * Pause & restart activities (through flexible HandlerWrapper implementation).
18
+ * Build your own process engines & process languages.
19
+ * Never worry about the speed of the engine - updates of ruby interpreter
20
+ (ruby 1.9, ruby 2.0, jruby) ensure speed.
data/INSTALL ADDED
@@ -0,0 +1,7 @@
1
+ BEWARE - PRELIMINARY VERSION
2
+ ----------------------------
3
+
4
+ For WEE Library and tests:
5
+ * gem install weel
6
+ * No dependencies required
7
+ * rake test
data/README ADDED
@@ -0,0 +1,4 @@
1
+ All code in this package is provided under the LGPL license.
2
+ Please read the file COPYING.
3
+
4
+ Tested for MRI >= 1.8.7 (including 1.9.2 and 1.9.3)
@@ -0,0 +1,15 @@
1
+ require 'rake'
2
+ require 'rubygems/package_task'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['./test/*/tc_*.rb']
8
+ t.verbose = false
9
+ end
10
+
11
+ spec = eval(File.read('weel.gemspec'))
12
+ Gem::PackageTask.new(spec) do |pkg|
13
+ pkg.need_zip = true
14
+ pkg.need_tar = true
15
+ end
@@ -0,0 +1,68 @@
1
+ require "pp"
2
+
3
+ class SimpleHandlerWrapper < WEEL::HandlerWrapperBase
4
+ def initialize(args,endpoint=nil,position=nil,continue=nil)
5
+ @__myhandler_stopped = false
6
+ @__myhandler_position = position
7
+ @__myhandler_continue = continue
8
+ @__myhandler_endpoint = endpoint
9
+ @__myhandler_returnValue = nil
10
+ end
11
+
12
+ # executes a ws-call to the given endpoint with the given parameters. the call
13
+ # can be executed asynchron, see finished_call & return_value
14
+ def activity_handle(passthrough, parameters)
15
+ puts "Handle call:"
16
+ puts " position=[#{@__myhandler_position}]"
17
+ puts " passthrough=[#{passthrough}]"
18
+ puts " endpoint=[#{@__myhandler_endpoint}]"
19
+ print " parameters="
20
+ pp parameters
21
+ @__myhandler_returnValue = 'Handler_Dummy_Result'
22
+ @__myhandler_continue.continue
23
+ end
24
+
25
+ # returns the result of the last handled call
26
+ def activity_result_value
27
+ @__myhandler_returnValue
28
+ end
29
+ # Called if the WS-Call should be interrupted. The decision how to deal
30
+ # with this situation is given to the handler. To provide the possibility
31
+ # of a continue the Handler will be asked for a passthrough
32
+ def activity_stop
33
+ puts "Handler: Recieved stop signal, deciding if stopping\n"
34
+ @__myhandler_stopped = true
35
+ end
36
+ # is called from WEEL after stop_call to ask for a passthrough-value that may give
37
+ # information about how to continue the call. This passthrough-value is given
38
+ # to activity_handle if the workflow is configured to do so.
39
+ def activity_passthrough_value
40
+ nil
41
+ end
42
+
43
+ # Called if the execution of the actual activity_handle is not necessary anymore
44
+ # It is definit that the call will not be continued.
45
+ # At this stage, this is only the case if parallel branches are not needed
46
+ # anymore to continue the workflow
47
+ def activity_no_longer_necessary
48
+ puts "Handler: Recieved no_longer_necessary signal, deciding if stopping\n"
49
+ @__myhandler_stopped = true
50
+ end
51
+ # Is called if a Activity is executed correctly
52
+ def inform_activity_done
53
+ puts "Activity #{@__myhandler_position} done\n"
54
+ end
55
+ # Is called if a Activity is executed with an error
56
+ def inform_activity_failed(err)
57
+ puts "Activity #{@__myhandler_position} failed with error #{err}\n"
58
+ raise(err)
59
+ end
60
+ def inform_syntax_error(err,code)
61
+ puts "Syntax messed with error #{code}:#{err}\n"
62
+ raise(err)
63
+ end
64
+ def inform_state_change(newstate)
65
+ puts "State changed to #{newstate}.\n"
66
+ end
67
+
68
+ end
@@ -0,0 +1,15 @@
1
+ require ::File.dirname(__FILE__) + '/../lib/weel'
2
+ require ::File.dirname(__FILE__) + '/SimpleHandlerWrapper'
3
+
4
+ class SimpleWorkflow < WEEL
5
+ handlerwrapper SimpleHandlerWrapper
6
+
7
+ endpoint :ep1 => "orf.at"
8
+ data :a => 17
9
+
10
+ control flow do
11
+ activity :a1, :call, :ep1, :a => data.a, :b => 2 do
12
+ data.a += 3
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ require ::File.dirname(__FILE__) + '/SimpleWorkflow'
3
+
4
+ t = SimpleWorkflow.new
5
+ execution = t.start
6
+ execution.join()
7
+ puts "========> Ending-Result:"
8
+ puts " data:#{t.data.inspect}"
9
+ puts " status:#{t.status.inspect}"
10
+ puts " state:#{t.state}"
11
+
12
+
@@ -0,0 +1,749 @@
1
+ require 'thread'
2
+
3
+ # OMG!111! strings have to be emptied
4
+ class String # {{{
5
+ def clear
6
+ self.slice!(0..-1)
7
+ end
8
+ end # }}}
9
+
10
+ class WEEL
11
+ def initialize(*args)# {{{
12
+ @wfsource = nil
13
+ @dslr = DSLRealization.new
14
+ @dslr.__weel_handlerwrapper_args = args
15
+
16
+ ### 1.8
17
+ initialize_search if methods.include?('initialize_search')
18
+ initialize_data if methods.include?('initialize_data')
19
+ initialize_endpoints if methods.include?('initialize_endpoints')
20
+ initialize_handlerwrapper if methods.include?('initialize_handlerwrapper')
21
+ initialize_control if methods.include?('initialize_control')
22
+ ### 1.9
23
+ initialize_search if methods.include?(:initialize_search)
24
+ initialize_data if methods.include?(:initialize_data)
25
+ initialize_endpoints if methods.include?(:initialize_endpoints)
26
+ initialize_handlerwrapper if methods.include?(:initialize_handlerwrapper)
27
+ initialize_control if methods.include?(:initialize_control)
28
+ end # }}}
29
+
30
+ module Signal # {{{
31
+ class SkipManipulate < Exception; end
32
+ class StopSkipManipulate < Exception; end
33
+ class Stop < Exception; end
34
+ class Proceed < Exception; end
35
+ class NoLongerNecessary < Exception; end
36
+ end # }}}
37
+
38
+ class ManipulateRealization # {{{
39
+ def initialize(data,endpoints,status)
40
+ @__weel_data = data
41
+ @__weel_endpoints = endpoints
42
+ @__weel_status = status
43
+ @changed_status = status.id
44
+ @changed_data = []
45
+ @changed_endpoints = []
46
+ end
47
+
48
+ attr_reader :changed_data, :changed_endpoints
49
+
50
+ def changed_status
51
+ @changed_status != status.id
52
+ end
53
+
54
+ def data
55
+ ManipulateHash.new(@__weel_data,@changed_data)
56
+ end
57
+ def endpoints
58
+ ManipulateHash.new(@__weel_endpoints,@changed_endpoints)
59
+ end
60
+ def status
61
+ @__weel_status
62
+ end
63
+ end # }}}
64
+ class ManipulateHash # {{{
65
+ def initialize(values,what)
66
+ @__weel_values = values
67
+ @__weel_what = what
68
+ end
69
+
70
+ def delete(value)
71
+ if @__weel_values.has_key?(value)
72
+ @__weel_what << value
73
+ @__weel_values.delete(value)
74
+ end
75
+ end
76
+
77
+ def clear
78
+ @__weel_what += @__weel_values.keys
79
+ @__weel_values.clear
80
+ end
81
+
82
+ def method_missing(name,*args)
83
+
84
+ if args.empty? && @__weel_values.has_key?(name)
85
+ @__weel_values[name]
86
+ elsif name.to_s[-1..-1] == "=" && args.length == 1
87
+ temp = name.to_s[0..-2]
88
+ @__weel_what << temp.to_sym
89
+ @__weel_values[temp.to_sym] = args[0]
90
+ elsif name.to_s == "[]=" && args.length == 2
91
+ @__weel_values[args[0]] = args[1]
92
+ elsif name.to_s == "[]" && args.length == 1
93
+ @__weel_values[args[0]]
94
+ else
95
+ nil
96
+ end
97
+ end
98
+ end # }}}
99
+
100
+ class Status #{{{
101
+ def initialize(id,message)
102
+ @id = id
103
+ @message = message
104
+ end
105
+ def update(id,message)
106
+ @id = id
107
+ @message = message
108
+ end
109
+ attr_reader :id, :message
110
+ end #}}}
111
+
112
+ class ReadHash # {{{
113
+ def initialize(values)
114
+ @__weel_values = values
115
+ end
116
+
117
+ def method_missing(name,*args)
118
+ temp = nil
119
+ if args.empty? && @__weel_values.has_key?(name)
120
+ @__weel_values[name]
121
+ #TODO dont let user change stuff
122
+ else
123
+ nil
124
+ end
125
+ end
126
+ end # }}}
127
+
128
+ class HandlerWrapperBase # {{{
129
+ def initialize(arguments,endpoint=nil,position=nil,continue=nil); end
130
+
131
+ def activity_handle(passthrough, endpoint, parameters); end
132
+
133
+ def activity_result_value; end
134
+ def activity_result_status; end
135
+
136
+ def activity_stop; end
137
+ def activity_passthrough_value; end
138
+
139
+ def activity_no_longer_necessary; end
140
+
141
+ def inform_activity_done; end
142
+ def inform_activity_manipulate; end
143
+ def inform_activity_failed(err); end
144
+
145
+ def inform_syntax_error(err,code); end
146
+ def inform_manipulate_change(status,data,endpoints); end
147
+ def inform_position_change(ipc); end
148
+ def inform_state_change(newstate); end
149
+
150
+ def vote_sync_before; true; end
151
+ def vote_sync_after; true; end
152
+
153
+ def callback(result); end
154
+ end # }}}
155
+
156
+ class Position # {{{
157
+ attr_reader :position
158
+ attr_accessor :detail, :passthrough
159
+ def initialize(position, detail=:at, passthrough=nil) # :at or :after or :unmark
160
+ @position = position
161
+ @detail = detail
162
+ @passthrough = passthrough
163
+ end
164
+ end # }}}
165
+
166
+ class Continue #{{{
167
+ def initialize
168
+ @thread = Thread.new{Thread.stop}
169
+ end
170
+ def waiting?
171
+ @thread.alive?
172
+ end
173
+ def continue
174
+ while @thread.status != 'sleep' && @thread.alive?
175
+ Thread.pass
176
+ end
177
+ @thread.wakeup if @thread.alive?
178
+ end
179
+ def wait
180
+ @thread.join
181
+ end
182
+ end #}}}
183
+
184
+ def self::search(weel_search)# {{{
185
+ define_method :initialize_search do
186
+ self.search weel_search
187
+ end
188
+ end # }}}
189
+ def self::endpoint(new_endpoints)# {{{
190
+ @@__weel_new_endpoints ||= {}
191
+ @@__weel_new_endpoints.merge! new_endpoints
192
+ define_method :initialize_endpoints do
193
+ @@__weel_new_endpoints.each do |name,value|
194
+ @dslr.__weel_endpoints[name.to_s.to_sym] = value
195
+ end
196
+ end
197
+ end # }}}
198
+ def self::data(data_elements)# {{{
199
+ @@__weel_new_data_elements ||= {}
200
+ @@__weel_new_data_elements.merge! data_elements
201
+ define_method :initialize_data do
202
+ @@__weel_new_data_elements.each do |name,value|
203
+ @dslr.__weel_data[name.to_s.to_sym] = value
204
+ end
205
+ end
206
+ end # }}}
207
+ def self::handlerwrapper(aClassname, *args)# {{{
208
+ define_method :initialize_handlerwrapper do
209
+ self.handlerwrapper = aClassname
210
+ self.handlerwrapper_args = args unless args.empty?
211
+ end
212
+ end # }}}
213
+ def self::control(flow, &block)# {{{
214
+ @@__weel_control_block = block
215
+ define_method :initialize_control do
216
+ self.description(&(@@__weel_control_block))
217
+ end
218
+ end # }}}
219
+ def self::flow #{{{
220
+ end #}}}
221
+
222
+ class DSLRealization # {{{
223
+ def initialize
224
+ @__weel_search_positions = {}
225
+ @__weel_positions = Array.new
226
+ @__weel_main = nil
227
+ @__weel_data ||= Hash.new
228
+ @__weel_endpoints ||= Hash.new
229
+ @__weel_handlerwrapper = HandlerWrapperBase
230
+ @__weel_handlerwrapper_args = []
231
+ @__weel_state = :ready
232
+ @__weel_status = Status.new(0,"undefined")
233
+ end
234
+ attr_accessor :__weel_search_positions, :__weel_positions, :__weel_main, :__weel_data, :__weel_endpoints, :__weel_handlerwrapper, :__weel_handlerwrapper_args
235
+ attr_reader :__weel_state, :__weel_status
236
+
237
+ # DSL-Construct for an atomic activity
238
+ # position: a unique identifier within the wf-description (may be used by the search to identify a starting point
239
+ # type:
240
+ # - :manipulate - just yield a given block
241
+ # - :call - order the handlerwrapper to perform a service call
242
+ # endpoint: (only with :call) ep of the service
243
+ # parameters: (only with :call) service parameters
244
+ def activity(position, type, endpoint=nil, *parameters, &blk)# {{{
245
+ position = __weel_position_test position
246
+ begin
247
+ searchmode = __weel_is_in_search_mode(position)
248
+ return if searchmode == true
249
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
250
+
251
+ Thread.current[:continue] = Continue.new
252
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args, @__weel_endpoints[endpoint], position, Thread.current[:continue]
253
+
254
+ ipc = {}
255
+ if searchmode == :after
256
+ wp = WEEL::Position.new(position, :after, nil)
257
+ ipc[:after] = [wp.position]
258
+ else
259
+ if Thread.current[:branch_parent] && Thread.current[:branch_parent][:branch_position]
260
+ @__weel_positions.delete Thread.current[:branch_parent][:branch_position]
261
+ ipc[:unmark] ||= []
262
+ ipc[:unmark] << Thread.current[:branch_parent][:branch_position].position rescue nil
263
+ Thread.current[:branch_parent][:branch_position] = nil
264
+ end
265
+ if Thread.current[:branch_position]
266
+ @__weel_positions.delete Thread.current[:branch_position]
267
+ ipc[:unmark] ||= []
268
+ ipc[:unmark] << Thread.current[:branch_position].position rescue nil
269
+ end
270
+ wp = WEEL::Position.new(position, :at, nil)
271
+ ipc[:at] = [wp.position]
272
+ end
273
+ @__weel_positions << wp
274
+ Thread.current[:branch_position] = wp
275
+
276
+ handlerwrapper.inform_position_change(ipc)
277
+
278
+ # searchmode position is after, jump directly to vote_sync_after
279
+ raise Signal::Proceed if searchmode == :after
280
+
281
+ raise Signal::Stop unless handlerwrapper.vote_sync_before
282
+
283
+ case type
284
+ when :manipulate
285
+ if block_given?
286
+ handlerwrapper.inform_activity_manipulate
287
+ mr = ManipulateRealization.new(@__weel_data,@__weel_endpoints,@__weel_status)
288
+ status = nil
289
+ parameters.delete_if do |para|
290
+ status = para if para.is_a?(Status)
291
+ para.is_a?(Status)
292
+ end
293
+ case blk.arity
294
+ when 1; mr.instance_exec(parameters,&blk)
295
+ when 2; mr.instance_exec(parameters,status,&blk)
296
+ else
297
+ mr.instance_eval(&blk)
298
+ end
299
+ handlerwrapper.inform_manipulate_change(
300
+ (mr.changed_status ? @__weel_status : nil),
301
+ (mr.changed_data.any? ? mr.changed_data.uniq : nil),
302
+ (mr.changed_endpoints.any? ? mr.changed_endpoints.uniq : nil)
303
+ )
304
+ handlerwrapper.inform_activity_done
305
+ wp.detail = :after
306
+ handlerwrapper.inform_position_change :after => [wp.position]
307
+ end
308
+ when :call
309
+ params = { }
310
+ passthrough = @__weel_search_positions[position] ? @__weel_search_positions[position].passthrough : nil
311
+ parameters.each do |p|
312
+ if p.class == Hash && parameters.length == 1
313
+ params = p
314
+ else
315
+ if !p.is_a?(Symbol) || !@__weel_data.include?(p)
316
+ raise("not all passed parameters are data elements")
317
+ end
318
+ params[p] = @__weel_data[p]
319
+ end
320
+ end
321
+ # handshake call and wait until it finished
322
+ handlerwrapper.activity_handle passthrough, params
323
+ Thread.current[:continue].wait unless Thread.current[:nolongernecessary] || self.__weel_state == :stopping || self.__weel_state == :stopped
324
+
325
+ if Thread.current[:nolongernecessary]
326
+ handlerwrapper.activity_no_longer_necessary
327
+ raise Signal::NoLongerNecessary
328
+ end
329
+ if self.__weel_state == :stopping
330
+ handlerwrapper.activity_stop
331
+ wp.passthrough = handlerwrapper.activity_passthrough_value
332
+ end
333
+
334
+ if wp.passthrough.nil? && block_given?
335
+ handlerwrapper.inform_activity_manipulate
336
+ mr = ManipulateRealization.new(@__weel_data,@__weel_endpoints,@__weel_status)
337
+ status = handlerwrapper.activity_result_status
338
+ case blk.arity
339
+ when 1; mr.instance_exec(handlerwrapper.activity_result_value,&blk)
340
+ when 2; mr.instance_exec(handlerwrapper.activity_result_value,(status.is_a?(Status)?status:nil),&blk)
341
+ else
342
+ mr.instance_eval(&blk)
343
+ end
344
+ handlerwrapper.inform_manipulate_change(
345
+ (mr.changed_status ? @__weel_status : nil),
346
+ (mr.changed_data.any? ? mr.changed_data.uniq : nil),
347
+ (mr.changed_endpoints.any? ? mr.changed_endpoints.uniq : nil)
348
+ )
349
+ end
350
+ if wp.passthrough.nil?
351
+ handlerwrapper.inform_activity_done
352
+ wp.detail = :after
353
+ handlerwrapper.inform_position_change :after => [wp.position]
354
+ end
355
+ end
356
+ raise Signal::Proceed
357
+ rescue Signal::SkipManipulate, Signal::Proceed
358
+ if self.__weel_state != :stopping && !handlerwrapper.vote_sync_after
359
+ self.__weel_state = :stopping
360
+ wp.detail = :unmark
361
+ end
362
+ rescue Signal::NoLongerNecessary
363
+ @__weel_positions.delete wp
364
+ Thread.current[:branch_position] = nil
365
+ wp.detail = :unmark
366
+ handlerwrapper.inform_position_change :unmark => [wp.position]
367
+ rescue Signal::StopSkipManipulate, Signal::Stop
368
+ self.__weel_state = :stopping
369
+ rescue => err
370
+ handlerwrapper.inform_activity_failed err
371
+ self.__weel_state = :stopping
372
+ end
373
+ end # }}}
374
+
375
+ # Parallel DSL-Construct
376
+ # Defines Workflow paths that can be executed parallel.
377
+ # May contain multiple branches (parallel_branch)
378
+ def parallel(type=nil)# {{{
379
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
380
+
381
+ Thread.current[:branches] = []
382
+ Thread.current[:branch_finished_count] = 0
383
+ Thread.current[:branch_event] = Continue.new
384
+ Thread.current[:mutex] = Mutex.new
385
+ yield
386
+
387
+ Thread.current[:branch_wait_count] = (type.is_a?(Hash) && type.size == 1 && type[:wait] != nil && (type[:wait].is_a?(Integer)) ? type[:wait] : Thread.current[:branches].size)
388
+ Thread.current[:branches].each do |thread|
389
+ while thread.status != 'sleep' && thread.alive?
390
+ Thread.pass
391
+ end
392
+ # decide after executing block in parallel cause for coopis
393
+ # it goes out of search mode while dynamically counting branches
394
+ if Thread.current[:branch_search] == false
395
+ thread[:branch_search] = false
396
+ end
397
+ thread.wakeup if thread.alive?
398
+ end
399
+
400
+ Thread.current[:branch_event].wait
401
+ #Thread.current[:branch_event] = nil
402
+
403
+ unless self.__weel_state == :stopping || self.__weel_state == :stopped
404
+ # first set all to no_longer_neccessary
405
+ Thread.current[:branches].each do |thread|
406
+ if thread.alive?
407
+ thread[:nolongernecessary] = true
408
+ __weel_recursive_continue(thread)
409
+ end
410
+ end
411
+ # wait for all
412
+ Thread.current[:branches].each do |thread|
413
+ __weel_recursive_join(thread)
414
+ end
415
+ end
416
+ end # }}}
417
+
418
+ # Defines a branch of a parallel-Construct
419
+ def parallel_branch(*vars)# {{{
420
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
421
+ branch_parent = Thread.current
422
+ Thread.current[:branches] << Thread.new(*vars) do |*local|
423
+ branch_parent[:mutex].synchronize do
424
+ Thread.current.abort_on_exception = true
425
+ Thread.current[:branch_status] = false
426
+ Thread.current[:branch_parent] = branch_parent
427
+ if branch_parent[:alternative_executed] && branch_parent[:alternative_executed].length > 0
428
+ Thread.current[:alternative_executed] = [branch_parent[:alternative_executed].last]
429
+ end
430
+ end
431
+
432
+ Thread.stop
433
+ yield(*local)
434
+
435
+ branch_parent[:mutex].synchronize do
436
+ Thread.current[:branch_status] = true
437
+ branch_parent[:branch_finished_count] += 1
438
+ if branch_parent[:branch_finished_count] == branch_parent[:branch_wait_count] && self.__weel_state != :stopping
439
+ branch_parent[:branch_event].continue
440
+ end
441
+ end
442
+ if self.__weel_state != :stopping && self.__weel_state != :stopped
443
+ if Thread.current[:branch_position]
444
+ @__weel_positions.delete Thread.current[:branch_position]
445
+ begin
446
+ ipc = {}
447
+ ipc[:unmark] = [Thread.current[:branch_position].position]
448
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
449
+ handlerwrapper.inform_position_change(ipc)
450
+ end rescue nil
451
+ Thread.current[:branch_position] = nil
452
+ end
453
+ end
454
+ end
455
+ Thread.pass
456
+ end # }}}
457
+
458
+ # Choose DSL-Construct
459
+ # Defines a choice in the Workflow path.
460
+ # May contain multiple execution alternatives
461
+ def choose # {{{
462
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
463
+ Thread.current[:alternative_executed] ||= []
464
+ Thread.current[:alternative_executed] << false
465
+ yield
466
+ Thread.current[:alternative_executed].pop
467
+ nil
468
+ end # }}}
469
+
470
+ # Defines a possible choice of a choose-Construct
471
+ # Block is executed if condition == true or
472
+ # searchmode is active (to find the starting position)
473
+ def alternative(condition)# {{{
474
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
475
+ yield if __weel_is_in_search_mode || condition
476
+ Thread.current[:alternative_executed][-1] = true if condition
477
+ end # }}}
478
+ def otherwise # {{{
479
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
480
+ yield if __weel_is_in_search_mode || !Thread.current[:alternative_executed].last
481
+ end # }}}
482
+
483
+ # Defines a critical block (=Mutex)
484
+ def critical(id)# {{{
485
+ @__weel_critical ||= Mutex.new
486
+ semaphore = nil
487
+ @__weel_critical.synchronize do
488
+ @__weel_critical_sections ||= {}
489
+ semaphore = @__weel_critical_sections[id] ? @__weel_critical_sections[id] : Mutex.new
490
+ @__weel_critical_sections[id] = semaphore if id
491
+ end
492
+ semaphore.synchronize do
493
+ yield
494
+ end
495
+ end # }}}
496
+
497
+ # Defines a Cycle (loop/iteration)
498
+ def loop(condition)# {{{
499
+ unless condition.is_a?(Array) && condition[0].is_a?(Proc) && [:pre_test,:post_test].include?(condition[1])
500
+ raise "condition must be called pre_test{} or post_test{}"
501
+ end
502
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
503
+ if __weel_is_in_search_mode
504
+ yield
505
+ return if __weel_is_in_search_mode
506
+ end
507
+ case condition[1]
508
+ when :pre_test
509
+ yield while condition[0].call && self.__weel_state != :stopping && self.__weel_state != :stopped
510
+ when :post_test
511
+ begin; yield; end while condition[0].call && self.__weel_state != :stopping && self.__weel_state != :stopped
512
+ end
513
+ end # }}}
514
+
515
+ def pre_test(&blk)# {{{
516
+ [blk, :pre_test]
517
+ end # }}}
518
+ def post_test(&blk)# {{{
519
+ [blk, :post_test]
520
+ end # }}}
521
+
522
+ def status # {{{
523
+ @__weel_status
524
+ end # }}}
525
+ def data # {{{
526
+ ReadHash.new(@__weel_data)
527
+ end # }}}
528
+ def endpoints # {{{
529
+ ReadHash.new(@__weel_endpoints)
530
+ end # }}}
531
+
532
+ private
533
+ def __weel_recursive_print(thread,indent='')# {{{
534
+ p "#{indent}#{thread}"
535
+ if thread[:branches]
536
+ thread[:branches].each do |b|
537
+ __weel_recursive_print(b,indent+' ')
538
+ end
539
+ end
540
+ end # }}}
541
+ def __weel_recursive_continue(thread)# {{{
542
+ return unless thread
543
+ if thread.alive? && thread[:continue] && thread[:continue].waiting?
544
+ thread[:continue].continue
545
+ end
546
+ if thread.alive? && thread[:branch_event] && thread[:branch_event].waiting?
547
+ thread[:mutex].synchronize do
548
+ unless thread[:branch_event].nil?
549
+ thread[:branch_event].continue
550
+ # thread[:branch_event] = nil
551
+ end
552
+ end
553
+ end
554
+ if thread[:branches]
555
+ thread[:branches].each do |b|
556
+ __weel_recursive_continue(b)
557
+ end
558
+ end
559
+ end # }}}
560
+ def __weel_recursive_join(thread)# {{{
561
+ return unless thread
562
+ if thread.alive? && thread != Thread.current
563
+ thread.join
564
+ end
565
+ if thread[:branches]
566
+ thread[:branches].each do |b|
567
+ __weel_recursive_join(b)
568
+ end
569
+ end
570
+ end # }}}
571
+
572
+ def __weel_position_test(position)# {{{
573
+ if position.is_a?(Symbol) && position.to_s =~ /[a-zA-Z][a-zA-Z0-9_]*/
574
+ position
575
+ else
576
+ self.__weel_state = :stopping
577
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
578
+ handlerwrapper.inform_syntax_error(Exception.new("position (#{position}) not valid"),nil)
579
+ end
580
+ end # }}}
581
+
582
+ def __weel_is_in_search_mode(position = nil)# {{{
583
+ branch = Thread.current
584
+ return false if @__weel_search_positions.empty? || branch[:branch_search] == false
585
+
586
+ if position && @__weel_search_positions.include?(position) # matching searchpos => start execution from here
587
+ branch[:branch_search] = false # execute all activities in THIS branch (thread) after this point
588
+ while branch.key?(:branch_parent) # also all parent branches should execute activities after this point, additional branches spawned by parent branches should still be in search mode
589
+ branch = branch[:branch_parent]
590
+ branch[:branch_search] = false
591
+ end
592
+ @__weel_search_positions[position].detail == :after ? :after : false
593
+ else
594
+ branch[:branch_search] = true
595
+ end
596
+ end # }}}
597
+
598
+ public
599
+ def __weel_finalize
600
+ __weel_recursive_join(@__weel_main)
601
+ @__weel_state = :stopped
602
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
603
+ handlerwrapper.inform_state_change @__weel_state
604
+ end
605
+
606
+ def __weel_state=(newState)# {{{
607
+ return @__weel_state if newState == @__weel_state
608
+ @__weel_positions = Array.new if @__weel_state != newState && newState == :running
609
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
610
+ @__weel_state = newState
611
+
612
+ if newState == :stopping
613
+ __weel_recursive_continue(@__weel_main)
614
+ end
615
+
616
+ handlerwrapper.inform_state_change @__weel_state
617
+ end # }}}
618
+
619
+ end # }}}
620
+
621
+ public
622
+ def positions # {{{
623
+ @dslr.__weel_positions
624
+ end # }}}
625
+
626
+ # set the handlerwrapper
627
+ def handlerwrapper # {{{
628
+ @dslr.__weel_handlerwrapper
629
+ end # }}}
630
+ def handlerwrapper=(new_weel_handlerwrapper) # {{{
631
+ superclass = new_weel_handlerwrapper
632
+ while superclass
633
+ check_ok = true if superclass == WEEL::HandlerWrapperBase
634
+ superclass = superclass.superclass
635
+ end
636
+ raise "Handlerwrapper is not inherited from HandlerWrapperBase" unless check_ok
637
+ @dslr.__weel_handlerwrapper = new_weel_handlerwrapper
638
+ end # }}}
639
+
640
+ # Get/Set the handlerwrapper arguments
641
+ def handlerwrapper_args # {{{
642
+ @dslr.__weel_handlerwrapper_args
643
+ end # }}}
644
+ def handlerwrapper_args=(args) # {{{
645
+ if args.class == Array
646
+ @dslr.__weel_handlerwrapper_args = args
647
+ end
648
+ nil
649
+ end # }}}
650
+
651
+ # Get the state of execution (ready|running|stopping|stopped|finished)
652
+ def state # {{{
653
+ @dslr.__weel_state
654
+ end # }}}
655
+
656
+ # Set search positions
657
+ # set new_weel_search to a boolean (or anything else) to start the process from beginning (reset serach positions)
658
+ def search(new_weel_search=false) # {{{
659
+ @dslr.__weel_search_positions.clear
660
+
661
+ new_weel_search = [new_weel_search] if new_weel_search.is_a?(Position)
662
+
663
+ if !new_weel_search.is_a?(Array) || new_weel_search.empty?
664
+ false
665
+ else
666
+ new_weel_search.each do |search_position|
667
+ @dslr.__weel_search_positions[search_position.position] = search_position
668
+ end
669
+ true
670
+ end
671
+ end # }}}
672
+
673
+ def data(new_data=nil) # {{{
674
+ unless new_data.nil? || !new_data.is_a?(Hash)
675
+ new_data.each{|k,v|@dslr.__weel_data[k] = v}
676
+ end
677
+ @dslr.__weel_data
678
+ end # }}}
679
+ def endpoints(new_endpoints=nil) # {{{
680
+ unless new_endpoints.nil? || !new_endpoints.is_a?(Hash)
681
+ new_endpoints.each{|k,v|@dslr.__weel_endpoints[k] = v}
682
+ end
683
+ @dslr.__weel_endpoints
684
+ end # }}}
685
+ def endpoint(new_endpoints) # {{{
686
+ unless new_endpoints.nil? || !new_endpoints.is_a?(Hash) || !new_endpoints.length == 1
687
+ new_endpoints.each{|k,v|@dslr.__weel_endpoints[k] = v}
688
+ end
689
+ nil
690
+ end # }}}
691
+ def status # {{{
692
+ @dslr.__weel_status
693
+ end # }}}
694
+
695
+ # get/set workflow description
696
+ def description(code = nil,&blk) # {{{
697
+ bgiven = block_given?
698
+ if code.nil? && !bgiven
699
+ @wfsource
700
+ else
701
+ @wfsource = code unless bgiven
702
+ (class << self; self; end).class_eval do
703
+ define_method :__weel_control_flow do
704
+ @dslr.__weel_positions.clear
705
+ @dslr.__weel_state = :running
706
+ begin
707
+ if bgiven
708
+ @dslr.instance_eval(&blk)
709
+ else
710
+ @dslr.instance_eval(code)
711
+ end
712
+ rescue Exception => err
713
+ @dslr.__weel_state = :stopping
714
+ handlerwrapper = @dslr.__weel_handlerwrapper.new @dslr.__weel_handlerwrapper_args
715
+ handlerwrapper.inform_syntax_error(err,code)
716
+ end
717
+ if @dslr.__weel_state == :running
718
+ @dslr.__weel_state = :finished
719
+ ipc = { :unmark => [] }
720
+ @dslr.__weel_positions.each{|wp| ipc[:unmark] << wp.position}
721
+ @dslr.__weel_positions.clear
722
+ handlerwrapper = @dslr.__weel_handlerwrapper.new @dslr.__weel_handlerwrapper_args
723
+ handlerwrapper.inform_position_change(ipc)
724
+ end
725
+ if @dslr.__weel_state == :stopping
726
+ @dslr.__weel_finalize
727
+ end
728
+ end
729
+ end
730
+ bgiven ? blk : code
731
+ end
732
+ end # }}}
733
+
734
+ # Stop the workflow execution
735
+ def stop # {{{
736
+ Thread.new do
737
+ @dslr.__weel_state = :stopping
738
+ @dslr.__weel_main.join if @dslr.__weel_main
739
+ end
740
+ end # }}}
741
+ # Start the workflow execution
742
+ def start # {{{
743
+ return nil if @dslr.__weel_state != :ready && @dslr.__weel_state != :stopped
744
+ @dslr.__weel_main = Thread.new do
745
+ __weel_control_flow
746
+ end
747
+ end # }}}
748
+
749
+ end