activemessaging 0.6.1 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +22 -0
- data/Rakefile +48 -26
- data/VERSION +1 -0
- data/activemessaging.gemspec +113 -0
- data/generators/filter/templates/filter_test.rb +1 -1
- data/generators/processor/templates/broker.yml +18 -7
- data/init.rb +3 -0
- data/lib/activemessaging.rb +0 -3
- data/lib/activemessaging/adapters/asqs.rb +69 -61
- data/lib/activemessaging/adapters/base.rb +51 -63
- data/lib/activemessaging/adapters/beanstalk.rb +88 -0
- data/lib/activemessaging/adapters/jms.rb +7 -7
- data/lib/activemessaging/adapters/reliable_msg.rb +136 -140
- data/lib/activemessaging/adapters/stomp.rb +90 -22
- data/lib/activemessaging/adapters/test.rb +7 -25
- data/lib/activemessaging/adapters/wmq.rb +7 -16
- data/lib/activemessaging/base_message.rb +19 -0
- data/lib/activemessaging/gateway.rb +38 -41
- data/lib/activemessaging/test_helper.rb +4 -9
- data/poller.rb +14 -0
- data/test/all_tests.rb +10 -0
- data/test/app/config/broker.yml +4 -0
- data/test/asqs_test.rb +102 -0
- data/test/config_test.rb +42 -0
- data/test/filter_test.rb +131 -0
- data/test/gateway_test.rb +195 -0
- data/test/jms_test.rb +61 -0
- data/test/reliable_msg_test.rb +83 -0
- data/test/stomp_test.rb +131 -0
- data/test/test_helper.rb +24 -0
- data/test/tracer_test.rb +57 -0
- metadata +69 -53
- data/messaging.rb.example +0 -5
@@ -1,41 +1,82 @@
|
|
1
1
|
require 'stomp'
|
2
2
|
|
3
|
+
require 'activemessaging/adapters/base'
|
4
|
+
|
3
5
|
module ActiveMessaging
|
4
6
|
module Adapters
|
5
7
|
module Stomp
|
6
|
-
|
7
|
-
class Connection < ::
|
8
|
-
include ActiveMessaging::Adapter
|
8
|
+
|
9
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
9
10
|
register :stomp
|
10
11
|
|
11
|
-
attr_accessor :
|
12
|
+
attr_accessor :stomp_connection, :retryMax, :deadLetterQueue, :configuration
|
12
13
|
|
13
14
|
def initialize(cfg)
|
14
15
|
@retryMax = cfg[:retryMax] || 0
|
15
16
|
@deadLetterQueue = cfg[:deadLetterQueue] || nil
|
16
|
-
|
17
|
+
|
17
18
|
cfg[:login] ||= ""
|
18
19
|
cfg[:passcode] ||= ""
|
19
20
|
cfg[:host] ||= "localhost"
|
20
21
|
cfg[:port] ||= "61613"
|
21
|
-
cfg[:reliable]
|
22
|
+
cfg[:reliable] = cfg[:reliable].nil? ? TRUE : cfg[:reliable].nil?
|
22
23
|
cfg[:reconnectDelay] ||= 5
|
23
24
|
cfg[:clientId] ||= nil
|
24
|
-
|
25
|
-
if cfg[:clientId]
|
26
|
-
super(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay],cfg[:clientId])
|
27
|
-
else
|
28
|
-
super(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay])
|
29
|
-
end
|
30
25
|
|
26
|
+
# hold on to the config
|
27
|
+
@configuration = cfg
|
28
|
+
|
29
|
+
# create a new stomp connection
|
30
|
+
connect_headers = {}
|
31
|
+
connect_headers['client-id'] = cfg[:clientId] if cfg[:clientId]
|
32
|
+
@stomp_connection = ::Stomp::Connection.new(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay], connect_headers)
|
31
33
|
end
|
32
|
-
|
34
|
+
|
35
|
+
# called to cleanly get rid of connection
|
36
|
+
def disconnect
|
37
|
+
@stomp_connection.disconnect
|
38
|
+
end
|
39
|
+
|
40
|
+
# destination_name string, headers hash
|
41
|
+
# subscribe to listen on a destination
|
42
|
+
def subscribe destination_name, message_headers={}
|
43
|
+
@stomp_connection.subscribe(destination_name, message_headers)
|
44
|
+
end
|
45
|
+
|
46
|
+
# destination_name string, headers hash
|
47
|
+
# unsubscribe to listen on a destination
|
48
|
+
def unsubscribe destination_name, message_headers={}
|
49
|
+
@stomp_connection.unsubscribe(destination_name, message_headers)
|
50
|
+
end
|
51
|
+
|
52
|
+
# destination_name string, body string, headers hash
|
53
|
+
# send a single message to a destination
|
54
|
+
def send destination_name, message_body, message_headers={}
|
55
|
+
stomp_publish(destination_name, message_body, message_headers)
|
56
|
+
end
|
57
|
+
|
58
|
+
# receive a single message from any of the subscribed destinations
|
59
|
+
# check each destination once, then sleep for poll_interval
|
60
|
+
def receive
|
61
|
+
m = @stomp_connection.receive
|
62
|
+
Message.new(m) if m
|
63
|
+
end
|
64
|
+
|
33
65
|
def received message, headers={}
|
34
66
|
#check to see if the ack mode for this subscription is auto or client
|
35
67
|
# if the ack mode is client, send an ack
|
36
68
|
if (headers[:ack] === 'client')
|
37
|
-
ack_headers = message.headers.has_key?(:transaction) ? message.headers[:transaction] : {}
|
38
|
-
ack
|
69
|
+
ack_headers = message.headers.has_key?(:transaction) ? { :transaction=>message.headers[:transaction]} : {}
|
70
|
+
@stomp_connection.ack(message.headers['message-id'], ack_headers)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# send has been deprecated in latest stomp gem (as it should be)
|
75
|
+
def stomp_publish(destination_name="", message_body="", message_headers={})
|
76
|
+
if @stomp_connection.respond_to?(:publish)
|
77
|
+
@stomp_connection.publish(destination_name, message_body, message_headers)
|
78
|
+
else
|
79
|
+
@stomp_connection.send(destination_name, message_body, message_headers)
|
39
80
|
end
|
40
81
|
end
|
41
82
|
|
@@ -44,7 +85,7 @@ module ActiveMessaging
|
|
44
85
|
transaction_id = "transaction-#{message.headers['message-id']}-#{retry_count}"
|
45
86
|
|
46
87
|
# start a transaction, send the message back to the original destination
|
47
|
-
|
88
|
+
@stomp_connection.begin(transaction_id)
|
48
89
|
begin
|
49
90
|
|
50
91
|
if @retryMax > 0
|
@@ -53,6 +94,8 @@ module ActiveMessaging
|
|
53
94
|
retry_headers.delete('content-length')
|
54
95
|
retry_headers.delete('content-type')
|
55
96
|
|
97
|
+
retry_destination = retry_headers.delete('destination')
|
98
|
+
|
56
99
|
if retry_count < @retryMax
|
57
100
|
# now send the message back to the destination
|
58
101
|
# set the headers for message id, priginal message id, and retry count
|
@@ -65,13 +108,15 @@ module ActiveMessaging
|
|
65
108
|
retry_headers['a13g-retry-count'] = retry_count + 1
|
66
109
|
|
67
110
|
# send the updated message to retry in the same transaction
|
68
|
-
self.
|
111
|
+
self.stomp_publish(retry_destination, message.body, retry_headers)
|
69
112
|
|
70
113
|
elsif retry_count >= @retryMax && @deadLetterQueue
|
71
|
-
# send the 'poison pill' message to the dead letter queue
|
114
|
+
# send the 'poison pill' message to the dead letter queue - make it persistent by default
|
72
115
|
retry_headers['a13g-original-destination'] = retry_headers.delete('destination')
|
116
|
+
retry_headers['persistent'] = true
|
73
117
|
retry_headers.delete('message-id')
|
74
|
-
|
118
|
+
|
119
|
+
self.stomp_publish(@deadLetterQueue, message.body, retry_headers)
|
75
120
|
end
|
76
121
|
|
77
122
|
end
|
@@ -79,14 +124,14 @@ module ActiveMessaging
|
|
79
124
|
#check to see if the ack mode is client, and if it is, ack it in this transaction
|
80
125
|
if (headers[:ack] === 'client')
|
81
126
|
# ack the original message
|
82
|
-
|
127
|
+
@stomp_connection.ack(message.headers['message-id'], message.headers.stringify_keys.merge('transaction'=>transaction_id))
|
83
128
|
end
|
84
129
|
|
85
130
|
# now commit the transaction
|
86
|
-
|
131
|
+
@stomp_connection.commit transaction_id
|
87
132
|
rescue Exception=>exc
|
88
133
|
# if there is an error, try to abort the transaction, then raise the error
|
89
|
-
|
134
|
+
@stomp_connection.abort transaction_id
|
90
135
|
raise exc
|
91
136
|
end
|
92
137
|
|
@@ -94,6 +139,29 @@ module ActiveMessaging
|
|
94
139
|
|
95
140
|
end
|
96
141
|
|
142
|
+
class Message < ActiveMessaging::BaseMessage
|
143
|
+
|
144
|
+
def initialize(msg)
|
145
|
+
super(msg.body, msg.headers['message-id'], msg.headers, msg.headers['destination'])
|
146
|
+
end
|
147
|
+
|
148
|
+
def matches_subscription?(subscription)
|
149
|
+
# if the subscription has been specified in the headers, rely on this
|
150
|
+
if self.headers['subscription'] && subscription.subscribe_headers['id']
|
151
|
+
self.headers['subscription'].to_s == subscription.subscribe_headers['id'].to_s
|
152
|
+
|
153
|
+
# see if the destination uses a wildcard representation
|
154
|
+
elsif subscription.destination.wildcard
|
155
|
+
self.destination.to_s =~ subscription.destination.wildcard
|
156
|
+
|
157
|
+
# no subscription id? no wildcard? use the name of the destination as a straight match
|
158
|
+
else
|
159
|
+
self.destination.to_s == subscription.destination.value.to_s
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
97
165
|
end
|
98
166
|
end
|
99
167
|
end
|
@@ -1,11 +1,10 @@
|
|
1
|
-
require 'activemessaging/
|
1
|
+
require 'activemessaging/adapters/base'
|
2
2
|
|
3
3
|
module ActiveMessaging
|
4
4
|
module Adapters
|
5
5
|
module Test
|
6
6
|
|
7
|
-
class Connection
|
8
|
-
include ActiveMessaging::Adapter
|
7
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
9
8
|
register :test
|
10
9
|
|
11
10
|
attr_accessor :config, :subscriptions, :destinations, :connected, :received_messages, :unreceived_messages
|
@@ -42,7 +41,7 @@ module ActiveMessaging
|
|
42
41
|
def send destination_name, message_body, message_headers={}
|
43
42
|
open_destination destination_name
|
44
43
|
destination = find_destination destination_name
|
45
|
-
destination.send Message.new(
|
44
|
+
destination.send Message.new(message_body, nil, message_headers, destination_name)
|
46
45
|
end
|
47
46
|
|
48
47
|
def receive
|
@@ -52,15 +51,6 @@ module ActiveMessaging
|
|
52
51
|
destination.receive unless destination.nil?
|
53
52
|
end
|
54
53
|
|
55
|
-
# should not be 2 defs for receive, this isn't java, ya know? -Andrew
|
56
|
-
# def receive destination_name, headers={}
|
57
|
-
# subscribe destination_name, headers
|
58
|
-
# destination = find_destination destination_name
|
59
|
-
# message = destination.receive
|
60
|
-
# unsubscribe destination_name, headers
|
61
|
-
# message
|
62
|
-
# end
|
63
|
-
|
64
54
|
def received message, headers={}
|
65
55
|
@received_messages << message
|
66
56
|
end
|
@@ -71,8 +61,9 @@ module ActiveMessaging
|
|
71
61
|
|
72
62
|
#test helper methods
|
73
63
|
def find_message destination_name, body
|
64
|
+
|
74
65
|
all_messages.find do |m|
|
75
|
-
m.
|
66
|
+
m.destination == destination_name && if body.is_a?(Regexp)
|
76
67
|
m.body =~ body
|
77
68
|
else
|
78
69
|
m.body == body.to_s
|
@@ -138,18 +129,9 @@ module ActiveMessaging
|
|
138
129
|
end
|
139
130
|
end
|
140
131
|
|
141
|
-
class Message
|
142
|
-
attr_accessor :headers, :body, :command
|
143
|
-
|
144
|
-
def initialize headers, id, body, response, destination, command='MESSAGE'
|
145
|
-
@headers, @body, @command = headers, body, command
|
146
|
-
headers['destination'] = destination.name
|
147
|
-
end
|
148
|
-
|
149
|
-
def to_s
|
150
|
-
"<Test::Message body='#{body}' headers='#{headers.inspect}' command='#{command}' >"
|
151
|
-
end
|
132
|
+
class Message < ActiveMessaging::BaseMessage
|
152
133
|
end
|
134
|
+
|
153
135
|
end
|
154
136
|
end
|
155
137
|
end
|
@@ -19,18 +19,16 @@
|
|
19
19
|
#
|
20
20
|
require 'wmq/wmq'
|
21
21
|
|
22
|
+
require 'activemessaging/adapters/base'
|
23
|
+
|
22
24
|
module ActiveMessaging
|
23
25
|
module Adapters
|
24
26
|
module Adapter
|
25
27
|
|
26
28
|
# Connection class needed by a13g
|
27
|
-
class Connection
|
28
|
-
include ActiveMessaging::Adapter
|
29
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
29
30
|
register :wmq
|
30
31
|
|
31
|
-
# Needed by a13g but never used within this adapter
|
32
|
-
attr_accessor :reliable
|
33
|
-
|
34
32
|
# Generic init method needed by a13g
|
35
33
|
def initialize(cfg)
|
36
34
|
# Set default values
|
@@ -147,22 +145,15 @@ module ActiveMessaging
|
|
147
145
|
|
148
146
|
# Message class needed by a13g (based on the same Message class in Stomp adapter)
|
149
147
|
# Contains a reference to the MQ message object ;-) !
|
150
|
-
class Message
|
151
|
-
|
152
|
-
attr_accessor :
|
148
|
+
class Message < ActiveMessaging::BaseMessage
|
149
|
+
|
150
|
+
attr_accessor :wmq_message
|
153
151
|
|
154
152
|
def initialize(wmq_message, q_name)
|
153
|
+
super(wmq_message.data, wmq_message.descriptor[:msg_id], {'destination' => q_name}, q_name)
|
155
154
|
@wmq_message = wmq_message
|
156
|
-
|
157
|
-
# Needed by a13g
|
158
|
-
@headers = {'destination' => q_name}
|
159
|
-
@body = wmq_message.data
|
160
|
-
@command = 'MESSAGE'
|
161
155
|
end
|
162
156
|
|
163
|
-
def to_s
|
164
|
-
"<Adapter::Message headers=#{@headers.inspect} body='#{@body}' command='#{@command}' wmq_message=#{@wmq_message}>"
|
165
|
-
end
|
166
157
|
end
|
167
158
|
|
168
159
|
private
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveMessaging
|
2
|
+
|
3
|
+
class BaseMessage
|
4
|
+
attr_accessor :body, :id, :headers, :destination
|
5
|
+
|
6
|
+
def initialize(body=nil, id=nil, headers={}, destination=nil)
|
7
|
+
@body, @id, @headers, @destination = body, id, headers, destination
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches_subscription?(subscription)
|
11
|
+
self.destination.to_s == subscription.destination.value.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"<#{self.class.name} id='#{id}' headers='#{headers.inspect}' body='#{body}' >"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -189,13 +189,14 @@ module ActiveMessaging
|
|
189
189
|
|
190
190
|
def prepare_application
|
191
191
|
if defined? Rails
|
192
|
-
Dispatcher.prepare_application_for_dispatch
|
192
|
+
# Dispatcher.prepare_application_for_dispatch
|
193
|
+
ActiveRecord::Base.verify_active_connections!
|
193
194
|
end
|
194
195
|
end
|
195
196
|
|
196
197
|
def reset_application
|
197
198
|
if defined? Rails
|
198
|
-
Dispatcher.reset_application_after_dispatch
|
199
|
+
# Dispatcher.reset_application_after_dispatch
|
199
200
|
end
|
200
201
|
end
|
201
202
|
|
@@ -215,39 +216,33 @@ module ActiveMessaging
|
|
215
216
|
end
|
216
217
|
|
217
218
|
def _dispatch(message)
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
}
|
233
|
-
begin
|
234
|
-
execute_filter_chain(:incoming, message, routing) do |m|
|
235
|
-
result = subscription.processor_class.new.process!(m)
|
236
|
-
end
|
237
|
-
rescue ActiveMessaging::AbortMessageException
|
238
|
-
abort_message subscription, message
|
239
|
-
abort = true
|
240
|
-
return
|
241
|
-
ensure
|
242
|
-
acknowledge_message subscription, message unless abort
|
219
|
+
abort = false
|
220
|
+
processed = false
|
221
|
+
|
222
|
+
subscriptions.each do |key, subscription|
|
223
|
+
if message.matches_subscription?(subscription) then
|
224
|
+
processed = true
|
225
|
+
routing = {
|
226
|
+
:receiver=>subscription.processor_class,
|
227
|
+
:destination=>subscription.destination,
|
228
|
+
:direction => :incoming
|
229
|
+
}
|
230
|
+
begin
|
231
|
+
execute_filter_chain(:incoming, message, routing) do |m|
|
232
|
+
result = subscription.processor_class.new.process!(m)
|
243
233
|
end
|
234
|
+
rescue ActiveMessaging::AbortMessageException
|
235
|
+
abort_message subscription, message
|
236
|
+
abort = true
|
237
|
+
return
|
238
|
+
ensure
|
239
|
+
acknowledge_message subscription, message unless abort
|
244
240
|
end
|
245
241
|
end
|
246
|
-
|
247
|
-
ActiveMessaging.logger.error("No-one responded to #{message}") unless processed
|
248
|
-
else
|
249
|
-
ActiveMessaging.logger.error('Unknown message command: ' + message.inspect)
|
250
242
|
end
|
243
|
+
|
244
|
+
ActiveMessaging.logger.error("No-one responded to #{message}") unless processed
|
245
|
+
|
251
246
|
end
|
252
247
|
|
253
248
|
# acknowledge_message is called when the message has been processed w/o error by at least one processor
|
@@ -268,7 +263,7 @@ module ActiveMessaging
|
|
268
263
|
|
269
264
|
def destination destination_name, destination, publish_headers={}, broker='default'
|
270
265
|
raise "You already defined #{destination_name} to #{named_destinations[destination_name].value}" if named_destinations.has_key?(destination_name)
|
271
|
-
named_destinations[destination_name] = Destination.new
|
266
|
+
named_destinations[destination_name] = Destination.new(destination_name, destination, publish_headers, broker)
|
272
267
|
end
|
273
268
|
|
274
269
|
alias queue destination
|
@@ -385,16 +380,10 @@ module ActiveMessaging
|
|
385
380
|
@destination, @processor_class, @subscribe_headers = destination, processor_class, subscribe_headers
|
386
381
|
subscribe_headers['id'] = processor_class.name.underscore unless subscribe_headers.key? 'id'
|
387
382
|
end
|
388
|
-
|
389
|
-
def matches?(message)
|
390
|
-
message.headers['destination'].to_s == @destination.value.to_s
|
391
|
-
end
|
392
|
-
|
383
|
+
|
393
384
|
def subscribe
|
394
385
|
ActiveMessaging.logger.error "=> Subscribing to #{destination.value} (processed by #{processor_class})"
|
395
386
|
Gateway.connection(@destination.broker_name).subscribe(@destination.value, subscribe_headers)
|
396
|
-
# FIXME (uwe): Not sure why this needs to happen here
|
397
|
-
@processor = @processor_class.new
|
398
387
|
end
|
399
388
|
|
400
389
|
def unsubscribe
|
@@ -406,15 +395,23 @@ module ActiveMessaging
|
|
406
395
|
class Destination
|
407
396
|
DEFAULT_PUBLISH_HEADERS = { :persistent=>true }
|
408
397
|
|
409
|
-
attr_accessor :name, :value, :publish_headers, :broker_name
|
398
|
+
attr_accessor :name, :value, :publish_headers, :broker_name, :wildcard
|
410
399
|
|
411
400
|
def initialize(name, value, publish_headers, broker_name)
|
412
401
|
@name, @value, @publish_headers, @broker_name = name, value, publish_headers, broker_name
|
413
402
|
@publish_headers.reverse_merge! DEFAULT_PUBLISH_HEADERS
|
403
|
+
@wildcard = wildcard_match_exp_for(value) if (value =~ /\*/)
|
414
404
|
end
|
415
405
|
|
416
406
|
def to_s
|
417
|
-
"#{broker_name}
|
407
|
+
"<destination: #{broker_name} :#{name}=>'#{value}'>"
|
408
|
+
end
|
409
|
+
|
410
|
+
private
|
411
|
+
|
412
|
+
def wildcard_match_exp_for(destination)
|
413
|
+
exp = destination.to_s.gsub(/[.]/, '\.').gsub(/[*]/, '[^.*]+').gsub(/([>].*$)/, '.*') + '$'
|
414
|
+
Regexp.new(exp)
|
418
415
|
end
|
419
416
|
|
420
417
|
end
|