hot_bunnies 1.5.0-java → 2.0.0.pre1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/lib/ext/rabbitmq-client.jar +0 -0
  2. data/lib/hot_bunnies/channel.rb +287 -9
  3. data/lib/hot_bunnies/consumers.rb +152 -0
  4. data/lib/hot_bunnies/exceptions.rb +151 -0
  5. data/lib/hot_bunnies/exchange.rb +22 -30
  6. data/lib/hot_bunnies/juc.rb +9 -0
  7. data/lib/hot_bunnies/metadata.rb +90 -0
  8. data/lib/hot_bunnies/queue.rb +52 -265
  9. data/lib/hot_bunnies/session.rb +170 -0
  10. data/lib/hot_bunnies/version.rb +1 -1
  11. data/lib/hot_bunnies.rb +5 -91
  12. metadata +15 -39
  13. data/.gitignore +0 -7
  14. data/.rvmrc +0 -1
  15. data/.travis.yml +0 -12
  16. data/ChangeLog.md +0 -46
  17. data/Gemfile +0 -10
  18. data/LICENSE +0 -20
  19. data/README.md +0 -58
  20. data/Rakefile +0 -6
  21. data/examples/blocking_subscription.rb +0 -36
  22. data/examples/non_blocking_subscription.rb +0 -32
  23. data/examples/non_blocking_subscription_with_executor.rb +0 -38
  24. data/hot_bunnies.gemspec +0 -24
  25. data/spec/integration/alternate_exchanges_spec.rb +0 -36
  26. data/spec/integration/basic_consume_spec.rb +0 -128
  27. data/spec/integration/connection_spec.rb +0 -26
  28. data/spec/integration/error_handling_by_consumers_spec.rb +0 -97
  29. data/spec/integration/exchange_bind_spec.rb +0 -35
  30. data/spec/integration/exchange_declare_spec.rb +0 -113
  31. data/spec/integration/message_metadata_access_spec.rb +0 -94
  32. data/spec/integration/publisher_confirmations_spec.rb +0 -51
  33. data/spec/integration/queue_bind_spec.rb +0 -56
  34. data/spec/integration/queue_declare_spec.rb +0 -44
  35. data/spec/integration/queue_delete_spec.rb +0 -23
  36. data/spec/integration/queue_purge_spec.rb +0 -38
  37. data/spec/integration/queue_unbind_spec.rb +0 -53
  38. data/spec/integration/sender_selected_distribution_spec.rb +0 -47
  39. data/spec/spec_helper.rb +0 -18
