ruote 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +166 -1
- data/CREDITS.txt +36 -17
- data/LICENSE.txt +1 -1
- data/README.rdoc +1 -7
- data/Rakefile +38 -29
- data/TODO.txt +93 -52
- data/lib/ruote-fs.rb +3 -0
- data/lib/ruote.rb +5 -1
- data/lib/ruote/context.rb +140 -35
- data/lib/ruote/dashboard.rb +1247 -0
- data/lib/ruote/{engine → dboard}/process_error.rb +22 -2
- data/lib/ruote/dboard/process_status.rb +587 -0
- data/lib/ruote/engine.rb +6 -871
- data/lib/ruote/exp/command.rb +7 -2
- data/lib/ruote/exp/commanded.rb +2 -2
- data/lib/ruote/exp/condition.rb +38 -13
- data/lib/ruote/exp/fe_add_branches.rb +1 -1
- data/lib/ruote/exp/fe_apply.rb +1 -1
- data/lib/ruote/exp/fe_await.rb +357 -0
- data/lib/ruote/exp/fe_cancel_process.rb +17 -3
- data/lib/ruote/exp/fe_command.rb +8 -4
- data/lib/ruote/exp/fe_concurrence.rb +218 -18
- data/lib/ruote/exp/fe_concurrent_iterator.rb +71 -10
- data/lib/ruote/exp/fe_cron.rb +3 -10
- data/lib/ruote/exp/fe_cursor.rb +14 -4
- data/lib/ruote/exp/fe_define.rb +3 -1
- data/lib/ruote/exp/fe_echo.rb +1 -1
- data/lib/ruote/exp/fe_equals.rb +1 -1
- data/lib/ruote/exp/fe_error.rb +1 -1
- data/lib/ruote/exp/fe_filter.rb +163 -4
- data/lib/ruote/exp/fe_forget.rb +21 -4
- data/lib/ruote/exp/fe_given.rb +1 -1
- data/lib/ruote/exp/fe_if.rb +1 -1
- data/lib/ruote/exp/fe_inc.rb +102 -35
- data/lib/ruote/exp/fe_iterator.rb +47 -12
- data/lib/ruote/exp/fe_listen.rb +96 -11
- data/lib/ruote/exp/fe_lose.rb +31 -4
- data/lib/ruote/exp/fe_noop.rb +1 -1
- data/lib/ruote/exp/fe_on_error.rb +109 -0
- data/lib/ruote/exp/fe_once.rb +10 -19
- data/lib/ruote/exp/fe_participant.rb +90 -28
- data/lib/ruote/exp/fe_read.rb +69 -0
- data/lib/ruote/exp/fe_redo.rb +3 -2
- data/lib/ruote/exp/fe_ref.rb +57 -27
- data/lib/ruote/exp/fe_registerp.rb +1 -3
- data/lib/ruote/exp/fe_reserve.rb +1 -1
- data/lib/ruote/exp/fe_restore.rb +6 -6
- data/lib/ruote/exp/fe_save.rb +12 -19
- data/lib/ruote/exp/fe_sequence.rb +38 -2
- data/lib/ruote/exp/fe_set.rb +143 -40
- data/lib/ruote/exp/{fe_let.rb → fe_stall.rb} +7 -38
- data/lib/ruote/exp/fe_subprocess.rb +8 -2
- data/lib/ruote/exp/fe_that.rb +1 -1
- data/lib/ruote/exp/fe_undo.rb +40 -4
- data/lib/ruote/exp/fe_unregisterp.rb +1 -3
- data/lib/ruote/exp/fe_wait.rb +12 -25
- data/lib/ruote/exp/{flowexpression.rb → flow_expression.rb} +375 -229
- data/lib/ruote/exp/iterator.rb +2 -2
- data/lib/ruote/exp/merge.rb +78 -17
- data/lib/ruote/exp/ro_attributes.rb +46 -36
- data/lib/ruote/exp/ro_filters.rb +34 -8
- data/lib/ruote/exp/ro_on_x.rb +431 -0
- data/lib/ruote/exp/ro_persist.rb +19 -7
- data/lib/ruote/exp/ro_timers.rb +123 -0
- data/lib/ruote/exp/ro_variables.rb +90 -29
- data/lib/ruote/fei.rb +57 -3
- data/lib/ruote/fs.rb +3 -0
- data/lib/ruote/id/mnemo_wfid_generator.rb +30 -7
- data/lib/ruote/id/wfid_generator.rb +17 -38
- data/lib/ruote/log/default_history.rb +23 -9
- data/lib/ruote/log/fancy_printing.rb +265 -0
- data/lib/ruote/log/storage_history.rb +23 -13
- data/lib/ruote/log/wait_logger.rb +224 -17
- data/lib/ruote/observer.rb +82 -0
- data/lib/ruote/part/block_participant.rb +65 -28
- data/lib/ruote/part/code_participant.rb +81 -0
- data/lib/ruote/part/engine_participant.rb +7 -2
- data/lib/ruote/part/local_participant.rb +221 -21
- data/lib/ruote/part/no_op_participant.rb +1 -1
- data/lib/ruote/part/null_participant.rb +1 -1
- data/lib/ruote/part/participant.rb +50 -0
- data/lib/ruote/part/rev_participant.rb +178 -0
- data/lib/ruote/part/smtp_participant.rb +2 -2
- data/lib/ruote/part/storage_participant.rb +228 -60
- data/lib/ruote/part/template.rb +1 -1
- data/lib/ruote/participant.rb +2 -0
- data/lib/ruote/reader.rb +205 -68
- data/lib/ruote/reader/json.rb +49 -0
- data/lib/ruote/reader/radial.rb +303 -0
- data/lib/ruote/reader/ruby_dsl.rb +44 -9
- data/lib/ruote/reader/xml.rb +11 -8
- data/lib/ruote/receiver/base.rb +98 -45
- data/lib/ruote/storage/base.rb +104 -35
- data/lib/ruote/storage/composite_storage.rb +50 -60
- data/lib/ruote/storage/fs_storage.rb +25 -34
- data/lib/ruote/storage/hash_storage.rb +38 -36
- data/lib/ruote/svc/dispatch_pool.rb +104 -35
- data/lib/ruote/svc/dollar_sub.rb +10 -8
- data/lib/ruote/svc/error_handler.rb +108 -52
- data/lib/ruote/svc/expression_map.rb +3 -3
- data/lib/ruote/svc/participant_list.rb +160 -55
- data/lib/ruote/svc/tracker.rb +31 -31
- data/lib/ruote/svc/treechecker.rb +28 -16
- data/lib/ruote/tree_dot.rb +1 -1
- data/lib/ruote/util/deep.rb +143 -0
- data/lib/ruote/util/filter.rb +125 -18
- data/lib/ruote/util/hashdot.rb +15 -13
- data/lib/ruote/util/look.rb +1 -1
- data/lib/ruote/util/lookup.rb +60 -22
- data/lib/ruote/util/misc.rb +63 -18
- data/lib/ruote/util/mpatch.rb +53 -0
- data/lib/ruote/util/ometa.rb +1 -2
- data/lib/ruote/util/process_observer.rb +177 -0
- data/lib/ruote/util/subprocess.rb +1 -1
- data/lib/ruote/util/time.rb +2 -2
- data/lib/ruote/util/tree.rb +64 -2
- data/lib/ruote/version.rb +3 -2
- data/lib/ruote/worker.rb +421 -92
- data/lib/ruote/workitem.rb +157 -22
- data/ruote.gemspec +15 -9
- data/test/bm/ci.rb +0 -2
- data/test/bm/ici.rb +0 -2
- data/test/bm/load_26c.rb +0 -3
- data/test/bm/mega.rb +0 -2
- data/test/functional/base.rb +57 -43
- data/test/functional/concurrent_base.rb +16 -13
- data/test/functional/ct_0_concurrence.rb +7 -11
- data/test/functional/ct_1_iterator.rb +9 -11
- data/test/functional/ct_2_cancel.rb +28 -17
- data/test/functional/eft_0_flow_expression.rb +35 -0
- data/test/functional/eft_10_cancel_process.rb +1 -1
- data/test/functional/eft_11_wait.rb +13 -13
- data/test/functional/eft_12_listen.rb +199 -66
- data/test/functional/eft_13_iterator.rb +95 -29
- data/test/functional/eft_14_cursor.rb +74 -24
- data/test/functional/eft_15_loop.rb +7 -7
- data/test/functional/eft_16_if.rb +1 -1
- data/test/functional/eft_17_equals.rb +1 -1
- data/test/functional/eft_18_concurrent_iterator.rb +156 -68
- data/test/functional/eft_19_reserve.rb +15 -15
- data/test/functional/eft_1_echo.rb +1 -1
- data/test/functional/eft_20_save.rb +51 -9
- data/test/functional/eft_21_restore.rb +1 -1
- data/test/functional/eft_22_noop.rb +1 -1
- data/test/functional/eft_23_apply.rb +1 -1
- data/test/functional/eft_24_add_branches.rb +7 -8
- data/test/functional/eft_25_command.rb +1 -1
- data/test/functional/eft_26_error.rb +11 -11
- data/test/functional/eft_27_inc.rb +111 -67
- data/test/functional/eft_28_once.rb +16 -16
- data/test/functional/eft_29_cron.rb +9 -9
- data/test/functional/eft_2_sequence.rb +23 -4
- data/test/functional/eft_30_ref.rb +36 -24
- data/test/functional/eft_31_registerp.rb +24 -24
- data/test/functional/eft_32_lose.rb +46 -20
- data/test/functional/eft_34_given.rb +1 -1
- data/test/functional/eft_35_filter.rb +161 -7
- data/test/functional/eft_36_read.rb +97 -0
- data/test/functional/{eft_0_process_definition.rb → eft_37_process_definition.rb} +4 -4
- data/test/functional/eft_38_on_error.rb +195 -0
- data/test/functional/eft_39_stall.rb +35 -0
- data/test/functional/eft_3_participant.rb +77 -22
- data/test/functional/eft_40_await.rb +297 -0
- data/test/functional/eft_4_set.rb +110 -11
- data/test/functional/eft_5_subprocess.rb +27 -5
- data/test/functional/eft_6_concurrence.rb +299 -60
- data/test/functional/eft_7_forget.rb +24 -22
- data/test/functional/eft_8_undo.rb +52 -15
- data/test/functional/eft_9_redo.rb +18 -20
- data/test/functional/ft_0_worker.rb +122 -13
- data/test/functional/ft_10_dollar.rb +77 -16
- data/test/functional/ft_11_recursion.rb +9 -9
- data/test/functional/ft_12_launchitem.rb +7 -9
- data/test/functional/ft_13_variables.rb +125 -22
- data/test/functional/ft_14_re_apply.rb +112 -56
- data/test/functional/ft_15_timeout.rb +64 -33
- data/test/functional/ft_16_participant_params.rb +59 -6
- data/test/functional/ft_17_conditional.rb +68 -2
- data/test/functional/ft_18_kill.rb +48 -30
- data/test/functional/ft_19_participant_code.rb +67 -0
- data/test/functional/ft_1_process_status.rb +222 -150
- data/test/functional/ft_20_storage_participant.rb +445 -44
- data/test/functional/ft_21_forget.rb +21 -26
- data/test/functional/ft_22_process_definitions.rb +8 -6
- data/test/functional/ft_23_load_defs.rb +29 -5
- data/test/functional/ft_24_block_participant.rb +199 -20
- data/test/functional/ft_25_receiver.rb +98 -46
- data/test/functional/ft_26_participant_rtimeout.rb +34 -26
- data/test/functional/ft_27_var_indirection.rb +40 -5
- data/test/functional/ft_28_null_noop_participants.rb +5 -5
- data/test/functional/ft_29_part_template.rb +2 -2
- data/test/functional/ft_2_errors.rb +106 -74
- data/test/functional/ft_30_smtp_participant.rb +7 -7
- data/test/functional/ft_31_part_blocking.rb +11 -11
- data/test/functional/ft_32_scope.rb +50 -0
- data/test/functional/ft_33_participant_subprocess_priority.rb +3 -3
- data/test/functional/ft_34_cursor_rewind.rb +14 -14
- data/test/functional/ft_35_add_service.rb +67 -9
- data/test/functional/ft_36_storage_history.rb +92 -24
- data/test/functional/ft_37_default_history.rb +35 -23
- data/test/functional/ft_38_participant_more.rb +189 -32
- data/test/functional/ft_39_wait_for.rb +25 -25
- data/test/functional/ft_3_participant_registration.rb +235 -107
- data/test/functional/ft_40_wait_logger.rb +105 -18
- data/test/functional/ft_41_participants.rb +13 -12
- data/test/functional/ft_42_storage_copy.rb +12 -12
- data/test/functional/ft_43_participant_on_reply.rb +85 -11
- data/test/functional/ft_44_var_participant.rb +5 -5
- data/test/functional/ft_45_participant_accept.rb +3 -3
- data/test/functional/ft_46_launch_single.rb +17 -17
- data/test/functional/ft_47_wfids.rb +41 -0
- data/test/functional/ft_48_lose.rb +19 -25
- data/test/functional/ft_49_engine_on_error.rb +54 -70
- data/test/functional/ft_4_cancel.rb +84 -26
- data/test/functional/ft_50_engine_config.rb +4 -4
- data/test/functional/ft_51_misc.rb +12 -12
- data/test/functional/ft_52_case.rb +17 -17
- data/test/functional/ft_53_engine_on_terminate.rb +18 -21
- data/test/functional/ft_54_patterns.rb +18 -16
- data/test/functional/ft_55_engine_participant.rb +55 -55
- data/test/functional/ft_56_filter_attribute.rb +90 -52
- data/test/functional/ft_57_rev_participant.rb +252 -0
- data/test/functional/ft_58_workitem.rb +150 -0
- data/test/functional/ft_59_pause.rb +329 -0
- data/test/functional/ft_5_on_error.rb +430 -77
- data/test/functional/ft_60_code_participant.rb +65 -0
- data/test/functional/ft_61_trailing_fields.rb +34 -0
- data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
- data/test/functional/ft_63_participants_221.rb +458 -0
- data/test/functional/ft_64_stash.rb +41 -0
- data/test/functional/ft_65_timers.rb +313 -0
- data/test/functional/ft_66_flank.rb +133 -0
- data/test/functional/ft_67_radial_misc.rb +34 -0
- data/test/functional/ft_68_reput.rb +72 -0
- data/test/functional/ft_69_worker_info.rb +56 -0
- data/test/functional/ft_6_on_cancel.rb +189 -36
- data/test/functional/ft_70_take_and_discard_attributes.rb +94 -0
- data/test/functional/ft_71_retries.rb +144 -0
- data/test/functional/ft_72_on_terminate.rb +60 -0
- data/test/functional/ft_73_raise_msg.rb +107 -0
- data/test/functional/ft_74_respark.rb +106 -0
- data/test/functional/ft_75_context.rb +66 -0
- data/test/functional/ft_76_observer.rb +53 -0
- data/test/functional/ft_77_process_observer.rb +157 -0
- data/test/functional/ft_78_part_participant.rb +37 -0
- data/test/functional/ft_7_tags.rb +238 -50
- data/test/functional/ft_8_participant_consumption.rb +27 -21
- data/test/functional/ft_9_subprocesses.rb +48 -18
- data/test/functional/restart_base.rb +4 -6
- data/test/functional/rt_0_wait.rb +10 -10
- data/test/functional/rt_1_listen.rb +6 -6
- data/test/functional/rt_2_errors.rb +12 -12
- data/test/functional/rt_3_once.rb +17 -12
- data/test/functional/rt_4_cron.rb +17 -17
- data/test/functional/rt_5_timeout.rb +13 -13
- data/test/functional/signals.rb +103 -0
- data/test/functional/storage.rb +730 -0
- data/test/functional/storage_helper.rb +48 -35
- data/test/functional/test.rb +6 -2
- data/test/misc/idle.rb +21 -0
- data/test/misc/light.rb +29 -0
- data/test/path_helper.rb +1 -1
- data/test/test.rb +2 -5
- data/test/test_helper.rb +13 -0
- data/test/unit/test.rb +1 -4
- data/test/unit/ut_0_ruby_reader.rb +25 -9
- data/test/unit/ut_10_participants.rb +47 -0
- data/test/unit/ut_11_lookup.rb +59 -2
- data/test/unit/ut_12_wait_logger.rb +123 -0
- data/test/unit/ut_14_is_uri.rb +1 -1
- data/test/unit/ut_15_util.rb +1 -1
- data/test/unit/ut_16_reader.rb +136 -14
- data/test/unit/ut_17_merge.rb +155 -0
- data/test/unit/ut_19_part_template.rb +1 -1
- data/test/unit/ut_1_fei.rb +11 -2
- data/test/unit/ut_20_composite_storage.rb +27 -1
- data/test/unit/{ut_21_participant_list.rb → ut_21_svc_participant_list.rb} +2 -3
- data/test/unit/ut_22_filter.rb +231 -10
- data/test/unit/ut_23_svc_tracker.rb +48 -0
- data/test/unit/ut_24_radial_reader.rb +458 -0
- data/test/unit/ut_25_process_status.rb +143 -0
- data/test/unit/ut_26_deep.rb +131 -0
- data/test/unit/ut_2_dashboard.rb +114 -0
- data/test/unit/ut_3_worker.rb +54 -0
- data/test/unit/ut_4_expmap.rb +1 -1
- data/test/unit/ut_5_tree.rb +23 -23
- data/test/unit/ut_6_condition.rb +71 -29
- data/test/unit/ut_7_workitem.rb +18 -4
- data/test/unit/ut_8_tree_to_dot.rb +1 -1
- data/test/unit/ut_9_xml_reader.rb +1 -1
- metadata +142 -63
- data/jruby_issue.txt +0 -32
- data/lib/ruote/engine/process_status.rb +0 -403
- data/lib/ruote/log/pretty.rb +0 -165
- data/lib/ruote/log/test_logger.rb +0 -204
- data/lib/ruote/util/serializer.rb +0 -103
- data/phil.txt +0 -14
- data/test/functional/eft_33_let.rb +0 -31
- data/test/functional/ft_19_alias.rb +0 -33
- data/test/functional/ft_47_wfid_generator.rb +0 -54
- data/test/unit/storage.rb +0 -403
- data/test/unit/storages.rb +0 -37
- data/test/unit/ut_13_serializer.rb +0 -65
- data/test/unit/ut_18_engine.rb +0 -47
- data/test/unit/ut_3_wait_logger.rb +0 -39
data/lib/ruote-fs.rb
ADDED
data/lib/ruote.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
|
2
|
+
require 'ruote/util/deep'
|
3
|
+
require 'ruote/util/lookup'
|
4
|
+
require 'ruote/util/mpatch'
|
2
5
|
require 'ruote/storage/hash_storage'
|
3
6
|
require 'ruote/worker'
|
4
|
-
require 'ruote/engine'
|
7
|
+
require 'ruote/engine' # for backward compatibility
|
8
|
+
require 'ruote/dashboard'
|
5
9
|
require 'ruote/participant'
|
6
10
|
require 'ruote/reader/ruby_dsl'
|
7
11
|
|
data/lib/ruote/context.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2005-
|
2
|
+
# Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -37,19 +37,16 @@ module Ruote
|
|
37
37
|
SERVICE_PREFIX = /^s\_/
|
38
38
|
|
39
39
|
attr_reader :storage
|
40
|
-
attr_accessor :
|
41
|
-
attr_accessor :engine
|
40
|
+
attr_accessor :dashboard
|
42
41
|
|
43
|
-
def initialize(storage
|
42
|
+
def initialize(storage)
|
44
43
|
|
45
44
|
@storage = storage
|
46
45
|
@storage.context = self
|
47
46
|
|
48
|
-
@
|
49
|
-
@worker = worker
|
47
|
+
@dashboard = nil
|
50
48
|
|
51
49
|
@services = {}
|
52
|
-
|
53
50
|
initialize_services
|
54
51
|
end
|
55
52
|
|
@@ -66,12 +63,25 @@ module Ruote
|
|
66
63
|
self
|
67
64
|
end
|
68
65
|
|
66
|
+
# Let's make sure Context always responds to #storage, #dashboard (#engine)
|
67
|
+
# and #worker.
|
68
|
+
#
|
69
|
+
alias engine dashboard
|
70
|
+
|
71
|
+
# Let's make sure Context always responds to #storage, #dashboard (#engine)
|
72
|
+
# and #worker.
|
73
|
+
#
|
74
|
+
def worker
|
75
|
+
|
76
|
+
@services['s_worker']
|
77
|
+
end
|
78
|
+
|
69
79
|
# Returns the engine_id (as set in the configuration under the key
|
70
80
|
# "engine_id"), or, by default, "engine".
|
71
81
|
#
|
72
82
|
def engine_id
|
73
83
|
|
74
|
-
|
84
|
+
conf['engine_id'] || 'engine'
|
75
85
|
end
|
76
86
|
|
77
87
|
# Used for things like
|
@@ -82,7 +92,11 @@ module Ruote
|
|
82
92
|
#
|
83
93
|
def [](key)
|
84
94
|
|
85
|
-
SERVICE_PREFIX.match(key)
|
95
|
+
if SERVICE_PREFIX.match(key)
|
96
|
+
@services[key]
|
97
|
+
else
|
98
|
+
conf[key]
|
99
|
+
end
|
86
100
|
end
|
87
101
|
|
88
102
|
# Mostly used by engine#configure
|
@@ -93,74 +107,140 @@ module Ruote
|
|
93
107
|
ArgumentError.new('use context#add_service to register services')
|
94
108
|
) if SERVICE_PREFIX.match(key)
|
95
109
|
|
96
|
-
|
97
|
-
|
98
|
-
@storage.put(cf)
|
110
|
+
@storage.put(conf.merge(key => value))
|
111
|
+
# TODO blindly trust the put ? retry in case of failure ?
|
99
112
|
|
100
113
|
value
|
101
114
|
end
|
102
115
|
|
116
|
+
# Configuration keys and service keys.
|
117
|
+
#
|
103
118
|
def keys
|
104
119
|
|
105
|
-
|
120
|
+
(@services.keys + conf.keys).uniq.sort
|
106
121
|
end
|
107
122
|
|
123
|
+
# Called by Ruote::Dashboard#add_service
|
124
|
+
#
|
108
125
|
def add_service(key, *args)
|
109
126
|
|
127
|
+
raise ArgumentError.new(
|
128
|
+
'#add_service: at least two arguments please'
|
129
|
+
) if args.empty?
|
130
|
+
|
131
|
+
key = key.to_s
|
110
132
|
path, klass, opts = args
|
111
133
|
|
112
134
|
key = "s_#{key}" unless SERVICE_PREFIX.match(key)
|
113
135
|
|
114
|
-
|
136
|
+
aa = [ self, opts ].compact
|
115
137
|
|
116
|
-
|
138
|
+
service = if klass
|
117
139
|
|
118
|
-
|
119
|
-
aa << opts if opts
|
140
|
+
require(path)
|
120
141
|
|
121
142
|
@services[key] = Ruote.constantize(klass).new(*aa)
|
143
|
+
|
144
|
+
elsif path.is_a?(Class)
|
145
|
+
|
146
|
+
@services[key] = path.new(*aa)
|
147
|
+
|
122
148
|
else
|
123
149
|
|
124
150
|
@services[key] = path
|
125
151
|
end
|
126
152
|
|
127
|
-
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
153
|
+
(class << self; self; end).class_eval(
|
154
|
+
%{ def #{key[2..-1]}; @services['#{key}']; end })
|
155
|
+
#
|
156
|
+
# This 'two-liner' will add an instance method to Context for this
|
157
|
+
# service.
|
158
|
+
#
|
159
|
+
# If the service key is 's_dishwasher', then the service will be
|
160
|
+
# available via Context#dishwasher.
|
161
|
+
#
|
162
|
+
# I.e. dishwasher = engine.context.dishwasher
|
136
163
|
|
137
164
|
service
|
138
165
|
end
|
139
166
|
|
167
|
+
# This is kind of evil. Notifies services responding to #on_pre_msg
|
168
|
+
# with the msg before it gets processed.
|
169
|
+
#
|
170
|
+
# Might be useful in some cases. Use with great care.
|
171
|
+
#
|
172
|
+
def pre_notify(msg)
|
173
|
+
|
174
|
+
@services.select { |n, s|
|
175
|
+
s.respond_to?(:on_pre_msg)
|
176
|
+
}.sort_by { |n, s|
|
177
|
+
n
|
178
|
+
}.each { |n, s|
|
179
|
+
s.on_pre_msg(msg)
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
# This method is called by the worker each time it sucessfully processed
|
184
|
+
# a msg. This method calls in turn the #on_msg method for each of the
|
185
|
+
# services (that respond to that method).
|
186
|
+
#
|
187
|
+
# Makes sure that observers that respond to #wait_for are called last.
|
188
|
+
#
|
189
|
+
def notify(msg)
|
190
|
+
|
191
|
+
waiters, observers = @services.select { |n, s|
|
192
|
+
s.respond_to?(:on_msg)
|
193
|
+
}.sort_by { |n, s|
|
194
|
+
n
|
195
|
+
}.partition { |n, s|
|
196
|
+
s.respond_to?(:wait_for)
|
197
|
+
}
|
198
|
+
|
199
|
+
(observers + waiters).each { |n, s| s.on_msg(msg) }
|
200
|
+
end
|
201
|
+
|
140
202
|
# Takes care of shutting down every service registered in this context.
|
141
203
|
#
|
142
204
|
def shutdown
|
143
205
|
|
144
|
-
@
|
145
|
-
|
206
|
+
([ @storage ] + @services.values).each do |s|
|
207
|
+
s.shutdown if s.respond_to?(:shutdown)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
alias engine dashboard
|
212
|
+
alias engine= dashboard=
|
213
|
+
|
214
|
+
# Returns true if this context has a given service registered.
|
215
|
+
#
|
216
|
+
def has_service?(service_name)
|
217
|
+
|
218
|
+
service_name = service_name.to_s
|
219
|
+
service_name = "s_#{service_name}" if ! SERVICE_PREFIX.match(service_name)
|
220
|
+
|
221
|
+
@services.has_key?(service_name)
|
222
|
+
end
|
223
|
+
|
224
|
+
# List of services in this context, sorted by their name in alphabetical
|
225
|
+
# order.
|
226
|
+
#
|
227
|
+
def services
|
146
228
|
|
147
|
-
@services.
|
229
|
+
@services.keys.sort.collect { |k| @services[k] }
|
148
230
|
end
|
149
231
|
|
150
232
|
protected
|
151
233
|
|
152
|
-
def
|
234
|
+
def conf
|
153
235
|
|
154
|
-
@storage.get_configuration('engine')
|
236
|
+
@storage.get_configuration('engine')
|
155
237
|
end
|
156
238
|
|
157
239
|
def initialize_services
|
158
240
|
|
159
|
-
default_conf.merge(
|
241
|
+
default_conf.merge(conf).each do |key, value|
|
160
242
|
|
161
|
-
|
162
|
-
|
163
|
-
add_service(key, *value)
|
243
|
+
add_service(key, *value) if SERVICE_PREFIX.match(key)
|
164
244
|
end
|
165
245
|
end
|
166
246
|
|
@@ -190,5 +270,30 @@ module Ruote
|
|
190
270
|
'ruote/log/default_history', 'Ruote::DefaultHistory' ] }
|
191
271
|
end
|
192
272
|
end
|
273
|
+
|
274
|
+
#
|
275
|
+
# A minimal context, useful for testing expressions in isolation.
|
276
|
+
#
|
277
|
+
class TestContext < Context
|
278
|
+
|
279
|
+
def initialize
|
280
|
+
|
281
|
+
@services = {}
|
282
|
+
initialize_services
|
283
|
+
end
|
284
|
+
|
285
|
+
protected
|
286
|
+
|
287
|
+
def conf
|
288
|
+
|
289
|
+
{}
|
290
|
+
end
|
291
|
+
|
292
|
+
def default_conf
|
293
|
+
|
294
|
+
{ 's_dollar_sub' => [
|
295
|
+
'ruote/svc/dollar_sub', 'Ruote::DollarSubstitution' ] }
|
296
|
+
end
|
297
|
+
end
|
193
298
|
end
|
194
299
|
|
@@ -0,0 +1,1247 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
require 'ruote/context'
|
26
|
+
require 'ruote/util/ometa'
|
27
|
+
require 'ruote/receiver/base'
|
28
|
+
require 'ruote/dboard/process_status'
|
29
|
+
|
30
|
+
|
31
|
+
module Ruote
|
32
|
+
|
33
|
+
#
|
34
|
+
# This class was once named 'Engine', but since ruote 2.x and its introduction
|
35
|
+
# of workers, the methods here are those of a "dashboard". The real engine
|
36
|
+
# being the set of workers.
|
37
|
+
#
|
38
|
+
# The methods here allow to launch processes
|
39
|
+
# and to query about their status. There are also methods for fixing
|
40
|
+
# issues with stalled processes or processes stuck in errors.
|
41
|
+
#
|
42
|
+
# NOTE : the methods #launch and #reply are implemented in
|
43
|
+
# Ruote::ReceiverMixin (this Engine class has all the methods of a Receiver).
|
44
|
+
#
|
45
|
+
class Dashboard
|
46
|
+
|
47
|
+
include ReceiverMixin
|
48
|
+
|
49
|
+
attr_reader :context
|
50
|
+
attr_reader :variables
|
51
|
+
|
52
|
+
# Creates an engine using either worker or storage.
|
53
|
+
#
|
54
|
+
# If a storage instance is given as the first argument, the engine will be
|
55
|
+
# able to manage processes (for example, launch and cancel workflows) but
|
56
|
+
# will not actually run any workflows.
|
57
|
+
#
|
58
|
+
# If a worker instance is given as the first argument and the second
|
59
|
+
# argument is true, engine will start the worker and will be able to both
|
60
|
+
# manage and run workflows.
|
61
|
+
#
|
62
|
+
# If the second options is set to { :join => true }, the worker will
|
63
|
+
# be started and run in the current thread (and the initialize method
|
64
|
+
# will not return).
|
65
|
+
#
|
66
|
+
def initialize(worker_or_storage, opts=true)
|
67
|
+
|
68
|
+
@context = worker_or_storage.context
|
69
|
+
@context.dashboard = self
|
70
|
+
|
71
|
+
@variables = EngineVariables.new(@context.storage)
|
72
|
+
|
73
|
+
workers = @context.services.select { |ser|
|
74
|
+
ser.respond_to?(:run) && ser.respond_to?(:run_in_thread)
|
75
|
+
}
|
76
|
+
|
77
|
+
return unless opts && workers.any?
|
78
|
+
|
79
|
+
# let's isolate a worker to join
|
80
|
+
|
81
|
+
worker = if opts.is_a?(Hash) && opts[:join]
|
82
|
+
workers.find { |wor| wor.name == 'worker' } || workers.first
|
83
|
+
else
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
(workers - Array(worker)).each { |wor| wor.run_in_thread }
|
88
|
+
# launch their thread, but let's not join them
|
89
|
+
|
90
|
+
worker.run if worker
|
91
|
+
# and let's not return
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the storage this engine works with passed at engine
|
95
|
+
# initialization.
|
96
|
+
#
|
97
|
+
def storage
|
98
|
+
|
99
|
+
@context.storage
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the worker nested inside this engine (passed at initialization).
|
103
|
+
# Returns nil if this engine is only linked to a storage (and the worker
|
104
|
+
# is running somewhere else (hopefully)).
|
105
|
+
#
|
106
|
+
def worker
|
107
|
+
|
108
|
+
@context.worker
|
109
|
+
end
|
110
|
+
|
111
|
+
# A shortcut for engine.context.history
|
112
|
+
#
|
113
|
+
def history
|
114
|
+
|
115
|
+
@context.history
|
116
|
+
end
|
117
|
+
|
118
|
+
# A shortcut for engine.context.logger
|
119
|
+
#
|
120
|
+
def logger
|
121
|
+
|
122
|
+
@context.logger
|
123
|
+
end
|
124
|
+
|
125
|
+
# Quick note : the implementation of launch is found in the module
|
126
|
+
# Ruote::ReceiverMixin that the engine includes.
|
127
|
+
#
|
128
|
+
# Some processes have to have one and only one instance of themselves
|
129
|
+
# running, these are called 'singles' ('singleton' is too object-oriented).
|
130
|
+
#
|
131
|
+
# When called, this method will check if an instance of the pdef is
|
132
|
+
# already running (it uses the process definition name attribute), if
|
133
|
+
# yes, it will return without having launched anything. If there is no
|
134
|
+
# such process running, it will launch it (and register it).
|
135
|
+
#
|
136
|
+
# Returns the wfid (workflow instance id) of the running single.
|
137
|
+
#
|
138
|
+
def launch_single(process_definition, fields={}, variables={}, root_stash=nil)
|
139
|
+
|
140
|
+
tree = @context.reader.read(process_definition)
|
141
|
+
name = tree[1]['name'] || (tree[1].find { |k, v| v.nil? } || []).first
|
142
|
+
|
143
|
+
raise ArgumentError.new(
|
144
|
+
'process definition is missing a name, cannot launch as single'
|
145
|
+
) unless name
|
146
|
+
|
147
|
+
singles = @context.storage.get('variables', 'singles') || {
|
148
|
+
'_id' => 'singles', 'type' => 'variables', 'h' => {}
|
149
|
+
}
|
150
|
+
wfid, timestamp = singles['h'][name]
|
151
|
+
|
152
|
+
return wfid if wfid && (ps(wfid) || Time.now.to_f - timestamp < 1.0)
|
153
|
+
# return wfid if 'singleton' process is already running
|
154
|
+
|
155
|
+
wfid = @context.wfidgen.generate
|
156
|
+
|
157
|
+
singles['h'][name] = [ wfid, Time.now.to_f ]
|
158
|
+
|
159
|
+
r = @context.storage.put(singles)
|
160
|
+
|
161
|
+
return launch_single(tree, fields, variables, root_stash) unless r.nil?
|
162
|
+
#
|
163
|
+
# the put failed, back to the start...
|
164
|
+
#
|
165
|
+
# all this to prevent races between multiple engines,
|
166
|
+
# multiple launch_single calls (from different Ruby runtimes)
|
167
|
+
|
168
|
+
# ... green for launch
|
169
|
+
|
170
|
+
@context.storage.put_msg(
|
171
|
+
'launch',
|
172
|
+
'wfid' => wfid,
|
173
|
+
'tree' => tree,
|
174
|
+
'workitem' => { 'fields' => fields },
|
175
|
+
'variables' => variables,
|
176
|
+
'stash' => root_stash)
|
177
|
+
|
178
|
+
wfid
|
179
|
+
end
|
180
|
+
|
181
|
+
# Given a workitem or a fei, will do a cancel_expression,
|
182
|
+
# else it's a wfid and it does a cancel_process.
|
183
|
+
#
|
184
|
+
# == A note about opts
|
185
|
+
#
|
186
|
+
# They will get passed as is in the underlying 'msg',
|
187
|
+
# it can be useful to flag the message for historical purposes as in
|
188
|
+
#
|
189
|
+
# dashboard.cancel(wfid, 'reason' => 'cleanup', 'user' => current_user)
|
190
|
+
#
|
191
|
+
def cancel(wi_or_fei_or_wfid, opts={})
|
192
|
+
|
193
|
+
do_misc('cancel', wi_or_fei_or_wfid, opts)
|
194
|
+
end
|
195
|
+
|
196
|
+
alias cancel_process cancel
|
197
|
+
alias cancel_expression cancel
|
198
|
+
|
199
|
+
# Given a workitem or a fei, will do a kill_expression,
|
200
|
+
# else it's a wfid and it does a kill_process.
|
201
|
+
#
|
202
|
+
# (also see notes about opts for #cancel)
|
203
|
+
#
|
204
|
+
def kill(wi_or_fei_or_wfid, opts={})
|
205
|
+
|
206
|
+
do_misc('cancel', wi_or_fei_or_wfid, opts.merge('flavour' => 'kill'))
|
207
|
+
end
|
208
|
+
|
209
|
+
alias kill_process kill
|
210
|
+
alias kill_expression kill
|
211
|
+
|
212
|
+
# Removes a process by removing all its schedules, expressions, errors,
|
213
|
+
# workitems and trackers.
|
214
|
+
#
|
215
|
+
# Warning: will not trigger any cancel behaviours at all, just removes
|
216
|
+
# the process.
|
217
|
+
#
|
218
|
+
def remove_process(wfid)
|
219
|
+
|
220
|
+
@context.storage.remove_process(wfid)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Given a wfid, will [attempt to] pause the corresponding process instance.
|
224
|
+
# Given an expression id (fei) will [attempt to] pause the expression
|
225
|
+
# and its children.
|
226
|
+
#
|
227
|
+
# The only known option for now is :breakpoint => true, which lets
|
228
|
+
# the engine only pause the targetted expression.
|
229
|
+
#
|
230
|
+
#
|
231
|
+
# == fei and :breakpoint => true
|
232
|
+
#
|
233
|
+
# By default, pausing an expression will pause that expression and
|
234
|
+
# all its children.
|
235
|
+
#
|
236
|
+
# engine.pause(fei, :breakpoint => true)
|
237
|
+
#
|
238
|
+
# will only flag as paused the given fei. When the children of that
|
239
|
+
# expression will reply to it, the execution for this branch of the
|
240
|
+
# process will stop, much like a break point.
|
241
|
+
#
|
242
|
+
def pause(wi_or_fei_or_wfid, opts={})
|
243
|
+
|
244
|
+
opts = Ruote.keys_to_s(opts)
|
245
|
+
|
246
|
+
raise ArgumentError.new(
|
247
|
+
':breakpoint option only valid when passing a workitem or a fei'
|
248
|
+
) if opts['breakpoint'] and wi_or_fei_or_wfid.is_a?(String)
|
249
|
+
|
250
|
+
do_misc('pause', wi_or_fei_or_wfid, opts)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Given a wfid will [attempt to] resume the process instance.
|
254
|
+
# Given an expression id (fei) will [attempt to] to resume the expression
|
255
|
+
# and its children.
|
256
|
+
#
|
257
|
+
# Note : this is supposed to be called on paused expressions / instances,
|
258
|
+
# this is NOT meant to be called to unstuck / unhang a process.
|
259
|
+
#
|
260
|
+
# == resume(wfid, :anyway => true)
|
261
|
+
#
|
262
|
+
# Resuming a process instance is equivalent to calling resume on its
|
263
|
+
# root expression. If the root is not paused itself, this will have no
|
264
|
+
# effect.
|
265
|
+
#
|
266
|
+
# dashboard.resume(wfid, :anyway => true)
|
267
|
+
#
|
268
|
+
# will make sure to call resume on each of the paused branch within the
|
269
|
+
# process instance (tree), effectively resuming the whole process.
|
270
|
+
#
|
271
|
+
def resume(wi_or_fei_or_wfid, opts={})
|
272
|
+
|
273
|
+
do_misc('resume', wi_or_fei_or_wfid, opts)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Replays at a given error (hopefully the cause of the error got fixed
|
277
|
+
# before replaying...)
|
278
|
+
#
|
279
|
+
def replay_at_error(err)
|
280
|
+
|
281
|
+
err = error(err) unless err.is_a?(Ruote::ProcessError)
|
282
|
+
|
283
|
+
msg = err.msg.dup
|
284
|
+
|
285
|
+
if tree = msg['tree']
|
286
|
+
#
|
287
|
+
# as soon as there is a tree, it means it's a re_apply
|
288
|
+
|
289
|
+
re_apply(
|
290
|
+
msg['fei'],
|
291
|
+
'tree' => tree,
|
292
|
+
'replay_at_error' => true,
|
293
|
+
'workitem' => msg['workitem'])
|
294
|
+
|
295
|
+
else
|
296
|
+
|
297
|
+
action = msg.delete('action')
|
298
|
+
|
299
|
+
msg['replay_at_error'] = true
|
300
|
+
# just an indication
|
301
|
+
|
302
|
+
@context.storage.delete(err.to_h) # remove error
|
303
|
+
@context.storage.put_msg(action, msg) # trigger replay
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Re-applies an expression (given via its FlowExpressionId).
|
308
|
+
#
|
309
|
+
# That will cancel the expression and, once the cancel operation is over
|
310
|
+
# (all the children have been cancelled), the expression will get
|
311
|
+
# re-applied.
|
312
|
+
#
|
313
|
+
# The fei parameter may be a hash, a Ruote::FlowExpressionId instance,
|
314
|
+
# a Ruote::Workitem instance or a sid string.
|
315
|
+
#
|
316
|
+
# == options
|
317
|
+
#
|
318
|
+
# :tree is used to completely change the tree of the expression at re_apply
|
319
|
+
#
|
320
|
+
# dashboard.re_apply(
|
321
|
+
# fei, :tree => [ 'participant', { 'ref' => 'bob' }, [] ])
|
322
|
+
#
|
323
|
+
# :fields is used to replace the fields of the workitem at re_apply
|
324
|
+
#
|
325
|
+
# dashboard.re_apply(
|
326
|
+
# fei, :fields => { 'customer' => 'bob' })
|
327
|
+
#
|
328
|
+
# :merge_in_fields is used to add / override fields
|
329
|
+
#
|
330
|
+
# dashboard.re_apply(
|
331
|
+
# fei, :merge_in_fields => { 'customer' => 'bob' })
|
332
|
+
#
|
333
|
+
def re_apply(fei, opts={})
|
334
|
+
|
335
|
+
@context.storage.put_msg(
|
336
|
+
'cancel',
|
337
|
+
'fei' => FlowExpressionId.extract_h(fei),
|
338
|
+
're_apply' => Ruote.keys_to_s(opts))
|
339
|
+
end
|
340
|
+
|
341
|
+
# This method re_apply all the leaves of a process instance. It's meant
|
342
|
+
# to be used against stalled workflows to give them back the spark of
|
343
|
+
# life.
|
344
|
+
#
|
345
|
+
# Stalled workflows can happen when msgs get lost. It also happens with
|
346
|
+
# some storage implementations where msgs are stored differently from
|
347
|
+
# expressions and co.
|
348
|
+
#
|
349
|
+
# By default, it doesn't re_apply leaves that are in error. If the
|
350
|
+
# 'errors_too' option is set to true, it will re_apply leaves in error
|
351
|
+
# as well. For example:
|
352
|
+
#
|
353
|
+
# $dashboard.respark(wfid, 'errors_too' => true)
|
354
|
+
#
|
355
|
+
def respark(wfid, opts={})
|
356
|
+
|
357
|
+
@context.storage.put_msg(
|
358
|
+
'respark',
|
359
|
+
'wfid' => wfid,
|
360
|
+
'respark' => Ruote.keys_to_s(opts))
|
361
|
+
end
|
362
|
+
|
363
|
+
# Returns a ProcessStatus instance describing the current status of
|
364
|
+
# a process instance.
|
365
|
+
#
|
366
|
+
def process(wfid)
|
367
|
+
|
368
|
+
ProcessStatus.fetch(@context, [ wfid ], {}).first
|
369
|
+
end
|
370
|
+
|
371
|
+
# Returns an array of ProcessStatus instances.
|
372
|
+
#
|
373
|
+
# WARNING : this is an expensive operation, but it understands :skip
|
374
|
+
# and :limit, so pagination is our friend.
|
375
|
+
#
|
376
|
+
# Please note, if you're interested only in processes that have errors,
|
377
|
+
# Engine#errors is a more efficient means.
|
378
|
+
#
|
379
|
+
# To simply list the wfids of the currently running, Engine#process_wfids
|
380
|
+
# is way cheaper to call.
|
381
|
+
#
|
382
|
+
def processes(opts={})
|
383
|
+
|
384
|
+
wfids = @context.storage.expression_wfids(opts)
|
385
|
+
|
386
|
+
opts[:count] ? wfids.size : ProcessStatus.fetch(@context, wfids, opts)
|
387
|
+
end
|
388
|
+
|
389
|
+
# Returns a list of processes or the process status of a given process
|
390
|
+
# instance.
|
391
|
+
#
|
392
|
+
def ps(wfid=nil)
|
393
|
+
|
394
|
+
wfid == nil ? processes : process(wfid)
|
395
|
+
end
|
396
|
+
|
397
|
+
# Returns an array of current errors (hashes)
|
398
|
+
#
|
399
|
+
# Can be called in two ways :
|
400
|
+
#
|
401
|
+
# dashboard.errors(wfid)
|
402
|
+
#
|
403
|
+
# and
|
404
|
+
#
|
405
|
+
# dashboard.errors(:skip => 100, :limit => 100)
|
406
|
+
#
|
407
|
+
def errors(wfid=nil)
|
408
|
+
|
409
|
+
wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
|
410
|
+
|
411
|
+
errs = wfid.nil? ?
|
412
|
+
@context.storage.get_many('errors', nil, options) :
|
413
|
+
@context.storage.get_many('errors', wfid)
|
414
|
+
|
415
|
+
return errs if options[:count]
|
416
|
+
|
417
|
+
errs.collect { |err| ProcessError.new(err) }
|
418
|
+
end
|
419
|
+
|
420
|
+
# Given a workitem or a fei (or a String version of a fei), returns
|
421
|
+
# the corresponding error (or nil if there is no other).
|
422
|
+
#
|
423
|
+
def error(wi_or_fei)
|
424
|
+
|
425
|
+
fei = Ruote.extract_fei(wi_or_fei)
|
426
|
+
err = @context.storage.get('errors', "err_#{fei.sid}")
|
427
|
+
|
428
|
+
err ? ProcessError.new(err) : nil
|
429
|
+
end
|
430
|
+
|
431
|
+
# Returns an array of schedules. Those schedules are open structs
|
432
|
+
# with various properties, like target, owner, at, put_at, ...
|
433
|
+
#
|
434
|
+
# Introduced mostly for ruote-kit.
|
435
|
+
#
|
436
|
+
# Can be called in two ways :
|
437
|
+
#
|
438
|
+
# dashboard.schedules(wfid)
|
439
|
+
#
|
440
|
+
# and
|
441
|
+
#
|
442
|
+
# dashboard.schedules(:skip => 100, :limit => 100)
|
443
|
+
#
|
444
|
+
def schedules(wfid=nil)
|
445
|
+
|
446
|
+
wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
|
447
|
+
|
448
|
+
scheds = wfid.nil? ?
|
449
|
+
@context.storage.get_many('schedules', nil, options) :
|
450
|
+
@context.storage.get_many('schedules', /!#{wfid}-\d+$/)
|
451
|
+
|
452
|
+
return scheds if options[:count]
|
453
|
+
|
454
|
+
scheds.collect { |s| Ruote.schedule_to_h(s) }.sort_by { |s| s['wfid'] }
|
455
|
+
end
|
456
|
+
|
457
|
+
# Returns a [sorted] list of wfids of the process instances currently
|
458
|
+
# running in the engine.
|
459
|
+
#
|
460
|
+
# This operation is substantially less costly than Engine#processes (though
|
461
|
+
# the 'how substantially' depends on the storage chosen).
|
462
|
+
#
|
463
|
+
def process_ids
|
464
|
+
|
465
|
+
@context.storage.expression_wfids({})
|
466
|
+
end
|
467
|
+
|
468
|
+
alias process_wfids process_ids
|
469
|
+
|
470
|
+
# Warning : expensive operation.
|
471
|
+
#
|
472
|
+
# Leftovers are workitems, errors and schedules belonging to process
|
473
|
+
# instances for which there are no more expressions left.
|
474
|
+
#
|
475
|
+
# Better delete them or investigate why they are left here.
|
476
|
+
#
|
477
|
+
# The result is a list of documents (hashes) as found in the storage. Each
|
478
|
+
# of them might represent a workitem, an error or a schedule.
|
479
|
+
#
|
480
|
+
# If you want to delete one of them you can do
|
481
|
+
#
|
482
|
+
# dashboard.storage.delete(doc)
|
483
|
+
#
|
484
|
+
def leftovers
|
485
|
+
|
486
|
+
wfids = @context.storage.expression_wfids({})
|
487
|
+
|
488
|
+
wis = @context.storage.get_many('workitems').compact
|
489
|
+
ers = @context.storage.get_many('errors').compact
|
490
|
+
scs = @context.storage.get_many('schedules').compact
|
491
|
+
# some slow storages need the compaction... [c]ouch...
|
492
|
+
|
493
|
+
(wis + ers + scs).reject { |doc| wfids.include?(doc['fei']['wfid']) }
|
494
|
+
end
|
495
|
+
|
496
|
+
# Shuts down the engine, mostly passes the shutdown message to the other
|
497
|
+
# services and hope they'll shut down properly.
|
498
|
+
#
|
499
|
+
def shutdown
|
500
|
+
|
501
|
+
@context.shutdown
|
502
|
+
end
|
503
|
+
|
504
|
+
# This method expects there to be a logger with a wait_for method in the
|
505
|
+
# context, else it will raise an exception.
|
506
|
+
#
|
507
|
+
# *WARNING*: #wait_for() is meant for environments where there is a unique
|
508
|
+
# worker and that worker is nested in this engine. In a multiple worker
|
509
|
+
# environment wait_for doesn't see events handled by 'other' workers.
|
510
|
+
#
|
511
|
+
# This method is only useful for test/quickstart/examples environments.
|
512
|
+
#
|
513
|
+
# dashboard.wait_for(:alpha)
|
514
|
+
# # will make the current thread block until a workitem is delivered
|
515
|
+
# # to the participant named 'alpha'
|
516
|
+
#
|
517
|
+
# engine.wait_for('123432123-9043')
|
518
|
+
# # will make the current thread block until the processed whose
|
519
|
+
# # wfid is given (String) terminates or produces an error.
|
520
|
+
#
|
521
|
+
# engine.wait_for(5)
|
522
|
+
# # will make the current thread block until 5 messages have been
|
523
|
+
# # processed on the workqueue...
|
524
|
+
#
|
525
|
+
# engine.wait_for(:empty)
|
526
|
+
# # will return as soon as the engine/storage is empty, ie as soon
|
527
|
+
# # as there are no more processes running in the engine (no more
|
528
|
+
# # expressions placed in the storage)
|
529
|
+
#
|
530
|
+
# engine.wait_for('terminated')
|
531
|
+
# # will return as soon as any process has a 'terminated' event.
|
532
|
+
#
|
533
|
+
# It's OK to wait for multiple wfids:
|
534
|
+
#
|
535
|
+
# engine.wait_for('20100612-bezerijozo', '20100612-yakisoba')
|
536
|
+
#
|
537
|
+
# If one needs to wait for something else than a wfid but needs to break
|
538
|
+
# in case of error:
|
539
|
+
#
|
540
|
+
# engine.wait_for(:alpha, :or_error)
|
541
|
+
#
|
542
|
+
#
|
543
|
+
# == ruote 2.3.0 and wait_for(event)
|
544
|
+
#
|
545
|
+
# Ruote 2.3.0 introduced the ability to wait for an event given its name.
|
546
|
+
# Here is a quick list of event names and a their description:
|
547
|
+
#
|
548
|
+
# * 'launch' - [sub]process launch
|
549
|
+
# * 'terminated' - process terminated
|
550
|
+
# * 'ceased' - orphan process terminated
|
551
|
+
# * 'apply' - expression application
|
552
|
+
# * 'reply' - expression reply
|
553
|
+
# * 'dispatched' - emitted workitem towards participant
|
554
|
+
# * 'receive' - received workitem from participant
|
555
|
+
# * 'pause' - pause order
|
556
|
+
# * 'resume' - pause order
|
557
|
+
# * 'dispatch_cancel' - emitting a cancel order to a participant
|
558
|
+
# * 'dispatch_pause' - emitting a pause order to a participant
|
559
|
+
# * 'dispatch_resume' - emitting a resume order to a participant
|
560
|
+
#
|
561
|
+
# Names that are past participles are for notification events, while
|
562
|
+
# plain verbs are for action events. Most of the time, a notitication
|
563
|
+
# is emitted has the result of an action event, workers don't take any
|
564
|
+
# action on them, but services that are listening to the ruote activity
|
565
|
+
# might want to do something about them.
|
566
|
+
#
|
567
|
+
#
|
568
|
+
# == ruote 2.3.0 and wait_for(hash)
|
569
|
+
#
|
570
|
+
# For more precise testing, wait_for accepts hashes, for example:
|
571
|
+
#
|
572
|
+
# r = dashboard.wait_for('action' => 'apply', 'exp_name' => 'wait')
|
573
|
+
#
|
574
|
+
# will block until a wait expression is applied.
|
575
|
+
#
|
576
|
+
# If you know ruote msgs, you can pinpoint at will:
|
577
|
+
#
|
578
|
+
# r = dashboard.wait_for(
|
579
|
+
# 'action' => 'apply',
|
580
|
+
# 'exp_name' => 'wait',
|
581
|
+
# 'fei.wfid' => wfid)
|
582
|
+
#
|
583
|
+
# == what wait_for returns
|
584
|
+
#
|
585
|
+
# #wait_for returns the intercepted event. It's useful when testing/
|
586
|
+
# spec'ing, as in:
|
587
|
+
#
|
588
|
+
# it 'completes successfully' do
|
589
|
+
#
|
590
|
+
# definition = Ruote.define :on_error => 'charly' do
|
591
|
+
# alpha
|
592
|
+
# bravo
|
593
|
+
# end
|
594
|
+
#
|
595
|
+
# wfid = @board.launch(definition)
|
596
|
+
#
|
597
|
+
# r = @board.wait_for(wfid)
|
598
|
+
# # wait until process terminates or hits an error
|
599
|
+
#
|
600
|
+
# r['workitem'].should_not == nil
|
601
|
+
# r['workitem']['fields']['alpha'].should == 'was here'
|
602
|
+
# r['workitem']['fields']['bravo'].should == 'was here'
|
603
|
+
# r['workitem']['fields']['charly'].should == nil
|
604
|
+
# end
|
605
|
+
#
|
606
|
+
# == :timeout option
|
607
|
+
#
|
608
|
+
# One can pass a timeout value in seconds for the #wait_for call, as in:
|
609
|
+
#
|
610
|
+
# dashboard.wait_for(wfid, :timeout => 5 * 60)
|
611
|
+
#
|
612
|
+
# The default timeout is 60 (seconds). A nil or negative timeout disables
|
613
|
+
# the timeout.
|
614
|
+
#
|
615
|
+
def wait_for(*items)
|
616
|
+
|
617
|
+
opts = (items.size > 1 && items.last.is_a?(Hash)) ? items.pop : {}
|
618
|
+
|
619
|
+
@context.logger.wait_for(items, opts)
|
620
|
+
end
|
621
|
+
|
622
|
+
# Joins the worker thread. If this engine has no nested worker, calling
|
623
|
+
# this method will simply return immediately.
|
624
|
+
#
|
625
|
+
def join
|
626
|
+
|
627
|
+
worker.join if worker
|
628
|
+
end
|
629
|
+
|
630
|
+
# Loads (and turns into a tree) the process definition at the given path.
|
631
|
+
#
|
632
|
+
def load_definition(path)
|
633
|
+
|
634
|
+
@context.reader.read(path)
|
635
|
+
end
|
636
|
+
|
637
|
+
# Registers a participant in the engine.
|
638
|
+
#
|
639
|
+
# Takes the form
|
640
|
+
#
|
641
|
+
# dashboard.register_participant name_or_regex, klass, opts={}
|
642
|
+
#
|
643
|
+
# With the form
|
644
|
+
#
|
645
|
+
# dashboard.register_participant name_or_regex do |workitem|
|
646
|
+
# # ...
|
647
|
+
# end
|
648
|
+
#
|
649
|
+
# A BlockParticipant is automatically created.
|
650
|
+
#
|
651
|
+
#
|
652
|
+
# == name or regex
|
653
|
+
#
|
654
|
+
# When registering participants, strings or regexes are accepted. Behind
|
655
|
+
# the scenes, a regex is kept.
|
656
|
+
#
|
657
|
+
# Passing a string like "alain" will get ruote to automatically turn it
|
658
|
+
# into the following regex : /^alain$/.
|
659
|
+
#
|
660
|
+
# For finer control over this, pass a regex directly
|
661
|
+
#
|
662
|
+
# dashboard.register_participant /^user-/, MyParticipant
|
663
|
+
# # will match all workitems whose participant name starts with "user-"
|
664
|
+
#
|
665
|
+
#
|
666
|
+
# == some examples
|
667
|
+
#
|
668
|
+
# dashboard.register_participant 'compute_sum' do |wi|
|
669
|
+
# wi.fields['sum'] = wi.fields['articles'].inject(0) do |s, (c, v)|
|
670
|
+
# s + c * v # sum + count * value
|
671
|
+
# end
|
672
|
+
# # a block participant implicitely replies to the engine immediately
|
673
|
+
# end
|
674
|
+
#
|
675
|
+
# class MyParticipant
|
676
|
+
# def initialize(opts)
|
677
|
+
# @name = opts['name']
|
678
|
+
# end
|
679
|
+
# def consume(workitem)
|
680
|
+
# workitem.fields['rocket_name'] = @name
|
681
|
+
# send_to_the_moon(workitem)
|
682
|
+
# end
|
683
|
+
# def cancel(fei, flavour)
|
684
|
+
# # do nothing
|
685
|
+
# end
|
686
|
+
# end
|
687
|
+
#
|
688
|
+
# dashboard.register_participant(
|
689
|
+
# /^moon-.+/, MyParticipant, 'name' => 'Saturn-V')
|
690
|
+
#
|
691
|
+
# # computing the total for a invoice being passed in the workitem.
|
692
|
+
# #
|
693
|
+
# class TotalParticipant
|
694
|
+
# include Ruote::LocalParticipant
|
695
|
+
#
|
696
|
+
# def consume(workitem)
|
697
|
+
# workitem['total'] = workitem.fields['items'].inject(0.0) { |t, item|
|
698
|
+
# t + item['count'] * PricingService.lookup(item['id'])
|
699
|
+
# }
|
700
|
+
# reply_to_engine(workitem)
|
701
|
+
# end
|
702
|
+
# end
|
703
|
+
# dashboard.register_participant 'total', TotalParticipant
|
704
|
+
#
|
705
|
+
# Remember that the options (the hash that follows the class name), must be
|
706
|
+
# serializable via JSON.
|
707
|
+
#
|
708
|
+
#
|
709
|
+
# == require_path and load_path
|
710
|
+
#
|
711
|
+
# It's OK to register a participant by passing its full classname as a
|
712
|
+
# String.
|
713
|
+
#
|
714
|
+
# dashboard.register_participant(
|
715
|
+
# 'auditor', 'AuditParticipant', 'require_path' => 'part/audit.rb')
|
716
|
+
# dashboard.register_participant(
|
717
|
+
# 'auto_decision', 'DecParticipant', 'load_path' => 'part/dec.rb')
|
718
|
+
#
|
719
|
+
# Note the option load_path / require_path that point to the ruby file
|
720
|
+
# containing the participant implementation. 'require' will load and eval
|
721
|
+
# the ruby code only once, 'load' each time.
|
722
|
+
#
|
723
|
+
#
|
724
|
+
# == :override => false
|
725
|
+
#
|
726
|
+
# By default, when registering a participant, if this results in a regex
|
727
|
+
# that is already used, the previously registered participant gets
|
728
|
+
# unregistered.
|
729
|
+
#
|
730
|
+
# dashboard.register_participant 'alpha', AaParticipant
|
731
|
+
# dashboard.register_participant 'alpha', BbParticipant, :override => false
|
732
|
+
#
|
733
|
+
# This can be useful when the #accept? method of participants are in use.
|
734
|
+
#
|
735
|
+
# Note that using the #register(&block) method, :override => false is
|
736
|
+
# automatically enforced.
|
737
|
+
#
|
738
|
+
# dashboard.register do
|
739
|
+
# alpha AaParticipant
|
740
|
+
# alpha BbParticipant
|
741
|
+
# end
|
742
|
+
#
|
743
|
+
#
|
744
|
+
# == :position / :pos => 'last' / 'first' / 'before' / 'after' / 'over'
|
745
|
+
#
|
746
|
+
# One can specify the position where the participant should be inserted
|
747
|
+
# in the participant list.
|
748
|
+
#
|
749
|
+
# dashboard.register_participant 'auditor', AuditParticipant, :pos => 'last'
|
750
|
+
#
|
751
|
+
# * last : it's the default, places the participant at the end of the list
|
752
|
+
# * first : top of the list
|
753
|
+
# * before : implies :override => false, places before the existing
|
754
|
+
# participant with the same regex
|
755
|
+
# * after : implies :override => false, places after the last existing
|
756
|
+
# participant with the same regex
|
757
|
+
# * over : overrides in the same position (while the regular, default
|
758
|
+
# overide removes and then places the new participant at the end of
|
759
|
+
# the list)
|
760
|
+
#
|
761
|
+
def register_participant(regex, participant=nil, opts={}, &block)
|
762
|
+
|
763
|
+
if participant.is_a?(Hash)
|
764
|
+
opts = participant
|
765
|
+
participant = nil
|
766
|
+
end
|
767
|
+
|
768
|
+
pa = @context.plist.register(regex, participant, opts, block)
|
769
|
+
|
770
|
+
@context.storage.put_msg(
|
771
|
+
'participant_registered',
|
772
|
+
'regex' => regex.is_a?(Regexp) ? regex.inspect : regex.to_s)
|
773
|
+
|
774
|
+
pa
|
775
|
+
end
|
776
|
+
|
777
|
+
# A shorter version of #register_participant
|
778
|
+
#
|
779
|
+
# dashboard.register 'alice', MailParticipant, :target => 'alice@example.com'
|
780
|
+
#
|
781
|
+
# or a block registering mechanism.
|
782
|
+
#
|
783
|
+
# dashboard.register do
|
784
|
+
# alpha 'Participants::Alpha', 'flavour' => 'vanilla'
|
785
|
+
# participant 'bravo', 'Participants::Bravo', :flavour => 'peach'
|
786
|
+
# catchall ParticipantCharlie, 'flavour' => 'coconut'
|
787
|
+
# end
|
788
|
+
#
|
789
|
+
# Originally implemented in ruote-kit by Torsten Schoenebaum.
|
790
|
+
#
|
791
|
+
# == registration in block and :clear
|
792
|
+
#
|
793
|
+
# By default, when registering multiple participants in block, ruote
|
794
|
+
# considers you're wiping the participant list and re-adding them all.
|
795
|
+
#
|
796
|
+
# You can prevent the clearing by stating :clear => false like in :
|
797
|
+
#
|
798
|
+
# dashboard.register :clear => false do
|
799
|
+
# alpha 'Participants::Alpha', 'flavour' => 'vanilla'
|
800
|
+
# participant 'bravo', 'Participants::Bravo', :flavour => 'peach'
|
801
|
+
# catchall ParticipantCharlie, 'flavour' => 'coconut'
|
802
|
+
# end
|
803
|
+
#
|
804
|
+
def register(*args, &block)
|
805
|
+
|
806
|
+
clear = args.first.is_a?(Hash) ? args.pop[:clear] : true
|
807
|
+
|
808
|
+
if args.size > 0
|
809
|
+
register_participant(*args, &block)
|
810
|
+
else
|
811
|
+
proxy = ParticipantRegistrationProxy.new(self, clear)
|
812
|
+
block.arity < 1 ? proxy.instance_eval(&block) : block.call(proxy)
|
813
|
+
proxy._flush
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
# Removes/unregisters a participant from the engine.
|
818
|
+
#
|
819
|
+
def unregister_participant(name_or_participant)
|
820
|
+
|
821
|
+
re = @context.plist.unregister(name_or_participant)
|
822
|
+
|
823
|
+
raise(ArgumentError.new('participant not found')) unless re
|
824
|
+
|
825
|
+
@context.storage.put_msg(
|
826
|
+
'participant_unregistered',
|
827
|
+
'regex' => re.to_s)
|
828
|
+
end
|
829
|
+
|
830
|
+
alias :unregister :unregister_participant
|
831
|
+
|
832
|
+
# Returns a list of Ruote::ParticipantEntry instances.
|
833
|
+
#
|
834
|
+
# dashboard.register_participant :alpha, MyParticipant, 'message' => 'hello'
|
835
|
+
#
|
836
|
+
# # interrogate participant list
|
837
|
+
# #
|
838
|
+
# list = dashboard.participant_list
|
839
|
+
# participant = list.first
|
840
|
+
# p participant.regex
|
841
|
+
# # => "^alpha$"
|
842
|
+
# p participant.classname
|
843
|
+
# # => "MyParticipant"
|
844
|
+
# p participant.options
|
845
|
+
# # => {"message"=>"hello"}
|
846
|
+
#
|
847
|
+
# # update participant list
|
848
|
+
# #
|
849
|
+
# participant.regex = '^alfred$'
|
850
|
+
# dashboard.participant_list = list
|
851
|
+
#
|
852
|
+
def participant_list
|
853
|
+
|
854
|
+
@context.plist.list
|
855
|
+
end
|
856
|
+
|
857
|
+
# Accepts a list of Ruote::ParticipantEntry instances or a list of
|
858
|
+
# [ regex, [ classname, opts ] ] arrays.
|
859
|
+
#
|
860
|
+
# See Engine#participant_list
|
861
|
+
#
|
862
|
+
# Some examples :
|
863
|
+
#
|
864
|
+
# dashboard.participant_list = [
|
865
|
+
# [ '^charly$', [ 'Ruote::StorageParticipant', {} ] ],
|
866
|
+
# [ '.+', [ 'MyDefaultParticipant', { 'default' => true } ]
|
867
|
+
# ]
|
868
|
+
#
|
869
|
+
# This method writes the participant list in one go, it might be easier to
|
870
|
+
# use than to register participant one by ones.
|
871
|
+
#
|
872
|
+
def participant_list=(pl)
|
873
|
+
|
874
|
+
@context.plist.list = pl
|
875
|
+
end
|
876
|
+
|
877
|
+
# A convenience method for
|
878
|
+
#
|
879
|
+
# sp = Ruote::StorageParticipant.new(dashboard)
|
880
|
+
#
|
881
|
+
# simply do
|
882
|
+
#
|
883
|
+
# sp = dashboard.storage_participant
|
884
|
+
#
|
885
|
+
def storage_participant
|
886
|
+
|
887
|
+
@storage_participant ||= Ruote::StorageParticipant.new(self)
|
888
|
+
end
|
889
|
+
|
890
|
+
# #worklist or #storage_participant
|
891
|
+
#
|
892
|
+
alias worklist storage_participant
|
893
|
+
|
894
|
+
# Returns an instance of the participant registered under the given name.
|
895
|
+
# Returns nil if there is no participant registered for that name.
|
896
|
+
#
|
897
|
+
def participant(name)
|
898
|
+
|
899
|
+
@context.plist.lookup(name.to_s, nil)
|
900
|
+
end
|
901
|
+
|
902
|
+
# Adds a service locally (will not get propagated to other workers).
|
903
|
+
#
|
904
|
+
# tracer = Tracer.new
|
905
|
+
# @dashboard.add_service('tracer', tracer)
|
906
|
+
#
|
907
|
+
# or
|
908
|
+
#
|
909
|
+
# @dashboard.add_service(
|
910
|
+
# 'tracer', 'ruote/exp/tracer', 'Ruote::Exp::Tracer')
|
911
|
+
#
|
912
|
+
# This method returns the service instance it just bound.
|
913
|
+
#
|
914
|
+
def add_service(name, path_or_instance, classname=nil, opts=nil)
|
915
|
+
|
916
|
+
@context.add_service(name, path_or_instance, classname, opts)
|
917
|
+
end
|
918
|
+
|
919
|
+
# Sets a configuration option. Examples:
|
920
|
+
#
|
921
|
+
# # allow remote workflow definitions (for subprocesses or when launching
|
922
|
+
# # processes)
|
923
|
+
# @dashboard.configure('remote_definition_allowed', true)
|
924
|
+
#
|
925
|
+
# # allow ruby_eval
|
926
|
+
# @dashboard.configure('ruby_eval_allowed', true)
|
927
|
+
#
|
928
|
+
def configure(config_key, value)
|
929
|
+
|
930
|
+
@context[config_key] = value
|
931
|
+
end
|
932
|
+
|
933
|
+
# Returns a configuration value.
|
934
|
+
#
|
935
|
+
# dashboard.configure('ruby_eval_allowed', true)
|
936
|
+
#
|
937
|
+
# p dashboard.configuration('ruby_eval_allowed')
|
938
|
+
# # => true
|
939
|
+
#
|
940
|
+
def configuration(config_key)
|
941
|
+
|
942
|
+
@context[config_key]
|
943
|
+
end
|
944
|
+
|
945
|
+
# Returns the hash containing info about each worker connected to the
|
946
|
+
# storage.
|
947
|
+
#
|
948
|
+
def worker_info
|
949
|
+
|
950
|
+
(@context.storage.get('variables', 'workers') || {})['workers']
|
951
|
+
end
|
952
|
+
|
953
|
+
# Returns the state the workers are supposed to be in right now.
|
954
|
+
# It's usually 'running', but it could be 'stopped' or 'paused'.
|
955
|
+
#
|
956
|
+
def worker_state
|
957
|
+
|
958
|
+
doc =
|
959
|
+
@context.storage.get('variables', 'worker') ||
|
960
|
+
{ 'type' => 'variables', '_id' => 'worker', 'state' => 'running' }
|
961
|
+
|
962
|
+
doc['state']
|
963
|
+
end
|
964
|
+
|
965
|
+
WORKER_STATES = %w[ running stopped paused ]
|
966
|
+
|
967
|
+
# Sets the [desired] worker state. The workers will check that target
|
968
|
+
# state at their next beat and switch to it.
|
969
|
+
#
|
970
|
+
# Setting the state to 'stopped' will force the workers to stop as soon
|
971
|
+
# as they notice the new state.
|
972
|
+
#
|
973
|
+
# Setting the state to 'paused' will force the workers to pause. They
|
974
|
+
# will not process msgs until the state is set back to 'running'.
|
975
|
+
#
|
976
|
+
# By default the [engine] option 'worker_state_enabled' is not set, so
|
977
|
+
# calling this method will result in a error, unless 'worker_state_enabled'
|
978
|
+
# was set to true when the storage was initialized.
|
979
|
+
#
|
980
|
+
def worker_state=(state)
|
981
|
+
|
982
|
+
raise RuntimeError.new(
|
983
|
+
"'worker_state_enabled' is not set, cannot change state"
|
984
|
+
) unless @context['worker_state_enabled']
|
985
|
+
|
986
|
+
state = state.to_s
|
987
|
+
|
988
|
+
raise ArgumentError.new(
|
989
|
+
"#{state.inspect} not in #{WORKER_STATES.inspect}"
|
990
|
+
) unless WORKER_STATES.include?(state)
|
991
|
+
|
992
|
+
doc =
|
993
|
+
@context.storage.get('variables', 'worker') ||
|
994
|
+
{ 'type' => 'variables', '_id' => 'worker', 'state' => 'running' }
|
995
|
+
|
996
|
+
doc['state'] = state
|
997
|
+
|
998
|
+
@context.storage.put(doc) && worker_state=(state)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
# Returns the process tree that is triggered in case of error.
|
1002
|
+
#
|
1003
|
+
# Note that this 'on_error' doesn't trigger if an on_error is defined
|
1004
|
+
# in the process itself.
|
1005
|
+
#
|
1006
|
+
# Returns nil if there is no 'on_error' set.
|
1007
|
+
#
|
1008
|
+
def on_error
|
1009
|
+
|
1010
|
+
@context.storage.get_trackers['trackers']['on_error']['msg']['tree']
|
1011
|
+
|
1012
|
+
rescue
|
1013
|
+
nil
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# Returns the process tree that is triggered in case of process termination.
|
1017
|
+
#
|
1018
|
+
# Note that a termination process doesn't raise a termination process when
|
1019
|
+
# it terminates itself.
|
1020
|
+
#
|
1021
|
+
# Returns nil if there is no 'on_terminate' set.
|
1022
|
+
#
|
1023
|
+
def on_terminate
|
1024
|
+
|
1025
|
+
@context.storage.get_trackers['trackers']['on_terminate']['msg']['tree']
|
1026
|
+
|
1027
|
+
rescue
|
1028
|
+
nil
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
# Sets a participant or subprocess to be triggered when an error occurs
|
1032
|
+
# in a process instance.
|
1033
|
+
#
|
1034
|
+
# dashboard.on_error = participant_name
|
1035
|
+
#
|
1036
|
+
# dashboard.on_error = subprocess_name
|
1037
|
+
#
|
1038
|
+
# dashboard.on_error = Ruote.process_definition do
|
1039
|
+
# alpha
|
1040
|
+
# end
|
1041
|
+
#
|
1042
|
+
# Note that this 'on_error' doesn't trigger if an on_error is defined
|
1043
|
+
# in the process itself.
|
1044
|
+
#
|
1045
|
+
def on_error=(target)
|
1046
|
+
|
1047
|
+
@context.tracker.add_tracker(
|
1048
|
+
nil, # do not track a specific wfid
|
1049
|
+
'error_intercepted', # react on 'error_intercepted' msgs
|
1050
|
+
'on_error', # the identifier
|
1051
|
+
nil, # no specific condition
|
1052
|
+
{ 'action' => 'launch',
|
1053
|
+
'wfid' => 'replace',
|
1054
|
+
'tree' => target.is_a?(String) ?
|
1055
|
+
[ 'define', {}, [ [ target, {}, [] ] ] ] : target,
|
1056
|
+
'workitem' => 'replace',
|
1057
|
+
'variables' => 'compile' })
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
# Sets a participant or a subprocess that is to be launched/called whenever
|
1061
|
+
# a regular process terminates.
|
1062
|
+
#
|
1063
|
+
# dashboard.on_terminate = participant_name
|
1064
|
+
#
|
1065
|
+
# dashboard.on_terminate = subprocess_name
|
1066
|
+
#
|
1067
|
+
# dashboard.on_terminate = Ruote.define do
|
1068
|
+
# alpha
|
1069
|
+
# bravo
|
1070
|
+
# end
|
1071
|
+
#
|
1072
|
+
# Note that a termination process doesn't raise a termination process when
|
1073
|
+
# it terminates itself.
|
1074
|
+
#
|
1075
|
+
# on_terminate processes are not triggered for on_error processes.
|
1076
|
+
# on_error processes are triggered for on_terminate processes as well.
|
1077
|
+
#
|
1078
|
+
def on_terminate=(target)
|
1079
|
+
|
1080
|
+
@context.tracker.add_tracker(
|
1081
|
+
nil, # do not track a specific wfid
|
1082
|
+
'terminated', # react on 'error_intercepted' msgs
|
1083
|
+
'on_terminate', # the identifier
|
1084
|
+
nil, # no specific condition
|
1085
|
+
{ 'action' => 'launch',
|
1086
|
+
'tree' => target.is_a?(String) ?
|
1087
|
+
[ 'define', {}, [ [ target, {}, [] ] ] ] : target,
|
1088
|
+
'workitem' => 'replace' })
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
# A debug helper :
|
1092
|
+
#
|
1093
|
+
# dashboard.noisy = true
|
1094
|
+
#
|
1095
|
+
# will let the dashboard (in fact the worker) pour all the details of the
|
1096
|
+
# executing process instances to STDOUT.
|
1097
|
+
#
|
1098
|
+
def noisy=(b)
|
1099
|
+
|
1100
|
+
@context.logger.noisy = b
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
protected
|
1104
|
+
|
1105
|
+
# Used by #pause and #resume.
|
1106
|
+
#
|
1107
|
+
def do_misc(action, wi_or_fei_or_wfid, opts)
|
1108
|
+
|
1109
|
+
opts = Ruote.keys_to_s(opts)
|
1110
|
+
|
1111
|
+
target = Ruote.extract_id(wi_or_fei_or_wfid)
|
1112
|
+
|
1113
|
+
if action == 'resume' && opts['anyway']
|
1114
|
+
#
|
1115
|
+
# determines the roots of the branches that are paused
|
1116
|
+
# sends the resume message to them.
|
1117
|
+
|
1118
|
+
exps = ps(target).expressions.select { |fexp| fexp.state == 'paused' }
|
1119
|
+
feis = exps.collect { |fexp| fexp.fei }
|
1120
|
+
|
1121
|
+
roots = exps.inject([]) { |a, fexp|
|
1122
|
+
a << fexp.fei.h unless feis.include?(fexp.parent_id)
|
1123
|
+
a
|
1124
|
+
}
|
1125
|
+
|
1126
|
+
roots.each { |fei| @context.storage.put_msg('resume', 'fei' => fei) }
|
1127
|
+
|
1128
|
+
elsif target.is_a?(String)
|
1129
|
+
#
|
1130
|
+
# action targets a process instance (a string wfid)
|
1131
|
+
|
1132
|
+
@context.storage.put_msg(
|
1133
|
+
"#{action}_process", opts.merge('wfid' => target))
|
1134
|
+
|
1135
|
+
else
|
1136
|
+
|
1137
|
+
@context.storage.put_msg(
|
1138
|
+
action, opts.merge('fei' => target))
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
#
|
1144
|
+
# A wrapper class giving easy access to engine variables.
|
1145
|
+
#
|
1146
|
+
# There is one instance of this class for an Engine instance. It is
|
1147
|
+
# returned when calling Engine#variables.
|
1148
|
+
#
|
1149
|
+
class EngineVariables
|
1150
|
+
|
1151
|
+
def initialize(storage)
|
1152
|
+
|
1153
|
+
@storage = storage
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
def [](k)
|
1157
|
+
|
1158
|
+
@storage.get_engine_variable(k)
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
def []=(k, v)
|
1162
|
+
|
1163
|
+
@storage.put_engine_variable(k, v)
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
#
|
1168
|
+
# Engine#register uses this proxy when it's passed a block.
|
1169
|
+
#
|
1170
|
+
# Originally written by Torsten Schoenebaum for ruote-kit.
|
1171
|
+
#
|
1172
|
+
class ParticipantRegistrationProxy < Ruote::BlankSlate
|
1173
|
+
|
1174
|
+
def initialize(dashboard, clear)
|
1175
|
+
|
1176
|
+
@dashboard = dashboard
|
1177
|
+
|
1178
|
+
@dashboard.context.plist.clear if clear
|
1179
|
+
|
1180
|
+
@list = clear ? [] : nil
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
def participant(name, klass=nil, options={}, &block)
|
1184
|
+
|
1185
|
+
if @list
|
1186
|
+
|
1187
|
+
@list <<
|
1188
|
+
@dashboard.context.plist.to_entry(name, klass, options, block)
|
1189
|
+
|
1190
|
+
else
|
1191
|
+
|
1192
|
+
@dashboard.register_participant(
|
1193
|
+
name, klass, options.merge!(:override => false), &block)
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
def catchall(*args)
|
1198
|
+
|
1199
|
+
klass = args.empty? ? Ruote::StorageParticipant : args.first
|
1200
|
+
options = args[1] || {}
|
1201
|
+
|
1202
|
+
participant('.+', klass, options)
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
alias catch_all catchall
|
1206
|
+
|
1207
|
+
# Maybe a bit audacious...
|
1208
|
+
#
|
1209
|
+
def method_missing(method_name, *args, &block)
|
1210
|
+
|
1211
|
+
participant(method_name, *args, &block)
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
def _flush
|
1215
|
+
|
1216
|
+
@dashboard.participant_list = @list if @list
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
# Refines a schedule as found in the ruote storage into something a bit
|
1221
|
+
# easier to present.
|
1222
|
+
#
|
1223
|
+
def self.schedule_to_h(sched)
|
1224
|
+
|
1225
|
+
h = sched.dup
|
1226
|
+
|
1227
|
+
class << h; attr_accessor :h; end
|
1228
|
+
h.h = sched
|
1229
|
+
#
|
1230
|
+
# for the sake of ProcessStatus#to_h
|
1231
|
+
|
1232
|
+
h.delete('_rev')
|
1233
|
+
h.delete('type')
|
1234
|
+
msg = h.delete('msg')
|
1235
|
+
owner = h.delete('owner')
|
1236
|
+
|
1237
|
+
h['wfid'] = owner['wfid']
|
1238
|
+
h['action'] = msg['action']
|
1239
|
+
h['type'] = msg['flavour']
|
1240
|
+
h['owner'] = Ruote::FlowExpressionId.new(owner)
|
1241
|
+
|
1242
|
+
h['target'] = Ruote::FlowExpressionId.new(msg['fei']) if msg['fei']
|
1243
|
+
|
1244
|
+
h
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|