activemessaging 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/Rakefile +50 -0
  2. data/generators/a13g_test_harness/a13g_test_harness_generator.rb +19 -0
  3. data/generators/a13g_test_harness/templates/active_messaging_test.rhtml +13 -0
  4. data/generators/a13g_test_harness/templates/active_messaging_test_controller.rb +29 -0
  5. data/generators/a13g_test_harness/templates/index.rhtml +17 -0
  6. data/generators/filter/USAGE +0 -0
  7. data/generators/filter/filter_generator.rb +19 -0
  8. data/generators/filter/templates/filter.rb +12 -0
  9. data/generators/filter/templates/filter_test.rb +28 -0
  10. data/generators/processor/USAGE +8 -0
  11. data/generators/processor/processor_generator.rb +31 -0
  12. data/generators/processor/templates/application.rb +18 -0
  13. data/generators/processor/templates/broker.yml +79 -0
  14. data/generators/processor/templates/jruby_poller +117 -0
  15. data/generators/processor/templates/messaging.rb +12 -0
  16. data/generators/processor/templates/poller +23 -0
  17. data/generators/processor/templates/poller.rb +23 -0
  18. data/generators/processor/templates/processor.rb +8 -0
  19. data/generators/processor/templates/processor_test.rb +20 -0
  20. data/generators/tracer/USAGE +8 -0
  21. data/generators/tracer/templates/controller.rb +14 -0
  22. data/generators/tracer/templates/helper.rb +2 -0
  23. data/generators/tracer/templates/index.rhtml +4 -0
  24. data/generators/tracer/templates/layout.rhtml +16 -0
  25. data/generators/tracer/templates/trace_processor.rb +100 -0
  26. data/generators/tracer/tracer_generator.rb +25 -0
  27. data/lib/activemessaging.rb +133 -0
  28. data/lib/activemessaging/adapter.rb +21 -0
  29. data/lib/activemessaging/adapters/asqs.rb +412 -0
  30. data/lib/activemessaging/adapters/base.rb +82 -0
  31. data/lib/activemessaging/adapters/jms.rb +237 -0
  32. data/lib/activemessaging/adapters/reliable_msg.rb +190 -0
  33. data/lib/activemessaging/adapters/stomp.rb +99 -0
  34. data/lib/activemessaging/adapters/test.rb +155 -0
  35. data/lib/activemessaging/adapters/wmq.rb +202 -0
  36. data/lib/activemessaging/filter.rb +29 -0
  37. data/lib/activemessaging/gateway.rb +422 -0
  38. data/lib/activemessaging/message_sender.rb +30 -0
  39. data/lib/activemessaging/named_base.rb +54 -0
  40. data/lib/activemessaging/processor.rb +45 -0
  41. data/lib/activemessaging/support.rb +17 -0
  42. data/lib/activemessaging/test_helper.rb +194 -0
  43. data/lib/activemessaging/trace_filter.rb +34 -0
  44. data/messaging.rb.example +5 -0
  45. data/tasks/start_consumers.rake +8 -0
  46. metadata +123 -0
