activemessaging 0.6.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/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
|