message-driver 0.1.0 → 0.2.0.rc1

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.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +18 -7
  4. data/CHANGELOG.md +12 -2
  5. data/Gemfile +17 -0
  6. data/Guardfile +8 -4
  7. data/README.md +14 -5
  8. data/Rakefile +44 -11
  9. data/examples/basic_producer_and_consumer/Gemfile +5 -0
  10. data/examples/basic_producer_and_consumer/common.rb +17 -0
  11. data/examples/basic_producer_and_consumer/consumer.rb +24 -0
  12. data/examples/basic_producer_and_consumer/producer.rb +33 -0
  13. data/features/.nav +8 -0
  14. data/features/CHANGELOG.md +12 -2
  15. data/features/amqp_specific_features/binding_amqp_destinations.feature +7 -7
  16. data/features/amqp_specific_features/declaring_amqp_exchanges.feature +3 -3
  17. data/features/amqp_specific_features/nack_redelivered_messages.feature +92 -0
  18. data/features/amqp_specific_features/requeueing_on_nack.feature +44 -0
  19. data/features/amqp_specific_features/server_named_destinations.feature +5 -5
  20. data/features/client_acks.feature +92 -0
  21. data/features/destination_metadata.feature +9 -11
  22. data/features/dynamic_destinations.feature +7 -7
  23. data/features/error_handling.feature +11 -9
  24. data/features/logging.feature +14 -0
  25. data/features/message_consumers/auto_ack_consumers.feature +79 -0
  26. data/features/message_consumers/manual_ack_consumers.feature +95 -0
  27. data/features/message_consumers/transactional_ack_consumers.feature +77 -0
  28. data/features/message_consumers.feature +54 -0
  29. data/features/publishing_a_message.feature +6 -10
  30. data/features/publishing_with_transactions.feature +10 -14
  31. data/features/rabbitmq_specific_features/dead_letter_queueing.feature +116 -0
  32. data/features/step_definitions/dynamic_destinations_steps.rb +3 -3
  33. data/features/step_definitions/error_handling_steps.rb +4 -2
  34. data/features/step_definitions/logging_steps.rb +28 -0
  35. data/features/step_definitions/message_consumers_steps.rb +29 -0
  36. data/features/step_definitions/steps.rb +60 -9
  37. data/features/support/broker_config_helper.rb +19 -0
  38. data/features/support/env.rb +1 -0
  39. data/features/support/firewall_helper.rb +8 -11
  40. data/features/support/message_table_matcher.rb +21 -5
  41. data/features/support/test_runner.rb +39 -16
  42. data/lib/message_driver/adapters/base.rb +51 -4
  43. data/lib/message_driver/adapters/bunny_adapter.rb +251 -127
  44. data/lib/message_driver/adapters/in_memory_adapter.rb +97 -18
  45. data/lib/message_driver/adapters/stomp_adapter.rb +127 -0
  46. data/lib/message_driver/broker.rb +23 -24
  47. data/lib/message_driver/client.rb +157 -0
  48. data/lib/message_driver/destination.rb +7 -4
  49. data/lib/message_driver/errors.rb +27 -0
  50. data/lib/message_driver/logging.rb +11 -0
  51. data/lib/message_driver/message.rb +8 -0
  52. data/lib/message_driver/subscription.rb +18 -0
  53. data/lib/message_driver/vendor/.document +0 -0
  54. data/lib/message_driver/vendor/nesty/nested_error.rb +26 -0
  55. data/lib/message_driver/vendor/nesty.rb +1 -0
  56. data/lib/message_driver/version.rb +1 -1
  57. data/lib/message_driver.rb +4 -2
  58. data/message-driver.gemspec +4 -4
  59. data/spec/integration/{amqp_integration_spec.rb → bunny/amqp_integration_spec.rb} +29 -28
  60. data/spec/integration/bunny/bunny_adapter_spec.rb +339 -0
  61. data/spec/integration/in_memory/in_memory_adapter_spec.rb +126 -0
  62. data/spec/integration/stomp/stomp_adapter_spec.rb +142 -0
  63. data/spec/spec_helper.rb +5 -2
  64. data/spec/support/shared/adapter_examples.rb +17 -0
  65. data/spec/support/shared/client_ack_examples.rb +18 -0
  66. data/spec/support/shared/context_examples.rb +14 -0
  67. data/spec/support/shared/destination_examples.rb +4 -5
  68. data/spec/support/shared/subscription_examples.rb +146 -0
  69. data/spec/support/shared/transaction_examples.rb +43 -0
  70. data/spec/support/utils.rb +14 -0
  71. data/spec/units/message_driver/adapters/base_spec.rb +38 -19
  72. data/spec/units/message_driver/broker_spec.rb +71 -18
  73. data/spec/units/message_driver/client_spec.rb +375 -0
  74. data/spec/units/message_driver/destination_spec.rb +9 -0
  75. data/spec/units/message_driver/logging_spec.rb +18 -0
  76. data/spec/units/message_driver/message_spec.rb +36 -0
  77. data/spec/units/message_driver/subscription_spec.rb +24 -0
  78. data/test_lib/broker_config.rb +50 -20
  79. metadata +83 -45
  80. data/.rbenv-version +0 -1
  81. data/lib/message_driver/exceptions.rb +0 -18
  82. data/lib/message_driver/message_publisher.rb +0 -15
  83. data/spec/integration/message_driver/adapters/bunny_adapter_spec.rb +0 -301
  84. data/spec/units/message_driver/adapters/in_memory_adapter_spec.rb +0 -43
  85. data/spec/units/message_driver/message_publisher_spec.rb +0 -65
