message-driver 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.rubocop.yml +26 -2
  4. data/.rubocop_todo.yml +15 -123
  5. data/.travis.yml +2 -1
  6. data/CHANGELOG.md +10 -1
  7. data/Gemfile +15 -6
  8. data/Rakefile +23 -12
  9. data/ci/reset_vhost +8 -3
  10. data/ci/travis_setup +0 -3
  11. data/features/.nav +6 -1
  12. data/features/CHANGELOG.md +10 -1
  13. data/features/amqp_specific_features/binding_amqp_destinations.feature +1 -0
  14. data/features/amqp_specific_features/declaring_amqp_exchanges.feature +1 -0
  15. data/features/amqp_specific_features/server_named_destinations.feature +1 -0
  16. data/features/destination_metadata.feature +26 -0
  17. data/features/logging.feature +1 -1
  18. data/features/middleware/middleware_basics.feature +91 -0
  19. data/features/middleware/middleware_ordering.feature +60 -0
  20. data/features/middleware/middleware_parameters.feature +43 -0
  21. data/features/middleware/middleware_with_blocks.feature +85 -0
  22. data/features/step_definitions/dynamic_destinations_steps.rb +1 -1
  23. data/features/step_definitions/message_consumers_steps.rb +5 -0
  24. data/features/step_definitions/middleware_steps.rb +10 -0
  25. data/features/step_definitions/steps.rb +10 -2
  26. data/features/support/env.rb +4 -3
  27. data/features/support/firewall_helper.rb +1 -1
  28. data/features/support/message_table_matcher.rb +3 -2
  29. data/features/support/no_error_matcher.rb +2 -2
  30. data/features/support/test_runner.rb +11 -57
  31. data/features/support/transforms.rb +12 -10
  32. data/lib/message_driver.rb +3 -1
  33. data/lib/message_driver/adapters/base.rb +11 -11
  34. data/lib/message_driver/adapters/bunny_adapter.rb +189 -132
  35. data/lib/message_driver/adapters/in_memory_adapter.rb +51 -34
  36. data/lib/message_driver/adapters/stomp_adapter.rb +22 -23
  37. data/lib/message_driver/broker.rb +21 -16
  38. data/lib/message_driver/client.rb +15 -16
  39. data/lib/message_driver/destination.rb +26 -8
  40. data/lib/message_driver/message.rb +5 -4
  41. data/lib/message_driver/middleware.rb +8 -0
  42. data/lib/message_driver/middleware/base.rb +19 -0
  43. data/lib/message_driver/middleware/block_middleware.rb +33 -0
  44. data/lib/message_driver/middleware/middleware_stack.rb +61 -0
  45. data/lib/message_driver/subscription.rb +2 -2
  46. data/lib/message_driver/version.rb +1 -1
  47. data/message-driver.gemspec +3 -4
  48. data/spec/integration/bunny/amqp_integration_spec.rb +21 -82
  49. data/spec/integration/bunny/bunny_adapter_spec.rb +288 -268
  50. data/spec/integration/in_memory/in_memory_adapter_spec.rb +93 -90
  51. data/spec/integration/stomp/stomp_adapter_spec.rb +126 -93
  52. data/spec/spec_helper.rb +11 -9
  53. data/spec/support/shared/adapter_examples.rb +1 -1
  54. data/spec/support/shared/client_ack_examples.rb +4 -4
  55. data/spec/support/shared/context_examples.rb +6 -4
  56. data/spec/support/shared/destination_examples.rb +54 -14
  57. data/spec/support/shared/subscription_examples.rb +33 -26
  58. data/spec/support/shared/transaction_examples.rb +12 -12
  59. data/spec/support/utils.rb +1 -1
  60. data/spec/units/message_driver/adapters/base_spec.rb +42 -40
  61. data/spec/units/message_driver/broker_spec.rb +38 -38
  62. data/spec/units/message_driver/client_spec.rb +87 -87
  63. data/spec/units/message_driver/destination_spec.rb +16 -11
  64. data/spec/units/message_driver/message_spec.rb +96 -70
  65. data/spec/units/message_driver/middleware/base_spec.rb +30 -0
  66. data/spec/units/message_driver/middleware/block_middleware_spec.rb +82 -0
  67. data/spec/units/message_driver/middleware/middleware_stack_spec.rb +165 -0
  68. data/spec/units/message_driver/subscription_spec.rb +18 -16
  69. data/test_lib/broker_config.rb +21 -5
  70. data/test_lib/coverage.rb +11 -0
  71. data/test_lib/provider/base.rb +59 -0
  72. data/test_lib/provider/in_memory.rb +6 -0
  73. data/test_lib/provider/rabbitmq.rb +67 -0
  74. metadata +46 -35