@@ -0,0 +1,99 @@
1
+ require 'stomp'
2
+
3
+ module ActiveMessaging
4
+ module Adapters
5
+ module Stomp
6
+
7
+ class Connection < ::Stomp::Connection
8
+ include ActiveMessaging::Adapter
9
+ register :stomp
10
+
11
+ attr_accessor :reliable, :retryMax, :deadLetterQueue
12
+
13
+ def initialize(cfg)
14
+ @retryMax = cfg[:retryMax] || 0
15
+ @deadLetterQueue = cfg[:deadLetterQueue] || nil
16
+
17
+ cfg[:login] ||= ""
18
+ cfg[:passcode] ||= ""
19
+ cfg[:host] ||= "localhost"
20
+ cfg[:port] ||= "61613"
21
+ cfg[:reliable] ||= TRUE
22
+ cfg[:reconnectDelay] ||= 5
23
+ cfg[:clientId] ||= nil
24
+
25
+ if cfg[:clientId]
26
+ super(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay],cfg[:clientId])
27
+ else
28
+ super(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay])
29
+ end
30
+
31
+ end
32
+
33
+ def received message, headers={}
34
+ #check to see if the ack mode for this subscription is auto or client
35
+ # if the ack mode is client, send an ack
36
+ if (headers[:ack] === 'client')
37
+ ack_headers = message.headers.has_key?(:transaction) ? message.headers[:transaction] : {}
38
+ ack message.headers['message-id'], ack_headers
39
+ end
40
+ end
41
+
42
+ def unreceive message, headers={}
43
+ retry_count = message.headers['a13g-retry-count'].to_i || 0
44
+ transaction_id = "transaction-#{message.headers['message-id']}-#{retry_count}"
45
+
46
+ # start a transaction, send the message back to the original destination
47
+ self.begin(transaction_id)
48
+ begin
49
+
50
+ if @retryMax > 0
51
+ retry_headers = message.headers.stringify_keys
52
+ retry_headers['transaction']= transaction_id
53
+ retry_headers.delete('content-length')
54
+ retry_headers.delete('content-type')
55
+
56
+ if retry_count < @retryMax
57
+ # now send the message back to the destination
58
+ # set the headers for message id, priginal message id, and retry count
59
+ retry_headers['a13g-original-message-id'] = retry_headers['message-id'] unless retry_headers.has_key?('a13g-original-message-id')
60
+ retry_headers.delete('message-id')
61
+
62
+ retry_headers['a13g-original-timestamp'] = retry_headers['timestamp'] unless retry_headers.has_key?('a13g-original-timestamp')
63
+ retry_headers.delete('timestamp')
64
+
65
+ retry_headers['a13g-retry-count'] = retry_count + 1
66
+
67
+ # send the updated message to retry in the same transaction
68
+ self.send retry_headers['destination'], message.body, retry_headers
69
+
70
+ elsif retry_count >= @retryMax && @deadLetterQueue
71
+ # send the 'poison pill' message to the dead letter queue
72
+ retry_headers['a13g-original-destination'] = retry_headers.delete('destination')
73
+ retry_headers.delete('message-id')
74
+ self.send @deadLetterQueue, message.body, retry_headers
75
+ end
76
+
77
+ end
78
+
79
+ #check to see if the ack mode is client, and if it is, ack it in this transaction
80
+ if (headers[:ack] === 'client')
81
+ # ack the original message
82
+ self.ack message.headers['message-id'], message.headers.stringify_keys.merge('transaction'=>transaction_id)
83
+ end
84
+
85
+ # now commit the transaction
86
+ self.commit transaction_id
87
+ rescue Exception=>exc
88
+ # if there is an error, try to abort the transaction, then raise the error
89
+ self.abort transaction_id
90
+ raise exc
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,155 @@
1
+ require 'activemessaging/adapter'
2
+
3
+ module ActiveMessaging
4
+ module Adapters
5
+ module Test
6
+
7
+ class Connection
8
+ include ActiveMessaging::Adapter
9
+ register :test
10
+
11
+ attr_accessor :config, :subscriptions, :destinations, :connected, :received_messages, :unreceived_messages
12
+
13
+ def initialize cfg
14
+ @config = cfg
15
+ @subscriptions = []
16
+ @destinations = []
17
+ @received_messages = []
18
+ @unreceived_messages = []
19
+ @connected = true
20
+ end
21
+
22
+ def disconnect
23
+ @subscriptions = []
24
+ @destinations = []
25
+ @received_messages = []
26
+ @unreceived_messages = []
27
+ @connected = false
28
+ end
29
+
30
+ def subscribe destination_name, subscribe_headers={}
31
+ open_destination destination_name
32
+ unless @subscriptions.find {|s| s.name == destination_name}
33
+ @subscriptions << Subscription.new(destination_name, subscribe_headers)
34
+ end
35
+ @subscriptions.last
36
+ end
37
+
38
+ def unsubscribe destination_name, unsubscribe_headers={}
39
+ @subscriptions.delete_if {|s| s.name == destination_name}
40
+ end
41
+
42
+ def send destination_name, message_body, message_headers={}
43
+ open_destination destination_name
44
+ destination = find_destination destination_name
45
+ destination.send Message.new(message_headers, nil, message_body, nil, destination)
46
+ end
47
+
48
+ def receive
49
+ destination = @destinations.find do |q|
50
+ find_subscription(q.name) && !q.empty?
51
+ end
52
+ destination.receive unless destination.nil?
53
+ end
54
+
55
+ # should not be 2 defs for receive, this isn't java, ya know? -Andrew
56
+ # def receive destination_name, headers={}
57
+ # subscribe destination_name, headers
58
+ # destination = find_destination destination_name
59
+ # message = destination.receive
60
+ # unsubscribe destination_name, headers
61
+ # message
62
+ # end
63
+
64
+ def received message, headers={}
65
+ @received_messages << message
66
+ end
67
+
68
+ def unreceive message, headers={}
69
+ @unreceived_messages << message
70
+ end
71
+
72
+ #test helper methods
73
+ def find_message destination_name, body
74
+ all_messages.find do |m|
75
+ m.headers['destination'] == destination_name && if body.is_a?(Regexp)
76
+ m.body =~ body
77
+ else
78
+ m.body == body.to_s
79
+ end
80
+ end
81
+ end
82
+
83
+ def open_destination destination_name
84
+ unless find_destination destination_name
85
+ @destinations << Destination.new(destination_name)
86
+ end
87
+ end
88
+
89
+ def find_destination destination_name
90
+ @destinations.find{|q| q.name == destination_name }
91
+ end
92
+
93
+ def find_subscription destination_name
94
+ @subscriptions.find{|s| s.name == destination_name}
95
+ end
96
+
97
+ def all_messages
98
+ @destinations.map {|q| q.messages }.flatten
99
+ end
100
+ end
101
+
102
+ class Destination
103
+
104
+ attr_accessor :name, :messages
105
+
106
+ def initialize name
107
+ @name = name
108
+ @messages = []
109
+ end
110
+
111
+ def receive
112
+ @messages.shift
113
+ end
114
+
115
+ def send message
116
+ @messages << message
117
+ end
118
+
119
+ def empty?
120
+ @messages.empty?
121
+ end
122
+
123
+ def to_s
124
+ "<Test::Destination name='#{name}' messages='#{@messages.inspect}'>"
125
+ end
126
+ end
127
+
128
+ class Subscription
129
+ attr_accessor :name, :headers
130
+
131
+ def initialize name, headers
132
+ @name = name
133
+ @headers = headers
134
+ end
135
+
136
+ def to_s
137
+ "<Test::Subscription destination='#{name}' headers='#{headers.inspect}' >"
138
+ end
139
+ end
140
+
141
+ class Message
142
+ attr_accessor :headers, :body, :command
143
+
144
+ def initialize headers, id, body, response, destination, command='MESSAGE'
145
+ @headers, @body, @command = headers, body, command
146
+ headers['destination'] = destination.name
147
+ end
148
+
149
+ def to_s
150
+ "<Test::Message body='#{body}' headers='#{headers.inspect}' command='#{command}' >"
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,202 @@
1
+ ################################################################################
2
+ # Copyright 2007 S. Perez. RBC Dexia Investor Servies Bank
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ################################################################################
16
+
17
+ #
18
+ # WebSphere MQ adapter for activemessaging
19
+ #
20
+ require 'wmq/wmq'
21
+
22
+ module ActiveMessaging
23
+ module Adapters
24
+ module Adapter
25
+
26
+ # Connection class needed by a13g
27
+ class Connection
28
+ include ActiveMessaging::Adapter
29
+ register :wmq
30
+
31
+ # Needed by a13g but never used within this adapter
32
+ attr_accessor :reliable
33
+
34
+ # Generic init method needed by a13g
35
+ def initialize(cfg)
36
+ # Set default values
37
+ cfg[:poll_interval] ||= 0.1
38
+
39
+ # Initialize instance members
40
+ # Trick for the connection_options is to allow settings WMQ constants directly in broker.yml :))
41
+ @connection_options = cfg.each_pair {|key, value| cfg[key] = instance_eval(value) if (value.instance_of?(String) && value.match("WMQ::")) }
42
+ @queue_names = []
43
+ @current_queue = 0
44
+ @queues = {}
45
+ end
46
+
47
+ # Disconnect method needed by a13g
48
+ # No need to disconnect from the queue manager since connection and disconnection occurs inside the send and receive methods
49
+ # headers is never used
50
+ def disconnect(headers = {})
51
+ end
52
+
53
+ # Receive method needed by a13g
54
+ def receive
55
+ raise "No subscription to receive messages from" if (@queue_names.nil? || @queue_names.empty?)
56
+ start = @current_queue
57
+ while true
58
+ @current_queue = ((@current_queue < @queue_names.length-1) ? @current_queue + 1 : 0)
59
+ sleep(@connection_options[:poll_interval]) if (@current_queue == start)
60
+ q = @queues[@queue_names[@current_queue]]
61
+ unless q.nil?
62
+ message = retrieve_message(q)
63
+ return message unless message.nil?
64
+ end
65
+ end
66
+ end
67
+
68
+ # Send method needed by a13g
69
+ # headers may contains 2 different hashes to gives more control over the sending process
70
+ # :descriptor => {...} to populate the descriptor of the message
71
+ # :put_options => {...} to specify the put options for that message
72
+ def send(q_name, message_data, headers={})
73
+ WMQ::QueueManager.connect(@connection_options) do |qmgr|
74
+ qmgr.open_queue(:q_name => q_name, :mode => :output) do |queue|
75
+
76
+ message_descriptor = headers[:descriptor] || {:format => WMQ::MQFMT_STRING}
77
+ put_options = headers[:put_options].nil? ? {} : headers[:put_options].dup
78
+
79
+ wmq_message = WMQ::Message.new(:data => message_data, :descriptor => message_descriptor)
80
+ queue.put(put_options.merge(:message => wmq_message, :data => nil))
81
+ return Message.new(wmq_message, q_name)
82
+ end
83
+ end
84
+ end
85
+
86
+ # Subscribe method needed by a13g
87
+ # headers may contains a hash to give more control over the get operation on the queue
88
+ # :get_options => {...} to specify the get options when receiving messages
89
+ # Warning : get options are set only on the first queue subscription and are common to all the queue's subscriptions
90
+ # Any other get options passed with subsequent subscribe on an existing queue will be discarded
91
+ # subId is never used
92
+ def subscribe(q_name, headers={}, subId=NIL)
93
+ if @queues[q_name].nil?
94
+ get_options = headers[:get_options] || {}
95
+ q = Queue.new(q_name, get_options)
96
+ @queues[q_name] = q
97
+ @queue_names << q.name
98
+ end
99
+
100
+ q.add_subscription
101
+ end
102
+
103
+ # Unsubscribe method needed by a13g
104
+ # Stop listening the queue only after the last unsubscription
105
+ # headers is never used
106
+ # subId is never used
107
+ def unsubscribe(q_name, headers={}, subId=NIL)
108
+ q = @queues[q_name]
109
+ unless q.nil?
110
+ q.remove_subscription
111
+ unless q.has_subscription?
112
+ @queues.delete(q_name)
113
+ @queue_names.delete(q_name)
114
+ end
115
+ end
116
+ end
117
+
118
+ # called after a message is successfully received and processed
119
+ def received message, headers={}
120
+ end
121
+
122
+ # called after a message is successfully received but unsuccessfully processed
123
+ # purpose is to return the message to the destination so receiving and processing and be attempted again
124
+ def unreceive message, headers={}
125
+ end
126
+
127
+ private
128
+
129
+ # Retrieve the first available message from the specicied queue
130
+ # Return nil if queue is empty
131
+ def retrieve_message(q)
132
+ WMQ::QueueManager.connect(@connection_options) do |qmgr|
133
+ qmgr.open_queue(:q_name => q.name, :mode => :input) do |queue|
134
+
135
+ get_options = q.get_options.dup
136
+ wmq_message = WMQ::Message.new
137
+
138
+ if queue.get(get_options.merge(:message => wmq_message))
139
+ return Message.new(wmq_message, q.name)
140
+ else
141
+ return nil
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ # Message class needed by a13g (based on the same Message class in Stomp adapter)
149
+ # Contains a reference to the MQ message object ;-) !
150
+ class Message
151
+ # Accessors needed by a13g
152
+ attr_accessor :headers, :body, :command, :wmq_message
153
+
154
+ def initialize(wmq_message, q_name)
155
+ @wmq_message = wmq_message
156
+
157
+ # Needed by a13g
158
+ @headers = {'destination' => q_name}
159
+ @body = wmq_message.data
160
+ @command = 'MESSAGE'
161
+ end
162
+
163
+ def to_s
164
+ "<Adapter::Message headers=#{@headers.inspect} body='#{@body}' command='#{@command}' wmq_message=#{@wmq_message}>"
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ # Queue class is used to keep track of the subscriptions
171
+ # It contains :
172
+ # - name of the queue
173
+ # - options to use when getting from the queue
174
+ # - number of subscriptions
175
+ class Queue
176
+ attr_accessor :name, :get_options, :nb_subscriptions
177
+
178
+ def initialize(name, get_options)
179
+ @name, @get_options = name, get_options
180
+ @nb_subscriptions = 0
181
+ end
182
+
183
+ def add_subscription
184
+ @nb_subscriptions += 1
185
+ end
186
+
187
+ def remove_subscription
188
+ @nb_subscriptions -= 1 unless @nb_subscriptions > 0
189
+ end
190
+
191
+ def has_subscription?
192
+ @nb_subscriptions > 0
193
+ end
194
+
195
+ def to_s
196
+ "<Adapter::Queue name='#{@name}' get_options=#{@get_options} nb_subscriptions=#{@nb_subscriptions}>"
197
+ end
198
+ end
199
+
200
+ end
201
+ end
202
+ end