@@ -9,6 +9,7 @@ module MessageDriver
9
9
 
10
10
  module Adapters
11
11
  class BunnyAdapter < Base
12
+ NETWORK_ERRORS = [Bunny::TCPConnectionFailed, Bunny::ConnectionClosedError, Bunny::ConnectionLevelException, Bunny::NetworkErrorWrapper, Bunny::NetworkFailure, IOError].freeze
12
13
 
13
14
  class Message < MessageDriver::Message::Base
14
15
  attr_reader :delivery_info
@@ -17,13 +18,21 @@ module MessageDriver
17
18
  super(payload, properties[:headers]||{}, properties)
18
19
  @delivery_info = delivery_info
19
20
  end
21
+
22
+ def delivery_tag
23
+ delivery_info.delivery_tag
24
+ end
25
+
26
+ def redelivered?
27
+ delivery_info.redelivered?
28
+ end
20
29
  end
21
30
 
22
31
  class Destination < MessageDriver::Destination::Base
23
- def publish(body, headers={}, properties={})
32
+ def publish_params(headers, properties)
24
33
  props = @message_props.merge(properties)
25
34
  props[:headers] = headers if headers
26
- @adapter.publish(body, exchange_name, routing_key(properties), props)
35
+ [exchange_name, routing_key(properties), props]
27
36
  end
28
37
 
29
38
  def exchange_name
@@ -36,24 +45,31 @@ module MessageDriver
36
45
  end
37
46
 
38
47
  class QueueDestination < Destination
39
- def after_initialize
48
+ def after_initialize(adapter_context)
40
49
  unless @dest_options[:no_declare]
41
- @adapter.current_context.with_channel(false) do |ch|
42
- queue = ch.queue(@name, @dest_options)
43
- @name = queue.name
44
- if bindings = @dest_options[:bindings]
45
- bindings.each do |bnd|
46
- raise MessageDriver::Exception, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
47
- queue.bind(bnd[:source], bnd[:args]||{})
48
- end
49
- end
50
+ adapter_context.with_channel(false) do |ch|
51
+ bunny_queue(ch, true)
50
52
  end
51
53
  else
52
- raise MessageDriver::Exception, "server-named queues must be declared, but you provided :no_declare => true" if @name.empty?
53
- raise MessageDriver::Exception, "queues with bindings must be declared, but you provided :no_declare => true" if @dest_options[:bindings]
54
+ raise MessageDriver::Error, "server-named queues must be declared, but you provided :no_declare => true" if @name.empty?
55
+ raise MessageDriver::Error, "queues with bindings must be declared, but you provided :no_declare => true" if @dest_options[:bindings]
54
56
  end