@@ -14,16 +14,14 @@ module MessageDriver
14
14
 
15
15
  class Subscription < MessageDriver::Subscription::Base
16
16
  def unsubscribe
17
- adapter.remove_subscription_for(destination.name)
17
+ adapter.remove_subscription_for(destination.name, self)
18
18
  end
19
19
 
20
20
  def deliver_message(message)
21
- begin
22
- consumer.call(message)
23
- rescue => e
24
- unless options[:error_handler].nil?
25
- options[:error_handler].call(e, message)
26
- end
21
+ consumer.call(message)
22
+ rescue => e
23
+ unless options[:error_handler].nil?
24
+ options[:error_handler].call(e, message)
27
25
  end
28
26
  end
29
27
  end
@@ -37,48 +35,61 @@ module MessageDriver
37
35
  message_queue.size
38
36
  end
39
37
 
40
- def pop_message(_options={})
41
- message_queue.shift
38
+ def consumer_count
39
+ adapter.consumer_count_for(name)
42
40
  end
43
41
 
44
- def subscribe(options={}, &consumer)
45
- subscription = Subscription.new(adapter, self, consumer, options)
46
- adapter.set_subscription_for(name, subscription)
47
- until (msg = pop_message).nil?
48
- subscription.deliver_message(msg)
42
+ def pop_message(_options = {})
43
+ message = message_queue.shift
44
+ if message.nil?
45
+ nil
46
+ else
47
+ raw_body = message.body
48
+ b, h, p = middleware.on_consume(message.body, message.headers, message.properties)
49
+ Message.new(nil, b, h, p, raw_body)
49
50
  end
51
+ end
52
+
53
+ def subscribe(options = {}, &consumer)
54
+ subscription = Subscription.new(adapter, self, consumer, options)
55
+ adapter.add_subscription_for(name, subscription)
56
+ deliver_messages(subscription)
50
57
  subscription
51
58
  end
52
59
 
53
- def publish(body, headers={}, properties={})
54
- msg = Message.new(nil, body, headers, properties)
55
- sub = subscription
56
- if sub.nil?
57
- message_queue << msg
58
- else
59
- sub.deliver_message(msg)
60
- end
60
+ def publish(body, headers = {}, properties = {})
61
+ raw_body = body
62
+ b, h, p = middleware.on_publish(body, headers, properties)
63
+ msg = Message.new(nil, b, h, p, raw_body)
64
+ message_queue << msg
65
+ deliver_messages(subscription) if subscription
61
66
  end
62
67
 
63
68
  private
64
69
 
70
+ def deliver_messages(sub)
71
+ until (msg = pop_message).nil?
72
+ sub.deliver_message(msg)
73
+ end
74
+ end
75
+
65
76
  def message_queue
66
77
  adapter.message_queue_for(name)
67
78
  end
68
79
  end
69
80
 
70
- def initialize(broker, _config={})
81
+ def initialize(broker, _config = {})
71
82
  @broker = broker
72
83
  @destinations = {}
73
- @message_store = Hash.new { |h,k| h[k] = [] }
74
- @subscriptions = {}
84
+ @message_store = Hash.new { |h, k| h[k] = [] }
85
+ @subscriptions = Hash.new { |h, k| h[k] = [] }
75
86
  end
76
87
 
77
88
  def build_context
