openwferu 0.9.11 → 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README.txt +0 -3
  2. data/examples/engine_template.rb +10 -4
  3. data/lib/openwfe/engine/engine.rb +336 -63
  4. data/lib/openwfe/engine/file_persisted_engine.rb +9 -1
  5. data/lib/openwfe/expool/errorjournal.rb +379 -0
  6. data/lib/openwfe/expool/expressionpool.rb +84 -55
  7. data/lib/openwfe/expool/expstorage.rb +54 -18
  8. data/lib/openwfe/expool/journal.rb +31 -22
  9. data/lib/openwfe/expool/yamlexpstorage.rb +57 -16
  10. data/lib/openwfe/expressions/fe_sequence.rb +1 -1
  11. data/lib/openwfe/expressions/flowexpression.rb +13 -1
  12. data/lib/openwfe/expressions/{fe_raw.rb → raw.rb} +5 -2
  13. data/lib/openwfe/expressions/raw_prog.rb +1 -1
  14. data/lib/openwfe/expressions/raw_xml.rb +1 -1
  15. data/lib/openwfe/expressions/time.rb +2 -0
  16. data/lib/openwfe/flowexpressionid.rb +21 -6
  17. data/lib/openwfe/omixins.rb +37 -14
  18. data/lib/openwfe/participants/atomparticipants.rb +6 -5
  19. data/lib/openwfe/participants/participantmap.rb +2 -0
  20. data/lib/openwfe/rest/controlclient.rb +1 -0
  21. data/lib/openwfe/rudefinitions.rb +5 -1
  22. data/lib/openwfe/storage/yamlfilestorage.rb +7 -3
  23. data/lib/openwfe/util/otime.rb +1 -1
  24. data/lib/openwfe/util/safe.rb +14 -0
  25. data/lib/openwfe/util/scheduler.rb +8 -5
  26. data/lib/openwfe/util/workqueue.rb +9 -2
  27. data/lib/openwfe/utils.rb +18 -0
  28. data/lib/openwfe/version.rb +1 -1
  29. data/test/atom_test.rb +27 -26
  30. data/test/fei_test.rb +3 -3
  31. data/test/file_persistence_test.rb +19 -2
  32. data/test/ft_0c_testname.rb +6 -3
  33. data/test/ft_26_load.rb +14 -7
  34. data/test/ft_26b_load.rb +87 -0
  35. data/test/ft_26c_load.rb +71 -0
  36. data/test/ft_27_getflowpos.rb +22 -3
  37. data/test/ft_34_cancelwfid.rb +3 -2
  38. data/test/ft_42_environments.rb +3 -1
  39. data/test/ft_58_ejournal.rb +119 -0
  40. data/test/ft_59_ps.rb +118 -0
  41. data/test/ft_60_ecancel.rb +87 -0
  42. data/test/ft_tests.rb +4 -0
  43. data/test/hparticipant_test.rb +1 -1
  44. data/test/orest_test.rb +27 -4
  45. data/test/param_test.rb +5 -1
  46. data/test/participant_test.rb +39 -0
  47. data/test/rake_qtest.rb +3 -5
  48. data/test/rest_test.rb +2 -2
  49. data/test/scheduler_test.rb +10 -15
  50. metadata +10 -3
