gz_activemessaging 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.travis.yml +40 -0
- data/Appraisals +19 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +87 -0
- data/README.md +27 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/activemessaging.gemspec +137 -0
- data/gemfiles/activesupport23.gemfile +10 -0
- data/gemfiles/activesupport23.gemfile.lock +51 -0
- data/gemfiles/activesupport30.gemfile +11 -0
- data/gemfiles/activesupport30.gemfile.lock +53 -0
- data/gemfiles/activesupport31.gemfile +11 -0
- data/gemfiles/activesupport31.gemfile.lock +55 -0
- data/gemfiles/activesupport32.gemfile +10 -0
- data/gemfiles/activesupport32.gemfile.lock +55 -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_processor.rb +18 -0
- data/generators/processor/templates/broker.yml +140 -0
- data/generators/processor/templates/jruby_poller +117 -0
- data/generators/processor/templates/messaging.rb +12 -0
- data/generators/processor/templates/poller +25 -0
- data/generators/processor/templates/poller.rb +26 -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/init.rb +1 -0
- data/lib/activemessaging.rb +133 -0
- data/lib/activemessaging/adapter.rb +20 -0
- data/lib/activemessaging/adapters/amqp.rb +215 -0
- data/lib/activemessaging/adapters/asqs.rb +487 -0
- data/lib/activemessaging/adapters/base.rb +71 -0
- data/lib/activemessaging/adapters/beanstalk.rb +88 -0
- data/lib/activemessaging/adapters/jms.rb +243 -0
- data/lib/activemessaging/adapters/reliable_msg.rb +186 -0
- data/lib/activemessaging/adapters/stomp.rb +212 -0
- data/lib/activemessaging/adapters/synch.rb +95 -0
- data/lib/activemessaging/adapters/test.rb +137 -0
- data/lib/activemessaging/adapters/wmq.rb +193 -0
- data/lib/activemessaging/base_message.rb +28 -0
- data/lib/activemessaging/filter.rb +29 -0
- data/lib/activemessaging/gateway.rb +429 -0
- data/lib/activemessaging/message_sender.rb +30 -0
- data/lib/activemessaging/named_base.rb +54 -0
- data/lib/activemessaging/processor.rb +44 -0
- data/lib/activemessaging/railtie.rb +26 -0
- data/lib/activemessaging/test_helper.rb +189 -0
- data/lib/activemessaging/threaded_poller.rb +234 -0
- data/lib/activemessaging/trace_filter.rb +34 -0
- data/lib/generators/active_messaging/install/USAGE +21 -0
- data/lib/generators/active_messaging/install/install_generator.rb +39 -0
- data/lib/generators/active_messaging/install/templates/application_processor.rb +18 -0
- data/lib/generators/active_messaging/install/templates/broker.yml +139 -0
- data/lib/generators/active_messaging/install/templates/poller +24 -0
- data/lib/generators/active_messaging/install/templates/poller.rb +22 -0
- data/lib/generators/active_messaging/install/templates/threaded_poller +46 -0
- data/lib/generators/active_messaging/processor/USAGE +2 -0
- data/lib/generators/active_messaging/processor/processor_generator.rb +39 -0
- data/lib/generators/active_messaging/processor/templates/messaging.rb +12 -0
- data/lib/generators/active_messaging/processor/templates/processor.rb +8 -0
- data/lib/generators/active_messaging/processor/templates/processor_spec.rb +24 -0
- data/lib/generators/active_messaging/processor/templates/processor_test.rb +20 -0
- data/lib/tasks/start_consumers.rake +8 -0
- 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 +125 -0
- data/test/config_test.rb +42 -0
- data/test/filter_test.rb +131 -0
- data/test/gateway_test.rb +220 -0
- data/test/jms_test.rb +64 -0
- data/test/reliable_msg_test.rb +83 -0
- data/test/stomp_test.rb +168 -0
- data/test/test_helper.rb +36 -0
- data/test/tracer_test.rb +57 -0
- metadata +202 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'stomp'
|
2
|
+
|
3
|
+
require 'activemessaging/adapters/base'
|
4
|
+
|
5
|
+
module ActiveMessaging
|
6
|
+
module Adapters
|
7
|
+
module Stomp
|
8
|
+
|
9
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
10
|
+
register :stomp
|
11
|
+
|
12
|
+
attr_accessor :stomp_connection, :retryMax, :deadLetterQueue, :configuration, :deadLetterQueuePrefix
|
13
|
+
|
14
|
+
def initialize(cfg)
|
15
|
+
@retryMax = cfg[:retryMax] || 0
|
16
|
+
@deadLetterQueue = cfg[:deadLetterQueue] || nil
|
17
|
+
@deadLetterQueuePrefix = cfg[:deadLetterQueuePrefix] || nil
|
18
|
+
|
19
|
+
cfg[:login] ||= ""
|
20
|
+
cfg[:passcode] ||= ""
|
21
|
+
cfg[:host] ||= "localhost"
|
22
|
+
cfg[:port] ||= "61613"
|
23
|
+
cfg[:reliable] = cfg[:reliable].nil? ? TRUE : cfg[:reliable]
|
24
|
+
cfg[:reconnectDelay] ||= 5
|
25
|
+
cfg[:clientId] ||= nil
|
26
|
+
|
27
|
+
# hold on to the config
|
28
|
+
@configuration = cfg
|
29
|
+
|
30
|
+
# create a new stomp connection
|
31
|
+
connect_headers = cfg[:connect_headers] || {}
|
32
|
+
connect_headers['client-id'] = cfg[:clientId] if cfg[:clientId]
|
33
|
+
@stomp_connection = ::Stomp::Connection.new(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay], connect_headers)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Checks if the connection supports dead letter queues
|
37
|
+
def supports_dlq?
|
38
|
+
!@deadLetterQueue.nil? || !@deadLetterQueuePrefix.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# add the dead letter queue prefix to the destination
|
42
|
+
def add_dlq_prefix(destination)
|
43
|
+
if (ri = destination.rindex("/"))
|
44
|
+
destination.clone.insert(ri + 1, @deadLetterQueuePrefix)
|
45
|
+
else
|
46
|
+
@deadLetterQueuePrefix + destination
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# called to cleanly get rid of connection
|
52
|
+
def disconnect
|
53
|
+
@stomp_connection.disconnect
|
54
|
+
end
|
55
|
+
|
56
|
+
# destination_name string, headers hash
|
57
|
+
# subscribe to listen on a destination
|
58
|
+
def subscribe destination_name, message_headers={}
|
59
|
+
@stomp_connection.subscribe(destination_name, message_headers)
|
60
|
+
end
|
61
|
+
|
62
|
+
# destination_name string, headers hash
|
63
|
+
# unsubscribe to listen on a destination
|
64
|
+
def unsubscribe destination_name, message_headers={}
|
65
|
+
@stomp_connection.unsubscribe(destination_name, message_headers)
|
66
|
+
end
|
67
|
+
|
68
|
+
# destination_name string, body string, headers hash
|
69
|
+
# send a single message to a destination
|
70
|
+
def send destination_name, message_body, message_headers={}
|
71
|
+
stomp_publish(destination_name, message_body, message_headers)
|
72
|
+
end
|
73
|
+
|
74
|
+
# receive a single message from any of the subscribed destinations
|
75
|
+
# check each destination once, then sleep for poll_interval
|
76
|
+
def receive(options={})
|
77
|
+
m = @stomp_connection.receive
|
78
|
+
Message.new(m) if m
|
79
|
+
end
|
80
|
+
|
81
|
+
def received message, headers={}
|
82
|
+
#check to see if the ack mode for this subscription is auto or client
|
83
|
+
# if the ack mode is client or client-individual, send an ack
|
84
|
+
if (headers[:ack] === 'client' || headers[:ack] === 'client-individual')
|
85
|
+
ack_headers = message.headers.has_key?(:transaction) ? { :transaction=>message.headers[:transaction]} : {}
|
86
|
+
# All 3 protocol levels have unique ACK requirements.
|
87
|
+
case @stomp_connection.protocol
|
88
|
+
when ::Stomp::SPL_10
|
89
|
+
@stomp_connection.ack(message.headers['message-id'], ack_headers)
|
90
|
+
when ::Stomp::SPL_11
|
91
|
+
ack_headers['subscription'] = message.headers['subscription']
|
92
|
+
@stomp_connection.ack(message.headers['message-id'], ack_headers)
|
93
|
+
else # avoid ::Stomp::SPL_12 constant when using older Stomp gems
|
94
|
+
@stomp_connection.ack(message.headers['ack'], ack_headers)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# send has been deprecated in latest stomp gem (as it should be)
|
100
|
+
def stomp_publish(destination_name="", message_body="", message_headers={})
|
101
|
+
if @stomp_connection.respond_to?(:publish)
|
102
|
+
@stomp_connection.publish(destination_name, message_body, message_headers)
|
103
|
+
else
|
104
|
+
@stomp_connection.send(destination_name, message_body, message_headers)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def unreceive message, headers={}
|
109
|
+
retry_count = message.headers['a13g-retry-count'].to_i || 0
|
110
|
+
transaction_id = "transaction-#{message.headers['message-id']}-#{retry_count}"
|
111
|
+
# start a transaction, send the message back to the original destination
|
112
|
+
@stomp_connection.begin(transaction_id)
|
113
|
+
begin
|
114
|
+
|
115
|
+
if @retryMax > 0
|
116
|
+
retry_headers = message.headers.stringify_keys
|
117
|
+
retry_headers['transaction']= transaction_id
|
118
|
+
content_type_header = retry_headers.delete('content-type')
|
119
|
+
content_length_header = retry_headers.delete('content-length')
|
120
|
+
# If the content-length header in the original message is nil
|
121
|
+
# then we need to set the :suppress_content_length option so
|
122
|
+
# that the stomp client does not set the content-length of the
|
123
|
+
# retried message. This option will allow ActiveMQ to interpret the
|
124
|
+
# message as a TextMessage.
|
125
|
+
# This is somewhat of a hack because the setting of the :suppress_content_length
|
126
|
+
# header is usually done in the messaging.rb and is removed by the time
|
127
|
+
# the unreceive message is called. So I am making some assumptions here
|
128
|
+
# on whether or not to set the option
|
129
|
+
if content_type_header and content_type_header.include?('text/plain') && content_length_header.nil?
|
130
|
+
retry_headers[:suppress_content_length] = true
|
131
|
+
end
|
132
|
+
|
133
|
+
retry_destination = retry_headers.delete('destination')
|
134
|
+
retry_destination = headers[:destination] if headers[:destination]
|
135
|
+
|
136
|
+
if retry_count < @retryMax
|
137
|
+
# now send the message back to the destination
|
138
|
+
# set the headers for message id, priginal message id, and retry count
|
139
|
+
retry_headers['a13g-original-message-id'] = retry_headers['message-id'] unless retry_headers.has_key?('a13g-original-message-id')
|
140
|
+
retry_headers.delete('message-id')
|
141
|
+
|
142
|
+
retry_headers['a13g-original-timestamp'] = retry_headers['timestamp'] unless retry_headers.has_key?('a13g-original-timestamp')
|
143
|
+
retry_headers.delete('timestamp')
|
144
|
+
|
145
|
+
retry_headers['a13g-retry-count'] = retry_count + 1
|
146
|
+
|
147
|
+
# send the updated message to retry in the same transaction
|
148
|
+
logger.warn "retrying message on #{retry_destination}"
|
149
|
+
self.stomp_publish(retry_destination, message.body, retry_headers)
|
150
|
+
|
151
|
+
elsif retry_count >= @retryMax && supports_dlq?
|
152
|
+
# send the 'poison pill' message to the dead letter queue - make it persistent by default
|
153
|
+
retry_headers['a13g-original-destination'] = retry_destination #retry_headers.delete('destination')
|
154
|
+
retry_headers['persistent'] = true
|
155
|
+
retry_headers.delete('message-id')
|
156
|
+
|
157
|
+
# If the prefix option is set then put the prefix after the /queue/ or /topic/
|
158
|
+
if (@deadLetterQueuePrefix)
|
159
|
+
dlq = add_dlq_prefix(retry_destination)
|
160
|
+
else
|
161
|
+
dlq = @deadLetterQueue
|
162
|
+
end
|
163
|
+
logger.warn "putting message on DLQ: #{dlq}"
|
164
|
+
self.stomp_publish(dlq, message.body, retry_headers)
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
#check to see if the ack mode is client, and if it is, ack it in this transaction
|
170
|
+
if (headers[:ack] === 'client')
|
171
|
+
# ack the original message
|
172
|
+
@stomp_connection.ack(message.headers['message-id'], message.headers.stringify_keys.merge('transaction'=>transaction_id))
|
173
|
+
end
|
174
|
+
|
175
|
+
# now commit the transaction
|
176
|
+
@stomp_connection.commit transaction_id
|
177
|
+
rescue Exception=>exc
|
178
|
+
# if there is an error, try to abort the transaction, then raise the error
|
179
|
+
@stomp_connection.abort transaction_id
|
180
|
+
raise exc
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
class Message < ActiveMessaging::BaseMessage
|
188
|
+
|
189
|
+
def initialize(msg)
|
190
|
+
super(msg.body, msg.headers['message-id'], msg.headers, msg.headers['destination'])
|
191
|
+
end
|
192
|
+
|
193
|
+
def matches_subscription?(subscription)
|
194
|
+
# if the subscription has been specified in the headers, rely on this
|
195
|
+
if self.headers['subscription'] && subscription.subscribe_headers['id']
|
196
|
+
self.headers['subscription'].to_s == subscription.subscribe_headers['id'].to_s
|
197
|
+
|
198
|
+
# see if the destination uses a wildcard representation
|
199
|
+
elsif subscription.destination.wildcard
|
200
|
+
self.destination.to_s =~ subscription.destination.wildcard
|
201
|
+
|
202
|
+
# no subscription id? no wildcard? use the name of the destination as a straight match
|
203
|
+
else
|
204
|
+
self.destination.to_s == subscription.destination.value.to_s
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'activemessaging/adapters/base'
|
2
|
+
#
|
3
|
+
# This is meant to avoid the need to use a broker in development, and generally make development mode easier
|
4
|
+
#
|
5
|
+
module ActiveMessaging
|
6
|
+
module Adapters
|
7
|
+
module Synch
|
8
|
+
|
9
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
10
|
+
register :synch
|
11
|
+
|
12
|
+
#configurable params
|
13
|
+
attr_accessor :configuration, :max_process, :processing_pids, :use_fork
|
14
|
+
|
15
|
+
#generic init method needed by a13g
|
16
|
+
def initialize cfg
|
17
|
+
ActiveMessaging.logger.debug "ActiveMessaging::Adapters::Synch::Connection.initialize: #{cfg.inspect}"
|
18
|
+
@configuration = cfg
|
19
|
+
|
20
|
+
@use_fork = !!@configuration[:use_fork]
|
21
|
+
|
22
|
+
# max at once
|
23
|
+
@max_process = 10
|
24
|
+
# keep track of the processes running
|
25
|
+
@processing_pids = {}
|
26
|
+
|
27
|
+
if use_fork
|
28
|
+
Thread.new {
|
29
|
+
watch_processes
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def watch_processes
|
35
|
+
while true
|
36
|
+
begin
|
37
|
+
pid = Process.wait(0, Process::WNOHANG)
|
38
|
+
if m = processing_pids.delete(pid)
|
39
|
+
ActiveMessaging.logger.debug "ActiveMessaging:synch - processing complete for pid (#{pid}):\n\t#{m}"
|
40
|
+
end
|
41
|
+
sleep(0.5)
|
42
|
+
rescue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def send destination_name, message_body, message_headers={}
|
48
|
+
message = Message.new(message_body, 'id', message_headers, destination_name, 'MESSAGE')
|
49
|
+
|
50
|
+
if use_fork
|
51
|
+
|
52
|
+
if processing_pids.size > max_process
|
53
|
+
ActiveMessaging.logger.debug "ActiveMessaging:synch too many processes: #{processing_pids.size} > #{max_process}"
|
54
|
+
sleep(0.5)
|
55
|
+
end
|
56
|
+
|
57
|
+
pid = fork {
|
58
|
+
ActiveMessaging.logger.debug "\n-------------------- ActiveMessaging:synch start fork dispath (#{Process.pid}) --------------------"
|
59
|
+
ActiveMessaging::Gateway.prepare_application
|
60
|
+
ActiveMessaging::Gateway._dispatch(message)
|
61
|
+
ActiveMessaging::Gateway.reset_application
|
62
|
+
ActiveMessaging.logger.debug "-------------------- ActiveMessaging:synch end fork dispath (#{Process.pid})--------------------\n"
|
63
|
+
}
|
64
|
+
|
65
|
+
Process.detach(pid)
|
66
|
+
processing_pids[pid] = "Destination: #{destination_name}, Message: #{message_body}"
|
67
|
+
|
68
|
+
else
|
69
|
+
|
70
|
+
ActiveMessaging.logger.debug "\n-------------------- ActiveMessaging:synch before dispath --------------------"
|
71
|
+
ActiveMessaging::Gateway.prepare_application
|
72
|
+
ActiveMessaging::Gateway._dispatch(message)
|
73
|
+
ActiveMessaging::Gateway.reset_application
|
74
|
+
ActiveMessaging.logger.debug "-------------------- ActiveMessaging:synch after dispath --------------------\n"
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
class Message < ActiveMessaging::BaseMessage
|
83
|
+
attr_accessor :command
|
84
|
+
|
85
|
+
def initialize body, id, headers, destination, command='MESSAGE'
|
86
|
+
# ActiveMessaging.logger.debug "Message headers:#{headers.inspect}, id:#{id}, body:#{body}, destination:#{destination}, command:#{command}"
|
87
|
+
@headers, @body, @destination, @command = headers, body, destination, command
|
88
|
+
headers['destination'] = destination
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'activemessaging/adapters/base'
|
2
|
+
|
3
|
+
module ActiveMessaging
|
4
|
+
module Adapters
|
5
|
+
module Test
|
6
|
+
|
7
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
8
|
+
register :test
|
9
|
+
|
10
|
+
attr_accessor :config, :subscriptions, :destinations, :connected, :received_messages, :unreceived_messages
|
11
|
+
|
12
|
+
def initialize cfg
|
13
|
+
@config = cfg
|
14
|
+
@subscriptions = []
|
15
|
+
@destinations = []
|
16
|
+
@received_messages = []
|
17
|
+
@unreceived_messages = []
|
18
|
+
@connected = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def disconnect
|
22
|
+
@subscriptions = []
|
23
|
+
@destinations = []
|
24
|
+
@received_messages = []
|
25
|
+
@unreceived_messages = []
|
26
|
+
@connected = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def subscribe destination_name, subscribe_headers={}
|
30
|
+
open_destination destination_name
|
31
|
+
unless @subscriptions.find {|s| s.name == destination_name}
|
32
|
+
@subscriptions << Subscription.new(destination_name, subscribe_headers)
|
33
|
+
end
|
34
|
+
@subscriptions.last
|
35
|
+
end
|
36
|
+
|
37
|
+
def unsubscribe destination_name, unsubscribe_headers={}
|
38
|
+
@subscriptions.delete_if {|s| s.name == destination_name}
|
39
|
+
end
|
40
|
+
|
41
|
+
def send destination_name, message_body, message_headers={}
|
42
|
+
open_destination destination_name
|
43
|
+
destination = find_destination destination_name
|
44
|
+
destination.send Message.new(message_body, nil, message_headers, destination_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def receive(options={})
|
48
|
+
destination = @destinations.find do |q|
|
49
|
+
find_subscription(q.name) && !q.empty?
|
50
|
+
end
|
51
|
+
destination.receive unless destination.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def received message, headers={}
|
55
|
+
@received_messages << message
|
56
|
+
end
|
57
|
+
|
58
|
+
def unreceive message, headers={}
|
59
|
+
@unreceived_messages << message
|
60
|
+
end
|
61
|
+
|
62
|
+
#test helper methods
|
63
|
+
def find_message destination_name, body
|
64
|
+
|
65
|
+
all_messages.find do |m|
|
66
|
+
m.destination == destination_name && if body.is_a?(Regexp)
|
67
|
+
m.body =~ body
|
68
|
+
else
|
69
|
+
m.body == body.to_s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def open_destination destination_name
|
75
|
+
unless find_destination destination_name
|
76
|
+
@destinations << Destination.new(destination_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_destination destination_name
|
81
|
+
@destinations.find{|q| q.name == destination_name }
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_subscription destination_name
|
85
|
+
@subscriptions.find{|s| s.name == destination_name}
|
86
|
+
end
|
87
|
+
|
88
|
+
def all_messages
|
89
|
+
@destinations.map {|q| q.messages }.flatten
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Destination
|
94
|
+
|
95
|
+
attr_accessor :name, :messages
|
96
|
+
|
97
|
+
def initialize name
|
98
|
+
@name = name
|
99
|
+
@messages = []
|
100
|
+
end
|
101
|
+
|
102
|
+
def receive
|
103
|
+
@messages.shift
|
104
|
+
end
|
105
|
+
|
106
|
+
def send message
|
107
|
+
@messages << message
|
108
|
+
end
|
109
|
+
|
110
|
+
def empty?
|
111
|
+
@messages.empty?
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s
|
115
|
+
"<Test::Destination name='#{name}' messages='#{@messages.inspect}'>"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class Subscription
|
120
|
+
attr_accessor :name, :headers
|
121
|
+
|
122
|
+
def initialize name, headers
|
123
|
+
@name = name
|
124
|
+
@headers = headers
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_s
|
128
|
+
"<Test::Subscription destination='#{name}' headers='#{headers.inspect}' >"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Message < ActiveMessaging::BaseMessage
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,193 @@
|
|
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
|
+
require 'activemessaging/adapters/base'
|
23
|
+
|
24
|
+
module ActiveMessaging
|
25
|
+
module Adapters
|
26
|
+
module Adapter
|
27
|
+
|
28
|
+
# Connection class needed by a13g
|
29
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
30
|
+
register :wmq
|
31
|
+
|
32
|
+
# Generic init method needed by a13g
|
33
|
+
def initialize(cfg)
|
34
|
+
# Set default values
|
35
|
+
cfg[:poll_interval] ||= 0.1
|
36
|
+
|
37
|
+
# Initialize instance members
|
38
|
+
# Trick for the connection_options is to allow settings WMQ constants directly in broker.yml :))
|
39
|
+
@connection_options = cfg.each_pair {|key, value| cfg[key] = instance_eval(value) if (value.instance_of?(String) && value.match("WMQ::")) }
|
40
|
+
@queue_names = []
|
41
|
+
@current_queue = 0
|
42
|
+
@queues = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Disconnect method needed by a13g
|
46
|
+
# No need to disconnect from the queue manager since connection and disconnection occurs inside the send and receive methods
|
47
|
+
# headers is never used
|
48
|
+
def disconnect(headers = {})
|
49
|
+
end
|
50
|
+
|
51
|
+
# Receive method needed by a13g
|
52
|
+
def receive(options={})
|
53
|
+
raise "No subscription to receive messages from" if (@queue_names.nil? || @queue_names.empty?)
|
54
|
+
start = @current_queue
|
55
|
+
while true
|
56
|
+
@current_queue = ((@current_queue < @queue_names.length-1) ? @current_queue + 1 : 0)
|
57
|
+
sleep(@connection_options[:poll_interval]) if (@current_queue == start)
|
58
|
+
q = @queues[@queue_names[@current_queue]]
|
59
|
+
unless q.nil?
|
60
|
+
message = retrieve_message(q)
|
61
|
+
return message unless message.nil?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Send method needed by a13g
|
67
|
+
# headers may contains 2 different hashes to gives more control over the sending process
|
68
|
+
# :descriptor => {...} to populate the descriptor of the message
|
69
|
+
# :put_options => {...} to specify the put options for that message
|
70
|
+
def send(q_name, message_data, headers={})
|
71
|
+
WMQ::QueueManager.connect(@connection_options) do |qmgr|
|
72
|
+
qmgr.open_queue(:q_name => q_name, :mode => :output) do |queue|
|
73
|
+
|
74
|
+
message_descriptor = headers[:descriptor] || {:format => WMQ::MQFMT_STRING}
|
75
|
+
put_options = headers[:put_options].nil? ? {} : headers[:put_options].dup
|
76
|
+
|
77
|
+
wmq_message = WMQ::Message.new(:data => message_data, :descriptor => message_descriptor)
|
78
|
+
queue.put(put_options.merge(:message => wmq_message, :data => nil))
|
79
|
+
return Message.new(wmq_message, q_name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Subscribe method needed by a13g
|
85
|
+
# headers may contains a hash to give more control over the get operation on the queue
|
86
|
+
# :get_options => {...} to specify the get options when receiving messages
|
87
|
+
# Warning : get options are set only on the first queue subscription and are common to all the queue's subscriptions
|
88
|
+
# Any other get options passed with subsequent subscribe on an existing queue will be discarded
|
89
|
+
# subId is never used
|
90
|
+
def subscribe(q_name, headers={}, subId=NIL)
|
91
|
+
if @queues[q_name].nil?
|
92
|
+
get_options = headers[:get_options] || {}
|
93
|
+
q = Queue.new(q_name, get_options)
|
94
|
+
@queues[q_name] = q
|
95
|
+
@queue_names << q.name
|
96
|
+
end
|
97
|
+
|
98
|
+
q.add_subscription
|
99
|
+
end
|
100
|
+
|
101
|
+
# Unsubscribe method needed by a13g
|
102
|
+
# Stop listening the queue only after the last unsubscription
|
103
|
+
# headers is never used
|
104
|
+
# subId is never used
|
105
|
+
def unsubscribe(q_name, headers={}, subId=NIL)
|
106
|
+
q = @queues[q_name]
|
107
|
+
unless q.nil?
|
108
|
+
q.remove_subscription
|
109
|
+
unless q.has_subscription?
|
110
|
+
@queues.delete(q_name)
|
111
|
+
@queue_names.delete(q_name)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# called after a message is successfully received and processed
|
117
|
+
def received message, headers={}
|
118
|
+
end
|
119
|
+
|
120
|
+
# called after a message is successfully received but unsuccessfully processed
|
121
|
+
# purpose is to return the message to the destination so receiving and processing and be attempted again
|
122
|
+
def unreceive message, headers={}
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# Retrieve the first available message from the specicied queue
|
128
|
+
# Return nil if queue is empty
|
129
|
+
def retrieve_message(q)
|
130
|
+
WMQ::QueueManager.connect(@connection_options) do |qmgr|
|
131
|
+
qmgr.open_queue(:q_name => q.name, :mode => :input) do |queue|
|
132
|
+
|
133
|
+
get_options = q.get_options.dup
|
134
|
+
wmq_message = WMQ::Message.new
|
135
|
+
|
136
|
+
if queue.get(get_options.merge(:message => wmq_message))
|
137
|
+
return Message.new(wmq_message, q.name)
|
138
|
+
else
|
139
|
+
return nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Message class needed by a13g (based on the same Message class in Stomp adapter)
|
147
|
+
# Contains a reference to the MQ message object ;-) !
|
148
|
+
class Message < ActiveMessaging::BaseMessage
|
149
|
+
|
150
|
+
attr_accessor :wmq_message
|
151
|
+
|
152
|
+
def initialize(wmq_message, q_name)
|
153
|
+
super(wmq_message.data, wmq_message.descriptor[:msg_id], {'destination' => q_name}, q_name)
|
154
|
+
@wmq_message = wmq_message
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Queue class is used to keep track of the subscriptions
|
162
|
+
# It contains :
|
163
|
+
# - name of the queue
|
164
|
+
# - options to use when getting from the queue
|
165
|
+
# - number of subscriptions
|
166
|
+
class Queue
|
167
|
+
attr_accessor :name, :get_options, :nb_subscriptions
|
168
|
+
|
169
|
+
def initialize(name, get_options)
|
170
|
+
@name, @get_options = name, get_options
|
171
|
+
@nb_subscriptions = 0
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_subscription
|
175
|
+
@nb_subscriptions += 1
|
176
|
+
end
|
177
|
+
|
178
|
+
def remove_subscription
|
179
|
+
@nb_subscriptions -= 1 unless @nb_subscriptions > 0
|
180
|
+
end
|
181
|
+
|
182
|
+
def has_subscription?
|
183
|
+
@nb_subscriptions > 0
|
184
|
+
end
|
185
|
+
|
186
|
+
def to_s
|
187
|
+
"<Adapter::Queue name='#{@name}' get_options=#{@get_options} nb_subscriptions=#{@nb_subscriptions}>"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|