78
89
  InMemoryContext.new(self)
79
90
  end
80
91
 
81
- def create_destination(name, dest_options={}, message_props={})
92
+ def create_destination(name, dest_options = {}, message_props = {})
82
93
  destination = Destination.new(self, name, dest_options, message_props)
83
94
  @destinations[name] = destination
84
95
  end
@@ -88,15 +99,15 @@ module MessageDriver
88
99
 
89
100
  def_delegators :adapter, :create_destination
90
101
 
91
- def publish(destination, body, headers={}, properties={})
102
+ def publish(destination, body, headers = {}, properties = {})
92
103
  destination.publish(body, headers, properties)
93
104
  end
94
105
 
95
- def pop_message(destination, options={})
106
+ def pop_message(destination, options = {})
96
107
  destination.pop_message(options)
97
108
  end
98
109
 
99
- def subscribe(destination, options={}, &consumer)
110
+ def subscribe(destination, options = {}, &consumer)
100
111
  destination.subscribe(options, &consumer)
101
112
  end
102
113
 
@@ -122,15 +133,21 @@ module MessageDriver
122
133
  end
123
134
 
124
135
  def subscription_for(name)
125
- @subscriptions[name]
136
+ sub = @subscriptions[name].shift
137
+ @subscriptions[name].push sub
138
+ sub
139
+ end
140
+
141
+ def add_subscription_for(name, subscription)
142
+ @subscriptions[name].push subscription
126
143
  end
127
144
 
128
- def set_subscription_for(name, subscription)
129
- @subscriptions[name] = subscription
145
+ def remove_subscription_for(name, subscription)
146
+ @subscriptions[name].delete(subscription)
130
147
  end
131
148
 
132
- def remove_subscription_for(name)
133
- @subscriptions.delete(name)
149
+ def consumer_count_for(name)
150
+ @subscriptions[name].size
134
151
  end
135
152
  end
136
153
  end
@@ -19,6 +19,11 @@ module MessageDriver
19
19
  end
20
20
 
21
21
  class Destination < MessageDriver::Destination::Base
22
+ def queue_path
23
+ @queue_path ||= begin
24
+ name.start_with?('/') ? name : "/queue/#{name}"
25
+ end
26
+ end
22
27
  end
23
28
 
24
29
  attr_reader :config, :poll_timeout
@@ -43,29 +48,26 @@ module MessageDriver
43
48
 
44
49
  def_delegators :adapter, :with_connection, :poll_timeout
45
50
 
46
- #def subscribe(destination, consumer)
47
- #destination.subscribe(&consumer)
48
- #end
51
+ # def subscribe(destination, consumer)
52
+ # destination.subscribe(&consumer)
53
+ # end
49
54
 
50
- def create_destination(name, dest_options={}, message_props={})
51
- unless name.start_with?('/')
52
- name = "/queue/#{name}"
53
- end
55
+ def create_destination(name, dest_options = {}, message_props = {})
54
56
  Destination.new(adapter, name, dest_options, message_props)
55
57
  end
56
58
 
57
- def publish(destination, body, headers={}, _properties={})
59
+ def publish(destination, body, headers = {}, _properties = {})
58
60
  with_connection do |connection|
59
- connection.publish(destination.name, body, headers)
61
+ connection.publish(destination.queue_path, body, headers)
60
62
  end
61
63
  end
62
64
 
63
- def pop_message(destination, options={})
65
+ def pop_message(destination, options = {})
64
66
  with_connection do |connection|
65
67
  sub_id = connection.uuid
66
68
  msg = nil
67
69
  count = 0
68
- connection.subscribe(destination.name, options, sub_id)
70
+ connection.subscribe(destination.queue_path, options, sub_id)
69
71
  while msg.nil? && count < max_poll_count
70
72
  msg = connection.poll
71
73
  if msg.nil?
@@ -73,7 +75,7 @@ module MessageDriver
73
75
  sleep 0.1
74
76
  end
75
77
  end
