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
data/lib/rbpm/nodes.rb
ADDED
@@ -0,0 +1,109 @@
|
|
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 arriving (parent-) token won't leave the node using the node's default transition. in lieu thereof, a number
|
17
|
+
#of cild tokens will be generated and every child token will leave the node using the node's :default transition
|
18
|
+
#immediately. only one leaving transition - :default - is allowed. the number of child tokens is determined by
|
19
|
+
#the length of the :children_ctx token context variable. the :children_ctx token variable holds a hash of initial
|
20
|
+
#context variables for every child token (:token_variable_name => token_variable_content hash).
|
21
|
+
class ForkNode
|
22
|
+
def initialize(children_ctx_var)
|
23
|
+
@children_ctx_var = children_ctx_var
|
24
|
+
end
|
25
|
+
|
26
|
+
def action(token, caller)
|
27
|
+
exits = []
|
28
|
+
token[@children_ctx_var].each do |ctx_vars|
|
29
|
+
exits << [:default, token.fork(ctx_vars)]
|
30
|
+
end
|
31
|
+
return exits
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#arriving child tokens - generated by a fork node - are consumed upon arrival. the last arriving child token transfers the
|
36
|
+
#child's parent token to this node which will leave the node immediately using the node's :default transition. thus, only
|
37
|
+
#one leaving transition - :default - is allowed. children token variables will also be lost upon arrival of their associated
|
38
|
+
#tokens - you have to save child tokens variables manually if you want to preserve them (by copying them to the parent token's
|
39
|
+
#context, for instance)
|
40
|
+
class JoinNode
|
41
|
+
def action(token, caller)
|
42
|
+
parent_token = token.parent
|
43
|
+
token.done
|
44
|
+
parent_token.childs? ? nil : [[:default, parent_token]]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#the arriving token triggers the creation and execution of a named sub process instance/workflow. token variables mentioned in
|
49
|
+
#the :call_in mapping are copied from arriving token's context to the root token of the newly created sub process instance
|
50
|
+
#before starting the sub process instance. if the sub workflow does not contain states - and therefore the sub
|
51
|
+
#workflow will terminate immediately -, the token will leave the call node
|
52
|
+
#immediately using the node's :default transition. only one leaving transition - :default - is allowed. before leaving,
|
53
|
+
#all token variables mentioned in the :call_out mapping are copied from the sub workflow's root token context to the
|
54
|
+
#context of the token waiting in the call node. otherwise, if the sub workflow's root token arrived in a state, the token remains in the
|
55
|
+
#call node and - from a workflow user's point of view - the call node behaves like a state node. but do not call signal
|
56
|
+
#on the token waiting in the call node! you have to call signal on the sub workflow's token waiting in the sub workflow's
|
57
|
+
#state node - the node waiting in the call node will be signaled automatically upon sub workflow termination.
|
58
|
+
class CallNode
|
59
|
+
def initialize(workflow, workflow_version, call_in, call_out)
|
60
|
+
@workflow = workflow
|
61
|
+
@workflow_version = workflow_version
|
62
|
+
@call_in = call_in
|
63
|
+
@call_out = call_out
|
64
|
+
end
|
65
|
+
|
66
|
+
def action(token, caller)
|
67
|
+
if token.sub_process
|
68
|
+
#sub process has been finished...
|
69
|
+
|
70
|
+
@call_out.each do |remote,local|
|
71
|
+
token[local] = token.sub_process[remote]
|
72
|
+
end
|
73
|
+
|
74
|
+
token.sub_process = nil
|
75
|
+
|
76
|
+
return [[:default, token]]
|
77
|
+
else
|
78
|
+
#let's start a sub process...
|
79
|
+
|
80
|
+
if (@workflow.is_a? Symbol)
|
81
|
+
sub_wf = WorkflowVersionManager.create_workflow_instance(@workflow, @workflow_version.nil? ? :latest : @workflow_version, token.workflow.super_workflow_ref)
|
82
|
+
else
|
83
|
+
sub_wf = @workflow.new(token.workflow.super_workflow_ref)
|
84
|
+
end
|
85
|
+
sub_wf.token_id_generator = token.workflow.token_id_generator
|
86
|
+
|
87
|
+
params = {}
|
88
|
+
@call_in.each do |local,remote|
|
89
|
+
params[remote] = token[local]
|
90
|
+
end
|
91
|
+
|
92
|
+
sub_t = sub_wf.start(params)
|
93
|
+
|
94
|
+
if (sub_t.end?)
|
95
|
+
@call_out.each do |remote,local|
|
96
|
+
token[local] = sub_t[remote]
|
97
|
+
end
|
98
|
+
|
99
|
+
return [[:default, token]]
|
100
|
+
else
|
101
|
+
sub_t.parent_process = token
|
102
|
+
token.sub_process = sub_t
|
103
|
+
|
104
|
+
return nil #stop wf execution... we'll be called later...
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,265 @@
|
|
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
|
+
require 'yaml'
|
15
|
+
|
16
|
+
module Rbpm
|
17
|
+
#placeholder
|
18
|
+
end
|
19
|
+
|
20
|
+
module Rbpm::FlockFileSystem
|
21
|
+
|
22
|
+
class WorkflowPersistenceManager
|
23
|
+
def initialize(index_file_path)
|
24
|
+
@token_list = WorkflowTokenIndex.new index_file_path
|
25
|
+
end
|
26
|
+
|
27
|
+
#creates a - named - workflow and starts the workflow afterwards.
|
28
|
+
#if the workflow is not finished - a token waits in a state -
|
29
|
+
#after execution, the current state of the workflow - and
|
30
|
+
#the state of started sub workflows as well - will be
|
31
|
+
#persisted in a YAML file associated with the workflow instance.
|
32
|
+
#furthermore, all active tokens - and child tokens, and
|
33
|
+
#tokens of started sub processes as well - are added to the
|
34
|
+
#token list and the current state of the token list (some sort
|
35
|
+
#of index which allows to find a persisted workflow associated
|
36
|
+
#with a token) will be persisted too.
|
37
|
+
def create_and_start(workflow_name, token_vars = {})
|
38
|
+
wf_ref = @token_list.create_new_super_workflow_id
|
39
|
+
begin
|
40
|
+
wf = Rbpm::WorkflowVersionManager.create_workflow_instance workflow_name, :latest, wf_ref
|
41
|
+
wf.token_id_generator = @token_list
|
42
|
+
t = wf.start token_vars
|
43
|
+
unless t.end?
|
44
|
+
create_persistent_workflow wf, wf_ref
|
45
|
+
wf_ref = nil
|
46
|
+
end
|
47
|
+
return t.freeze
|
48
|
+
ensure
|
49
|
+
@token_list.update_workflow_tokens(wf_ref, []) if wf_ref
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#sends a signal to a token associated with a persisted workflow (only token
|
54
|
+
#residing in a state should be signaled). the state of the "root-parent" workflow
|
55
|
+
#of the workflow associated with the token - can be the workflow itself :) -
|
56
|
+
#is loaded from disk - and therefore the state of all sub workflows is restored as well -
|
57
|
+
#and the token is signalled afterwards. from now on, this method behaves like
|
58
|
+
#create_and_start: the state is only persisted again, if there is at least one
|
59
|
+
#token waiting in a state after execution. this means that you business logic
|
60
|
+
#has to preserve all relevant information stored as token context variables!
|
61
|
+
def signal(token_ref, token_vars = {})
|
62
|
+
wf_ref = @token_list.lookup_super_workflow token_ref
|
63
|
+
wf_tokens = []
|
64
|
+
begin
|
65
|
+
modify_lock_workflow(wf_ref, true) do |wf|
|
66
|
+
token = wf.find_token token_ref
|
67
|
+
token_vars.each do |key,value|
|
68
|
+
token[key] = value
|
69
|
+
end
|
70
|
+
token.signal
|
71
|
+
wf.all_tokens do |wf_token|
|
72
|
+
wf_tokens << wf_token.ref unless wf_token.end?
|
73
|
+
end
|
74
|
+
return token.freeze
|
75
|
+
end
|
76
|
+
ensure
|
77
|
+
@token_list.update_workflow_tokens wf_ref, wf_tokens
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
#returns a freezed version of a token associated with a persisted workflow. the
|
82
|
+
#workflow is loaded using the logic mentioned in the comment of the signal method.
|
83
|
+
#using this method you can gain access to token variables of tokens waiting
|
84
|
+
#in states. as mentioned above, you cannot access the token context of "finished"
|
85
|
+
#tokens because they are already destroyed!
|
86
|
+
def freezed_token(token_ref)
|
87
|
+
wf_ref = @token_list.lookup_super_workflow token_ref
|
88
|
+
modify_lock_workflow(wf_ref, false) do |wf|
|
89
|
+
token = wf.find_token token_ref
|
90
|
+
return token.freeze
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def create_persistent_workflow(wf, wf_ref)
|
97
|
+
file = File.open("rbpm-workflow-#{wf_ref}.yml", "w+") do |file|
|
98
|
+
file.flock(File::LOCK_EX)
|
99
|
+
write(file, wf)
|
100
|
+
file.flock(File::LOCK_UN)
|
101
|
+
end
|
102
|
+
wf_tokens = []
|
103
|
+
wf.all_tokens do |wf_token|
|
104
|
+
wf_tokens << wf_token.ref unless wf_token.end?
|
105
|
+
end
|
106
|
+
@token_list.update_workflow_tokens wf_ref, wf_tokens
|
107
|
+
end
|
108
|
+
|
109
|
+
def modify_lock_workflow(wf_ref, write_back) #do |wf|
|
110
|
+
delete_path = nil
|
111
|
+
begin
|
112
|
+
file = File.open("rbpm-workflow-#{wf_ref}.yml", "r+") do |file|
|
113
|
+
file.flock(File::LOCK_EX)
|
114
|
+
begin
|
115
|
+
wf = read(file)
|
116
|
+
begin
|
117
|
+
yield(wf)
|
118
|
+
ensure
|
119
|
+
unless wf.find_root_token
|
120
|
+
delete_path = file.path
|
121
|
+
else
|
122
|
+
if write_back
|
123
|
+
file.pos = 0
|
124
|
+
file.truncate 0
|
125
|
+
write(file, wf)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
ensure
|
130
|
+
file.flock(File::LOCK_UN)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
ensure
|
134
|
+
File.delete delete_path if delete_path
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def read(f)
|
139
|
+
content = ""
|
140
|
+
while (line = f.gets)
|
141
|
+
content = content + "\n" + line
|
142
|
+
end
|
143
|
+
YAML.load(content)
|
144
|
+
end
|
145
|
+
|
146
|
+
def write(file, object)
|
147
|
+
content = YAML.dump(object)
|
148
|
+
file.puts content
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class WorkflowTokenIndex
|
153
|
+
def initialize(index_file_path)
|
154
|
+
@index_file_path = index_file_path
|
155
|
+
end
|
156
|
+
|
157
|
+
def lookup_super_workflow(lookup_token_id)
|
158
|
+
return nil unless index_file_exists?
|
159
|
+
file = open_read_locked_index_file
|
160
|
+
begin
|
161
|
+
while (token_workflow = read_index_entry file)
|
162
|
+
token_id, workflow_id = token_workflow
|
163
|
+
return workflow_id if token_id == lookup_token_id
|
164
|
+
end
|
165
|
+
ensure
|
166
|
+
close_unlock_file file
|
167
|
+
end
|
168
|
+
return nil
|
169
|
+
end
|
170
|
+
|
171
|
+
def update_workflow_tokens(super_workflow_id, token_ids)
|
172
|
+
manage_workflow_tokens :update_workflow_tokens, super_workflow_id, token_ids
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_new_super_workflow_id
|
176
|
+
manage_workflow_tokens
|
177
|
+
end
|
178
|
+
|
179
|
+
def generate_token_id(super_workflow_id)
|
180
|
+
manage_workflow_tokens :create_new_workflow_token_id, super_workflow_id
|
181
|
+
end
|
182
|
+
|
183
|
+
protected
|
184
|
+
|
185
|
+
def manage_workflow_tokens(operation = :create_super_workflow_and_placeholder_token, super_workflow_id = nil, token_ids = [])
|
186
|
+
token_workflows = []
|
187
|
+
not_empty = index_file_exists?
|
188
|
+
file = not_empty ? open_readwrite_locked_index_file : open_create_locked_index_file
|
189
|
+
begin
|
190
|
+
max_id = -1;
|
191
|
+
if not_empty
|
192
|
+
while (token_workflow = read_index_entry file)
|
193
|
+
token_id, workflow_id = token_workflow
|
194
|
+
case operation
|
195
|
+
when :update_workflow_tokens
|
196
|
+
token_workflows << token_workflow unless workflow_id == super_workflow_id
|
197
|
+
when :create_super_workflow_and_placeholder_token
|
198
|
+
token_workflows << token_workflow
|
199
|
+
max_id = workflow_id if workflow_id > max_id
|
200
|
+
when :create_new_workflow_token_id
|
201
|
+
token_workflows << token_workflow
|
202
|
+
max_id = token_id if token_id > max_id
|
203
|
+
end
|
204
|
+
end
|
205
|
+
file.pos = 0
|
206
|
+
file.truncate 0
|
207
|
+
end
|
208
|
+
case operation
|
209
|
+
when :create_super_workflow_and_placeholder_token
|
210
|
+
super_workflow_id = (max_id + 1)
|
211
|
+
token_ids << -1
|
212
|
+
when :create_new_workflow_token_id
|
213
|
+
token_ids << (max_id + 1)
|
214
|
+
end
|
215
|
+
token_ids.each do |token_id|
|
216
|
+
token_workflows << [token_id, super_workflow_id]
|
217
|
+
end
|
218
|
+
token_workflows.each do |token_workflow|
|
219
|
+
token_id, workflow_id = token_workflow
|
220
|
+
file.puts token_id
|
221
|
+
file.puts workflow_id
|
222
|
+
end
|
223
|
+
return (max_id + 1)
|
224
|
+
ensure
|
225
|
+
close_unlock_file file
|
226
|
+
File.delete file.path if token_workflows.empty?
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def index_file_exists?
|
231
|
+
File.exist?(@index_file_path)
|
232
|
+
end
|
233
|
+
|
234
|
+
def open_read_locked_index_file
|
235
|
+
file = File.open(@index_file_path, "r")
|
236
|
+
file.flock(File::LOCK_EX)
|
237
|
+
return file
|
238
|
+
end
|
239
|
+
|
240
|
+
def open_readwrite_locked_index_file
|
241
|
+
file = File.open(@index_file_path, "r+")
|
242
|
+
file.flock(File::LOCK_EX)
|
243
|
+
return file
|
244
|
+
end
|
245
|
+
|
246
|
+
def open_create_locked_index_file
|
247
|
+
file = File.open(@index_file_path, "w+")
|
248
|
+
file.flock(File::LOCK_EX)
|
249
|
+
return file
|
250
|
+
end
|
251
|
+
|
252
|
+
def read_index_entry(file)
|
253
|
+
token_id = file.gets
|
254
|
+
return nil unless
|
255
|
+
workflow_id = file.gets
|
256
|
+
return nil unless workflow_id
|
257
|
+
return [token_id.to_i, workflow_id.to_i]
|
258
|
+
end
|
259
|
+
|
260
|
+
def close_unlock_file(file)
|
261
|
+
file.flock(File::LOCK_UN)
|
262
|
+
file.close
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
require_gem 'rbpm', '>= 0.0.3'
|
4
|
+
rescue LoadError
|
5
|
+
require '../lib/rbpm'
|
6
|
+
end
|
7
|
+
|
8
|
+
def args_to_hash(args, start_index = 1)
|
9
|
+
result = {}
|
10
|
+
if args.length > start_index
|
11
|
+
key = nil
|
12
|
+
args[start_index .. args.length - 1].each do |arg|
|
13
|
+
if key
|
14
|
+
result[key.to_sym] = arg
|
15
|
+
key = nil
|
16
|
+
else
|
17
|
+
key = arg
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
#note: don't forget to load (-r) your workflow. sample: ruby -r sample_wf generic_persistent_workflow_manager.rb create "SampleWorkflow"
|
25
|
+
|
26
|
+
mgr = Rbpm::FlockFileSystem::WorkflowPersistenceManager.new "ptoken.ind" #stores workflows in the current working directory!
|
27
|
+
cmd = ARGV[0].to_sym
|
28
|
+
|
29
|
+
case cmd
|
30
|
+
when :create
|
31
|
+
workflow_name = ARGV[1]
|
32
|
+
token_vars = args_to_hash ARGV, 2
|
33
|
+
token = mgr.create_and_start(workflow_name, token_vars)
|
34
|
+
puts token.ref
|
35
|
+
exit(token.end? ? 0 : token.ref)
|
36
|
+
when :signal
|
37
|
+
token_ref = ARGV[1].to_i
|
38
|
+
token_vars = args_to_hash ARGV, 2
|
39
|
+
token = mgr.signal(token_ref, token_vars)
|
40
|
+
exit(token.end? ? 0 : token.ref)
|
41
|
+
when :print
|
42
|
+
token_ref = ARGV[1].to_i
|
43
|
+
ctx_var = ARGV[2].to_sym
|
44
|
+
token = mgr.freezed_token(token_ref)
|
45
|
+
puts token[ctx_var]
|
46
|
+
end
|
data/lib/rbpm/token.rb
ADDED
@@ -0,0 +1,169 @@
|
|
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
|
+
#tokens traverse a directed graph composed of nodes and transitions. they cause execution
|
17
|
+
#of event actions upon arrival in a node, before leaving a node or while moving from one
|
18
|
+
#node to another by taking a transition. tokens can create child tokens and a token
|
19
|
+
#can be associated with 0..1 parent- and 0..1 child- process instances. if a token does not
|
20
|
+
#have a parent token then it is the process instance's root token. in general, a token never
|
21
|
+
#waits in a node but immediately leaves the node after arrival. there are three exceptions:
|
22
|
+
#a token can create sub tokens which leave the node immediately (instead of the parent token).
|
23
|
+
#a token can wait in a state node (wait being signaled manually). and last but
|
24
|
+
#not least a node can defer execution to the root node of a newly created
|
25
|
+
#sub process and wait in a call node until the sub process has been finished. you can
|
26
|
+
#store :named variables in the token context. token variables can be involved in transition
|
27
|
+
#condition expression and can be get or set by event actions. if a token waits in a state
|
28
|
+
#the token variables can be manipulated by the workflow caller as well. but note that
|
29
|
+
#child tokens - and token variables - are destroyed upon arrival in a join node and
|
30
|
+
#that the root token is destroyed after the workflow has been finished.
|
31
|
+
class Token
|
32
|
+
attr_writer :sub_process, :parent_process
|
33
|
+
attr_reader :workflow, :node, :parent, :sub_process, :parent_process, :ref
|
34
|
+
|
35
|
+
def initialize(workflow, parent = nil, ctx_vars = {}, ref = -1)
|
36
|
+
@workflow = workflow
|
37
|
+
@parent = parent
|
38
|
+
@ctx_vars = ctx_vars
|
39
|
+
@ref = ref
|
40
|
+
@childs = []
|
41
|
+
@sub_process = nil
|
42
|
+
@parent_process = nil
|
43
|
+
@done = false
|
44
|
+
if parent
|
45
|
+
@node = parent.node
|
46
|
+
@parent.add_child(self)
|
47
|
+
else
|
48
|
+
@node = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_root_token
|
53
|
+
return parent.nil? ? self : parent.find_root_token
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_super_token
|
57
|
+
return find_root_token.parent_process.nil? ? find_root_token : find_root_token.parent_process.find_super_token
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_token(token_ref)
|
61
|
+
if @ref == token_ref
|
62
|
+
return self
|
63
|
+
elsif sub_process
|
64
|
+
return sub_process.find_token(token_ref)
|
65
|
+
else
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def all_tokens(&blk)
|
71
|
+
blk.call(self)
|
72
|
+
if sub_process
|
73
|
+
sub_process.all_tokens(&blk)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def all_ctx_vars
|
78
|
+
@ctx_vars
|
79
|
+
end
|
80
|
+
|
81
|
+
#transition condition expressions are evaluated using token's binding()
|
82
|
+
#and this method is here to support expressions like "token[:var_name] == ..."
|
83
|
+
def token
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
#set a token variable
|
88
|
+
def []=(key, value)
|
89
|
+
@ctx_vars[key] = value
|
90
|
+
end
|
91
|
+
|
92
|
+
#get a token variable
|
93
|
+
def [](key)
|
94
|
+
@ctx_vars[key]
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_child(token)
|
98
|
+
@childs << token
|
99
|
+
end
|
100
|
+
|
101
|
+
def remove_child(token)
|
102
|
+
@childs.delete(token)
|
103
|
+
end
|
104
|
+
|
105
|
+
#is this the parent of children tokens?
|
106
|
+
def childs?
|
107
|
+
not @childs.empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
#sends a signal to a node waiting in a state.
|
111
|
+
#never call this method manually if the node
|
112
|
+
#does not wait in a state but does wait in
|
113
|
+
#a call node, because the node will be signalled
|
114
|
+
#automatically after the sub process instance
|
115
|
+
#has been finished.
|
116
|
+
def signal
|
117
|
+
@workflow.signal_node(self, node)
|
118
|
+
|
119
|
+
if end? && @parent_process
|
120
|
+
@parent_process.signal
|
121
|
+
@parent_process = nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def enter(node)
|
126
|
+
@workflow.enter_node(self, node)
|
127
|
+
end
|
128
|
+
|
129
|
+
def entered(node)
|
130
|
+
@node = node
|
131
|
+
|
132
|
+
if @workflow.clazz.end_node?(@node) and not @parent
|
133
|
+
@workflow.token_done(self)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def transit(transition)
|
138
|
+
hardcoded_transition = @workflow.leave_node(self, @node)
|
139
|
+
if hardcoded_transition
|
140
|
+
@workflow.transit(self, @node, hardcoded_transition)
|
141
|
+
else
|
142
|
+
@workflow.transit(self, @node, transition)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def jump(child)
|
147
|
+
@node = child.node
|
148
|
+
end
|
149
|
+
|
150
|
+
def done
|
151
|
+
@parent.remove_child(self) if @parent
|
152
|
+
@parent.jump(self) if @parent
|
153
|
+
@workflow.token_done(self)
|
154
|
+
@done = true
|
155
|
+
end
|
156
|
+
|
157
|
+
def end?
|
158
|
+
@done || @workflow.clazz.end_node?(@node)
|
159
|
+
end
|
160
|
+
|
161
|
+
def fork(ctx_vars = {})
|
162
|
+
@workflow.create_token(self, ctx_vars)
|
163
|
+
end
|
164
|
+
|
165
|
+
def eval_expr(expr)
|
166
|
+
eval(expr, binding())
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|