55
57
  end
56
58
 
59
+ def bunny_queue(channel, initialize=false)
60
+ queue = channel.queue(@name, @dest_options)
61
+ if initialize
62
+ @name = queue.name
63
+ if bindings = @dest_options[:bindings]
64
+ bindings.each do |bnd|
65
+ raise MessageDriver::Error, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
66
+ queue.bind(bnd[:source], bnd[:args]||{})
67
+ end
68
+ end
69
+ end
70
+ queue
71
+ end
72
+
57
73
  def exchange_name
58
74
  ""
59
75
  end
@@ -63,29 +79,31 @@ module MessageDriver
63
79
  end
64
80
 
65
81
  def message_count
66
- @adapter.current_context.with_channel(false) do |ch|
82
+ Client.current_adapter_context.with_channel(false) do |ch|
67
83
  ch.queue(@name, @dest_options.merge(passive: true)).message_count
68
84
  end
69
85
  end
70
- end
71
86
 
72
- class ExchangeDestination < Destination
73
- def pop_message(destination, options={})
74
- raise MessageDriver::Exception, "You can't pop a message off an exchange"
87
+ def purge
88
+ Client.current_adapter_context.with_channel(false) do |ch|
89
+ bunny_queue(ch).purge
90
+ end
75
91
  end
92
+ end
76
93
 
77
- def after_initialize
94
+ class ExchangeDestination < Destination
95
+ def after_initialize(adapter_context)
78
96
  if declare = @dest_options[:declare]
79
- @adapter.current_context.with_channel(false) do |ch|
97
+ adapter_context.with_channel(false) do |ch|
80
98
  type = declare.delete(:type)
81
- raise MessageDriver::Exception, "you must provide a valid exchange type" unless type
99
+ raise MessageDriver::Error, "you must provide a valid exchange type" unless type
82
100
  ch.exchange_declare(@name, type, declare)
83
101
  end
84
102
  end
85
103
  if bindings = @dest_options[:bindings]
86
- @adapter.current_context.with_channel(false) do |ch|
104
+ adapter_context.with_channel(false) do |ch|
87
105
  bindings.each do |bnd|
88
- raise MessageDriver::Exception, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
106
+ raise MessageDriver::Error, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
89
107
  ch.exchange_bind(bnd[:source], @name, bnd[:args]||{})
90
108
  end
91
109
  end
@@ -93,139 +111,261 @@ module MessageDriver
93
111
  end
94
112
  end
95
113
 
114
+ class Subscription < Subscription::Base
115
+ def start
116
+ raise MessageDriver::Error, "subscriptions are only supported with QueueDestinations" unless destination.is_a? QueueDestination
117
+ @sub_ctx = adapter.new_subscription_context(self)
118
+ @error_handler = options[:error_handler]
119
+ @sub_ctx.with_channel do |ch|
120
+ queue = destination.bunny_queue(@sub_ctx.channel)
121
+ ack_mode = case options[:ack]
122
+ when :auto, nil
123
+ :auto
124
+ when :manual
125
+ :manual
126
+ when :transactional
127
+ :transactional
128
+ else
129
+ raise MessageDriver::Error, "unrecognized :ack option #{options[:ack]}"
130
+ end
131
+ @bunny_consumer = queue.subscribe(options.merge(manual_ack: true)) do |delivery_info, properties, payload|
132
+ message = @sub_ctx.args_to_message(delivery_info, properties, payload)
133
+ Client.with_adapter_context(@sub_ctx) do
134
+ begin
135
+ case ack_mode
136
+ when :auto
137
+ consumer.call(message)
138
+ @sub_ctx.ack_message(message)
139
+ when :manual
140
+ consumer.call(message)
141
+ when :transactional
142
+ Client.with_message_transaction do
143
+ consumer.call(message)
144
+ @sub_ctx.ack_message(message)
145
+ end
146
+ end
147
+ rescue => e
148
+ if e.is_a?(DontRequeue) || (options[:retry_redelivered] == false && message.redelivered?)
149
+ if [:auto, :transactional].include? ack_mode
150
+ @sub_ctx.nack_message(message, requeue: false)
151
+ end
152
+ else
153
+ if @sub_ctx.valid? && ack_mode == :auto
154
+ begin
155
+ @sub_ctx.nack_message(message, requeue: true)
156
+ rescue => e
157
+ logger.error exception_to_str(e)
158
+ end
159
+ end
160
+ @error_handler.call(e, message) unless @error_handler.nil?
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ def unsubscribe
169
+ unless @bunny_consumer.nil?
170
+ @bunny_consumer.cancel
171
+ @bunny_consumer = nil
172
+ end
173
+ unless @sub_ctx.nil?
174
+ @sub_ctx.invalidate(true)
175
+ @sub_ctx = nil
176
+ end
177
+ end
178
+ end
179
+
96
180
  def initialize(config)