@@ -0,0 +1,90 @@
1
+ module HotBunnies
2
+ class Headers
3
+ attr_reader :channel, :consumer_tag, :envelope, :properties
4
+
5
+ def initialize(channel, consumer_tag, envelope, properties)
6
+ @channel = channel
7
+ @consumer_tag = consumer_tag
8
+ @envelope = envelope
9
+ @properties = properties
10
+ end
11
+
12
+ def ack(options={})
13
+ @channel.basic_ack(delivery_tag, options.fetch(:multiple, false))
14
+ end
15
+
16
+ def reject(options={})
17
+ @channel.basic_reject(delivery_tag, options.fetch(:requeue, false))
18
+ end
19
+
20
+ begin :envelope_delegation
21
+ [
22
+ :delivery_tag,
23
+ :routing_key,
24
+ :redeliver,
25
+ :exchange
26
+ ].each do |envelope_property|
27
+ define_method(envelope_property) { @envelope.__send__(envelope_property) }
28
+ end
29
+
30
+ alias_method :redelivered?, :redeliver
31
+ end
32
+
33
+ begin :message_properties_delegation
34
+ [
35
+ :content_encoding,
36
+ :content_type,
37
+ :content_encoding,
38
+ :headers,
39
+ :delivery_mode,
40
+ :priority,
41
+ :correlation_id,
42
+ :reply_to,
43
+ :expiration,
44
+ :message_id,
45
+ :timestamp,
46
+ :type,
47
+ :user_id,
48
+ :app_id,
49
+ :cluster_id
50
+ ].each do |properties_property|
51
+ define_method(properties_property) { @properties.__send__(properties_property) }
52
+ end # each
53
+ end
54
+
55
+ def persistent?
56
+ delivery_mode == 2
57
+ end
58
+
59
+ def redelivered?
60
+ redeliver
61
+ end
62
+
63
+ def redelivery?
64
+ redeliver
65
+ end
66
+ end # Headers
67
+
68
+
69
+ class BasicPropertiesBuilder
70
+ def self.build_properties_from(props = {})
71
+ builder = AMQP::BasicProperties::Builder.new
72
+
73
+ builder.content_type(props[:content_type]).
74
+ content_encoding(props[:content_encoding]).
75
+ headers(props[:headers]).
76
+ delivery_mode(props[:persistent] ? 2 : 1).
77
+ priority(props[:priority]).
78
+ correlation_id(props[:correlation_id]).
79
+ reply_to(props[:reply_to]).
80
+ expiration(if props[:expiration] then props[:expiration].to_s end).
81
+ message_id(props[:message_id]).
82
+ timestamp(props[:timestamp]).
83
+ type(props[:type]).
84
+ user_id(props[:user_id]).
85
+ app_id(props[:app_id]).
86
+ cluster_id(props[:cluster_id]).
87
+ build
88
+ end
89
+ end
90
+ end # HotBunnies
@@ -1,14 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
- module JavaConcurrent
4
- java_import 'java.lang.Thread'
5
- java_import 'java.lang.InterruptedException'
6
- java_import 'java.util.concurrent.Executors'
7
- java_import 'java.util.concurrent.LinkedBlockingQueue'
8
- java_import 'java.util.concurrent.ArrayBlockingQueue'
9
- java_import 'java.util.concurrent.TimeUnit'
10
- java_import 'java.util.concurrent.atomic.AtomicBoolean'
11
- end
3
+ require "hot_bunnies/juc"
4
+ require "hot_bunnies/metadata"
5
+ require "hot_bunnies/consumers"
12
6
 
13
7
  module HotBunnies
14
8
  class Queue
@@ -18,17 +12,20 @@ module HotBunnies
18
12
  @channel = channel
19
13
  @name = name
20
14
  @options = {:durable => false, :exclusive => false, :auto_delete => false, :passive => false, :arguments => Hash.new}.merge(options)
21
- declare!
22
15
  end
23
16
 
24
17
  def bind(exchange, options={})
25
18
  exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end
26
19
  @channel.queue_bind(@name, exchange_name, options.fetch(:routing_key, ''), options[:arguments])
20
+
21
+ self
27
22
  end
28
23
 
29
24
  def unbind(exchange, options={})
30
25
  exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end
31
26
  @channel.queue_unbind(@name, exchange_name, options.fetch(:routing_key, ''))
27
+
28
+ self
32
29
  end
33
30
 
34
31
  def delete
@@ -39,281 +36,71 @@ module HotBunnies
39
36
  @channel.queue_purge(@name)
40
37
  end
41
38
 
42
- def get(options={})
39
+ def get(options = {:block => false})
43
40
  response = @channel.basic_get(@name, !options.fetch(:ack, false))
41
+
44
42
  if response
45
- then [Headers.new(@channel, nil, response.envelope, response.props), String.from_java_bytes(response.body)]
46
- else nil
43
+ [Headers.new(@channel, nil, response.envelope, response.props), String.from_java_bytes(response.body)]
44
+ else
45
+ nil
47
46
  end
48
47
  end
48
+ alias pop get
49
49
 