76
- connection.unsubscribe(destination.name, options, sub_id)
78
+ connection.unsubscribe(destination.queue_path, options, sub_id)
77
79
  Message.new(self, msg) if msg
78
80
  end
79
81
  end
@@ -90,26 +92,22 @@ module MessageDriver
90
92
  end
91
93
 
92
94
  def with_connection
93
- begin
94
- @connection ||= open_connection
95
- yield @connection
96
- rescue SystemCallError, IOError => e
97
- raise MessageDriver::ConnectionError.new(e)
98
- end
95
+ @connection ||= open_connection
96
+ yield @connection
97
+ rescue SystemCallError, IOError => e
98
+ raise MessageDriver::ConnectionError.new(e)
99
99
  end
100
100
 
101
101
  def stop
102
102
  super
103
- if @connection
104
- @connection.disconnect
105
- end
103
+ @connection.disconnect if @connection
106
104
  end
107
105
 
108
106
  private
109
107
 
110
108
  def open_connection
111
109
  conn = Stomp::Connection.new(@config)
112
- raise MessageDriver::ConnectionError, conn.connection_frame.to_s unless conn.open?
110
+ fail MessageDriver::ConnectionError, conn.connection_frame.to_s unless conn.open?
113
111
  conn
114
112
  end
115
113
 
@@ -117,7 +115,8 @@ module MessageDriver
117
115
  required = Gem::Requirement.create('~> 1.3.1')
118
116
  current = Gem::Version.create(Stomp::Version::STRING)
119
117
  unless required.satisfied_by? current
120
- raise MessageDriver::Error, 'stomp 1.3.1 or a later version of the 1.3.x series is required for the stomp adapter'
118
+ fail MessageDriver::Error,
119
+ 'stomp 1.3.1 or a later version of the 1.3.x series is required for the stomp adapter'
121
120
  end
122
121
  end
123
122
  end
@@ -9,7 +9,7 @@ module MessageDriver
9
9
  class << self
10
10
  def configure(name = DEFAULT_BROKER_NAME, options)
11
11
  if brokers.keys.include? name
12
- raise BrokerAlreadyConfigured, "there is already a broker named #{name} configured"
12
+ fail BrokerAlreadyConfigured, "there is already a broker named #{name} configured"
13
13
  end
14
14
  brokers[name] = new(name, options)
15
15
  end
@@ -21,7 +21,8 @@ module MessageDriver
21
21
  def broker(name = DEFAULT_BROKER_NAME)
22
22
  result = brokers[name]
23
23
  if result.nil?
24
- raise BrokerNotConfigured, "There is no broker named #{name} configured. The configured brokers are #{brokers.keys}"
24
+ fail BrokerNotConfigured,
25
+ "There is no broker named #{name} configured. The configured brokers are #{brokers.keys}"
25
26
  end
26
27
  result
27
28
  end
@@ -65,11 +66,11 @@ module MessageDriver
65
66
  private
66
67
 
67
68
  def brokers
68
- @brokers ||= { }
69
+ @brokers ||= {}
69
70
  end
70
71
 
71
72
  def clients
72
- @clients ||= { }
73
+ @clients ||= {}
73
74
  end
74
75
 
75
76
  def each_broker
@@ -107,36 +108,36 @@ module MessageDriver
107
108
  end
108
109
 
109
110
  def restart
110
- unless stopped?
111
- @adapter.stop
112
- end
111
+ @adapter.stop unless stopped?
113
112
  @adapter = resolve_adapter(@configuration[:adapter], @configuration)
114
113
  @stopped = false
115
114
  end
116
115
 
117
- def dynamic_destination(dest_name, dest_options={}, message_props={})
116
+ def dynamic_destination(dest_name, dest_options = {}, message_props = {})
118
117
  client.dynamic_destination(dest_name, dest_options, message_props)
119
118
  end
120
119
 
121
- def destination(key, dest_name, dest_options={}, message_props={})
122
- dest = self.dynamic_destination(dest_name, dest_options, message_props)
120
+ def destination(key, dest_name, dest_options = {}, message_props = {})
121
+ dest = dynamic_destination(dest_name, dest_options, message_props)
123
122
  @destinations[key] = dest
