ruote-amqp 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 +5 -0
- data/CREDITS.txt +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +173 -0
- data/Rakefile +30 -28
- data/TODO.txt +8 -0
- data/lib/ruote/amqp/alert_participant.rb +146 -0
- data/lib/ruote/amqp/participant.rb +350 -0
- data/lib/ruote/amqp/receiver.rb +181 -0
- data/lib/ruote/amqp/version.rb +28 -0
- data/lib/ruote/amqp.rb +68 -0
- data/lib/ruote-amqp.rb +1 -78
- data/ruote-amqp.gemspec +9 -6
- data/spec/alert_participant_spec.rb +58 -0
- data/spec/participant_spec.rb +268 -122
- data/spec/participant_subclass_spec.rb +66 -0
- data/spec/receiver_spec.rb +187 -79
- data/spec/spec_helper.rb +12 -70
- data/spec/support/ruote_amqp_helper.rb +40 -0
- metadata +28 -34
- data/PostInstall.txt +0 -12
- data/README.rdoc +0 -91
- data/lib/ruote-amqp/launchitem_listener.rb +0 -22
- data/lib/ruote-amqp/participant.rb +0 -241
- data/lib/ruote-amqp/receiver.rb +0 -147
- data/lib/ruote-amqp/version.rb +0 -6
- data/lib/ruote-amqp/workitem_listener.rb +0 -12
- data/spec/launchitem_listener_spec.rb +0 -70
- data/spec/ruote_amqp_spec.rb +0 -18
- data/spec/support/ruote_helpers.rb +0 -29
- data/spec/support/ruote_matchers.rb +0 -51
- data/spec/workitem_listener_spec.rb +0 -66
@@ -0,0 +1,350 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2012, Kenneth Kalmer, John Mettraux.
|
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
|
+
|
23
|
+
|
24
|
+
module Ruote::Amqp
|
25
|
+
|
26
|
+
#
|
27
|
+
# This participant publishes messages on AMQP exchanges.
|
28
|
+
#
|
29
|
+
# == options
|
30
|
+
#
|
31
|
+
# A few options are supported. They can be declared at 3 levels:
|
32
|
+
#
|
33
|
+
# * options (when the participant is registered)
|
34
|
+
#
|
35
|
+
# dashboard.register(
|
36
|
+
# 'amqp_participant',
|
37
|
+
# Ruote::Amqp::Participant,
|
38
|
+
# :routing_key => 'nada.x')
|
39
|
+
#
|
40
|
+
# * params (from the process definition)
|
41
|
+
#
|
42
|
+
# sequence do
|
43
|
+
# amqp_participant :routing_key => 'nada.x'
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# * fields (from the passing workitem)
|
47
|
+
#
|
48
|
+
# sequence do
|
49
|
+
# set 'f:routing_key' => 'nada.x'
|
50
|
+
# amqp_participant
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# The 'conf' option (only available at participant registration) decides
|
54
|
+
# which levels are enabled or not.
|
55
|
+
#
|
56
|
+
# By default 'conf' is set to 'params, fields, options'.
|
57
|
+
#
|
58
|
+
# === 'conf'
|
59
|
+
#
|
60
|
+
# As said above, this option decides who can tweak this participant's
|
61
|
+
# options. It accepts a comma-separated list of levels.
|
62
|
+
#
|
63
|
+
# The levels are "params", "fields", "options".
|
64
|
+
#
|
65
|
+
# The order in which the levels are given is the order in which they
|
66
|
+
# are investigated for values.
|
67
|
+
#
|
68
|
+
# === 'connection'
|
69
|
+
#
|
70
|
+
# A hash of connection options. This is direcly fed to the amqp gem, the
|
71
|
+
# options of that gem apply thus ('host', 'port', 'vhost', 'username' and
|
72
|
+
# 'password').
|
73
|
+
#
|
74
|
+
# If no 'connection' (or :connection) hash is passed, the participant
|
75
|
+
# will attempt to use the connection (AMQP::Session) found in
|
76
|
+
# Ruote::Amqp.session. If there is nothing in there, it will [attempt] to
|
77
|
+
# create a new connection with AMQP's default settings.
|
78
|
+
#
|
79
|
+
# === 'exchange'
|
80
|
+
#
|
81
|
+
# Accepts a two or three sized Array.
|
82
|
+
#
|
83
|
+
# The first element is a string or symbol detailing the exchange type,
|
84
|
+
# like :direct, :fanout, :topic, ...
|
85
|
+
#
|
86
|
+
# The second element is an exchange name.
|
87
|
+
#
|
88
|
+
# The third, optional, element is a hash of exchange options.
|
89
|
+
#
|
90
|
+
# There is more information at http://rubyamqp.info/
|
91
|
+
#
|
92
|
+
# By default, 'exchange' is set to [ 'direct', '' ] (the default exchange).
|
93
|
+
#
|
94
|
+
# Note: you cannot pass an instantiated Ruby-AMQP exchange here. Ruote
|
95
|
+
# cannot serialize it for remote workers, so the settings are passed
|
96
|
+
# in a flat form, easily JSONifiable.
|
97
|
+
#
|
98
|
+
# === 'field_prefix'
|
99
|
+
#
|
100
|
+
# Sometimes one wants to separate his AMQP participant settings from other
|
101
|
+
# workitem fields.
|
102
|
+
#
|
103
|
+
# dashboard.register(
|
104
|
+
# 'amqp_participant',
|
105
|
+
# Ruote::Amqp::Participant,
|
106
|
+
# :conf => 'fields', :field_prefix => 'amqp_')
|
107
|
+
#
|
108
|
+
# registers a participant that draws is configuration from workitem fields
|
109
|
+
# prefixed with 'amqp_'.
|
110
|
+
#
|
111
|
+
# Note that setting this option doesn't implicitely add 'fields' to the
|
112
|
+
# 'conf' option.
|
113
|
+
#
|
114
|
+
# === 'forget'
|
115
|
+
#
|
116
|
+
# When set to true forces the participant to reply to the engine immediately
|
117
|
+
# after the message got published, in a "fire and forget" fashion.
|
118
|
+
#
|
119
|
+
# === 'routing_key'
|
120
|
+
#
|
121
|
+
# Depending on the exchange used, this option lets you influence how the
|
122
|
+
# exchange routes the message towards queues.
|
123
|
+
#
|
124
|
+
# Consult your AMQP documentation for more information.
|
125
|
+
#
|
126
|
+
# === 'message'
|
127
|
+
#
|
128
|
+
# By default, the workitem is turned into a JSON string and transmitted in
|
129
|
+
# the AMQP message payload. If this 'message' option is set, its value is
|
130
|
+
# used as the payload.
|
131
|
+
#
|
132
|
+
# === 'persistent'
|
133
|
+
#
|
134
|
+
# If this option is set to something else than false or nil, messages
|
135
|
+
# messages published by this participant will be persistent (hopefully
|
136
|
+
# the queues they'll end up in will be persistent as well).
|
137
|
+
#
|
138
|
+
#
|
139
|
+
# == #encode_workitem
|
140
|
+
#
|
141
|
+
# The default way to encode a workitem before pushing it to the exchange
|
142
|
+
# is by turning it entirely into a JSON string.
|
143
|
+
#
|
144
|
+
# To alter this, one can subclass this participant and provide its own
|
145
|
+
# #encode_workitem(wi) method:
|
146
|
+
#
|
147
|
+
# require 'yaml'
|
148
|
+
#
|
149
|
+
# class MyAmqpParticipant < Ruote::Amqp::Participant
|
150
|
+
#
|
151
|
+
# def encode_workitem(workitem)
|
152
|
+
# YAML.dump(workitem)
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# or when one needs to filter some fields:
|
157
|
+
#
|
158
|
+
# class MyAmqpParticipant < Ruote::Amqp::Participant
|
159
|
+
#
|
160
|
+
# def encode_workitem(workitem)
|
161
|
+
# workitem.fields.delete_if { |k, v| k.match(/^private_/) }
|
162
|
+
# super(workitem)
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
class Participant
|
167
|
+
include Ruote::LocalParticipant
|
168
|
+
|
169
|
+
# Initializing the participant, right before calling #on_workitem or
|
170
|
+
# another on_ method.
|
171
|
+
#
|
172
|
+
def initialize(options)
|
173
|
+
|
174
|
+
@options = options
|
175
|
+
|
176
|
+
@conf = (@options['conf'] || 'params, fields, options').split(/\s*,\s*/)
|
177
|
+
@conf = %w[ params fields options ] if @conf.include?('all')
|
178
|
+
|
179
|
+
@field_prefix = @options['field_prefix'] || ''
|
180
|
+
end
|
181
|
+
|
182
|
+
# Workitem consumption code.
|
183
|
+
#
|
184
|
+
def on_workitem
|
185
|
+
|
186
|
+
instantiate_exchange.publish(
|
187
|
+
message,
|
188
|
+
:routing_key => routing_key,
|
189
|
+
:persistent => persistent,
|
190
|
+
:correlation_id => correlation_id)
|
191
|
+
|
192
|
+
reply if forget
|
193
|
+
end
|
194
|
+
|
195
|
+
def on_cancel
|
196
|
+
|
197
|
+
return if opt('discard_cancel')
|
198
|
+
|
199
|
+
instantiate_exchange.publish(
|
200
|
+
encode_cancelitem,
|
201
|
+
:routing_key => routing_key,
|
202
|
+
:persistent => persistent,
|
203
|
+
:correlation_id => correlation_id)
|
204
|
+
end
|
205
|
+
|
206
|
+
# No need for a dedicated thread when dispatching messages. Respond
|
207
|
+
# true.
|
208
|
+
#
|
209
|
+
def do_not_thread; true; end
|
210
|
+
|
211
|
+
# Returns the exchange coordinates (a triple [ type, name, options ]).
|
212
|
+
# Defaults to the direct exchange.
|
213
|
+
#
|
214
|
+
# Available as a method so it can be overriden (the return value could
|
215
|
+
# depend on the @workitem or other factors).
|
216
|
+
#
|
217
|
+
def exchange
|
218
|
+
|
219
|
+
opt('exchange') || [ 'direct', '', {} ]
|
220
|
+
#
|
221
|
+
# defaults to the "default exchange"...
|
222
|
+
end
|
223
|
+
|
224
|
+
# Returns the message to publish.
|
225
|
+
#
|
226
|
+
# Available as a method so it can be overriden (the return value could
|
227
|
+
# depend on the @workitem or other factors).
|
228
|
+
#
|
229
|
+
def message; opt('message') || encode_workitem; end
|
230
|
+
|
231
|
+
# Returns the routing key for the message to publish.
|
232
|
+
#
|
233
|
+
# Available as a method so it can be overriden (the return value could
|
234
|
+
# depend on the @workitem or other factors).
|
235
|
+
#
|
236
|
+
def routing_key; opt('routing_key'); end
|
237
|
+
|
238
|
+
# Returns whether the publish should be persistent or not.
|
239
|
+
#
|
240
|
+
# Available as a method so it can be overriden (the return value could
|
241
|
+
# depend on the @workitem or other factors).
|
242
|
+
#
|
243
|
+
def persistent; opt('persistent'); end
|
244
|
+
|
245
|
+
# Returns the correlation_id for the message publication. Returns ''
|
246
|
+
# by default.
|
247
|
+
#
|
248
|
+
# Available as a method so it can be overriden (the return value could
|
249
|
+
# depend on the @workitem or other factors).
|
250
|
+
#
|
251
|
+
def correlation_id
|
252
|
+
|
253
|
+
opt('correlation_id') || ''
|
254
|
+
end
|
255
|
+
|
256
|
+
# Returns something true-ish if the participant should not reply to the
|
257
|
+
# engine once the publish operation is done.
|
258
|
+
#
|
259
|
+
# Available as a method so it can be overriden (the return value could
|
260
|
+
# depend on the @workitem or other factors).
|
261
|
+
#
|
262
|
+
def forget; opt('forget'); end
|
263
|
+
|
264
|
+
protected
|
265
|
+
|
266
|
+
# How a workitem is turned into an AMQP message payload (string).
|
267
|
+
#
|
268
|
+
# Feel free to override this method to accommodate your needs.
|
269
|
+
#
|
270
|
+
def encode_workitem
|
271
|
+
|
272
|
+
workitem.as_json
|
273
|
+
end
|
274
|
+
|
275
|
+
# How a "cancelitem" is turned into an AMQP message payload (string).
|
276
|
+
#
|
277
|
+
# Feel free to override this method to accommodate your needs.
|
278
|
+
#
|
279
|
+
def encode_cancelitem
|
280
|
+
|
281
|
+
Rufus::Json.encode(
|
282
|
+
'cancel' => true, 'fei' => @fei.h, 'flavour' => @flavour)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Default AMQP.connect method.
|
286
|
+
#
|
287
|
+
# Feel free to override this method to accommodate your needs (See
|
288
|
+
# the README for an example).
|
289
|
+
#
|
290
|
+
def amqp_connect
|
291
|
+
|
292
|
+
ocon = opt('connection')
|
293
|
+
|
294
|
+
if Ruote::Amqp.session && ( ! ocon)
|
295
|
+
Ruote::Amqp.session
|
296
|
+
else
|
297
|
+
AMQP.connect(Ruote.keys_to_sym(ocon || {}))
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Given connection options passed at registration time (when the
|
302
|
+
# participant is registered in ruote) or from the process definition,
|
303
|
+
# returns an AMQP::Channel instance.
|
304
|
+
#
|
305
|
+
def channel
|
306
|
+
|
307
|
+
Thread.current['_ruote_amqp_channel'] ||= AMQP::Channel.new(amqp_connect)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Given exchange options passed at registrations time or from the process
|
311
|
+
# definition, returns an AMQP::Exchange instance.
|
312
|
+
#
|
313
|
+
def instantiate_exchange
|
314
|
+
|
315
|
+
Thread.current['_ruote_amqp_exchange'] ||= begin
|
316
|
+
|
317
|
+
exc = exchange
|
318
|
+
type, name, options = exc
|
319
|
+
|
320
|
+
raise ArgumentError.new(
|
321
|
+
"couldn't determine exchange from #{exc.inspect}"
|
322
|
+
) unless name
|
323
|
+
|
324
|
+
exchange_opts = (options || {}).each_with_object({}) { |(k, v), h|
|
325
|
+
h[k.to_sym] = v
|
326
|
+
}
|
327
|
+
|
328
|
+
AMQP::Exchange.new(channel, type.to_sym, name, exchange_opts)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# The mechanism for looking up options like 'connection', 'exchange',
|
333
|
+
# 'routing_key' in either the participant options, the process
|
334
|
+
# definition or the workitem fields...
|
335
|
+
#
|
336
|
+
def opt(key)
|
337
|
+
|
338
|
+
@conf.each do |type|
|
339
|
+
|
340
|
+
container = (type == 'options' ? @options : workitem.send(type))
|
341
|
+
k = type == 'fields' ? "#{@field_prefix}#{key}" : key
|
342
|
+
|
343
|
+
return container[k] if container.has_key?(k)
|
344
|
+
end
|
345
|
+
|
346
|
+
nil
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
@@ -0,0 +1,181 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2012, Kenneth Kalmer, John Mettraux.
|
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
|
+
|
23
|
+
|
24
|
+
module Ruote::Amqp
|
25
|
+
|
26
|
+
#
|
27
|
+
# A receiver is plugged between a ruote engine/storage and an AMQP queue.
|
28
|
+
# It will listen on the queue for messages, try to turn them into
|
29
|
+
# workitems and feed those workitem back to the engine/storage (in the usual
|
30
|
+
# use case, those workitems were initially emitted by the engine).
|
31
|
+
#
|
32
|
+
# == #decode_message(headers, payload)
|
33
|
+
#
|
34
|
+
# By default, the receiver expects the incoming workitem to be serialized
|
35
|
+
# entirely in the payload of the AMQP message. One can change this
|
36
|
+
# behaviour by overriding the #decode_workitem method (usually when
|
37
|
+
# subclassing)
|
38
|
+
#
|
39
|
+
# class MyYamlReceiver < Ruote::Amqp::Receiver
|
40
|
+
# def decode_message(headers, payload)
|
41
|
+
# YAML.load(payload)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# #decode_message is supposed to return a Ruby hash (describing either a
|
46
|
+
# workitem or a launchitem), the difference is explained below.
|
47
|
+
#
|
48
|
+
# === workitems and launchitems
|
49
|
+
#
|
50
|
+
# The standard use case is to accept workitems coming back (they probably
|
51
|
+
# left the engine via Ruote::Amqp::Participant). But it's also OK
|
52
|
+
# to accept "launchitems", hashes with at least one 'process_definition'
|
53
|
+
# (or 'definition') entry.
|
54
|
+
#
|
55
|
+
# Upon receiving a launchitem, the receiver will launch a new process
|
56
|
+
# instances.
|
57
|
+
#
|
58
|
+
# Launchitems may have two more optional entries, 'workitems_fields' (or
|
59
|
+
# 'fields') and 'process_variables' (or 'variables').
|
60
|
+
#
|
61
|
+
# 'workitem_fields' must contain a hash of initial workitem fields (they will
|
62
|
+
# populate the initial workitem.
|
63
|
+
#
|
64
|
+
# 'process_variables' are a very advanced option. It's possible to set the
|
65
|
+
# initial variables in a workflow. Read the general ruote documentation to
|
66
|
+
# learn about the difference between fields and variables.
|
67
|
+
#
|
68
|
+
# The #decode_message is supposed to return a hash representing either a
|
69
|
+
# workitem, either a launchitem.
|
70
|
+
#
|
71
|
+
# == #handle_error(err)
|
72
|
+
#
|
73
|
+
# Out of the box, the receiver will print out to $stderr the details of
|
74
|
+
# errors it encounters when receiving, decoding and handing back the
|
75
|
+
# workitems (messages?) to the engine. This can be changed by overriding
|
76
|
+
# the #handle_error method:
|
77
|
+
#
|
78
|
+
# class MyReceiver < Ruote::Amqp::Receiver
|
79
|
+
# def handle_error(err)
|
80
|
+
# ThatLoggerService.log(err)
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# == initialization options
|
86
|
+
#
|
87
|
+
# One can set the "launch_only" option to true when initializing the receiver
|
88
|
+
# to prevent it from handling anything but launchitems.
|
89
|
+
#
|
90
|
+
# receiver = Ruote::Amqp::Receiver.new(
|
91
|
+
# @dashboard, @amqp_queue, :launch_only => true)
|
92
|
+
#
|
93
|
+
# 'launch_only' (string) is valid too.
|
94
|
+
#
|
95
|
+
class Receiver < Ruote::Receiver
|
96
|
+
|
97
|
+
attr_reader :queue
|
98
|
+
|
99
|
+
def initialize(engine_or_storage, queue, options={})
|
100
|
+
|
101
|
+
super(engine_or_storage, Ruote.keys_to_s(options))
|
102
|
+
|
103
|
+
@queue = queue
|
104
|
+
@queue.subscribe(&method(:handle))
|
105
|
+
end
|
106
|
+
|
107
|
+
def shutdown
|
108
|
+
|
109
|
+
@queue.unsubscribe
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
def handle(header, payload)
|
115
|
+
|
116
|
+
item = decode_message(header, payload)
|
117
|
+
|
118
|
+
if item['error'] && item['fei']
|
119
|
+
flunk(item)
|
120
|
+
elsif item['fields'] && item['fei']
|
121
|
+
receive(item)
|
122
|
+
elsif item['process_definition'] || item['definition']
|
123
|
+
launch(item)
|
124
|
+
else
|
125
|
+
raise ArgumentError.new("cannot receive or launch #{item.inspect}")
|
126
|
+
end
|
127
|
+
|
128
|
+
rescue => e
|
129
|
+
handle_error(e)
|
130
|
+
end
|
131
|
+
|
132
|
+
def decode_message(header, payload)
|
133
|
+
|
134
|
+
Rufus::Json.decode(payload)
|
135
|
+
end
|
136
|
+
|
137
|
+
def handle_error(err)
|
138
|
+
|
139
|
+
$stderr.puts '**err**'
|
140
|
+
$stderr.puts err.inspect
|
141
|
+
$stderr.puts err.backtrace
|
142
|
+
end
|
143
|
+
|
144
|
+
def flunk(h)
|
145
|
+
|
146
|
+
return if @options['launch_only']
|
147
|
+
|
148
|
+
err = h.delete('error')
|
149
|
+
|
150
|
+
args = case err
|
151
|
+
when String then [ RemoteError, err ]
|
152
|
+
when Hash then [ Ruote.constantize(err['class']), err['message'] ]
|
153
|
+
else [ RemoteError, err.inspect ]
|
154
|
+
end
|
155
|
+
|
156
|
+
super(h, *args)
|
157
|
+
end
|
158
|
+
|
159
|
+
def receive(h)
|
160
|
+
|
161
|
+
return if @options['launch_only']
|
162
|
+
|
163
|
+
super(h)
|
164
|
+
end
|
165
|
+
|
166
|
+
def launch(h)
|
167
|
+
|
168
|
+
super(
|
169
|
+
h['process_definition'] || h['definition'],
|
170
|
+
h['workitem_fields'] || h['fields'] || {},
|
171
|
+
h['process_variables'] || h['variables'] || {})
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# Used to wrap errors that come as string (well, errors that don't come
|
177
|
+
# with a class name). Could be thought of as "anonymous remote error".
|
178
|
+
#
|
179
|
+
class RemoteError < StandardError; end
|
180
|
+
end
|
181
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2012, Kenneth Kalmer, John Mettraux.
|
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
|
+
|
23
|
+
|
24
|
+
module Ruote::Amqp
|
25
|
+
|
26
|
+
VERSION = '2.3.0'
|
27
|
+
end
|
28
|
+
|
data/lib/ruote/amqp.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2012, Kenneth Kalmer, John Mettraux.
|
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
|
+
|
23
|
+
require 'amqp'
|
24
|
+
require 'ruote'
|
25
|
+
|
26
|
+
require 'ruote/amqp/participant'
|
27
|
+
require 'ruote/amqp/alert_participant'
|
28
|
+
require 'ruote/amqp/receiver'
|
29
|
+
|
30
|
+
|
31
|
+
module Ruote::Amqp
|
32
|
+
|
33
|
+
# Returns the AMQP::Session shared by the ruote participants (and potentially
|
34
|
+
# the receivers too).
|
35
|
+
#
|
36
|
+
# Returns nil if none is set (the participant will mostly create a connection
|
37
|
+
# on their own).
|
38
|
+
#
|
39
|
+
# See http://rubydoc.info/github/ruby-amqp/amqp/master/AMQP/Session
|
40
|
+
#
|
41
|
+
def self.session
|
42
|
+
|
43
|
+
@session
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sets the AMQP::Session shared by the ruote participants (and potentially
|
47
|
+
# the receivers too).
|
48
|
+
#
|
49
|
+
# Ruote::Amqp.session = AMQP.connect(:auto_recovery => true) do |con|
|
50
|
+
# con.on_recovery do |con|
|
51
|
+
# puts "Recovered..."
|
52
|
+
# end
|
53
|
+
# connection.on_tcp_connection_loss do |con, settings|
|
54
|
+
# puts "Reconnecting... please wait"
|
55
|
+
# conn.reconnect(false, 20)
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# (Thanks Jim Li - https://github.com/marsbomber/ruote-amqp/commit/0f36a41f)
|
60
|
+
#
|
61
|
+
# See http://rubydoc.info/github/ruby-amqp/amqp/master/AMQP/Session
|
62
|
+
#
|
63
|
+
def self.session=(s)
|
64
|
+
|
65
|
+
@session = s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
data/lib/ruote-amqp.rb
CHANGED
@@ -1,80 +1,3 @@
|
|
1
1
|
|
2
|
-
require '
|
3
|
-
|
4
|
-
require 'ruote-amqp/version'
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
# AMQP participant and listener pair for ruote.
|
9
|
-
#
|
10
|
-
# == Documentation
|
11
|
-
#
|
12
|
-
# See #RuoteAMQP::Listener and #RuoteAMQP::Participant for detailed
|
13
|
-
# documentation on using each of them.
|
14
|
-
#
|
15
|
-
# == AMQP Notes
|
16
|
-
#
|
17
|
-
# RuoteAMQP uses durable queues and persistent messages by default, to ensure
|
18
|
-
# no messages get lost along the way and that running expressions doesn't have
|
19
|
-
# to be restarted in order for messages to be resent.
|
20
|
-
#
|
21
|
-
module RuoteAMQP
|
22
|
-
|
23
|
-
autoload 'ParticipantProxy', 'ruote-amqp/participant'
|
24
|
-
|
25
|
-
autoload 'Receiver', 'ruote-amqp/receiver'
|
26
|
-
autoload 'WorkitemListener', 'ruote-amqp/workitem_listener'
|
27
|
-
autoload 'LaunchitemListener', 'ruote-amqp/launchitem_listener'
|
28
|
-
|
29
|
-
class << self
|
30
|
-
|
31
|
-
attr_writer :use_persistent_messages
|
32
|
-
|
33
|
-
# Whether or not to use persistent messages (true by default)
|
34
|
-
def use_persistent_messages?
|
35
|
-
@use_persistent_messages = true if @use_persistent_messages.nil?
|
36
|
-
@use_persistent_messages
|
37
|
-
end
|
38
|
-
|
39
|
-
# Ensure the AMQP connection is started
|
40
|
-
def start!
|
41
|
-
return if started?
|
42
|
-
|
43
|
-
mutex = Mutex.new
|
44
|
-
cv = ConditionVariable.new
|
45
|
-
|
46
|
-
Thread.main[:ruote_amqp_connection] = Thread.new do
|
47
|
-
Thread.abort_on_exception = true
|
48
|
-
AMQP.start {
|
49
|
-
started!
|
50
|
-
cv.signal
|
51
|
-
}
|
52
|
-
end
|
53
|
-
|
54
|
-
mutex.synchronize { cv.wait(mutex) }
|
55
|
-
|
56
|
-
MQ.prefetch(1)
|
57
|
-
|
58
|
-
yield if block_given?
|
59
|
-
end
|
60
|
-
|
61
|
-
# Check whether the AMQP connection is started
|
62
|
-
def started?
|
63
|
-
Thread.main[:ruote_amqp_started] == true
|
64
|
-
end
|
65
|
-
|
66
|
-
def started! #:nodoc:
|
67
|
-
Thread.main[:ruote_amqp_started] = true
|
68
|
-
end
|
69
|
-
|
70
|
-
# Close down the AMQP connections
|
71
|
-
def stop!
|
72
|
-
return unless started?
|
73
|
-
|
74
|
-
AMQP.stop
|
75
|
-
Thread.main[:ruote_amqp_connection].join
|
76
|
-
Thread.main[:ruote_amqp_started] = false
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
2
|
+
require 'ruote/amqp'
|
80
3
|
|