openwferu 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/examples/mano_tracker.rb +165 -0
- data/examples/scheduler_cron_usage.rb +46 -0
- data/examples/scheduler_usage.rb +54 -0
- data/lib/openwfe/contextual.rb +7 -1
- data/lib/openwfe/engine/engine.rb +58 -15
- data/lib/openwfe/expool/expressionpool.rb +116 -14
- data/lib/openwfe/expool/expstorage.rb +12 -12
- data/lib/openwfe/expool/journalexpstorage.rb +1 -1
- data/lib/openwfe/expool/yamlexpstorage.rb +58 -22
- data/lib/openwfe/expressions/environment.rb +32 -2
- data/lib/openwfe/expressions/expressionmap.rb +17 -0
- data/lib/openwfe/expressions/fe_condition.rb +122 -0
- data/lib/openwfe/expressions/fe_cursor.rb +14 -5
- data/lib/openwfe/expressions/fe_participant.rb +55 -4
- data/lib/openwfe/expressions/fe_raw.rb +43 -12
- data/lib/openwfe/expressions/fe_subprocess.rb +10 -0
- data/lib/openwfe/expressions/fe_time.rb +117 -22
- data/lib/openwfe/expressions/fe_value.rb +27 -8
- data/lib/openwfe/expressions/flowexpression.rb +13 -6
- data/lib/openwfe/expressions/raw_prog.rb +13 -11
- data/lib/openwfe/expressions/timeout.rb +94 -0
- data/lib/openwfe/flowexpressionid.rb +17 -19
- data/lib/openwfe/logging.rb +35 -16
- data/lib/openwfe/participants/atomparticipants.rb +31 -7
- data/lib/openwfe/participants/enoparticipant.rb +43 -3
- data/lib/openwfe/participants/participant.rb +21 -1
- data/lib/openwfe/participants/participantmap.rb +4 -2
- data/lib/openwfe/participants/participants.rb +12 -17
- data/lib/openwfe/participants/soapparticipants.rb +15 -3
- data/lib/openwfe/rudefinitions.rb +3 -0
- data/lib/openwfe/service.rb +8 -0
- data/lib/openwfe/storage/yamlfilestorage.rb +85 -47
- data/lib/openwfe/{otime.rb → util/otime.rb} +0 -0
- data/lib/openwfe/util/scheduler.rb +415 -231
- data/lib/openwfe/util/schedulers.rb +11 -3
- data/lib/openwfe/util/stoppable.rb +69 -0
- data/lib/openwfe/utils.rb +14 -25
- data/lib/openwfe/workitem.rb +12 -6
- data/lib/openwfe/worklist/storeparticipant.rb +145 -0
- data/test/{atomtest.rb → atom_test.rb} +0 -0
- data/test/{crontest.rb → cron_test.rb} +7 -6
- data/test/cronline_test.rb +51 -0
- data/test/{dollartest.rb → dollar_test.rb} +0 -0
- data/test/{feitest.rb → fei_test.rb} +0 -0
- data/test/file_persistence_test.rb +15 -9
- data/test/flowtestbase.rb +11 -5
- data/test/ft_0.rb +8 -0
- data/test/ft_10_loop.rb +72 -10
- data/test/ft_11_ppd.rb +49 -0
- data/test/ft_17_condition.rb +83 -0
- data/test/ft_18_pname.rb +59 -0
- data/test/hparticipant_test.rb +96 -0
- data/test/{misctest.rb → misc_test.rb} +1 -1
- data/test/rake_qtest.rb +10 -4
- data/test/rake_test.rb +12 -1
- data/test/raw_prog_test.rb +1 -1
- data/test/restart_cron_test.rb +78 -0
- data/test/restart_test.rb +79 -0
- data/test/scheduler_test.rb +92 -0
- data/test/{timetest.rb → time_test.rb} +3 -38
- data/test/timeout_test.rb +73 -0
- metadata +26 -11
- data/lib/openwfe/worklist/worklists.rb +0 -175
@@ -71,6 +71,7 @@ module OpenWFE
|
|
71
71
|
|
72
72
|
#
|
73
73
|
# call the add method for each registered storage
|
74
|
+
#
|
74
75
|
def []= (fei, flowExpression)
|
75
76
|
@journal[fei] = flowExpression if @journal
|
76
77
|
@cache[fei] = flowExpression if @cache
|
@@ -79,10 +80,11 @@ module OpenWFE
|
|
79
80
|
|
80
81
|
#
|
81
82
|
# remove the expressionid from each registered storage
|
82
|
-
|
83
|
-
|
84
|
-
@
|
85
|
-
@
|
83
|
+
#
|
84
|
+
def delete (fei)
|
85
|
+
@journal.delete(fei) if @journal
|
86
|
+
@cache.delete(fei) if @cache
|
87
|
+
@persistence.delete(fei)
|
86
88
|
end
|
87
89
|
|
88
90
|
#
|
@@ -119,7 +121,8 @@ module OpenWFE
|
|
119
121
|
|
120
122
|
#
|
121
123
|
# interface method to remove an entry for the given expression_id
|
122
|
-
|
124
|
+
#
|
125
|
+
def delete (fei)
|
123
126
|
@cache.delete(fei)
|
124
127
|
end
|
125
128
|
|
@@ -151,16 +154,13 @@ module OpenWFE
|
|
151
154
|
include ServiceMixin
|
152
155
|
|
153
156
|
def initialize (service_name, application_context)
|
154
|
-
|
155
|
-
@application_context = application_context
|
157
|
+
service_init(service_name, application_context)
|
156
158
|
end
|
157
159
|
|
158
|
-
|
159
|
-
delete(fei)
|
160
|
-
end
|
160
|
+
alias :purge :clear
|
161
161
|
|
162
|
-
def
|
163
|
-
|
162
|
+
def each_of_kind (kind)
|
163
|
+
# TODO : implement with a filter
|
164
164
|
end
|
165
165
|
|
166
166
|
def to_s
|
@@ -40,8 +40,10 @@
|
|
40
40
|
# John Mettraux at openwfe.org
|
41
41
|
#
|
42
42
|
|
43
|
-
require 'find'
|
43
|
+
#require 'find'
|
44
44
|
|
45
|
+
require 'openwfe/utils'
|
46
|
+
require 'openwfe/rudefinitions'
|
45
47
|
require 'openwfe/storage/yamlfilestorage'
|
46
48
|
|
47
49
|
require 'openwfe/expressions/flowexpression'
|
@@ -84,6 +86,7 @@ module OpenWFE
|
|
84
86
|
# yaml expression storage
|
85
87
|
#
|
86
88
|
class YamlFileExpressionStorage < YamlFileStorage
|
89
|
+
include OwfeServiceLocator
|
87
90
|
|
88
91
|
def initialize (service_name, application_context)
|
89
92
|
path = if (@application_context)
|
@@ -93,35 +96,68 @@ module OpenWFE
|
|
93
96
|
end
|
94
97
|
super(service_name, application_context, path + "/expool")
|
95
98
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
99
|
+
|
100
|
+
#
|
101
|
+
# Iterates on each expression that is of the given kind.
|
102
|
+
# Used for example by the expression pool when rescheduling.
|
103
|
+
#
|
104
|
+
def each_of_kind (kind, &block)
|
105
|
+
|
106
|
+
return unless block
|
107
|
+
|
108
|
+
exp_names = get_expression_map.get_expression_names(kind)
|
109
|
+
#require 'pp'
|
110
|
+
#pp exp_names
|
111
|
+
|
112
|
+
each_object_path do |path|
|
113
|
+
|
114
|
+
#ldebug { "each_of_kind() path is #{path}" }
|
115
|
+
|
116
|
+
next unless matches(path, exp_names)
|
117
|
+
|
118
|
+
expression = load_object(path)
|
119
|
+
expression.application_context = @application_context
|
120
|
+
|
121
|
+
block.call expression
|
122
|
+
end
|
123
|
+
end
|
112
124
|
|
113
125
|
def to_s
|
114
126
|
s = "\n\n==== #{self.class} ===="
|
115
127
|
s << "\n"
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
s << "\n"
|
120
|
-
end
|
128
|
+
each_expression_path do |path|
|
129
|
+
s << path
|
130
|
+
s << "\n"
|
121
131
|
end
|
122
132
|
s << "==== . ====\n"
|
123
133
|
return s
|
124
134
|
end
|
135
|
+
|
136
|
+
protected
|
137
|
+
|
138
|
+
def compute_file_path (fei)
|
139
|
+
|
140
|
+
return @basepath + "/engine_environment.yaml" \
|
141
|
+
if fei.workflow_instance_id == "0"
|
142
|
+
|
143
|
+
wfid = fei.parent_workflow_instance_id
|
144
|
+
|
145
|
+
@basepath + "/" +
|
146
|
+
wfid[-1, 1] + "/" +
|
147
|
+
wfid[-2, 1] + "/" +
|
148
|
+
wfid + "/" +
|
149
|
+
fei.workflow_instance_id + "__" +
|
150
|
+
fei.expression_id + "_" +
|
151
|
+
fei.expression_name + ".yaml"
|
152
|
+
end
|
153
|
+
|
154
|
+
def matches (path, exp_names)
|
155
|
+
exp_names.each do |exp_name|
|
156
|
+
return true \
|
157
|
+
if OpenWFE::ends_with(path, "_#{exp_name}.yaml")
|
158
|
+
end
|
159
|
+
return false
|
160
|
+
end
|
125
161
|
|
126
162
|
end
|
127
163
|
end
|
@@ -40,6 +40,7 @@
|
|
40
40
|
#
|
41
41
|
|
42
42
|
require 'openwfe/utils'
|
43
|
+
require 'openwfe/util/scheduler'
|
43
44
|
require 'openwfe/expressions/flowexpression'
|
44
45
|
|
45
46
|
|
@@ -50,6 +51,7 @@ module OpenWFE
|
|
50
51
|
# It's an expression as it's storable in the expression pool.
|
51
52
|
#
|
52
53
|
class Environment < FlowExpression
|
54
|
+
include Schedulable
|
53
55
|
|
54
56
|
attr_accessor \
|
55
57
|
:variables
|
@@ -122,8 +124,11 @@ module OpenWFE
|
|
122
124
|
def unbind ()
|
123
125
|
|
124
126
|
@variables.each do |key, value|
|
125
|
-
|
126
|
-
|
127
|
+
if value.kind_of? FlowExpressionId
|
128
|
+
get_expression_pool().remove(value)
|
129
|
+
elsif value.kind_of? FlowExpression
|
130
|
+
value.cancel
|
131
|
+
end
|
127
132
|
end
|
128
133
|
end
|
129
134
|
|
@@ -134,6 +139,31 @@ module OpenWFE
|
|
134
139
|
return @fei == get_expression_pool().engine_environment_id
|
135
140
|
end
|
136
141
|
|
142
|
+
#
|
143
|
+
# Should never get used, only the reschedule() method is relevant
|
144
|
+
# for the Schedulable aspect of an environment expression.
|
145
|
+
#
|
146
|
+
def trigger (params)
|
147
|
+
raise "an environment should never get directly triggered"
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Will reschedule any 'Schedulable' variable found in this environment
|
152
|
+
# this method especially targets cron expressions that are stored
|
153
|
+
# as variables and need to be rescheduled upon engine restart.
|
154
|
+
#
|
155
|
+
def reschedule (scheduler)
|
156
|
+
|
157
|
+
@variables.each do |key, value|
|
158
|
+
ldebug { "reschedule() - item of class #{value.class}" }
|
159
|
+
next unless value.kind_of? Schedulable
|
160
|
+
value.application_context = @application_context
|
161
|
+
value.reschedule(scheduler)
|
162
|
+
end
|
163
|
+
|
164
|
+
store_itself()
|
165
|
+
end
|
166
|
+
|
137
167
|
protected
|
138
168
|
|
139
169
|
def get_root_environment ()
|
@@ -40,6 +40,7 @@
|
|
40
40
|
#
|
41
41
|
|
42
42
|
require 'openwfe/service'
|
43
|
+
require 'openwfe/expressions/environment'
|
43
44
|
require 'openwfe/expressions/fe_define'
|
44
45
|
require 'openwfe/expressions/fe_misc'
|
45
46
|
require 'openwfe/expressions/fe_value'
|
@@ -113,6 +114,10 @@ module OpenWFE
|
|
113
114
|
@map["f"] = FqvExpression
|
114
115
|
@map["q"] = FqvExpression
|
115
116
|
@map["v"] = FqvExpression
|
117
|
+
|
118
|
+
@map["environment"] = Environment
|
119
|
+
#
|
120
|
+
# only used by get_expression_names()
|
116
121
|
end
|
117
122
|
|
118
123
|
#
|
@@ -146,6 +151,18 @@ module OpenWFE
|
|
146
151
|
return @map.keys
|
147
152
|
end
|
148
153
|
|
154
|
+
#
|
155
|
+
# Returns an array of expression names whose class are assignable
|
156
|
+
# from the given expression_class.
|
157
|
+
#
|
158
|
+
def get_expression_names (expression_class)
|
159
|
+
names = []
|
160
|
+
@map.each do |name, exp_class|
|
161
|
+
names << name \
|
162
|
+
if exp_class.ancestors.include? expression_class
|
163
|
+
end
|
164
|
+
return names
|
165
|
+
end
|
149
166
|
end
|
150
167
|
|
151
168
|
end
|
@@ -0,0 +1,122 @@
|
|
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: definitions.rb 2725 2006-06-02 13:26:32Z jmettraux $
|
34
|
+
#
|
35
|
+
|
36
|
+
#
|
37
|
+
# "made in Japan"
|
38
|
+
#
|
39
|
+
# John Mettraux at openwfe.org
|
40
|
+
#
|
41
|
+
|
42
|
+
#require 'openwfe/workitem'
|
43
|
+
#require 'openwfe/flowexpressionid'
|
44
|
+
|
45
|
+
|
46
|
+
module OpenWFE
|
47
|
+
|
48
|
+
#
|
49
|
+
# A ConditionMixin is a mixin for flow expressions like 'if' and 'break' for
|
50
|
+
# example.
|
51
|
+
# It allows for shorter notations like
|
52
|
+
#
|
53
|
+
# <if test="${f:approved} == true"/>
|
54
|
+
#
|
55
|
+
# or
|
56
|
+
#
|
57
|
+
# _loop do
|
58
|
+
# participant :graphical_designer
|
59
|
+
# participant :business_analyst
|
60
|
+
# _break :if => "${f:approved}"
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
module ConditionMixin
|
64
|
+
|
65
|
+
def eval_condition (attname, workitem)
|
66
|
+
|
67
|
+
conditional = lookup_attribute(attname, workitem)
|
68
|
+
rconditional = lookup_attribute("r"+attname.to_s, workitem)
|
69
|
+
|
70
|
+
if rconditional and not conditional
|
71
|
+
return instance_eval(rconditional)
|
72
|
+
end
|
73
|
+
|
74
|
+
return nil unless conditional
|
75
|
+
|
76
|
+
#ldebug { "eval_condition() 0 for '#{conditional}'" }
|
77
|
+
|
78
|
+
conditional = from_xml(conditional)
|
79
|
+
|
80
|
+
#ldebug { "eval_condition() 1 for '#{conditional}'" }
|
81
|
+
|
82
|
+
conditional = do_quote(conditional)
|
83
|
+
|
84
|
+
ldebug { "eval_condition() 2 for '#{conditional}'" }
|
85
|
+
|
86
|
+
return true if conditional == '"true"'
|
87
|
+
return false if conditional == '"false"'
|
88
|
+
|
89
|
+
return instance_eval(conditional)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def from_xml (string)
|
95
|
+
|
96
|
+
s = string
|
97
|
+
s.gsub!(">", ">")
|
98
|
+
s.gsub!("<", "<")
|
99
|
+
s
|
100
|
+
end
|
101
|
+
|
102
|
+
def do_quote (string)
|
103
|
+
|
104
|
+
i = string.index("==")
|
105
|
+
i = string.index("!=") unless i
|
106
|
+
i = string.index("<") unless i
|
107
|
+
i = string.index(">") unless i
|
108
|
+
|
109
|
+
return '"' + string + '"' unless i
|
110
|
+
|
111
|
+
'"' +
|
112
|
+
string[0..i-1].strip +
|
113
|
+
'" ' +
|
114
|
+
string[i..i+2] +
|
115
|
+
' "' +
|
116
|
+
string[i+2..-1].strip +
|
117
|
+
'"'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
@@ -40,6 +40,7 @@
|
|
40
40
|
#
|
41
41
|
|
42
42
|
require 'openwfe/expressions/flowexpression'
|
43
|
+
require 'openwfe/expressions/fe_condition'
|
43
44
|
|
44
45
|
|
45
46
|
#
|
@@ -216,17 +217,25 @@ module OpenWFE
|
|
216
217
|
# 'skip', 'continue', and the like
|
217
218
|
#
|
218
219
|
class CursorCommandExpression < FlowExpression
|
220
|
+
include ConditionMixin
|
219
221
|
|
220
222
|
def apply (workitem)
|
221
223
|
|
222
|
-
|
224
|
+
conditional = eval_condition(:if, workitem)
|
225
|
+
#
|
226
|
+
# for example : <break if="${approved} == true"/>
|
227
|
+
|
228
|
+
if conditional == nil or conditional
|
223
229
|
|
224
|
-
|
225
|
-
step = Integer(step)
|
230
|
+
command = @fei.expression_name
|
226
231
|
|
227
|
-
|
232
|
+
step = lookup_attribute(A_STEP, workitem, "1")
|
233
|
+
step = Integer(step)
|
228
234
|
|
229
|
-
|
235
|
+
command = "#{command} #{step}" if step != 1
|
236
|
+
|
237
|
+
workitem.attributes[F_COMMAND] = command
|
238
|
+
end
|
230
239
|
|
231
240
|
reply_to_parent(workitem)
|
232
241
|
end
|
@@ -39,7 +39,9 @@
|
|
39
39
|
# John Mettraux at openwfe.org
|
40
40
|
#
|
41
41
|
|
42
|
+
require 'openwfe/utils'
|
42
43
|
require 'openwfe/rudefinitions'
|
44
|
+
require 'openwfe/expressions/timeout'
|
43
45
|
require 'openwfe/expressions/fe_utils'
|
44
46
|
|
45
47
|
|
@@ -56,6 +58,7 @@ module OpenWFE
|
|
56
58
|
# tied to the engine.
|
57
59
|
#
|
58
60
|
class ParticipantExpression < FlowExpression
|
61
|
+
include TimeoutMixin
|
59
62
|
|
60
63
|
attr_accessor \
|
61
64
|
:participant_name,
|
@@ -66,22 +69,70 @@ module OpenWFE
|
|
66
69
|
|
67
70
|
def apply (workitem)
|
68
71
|
|
69
|
-
@applied_workitem = workitem
|
70
|
-
#puts "apply() \n#{@applied_workitem}"
|
71
|
-
#ldebug { "apply() \n#{@applied_workitem}" }
|
72
|
+
@applied_workitem = workitem.dup
|
72
73
|
|
73
74
|
@participant_name = OpenWFE::lookup_ref(self, workitem)
|
74
75
|
|
75
76
|
@participant_name = OpenWFE::fetch_text_content(self, workitem) \
|
76
77
|
unless @participant_name
|
77
78
|
|
79
|
+
determine_timeout()
|
80
|
+
|
78
81
|
store_itself()
|
79
82
|
|
80
|
-
get_participant_map.dispatch(participant_name, workitem)
|
83
|
+
get_participant_map.dispatch(@participant_name, workitem)
|
81
84
|
end
|
82
85
|
|
83
86
|
#def reply (workitem)
|
84
87
|
#end
|
88
|
+
|
89
|
+
#
|
90
|
+
# The cancel() method of a ParticipantExpression is particular : it
|
91
|
+
# will emit a CancelItem instance towards the participant itself
|
92
|
+
# to notify it of the cancellation.
|
93
|
+
#
|
94
|
+
def cancel
|
95
|
+
|
96
|
+
wi = super()
|
97
|
+
|
98
|
+
if @scheduler_job_id
|
99
|
+
get_scheduler.unschedule(@scheduler_job_id)
|
100
|
+
end
|
101
|
+
|
102
|
+
return wi unless @applied_workitem
|
103
|
+
|
104
|
+
#
|
105
|
+
# have to cancel the workitem on the participant side
|
106
|
+
|
107
|
+
cancelitem = OpenWFE::CancelItem.new(@applied_workitem)
|
108
|
+
get_participant_map.dispatch(@participant_name, cancelitem)
|
109
|
+
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Upon timeout, the ParticipantExpression will cancel itself and
|
115
|
+
# the flow will resume.
|
116
|
+
#
|
117
|
+
def trigger (scheduler)
|
118
|
+
|
119
|
+
ldebug { "trigger() timeout requested for #{@fei.to_debug_s}" }
|
120
|
+
|
121
|
+
begin
|
122
|
+
|
123
|
+
@scheduler_job_id = nil
|
124
|
+
#
|
125
|
+
# so that cancel won't unschedule without need
|
126
|
+
|
127
|
+
cancel()
|
128
|
+
reply_to_parent(@applied_workitem)
|
129
|
+
rescue
|
130
|
+
lerror do
|
131
|
+
"trigger() problem while timing out\n"+
|
132
|
+
OpenWFE::exception_to_s($!)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
85
136
|
end
|
86
137
|
|
87
138
|
end
|