50
- def subscribe(options={}, &block)
51
- subscription = Subscription.new(@channel, @name, options)
52
- subscription.each(options, &block) if block
53
- subscription
54
- end
55
-
56
- def status
57
- response = @channel.queue_declare_passive(@name)
58
- [response.message_count, response.consumer_count]
59
- end
60
-
61
- private
62
-
63
- def declare!
64
- response = if @options[:passive]
65
- then @channel.queue_declare_passive(@name)
66
- else @channel.queue_declare(@name, @options[:durable], @options[:exclusive], @options[:auto_delete], @options[:arguments])
50
+ def subscribe(opts = {}, &block)
51
+ consumer = if opts[:block] || opts[:blocking]
52
+ BlockingCallbackConsumer.new(@channel, opts[:buffer_size], block)
53
+ else
54
+ AsyncCallbackConsumer.new(@channel, block, opts.fetch(:executor, JavaConcurrent::Executors.new_single_thread_executor))
67
55
  end
68
- @name = response.queue
69
- end
70
-
71
- class Subscription
72
- include JavaConcurrent
73
56
 
74
- attr_reader :channel, :queue_name, :consumer_tag
57
+ @consumer_tag = @channel.basic_consume(@name, !(opts[:ack] || opts[:manual_ack]), consumer)
58
+ consumer.consumer_tag = @consumer_tag
75
59
 
76
- def initialize(channel, queue_name, options={})
77
- @channel = channel
78
- @queue_name = queue_name
79
- @ack = options.fetch(:ack, false)
60
+ @default_consumer = consumer
61
+ @channel.register_consumer(@consumer_tag, consumer)
62
+ consumer.start
80
63
 
81
- @cancelled = AtomicBoolean.new(false)
82
- end
83
-
84
- def each(options={}, &block)
85
- raise 'The subscription already has a message listener' if @consumer
86
- start(create_consumer(options, block))
87
- nil
88
- end
89
- alias_method :each_message, :each
90
-
91
- def start(consumer)
92
- @consumer = consumer
93
- @consumer_tag = @channel.basic_consume(@queue_name, !@ack, @consumer)
94
- @consumer.start
95
- end
96
-
97
- def cancel
98
- raise 'Can\'t cancel: the subscriber haven\'t received an OK yet' if !self.active?
99
- @consumer.cancel
100
-
101
- # RabbitMQ Java client won't clear consumer_tag from cancelled consumers,
102
- # so we have to do this. Sharing consumers
103
- # between threads in general is a can of worms but someone somewhere
104
- # will almost certainly do it, so. MK.
105
- @cancelled.set(true)
106
-
107
- maybe_shutdown_executor
108
- end
109
-
110
- def cancelled?
111
- @cancelled.get
112
- end
113
-
114
- def active?
115
- !@cancelled.get && !@consumer.nil? && !@consumer.consumer_tag.nil?
116
- end
117
-
118
- def shutdown!
119
- if @executor && @shut_down_executor
120
- @executor.shutdown_now
121
- end
122
- end
123
- alias shut_down! shutdown!
124
-
125
- private
126
-
127
- def maybe_shutdown_executor
128
- if @executor && @shut_down_executor
129
- @executor.shutdown
130
- unless @executor.await_termination(1, TimeUnit::SECONDS)
131
- @executor.shutdown_now
132
- end
133
- end
134
- end
135
-
136
- def create_consumer(options, callback)
137
- if options.fetch(:blocking, true)
138
- BlockingCallbackConsumer.new(@channel, options[:buffer_size], callback)
139
- else
140
- if options[:executor]
141
- @shut_down_executor = false
142
- @executor = options[:executor]
143
- else
144
- @shut_down_executor = true
145
- @executor = Executors.new_single_thread_executor
146
- end
147
- AsyncCallbackConsumer.new(@channel, callback, @executor)
148
- end
149
- end
64
+ consumer
150
65
  end
151
66
 