97
181
  validate_bunny_version
98
-
99
- @connection = Bunny.new(config.merge(threaded: false))
182
+ @config = config
100
183
  end
101
184
 
102
185
  def connection(ensure_started=true)
186
+ @connection ||= Bunny.new(@config)
103
187
  if ensure_started && !@connection.open?
104
- @connection.start
188
+ begin
189
+ @connection.start
190
+ rescue *NETWORK_ERRORS => e
191
+ raise MessageDriver::ConnectionError.new(e.to_s, e)
192
+ end
105
193
  end
106
194
  @connection
107
195
  end
108
196
 
109
- def publish(body, exchange, routing_key, properties)
110
- current_context.with_channel(true) do |ch|
111
- ch.basic_publish(body, exchange, routing_key, properties)
197
+ def stop
198
+ begin
199
+ super
200
+ @connection.close if !@connection.nil? && @connection.open?
201
+ rescue *NETWORK_ERRORS => e
202
+ logger.error "error while attempting connection close\n#{exception_to_str(e)}"
112
203
  end
113
204
  end
114
205
 
115
- def pop_message(destination, options={})
116
- current_context.with_channel do |ch|
117
- queue = ch.queue(destination, passive: true)
206
+ def build_context
207
+ BunnyContext.new(self)
208
+ end
209
+
210
+ def new_subscription_context(subscription)
211
+ ctx = new_context
212
+ ctx.channel = connection.create_channel
213
+ ctx.subscription = subscription
214
+ ctx
215
+ end
216
+
217
+ class BunnyContext < ContextBase
218
+ attr_accessor :channel, :subscription
118
219
 
119
- message = queue.pop
120
- if message.nil? || message[0].nil?
121
- nil
220
+ def initialize(adapter)
221
+ super(adapter)
222
+ @is_transactional = false
223
+ @rollback_only = false
224
+ @need_channel_reset = false
225
+ @in_transaction = false
226
+ end
227
+
228
+ def create_destination(name, dest_options={}, message_props={})
229
+ dest = case type = dest_options.delete(:type)
230
+ when :exchange
231
+ ExchangeDestination.new(self.adapter, name, dest_options, message_props)
232
+ when :queue, nil
233
+ QueueDestination.new(self.adapter, name, dest_options, message_props)
122
234
  else
123
- Message.new(*message)
235
+ raise MessageDriver::Error, "invalid destination type #{type}"
124
236
  end
237
+ dest.after_initialize(self)
238
+ dest
125
239
  end
126
- end
127
240
 
128
- def create_destination(name, dest_options={}, message_props={})
129
- case type = dest_options.delete(:type)
130
- when :exchange
131
- ExchangeDestination.new(self, name, dest_options, message_props)
132
- when :queue, nil
133
- QueueDestination.new(self, name, dest_options, message_props)
134
- else
135
- raise MessageDriver::Exception, "invalid destination type #{type}"
241
+ def supports_transactions?
242
+ true
136
243
  end
137
- end
138
244
 