@@ -0,0 +1,379 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2007, John Mettraux, OpenWFE.org
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # . Redistributions of source code must retain the above copyright notice, this
10
+ # list of conditions and the following disclaimer.
11
+ #
12
+ # . Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ #
16
+ # . Neither the name of the "OpenWFE" nor the names of its contributors may be
17
+ # used to endorse or promote products derived from this software without
18
+ # specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+ #++
32
+ #
33
+ # $Id$
34
+ #
35
+
36
+ #
37
+ # "made in Japan"
38
+ #
39
+ # John Mettraux at openwfe.org
40
+ #
41
+
42
+ require 'find'
43
+ require 'fileutils'
44
+
45
+ require 'openwfe/service'
46
+ require 'openwfe/omixins'
47
+
48
+
49
+ module OpenWFE
50
+
51
+ #
52
+ # Encapsulating process error information.
53
+ #
54
+ # Instances of this class may be used to replay_at_error
55
+ #
56
+ class ProcessError
57
+
58
+ #
59
+ # When did the error occur.
60
+ #
61
+ attr_reader :date
62
+
63
+ #
64
+ # The FlowExpressionId instance uniquely pointing at the expression
65
+ # which 'failed'.
66
+ #
67
+ attr_reader :fei
68
+
69
+ #
70
+ # Generally something like :apply or :reply
71
+ #
72
+ attr_reader :message
73
+
74
+ #
75
+ # The workitem accompanying the message (apply(workitem) /
76
+ # reply (workitem)).
77
+ #
78
+ attr_reader :workitem
79
+
80
+ #
81
+ # The String stack trace of the error.
82
+ #
83
+ attr_reader :stacktrace
84
+
85
+ def initialize (*args)
86
+
87
+ @date = Time.new
88
+ @fei, @message, @workitem, @stacktrace = args
89
+ end
90
+
91
+ #
92
+ # Returns the parent workflow instance id (process id) of this
93
+ # ProcessError instance.
94
+ #
95
+ def wfid
96
+ @fei.parent_wfid
97
+ end
98
+
99
+ alias :parent_wfid :wfid
100
+
101
+ #
102
+ # Produces a human readable version of the information in the
103
+ # ProcessError instance.
104
+ #
105
+ def to_s
106
+ s = ""
107
+ s << "-- #{self.class.name} --\n"
108
+ s << " date : #{@date}\n"
109
+ s << " fei : #{@fei}\n"
110
+ s << " message : #{@message}\n"
111
+ s << " workitem : ...\n"
112
+ s << " stacktrace : #{@stacktrace[0, 80]}\n"
113
+ s
114
+ end
115
+ end
116
+
117
+ #
118
+ # This is a base class for all error journal, don't instantiate,
119
+ # work rather with InMemoryErrorJournal (only for testing envs though),
120
+ # or YamlErrorJournal.
121
+ #
122
+ class ErrorJournal < Service
123
+ include OwfeServiceLocator
124
+ include FeiMixin
125
+
126
+ def initialize (service_name, application_context)
127
+
128
+ super
129
+
130
+ get_expression_pool.add_observer(:error) do |event, *args|
131
+ #
132
+ # logs each error occuring in the expression pool
133
+
134
+ record_error(ProcessError.new(*args))
135
+ end
136
+ end
137
+
138
+ #
139
+ # Returns true if the given wfid (or fei) (process instance id)
140
+ # has had errors.
141
+ #
142
+ def has_errors? (wfid)
143
+
144
+ get_error_log(wfid).size > 0
145
+ end
146
+
147
+ #
148
+ # Replays the given process instance (wfid or fei) at its last
149
+ # recorded error.
150
+ #
151
+ # There is an optional 'offset' parameter. Its default value is '0'.
152
+ # Which means that the replay will occur at the last error.
153
+ #
154
+ # ejournal.replay_at_last_error('20070630-hiwakuzara', 1)
155
+ #
156
+ # Will replay a given process instance at its 1 to last error.
157
+ #
158
+ def replay_at_last_error (wfid, offset=0)
159
+
160
+ wfid = to_wfid(wfid)
161
+
162
+ log = get_error_log(wfid)
163
+
164
+ index = (-1 - offset)
165
+
166
+ error = log[index]
167
+
168
+ raise "no error for process '#{wfid}' at offset #{offset}" \
169
+ unless error
170
+
171
+ replay_at_error error
172
+ end
173
+
174
+ #
175
+ # Replays at a specific error (fetched with read_error_log()).
176
+ #
177
+ def replay_at_error (error)
178
+
179
+ get_expression_pool.queue_work(
180
+ error.message,
181
+ error.fei,
182
+ error.workitem)
183
+ end
184
+
185
+ #
186
+ # A utility method : given a list of errors, will make sure that for
187
+ # each flow expression only one expression (the most recent) will get
188
+ # listed.
189
+ # Returns a list of errors, from the oldest to the most recent.
190
+ #
191
+ # Could be useful when considering a process where multiple replay
192
+ # attempts failed.
193
+ #
194
+ def ErrorJournal.reduce_error_list (errors)
195
+
196
+ h = {}
197
+
198
+ errors.each do |e|
199
+ h[e.fei] = e
200
+ #
201
+ # last errors do override previous errors for the
202
+ # same fei
203
+ end
204
+
205
+ h.values.sort do |error_a, error_b|
206
+ error_a.date <=> error_b.date
207
+ end
208
+ end
209
+ end
210
+
211
+ #
212
+ # Stores all the errors in a hash... For testing purposes only, like
213
+ # the InMemoryExpressionStorage.
214
+ #
215
+ class InMemoryErrorJournal < ErrorJournal
216
+
217
+ def initialize (service_name, application_context)
218
+
219
+ super
220
+
221
+ @per_processes = {}
222
+ end
223
+
224
+ #
225
+ # Returns a list (older first) of the errors for a process
226
+ # instance identified by its fei or wfid.
227
+ #
228
+ # Will return an empty list if there a no errors for the process
229
+ # instances.
230
+ #
231
+ def get_error_log (wfid)
232
+
233
+ wfid = to_wfid(wfid)
234
+ @per_processes[wfid] or []
235
+ end
236
+
237
+ #
238
+ # Removes the error log for a process instance.
239
+ #
240
+ def remove_error_log (wfid)
241
+
242
+ wfid = to_wfid(wfid)
243
+ @per_processes.delete(wfid)
244
+ end
245
+
246
+ #
247
+ # Reads all the error logs currently stored.
248
+ # Returns a hash wfid --> error list.
249
+ #
250
+ def get_error_logs
251
+
252
+ @per_processes
253
+ end
254
+
255
+ protected
256
+
257
+ def record_error (error)
258
+
259
+ (@per_processes[error.wfid] ||= []) << error
260
+ # not that unreadable after all...
261
+ end
262
+ end
263
+
264
+ #
265
+ # A Journal that only keep track of error in process execution.
266
+ #
267
+ class YamlErrorJournal < ErrorJournal
268
+
269
+ attr_reader :workdir
270
+
271
+ def initialize (service_name, application_context)
272
+
273
+ require 'openwfe/storage/yamlextras'
274
+
275
+ super
276
+
277
+ @workdir = OpenWFE::get_work_directory + "/ejournal"
278
+
279
+ FileUtils.makedirs(@workdir) unless File.exist?(@workdir)
280
+ end
281
+
282
+ #
283
+ # Returns a list (older first) of the errors for a process
284
+ # instance identified by its fei or wfid.
285
+ #
286
+ # Will return an empty list if there a no errors for the process
287
+ # instances.
288
+ #
289
+ def get_error_log (wfid)
290
+
291
+ path = get_path(wfid)
292
+
293
+ return [] unless File.exist?(path)
294
+
295
+ read_error_log_from path
296
+ end
297
+
298
+ #
299
+ # Copies the error log of a process instance to a give path (and
300
+ # filename).
301
+ #
302
+ # Could be useful when one has to perform replay operations and wants
303
+ # to keep a copy of the original error[s].
304
+ #
305
+ def copy_error_log_to (wfid, path)
306
+
307
+ original_path = get_path wfid
308
+ FileUtils.copy_file original_path, path
309
+ end
310
+
311
+ #
312
+ # Reads an error log from a specific file (possibly as copied over
313
+ # via copy_error_log_to()).
314
+ #
315
+ def read_error_log_from (path)
316
+
317
+ raise "no error log file at #{path}" unless File.exist?(path)
318
+
319
+ File.open(path) do |f|
320
+ s = YAML.load_stream f
321
+ s.documents
322
+ end
323
+ end
324
+
325
+ #
326
+ # Removes the error log of a specific process instance.
327
+ # Could be a good idea after a succesful replay operation.
328
+ #
329
+ # 'wfid' may be either a workflow instance id (String) either
330
+ # a FlowExpressionId instance.
331
+ #
332
+ def remove_error_log (wfid)
333
+
334
+ File.delete(get_path(wfid))
335
+ end
336
+
337
+ #
338
+ # Reads all the error logs currently stored.
339
+ # Returns a hash wfid --> error list.
340
+ #
341
+ def get_error_logs
342
+
343
+ result = {}
344
+
345
+ Find.find(@workdir) do |path|
346
+ next unless path.endswith(".ejournal")
347
+ wfid = path[0..-9]
348
+ log = read_error_log wfid
349
+ result[wfid] = log
350
+ end
351
+
352
+ result
353
+ end
354
+
355
+ protected
356
+
357
+ #
358
+ # logs the error as a yaml string in an error log file
359
+ # (there is one error log file per workflow instance).
360
+ #
361
+ def record_error (error)
362
+
363
+ path = get_path error.fei
364
+
365
+ File.open(path, "a+") do |f|
366
+ f.puts error.to_yaml
367
+ end
368
+ end
369
+
370
+ #
371
+ # Returns the path to the error log file of a specific process
372
+ # instance.
373
+ #
374
+ def get_path (fei_or_wfid)
375
+
376
+ @workdir + "/" + to_wfid(fei_or_wfid) + ".ejournal"
377
+ end
378
+ end
379
+ end
@@ -64,44 +64,6 @@ include OpenWFE
64
64
 
