message-driver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +20 -0
- data/.rbenv-version +1 -0
- data/.relish +2 -0
- data/.rspec +2 -0
- data/.travis.yml +23 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +20 -0
- data/Guardfile +39 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +23 -0
- data/features/.nav +12 -0
- data/features/CHANGELOG.md +17 -0
- data/features/README.md +1 -0
- data/features/Rails.md +1 -0
- data/features/amqp_specific_features/README.md +3 -0
- data/features/amqp_specific_features/binding_amqp_destinations.feature +50 -0
- data/features/amqp_specific_features/declaring_amqp_exchanges.feature +22 -0
- data/features/amqp_specific_features/server_named_destinations.feature +35 -0
- data/features/destination_metadata.feature +33 -0
- data/features/dynamic_destinations.feature +41 -0
- data/features/error_handling.feature +47 -0
- data/features/getting_started.md +1 -0
- data/features/publishing_a_message.feature +19 -0
- data/features/publishing_with_transactions.feature +36 -0
- data/features/step_definitions/dynamic_destinations_steps.rb +12 -0
- data/features/step_definitions/error_handling_steps.rb +11 -0
- data/features/step_definitions/steps.rb +41 -0
- data/features/support/env.rb +7 -0
- data/features/support/firewall_helper.rb +59 -0
- data/features/support/message_table_matcher.rb +11 -0
- data/features/support/no_error_matcher.rb +13 -0
- data/features/support/test_runner.rb +50 -0
- data/features/support/transforms.rb +17 -0
- data/lib/message-driver.rb +1 -0
- data/lib/message_driver/adapters/base.rb +29 -0
- data/lib/message_driver/adapters/bunny_adapter.rb +270 -0
- data/lib/message_driver/adapters/in_memory_adapter.rb +58 -0
- data/lib/message_driver/broker.rb +95 -0
- data/lib/message_driver/destination.rb +31 -0
- data/lib/message_driver/exceptions.rb +18 -0
- data/lib/message_driver/message.rb +13 -0
- data/lib/message_driver/message_publisher.rb +15 -0
- data/lib/message_driver/version.rb +5 -0
- data/lib/message_driver.rb +18 -0
- data/message-driver.gemspec +27 -0
- data/spec/integration/amqp_integration_spec.rb +146 -0
- data/spec/integration/message_driver/adapters/bunny_adapter_spec.rb +301 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/shared/destination_examples.rb +41 -0
- data/spec/units/message_driver/adapters/base_spec.rb +44 -0
- data/spec/units/message_driver/adapters/in_memory_adapter_spec.rb +43 -0
- data/spec/units/message_driver/broker_spec.rb +98 -0
- data/spec/units/message_driver/destination_spec.rb +11 -0
- data/spec/units/message_driver/message_publisher_spec.rb +65 -0
- data/spec/units/message_driver/message_spec.rb +19 -0
- data/test_lib/broker_config.rb +25 -0
- metadata +203 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module FirewallHelper
|
2
|
+
|
3
|
+
COMMANDS = {
|
4
|
+
darwin: {
|
5
|
+
setup: [
|
6
|
+
"sudo ipfw add 02070 deny tcp from any to any 5672"
|
7
|
+
],
|
8
|
+
teardown: [
|
9
|
+
"sudo ipfw delete 02070"
|
10
|
+
]
|
11
|
+
},
|
12
|
+
linux: {
|
13
|
+
setup: [
|
14
|
+
"sudo iptables -N block-rabbit",
|
15
|
+
"sudo iptables -A block-rabbit -p tcp --dport 5672 -j DROP",
|
16
|
+
"sudo iptables -A block-rabbit -p tcp --sport 5672 -j DROP",
|
17
|
+
"sudo iptables -I INPUT -j block-rabbit",
|
18
|
+
"sudo iptables -I OUTPUT -j block-rabbit"
|
19
|
+
],
|
20
|
+
teardown: [
|
21
|
+
"sudo iptables -D INPUT -j block-rabbit",
|
22
|
+
"sudo iptables -D OUTPUT -j block-rabbit",
|
23
|
+
"sudo iptables -F block-rabbit",
|
24
|
+
"sudo iptables -X block-rabbit"
|
25
|
+
]
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
def block_broker_port
|
30
|
+
run_commands(:setup)
|
31
|
+
@firewall_rule_set = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def unblock_broker_port
|
35
|
+
run_commands(:teardown) if @firewall_rule_set
|
36
|
+
@firewall_rule_set = false
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_commands(step)
|
40
|
+
COMMANDS[os][step].each do |cmd|
|
41
|
+
result = system(cmd)
|
42
|
+
raise "command `#{cmd}` failed!" unless result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def os
|
47
|
+
if darwin?
|
48
|
+
:darwin
|
49
|
+
else
|
50
|
+
:linux
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def darwin?
|
55
|
+
system("uname | grep Darwin")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
World(FirewallHelper)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
RSpec::Matchers.define :have_no_errors do
|
2
|
+
match do |test_runner|
|
3
|
+
test_runner.raised_error == nil
|
4
|
+
end
|
5
|
+
|
6
|
+
failure_message_for_should do |test_runner|
|
7
|
+
err = test_runner.raised_error
|
8
|
+
filtered = (err.backtrace || []).reject do |line|
|
9
|
+
Cucumber::Ast::StepInvocation::BACKTRACE_FILTER_PATTERNS.detect { |p| line =~ p }
|
10
|
+
end
|
11
|
+
(["#{err.class}: #{err.to_s}"]+filtered).join("\n ")
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'message_driver'
|
2
|
+
|
3
|
+
class TestRunner
|
4
|
+
include MessageDriver::MessagePublisher
|
5
|
+
include RSpec::Matchers
|
6
|
+
|
7
|
+
attr_accessor :raised_error
|
8
|
+
|
9
|
+
def config_broker(src)
|
10
|
+
instance_eval(src)
|
11
|
+
end
|
12
|
+
|
13
|
+
def run_test_code(src)
|
14
|
+
begin
|
15
|
+
instance_eval(src)
|
16
|
+
rescue Exception => e
|
17
|
+
@raised_error = e
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_messages(destination)
|
22
|
+
case destination
|
23
|
+
when String, Symbol
|
24
|
+
fetch_messages(MessageDriver::Broker.find_destination(destination))
|
25
|
+
when MessageDriver::Destination::Base
|
26
|
+
result = []
|
27
|
+
begin
|
28
|
+
msg = destination.pop_message
|
29
|
+
result << msg unless msg.nil?
|
30
|
+
end until msg.nil?
|
31
|
+
result
|
32
|
+
else
|
33
|
+
raise "didn't understand destination #{destination}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def publish_table_to_destination(destination, table)
|
38
|
+
table.hashes.each do |msg|
|
39
|
+
destination.publish(msg[:body], msg[:headers]||{}, msg[:properties]||{})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module KnowsMyTestRunner
|
45
|
+
def test_runner
|
46
|
+
@test_runner ||= TestRunner.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
World(KnowsMyTestRunner)
|
@@ -0,0 +1 @@
|
|
1
|
+
require "message_driver"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MessageDriver
|
2
|
+
module Adapters
|
3
|
+
class Base
|
4
|
+
def initialize(configuration)
|
5
|
+
raise "Must be implemented in subclass"
|
6
|
+
end
|
7
|
+
|
8
|
+
def publish(destination, body, headers={}, properties={})
|
9
|
+
raise "Must be implemented in subclass"
|
10
|
+
end
|
11
|
+
|
12
|
+
def pop_message(destination, options={})
|
13
|
+
raise "Must be implemented in subclass"
|
14
|
+
end
|
15
|
+
|
16
|
+
def stop
|
17
|
+
raise "Must be implemented in subclass"
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_destination(name, dest_options={}, message_props={})
|
21
|
+
raise "Must be implemented in subclass"
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_transaction(options={}, &block)
|
25
|
+
yield
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
module MessageDriver
|
4
|
+
class Broker
|
5
|
+
def bunny_adapter
|
6
|
+
MessageDriver::Adapters::BunnyAdapter
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Adapters
|
11
|
+
class BunnyAdapter < Base
|
12
|
+
|
13
|
+
class Message < MessageDriver::Message::Base
|
14
|
+
attr_reader :delivery_info
|
15
|
+
|
16
|
+
def initialize(delivery_info, properties, payload)
|
17
|
+
super(payload, properties[:headers]||{}, properties)
|
18
|
+
@delivery_info = delivery_info
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Destination < MessageDriver::Destination::Base
|
23
|
+
def publish(body, headers={}, properties={})
|
24
|
+
props = @message_props.merge(properties)
|
25
|
+
props[:headers] = headers if headers
|
26
|
+
@adapter.publish(body, exchange_name, routing_key(properties), props)
|
27
|
+
end
|
28
|
+
|
29
|
+
def exchange_name
|
30
|
+
@name
|
31
|
+
end
|
32
|
+
|
33
|
+
def routing_key(properties)
|
34
|
+
properties[:routing_key]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class QueueDestination < Destination
|
39
|
+
def after_initialize
|
40
|
+
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
|
+
end
|
51
|
+
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
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def exchange_name
|
58
|
+
""
|
59
|
+
end
|
60
|
+
|
61
|
+
def routing_key(properties)
|
62
|
+
@name
|
63
|
+
end
|
64
|
+
|
65
|
+
def message_count
|
66
|
+
@adapter.current_context.with_channel(false) do |ch|
|
67
|
+
ch.queue(@name, @dest_options.merge(passive: true)).message_count
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class ExchangeDestination < Destination
|
73
|
+
def pop_message(destination, options={})
|
74
|
+
raise MessageDriver::Exception, "You can't pop a message off an exchange"
|
75
|
+
end
|
76
|
+
|
77
|
+
def after_initialize
|
78
|
+
if declare = @dest_options[:declare]
|
79
|
+
@adapter.current_context.with_channel(false) do |ch|
|
80
|
+
type = declare.delete(:type)
|
81
|
+
raise MessageDriver::Exception, "you must provide a valid exchange type" unless type
|
82
|
+
ch.exchange_declare(@name, type, declare)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
if bindings = @dest_options[:bindings]
|
86
|
+
@adapter.current_context.with_channel(false) do |ch|
|
87
|
+
bindings.each do |bnd|
|
88
|
+
raise MessageDriver::Exception, "binding #{bnd.inspect} must provide a source!" unless bnd[:source]
|
89
|
+
ch.exchange_bind(bnd[:source], @name, bnd[:args]||{})
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize(config)
|
97
|
+
validate_bunny_version
|
98
|
+
|
99
|
+
@connection = Bunny.new(config.merge(threaded: false))
|
100
|
+
end
|
101
|
+
|
102
|
+
def connection(ensure_started=true)
|
103
|
+
if ensure_started && !@connection.open?
|
104
|
+
@connection.start
|
105
|
+
end
|
106
|
+
@connection
|
107
|
+
end
|
108
|
+
|
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)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def pop_message(destination, options={})
|
116
|
+
current_context.with_channel do |ch|
|
117
|
+
queue = ch.queue(destination, passive: true)
|
118
|
+
|
119
|
+
message = queue.pop
|
120
|
+
if message.nil? || message[0].nil?
|
121
|
+
nil
|
122
|
+
else
|
123
|
+
Message.new(*message)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
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}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def with_transaction(options={}, &block)
|
140
|
+
current_context.with_transaction(&block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def stop
|
144
|
+
@connection.close if @connection.open?
|
145
|
+
@context = nil
|
146
|
+
end
|
147
|
+
|
148
|
+
def current_context
|
149
|
+
if !@context.nil? && @context.need_new_context?
|
150
|
+
@context = nil
|
151
|
+
end
|
152
|
+
@context ||= ChannelContext.new(connection)
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
class ChannelContext
|
158
|
+
attr_reader :connection, :transaction_depth
|
159
|
+
|
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
|
168
|
+
end
|
169
|
+
|
170
|
+
def is_transactional?
|
171
|
+
@is_transactional
|
172
|
+
end
|
173
|
+
|
174
|
+
def connection_failed?
|
175
|
+
@connection_failed
|
176
|
+
end
|
177
|
+
|
178
|
+
def with_transaction(&block)
|
179
|
+
if !is_transactional?
|
180
|
+
@channel.tx_select
|
181
|
+
@is_transactional = true
|
182
|
+
end
|
183
|
+
|
184
|
+
begin
|
185
|
+
@transaction_depth += 1
|
186
|
+
yield
|
187
|
+
commit_transaction
|
188
|
+
rescue
|
189
|
+
rollback_transaction
|
190
|
+
raise
|
191
|
+
ensure
|
192
|
+
@transaction_depth -= 1
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def with_channel(require_commit=true)
|
197
|
+
raise MessageDriver::TransactionRollbackOnly if @rollback_only
|
198
|
+
raise MessageDriver::Exception, "oh shit!" if @connection_failed
|
199
|
+
reset_channel if @need_channel_reset
|
200
|
+
begin
|
201
|
+
result = yield @channel
|
202
|
+
commit_transaction(true) if require_commit
|
203
|
+
result
|
204
|
+
rescue Bunny::ChannelLevelException => e
|
205
|
+
@need_channel_reset = true
|
206
|
+
@rollback_only = true if is_transactional?
|
207
|
+
if e.kind_of? Bunny::NotFound
|
208
|
+
raise MessageDriver::QueueNotFound.new(e)
|
209
|
+
else
|
210
|
+
raise MessageDriver::WrappedException.new(e)
|
211
|
+
end
|
212
|
+
rescue Bunny::NetworkErrorWrapper, IOError => e
|
213
|
+
@connection_failed = true
|
214
|
+
@rollback_only = true if is_transactional?
|
215
|
+
raise MessageDriver::ConnectionException.new(e)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
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
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def reset_channel
|
234
|
+
unless @channel.open?
|
235
|
+
@channel.open
|
236
|
+
@is_transactional = false
|
237
|
+
end
|
238
|
+
@need_channel_reset = false
|
239
|
+
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
|
+
end
|
260
|
+
|
261
|
+
def validate_bunny_version
|
262
|
+
required = Gem::Requirement.create('~> 0.9.0.pre7')
|
263
|
+
current = Gem::Version.create(Bunny::VERSION)
|
264
|
+
unless required.satisfied_by? current
|
265
|
+
raise MessageDriver::Exception, "bunny 0.9.0.pre7 or later is required for the bunny adapter"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module MessageDriver
|
2
|
+
class Broker
|
3
|
+
def in_memory_adapter
|
4
|
+
MessageDriver::Adapters::InMemoryAdapter
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module Adapters
|
9
|
+
class InMemoryAdapter < Base
|
10
|
+
|
11
|
+
class Message < MessageDriver::Message::Base
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
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
|
19
|
+
end
|
20
|
+
def message_count
|
21
|
+
@message_store[@name].size
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(config={})
|
26
|
+
#does nothing
|
27
|
+
end
|
28
|
+
|
29
|
+
def publish(destination, body, headers={}, properties={})
|
30
|
+
message_store[destination] << Message.new(body, headers, properties)
|
31
|
+
end
|
32
|
+
|
33
|
+
def pop_message(destination, options={})
|
34
|
+
message_store[destination].shift
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop
|
38
|
+
message_store.clear
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_destination(name, dest_options={}, message_props={})
|
42
|
+
Destination.new(self, name, dest_options, message_props, message_store)
|
43
|
+
end
|
44
|
+
|
45
|
+
def reset_after_tests
|
46
|
+
message_store.each do |destination, message_array|
|
47
|
+
message_array.replace([])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def message_store
|
54
|
+
@message_store ||= Hash.new { |h,k| h[k] = [] }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module MessageDriver
|
4
|
+
class Broker
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :adapter, :configuration, :destinations
|
8
|
+
|
9
|
+
def_delegators :@adapter, :stop
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def configure(options)
|
13
|
+
@instance = new(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(m, *args)
|
17
|
+
@instance.send(m, *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_transaction(options={}, &block)
|
21
|
+
@instance.with_transaction(options, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def instance
|
25
|
+
@instance
|
26
|
+
end
|
27
|
+
|
28
|
+
def define
|
29
|
+
yield @instance
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(options)
|
34
|
+
@adapter = resolve_adapter(options[:adapter], options)
|
35
|
+
@configuration = options
|
36
|
+
@destinations = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def publish(destination, body, headers={}, properties={})
|
40
|
+
dest = find_destination(destination)
|
41
|
+
dest.publish(body, headers, properties)
|
42
|
+
end
|
43
|
+
|
44
|
+
def pop_message(destination, options={})
|
45
|
+
dest = find_destination(destination)
|
46
|
+
dest.pop_message(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dynamic_destination(dest_name, dest_options={}, message_props={})
|
50
|
+
adapter.create_destination(dest_name, dest_options, message_props)
|
51
|
+
end
|
52
|
+
|
53
|
+
def destination(key, dest_name, dest_options={}, message_props={})
|
54
|
+
dest = dynamic_destination(dest_name, dest_options, message_props)
|
55
|
+
@destinations[key] = dest
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_transaction(options={}, &block)
|
59
|
+
adapter.with_transaction(options, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def find_destination(destination)
|
65
|
+
@destinations[destination]
|
66
|
+
end
|
67
|
+
|
68
|
+
def resolve_adapter(adapter, options)
|
69
|
+
case adapter
|
70
|
+
when nil
|
71
|
+
raise "you must specify an adapter"
|
72
|
+
when Symbol, String
|
73
|
+
resolve_adapter(find_adapter_class(adapter), options)
|
74
|
+
when Class
|
75
|
+
resolve_adapter(adapter.new(options), options)
|
76
|
+
when MessageDriver::Adapters::Base
|
77
|
+
adapter
|
78
|
+
else
|
79
|
+
raise "adapter must be a MessageDriver::Adapters::Base, but this object is a #{adapter.class}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_adapter_class(adapter_name)
|
84
|
+
require "message_driver/adapters/#{adapter_name}_adapter"
|
85
|
+
|
86
|
+
adapter_method = "#{adapter_name}_adapter"
|
87
|
+
|
88
|
+
unless respond_to?(adapter_method)
|
89
|
+
raise "the adapter #{adapter_name} must provide MessageDriver::Broker.#{adapter_method} that returns the adapter class"
|
90
|
+
end
|
91
|
+
|
92
|
+
send(adapter_method)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MessageDriver
|
2
|
+
module Destination
|
3
|
+
class Base
|
4
|
+
attr_reader :adapter, :name, :dest_options, :message_props
|
5
|
+
|
6
|
+
def initialize(adapter, name, dest_options, message_props)
|
7
|
+
@adapter = adapter
|
8
|
+
@name = name
|
9
|
+
@dest_options = dest_options
|
10
|
+
@message_props = message_props
|
11
|
+
after_initialize
|
12
|
+
end
|
13
|
+
|
14
|
+
def publish(body, headers={}, properties={})
|
15
|
+
@adapter.publish(@name, body, headers, @message_props.merge(properties))
|
16
|
+
end
|
17
|
+
|
18
|
+
def pop_message(options={})
|
19
|
+
@adapter.pop_message(@name, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def after_initialize
|
23
|
+
#does nothing, feel free to override as needed
|
24
|
+
end
|
25
|
+
|
26
|
+
def message_count
|
27
|
+
raise "#message_count is not supported by #{self.class}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module MessageDriver
|
2
|
+
class Exception < ::Exception; end
|
3
|
+
|
4
|
+
class WrappedException < Exception
|
5
|
+
attr_reader :other
|
6
|
+
|
7
|
+
def initialize(other, msg=nil)
|
8
|
+
super(msg || other.to_s)
|
9
|
+
@other = other
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class QueueNotFound < WrappedException; end
|
14
|
+
|
15
|
+
class ConnectionException < WrappedException; end
|
16
|
+
|
17
|
+
class TransactionRollbackOnly < Exception; end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MessageDriver
|
2
|
+
module MessagePublisher
|
3
|
+
def publish(destination, body, headers={}, properties={})
|
4
|
+
Broker.publish(destination, body, headers, properties)
|
5
|
+
end
|
6
|
+
|
7
|
+
def pop_message(destination, options={})
|
8
|
+
Broker.pop_message(destination, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_message_transaction(options={}, &block)
|
12
|
+
Broker.with_transaction(options, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|