139
- def with_transaction(options={}, &block)
140
- current_context.with_transaction(&block)
141
- end
245
+ def begin_transaction(options={})
246
+ raise MessageDriver::TransactionError, "you can't begin another transaction, you are already in one!" if in_transaction?
247
+ unless is_transactional?
248
+ with_channel(false) do |ch|
249
+ ch.tx_select
250
+ end
251
+ @is_transactional = true
252
+ end
253
+ @in_transaction = true
254
+ end
142
255
 
143
- def stop
144
- @connection.close if @connection.open?
145
- @context = nil
146
- end
256
+ def commit_transaction(channel_commit=false)
257
+ raise MessageDriver::TransactionError, "you can't finish the transaction unless you already in one!" if !in_transaction? && !channel_commit
258
+ begin
259
+ if is_transactional? && valid? && !@need_channel_reset
260
+ if @rollback_only
261
+ @channel.tx_rollback
262
+ else
263
+ @channel.tx_commit
264
+ end
265
+ end
266
+ ensure
267
+ @rollback_only = false
268
+ @in_transaction = false
269
+ end
270
+ end
147
271
 
148
- def current_context
149
- if !@context.nil? && @context.need_new_context?
150
- @context = nil
272
+ def rollback_transaction
273
+ @rollback_only = true
274
+ commit_transaction
151
275
  end
152
- @context ||= ChannelContext.new(connection)
153
- end
154
276
 
155
- private
277
+ def is_transactional?
278
+ @is_transactional
279
+ end
156
280
 
157
- class ChannelContext
158
- attr_reader :connection, :transaction_depth
281
+ def in_transaction?
282
+ @in_transaction
283
+ end
159
284
 
160
- def initialize(connection)
161
- @connection = connection
162
- @channel = connection.create_channel
163
- @transaction_depth = 0
164
- @is_transactional = false
165
- @rollback_only = false
166
- @need_channel_reset = false
167
- @connection_failed = false
285
+ def publish(destination, body, headers={}, properties={})
286
+ exchange, routing_key, props = *destination.publish_params(headers, properties)
287
+ with_channel(true) do |ch|
288
+ ch.basic_publish(body, exchange, routing_key, props)
289
+ end
168
290
  end
169
291
 
170
- def is_transactional?
171
- @is_transactional
292
+ def pop_message(destination, options={})
293
+ raise MessageDriver::Error, "You can't pop a message off an exchange" if destination.is_a? ExchangeDestination
294
+
295
+ with_channel(false) do |ch|
296
+ queue = ch.queue(destination.name, passive: true)
297
+
298
+ message = queue.pop(ack: !!options[:client_ack])
299
+ if message.nil? || message[0].nil?
300
+ nil
301
+ else
302
+ args_to_message(*message)
303
+ end
304
+ end
172
305
  end
173
306
 
174
- def connection_failed?
175
- @connection_failed
307
+ def supports_client_acks?
308
+ true
176
309
  end
177
310
 
178
- def with_transaction(&block)
179
- if !is_transactional?
180
- @channel.tx_select
181
- @is_transactional = true
311
+ def ack_message(message, options={})
312
+ with_channel(true) do |ch|
313
+ ch.ack(message.delivery_tag)
182
314
  end
315
+ end
183
316
 
184
- begin
185
- @transaction_depth += 1
186
- yield
187
- commit_transaction
188
- rescue
189
- rollback_transaction
190
- raise
191
- ensure
192
- @transaction_depth -= 1
317
+ def nack_message(message, options={})
318
+ requeue = options[:requeue].kind_of?(FalseClass) ? false : true
319
+ with_channel(true) do |ch|
320
+ ch.reject(message.delivery_tag, requeue)
321
+ end
322
+ end
323
+
324
+ def supports_subscriptions?
325
+ true
326
+ end
327
+
328
+ def subscribe(destination, options={}, &consumer)
329
+ sub = Subscription.new(adapter, destination, consumer, options)
330
+ sub.start
331
+ sub
332
+ end
333
+
334
+ def invalidate(in_unsubscribe=false)
335
+ super()
336
+ unless @subscription.nil? || in_unsubscribe
337
+ @subscription.unsubscribe
338
+ end
339
+ unless @channel.nil?
340
+ @channel.close if @channel.open?
193
341
  end
