gz_activemessaging 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
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