ruote-amqp 2.2.0 → 2.3.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.
- 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
|
|