194
342
  end
195
343
 
196
344
  def with_channel(require_commit=true)
197
345
  raise MessageDriver::TransactionRollbackOnly if @rollback_only
198
- raise MessageDriver::Exception, "oh shit!" if @connection_failed
346
+ raise MessageDriver::Error, "oh nos!" if !valid?
347
+ @channel = adapter.connection.create_channel if @channel.nil?
199
348
  reset_channel if @need_channel_reset
200
349
  begin
201
350
  result = yield @channel
202
- commit_transaction(true) if require_commit
351
+ commit_transaction(true) if require_commit && is_transactional? && !in_transaction?
203
352
  result
204
353
  rescue Bunny::ChannelLevelException => e
205
354
  @need_channel_reset = true
206
- @rollback_only = true if is_transactional?
355
+ @rollback_only = true if in_transaction?
207
356
  if e.kind_of? Bunny::NotFound
208
- raise MessageDriver::QueueNotFound.new(e)
357
+ raise MessageDriver::QueueNotFound.new(e.to_s, e)
209
358
  else
210
- raise MessageDriver::WrappedException.new(e)
359
+ raise MessageDriver::WrappedError.new(e.to_s, e)
211
360
  end
212
- rescue Bunny::NetworkErrorWrapper, IOError => e
213
- @connection_failed = true
214
- @rollback_only = true if is_transactional?
215
- raise MessageDriver::ConnectionException.new(e)
361
+ rescue *NETWORK_ERRORS => e
362
+ @rollback_only = true if in_transaction?
363
+ raise MessageDriver::ConnectionError.new(e.to_s, e)
216
364
  end
217
365
  end
218
366
 
219
- def within_transaction?
220
- @transaction_depth > 0
221
- end
222
-
223
- def need_new_context?
224
- if is_transactional?
225
- !within_transaction? && connection_failed?
226
- else
227
- connection_failed?
228
- end
367
+ def args_to_message(delivery_info, properties, payload)
368
+ Message.new(delivery_info, properties, payload)
229
369
  end
230
370
 
231
371
  private
@@ -234,35 +374,19 @@ module MessageDriver
234
374
  unless @channel.open?
235
375
  @channel.open
236
376
  @is_transactional = false
377
+ @rollback_only = true if in_transaction?
237
378
  end
238
379
  @need_channel_reset = false
239
380
  end
240
-
241
- def commit_transaction(from_channel=false)
242
- threshold = from_channel ? 0 : 1
243
- if is_transactional? && @transaction_depth <= threshold && !connection_failed?
244
- unless @need_channel_reset
245
- unless @rollback_only
246
- @channel.tx_commit
247
- else
248
- @channel.tx_rollback
249
- end
250
- end
251
- @rollback_only = false
252
- end
253
- end
254
-
255
- def rollback_transaction
256
- @rollback_only = true
257
- commit_transaction
258
- end
259
381
  end
260
382
 
383
+ private
384
+
261
385
  def validate_bunny_version
262
- required = Gem::Requirement.create('~> 0.9.0.pre7')
386
+ required = Gem::Requirement.create('>= 0.9.3')
263
387
  current = Gem::Version.create(Bunny::VERSION)
264
388
  unless required.satisfied_by? current
265
- raise MessageDriver::Exception, "bunny 0.9.0.pre7 or later is required for the bunny adapter"
389
+ raise MessageDriver::Error, "bunny 0.9.3 or later is required for the bunny adapter"
266
390
  end
267
391
  end
268
392
  end
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module MessageDriver
2
4
  class Broker
3
5
  def in_memory_adapter
@@ -9,49 +11,126 @@ module MessageDriver
9
11
  class InMemoryAdapter < Base
10
12
 
11
13
  class Message < MessageDriver::Message::Base
14
+ end
12
15
 
