activemessaging 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +50 -0
- data/generators/a13g_test_harness/a13g_test_harness_generator.rb +19 -0
- data/generators/a13g_test_harness/templates/active_messaging_test.rhtml +13 -0
- data/generators/a13g_test_harness/templates/active_messaging_test_controller.rb +29 -0
- data/generators/a13g_test_harness/templates/index.rhtml +17 -0
- data/generators/filter/USAGE +0 -0
- data/generators/filter/filter_generator.rb +19 -0
- data/generators/filter/templates/filter.rb +12 -0
- data/generators/filter/templates/filter_test.rb +28 -0
- data/generators/processor/USAGE +8 -0
- data/generators/processor/processor_generator.rb +31 -0
- data/generators/processor/templates/application.rb +18 -0
- data/generators/processor/templates/broker.yml +79 -0
- data/generators/processor/templates/jruby_poller +117 -0
- data/generators/processor/templates/messaging.rb +12 -0
- data/generators/processor/templates/poller +23 -0
- data/generators/processor/templates/poller.rb +23 -0
- data/generators/processor/templates/processor.rb +8 -0
- data/generators/processor/templates/processor_test.rb +20 -0
- data/generators/tracer/USAGE +8 -0
- data/generators/tracer/templates/controller.rb +14 -0
- data/generators/tracer/templates/helper.rb +2 -0
- data/generators/tracer/templates/index.rhtml +4 -0
- data/generators/tracer/templates/layout.rhtml +16 -0
- data/generators/tracer/templates/trace_processor.rb +100 -0
- data/generators/tracer/tracer_generator.rb +25 -0
- data/lib/activemessaging.rb +133 -0
- data/lib/activemessaging/adapter.rb +21 -0
- data/lib/activemessaging/adapters/asqs.rb +412 -0
- data/lib/activemessaging/adapters/base.rb +82 -0
- data/lib/activemessaging/adapters/jms.rb +237 -0
- data/lib/activemessaging/adapters/reliable_msg.rb +190 -0
- data/lib/activemessaging/adapters/stomp.rb +99 -0
- data/lib/activemessaging/adapters/test.rb +155 -0
- data/lib/activemessaging/adapters/wmq.rb +202 -0
- data/lib/activemessaging/filter.rb +29 -0
- data/lib/activemessaging/gateway.rb +422 -0
- data/lib/activemessaging/message_sender.rb +30 -0
- data/lib/activemessaging/named_base.rb +54 -0
- data/lib/activemessaging/processor.rb +45 -0
- data/lib/activemessaging/support.rb +17 -0
- data/lib/activemessaging/test_helper.rb +194 -0
- data/lib/activemessaging/trace_filter.rb +34 -0
- data/messaging.rb.example +5 -0
- data/tasks/start_consumers.rake +8 -0
- metadata +123 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'stomp'
|
2
|
+
|
3
|
+
module ActiveMessaging
|
4
|
+
module Adapters
|
5
|
+
module Stomp
|
6
|
+
|
7
|
+
class Connection < ::Stomp::Connection
|
8
|
+
include ActiveMessaging::Adapter
|
9
|
+
register :stomp
|
10
|
+
|
11
|
+
attr_accessor :reliable, :retryMax, :deadLetterQueue
|
12
|
+
|
13
|
+
def initialize(cfg)
|
14
|
+
@retryMax = cfg[:retryMax] || 0
|
15
|
+
@deadLetterQueue = cfg[:deadLetterQueue] || nil
|
16
|
+
|
17
|
+
cfg[:login] ||= ""
|
18
|
+
cfg[:passcode] ||= ""
|
19
|
+
cfg[:host] ||= "localhost"
|
20
|
+
cfg[:port] ||= "61613"
|
21
|
+
cfg[:reliable] ||= TRUE
|
22
|
+
cfg[:reconnectDelay] ||= 5
|
23
|
+
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
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def received message, headers={}
|
34
|
+
#check to see if the ack mode for this subscription is auto or client
|
35
|
+
# if the ack mode is client, send an ack
|
36
|
+
if (headers[:ack] === 'client')
|
37
|
+
ack_headers = message.headers.has_key?(:transaction) ? message.headers[:transaction] : {}
|
38
|
+
ack message.headers['message-id'], ack_headers
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unreceive message, headers={}
|
43
|
+
retry_count = message.headers['a13g-retry-count'].to_i || 0
|
44
|
+
transaction_id = "transaction-#{message.headers['message-id']}-#{retry_count}"
|
45
|
+
|
46
|
+
# start a transaction, send the message back to the original destination
|
47
|
+
self.begin(transaction_id)
|
48
|
+
begin
|
49
|
+
|
50
|
+
if @retryMax > 0
|
51
|
+
retry_headers = message.headers.stringify_keys
|
52
|
+
retry_headers['transaction']= transaction_id
|
53
|
+
retry_headers.delete('content-length')
|
54
|
+
retry_headers.delete('content-type')
|
55
|
+
|
56
|
+
if retry_count < @retryMax
|
57
|
+
# now send the message back to the destination
|
58
|
+
# set the headers for message id, priginal message id, and retry count
|
59
|
+
retry_headers['a13g-original-message-id'] = retry_headers['message-id'] unless retry_headers.has_key?('a13g-original-message-id')
|
60
|
+
retry_headers.delete('message-id')
|
61
|
+
|
62
|
+
retry_headers['a13g-original-timestamp'] = retry_headers['timestamp'] unless retry_headers.has_key?('a13g-original-timestamp')
|
63
|
+
retry_headers.delete('timestamp')
|
64
|
+
|
65
|
+
retry_headers['a13g-retry-count'] = retry_count + 1
|
66
|
+
|
67
|
+
# send the updated message to retry in the same transaction
|
68
|
+
self.send retry_headers['destination'], message.body, retry_headers
|
69
|
+
|
70
|
+
elsif retry_count >= @retryMax && @deadLetterQueue
|
71
|
+
# send the 'poison pill' message to the dead letter queue
|
72
|
+
retry_headers['a13g-original-destination'] = retry_headers.delete('destination')
|
73
|
+
retry_headers.delete('message-id')
|
74
|
+
self.send @deadLetterQueue, message.body, retry_headers
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
#check to see if the ack mode is client, and if it is, ack it in this transaction
|
80
|
+
if (headers[:ack] === 'client')
|
81
|
+
# ack the original message
|
82
|
+
self.ack message.headers['message-id'], message.headers.stringify_keys.merge('transaction'=>transaction_id)
|
83
|
+
end
|
84
|
+
|
85
|
+
# now commit the transaction
|
86
|
+
self.commit transaction_id
|
87
|
+
rescue Exception=>exc
|
88
|
+
# if there is an error, try to abort the transaction, then raise the error
|
89
|
+
self.abort transaction_id
|
90
|
+
raise exc
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'activemessaging/adapter'
|
2
|
+
|
3
|
+
module ActiveMessaging
|
4
|
+
module Adapters
|
5
|
+
module Test
|
6
|
+
|
7
|
+
class Connection
|
8
|
+
include ActiveMessaging::Adapter
|
9
|
+
register :test
|
10
|
+
|
11
|
+
attr_accessor :config, :subscriptions, :destinations, :connected, :received_messages, :unreceived_messages
|
12
|
+
|
13
|
+
def initialize cfg
|
14
|
+
@config = cfg
|
15
|
+
@subscriptions = []
|
16
|
+
@destinations = []
|
17
|
+
@received_messages = []
|
18
|
+
@unreceived_messages = []
|
19
|
+
@connected = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def disconnect
|
23
|
+
@subscriptions = []
|
24
|
+
@destinations = []
|
25
|
+
@received_messages = []
|
26
|
+
@unreceived_messages = []
|
27
|
+
@connected = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def subscribe destination_name, subscribe_headers={}
|
31
|
+
open_destination destination_name
|
32
|
+
unless @subscriptions.find {|s| s.name == destination_name}
|
33
|
+
@subscriptions << Subscription.new(destination_name, subscribe_headers)
|
34
|
+
end
|
35
|
+
@subscriptions.last
|
36
|
+
end
|
37
|
+
|
38
|
+
def unsubscribe destination_name, unsubscribe_headers={}
|
39
|
+
@subscriptions.delete_if {|s| s.name == destination_name}
|
40
|
+
end
|
41
|
+
|
42
|
+
def send destination_name, message_body, message_headers={}
|
43
|
+
open_destination destination_name
|
44
|
+
destination = find_destination destination_name
|
45
|
+
destination.send Message.new(message_headers, nil, message_body, nil, destination)
|
46
|
+
end
|
47
|
+
|
48
|
+
def receive
|
49
|
+
destination = @destinations.find do |q|
|
50
|
+
find_subscription(q.name) && !q.empty?
|
51
|
+
end
|
52
|
+
destination.receive unless destination.nil?
|
53
|
+
end
|
54
|
+
|
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
|
+
def received message, headers={}
|
65
|
+
@received_messages << message
|
66
|
+
end
|
67
|
+
|
68
|
+
def unreceive message, headers={}
|
69
|
+
@unreceived_messages << message
|
70
|
+
end
|
71
|
+
|
72
|
+
#test helper methods
|
73
|
+
def find_message destination_name, body
|
74
|
+
all_messages.find do |m|
|
75
|
+
m.headers['destination'] == destination_name && if body.is_a?(Regexp)
|
76
|
+
m.body =~ body
|
77
|
+
else
|
78
|
+
m.body == body.to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def open_destination destination_name
|
84
|
+
unless find_destination destination_name
|
85
|
+
@destinations << Destination.new(destination_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def find_destination destination_name
|
90
|
+
@destinations.find{|q| q.name == destination_name }
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_subscription destination_name
|
94
|
+
@subscriptions.find{|s| s.name == destination_name}
|
95
|
+
end
|
96
|
+
|
97
|
+
def all_messages
|
98
|
+
@destinations.map {|q| q.messages }.flatten
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Destination
|
103
|
+
|
104
|
+
attr_accessor :name, :messages
|
105
|
+
|
106
|
+
def initialize name
|
107
|
+
@name = name
|
108
|
+
@messages = []
|
109
|
+
end
|
110
|
+
|
111
|
+
def receive
|
112
|
+
@messages.shift
|
113
|
+
end
|
114
|
+
|
115
|
+
def send message
|
116
|
+
@messages << message
|
117
|
+
end
|
118
|
+
|
119
|
+
def empty?
|
120
|
+
@messages.empty?
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_s
|
124
|
+
"<Test::Destination name='#{name}' messages='#{@messages.inspect}'>"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Subscription
|
129
|
+
attr_accessor :name, :headers
|
130
|
+
|
131
|
+
def initialize name, headers
|
132
|
+
@name = name
|
133
|
+
@headers = headers
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_s
|
137
|
+
"<Test::Subscription destination='#{name}' headers='#{headers.inspect}' >"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
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
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Copyright 2007 S. Perez. RBC Dexia Investor Servies Bank
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
################################################################################
|
16
|
+
|
17
|
+
#
|
18
|
+
# WebSphere MQ adapter for activemessaging
|
19
|
+
#
|
20
|
+
require 'wmq/wmq'
|
21
|
+
|
22
|
+
module ActiveMessaging
|
23
|
+
module Adapters
|
24
|
+
module Adapter
|
25
|
+
|
26
|
+
# Connection class needed by a13g
|
27
|
+
class Connection
|
28
|
+
include ActiveMessaging::Adapter
|
29
|
+
register :wmq
|
30
|
+
|
31
|
+
# Needed by a13g but never used within this adapter
|
32
|
+
attr_accessor :reliable
|
33
|
+
|
34
|
+
# Generic init method needed by a13g
|
35
|
+
def initialize(cfg)
|
36
|
+
# Set default values
|
37
|
+
cfg[:poll_interval] ||= 0.1
|
38
|
+
|
39
|
+
# Initialize instance members
|
40
|
+
# Trick for the connection_options is to allow settings WMQ constants directly in broker.yml :))
|
41
|
+
@connection_options = cfg.each_pair {|key, value| cfg[key] = instance_eval(value) if (value.instance_of?(String) && value.match("WMQ::")) }
|
42
|
+
@queue_names = []
|
43
|
+
@current_queue = 0
|
44
|
+
@queues = {}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Disconnect method needed by a13g
|
48
|
+
# No need to disconnect from the queue manager since connection and disconnection occurs inside the send and receive methods
|
49
|
+
# headers is never used
|
50
|
+
def disconnect(headers = {})
|
51
|
+
end
|
52
|
+
|
53
|
+
# Receive method needed by a13g
|
54
|
+
def receive
|
55
|
+
raise "No subscription to receive messages from" if (@queue_names.nil? || @queue_names.empty?)
|
56
|
+
start = @current_queue
|
57
|
+
while true
|
58
|
+
@current_queue = ((@current_queue < @queue_names.length-1) ? @current_queue + 1 : 0)
|
59
|
+
sleep(@connection_options[:poll_interval]) if (@current_queue == start)
|
60
|
+
q = @queues[@queue_names[@current_queue]]
|
61
|
+
unless q.nil?
|
62
|
+
message = retrieve_message(q)
|
63
|
+
return message unless message.nil?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Send method needed by a13g
|
69
|
+
# headers may contains 2 different hashes to gives more control over the sending process
|
70
|
+
# :descriptor => {...} to populate the descriptor of the message
|
71
|
+
# :put_options => {...} to specify the put options for that message
|
72
|
+
def send(q_name, message_data, headers={})
|
73
|
+
WMQ::QueueManager.connect(@connection_options) do |qmgr|
|
74
|
+
qmgr.open_queue(:q_name => q_name, :mode => :output) do |queue|
|
75
|
+
|
76
|
+
message_descriptor = headers[:descriptor] || {:format => WMQ::MQFMT_STRING}
|
77
|
+
put_options = headers[:put_options].nil? ? {} : headers[:put_options].dup
|
78
|
+
|
79
|
+
wmq_message = WMQ::Message.new(:data => message_data, :descriptor => message_descriptor)
|
80
|
+
queue.put(put_options.merge(:message => wmq_message, :data => nil))
|
81
|
+
return Message.new(wmq_message, q_name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Subscribe method needed by a13g
|
87
|
+
# headers may contains a hash to give more control over the get operation on the queue
|
88
|
+
# :get_options => {...} to specify the get options when receiving messages
|
89
|
+
# Warning : get options are set only on the first queue subscription and are common to all the queue's subscriptions
|
90
|
+
# Any other get options passed with subsequent subscribe on an existing queue will be discarded
|
91
|
+
# subId is never used
|
92
|
+
def subscribe(q_name, headers={}, subId=NIL)
|
93
|
+
if @queues[q_name].nil?
|
94
|
+
get_options = headers[:get_options] || {}
|
95
|
+
q = Queue.new(q_name, get_options)
|
96
|
+
@queues[q_name] = q
|
97
|
+
@queue_names << q.name
|
98
|
+
end
|
99
|
+
|
100
|
+
q.add_subscription
|
101
|
+
end
|
102
|
+
|
103
|
+
# Unsubscribe method needed by a13g
|
104
|
+
# Stop listening the queue only after the last unsubscription
|
105
|
+
# headers is never used
|
106
|
+
# subId is never used
|
107
|
+
def unsubscribe(q_name, headers={}, subId=NIL)
|
108
|
+
q = @queues[q_name]
|
109
|
+
unless q.nil?
|
110
|
+
q.remove_subscription
|
111
|
+
unless q.has_subscription?
|
112
|
+
@queues.delete(q_name)
|
113
|
+
@queue_names.delete(q_name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# called after a message is successfully received and processed
|
119
|
+
def received message, headers={}
|
120
|
+
end
|
121
|
+
|
122
|
+
# called after a message is successfully received but unsuccessfully processed
|
123
|
+
# purpose is to return the message to the destination so receiving and processing and be attempted again
|
124
|
+
def unreceive message, headers={}
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# Retrieve the first available message from the specicied queue
|
130
|
+
# Return nil if queue is empty
|
131
|
+
def retrieve_message(q)
|
132
|
+
WMQ::QueueManager.connect(@connection_options) do |qmgr|
|
133
|
+
qmgr.open_queue(:q_name => q.name, :mode => :input) do |queue|
|
134
|
+
|
135
|
+
get_options = q.get_options.dup
|
136
|
+
wmq_message = WMQ::Message.new
|
137
|
+
|
138
|
+
if queue.get(get_options.merge(:message => wmq_message))
|
139
|
+
return Message.new(wmq_message, q.name)
|
140
|
+
else
|
141
|
+
return nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Message class needed by a13g (based on the same Message class in Stomp adapter)
|
149
|
+
# Contains a reference to the MQ message object ;-) !
|
150
|
+
class Message
|
151
|
+
# Accessors needed by a13g
|
152
|
+
attr_accessor :headers, :body, :command, :wmq_message
|
153
|
+
|
154
|
+
def initialize(wmq_message, q_name)
|
155
|
+
@wmq_message = wmq_message
|
156
|
+
|
157
|
+
# Needed by a13g
|
158
|
+
@headers = {'destination' => q_name}
|
159
|
+
@body = wmq_message.data
|
160
|
+
@command = 'MESSAGE'
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
"<Adapter::Message headers=#{@headers.inspect} body='#{@body}' command='#{@command}' wmq_message=#{@wmq_message}>"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
# Queue class is used to keep track of the subscriptions
|
171
|
+
# It contains :
|
172
|
+
# - name of the queue
|
173
|
+
# - options to use when getting from the queue
|
174
|
+
# - number of subscriptions
|
175
|
+
class Queue
|
176
|
+
attr_accessor :name, :get_options, :nb_subscriptions
|
177
|
+
|
178
|
+
def initialize(name, get_options)
|
179
|
+
@name, @get_options = name, get_options
|
180
|
+
@nb_subscriptions = 0
|
181
|
+
end
|
182
|
+
|
183
|
+
def add_subscription
|
184
|
+
@nb_subscriptions += 1
|
185
|
+
end
|
186
|
+
|
187
|
+
def remove_subscription
|
188
|
+
@nb_subscriptions -= 1 unless @nb_subscriptions > 0
|
189
|
+
end
|
190
|
+
|
191
|
+
def has_subscription?
|
192
|
+
@nb_subscriptions > 0
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_s
|
196
|
+
"<Adapter::Queue name='#{@name}' get_options=#{@get_options} nb_subscriptions=#{@nb_subscriptions}>"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|