rbpm 0.0.2 → 0.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.
- data/LICENSE +147 -0
- data/README +30 -5
- data/bin/signal.sh +6 -0
- data/bin/start.sh +6 -0
- data/docs/rbpm-0.0.2-manual--wiki-snapshot.pdf +0 -0
- data/docs/todo.txt +7 -4
- data/lib/rbpm.rb +24 -598
- data/lib/rbpm/nodes.rb +109 -0
- data/lib/rbpm/persistence2.rb +265 -0
- data/lib/rbpm/persistence2_standalone.rb +46 -0
- data/lib/rbpm/token.rb +169 -0
- data/lib/rbpm/workflow.rb +457 -0
- data/lib/rbpm/workflow_managment.rb +57 -0
- data/test/rbpm_action_tests.rb +7 -2
- data/test/rbpm_call_tests.rb +7 -2
- data/test/rbpm_concurrency_fine_grained_graph_programming_sync_tests.rb +153 -0
- data/test/rbpm_condition_tests.rb +7 -2
- data/test/rbpm_ctxvars_tests.rb +7 -2
- data/test/rbpm_exceptions_tests.rb +7 -2
- data/test/rbpm_fork_join_tests.rb +7 -2
- data/test/rbpm_fs2_persistence_tests.rb +144 -0
- data/test/rbpm_simple_tests.rb +7 -2
- data/test/rbpm_state_tests.rb +7 -2
- data/test/rbpm_task_role_tests.rb +7 -2
- data/test/rbpm_transition_tests.rb +8 -3
- data/test/rbpm_versionning_tests.rb +13 -2
- data/test/ruby_lang_tests.rb +21 -0
- data/test/ts_all.rb +35 -25
- data/tutorial/README +65 -0
- data/tutorial/hello_world.rb +17 -0
- data/tutorial/rbpm_tutorial.rb +46 -0
- data/tutorial/sample_state_2_wf.rb +56 -0
- data/tutorial/signal_sample_state2.sh +1 -0
- data/tutorial/start_sample_state2.sh +1 -0
- data/tutorial/workflow_template.rb +73 -0
- metadata +22 -2
@@ -0,0 +1,57 @@
|
|
1
|
+
#---------------------------------------------------------------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# rbpm lightweight workflows, copyright (c) 2005 Christian Tschenett <furthermore@nospam@tschenett.ch>
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
|
6
|
+
# as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
|
7
|
+
# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
8
|
+
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
9
|
+
# You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the
|
10
|
+
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
11
|
+
#
|
12
|
+
#---------------------------------------------------------------------------------------------------------------------------------
|
13
|
+
|
14
|
+
module Rbpm
|
15
|
+
|
16
|
+
#the version manager can deal with multiple versions of a workflow
|
17
|
+
#if you put you workflow in a module and if you follow well defined
|
18
|
+
#naming patterns for workflow class names and workflow module names.
|
19
|
+
#naming sample for a workflow named "foo" having version 1 and 2:
|
20
|
+
#module name of version 1: FooWorkflowVersion1.
|
21
|
+
#module name of version 2: FooWorkflowVersion2.
|
22
|
+
#class name of version 1 and 2: FooWorkflow.
|
23
|
+
#node: all workflow ruby files have to be "required" before
|
24
|
+
#relying on this version manager. maybe you introduce some sort
|
25
|
+
#of hot deploy directory or put workflow versions (ruby files)
|
26
|
+
#in a RDBMS table and automatically load them (or whatever :-)
|
27
|
+
class WorkflowVersionManager
|
28
|
+
|
29
|
+
#finds version numbers of a given workflow (or number prefixes
|
30
|
+
#of loaded workflow modules, to be precise)
|
31
|
+
def self.find_workflow_versions(workflow_name)
|
32
|
+
versions = []
|
33
|
+
ObjectSpace.each_object(Module) do |m|
|
34
|
+
module_name = m.to_s
|
35
|
+
if module_name =~ /#{workflow_name}Version([0-9]+)$/
|
36
|
+
versions << $1.to_i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
versions
|
40
|
+
end
|
41
|
+
|
42
|
+
#returns a new workflow instance. version can be a number or :latest.
|
43
|
+
def self.create_workflow_instance(workflow_name, version = :latest, super_workflow_ref = -1)
|
44
|
+
workflow_name = workflow_name.to_s if workflow_name.is_a? Symbol
|
45
|
+
if :latest == version
|
46
|
+
versions = find_workflow_versions(workflow_name)
|
47
|
+
if versions.empty?
|
48
|
+
#fallback to non-versionned wf
|
49
|
+
return eval("#{workflow_name}.new(#{super_workflow_ref})")
|
50
|
+
else
|
51
|
+
version = versions.max
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return eval("#{workflow_name}Version#{version}::#{workflow_name}.new(#{super_workflow_ref})")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/test/rbpm_action_tests.rb
CHANGED
data/test/rbpm_call_tests.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
|
3
3
|
begin
|
4
|
-
require '
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'rbpm'
|
5
6
|
rescue LoadError
|
6
|
-
|
7
|
+
begin
|
8
|
+
require '../lib/rbpm'
|
9
|
+
rescue LoadError
|
10
|
+
require 'lib/rbpm'
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
class DemoWorkflow6b < Rbpm::Workflow
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubygems'
|
6
|
+
require_gem 'rbpm'
|
7
|
+
rescue LoadError
|
8
|
+
begin
|
9
|
+
require '../lib/rbpm'
|
10
|
+
rescue LoadError
|
11
|
+
require 'lib/rbpm'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
#FIXME synchronized access to (parent)token and workflow instance variables which are target of concurrent access
|
16
|
+
|
17
|
+
module Rbpm::Concurrent
|
18
|
+
class Token < Rbpm::Token
|
19
|
+
attr_reader :token_count, :token_count_cond, :first_token
|
20
|
+
attr_writer :token_count, :token_count_cond, :first_token
|
21
|
+
|
22
|
+
def initialize(workflow, parent = nil, ctx_vars = {}, ref = -1)
|
23
|
+
super workflow, parent, ctx_vars, ref
|
24
|
+
@lock = Monitor.new
|
25
|
+
@first_token = false
|
26
|
+
@token_count = nil
|
27
|
+
@token_count_cond = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def synchronize
|
31
|
+
@lock.synchronize do
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Workflow < Rbpm::Workflow
|
38
|
+
def create_token_instance(parent = nil, ctx_vars = {})
|
39
|
+
Token.new(self, parent, ctx_vars, generate_token_id)
|
40
|
+
end
|
41
|
+
|
42
|
+
def take_transitions(transition_tokens)
|
43
|
+
first_transition,first_token = transition_tokens.shift
|
44
|
+
if (transition_tokens.length > 0)
|
45
|
+
|
46
|
+
#parallel execution (one thread per token)
|
47
|
+
|
48
|
+
token_count = [transition_tokens.length]
|
49
|
+
token_count.extend(MonitorMixin)
|
50
|
+
token_count_cond = token_count.new_cond
|
51
|
+
|
52
|
+
transition_tokens.each do |transition_token|
|
53
|
+
transition,token = transition_token
|
54
|
+
token.token_count = token_count
|
55
|
+
token.token_count_cond = token_count_cond
|
56
|
+
Thread.new(transition,token) do |transition,token|
|
57
|
+
#puts "(thread)"
|
58
|
+
token.transit(transition)
|
59
|
+
#puts "(/thread)"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#puts "(main)"
|
64
|
+
first_token.first_token = true
|
65
|
+
first_token.token.token_count = token_count
|
66
|
+
first_token.token.token_count_cond = token_count_cond
|
67
|
+
first_token.transit(first_transition) #first token will be executed within main thread
|
68
|
+
#puts "(/main)"
|
69
|
+
|
70
|
+
else
|
71
|
+
first_token.transit(first_transition) #first token will be executed within main thread
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.create_join_node_instance
|
76
|
+
JoinNode.new
|
77
|
+
end
|
78
|
+
|
79
|
+
class JoinNode
|
80
|
+
def action(token, caller)
|
81
|
+
parent_token = token.parent
|
82
|
+
token.done
|
83
|
+
|
84
|
+
if token.first_token
|
85
|
+
|
86
|
+
#puts "(1st done)"
|
87
|
+
token.token_count.synchronize do
|
88
|
+
token.token_count_cond.wait_while { token.token_count[0] > 0 }
|
89
|
+
end
|
90
|
+
#puts "(/1st done)"
|
91
|
+
|
92
|
+
[[:default, parent_token]]
|
93
|
+
else
|
94
|
+
|
95
|
+
#puts "(1+ done)"
|
96
|
+
token.token_count.synchronize do
|
97
|
+
token.token_count[0] = token.token_count[0] - 1
|
98
|
+
token.token_count_cond.signal
|
99
|
+
end
|
100
|
+
#puts "(/1+ done)"
|
101
|
+
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
#should rbpm support concurrent fine grained graph oriented programming like this?
|
111
|
+
class ConcurrentFineGrainedDemoWorkflow < Rbpm::Concurrent::Workflow
|
112
|
+
start_node :start,
|
113
|
+
:trans => :create_working_threads,
|
114
|
+
:on_enter => lambda { |token,caller| token[:sum] = 0 }
|
115
|
+
fork_node :create_working_threads,
|
116
|
+
:trans => :loop_start,
|
117
|
+
:children_ctx => :thread_contexts,
|
118
|
+
:on_enter => lambda { |token,caller| token[:thread_contexts] = [{},{}] }
|
119
|
+
node :loop_start,
|
120
|
+
:trans => :loop_end_decision,
|
121
|
+
:on_enter => lambda { |token,caller| token[:counter] = 0 }
|
122
|
+
node :loop_end_decision,
|
123
|
+
:increment_trans => :loop_inc,
|
124
|
+
:increment_trans_cond => "token[:counter] < 10",
|
125
|
+
:done_trans => :loop_end,
|
126
|
+
:done_trans_cond => "token[:counter] >= 10"
|
127
|
+
node :loop_inc,
|
128
|
+
:trans => :loop_end_decision,
|
129
|
+
:on_enter => lambda { |token,caller| token[:counter] = token[:counter] + 1 }
|
130
|
+
join_node :loop_end,
|
131
|
+
:trans => :end
|
132
|
+
end_node :end,
|
133
|
+
:on_enter => lambda { |token,caller| token[:result] << token[:sum] }
|
134
|
+
|
135
|
+
leave(:loop_inc) do |token,caller|
|
136
|
+
token.parent.synchronize do #ensure parent.sum++ is an atomic operation
|
137
|
+
#puts "#{token.ref}=#{token[:counter]}"
|
138
|
+
token.parent[:sum] = token.parent[:sum] + token[:counter]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class ConcurrentFineGrainedDemoWorkflowTest < Test::Unit::TestCase
|
144
|
+
|
145
|
+
def test_fine_grained_concurrent_workflow
|
146
|
+
wf = ConcurrentFineGrainedDemoWorkflow.new
|
147
|
+
result = []
|
148
|
+
#puts "(start)"
|
149
|
+
wf.start :result => result
|
150
|
+
#puts "(/start)"
|
151
|
+
assert_equal ((1..10).inject(0) {|sum,n| sum + 2 * n}), result[0]
|
152
|
+
end
|
153
|
+
end
|
@@ -1,9 +1,14 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
|
3
3
|
begin
|
4
|
-
require '
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'rbpm'
|
5
6
|
rescue LoadError
|
6
|
-
|
7
|
+
begin
|
8
|
+
require '../lib/rbpm'
|
9
|
+
rescue LoadError
|
10
|
+
require 'lib/rbpm'
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
class DemoWorkflow5 < Rbpm::Workflow
|
data/test/rbpm_ctxvars_tests.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
|
3
3
|
begin
|
4
|
-
require '
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'rbpm'
|
5
6
|
rescue LoadError
|
6
|
-
|
7
|
+
begin
|
8
|
+
require '../lib/rbpm'
|
9
|
+
rescue LoadError
|
10
|
+
require 'lib/rbpm'
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
class DemoWorkflow3 < Rbpm::Workflow
|
@@ -1,9 +1,14 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
|
3
3
|
begin
|
4
|
-
require '
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'rbpm'
|
5
6
|
rescue LoadError
|
6
|
-
|
7
|
+
begin
|
8
|
+
require '../lib/rbpm'
|
9
|
+
rescue LoadError
|
10
|
+
require 'lib/rbpm'
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
class ExceptionWf1 < Rbpm::Workflow
|
@@ -1,9 +1,14 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
|
3
3
|
begin
|
4
|
-
require '
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'rbpm'
|
5
6
|
rescue LoadError
|
6
|
-
|
7
|
+
begin
|
8
|
+
require '../lib/rbpm'
|
9
|
+
rescue LoadError
|
10
|
+
require 'lib/rbpm'
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
class DemoWorkflow4 < Rbpm::Workflow
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'rbpm'
|
6
|
+
rescue LoadError
|
7
|
+
begin
|
8
|
+
require '../lib/rbpm'
|
9
|
+
rescue LoadError
|
10
|
+
require 'lib/rbpm'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Inbox
|
15
|
+
class InboxMessage
|
16
|
+
attr_reader :token_ref, :body
|
17
|
+
|
18
|
+
def initialize(token_ref, body)
|
19
|
+
@token_ref = token_ref
|
20
|
+
@body = body
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@messages = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def post(token_ref, body)
|
29
|
+
@messages << InboxMessage.new(token_ref, body)
|
30
|
+
end
|
31
|
+
|
32
|
+
def shift
|
33
|
+
@messages.shift
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
$my_inbox = Inbox.new
|
38
|
+
|
39
|
+
class PersTestParentWorkflow < Rbpm::Workflow
|
40
|
+
start_node :start, :trans => :call
|
41
|
+
call_node :call,
|
42
|
+
:workflow => :PersTestWorkflow,
|
43
|
+
:call_in => {:in => :in},
|
44
|
+
:call_out => {},
|
45
|
+
:trans => :end
|
46
|
+
end_node :end
|
47
|
+
|
48
|
+
enter(:end) do |token, caller|
|
49
|
+
$my_inbox.post token.ref, "parent end notification"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class PersTestWorkflow < Rbpm::Workflow
|
54
|
+
start_node :start, :trans => :state, :trans_action => lambda { |token,caller| token[:tmp] = token[:in] }
|
55
|
+
state_node :state, :trans => :end, :trans_action => lambda { |token,caller| token[:out] = token[:tmp] + token[:in] }
|
56
|
+
end_node :end
|
57
|
+
|
58
|
+
enter(:state) do |token,caller|
|
59
|
+
$my_inbox.post token.ref, "i need you input"
|
60
|
+
end
|
61
|
+
|
62
|
+
enter(:end) do |token, caller|
|
63
|
+
$my_inbox.post token.ref, token[:out]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class RbpmFs2PersistenceTests < Test::Unit::TestCase
|
68
|
+
|
69
|
+
def test_fs_flock_persistence
|
70
|
+
mgr = Rbpm::FlockFileSystem::WorkflowPersistenceManager.new "ptokens.ind"
|
71
|
+
|
72
|
+
mgr.create_and_start "PersTestParentWorkflow", :in => "Hello "
|
73
|
+
|
74
|
+
#days later...
|
75
|
+
|
76
|
+
msg = $my_inbox.shift
|
77
|
+
|
78
|
+
assert_not_nil msg
|
79
|
+
assert_equal "i need you input", msg.body
|
80
|
+
|
81
|
+
mgr.signal msg.token_ref, :in => "World"
|
82
|
+
|
83
|
+
#hours later...
|
84
|
+
|
85
|
+
msg = $my_inbox.shift
|
86
|
+
|
87
|
+
assert_not_nil msg
|
88
|
+
assert_equal "Hello World", msg.body
|
89
|
+
|
90
|
+
msg = $my_inbox.shift
|
91
|
+
|
92
|
+
assert_not_nil msg
|
93
|
+
assert_equal "parent end notification", msg.body
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_workflow_token_index
|
97
|
+
wf_t_ind = Rbpm::FlockFileSystem::WorkflowTokenIndex.new('test.ind')
|
98
|
+
|
99
|
+
`rm test.ind` if File.exists?('test.ind')
|
100
|
+
|
101
|
+
assert_nil wf_t_ind.lookup_super_workflow(123)
|
102
|
+
assert_nil wf_t_ind.lookup_super_workflow(20)
|
103
|
+
|
104
|
+
`echo "10\n100\n100\n10\n20\n200\n21\n200" > test.ind`
|
105
|
+
|
106
|
+
assert_nil wf_t_ind.lookup_super_workflow(123)
|
107
|
+
assert_nil wf_t_ind.lookup_super_workflow(200)
|
108
|
+
assert_equal 200, wf_t_ind.lookup_super_workflow(20)
|
109
|
+
assert_equal 200, wf_t_ind.lookup_super_workflow(21)
|
110
|
+
assert_equal 100, wf_t_ind.lookup_super_workflow(10)
|
111
|
+
assert_equal 10, wf_t_ind.lookup_super_workflow(100)
|
112
|
+
|
113
|
+
wf_t_ind.update_workflow_tokens 1000, [1,2,3]
|
114
|
+
wf_t_ind.update_workflow_tokens 200, [20,22,23]
|
115
|
+
wf_t_ind.update_workflow_tokens 100, []
|
116
|
+
|
117
|
+
assert_equal 14, `grep -c . test.ind`.to_i
|
118
|
+
|
119
|
+
assert_equal 1000, wf_t_ind.lookup_super_workflow(1)
|
120
|
+
assert_equal 1000, wf_t_ind.lookup_super_workflow(2)
|
121
|
+
assert_equal 1000, wf_t_ind.lookup_super_workflow(3)
|
122
|
+
|
123
|
+
assert_equal 200, wf_t_ind.lookup_super_workflow(20)
|
124
|
+
assert_nil wf_t_ind.lookup_super_workflow(21)
|
125
|
+
assert_equal 200, wf_t_ind.lookup_super_workflow(22)
|
126
|
+
assert_equal 200, wf_t_ind.lookup_super_workflow(23)
|
127
|
+
|
128
|
+
assert_equal 101, wf_t_ind.generate_token_id(1000)
|
129
|
+
assert_equal 102, wf_t_ind.generate_token_id(10)
|
130
|
+
|
131
|
+
assert_equal 1001, wf_t_ind.create_new_super_workflow_id
|
132
|
+
|
133
|
+
wf_t_ind.update_workflow_tokens 1000, []
|
134
|
+
wf_t_ind.update_workflow_tokens 200, []
|
135
|
+
wf_t_ind.update_workflow_tokens 100, []
|
136
|
+
|
137
|
+
assert_equal 6, `grep -c . test.ind`.to_i
|
138
|
+
|
139
|
+
wf_t_ind.update_workflow_tokens 10, []
|
140
|
+
wf_t_ind.update_workflow_tokens 1001, []
|
141
|
+
|
142
|
+
assert_equal false, File.exists?('test.ind')
|
143
|
+
end
|
144
|
+
end
|