activemessaging 0.6.0

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 (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