march_hare 2.0.0-java

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: be4969a5a66b26f56f7b8bc7eefcd1f4e1627a5c
4
+ data.tar.gz: 5fde2978b7a8a1fd08b11734a046eb2200839635
5
+ SHA512:
6
+ metadata.gz: ca66334d7d382f363a5ac1cf2f77e619e99125e48b616819f6b3e5a1dd48df4571c55e6e838e88fa6488a1dcd619eeec800f5953f96602487e86050503888f81
7
+ data.tar.gz: a4ef1bd5756dac9a0ee79a9d619c4b57a2e0b19e0332ea3383335b4974780d93c11ff80fabfa9af9f41f6feb2c44fecff56e6904291ba0004330357c7c44c57e
Binary file
@@ -0,0 +1,2 @@
1
+ # Backwards compatibility
2
+ require "march_hare"
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ require 'java'
4
+ require 'ext/commons-io'
5
+ require 'ext/rabbitmq-client'
6
+
7
+ require 'march_hare/version'
8
+ require 'march_hare/exceptions'
9
+ require 'march_hare/session'
10
+
11
+ # MarchHare is a JRuby client for RabbitMQ built on top of the official Java client.
12
+ #
13
+ # @see MarchHare.connect
14
+ # @see MarchHare::Session
15
+ # @see MarchHare::Channel
16
+ module MarchHare
17
+ # Delegates to {MarchHare::Session.connect}
18
+ # @see MarchHare::Session.connect
19
+ def self.connect(*args)
20
+ Session.connect(*args)
21
+ end
22
+ end
23
+
24
+ # Backwards compatibility
25
+ # @private
26
+ Hotbunnies = MarchHare
27
+ # Backwards compatibility
28
+ # @private
29
+ HotBunnies = MarchHare
30
+
31
+ require 'march_hare/channel'
32
+ require 'march_hare/queue'
33
+ require 'march_hare/exchange'
@@ -0,0 +1,952 @@
1
+ # encoding: utf-8
2
+ require "march_hare/shutdown_listener"
3
+ require "march_hare/juc"
4
+
5
+ module MarchHare
6
+ # ## Channels in RabbitMQ
7
+ #
8
+ # To quote {http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification}:
9
+ #
10
+ # AMQP 0.9.1 is a multi-channelled protocol. Channels provide a way to multiplex
11
+ # a heavyweight TCP/IP connection into several light weight connections.
12
+ # This makes the protocol more “firewall friendly” since port usage is predictable.
13
+ # It also means that traffic shaping and other network QoS features can be easily employed.
14
+ # Channels are independent of each other and can perform different functions simultaneously
15
+ # with other channels, the available bandwidth being shared between the concurrent activities.
16
+ #
17
+ #
18
+ # ## Opening Channels
19
+ #
20
+ # Channels can be opened either via `MarchHare::Session#create_channel` (sufficient in the majority
21
+ # of cases) or by instantiating `MarchHare::Channel` directly:
22
+ #
23
+ # @example Using {MarchHare::Session#create_channel}:
24
+ # conn = MarchHare.new
25
+ # conn.start
26
+ #
27
+ # ch = conn.create_channel
28
+ #
29
+ # This will automatically allocate a channel id.
30
+ #
31
+ # ## Closing Channels
32
+ #
33
+ # Channels are closed via {MarchHare::Channel#close}. Channels that get a channel-level exception are
34
+ # closed, too. Closed channels can no longer be used. Attempts to use them will raise
35
+ # {MarchHare::ChannelAlreadyClosed}.
36
+ #
37
+ # @example
38
+ #
39
+ # ch = conn.create_channel
40
+ # ch.close
41
+ #
42
+ # ## Higher-level API
43
+ #
44
+ # MarchHare offers two sets of methods on {MarchHare::Channel}: known as higher-level and lower-level
45
+ # APIs, respectively. Higher-level API mimics {http://rubyamqp.info amqp gem} API where
46
+ # exchanges and queues are objects (instance of {MarchHare::Exchange} and {MarchHare::Queue}, respectively).
47
+ # Lower-level API is built around AMQP 0.9.1 methods (commands), where queues and exchanges are
48
+ # passed as strings (à la RabbitMQ Java client, {http://clojurerabbitmq.info Langohr} and Pika).
49
+ #
50
+ # ### Queue Operations In Higher-level API
51
+ #
52
+ # * {MarchHare::Channel#queue} is used to declare queues. The rest of the API is in {MarchHare::Queue}.
53
+ #
54
+ #
55
+ # ### Exchange Operations In Higher-level API
56
+ #
57
+ # * {MarchHare::Channel#topic} declares a topic exchange. The rest of the API is in {MarchHare::Exchange}.
58
+ # * {MarchHare::Channel#direct} declares a direct exchange.
59
+ # * {MarchHare::Channel#fanout} declares a fanout exchange.
60
+ # * {MarchHare::Channel#headers} declares a headers exchange.
61
+ # * {MarchHare::Channel#default_exchange}
62
+ # * {MarchHare::Channel#exchange} is used to declare exchanges with type specified as a symbol or string.
63
+ #
64
+ #
65
+ # ## Channel Qos (Prefetch Level)
66
+ #
67
+ # It is possible to control how many messages at most a consumer will be given (before it acknowledges
68
+ # or rejects previously consumed ones). This setting is per channel and controlled via {MarchHare::Channel#prefetch}.
69
+ #
70
+ #
71
+ # ## Channel IDs
72
+ #
73
+ # Channels are identified by their ids which are integers. MarchHare takes care of allocating and
74
+ # releasing them as channels are opened and closed. It is almost never necessary to specify
75
+ # channel ids explicitly.
76
+ #
77
+ # There is a limit on the maximum number of channels per connection, usually 65536. Note
78
+ # that allocating channels is very cheap on both client and server so having tens, hundreds
79
+ # or even thousands of channels is possible.
80
+ #
81
+ # ## Channels and Error Handling
82
+ #
83
+ # Channel-level exceptions are more common than connection-level ones and often indicate
84
+ # issues applications can recover from (such as consuming from or trying to delete
85
+ # a queue that does not exist).
86
+ #
87
+ # With MarchHare, channel-level exceptions are raised as Ruby exceptions, for example,
88
+ # {MarchHare::NotFound}, that provide access to the underlying `channel.close` method
89
+ # information.
90
+ #
91
+ # @example Handling 404 NOT_FOUND
92
+ # begin
93
+ # ch.queue_delete("queue_that_should_not_exist#{rand}")
94
+ # rescue MarchHare::NotFound => e
95
+ # puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
96
+ # end
97
+ #
98
+ # @example Handling 406 PRECONDITION_FAILED
99
+ # begin
100
+ # ch2 = conn.create_channel
101
+ # q = "rubymarchhare.examples.recovery.q#{rand}"
102
+ #
103
+ # ch2.queue_declare(q, :durable => false)
104
+ # ch2.queue_declare(q, :durable => true)
105
+ # rescue MarchHare::PreconditionFailed => e
106
+ # puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
107
+ # ensure
108
+ # conn.create_channel.queue_delete(q)
109
+ # end
110
+ #
111
+ # @see MarchHare::Session#create_channel
112
+ # @see http://www.rabbitmq.com/tutorials/amqp-concepts.html AMQP 0.9.1 Model Concepts Guide
113
+ # @see http://rubymarchhare.info/articles/getting_started.html Getting Started with RabbitMQ Using MarchHare
114
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers
115
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing
116
+ class Channel
117
+ # @return [Array<MarchHare::Consumer>] Consumers on this channel
118
+ attr_reader :consumers
119
+
120
+ # @private
121
+ def initialize(session, delegate)
122
+ @connection = session
123
+ @delegate = delegate
124
+
125
+ @exchanges = JavaConcurrent::ConcurrentHashMap.new
126
+ @queues = JavaConcurrent::ConcurrentHashMap.new
127
+ # we keep track of consumers in part to gracefully shut down their
128
+ # executors when the channel is closed. This frees library users
129
+ # from having to worry about this. MK.
130
+ @consumers = JavaConcurrent::ConcurrentHashMap.new
131
+ @shutdown_hooks = Array.new
132
+ @recoveries_counter = JavaConcurrent::AtomicInteger.new(0)
133
+
134
+ on_shutdown do |ch, cause|
135
+ ch.gracefully_shut_down_consumers
136
+ end
137
+ end
138
+
139
+ # @return [MarchHare::Session] Connection this channel is on
140
+ def session
141
+ @connection
142
+ end
143
+ alias client session
144
+ alias connection session
145
+
146
+ # @return [Integer] Channel id
147
+ def channel_number
148
+ @delegate.channel_number
149
+ end
150
+ alias id channel_number
151
+ alias number channel_number
152
+
153
+ # Closes the channel.
154
+ #
155
+ # Closed channels can no longer be used. Closed channel id is
156
+ # returned back to the pool of available ids and may be used by
157
+ # a different channel opened later.
158
+ def close(code = 200, reason = "Goodbye")
159
+ v = @delegate.close(code, reason)
160
+
161
+ @consumers.each do |tag, consumer|
162
+ consumer.gracefully_shut_down
163
+ end
164
+
165
+ @connection.unregister_channel(self)
166
+
167
+ v
168
+ end
169
+
170
+ # Defines a shutdown event callback. Shutdown events are
171
+ # broadcasted when a channel is closed, either explicitly
172
+ # or forcefully, or due to a network/peer failure.
173
+ def on_shutdown(&block)
174
+ sh = ShutdownListener.new(self, &block)
175
+
176
+ @shutdown_hooks << sh
177
+ @delegate.add_shutdown_listener(sh)
178
+
179
+ sh
180
+ end
181
+
182
+ # @private
183
+ def automatically_recover(session, java_connection)
184
+ jch = java_connection.create_channel(id)
185
+
186
+ self.revive_with(jch)
187
+ self.recover_shutdown_hooks
188
+
189
+ self.recover_prefetch_setting
190
+ self.recover_exchanges
191
+ # # this includes bindings recovery
192
+ self.recover_queues
193
+ self.recover_consumers
194
+ self.increment_recoveries_counter
195
+ end
196
+
197
+ # @private
198
+ def revive_with(java_ch)
199
+ @delegate = java_ch
200
+ end
201
+
202
+ # @private
203
+ def recover_shutdown_hooks
204
+ @shutdown_hooks.each do |sh|
205
+ @delegate.add_shutdown_listener(sh)
206
+ end
207
+ end
208
+
209
+ # Recovers basic.qos setting. Used by the Automatic Network Failure
210
+ # Recovery feature.
211
+ #
212
+ def recover_prefetch_setting
213
+ basic_qos(@prefetch_count) if @prefetch_count
214
+ end
215
+
216
+ # Recovers exchanges. Used by the Automatic Network Failure
217
+ # Recovery feature.
218
+ #
219
+ def recover_exchanges
220
+ @exchanges.values.each do |x|
221
+ begin
222
+ x.recover_from_network_failure
223
+ rescue Exception => e
224
+ # TODO: logger
225
+ $stderr.puts "Caught exception when recovering exchange #{x.name}"
226
+ end
227
+ end
228
+ end
229
+
230
+ # Recovers queues and bindings. Used by the Automatic Network Failure
231
+ # Recovery feature.
232
+ def recover_queues
233
+ @queues.values.each do |q|
234
+ begin
235
+ q.recover_from_network_failure
236
+ rescue Exception => e
237
+ # TODO: logger
238
+ $stderr.puts "Caught exception when recovering queue #{q.name}"
239
+ end
240
+ end
241
+ end
242
+
243
+ # Recovers consumers. Used by the Automatic Network Failure
244
+ # Recovery feature.
245
+ def recover_consumers
246
+ @consumers.values.each do |c|
247
+ begin
248
+ self.unregister_consumer(c)
249
+ c.recover_from_network_failure
250
+ rescue Exception => e
251
+ # TODO: logger
252
+ $stderr.puts "Caught exception when recovering consumer #{c.consumer_tag}"
253
+ end
254
+ end
255
+ end
256
+
257
+ # @private
258
+ def increment_recoveries_counter
259
+ @recoveries_counter.increment_and_get
260
+ end
261
+
262
+ attr_reader :recoveries_counter
263
+
264
+ # @group Exchanges
265
+
266
+ # Declares a headers exchange or looks it up in the cache of previously
267
+ # declared exchanges.
268
+ #
269
+ # @param [String] name Exchange name
270
+ # @param [Hash] opts Exchange parameters
271
+ #
272
+ # @option options [String,Symbol] :type (:direct) Exchange type, e.g. :fanout or "x-consistent-hash"
273
+ # @option options [Boolean] :durable (false) Should the exchange be durable?
274
+ # @option options [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
275
+ # @option options [Hash] :arguments ({}) Optional exchange arguments
276
+ #
277
+ # @return [MarchHare::Exchange] Exchange instance
278
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
279
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
280
+ def exchange(name, options={})
281
+ dx = Exchange.new(self, name, options).tap do |x|
282
+ x.declare!
283
+ end
284
+
285
+ self.register_exchange(dx)
286
+ end
287
+
288
+ # Declares a fanout exchange or looks it up in the cache of previously
289
+ # declared exchanges.
290
+ #
291
+ # @param [String] name Exchange name
292
+ # @param [Hash] opts Exchange parameters
293
+ #
294
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
295
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
296
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions)
297
+ #
298
+ # @return [MarchHare::Exchange] Exchange instance
299
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
300
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
301
+ def fanout(name, opts = {})
302
+ dx = Exchange.new(self, name, opts.merge(:type => "fanout")).tap do |x|
303
+ x.declare!
304
+ end
305
+
306
+ self.register_exchange(dx)
307
+ end
308
+
309
+ # Declares a direct exchange or looks it up in the cache of previously
310
+ # declared exchanges.
311
+ #
312
+ # @param [String] name Exchange name
313
+ # @param [Hash] opts Exchange parameters
314
+ #
315
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
316
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
317
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions)
318
+ #
319
+ # @return [MarchHare::Exchange] Exchange instance
320
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
321
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
322
+ def direct(name, opts = {})
323
+ dx = Exchange.new(self, name, opts.merge(:type => "direct")).tap do |x|
324
+ x.declare!
325
+ end
326
+
327
+ self.register_exchange(dx)
328
+ end
329
+
330
+ # Declares a topic exchange or looks it up in the cache of previously
331
+ # declared exchanges.
332
+ #
333
+ # @param [String] name Exchange name
334
+ # @param [Hash] opts Exchange parameters
335
+ #
336
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
337
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
338
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions)
339
+ #
340
+ # @return [MarchHare::Exchange] Exchange instance
341
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
342
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
343
+ def topic(name, opts = {})
344
+ dx = Exchange.new(self, name, opts.merge(:type => "topic")).tap do |x|
345
+ x.declare!
346
+ end
347
+
348
+ self.register_exchange(dx)
349
+ end
350
+
351
+ # Declares a headers exchange or looks it up in the cache of previously
352
+ # declared exchanges.
353
+ #
354
+ # @param [String] name Exchange name
355
+ # @param [Hash] opts Exchange parameters
356
+ #
357
+ # @option opts [Boolean] :durable (false) Should the exchange be durable?
358
+ # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
359
+ # @option opts [Hash] :arguments ({}) Optional exchange arguments
360
+ #
361
+ # @return [MarchHare::Exchange] Exchange instance
362
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
363
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
364
+ def headers(name, opts = {})
365
+ dx = Exchange.new(self, name, opts.merge(:type => "headers")).tap do |x|
366
+ x.declare!
367
+ end
368
+
369
+ self.register_exchange(dx)
370
+ end
371
+
372
+ # Provides access to the default exchange
373
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
374
+ def default_exchange
375
+ @default_exchange ||= self.exchange("", :durable => true, :auto_delete => false, :type => "direct")
376
+ end
377
+
378
+ # Declares a echange using echange.declare AMQP 0.9.1 method.
379
+ #
380
+ # @param [String] name Exchange name
381
+ # @param [Boolean] durable (false) Should information about this echange be persisted to disk so that it
382
+ # can survive broker restarts? Typically set to true for long-lived exchanges.
383
+ # @param [Boolean] auto_delete (false) Should this echange be deleted when it is no longer used?
384
+ # @param [Boolean] passive (false) If true, exchange will be checked for existence. If it does not
385
+ # exist, {MarchHare::NotFound} will be raised.
386
+ #
387
+ # @return RabbitMQ response
388
+ # @see http://rubymarchhare.info/articles/echanges.html Exchanges and Publishing guide
389
+ def exchange_declare(name, type, durable = false, auto_delete = false, arguments = nil)
390
+ @delegate.exchange_declare(name, type, durable, auto_delete, arguments)
391
+ end
392
+
393
+ # Binds an exchange to another exchange using exchange.bind method (RabbitMQ extension)
394
+ #
395
+ # @param [String] desitnation Destination exchange name
396
+ # @param [String] source Source exchange name
397
+ #
398
+ # @param [String] routing_key Routing key used for binding
399
+ # @param [Hash] arguments (nil) Optional arguments
400
+ #
401
+ # @return RabbitMQ response
402
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ extensions guide
403
+ # @see http://rubymarchhare.info/articles/bindings.html Bindings guide
404
+ def exchange_bind(destination, source, routing_key, arguments = nil)
405
+ @delegate.exchange_bind(destination, source, routing_key, arguments)
406
+ end
407
+
408
+ # Unbinds an exchange from another exchange using exchange.unbind method (RabbitMQ extension)
409
+ #
410
+ # @param [String] destination Destination exchange name
411
+ # @param [String] source Source exchange name
412
+ #
413
+ # @param [String] routing_key Routing key used for binding
414
+ # @param [Hash] arguments ({}) Optional arguments
415
+ #
416
+ # @return RabbitMQ response
417
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ extensions guide
418
+ # @see http://rubymarchhare.info/articles/bindings.html Bindings guide
419
+ def exchange_unbind(destination, source, routing_key, arguments = nil)
420
+ @delegate.exchange_unbind(destination, source, routing_key, arguments)
421
+ end
422
+
423
+ # @endgroup
424
+
425
+
426
+ # @group Queues
427
+
428
+ # Declares a queue or looks it up in the per-channel cache.
429
+ #
430
+ # @param [String] name Queue name. Pass an empty string to declare a server-named queue (make RabbitMQ generate a unique name).
431
+ # @param [Hash] options Queue properties and other options
432
+ #
433
+ # @option options [Boolean] :durable (false) Should this queue be durable?
434
+ # @option options [Boolean] :auto-delete (false) Should this queue be automatically deleted when the last consumer disconnects?
435
+ # @option options [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
436
+ # @option options [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
437
+ #
438
+ # @return [MarchHare::Queue] Queue that was declared or looked up in the cache
439
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
440
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
441
+ def queue(name, options={})
442
+ dq = Queue.new(self, name, options).tap do |q|
443
+ q.declare!
444
+ end
445
+
446
+ self.register_queue(dq)
447
+ end
448
+
449
+ # Declares a queue using queue.declare AMQP 0.9.1 method.
450
+ #
451
+ # @param [String] name Queue name
452
+ #
453
+ # @param [Boolean] durable (false) Should information about this queue be persisted to disk so that it
454
+ # can survive broker restarts? Typically set to true for long-lived queues.
455
+ # @param [Boolean] auto_delete (false) Should this queue be deleted when the last consumer is cancelled?
456
+ # @param [Boolean] exclusive (false) Should only this connection be able to use this queue?
457
+ # If true, the queue will be automatically deleted when this
458
+ # connection is closed
459
+ # @param [Boolean] passive (false) If true, queue will be checked for existence. If it does not
460
+ # exist, {MarchHare::NotFound} will be raised.
461
+ #
462
+ # @return RabbitMQ response
463
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
464
+ def queue_declare(name, durable, exclusive, auto_delete, arguments = {})
465
+ converting_rjc_exceptions_to_ruby do
466
+ @delegate.queue_declare(name, durable, exclusive, auto_delete, arguments)
467
+ end
468
+ end
469
+
470
+ # Checks if a queue exists using queue.declare AMQP 0.9.1 method.
471
+ # If it does not, a channel exception will be raised.
472
+ #
473
+ # @param [String] name Queue name
474
+ #
475
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
476
+ def queue_declare_passive(name)
477
+ converting_rjc_exceptions_to_ruby do
478
+ @delegate.queue_declare_passive(name)
479
+ end
480
+ end
481
+
482
+ # Deletes a queue using queue.delete AMQP 0.9.1 method
483
+ #
484
+ # @param [String] name Queue name
485
+ #
486
+ # @param [Boolean] if_empty (false) Should this queue be deleted only if it has no messages?
487
+ # @param [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers?
488
+ #
489
+ # @return RabbitMQ response
490
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
491
+ def queue_delete(name, if_empty = false, if_unused = false)
492
+ converting_rjc_exceptions_to_ruby do
493
+ @delegate.queue_delete(name, if_empty, if_unused)
494
+ end
495
+ end
496
+
497
+ # Binds a queue to an exchange using queue.bind AMQP 0.9.1 method
498
+ #
499
+ # @param [String] name Queue name
500
+ # @param [String] exchange Exchange name
501
+ #
502
+ # @param [String] routing_key Routing key used for binding
503
+ # @param [Hash] arguments (nil) Optional arguments
504
+ #
505
+ # @return RabbitMQ response
506
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
507
+ # @see http://rubymarchhare.info/articles/bindings.html Bindings guide
508
+ def queue_bind(queue, exchange, routing_key, arguments = nil)
509
+ converting_rjc_exceptions_to_ruby do
510
+ @delegate.queue_bind(queue, exchange, routing_key, arguments)
511
+ end
512
+ end
513
+
514
+ # Unbinds a queue from an exchange using queue.unbind AMQP 0.9.1 method
515
+ #
516
+ # @param [String] name Queue name
517
+ # @param [String] exchange Exchange name
518
+ #
519
+ # @param [String] routing_key Routing key used for binding
520
+ # @param [Hash] arguments ({}) Optional arguments
521
+ #
522
+ # @return RabbitMQ response
523
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
524
+ # @see http://rubymarchhare.info/articles/bindings.html Bindings guide
525
+ def queue_unbind(queue, exchange, routing_key, arguments = nil)
526
+ converting_rjc_exceptions_to_ruby do
527
+ @delegate.queue_unbind(queue, exchange, routing_key, arguments)
528
+ end
529
+ end
530
+
531
+ # Purges a queue (removes all messages from it) using queue.purge AMQP 0.9.1 method.
532
+ #
533
+ # @param [String] name Queue name
534
+ #
535
+ # @return RabbitMQ response
536
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
537
+ def queue_purge(name)
538
+ converting_rjc_exceptions_to_ruby do
539
+ @delegate.queue_purge(name)
540
+ end
541
+ end
542
+
543
+ # @endgroup
544
+
545
+
546
+ # @group basic.*
547
+
548
+ # Publishes a message using basic.publish AMQP 0.9.1 method.
549
+ #
550
+ # @param [String] exchange Exchange to publish to
551
+ # @param [String] routing_key Routing key
552
+ # @param [String] body Message payload. It will never be modified by MarchHare or RabbitMQ in any way.
553
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
554
+ #
555
+ # @param [Hash] properties Message properties
556
+ #
557
+ # @option properties [Boolean] :persistent Should the message be persisted to disk?
558
+ # @option properties [Integer] :timestamp A timestamp associated with this message
559
+ # @option properties [Integer] :expiration Expiration time after which the message will be deleted
560
+ # @option properties [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
561
+ # @option properties [String] :reply_to Queue name other apps should send the response to
562
+ # @option properties [String] :content_type Message content type (e.g. application/json)
563
+ # @option properties [String] :content_encoding Message content encoding (e.g. gzip)
564
+ # @option properties [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
565
+ # @option properties [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
566
+ # @option properties [String] :message_id Any message identifier
567
+ # @option properties [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
568
+ # @option properties [String] :app_id Optional application ID
569
+ #
570
+ # @return [MarchHare::Channel] Self
571
+ def basic_publish(exchange, routing_key, mandatory, properties, body)
572
+ converting_rjc_exceptions_to_ruby do
573
+ @delegate.basic_publish(exchange, routing_key, mandatory, false, BasicPropertiesBuilder.build_properties_from(properties || Hash.new), body)
574
+ end
575
+ end
576
+
577
+ def basic_get(queue, auto_ack)
578
+ converting_rjc_exceptions_to_ruby do
579
+ @delegate.basic_get(queue, auto_ack)
580
+ end
581
+ end
582
+
583
+ def basic_consume(queue, auto_ack, consumer)
584
+ consumer.auto_ack = auto_ack
585
+ tag = converting_rjc_exceptions_to_ruby do
586
+ @delegate.basic_consume(queue, auto_ack, consumer)
587
+ end
588
+ self.register_consumer(tag, consumer)
589
+
590
+ tag
591
+ end
592
+
593
+ def basic_qos(prefetch_count)
594
+ r = converting_rjc_exceptions_to_ruby do
595
+ @delegate.basic_qos(prefetch_count)
596
+ end
597
+ @prefetch_count = prefetch_count
598
+
599
+ r
600
+ end
601
+
602
+ def qos(options={})
603
+ if options.size == 1 && options[:prefetch_count]
604
+ then basic_qos(options[:prefetch_count])
605
+ else basic_qos(options.fetch(:prefetch_size, 0), options.fetch(:prefetch_count, 0), options.fetch(:global, false))
606
+ end
607
+ end
608
+
609
+ # Sets how many messages will be given to consumers on this channel before they
610
+ # have to acknowledge or reject one of the previously consumed messages
611
+ #
612
+ # @param [Integer] prefetch_count Prefetch (QoS setting) for this channel
613
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
614
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
615
+ def prefetch=(n)
616
+ basic_qos(n)
617
+ end
618
+
619
+ # Acknowledges a message. Acknowledged messages are completely removed from the queue.
620
+ #
621
+ # @param [Integer] delivery_tag Delivery tag to acknowledge
622
+ # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be acknowledged as well?
623
+ # @see #nack
624
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
625
+ def ack(delivery_tag, multiple = false)
626
+ guarding_against_stale_delivery_tags(delivery_tag) do
627
+ basic_ack(delivery_tag.to_i, multiple)
628
+ end
629
+ end
630
+ alias acknowledge ack
631
+
632
+ # Rejects a message. A rejected message can be requeued or
633
+ # dropped by RabbitMQ.
634
+ #
635
+ # @param [Integer] delivery_tag Delivery tag to reject
636
+ # @param [Boolean] requeue Should this message be requeued instead of dropping it?
637
+ # @see #ack
638
+ # @see #nack
639
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
640
+ def reject(delivery_tag, requeue = false)
641
+ guarding_against_stale_delivery_tags(delivery_tag) do
642
+ basic_reject(delivery_tag.to_i, requeue)
643
+ end
644
+ end
645
+
646
+ # Rejects a message. A rejected message can be requeued or
647
+ # dropped by RabbitMQ. This method is similar to {MarchHare::Channel#reject} but
648
+ # supports rejecting multiple messages at once, and is usually preferred.
649
+ #
650
+ # @param [Integer] delivery_tag Delivery tag to reject
651
+ # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be rejected as well?
652
+ # @param [Boolean] requeue (false) Should this message be requeued instead of dropping it?
653
+ # @see #ack
654
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
655
+ def nack(delivery_tag, multiple = false, requeue = false)
656
+ guarding_against_stale_delivery_tags(delivery_tag) do
657
+ basic_nack(delivery_tag.to_i, multiple, requeue)
658
+ end
659
+ end
660
+
661
+ # Rejects or requeues a message.
662
+ #
663
+ # @param [Integer] delivery_tag Delivery tag obtained from delivery info
664
+ # @param [Boolean] requeue Should the message be requeued?
665
+ # @return [NilClass] nil
666
+ #
667
+ # @example Requeue a message
668
+ # conn = MarchHare.new
669
+ # conn.start
670
+ #
671
+ # ch = conn.create_channel
672
+ # q.subscribe do |delivery_info, properties, payload|
673
+ # # requeue the message
674
+ # ch.basic_reject(delivery_info.delivery_tag, true)
675
+ # end
676
+ #
677
+ # @example Reject a message
678
+ # conn = MarchHare.new
679
+ # conn.start
680
+ #
681
+ # ch = conn.create_channel
682
+ # q.subscribe do |delivery_info, properties, payload|
683
+ # # requeue the message
684
+ # ch.basic_reject(delivery_info.delivery_tag, false)
685
+ # end
686
+ #
687
+ # @example Requeue a message fetched via basic.get
688
+ # conn = MarchHare.new
689
+ # conn.start
690
+ #
691
+ # ch = conn.create_channel
692
+ # # we assume the queue exists and has messages
693
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
694
+ # ch.basic_reject(delivery_info.delivery_tag, true)
695
+ #
696
+ # @see #basic_nack
697
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
698
+ def basic_reject(delivery_tag, requeue)
699
+ converting_rjc_exceptions_to_ruby do
700
+ @delegate.basic_reject(delivery_tag.to_i, requeue)
701
+ end
702
+ end
703
+
704
+ # Acknowledges one or more messages (deliveries).
705
+ #
706
+ # @param [Integer] delivery_tag Delivery tag obtained from delivery info
707
+ # @param [Boolean] multiple Should all deliveries up to this one be acknowledged?
708
+ # @return [NilClass] nil
709
+ #
710
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
711
+ def basic_ack(delivery_tag, multiple)
712
+ converting_rjc_exceptions_to_ruby do
713
+ @delegate.basic_ack(delivery_tag.to_i, multiple)
714
+ end
715
+ end
716
+
717
+ # Rejects or requeues messages just like {MarchHare::Channel#basic_reject} but can do so
718
+ # with multiple messages at once.
719
+ #
720
+ # @param [Integer] delivery_tag Delivery tag obtained from delivery info
721
+ # @param [Boolean] requeue Should the message be requeued?
722
+ # @param [Boolean] multiple Should all deliveries up to this one be rejected/requeued?
723
+ # @return [NilClass] nil
724
+ #
725
+ # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
726
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
727
+ def basic_nack(delivery_tag, multiple = false, requeue = false)
728
+ converting_rjc_exceptions_to_ruby do
729
+ @delegate.basic_nack(delivery_tag.to_i, multiple, requeue)
730
+ end
731
+ end
732
+
733
+ # Redeliver unacknowledged messages
734
+ #
735
+ # @param [Boolean] requeue Should messages be requeued?
736
+ # @return RabbitMQ response
737
+ def basic_recover(requeue = true)
738
+ converting_rjc_exceptions_to_ruby do
739
+ @delegate.basic_recover(requeue)
740
+ end
741
+ end
742
+
743
+ # Redeliver unacknowledged messages
744
+ #
745
+ # @param [Boolean] requeue Should messages be requeued?
746
+ # @return RabbitMQ response
747
+ def basic_recover_async(requeue = true)
748
+ converting_rjc_exceptions_to_ruby do
749
+ @delegate.basic_recover_async(requeue)
750
+ end
751
+ end
752
+
753
+ # @endgroup
754
+
755
+ # Enables publisher confirms on the channel.
756
+ # @return [NilClass] nil
757
+ #
758
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishers guide
759
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
760
+ def confirm_select
761
+ converting_rjc_exceptions_to_ruby do
762
+ @delegate.confirm_select
763
+ end
764
+ end
765
+
766
+ # Waits until all outstanding publisher confirms arrive.
767
+ #
768
+ # Takes an optional timeout in milliseconds. Will raise
769
+ # an exception in timeout has occured.
770
+ #
771
+ # @param [Integer] timeout Timeout in milliseconds
772
+ # @return [Boolean] true if all confirms were positive,
773
+ # false if some were negative
774
+ def wait_for_confirms(timeout = nil)
775
+ if timeout
776
+ converting_rjc_exceptions_to_ruby do
777
+ @delegate.wait_for_confirms(timeout)
778
+ end
779
+ else
780
+ @delegate.wait_for_confirms
781
+ end
782
+ end
783
+
784
+ def next_publisher_seq_no
785
+ @delegate.next_publisher_seq_no
786
+ end
787
+
788
+ # Enables transactions on the channel
789
+ def tx_select
790
+ converting_rjc_exceptions_to_ruby do
791
+ @delegate.tx_select
792
+ end
793
+ end
794
+
795
+ # Commits a transaction
796
+ def tx_commit
797
+ converting_rjc_exceptions_to_ruby do
798
+ @delegate.tx_commit
799
+ end
800
+ end
801
+
802
+ # Rolls back a transaction
803
+ def tx_rollback
804
+ converting_rjc_exceptions_to_ruby do
805
+ @delegate.tx_rollback
806
+ end
807
+ end
808
+
809
+ # Enables or disables channel flow. This feature id deprecated
810
+ # in RabbitMQ.
811
+ def channel_flow(active)
812
+ converting_rjc_exceptions_to_ruby do
813
+ @delegate.channel_flow(active)
814
+ end
815
+ end
816
+
817
+ # Defines a returned message handler.
818
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishers guide
819
+ def on_return(&block)
820
+ self.add_return_listener(BlockReturnListener.from(block))
821
+ end
822
+
823
+ # Defines a publisher confirm handler
824
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishers guide
825
+ def on_confirm(&block)
826
+ self.add_confirm_listener(BlockConfirmListener.from(block))
827
+ end
828
+
829
+ def method_missing(selector, *args)
830
+ @delegate.__send__(selector, *args)
831
+ end
832
+
833
+
834
+ #
835
+ # Implementation
836
+ #
837
+
838
+ # @private
839
+ class BlockConfirmListener
840
+ include com.rabbitmq.client.ConfirmListener
841
+
842
+ def self.from(block)
843
+ new(block)
844
+ end
845
+
846
+ def initialize(block)
847
+ @block = block
848
+ end
849
+
850
+ def handleAck(delivery_tag, multiple)
851
+ @block.call(:ack, delivery_tag, multiple)
852
+ end
853
+
854
+ def handleNack(delivery_tag, multiple)
855
+ @block.call(:nack, delivery_tag, multiple)
856
+ end
857
+ end
858
+
859
+ # @private
860
+ class BlockReturnListener
861
+ include com.rabbitmq.client.ReturnListener
862
+
863
+ def self.from(block)
864
+ new(block)
865
+ end
866
+
867
+ def initialize(block)
868
+ @block = block
869
+ end
870
+
871
+ def handleReturn(reply_code, reply_text, exchange, routing_key, basic_properties, payload)
872
+ # TODO: convert properties to a Ruby hash
873
+ @block.call(reply_code, reply_text, exchange, routing_key, basic_properties, String.from_java_bytes(payload))
874
+ end
875
+ end
876
+
877
+ # @private
878
+ def deregister_queue(queue)
879
+ @queues.delete(queue.name)
880
+ end
881
+
882
+ # @private
883
+ def deregister_queue_named(name)
884
+ @queues.delete(name)
885
+ end
886
+
887
+ # @private
888
+ def register_queue(queue)
889
+ @queues[queue.name] = queue
890
+ end
891
+
892
+ # @private
893
+ def find_queue(name)
894
+ @queues[name]
895
+ end
896
+
897
+ # @private
898
+ def deregister_exchange(exchange)
899
+ @exchanges.delete(exchange.name)
900
+ end
901
+
902
+ # @private
903
+ def register_exchange(exchange)
904
+ @exchanges[exchange.name] = exchange
905
+ end
906
+
907
+ # @private
908
+ def register_consumer(consumer_tag, consumer)
909
+ @consumers[consumer_tag] = consumer
910
+ end
911
+
912
+ # @private
913
+ def unregister_consumer(consumer_tag)
914
+ @consumers.delete(consumer_tag)
915
+ end
916
+
917
+ # @private
918
+ def gracefully_shut_down_consumers
919
+ @consumers.each do |tag, consumer|
920
+ consumer.gracefully_shut_down
921
+ end
922
+ end
923
+
924
+ # Executes a block, catching Java exceptions RabbitMQ Java client throws and
925
+ # transforms them to Ruby exceptions that are then re-raised.
926
+ #
927
+ # @private
928
+ def converting_rjc_exceptions_to_ruby(&block)
929
+ begin
930
+ block.call
931
+ rescue Exception, java.lang.Throwable => e
932
+ Exceptions.convert_and_reraise(e)
933
+ end
934
+ end
935
+
936
+ # @private
937
+ def guarding_against_stale_delivery_tags(tag, &block)
938
+ case tag
939
+ # if a fixnum was passed, execute unconditionally. MK.
940
+ when Fixnum then
941
+ block.call
942
+ # versioned delivery tags should be checked to avoid
943
+ # sending out stale (invalid) tags after channel was reopened
944
+ # during network failure recovery. MK.
945
+ when VersionedDeliveryTag then
946
+ if !tag.stale?(@recoveries_counter.get)
947
+ block.call
948
+ end
949
+ end
950
+ end
951
+ end
952
+ end