152
- public
153
-
154
- class BaseConsumer < DefaultConsumer
155
- def handleDelivery(consumer_tag, envelope, properties, body)
156
- body = String.from_java_bytes(body)
157
- headers = Headers.new(channel, consumer_tag, envelope, properties)
158
- deliver(headers, body)
159
- end
160
-
161
- def handleCancel(consumer_tag)
162
- @cancelled = true
163
- end
164
-
165
- def handleCancelOk(consumer_tag)
166
- @cancelled = true
167
- end
168
-
169
- def start
170
- end
171
-
172
- def deliver(headers, message)
173
- raise NotImplementedError, 'To be implemented by a subclass'
174
- end
175
-
176
- def cancel
177
- channel.basic_cancel(consumer_tag)
178
- @cancelling = true
179
- end
67
+ def status
68
+ response = @channel.queue_declare_passive(@name)
69
+ [response.message_count, response.consumer_count]
180
70
  end
181
71
 
182
- private
183
-
184
- class CallbackConsumer < BaseConsumer
185
- def initialize(channel, callback)
186
- super(channel)
187
- @callback = callback
188
- @callback_arity = @callback.arity
189
- @cancelled = false
190
- @cancelling = false
191
- end
192
-
193
- def callback(headers, message)
194
- if @callback_arity == 2
195
- @callback.call(headers, message)
196
- else
197
- @callback.call(message)
198
- end
199
- end
72
+ def message_count
73
+ response = @channel.queue_declare_passive(@name)
74
+ response.message_count
200
75
  end
201
76
 
202
- class AsyncCallbackConsumer < CallbackConsumer
203
- def initialize(channel, callback, executor)
204
- super(channel, callback)
205
- @executor = executor
206
- @tasks = []
207
- end
208
-
209
- def deliver(headers, message)
210
- unless @executor.shutdown?
211
- @executor.submit do
212
- callback(headers, message)
213
- end
214
- end
215
- end
77
+ def consumer_count
78
+ response = @channel.queue_declare_passive(@name)
79
+ response.consumer_count
216
80
  end
217
81
 
218
- class BlockingCallbackConsumer < CallbackConsumer
219
- include JavaConcurrent
82
+ # Publishes a message to the queue via default exchange. Takes the same arguments
83
+ # as {Bunny::Exchange#publish}
84
+ #
85
+ # @see HotBunnies::Exchange#publish
86
+ # @see HotBunnies::Channel#default_exchange
87
+ def publish(payload, opts = {})
88
+ @channel.default_exchange.publish(payload, opts.merge(:routing_key => @name))
220
89
 
221
- def initialize(channel, buffer_size, callback)
222
- super(channel, callback)
223
- if buffer_size
224
- @internal_queue = ArrayBlockingQueue.new(buffer_size)
225
- else
226
- @internal_queue = LinkedBlockingQueue.new
227
- end
228
- end
229
-
230
- def start
231
- interrupted = false
232
- until @cancelled || JavaConcurrent::Thread.current_thread.interrupted?
233
- begin
234
- pair = @internal_queue.take
235
- callback(*pair) if pair
236
- rescue InterruptedException => e
237
- interrupted = true
238
- end
239
- end
240
- while (pair = @internal_queue.poll)
241
- callback(*pair)
242
- end
243
- if interrupted
244
- JavaConcurrent::Thread.current_thread.interrupt
245
- end
246
- end
247
-
248
- def deliver(*pair)
249
- if @cancelling || @cancelled || JavaConcurrent::Thread.current_thread.interrupted?
250
- @internal_queue.offer(pair)
251
- else
252
- begin
253
- @internal_queue.put(pair)
254
- rescue InterruptedException => e
255
- JavaConcurrent::Thread.current_thread.interrupt
256
- end
257
- end
258
- end
90
+ self
259
91
  end
260
92
 