65
65
  module OpenWFE
66
66
 
67
- #
68
- # a small help class for storing monitors provided on demand
69
- # to expressions that need them
70
- #
71
- class MonitorProvider
72
- include MonitorMixin, Logging
73
-
74
- MAX_MONITORS = 10000
75
-
76
- def initialize (application_context=nil)
77
- super()
78
- @application_context = application_context
79
- @monitors = LruHash.new(MAX_MONITORS)
80
- end
81
-
82
- def [] (key)
83
- synchronize do
84
- #ldebug { "[] caller :\n" + OpenWFE::caller_to_s(8) }
85
- mon = @monitors[key]
86
- if not mon
87
- #ldebug { "[] creating new Monitor for #{key}" }
88
- mon = Monitor.new
89
- @monitors[key] = mon
90
- else
91
- #ldebug { "[] already had Monitor for #{key}" }
92
- end
93
- return mon
94
- end
95
- end
96
-
97
- def delete (key)
98
- synchronize do
99
- #ldebug { "delete() removing Monitor for #{key}" }
100
- @monitors.delete(key)
101
- end
102
- end
103
- end
104
-
105
67
  GONE = "gone"
106
68
 
107
69
  #
@@ -383,18 +345,40 @@ module OpenWFE
383
345
  inflowitem