124
123
  end
125
124
 
126
125
  def consumer(key, &block)
127
- raise MessageDriver::Error, 'you must provide a block' unless block_given?
126
+ fail MessageDriver::Error, 'you must provide a block' unless block_given?
128
127
  @consumers[key] = block
129
128
  end
130
129
 
131
130
  def find_destination(destination_name)
132
131
  destination = @destinations[destination_name]
133
- raise MessageDriver::NoSuchDestinationError, "no destination #{destination_name} has been configured" if destination.nil?
132
+ if destination.nil?
133
+ fail MessageDriver::NoSuchDestinationError, "no destination #{destination_name} has been configured"
134
+ end
134
135
  destination
135
136
  end
136
137
 
137
138
  def find_consumer(consumer_name)
138
139
  consumer = @consumers[consumer_name]
139
- raise MessageDriver::NoSuchConsumerError, "no consumer #{consumer_name} has been configured" if consumer.nil?
140
+ fail MessageDriver::NoSuchConsumerError, "no consumer #{consumer_name} has been configured" if consumer.nil?
140
141
  consumer
141
142
  end
142
143
 
@@ -145,7 +146,7 @@ module MessageDriver
145
146
  def resolve_adapter(adapter, options)
146
147
  case adapter
147
148
  when nil
148
- raise 'you must specify an adapter'
149
+ fail 'you must specify an adapter'
149
150
  when Symbol, String
150
151
  resolve_adapter(find_adapter_class(adapter), options)
151
152
  when Class
@@ -153,7 +154,7 @@ module MessageDriver
153
154
  when MessageDriver::Adapters::Base
154
155
  adapter
155
156
  else
156
- raise "adapter must be a MessageDriver::Adapters::Base, but this object is a #{adapter.class}"
157
+ fail "adapter must be a MessageDriver::Adapters::Base, but this object is a #{adapter.class}"
157
158
  end
158
159
  end
159
160
 
@@ -163,7 +164,11 @@ module MessageDriver
163
164
  adapter_method = "#{adapter_name}_adapter"
164
165
 
165
166
  unless respond_to?(adapter_method)
166
- raise "the adapter #{adapter_name} must provide MessageDriver::Broker##{adapter_method} that returns the adapter class"
167
+ fail ['the adapter',
168
+ adapter_name,
169
+ 'must provide',
170
+ "MessageDriver::Broker##{adapter_method}",
171
+ 'that returns the adapter class'].join(' ')
167
172
  end
168
173
 
169
174
  send(adapter_method)
@@ -5,35 +5,33 @@ module MessageDriver
5
5
  include Logging
6
6
  extend self
7
7
 
8
- def publish(destination, body, headers={}, properties={})
9
- dest = find_destination(destination)
10
- current_adapter_context.publish(dest, body, headers, properties)
8
+ def publish(destination, body, headers = {}, properties = {})
9
+ find_destination(destination).publish(body, headers, properties)
11
10
  end
12
11
 
13
- def pop_message(destination, options={})
14
- dest = find_destination(destination)
15
- current_adapter_context.pop_message(dest, options)
12
+ def pop_message(destination, options = {})
13
+ find_destination(destination).pop_message(options)
16
14
  end
17
15
 
18
- def subscribe(destination_name, consumer_name, options={})
16
+ def subscribe(destination_name, consumer_name, options = {})
19
17
  consumer = find_consumer(consumer_name)
20
18
  subscribe_with(destination_name, options, &consumer)
21
19
  end
22
20
 
23
- def subscribe_with(destination_name, options={}, &consumer)
21
+ def subscribe_with(destination_name, options = {}, &consumer)
24
22
  destination = find_destination(destination_name)
25
23
  current_adapter_context.subscribe(destination, options, &consumer)
26
24
  end
27
25
 