261
- class Headers
262
- attr_reader :channel, :consumer_tag, :envelope, :properties
263
-
264
- def initialize(channel, consumer_tag, envelope, properties)
265
- @channel = channel
266
- @consumer_tag = consumer_tag
267
- @envelope = envelope
268
- @properties = properties
269
- end
270
-
271
- def ack(options={})
272
- @channel.basic_ack(delivery_tag, options.fetch(:multiple, false))
273
- end
274
-
275
- def reject(options={})
276
- @channel.basic_reject(delivery_tag, options.fetch(:requeue, false))
277
- end
278
93
 
279
- begin :envelope_delegation
280
- [
281
- :delivery_tag,
282
- :routing_key,
283
- :redeliver,
284
- :exchange
285
- ].each do |envelope_property|
286
- define_method(envelope_property) { @envelope.__send__(envelope_property) }
287
- end
94
+ #
95
+ # Implementation
96
+ #
288
97
 
289
- alias_method :redelivered?, :redeliver
290
- end
291
-
292
- begin :message_properties_delegation
293
- [
294
- :content_encoding,
295
- :content_type,
296
- :content_encoding,
297
- :headers,
298
- :delivery_mode,
299
- :priority,
300
- :correlation_id,
301
- :reply_to,
302
- :expiration,
303
- :message_id,
304
- :timestamp,
305
- :type,
306
- :user_id,
307
- :app_id,
308
- :cluster_id
309
- ].each do |properties_property|
310
- define_method(properties_property) { @properties.__send__(properties_property) }
311
- end
312
-
313
- def persistent?
314
- persistent == 2
315
- end
316
- end
98
+ def declare!
99
+ response = if @options[:passive]
100
+ then @channel.queue_declare_passive(@name)
101
+ else @channel.queue_declare(@name, @options[:durable], @options[:exclusive], @options[:auto_delete], @options[:arguments])
102
+ end
103
+ @name = response.queue
317
104
  end