384
346
  end
385
347
 
348
+ #
349
+ # Cancels the given expression and makes sure to resume the flow
350
+ # if the expression or one of its children were active.
351
+ #
352
+ # If the cancelled branch was not active, this method will take
353
+ # care of removing the cancelled expression from the parent
354
+ # expression.
355
+ #
356
+ def cancel_expression (exp)
357
+
358
+ exp = fetch_expression(exp)
359
+
360
+ wi = cancel(exp)
361
+
362
+ if wi
363
+ reply_to_parent(exp, wi, false)
364
+ else
365
+ parent_exp = fetch_expression(exp.parent_id)
366
+ parent_exp.remove_child(exp.fei) if parent_exp
367
+ end
368
+ end
369
+
386
370
  #
387
371
  # Given any expression of a process, cancels the complete process
388
372
  # instance.
389
373
  #
390
- def cancel_flow (exp_or_wfid)
374
+ def cancel_process (exp_or_wfid)
391
375
 
392
- #ldebug { "cancel_flow() from #{exp_or_wfid}" }
376
+ ldebug { "cancel_process() from #{exp_or_wfid}" }
393
377
 
394
378
  root = fetch_root(exp_or_wfid)
395
379
  cancel(root)
396
380
  end
397
- alias :cancel_process :cancel_flow
381
+ alias :cancel_flow :cancel_process
398
382
 
399
383
  #
400
384
  # Forgets the given expression (makes sure to substitute its
@@ -515,9 +499,9 @@ module OpenWFE
515
499
 
516
500
  #ldebug { "fetch() exp is of kind #{exp.class}" }
517
501
 
518
- if exp.kind_of? FlowExpression
502
+ if exp.kind_of?(FlowExpression)
519
503
  fei = exp.fei
520
- elsif not exp.kind_of? FlowExpressionId
504
+ elsif not exp.kind_of?(FlowExpressionId)
521
505
  raise \
522
506
  "Cannot fetch expression with key : "+
523
507
  "'#{fei}' (#{fei.class})"
@@ -525,7 +509,7 @@ module OpenWFE
525
509
 
526
510
  #ldebug { "fetch() for #{fei.to_debug_s}" }
527
511
 
528
- return get_expression_storage()[fei], fei
512
+ [ get_expression_storage()[fei], fei ]
529
513
  end
530
514
  end
531
515
 
@@ -536,6 +520,7 @@ module OpenWFE
536
520
  # has to be reloaded.
537
521
  #
538
522
  def fetch_expression (exp)
523
+
539
524
  exp, _fei = fetch(exp)
540
525
  exp
541
526
  end
@@ -551,6 +536,8 @@ module OpenWFE
551
536
 
552
537
  exp = fetch_expression(exp_or_wfid)
553
538
 
539
+ raise "did not find root for expression #{exp_or_wfid}" unless exp
540
+
554
541
  return exp unless exp.parent_id
555
542
 
556
543
  fetch_root(fetch_expression(exp.parent_id))
@@ -634,7 +621,9 @@ module OpenWFE
634
621
  #
635
622
  def engine_environment_id ()
636
623
  synchronize do
624
+
637
625
  return @eei if @eei
626
+
638
627
  @eei = FlowExpressionId.new
639
628
  @eei.owfe_version = OPENWFERU_VERSION
