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.
Files changed (92) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +40 -0
  3. data/Appraisals +19 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +87 -0
  6. data/README.md +27 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/activemessaging.gemspec +137 -0
  10. data/gemfiles/activesupport23.gemfile +10 -0
  11. data/gemfiles/activesupport23.gemfile.lock +51 -0
  12. data/gemfiles/activesupport30.gemfile +11 -0
  13. data/gemfiles/activesupport30.gemfile.lock +53 -0
  14. data/gemfiles/activesupport31.gemfile +11 -0
  15. data/gemfiles/activesupport31.gemfile.lock +55 -0
  16. data/gemfiles/activesupport32.gemfile +10 -0
  17. data/gemfiles/activesupport32.gemfile.lock +55 -0
  18. data/generators/a13g_test_harness/a13g_test_harness_generator.rb +19 -0
  19. data/generators/a13g_test_harness/templates/active_messaging_test.rhtml +13 -0
  20. data/generators/a13g_test_harness/templates/active_messaging_test_controller.rb +29 -0
  21. data/generators/a13g_test_harness/templates/index.rhtml +17 -0
  22. data/generators/filter/USAGE +0 -0
  23. data/generators/filter/filter_generator.rb +19 -0
  24. data/generators/filter/templates/filter.rb +12 -0
  25. data/generators/filter/templates/filter_test.rb +28 -0
  26. data/generators/processor/USAGE +8 -0
  27. data/generators/processor/processor_generator.rb +31 -0
  28. data/generators/processor/templates/application_processor.rb +18 -0
  29. data/generators/processor/templates/broker.yml +140 -0
  30. data/generators/processor/templates/jruby_poller +117 -0
  31. data/generators/processor/templates/messaging.rb +12 -0
  32. data/generators/processor/templates/poller +25 -0
  33. data/generators/processor/templates/poller.rb +26 -0
  34. data/generators/processor/templates/processor.rb +8 -0
  35. data/generators/processor/templates/processor_test.rb +20 -0
  36. data/generators/tracer/USAGE +8 -0
  37. data/generators/tracer/templates/controller.rb +14 -0
  38. data/generators/tracer/templates/helper.rb +2 -0
  39. data/generators/tracer/templates/index.rhtml +4 -0
  40. data/generators/tracer/templates/layout.rhtml +16 -0
  41. data/generators/tracer/templates/trace_processor.rb +100 -0
  42. data/generators/tracer/tracer_generator.rb +25 -0
  43. data/init.rb +1 -0
  44. data/lib/activemessaging.rb +133 -0
  45. data/lib/activemessaging/adapter.rb +20 -0
  46. data/lib/activemessaging/adapters/amqp.rb +215 -0
  47. data/lib/activemessaging/adapters/asqs.rb +487 -0
  48. data/lib/activemessaging/adapters/base.rb +71 -0
  49. data/lib/activemessaging/adapters/beanstalk.rb +88 -0
  50. data/lib/activemessaging/adapters/jms.rb +243 -0
  51. data/lib/activemessaging/adapters/reliable_msg.rb +186 -0
  52. data/lib/activemessaging/adapters/stomp.rb +212 -0
  53. data/lib/activemessaging/adapters/synch.rb +95 -0
  54. data/lib/activemessaging/adapters/test.rb +137 -0
  55. data/lib/activemessaging/adapters/wmq.rb +193 -0
  56. data/lib/activemessaging/base_message.rb +28 -0
  57. data/lib/activemessaging/filter.rb +29 -0
  58. data/lib/activemessaging/gateway.rb +429 -0
  59. data/lib/activemessaging/message_sender.rb +30 -0
  60. data/lib/activemessaging/named_base.rb +54 -0
  61. data/lib/activemessaging/processor.rb +44 -0
  62. data/lib/activemessaging/railtie.rb +26 -0
  63. data/lib/activemessaging/test_helper.rb +189 -0
  64. data/lib/activemessaging/threaded_poller.rb +234 -0
  65. data/lib/activemessaging/trace_filter.rb +34 -0
  66. data/lib/generators/active_messaging/install/USAGE +21 -0
  67. data/lib/generators/active_messaging/install/install_generator.rb +39 -0
  68. data/lib/generators/active_messaging/install/templates/application_processor.rb +18 -0
  69. data/lib/generators/active_messaging/install/templates/broker.yml +139 -0
  70. data/lib/generators/active_messaging/install/templates/poller +24 -0
  71. data/lib/generators/active_messaging/install/templates/poller.rb +22 -0
  72. data/lib/generators/active_messaging/install/templates/threaded_poller +46 -0
  73. data/lib/generators/active_messaging/processor/USAGE +2 -0
  74. data/lib/generators/active_messaging/processor/processor_generator.rb +39 -0
  75. data/lib/generators/active_messaging/processor/templates/messaging.rb +12 -0
  76. data/lib/generators/active_messaging/processor/templates/processor.rb +8 -0
  77. data/lib/generators/active_messaging/processor/templates/processor_spec.rb +24 -0
  78. data/lib/generators/active_messaging/processor/templates/processor_test.rb +20 -0
  79. data/lib/tasks/start_consumers.rake +8 -0
  80. data/poller.rb +14 -0
  81. data/test/all_tests.rb +10 -0
  82. data/test/app/config/broker.yml +4 -0
  83. data/test/asqs_test.rb +125 -0
  84. data/test/config_test.rb +42 -0
  85. data/test/filter_test.rb +131 -0
  86. data/test/gateway_test.rb +220 -0
  87. data/test/jms_test.rb +64 -0
  88. data/test/reliable_msg_test.rb +83 -0
  89. data/test/stomp_test.rb +168 -0
  90. data/test/test_helper.rb +36 -0
  91. data/test/tracer_test.rb +57 -0
  92. 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