318
- end
319
- end
105
+ end # Queue
106
+ end # HotBunnies
@@ -0,0 +1,170 @@
1
+ module HotBunnies
2
+ java_import com.rabbitmq.client.ConnectionFactory
3
+ java_import com.rabbitmq.client.Connection
4
+ java_import java.util.concurrent.ConcurrentHashMap
5
+
6
+ class Session
7
+
8
+ #
9
+ # API
10
+ #
11
+
12
+ def self.connect(options={})
13
+ cf = ConnectionFactory.new
14
+
15
+ cf.uri = options[:uri] if options[:uri]
16
+ cf.host = hostname_from(options) if include_host?(options)
17
+ cf.port = options[:port] if options[:port]
18
+ cf.virtual_host = vhost_from(options) if include_vhost?(options)
19
+ cf.connection_timeout = timeout_from(options) if include_timeout?(options)
20
+ cf.username = username_from(options) if include_username?(options)
21
+ cf.password = password_from(options) if include_password?(options)
22
+
23
+ cf.requested_heartbeat = heartbeat_from(options) if include_heartbeat?(options)
24
+ cf.connection_timeout = connection_timeout_from(options) if include_connection_timeout?(options)
25
+
26
+ tls = (options[:ssl] || options[:tls])
27
+ case tls
28
+ when true then
29
+ cf.use_ssl_protocol
30
+ when String then
31
+ if options[:trust_manager]
32
+ cf.use_ssl_protocol(tls, options[:trust_manager])
33
+ else
34
+ cf.use_ssl_protocol(tls)
35
+ end
36
+ end
37
+
38
+ new(cf)
39
+ end
40
+
41
+
42
+ def initialize(connection_factory)
43
+ @cf = connection_factory
44
+ @connection = self.new_connection
45
+ @channels = ConcurrentHashMap.new
46
+ end
47
+
48
+ def create_channel(n = nil)
49
+ jc = if n
50
+ @connection.create_channel(n)
51
+ else
52
+ @connection.create_channel
53
+ end
54
+
55
+ ch = Channel.new(self, jc)
56
+ register_channel(ch)
57
+
58
+ ch
59
+ end
60
+
61
+ def close
62
+ @channels.select { |_, ch| ch.open? }.each do |_, ch|
63
+ ch.close
64
+ end
65
+
66
+ @connection.close
67
+ end
68
+
69
+ def flush
70
+ @connection.flush
71
+ end
72
+
73
+ def heartbeat=(n)
74
+ @connection.heartbeat = n
75
+ end
76
+
77
+ def method_missing(selector, *args)
78
+ @connection.__send__(selector, *args)
79
+ end
80
+
81
+
82
+ #
83
+ # Implementation
84
+ #
85
+
86
+ def register_channel(ch)
87
+ @channels[ch.channel_number] = ch
88
+ end
89
+
90
+ def unregister_channel(ch)
91
+ @channels.delete(ch.channel_number)
92
+ end
93
+
94
+ protected
95
+
96
+ def self.hostname_from(options)
97
+ options[:host] || options[:hostname] || ConnectionFactory.DEFAULT_HOST
98
+ end
99
+
100
+ def self.include_host?(options)
101
+ !!(options[:host] || options[:hostname])
102
+ end
103
+
104
+ def self.vhost_from(options)
105
+ options[:virtual_host] || options[:vhost] || ConnectionFactory.DEFAULT_VHOST
106
+ end
107
+
108
+ def self.include_vhost?(options)
109
+ !!(options[:virtual_host] || options[:vhost])
110
+ end
111
+
112
+ def self.timeout_from(options)
113
+ options[:connection_timeout] || options[:timeout]
114
+ end
115
+
116
+ def self.include_timeout?(options)
117
+ !!(options[:connection_timeout] || options[:timeout])
118
+ end
119
+
120
+ def self.username_from(options)
121
+ options[:username] || options[:user] || ConnectionFactory.DEFAULT_USER
122
+ end
123
+
124
+ def self.heartbeat_from(options)
125
+ options[:heartbeat_interval] || options[:requested_heartbeat] || ConnectionFactory.DEFAULT_HEARTBEAT
126
+ end
127
+
128
+ def self.connection_timeout_from(options)
129
+ options[:connection_timeout_interval] || options[:connection_timeout] || ConnectionFactory.DEFAULT_CONNECTION_TIMEOUT
130
+ end
131
+
132
+ def self.include_username?(options)
133
+ !!(options[:username] || options[:user])
134
+ end
135
+
136
+ def self.password_from(options)
137
+ options[:password] || options[:pass] || ConnectionFactory.DEFAULT_PASS
138
+ end
139
+
140
+ def self.include_password?(options)
141
+ !!(options[:password] || options[:pass])
142
+ end
143
+
144
+ def self.include_heartbeat?(options)
145
+ !!(options[:heartbeat_interval] || options[:requested_heartbeat] || options[:heartbeat])
146
+ end
147
+
148
+ def self.include_connection_timeout?(options)
149
+ !!(options[:connection_timeout_interval] || options[:connection_timeout])
150
+ end
151
+
152
+ # Executes a block, catching Java exceptions RabbitMQ Java client throws and
153
+ # transforms them to Ruby exceptions that are then re-raised.
154
+ #
155
+ # @private
156
+ def converting_rjc_exceptions_to_ruby(&block)
157
+ begin
158
+ block.call
159
+ rescue com.rabbitmq.client.PossibleAuthenticationFailureException => e
160
+ raise PossibleAuthenticationFailureError.new(@cf.username, @cf.virtual_host, @cf.password.bytesize)
161
+ end
162
+ end
163
+
164
+ def new_connection
165
+ converting_rjc_exceptions_to_ruby do
166
+ @cf.new_connection
167
+ end
168
+ end
169
+ end
170
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module HotBunnies
4
- VERSION = '1.5.0'
4
+ VERSION = '2.0.0.pre1'
5
5
  end