gz_activemessaging 0.13.1
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.
- 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
|