16
+ class Subscription < MessageDriver::Subscription::Base
17
+ def unsubscribe
18
+ adapter.remove_subscription_for(destination.name)
19
+ end
20
+
21
+ def deliver_message(message)
22
+ begin
23
+ consumer.call(message)
24
+ rescue => e
25
+ unless options[:error_handler].nil?
26
+ options[:error_handler].call(e, message)
27
+ end
28
+ end
29
+ end
13
30
  end
14
31
 
15
32
  class Destination < MessageDriver::Destination::Base
16
- def initialize(adapter, name, dest_options, message_props, message_store)
17
- super(adapter, name, dest_options, message_props)
18
- @message_store = message_store
33
+
34
+ def subscription
35
+ adapter.subscription_for(name)
19
36
  end
37
+
20
38
  def message_count
21
- @message_store[@name].size
39
+ message_queue.size
40
+ end
41
+
42
+ def pop_message(options={})
43
+ message_queue.shift
44
+ end
45
+
46
+ def subscribe(options={}, &consumer)
47
+ subscription = Subscription.new(adapter, self, consumer, options)
48
+ adapter.set_subscription_for(name, subscription)
49
+ until (msg = pop_message).nil?
50
+ subscription.deliver_message(msg)
51
+ end
52
+ subscription
53
+ end
54
+
55
+ def publish(body, headers={}, properties={})
56
+ msg = Message.new(body, headers, properties)
57
+ sub = subscription
58
+ if sub.nil?
59
+ message_queue << msg
60
+ else
61
+ sub.deliver_message(msg)
62
+ end
63
+ end
64
+
65
+ private
66
+ def message_queue
67
+ adapter.message_queue_for(name)
22
68
  end
23
69
  end
24
70
 
25
71
  def initialize(config={})
26
- #does nothing
72
+ @destinations = {}
73
+ @message_store = Hash.new { |h,k| h[k] = [] }
74
+ @subscriptions = Hash.new
27
75
  end
28
76
 
29
- def publish(destination, body, headers={}, properties={})
30
- message_store[destination] << Message.new(body, headers, properties)
77
+ def build_context
78
+ InMemoryContext.new(self)
31
79
  end
32
80
 
33
- def pop_message(destination, options={})
34
- message_store[destination].shift
81
+ def create_destination(name, dest_options={}, message_props={})
82
+ destination = Destination.new(self, name, dest_options, message_props)
83
+ @destinations[name] = destination
35
84
  end
36
85
 
37
- def stop
38
- message_store.clear
86
+ class InMemoryContext < ContextBase
87
+ extend Forwardable
88
+
89
+ def_delegators :adapter, :create_destination
90
+
91
+ def publish(destination, body, headers={}, properties={})
92
+ destination.publish(body, headers, properties)
93
+ end
94
+
95
+ def pop_message(destination, options={})
96
+ destination.pop_message(options)
97
+ end
98
+
99
+ def subscribe(destination, options={}, &consumer)
100
+ destination.subscribe(options, &consumer)
101
+ end
102
+
103
+ def supports_subscriptions?
104
+ true
105
+ end
39
106
  end
40
107
 
41
- def create_destination(name, dest_options={}, message_props={})
42
- Destination.new(self, name, dest_options, message_props, message_store)
108
+ def stop
109
+ super
110
+ reset_after_tests
43
111
  end
44
112
 
45
113
  def reset_after_tests
46
- message_store.each do |destination, message_array|
47
- message_array.replace([])
114
+ @message_store.keys.each do |k|
115
+ @message_store[k] = []
48
116
  end
117
+ @subscriptions.clear
118
+ end
119
+
120
+ def message_queue_for(name)
121
+ @message_store[name]
122
+ end
123
+
124
+ def subscription_for(name)
125
+ @subscriptions[name]
49
126
  end
50
127
 
51
- private
128
+ def set_subscription_for(name, subscription)
129
+ @subscriptions[name] = subscription
130
+ end
52
131
 
53
- def message_store
54
- @message_store ||= Hash.new { |h,k| h[k] = [] }
132
+ def remove_subscription_for(name)
133
+ @subscriptions.delete(name)
55
134
  end
56
135
  end
57
136
  end