640
629
  @eei.engine_id = get_engine.service_name
@@ -645,7 +634,7 @@ module OpenWFE
645
634
  @eei.workflow_instance_id = '0'
646
635
  @eei.expression_name = EN_ENVIRONMENT
647
636
  @eei.expression_id = '0'
648
- return @eei
637
+ @eei
649
638
  end
650
639
  end
651
640
 
@@ -653,11 +642,13 @@ module OpenWFE
653
642
  # Returns the list of applied expressions belonging to a given
654
643
  # workflow instance.
655
644
  #
656
- def get_flow_position (wfid)
645
+ def get_process_stack (wfid)
657
646
 
658
647
  raise "please provide a non-nil workflow instance id" \
659
648
  unless wfid
660
649
 
650
+ wfid = to_wfid wfid
651
+
661
652
  result = []
662
653
 
663
654
  get_expression_storage.real_each do |fei, fexp|
@@ -666,21 +657,21 @@ module OpenWFE
666
657
  next if fexp.kind_of?(RawExpression)
667
658
  next unless fexp.apply_time
668
659
 
669
- pi = fei.parent_wfid
670
-
671
- next if pi != wfid
660
+ next if fei.parent_wfid != wfid
672
661
 
673
662
  result << fexp
674
663
  end
675
664
 
676
665
  ldebug do
677
- "get_flow_position() " +
666
+ "process_stack() " +
678
667
  "found #{result.size} exps for flow #{wfid}"
679
668
  end
680
669
 
681
670
  result
682
671
  end
683
672
 
673
+ alias :get_flow_stack :get_process_stack
674
+
684
675
  #
685
676
  # Lists all workflows (processes) currently in the expool (in
686
677
  # the engine).
@@ -693,31 +684,32 @@ module OpenWFE
693
684
  # "wfid_prefix" allows your to query for specific workflow instance
694
685
  # id prefixes.
695
686
  #
696
- def list_workflows (consider_subprocesses=false, wfid_prefix=nil)
687
+ def list_processes (consider_subprocesses=false, wfid_prefix=nil)
697
688
 
698
689
  result = []
699
690
 
700
- get_expression_storage.real_each do |fei, fexp|
691
+ # collect() would look better
692
+
693
+ get_expression_storage.real_each(wfid_prefix) do |fei, fexp|
701
694
 
702
695
  next unless fexp.is_a? DefineExpression
703
696
 
704
697
  next if not consider_subprocesses and fei.wfid.index(".")
705
698
 
706
- next unless fei.wfid.match("^#{wfid_prefix}") if wfid_prefix
699
+ #next unless fei.wfid.match("^#{wfid_prefix}") if wfid_prefix
707
700
 
708
701
  result << fexp
709
702
  end
710
703
 
711
704
  result
712
705
  end
713
- alias :list_processes :list_workflows
714
706
 
715
707
  #
716
708
  # Returns the first expression found with the given wfid.
717
709
  #
718
710
  def fetch_expression_with_wfid (wfid)
719
711
 
720
- list_workflows(false, wfid)[0]
712
+ list_processes(false, wfid)[0]
721
713
  end
722
714
 
723
715
  protected
@@ -955,7 +947,44 @@ module OpenWFE
955
947
  procdef.raw_expression_class.new(
956
948
  fei, nil, nil, @application_context, procdef)
957
949
  end
950
+ end
951
+
952
+ #
953
+ # a small help class for storing monitors provided on demand
954
+ # to expressions that need them
955
+ #
956
+ class MonitorProvider
957
+ include MonitorMixin, Logging
958
958
 
959
+ MAX_MONITORS = 10000
960
+
961
+ def initialize (application_context=nil)
962
+ super()
963
+ @application_context = application_context
964
+ @monitors = LruHash.new(MAX_MONITORS)
965
+ end
966
+
967
+ def [] (key)
968
+ synchronize do
969
+ #ldebug { "[] caller :\n" + OpenWFE::caller_to_s(8) }
970
+ mon = @monitors[key]
971
+ if not mon
972
+ #ldebug { "[] creating new Monitor for #{key}" }
973
+ mon = Monitor.new
974
+ @monitors[key] = mon
975
+ else
976
+ #ldebug { "[] already had Monitor for #{key}" }
977
+ end
978
+ return mon
979
+ end
980
+ end
981
+
982
+ def delete (key)
983
+ synchronize do
984
+ #ldebug { "delete() removing Monitor for #{key}" }
985
+ @monitors.delete(key)
986
+ end
987
+ end
959
988
  end
960
989
 
961
990
  end