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.
@@ -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
@@ -1,9 +1,14 @@
1
1
  require 'test/unit'
2
2
 
3
3
  begin
4
- require 'rbpm'
4
+ require 'rubygems'
5
+ require_gem 'rbpm'
5
6
  rescue LoadError
6
- require '../lib/rbpm'
7
+ begin
8
+ require '../lib/rbpm'
9
+ rescue LoadError
10
+ require 'lib/rbpm'
11
+ end
7
12
  end
8
13
 
9
14
  $mylog = {}
@@ -1,9 +1,14 @@
1
1
  require 'test/unit'
2
2
 
3
3
  begin
4
- require 'rbpm'
4
+ require 'rubygems'
5
+ require_gem 'rbpm'
5
6
  rescue LoadError
6
- require '../lib/rbpm'
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 'rbpm'
4
+ require 'rubygems'
5
+ require_gem 'rbpm'
5
6
  rescue LoadError
6
- require '../lib/rbpm'
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
@@ -1,9 +1,14 @@
1
1
  require 'test/unit'
2
2
 
3
3
  begin
4
- require 'rbpm'
4
+ require 'rubygems'
5
+ require_gem 'rbpm'
5
6
  rescue LoadError
6
- require '../lib/rbpm'
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 'rbpm'
4
+ require 'rubygems'
5
+ require_gem 'rbpm'
5
6
  rescue LoadError
6
- require '../lib/rbpm'
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 'rbpm'
4
+ require 'rubygems'
5
+ require_gem 'rbpm'
5
6
  rescue LoadError
6
- require '../lib/rbpm'
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