rubywmq 2.0.2 → 2.1.0
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.
- checksums.yaml +4 -4
- data/Gemfile +10 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +1 -1
- data/README.md +29 -47
- data/Rakefile +12 -76
- data/examples/each_a.rb +2 -3
- data/examples/each_b.rb +4 -5
- data/examples/each_header.rb +5 -6
- data/examples/files_to_q.rb +7 -8
- data/examples/get_a.rb +3 -5
- data/examples/get_client.rb +9 -10
- data/examples/put1_a.rb +2 -3
- data/examples/put1_b.rb +4 -7
- data/examples/put1_c.rb +6 -6
- data/examples/put_a.rb +0 -2
- data/examples/put_b.rb +5 -7
- data/examples/put_dlh.rb +11 -12
- data/examples/put_dynamic_q.rb +7 -7
- data/examples/put_group_a.rb +3 -4
- data/examples/put_group_b.rb +7 -7
- data/examples/put_rfh.rb +13 -11
- data/examples/put_rfh2_a.rb +9 -10
- data/examples/put_rfh2_b.rb +9 -9
- data/examples/put_xmit_q.rb +63 -8
- data/examples/q_to_files.rb +6 -7
- data/examples/request.rb +20 -18
- data/examples/server.rb +19 -16
- data/ext/extconf.rb +2 -1
- data/ext/extconf_client.rb +3 -3
- data/ext/generate/generate_const.rb +30 -23
- data/ext/generate/generate_reason.rb +70 -72
- data/ext/generate/generate_structs.rb +20 -19
- data/ext/generate/wmq_structs.erb +67 -67
- data/ext/wmq.c +0 -16
- data/ext/wmq.h +0 -16
- data/ext/wmq_message.c +8 -24
- data/ext/wmq_mq_load.c +5 -17
- data/ext/wmq_queue.c +73 -90
- data/ext/wmq_queue_manager.c +115 -108
- data/lib/wmq/message.rb +36 -34
- data/lib/wmq/queue_manager.rb +22 -19
- data/lib/wmq/version.rb +1 -1
- data/rubywmq.gemspec +38 -0
- data/test/queue_manager_test.rb +334 -0
- data/test/test_helper.rb +14 -0
- metadata +16 -26
- data/tests/test.rb +0 -318
data/lib/wmq/queue_manager.rb
CHANGED
@@ -6,11 +6,11 @@ module WMQ
|
|
6
6
|
# Example
|
7
7
|
# require 'wmq/wmq'
|
8
8
|
# require 'wmq/wmq_const_admin'
|
9
|
-
# WMQ::QueueManager.connect(:
|
9
|
+
# WMQ::QueueManager.connect(q_mgr_name: 'REID', connection_name: 'localhost(1414)') do |qmgr|
|
10
10
|
# qmgr.mqsc('dis ql(*)').each {|item| p item }
|
11
11
|
# end
|
12
12
|
def mqsc(mqsc_text)
|
13
|
-
|
13
|
+
execute(command: :escape, escape_type: WMQ::MQET_MQSC, escape_text: mqsc_text).collect { |item| item[:escape_text] }
|
14
14
|
end
|
15
15
|
|
16
16
|
# Put a reply message back to the sender
|
@@ -44,19 +44,20 @@ module WMQ
|
|
44
44
|
if parms[:request_message].descriptor[:msg_type] == WMQ::MQMT_REQUEST
|
45
45
|
request = parms.delete(:request_message)
|
46
46
|
|
47
|
-
reply
|
47
|
+
reply = parms[:message] ||= Message.new(data: parms[:data])
|
48
48
|
reply.descriptor[:msg_type] = WMQ::MQMT_REPLY
|
49
49
|
reply.descriptor[:expiry] = request.descriptor[:expiry]
|
50
50
|
reply.descriptor[:priority] = request.descriptor[:priority]
|
51
51
|
reply.descriptor[:persistence]= request.descriptor[:persistence]
|
52
|
-
reply.descriptor[:format]
|
52
|
+
reply.descriptor[:format] = request.descriptor[:format]
|
53
53
|
|
54
54
|
# Set Correlation Id based on report options supplied
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
55
|
+
reply.descriptor[:correl_id] =
|
56
|
+
if request.descriptor[:report] & WMQ::MQRO_PASS_CORREL_ID != 0
|
57
|
+
request.descriptor[:correl_id]
|
58
|
+
else
|
59
|
+
request.descriptor[:msg_id]
|
60
|
+
end
|
60
61
|
|
61
62
|
# Set Message Id based on report options supplied
|
62
63
|
if request.descriptor[:report] & WMQ::MQRO_PASS_MSG_ID != 0
|
@@ -83,24 +84,26 @@ module WMQ
|
|
83
84
|
# are retained.
|
84
85
|
#
|
85
86
|
def put_to_dead_letter_q(parms)
|
86
|
-
message = parms[:message] ||= Message.new(:
|
87
|
-
dlh
|
88
|
-
:
|
89
|
-
:
|
90
|
-
:
|
91
|
-
:
|
87
|
+
message = parms[:message] ||= Message.new(data: parms[:data])
|
88
|
+
dlh = {
|
89
|
+
header_type: :dead_letter_header,
|
90
|
+
reason: parms.delete(:reason),
|
91
|
+
dest_q_name: parms.delete(:q_name),
|
92
|
+
dest_q_mgr_name: name
|
93
|
+
}
|
92
94
|
|
93
95
|
message.headers.unshift(dlh)
|
94
|
-
parms[:q_name]='SYSTEM.DEAD.LETTER.QUEUE'
|
95
|
-
|
96
|
+
parms[:q_name] = 'SYSTEM.DEAD.LETTER.QUEUE' #TODO Should be obtained from QMGR config
|
97
|
+
|
98
|
+
put(parms)
|
96
99
|
end
|
97
100
|
|
98
101
|
# Expose Commands directly as Queue Manager methods
|
99
102
|
def method_missing(name, *args)
|
100
103
|
if args.size == 1
|
101
|
-
|
104
|
+
execute({ command: name }.merge(args[0]))
|
102
105
|
elsif args.size == 0
|
103
|
-
|
106
|
+
execute(command: name)
|
104
107
|
else
|
105
108
|
raise("Invalid arguments supplied to QueueManager#:#{name}, args:#{args}")
|
106
109
|
end
|
data/lib/wmq/version.rb
CHANGED
data/rubywmq.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require 'wmq/version'
|
5
|
+
require 'rake/file_list'
|
6
|
+
|
7
|
+
# Describe your gem and declare its dependencies:
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
# Exclude locally compiled files since they are platform specific
|
10
|
+
excludes = [
|
11
|
+
/lib.wmq.constants\.rb/,
|
12
|
+
/lib.wmq.constants_admin\.rb/,
|
13
|
+
/ext.wmq_structs\.c/,
|
14
|
+
/ext.wmq_reason\.c/,
|
15
|
+
/ext.Makefile/,
|
16
|
+
/ext.*\.o/,
|
17
|
+
/ext.wmq\.so/,
|
18
|
+
/\.gem$/,
|
19
|
+
/\.log$/,
|
20
|
+
/nbproject/
|
21
|
+
]
|
22
|
+
s.name = 'rubywmq'
|
23
|
+
s.version = WMQ::VERSION
|
24
|
+
s.platform = Gem::Platform::RUBY
|
25
|
+
s.authors = ['Reid Morrison']
|
26
|
+
s.email = ['reidmo@gmail.com']
|
27
|
+
s.homepage = 'https://github.com/reidmorrison/rubywmq'
|
28
|
+
s.summary = 'Native Ruby interface into WebSphere MQ'
|
29
|
+
s.description = 'RubyWMQ is a high performance native Ruby interface into WebSphere MQ.'
|
30
|
+
s.files = Rake::FileList['./**/*'].exclude(*excludes).map { |f| f.sub(/^\.\//, '') } + ['.document']
|
31
|
+
s.test_files = Dir['test/**/*']
|
32
|
+
s.license = 'Apache License V2.0'
|
33
|
+
s.has_rdoc = true
|
34
|
+
s.extensions << 'ext/extconf.rb'
|
35
|
+
s.requirements << 'WebSphere MQ v5.3, v6 or v7 Client or Server with Development Kit'
|
36
|
+
s.required_ruby_version = '>= 1.9'
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,334 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
# Unit Test for RocketJob::Job
|
4
|
+
class WMQTest < Minitest::Test
|
5
|
+
context WMQ do
|
6
|
+
setup do
|
7
|
+
@queue_manager = WMQ::QueueManager.new(q_mgr_name: 'TEST') #, connection_name: 'localhost(1414)')
|
8
|
+
@queue_manager.connect
|
9
|
+
|
10
|
+
# Create Queue and clear any messages from the queue
|
11
|
+
@in_queue = WMQ::Queue.new(
|
12
|
+
queue_manager: @queue_manager,
|
13
|
+
mode: :input,
|
14
|
+
dynamic_q_name: 'UNIT.TEST.*',
|
15
|
+
q_name: 'SYSTEM.DEFAULT.MODEL.QUEUE',
|
16
|
+
fail_if_exists: false
|
17
|
+
)
|
18
|
+
@in_queue.open
|
19
|
+
@in_queue.each { |message|}
|
20
|
+
|
21
|
+
# Open same queue for Output. Queue should be created by now
|
22
|
+
@out_queue = WMQ::Queue.new(
|
23
|
+
queue_manager: @queue_manager,
|
24
|
+
mode: :output,
|
25
|
+
q_name: @in_queue.name
|
26
|
+
)
|
27
|
+
@out_queue.open
|
28
|
+
end
|
29
|
+
|
30
|
+
teardown do
|
31
|
+
@out_queue.close if @out_queue
|
32
|
+
@in_queue.close if @in_queue
|
33
|
+
@queue_manager.disconnect if @queue_manager
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'exceptions' do
|
37
|
+
should 'raise exceptions' do
|
38
|
+
assert_raises(TypeError) do
|
39
|
+
WMQ::QueueManager.new(1)
|
40
|
+
end
|
41
|
+
WMQ::QueueManager.new(exception_on_error: nil)
|
42
|
+
assert_raises(TypeError) do
|
43
|
+
WMQ::QueueManager.new(exception_on_error: 1)
|
44
|
+
end
|
45
|
+
assert_raises(TypeError) do
|
46
|
+
WMQ::QueueManager.new(q_mgr_name: 2).connect
|
47
|
+
end
|
48
|
+
assert_raises(WMQ::WMQException) do
|
49
|
+
WMQ::QueueManager.new(q_mgr_name: 'bad').connect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'queue manager' do
|
55
|
+
should 'exist' do
|
56
|
+
assert_equal Object, WMQ::QueueManager.superclass
|
57
|
+
assert_equal WMQ::QueueManager, @queue_manager.class
|
58
|
+
end
|
59
|
+
|
60
|
+
should 'open_queue' do
|
61
|
+
@queue_manager.open_queue(
|
62
|
+
mode: :output,
|
63
|
+
q_name: {
|
64
|
+
q_name: @in_queue.name,
|
65
|
+
q_mgr_name: @queue_manager.name
|
66
|
+
}
|
67
|
+
) do |test_queue|
|
68
|
+
assert_equal(true, test_queue.put(data: 'Hello World'))
|
69
|
+
message = WMQ::Message.new
|
70
|
+
assert_equal(true, @in_queue.get(message: message))
|
71
|
+
assert_equal('Hello World', message.data)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'execute' do
|
76
|
+
array = @queue_manager.inquire_q(
|
77
|
+
q_name: @in_queue.name,
|
78
|
+
q_type: WMQ::MQQT_LOCAL,
|
79
|
+
current_q_depth: nil
|
80
|
+
)
|
81
|
+
assert_equal 1, array.size
|
82
|
+
assert_equal @in_queue.name, array[0][:q_name]
|
83
|
+
|
84
|
+
assert_equal true, @queue_manager.inquire_process(process_name: '*').size > 0
|
85
|
+
assert_raises WMQ::WMQException do
|
86
|
+
@queue_manager.inquire_q(q_name: 'BADQUEUENAME*')
|
87
|
+
end
|
88
|
+
assert_equal 1, @queue_manager.ping_q_mgr.size
|
89
|
+
end
|
90
|
+
|
91
|
+
should 'mqsc' do
|
92
|
+
array = @queue_manager.mqsc("dis ql(#{@in_queue.name})")
|
93
|
+
assert_equal 1, array.size
|
94
|
+
assert_equal true, array[0].include?("QUEUE(#{@in_queue.name})")
|
95
|
+
end
|
96
|
+
|
97
|
+
should 'put1' do
|
98
|
+
data = 'Some Test Data'
|
99
|
+
assert_equal true, @queue_manager.put(q_name: @in_queue.name, data: data)
|
100
|
+
|
101
|
+
message = WMQ::Message.new
|
102
|
+
assert_equal true, @in_queue.get(message: message)
|
103
|
+
assert_equal data, message.data
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'Queue' do
|
109
|
+
should 'send and receive message' do
|
110
|
+
assert_equal @out_queue.put(data: 'Hello World'), true
|
111
|
+
message = WMQ::Message.new
|
112
|
+
assert_equal @in_queue.get(message: message), true
|
113
|
+
assert_equal message.data, 'Hello World'
|
114
|
+
end
|
115
|
+
|
116
|
+
should 'group messages' do
|
117
|
+
# Clear out queue of any messages
|
118
|
+
@in_queue.each { |message|}
|
119
|
+
|
120
|
+
msg = WMQ::Message.new
|
121
|
+
msg.data = 'First'
|
122
|
+
msg.descriptor[:msg_flags] = WMQ::MQMF_MSG_IN_GROUP
|
123
|
+
assert_equal(@out_queue.put(message: msg, options: WMQ::MQPMO_LOGICAL_ORDER), true)
|
124
|
+
|
125
|
+
msg.data = 'Second'
|
126
|
+
assert_equal(@out_queue.put(message: msg, options: WMQ::MQPMO_LOGICAL_ORDER), true)
|
127
|
+
|
128
|
+
msg.data = 'Last'
|
129
|
+
msg.descriptor[:msg_flags] = WMQ::MQMF_LAST_MSG_IN_GROUP
|
130
|
+
assert_equal(@out_queue.put(message: msg, options: WMQ::MQPMO_LOGICAL_ORDER), true)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'Message' do
|
135
|
+
should 'dynamic_buffer' do
|
136
|
+
test_sizes = [0, 1, 100, 101, 102, 500, 65534, 65535, 65536, 65537, 1*1024*1024, 4*1024*1024]
|
137
|
+
test_sizes.each do |size|
|
138
|
+
str = '0123456789ABCDEF' * (size/16) + '0123456789ABCDEF'[0, size%16]
|
139
|
+
assert_equal str.length, size
|
140
|
+
assert_equal @out_queue.put(data: str), true
|
141
|
+
end
|
142
|
+
|
143
|
+
# First test the browse mechanism
|
144
|
+
counter = 0
|
145
|
+
@queue_manager.open_queue(mode: :browse, q_name: @in_queue.name) do |browse_queue|
|
146
|
+
browse_queue.each do |message|
|
147
|
+
size = test_sizes[counter]
|
148
|
+
assert_equal(size, message.data.length)
|
149
|
+
str = '0123456789ABCDEF' * (size/16) + '0123456789ABCDEF'[0, size%16]
|
150
|
+
assert_equal(str, message.data)
|
151
|
+
counter = counter + 1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
assert_equal(test_sizes.size, counter)
|
155
|
+
|
156
|
+
# Now retrieve the messages destructively
|
157
|
+
message = WMQ::Message.new
|
158
|
+
test_sizes.each do |size|
|
159
|
+
assert_equal(true, @in_queue.get(message: message, match: WMQ::MQMO_NONE))
|
160
|
+
assert_equal(size, message.data.length)
|
161
|
+
str = '0123456789ABCDEF' * (size/16) + '0123456789ABCDEF'[0, size%16]
|
162
|
+
assert_equal(str, message.data)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
should 'dead letter header' do
|
167
|
+
# TODO: Something has changed with DLH since MQ V6
|
168
|
+
skip
|
169
|
+
dlh = {
|
170
|
+
header_type: :dead_letter_header,
|
171
|
+
reason: WMQ::MQRC_UNKNOWN_REMOTE_Q_MGR,
|
172
|
+
dest_q_name: 'ORIGINAL_QUEUE_NAME',
|
173
|
+
dest_q_mgr_name: 'BAD_Q_MGR',
|
174
|
+
put_appl_name: 'TestApp.exe',
|
175
|
+
}
|
176
|
+
|
177
|
+
verify_header(dlh, WMQ::MQFMT_DEAD_LETTER_HEADER)
|
178
|
+
end
|
179
|
+
|
180
|
+
should 'cics' do
|
181
|
+
cics = {
|
182
|
+
header_type: :cics,
|
183
|
+
reason: WMQ::MQRC_UNKNOWN_REMOTE_Q_MGR,
|
184
|
+
facility: 'TOKEN123',
|
185
|
+
reply_to_format: WMQ::MQFMT_STRING
|
186
|
+
}
|
187
|
+
verify_header(cics, WMQ::MQFMT_NONE)
|
188
|
+
end
|
189
|
+
|
190
|
+
should 'ims' do
|
191
|
+
ims = {
|
192
|
+
header_type: :ims,
|
193
|
+
l_term_override: 'LTERM',
|
194
|
+
reply_to_format: WMQ::MQFMT_STRING
|
195
|
+
}
|
196
|
+
verify_header(ims, WMQ::MQFMT_STRING)
|
197
|
+
end
|
198
|
+
|
199
|
+
should 'transmission_header' do
|
200
|
+
xqh = {
|
201
|
+
header_type: :xmit_q_header,
|
202
|
+
remote_q_name: 'SOME_REMOTE_QUEUE',
|
203
|
+
remote_q_mgr_name: 'SOME_REMOTE_Q_MGR',
|
204
|
+
msg_type: WMQ::MQMT_REQUEST,
|
205
|
+
msg_id: 'my message Id'
|
206
|
+
}
|
207
|
+
verify_header(xqh, WMQ::MQFMT_STRING)
|
208
|
+
end
|
209
|
+
|
210
|
+
should 'rf_header' do
|
211
|
+
rfh = {
|
212
|
+
header_type: :rf_header,
|
213
|
+
name_value: {
|
214
|
+
' name s' => ' v a "l" u e 1 ',
|
215
|
+
'n a m e 2 ' => 'v a l u e 2',
|
216
|
+
'' => ['"', '""', '"""', '""""', ''],
|
217
|
+
'name3' => ['"value3"', '', '"', ' value 43"']
|
218
|
+
}
|
219
|
+
}
|
220
|
+
verify_header(rfh, WMQ::MQFMT_STRING)
|
221
|
+
end
|
222
|
+
|
223
|
+
should 'rf_header_2' do
|
224
|
+
rfh2 = {
|
225
|
+
header_type: :rf_header_2,
|
226
|
+
xml: [
|
227
|
+
'<hello>to the world</hello>',
|
228
|
+
'<another>xml like string</another>'
|
229
|
+
],
|
230
|
+
}
|
231
|
+
verify_header(rfh2, WMQ::MQFMT_STRING)
|
232
|
+
end
|
233
|
+
|
234
|
+
should 'multiple_headers' do
|
235
|
+
headers = [
|
236
|
+
{header_type: :rf_header_2,
|
237
|
+
xml: [
|
238
|
+
'<hello>to the world</hello>',
|
239
|
+
'<another>xml like string</another>'],
|
240
|
+
},
|
241
|
+
|
242
|
+
{
|
243
|
+
header_type: :rf_header,
|
244
|
+
name_value: {
|
245
|
+
' name s' => ' v a l u e 1 ',
|
246
|
+
'n a m e 2 ' => 'v a l u e 2',
|
247
|
+
'name3' => ['value3', '', 'value 43']
|
248
|
+
}
|
249
|
+
},
|
250
|
+
|
251
|
+
{
|
252
|
+
header_type: :ims,
|
253
|
+
l_term_override: 'LTERM',
|
254
|
+
reply_to_format: WMQ::MQFMT_STRING
|
255
|
+
},
|
256
|
+
|
257
|
+
{
|
258
|
+
header_type: :rf_header,
|
259
|
+
name_value: {
|
260
|
+
' name s' => ' v a l u e 1 ',
|
261
|
+
'n a m e 2 ' => 'v a l u e 2',
|
262
|
+
'name3' => ['value3', '', 'value 43']
|
263
|
+
}
|
264
|
+
},
|
265
|
+
|
266
|
+
{
|
267
|
+
header_type: :cics,
|
268
|
+
reason: WMQ::MQRC_UNKNOWN_REMOTE_Q_MGR,
|
269
|
+
facility: 'TOKEN123',
|
270
|
+
reply_to_format: WMQ::MQFMT_STRING
|
271
|
+
},
|
272
|
+
|
273
|
+
{
|
274
|
+
header_type: :rf_header_2,
|
275
|
+
xml: ['<hello>to the world</hello>', '<another>xml like string</another>'],
|
276
|
+
},
|
277
|
+
|
278
|
+
{
|
279
|
+
header_type: :xmit_q_header,
|
280
|
+
remote_q_name: 'SOME_REMOTE_QUEUE',
|
281
|
+
remote_q_mgr_name: 'SOME_REMOTE_Q_MGR',
|
282
|
+
msg_type: WMQ::MQMT_REQUEST,
|
283
|
+
msg_id: 'my message Id'
|
284
|
+
},
|
285
|
+
]
|
286
|
+
verify_multiple_headers(headers, WMQ::MQFMT_STRING)
|
287
|
+
end
|
288
|
+
|
289
|
+
should 'xmit_q_header' do
|
290
|
+
headers = [
|
291
|
+
{
|
292
|
+
header_type: :xmit_q_header,
|
293
|
+
remote_q_name: 'SOME_REMOTE_QUEUE',
|
294
|
+
remote_q_mgr_name: 'SOME_REMOTE_Q_MGR',
|
295
|
+
msg_type: WMQ::MQMT_REQUEST,
|
296
|
+
msg_id: 'my message Id'},
|
297
|
+
|
298
|
+
{
|
299
|
+
header_type: :ims,
|
300
|
+
l_term_override: 'LTERM',
|
301
|
+
reply_to_format: WMQ::MQFMT_STRING
|
302
|
+
},
|
303
|
+
]
|
304
|
+
verify_multiple_headers(headers, WMQ::MQFMT_STRING)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def verify_header(header, format)
|
310
|
+
verify_multiple_headers([header], format)
|
311
|
+
end
|
312
|
+
|
313
|
+
def verify_multiple_headers(headers, format)
|
314
|
+
data = 'Some Test Data'
|
315
|
+
message = WMQ::Message.new
|
316
|
+
message.data = data
|
317
|
+
message.descriptor[:format] = format
|
318
|
+
message.headers = headers
|
319
|
+
#assert_equal(true,@queue_manager.put(q_name: @in_queue.name, message: message))
|
320
|
+
assert_equal(true, @out_queue.put(message: message))
|
321
|
+
|
322
|
+
message = WMQ::Message.new
|
323
|
+
assert_equal true, @in_queue.get(message: message)
|
324
|
+
assert_equal data, message.data
|
325
|
+
assert_equal headers.size, message.headers.size
|
326
|
+
count = 0
|
327
|
+
headers.each do |header|
|
328
|
+
reply_header = message.headers[count]
|
329
|
+
header.each_pair { |key, value| assert_equal(value, reply_header[key]) }
|
330
|
+
count = count + 1
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|