28
- def dynamic_destination(dest_name, dest_options={}, message_props={})
26
+ def dynamic_destination(dest_name, dest_options = {}, message_props = {})
29
27
  current_adapter_context.create_destination(dest_name, dest_options, message_props)
30
28
  end
31
29
 
32
- def ack_message(message, options={})
30
+ def ack_message(message, options = {})
33
31
  message.ack(options)
34
32
  end
35
33
 
36
- def nack_message(message, options={})
34
+ def nack_message(message, options = {})
37
35
  message.nack(options)
38
36
  end
39
37
 
@@ -54,7 +52,7 @@ module MessageDriver
54
52
  broker.find_consumer(consumer)
55
53
  end
56
54
 
57
- def with_message_transaction(options={})
55
+ def with_message_transaction(options = {})
58
56
  wrapper = fetch_context_wrapper
59
57
  wrapper.increment_transaction_depth
60
58
  begin
@@ -84,13 +82,14 @@ module MessageDriver
84
82
  end
85
83
  end
86
84
 
87
- def current_adapter_context(initialize=true)
85
+ def current_adapter_context(initialize = true)
88
86
  ctx = fetch_context_wrapper(initialize)
89
87
  ctx.nil? ? nil : ctx.ctx
90
88
  end
91
89
 
92
90
  def with_adapter_context(adapter_context)
93
- old_ctx, Thread.current[adapter_context_key] = fetch_context_wrapper(false), build_context_wrapper(adapter_context)
91
+ old_ctx = fetch_context_wrapper(false)
92
+ Thread.current[adapter_context_key] = build_context_wrapper(adapter_context)
94
93
  begin
95
94
  yield
96
95
  ensure
@@ -132,7 +131,7 @@ module MessageDriver
132
131
 
133
132
  private
134
133
 
135
- def fetch_context_wrapper(initialize=true)
134
+ def fetch_context_wrapper(initialize = true)
136
135
  wrapper = Thread.current[adapter_context_key]
137
136
  if wrapper.nil? || !wrapper.valid?
138
137
  if initialize
@@ -149,7 +148,7 @@ module MessageDriver
149
148
  Thread.current[adapter_context_key] = wrapper
150
149
  end
151
150
 
152
- def build_context_wrapper(ctx=adapter.new_context)
151
+ def build_context_wrapper(ctx = adapter.new_context)
153
152
  ContextWrapper.new(ctx)
154
153
  end
155
154
 
@@ -10,24 +10,42 @@ module MessageDriver
10
10
  @message_props = message_props
11
11
  end
12
12
 
13
- def publish(body, headers={}, properties={})
14
- adapter.broker.client.publish(self, body, headers, properties)
13
+ def publish(body, headers = {}, properties = {})
14
+ current_adapter_context.publish(self, body, headers, properties)
15
15
  end
16
16
 
17
- def pop_message(options={})
18
- adapter.broker.client.pop_message(self, options)
17
+ def pop_message(options = {})
18
+ current_adapter_context.pop_message(self, options)
19
19
  end
20
20
 
21
21
  def after_initialize(_adapter_context)
22
- #does nothing, feel free to override as needed
22
+ # does nothing, feel free to override as needed
23
23
  end
24
24
 
25
25
  def message_count
26
- raise "#message_count is not supported by #{self.class}"
26
+ fail "#message_count is not supported by #{self.class}"
27
27
  end
28
28
 
29
- def subscribe(&_consumer)
30
- raise "#subscribe is not supported by #{self.class}"
29
+ def subscribe(_options = {}, &_consumer)
30
+ fail "#subscribe is not supported by #{self.class}"
31
+ end
32
+
33
+ def consumer_count
34
+ fail "#consumer_count is not supported by #{self.class}"
35
+ end
36
+
37
+ def middleware
38
+ @middleware ||= Middleware::MiddlewareStack.new(self)
39
+ end
40
+
41
+ private
42
+
43
+ def current_adapter_context
44
+ adapter.broker.client.current_adapter_context
45
+ end
46
+
47
+ def client
48
+ @client ||= adapter.broker.client
31
49
  end
32
50
  end
33
51
  end