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,82 @@
1
+ module ActiveMessaging
2
+ module Adapters
3
+ module Base
4
+
5
+
6
+ # use this as a base for implementing new connections
7
+ class Connection
8
+ include ActiveMessaging::Adapter
9
+
10
+ #use the register method to add the adapter to the configurable list of supported adapters
11
+ # register :generic
12
+
13
+ #configurable params
14
+ attr_accessor :reliable
15
+
16
+ #generic init method needed by a13g
17
+ def initialize cfg
18
+ end
19
+
20
+ # called to cleanly get rid of connection
21
+ def disconnect
22
+ end
23
+
24
+ # destination_name string, headers hash
25
+ # subscribe to listen on a destination
26
+ def subscribe destination_name, message_headers={}
27
+ end
28
+
29
+ # destination_name string, headers hash
30
+ # unsubscribe to listen on a destination
31
+ def unsubscribe destination_name, message_headers={}
32
+ end
33
+
34
+ # destination_name string, body string, headers hash
35
+ # send a single message to a destination
36
+ def send destination_name, message_body, message_headers={}
37
+ end
38
+
39
+ # receive a single message from any of the subscribed destinations
40
+ # check each destination once, then sleep for poll_interval
41
+ def receive
42
+ end
43
+
44
+ # called after a message is successfully received and processed
45
+ def received message, headers={}
46
+ end
47
+
48
+ # called after a message is successfully received but unsuccessfully processed
49
+ # purpose is to return the message to the destination so receiving and processing and be attempted again
50
+ def unreceive message, headers={}
51
+ end
52
+
53
+ end
54
+
55
+ # I recommend having a destination object to represent each subscribed destination
56
+ class Destination
57
+ attr_accessor :name
58
+
59
+ def to_s
60
+ "<Base::Destination name='#{name}'>"
61
+ end
62
+ end
63
+
64
+ # based on stomp message
65
+ # command = MESSAGE for successful message from adapter, ERROR for problem from adapter
66
+ # !!!! must have headers['destination'] = subscription.destination in order to match message to subscription in gateway!!!!
67
+ class Message
68
+ attr_accessor :headers, :body, :command
69
+
70
+ def initialize headers, id, body, response, destination, command='MESSAGE'
71
+ @headers, @body, @command = headers, body, command
72
+ headers['destination'] = destination.name
73
+ end
74
+
75
+ def to_s
76
+ "<Base::Message body='#{body}' headers='#{headers.inspect}' command='#{command}' >"
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,237 @@
1
+
2
+ if defined?(JRUBY_VERSION)
3
+ #require 'java'
4
+ include Java
5
+
6
+ import javax.naming.InitialContext
7
+ import javax.jms.MessageListener
8
+
9
+ module ActiveMessaging
10
+ module Adapters
11
+ module Jms
12
+
13
+ class Connection
14
+ include ActiveMessaging::Adapter
15
+ register :jms
16
+
17
+ attr_accessor :reliable, :connection, :session, :producers, :consumers
18
+
19
+ def initialize cfg={}
20
+ @url = cfg[:url]
21
+ @login = cfg[:login]
22
+ @passcode = cfg[:passcode]
23
+ #initialize our connection factory
24
+ if cfg.has_key? :connection_factory
25
+ #this initialize is probably activemq specific. There might be a more generic
26
+ #way of getting this without resorting to jndi lookup.
27
+ eval <<-end_eval
28
+ @connection_factory = Java::#{cfg[:connection_factory]}.new(@login, @password, @url)
29
+ end_eval
30
+ elsif cfg.has_key? :jndi
31
+ @connection_factory = javax.naming.InitialContext.new().lookup(cfg[:jndi])
32
+ else
33
+ raise "Either jndi or connection_factory has to be set in the config."
34
+ end
35
+ raise "Connection factory could not be initialized." if @connection_factory.nil?
36
+
37
+ @connection = @connection_factory.create_connection()
38
+ @session = @connection.createSession(false, 1)
39
+ @destinations = []
40
+ @producers = {}
41
+ @consumers = {}
42
+ @connection.start
43
+ end
44
+
45
+ def subscribe queue_name, headers={}
46
+ queue_name = check_destination_type queue_name, headers
47
+ find_or_create_consumer queue_name, headers
48
+ end
49
+
50
+ def unsubscribe queue_name, headers={}
51
+ queue_name = check_destination_type queue_name, headers
52
+ consumer = @consumers[queue_name]
53
+ unless consumer.nil?
54
+ consumer.close
55
+ @consumers.delete queue_name
56
+ end
57
+ end
58
+
59
+ def send queue_name, body, headers={}
60
+ queue_name = check_destination_type queue_name, headers
61
+ producer = find_or_create_producer queue_name, headers.symbolize_keys
62
+ message = @session.create_text_message body
63
+ headers.stringify_keys.each do |key, value|
64
+ if ['id', 'message-id', 'JMSMessageID'].include? key
65
+ message.setJMSMessageID value.to_s
66
+ elsif ['correlation-id', 'JMSCorrelationID'].include? key
67
+ message.setJMSCorrelationID value.to_s
68
+ elsif ['expires', 'JMSExpiration'].include? key
69
+ message.setJMSExpiration value.to_i
70
+ elsif ['persistent', 'JMSDeliveryMode'].include? key
71
+ message.setJMSDeliveryMode(value ? 2 : 1)
72
+ elsif ['priority', 'JMSPriority'].include? key
73
+ message.setJMSPriority value.to_i
74
+ elsif ['reply-to', 'JMSReplyTo'].include? key
75
+ message.setJMSReplyTo value.to_s
76
+ elsif ['type', 'JMSType'].include? key
77
+ message.setJMSType value.to_s
78
+ else #is this the most appropriate thing to do here?
79
+ message.set_string_property key, value.to_s
80
+ end
81
+ end
82
+ producer.send message
83
+ end
84
+
85
+ def receive_any
86
+ @consumers.find do |k, c|
87
+ message = c.receive(1)
88
+ return condition_message(message) unless message.nil?
89
+ end
90
+ end
91
+
92
+ def receive queue_name=nil, headers={}
93
+ if queue_name.nil?
94
+ receive_any
95
+ else
96
+ consumer = subscribe queue_name, headers
97
+ message = consumer.receive(1)
98
+ unsubscribe queue_name, headers
99
+ condition_message message
100
+ end
101
+ end
102
+
103
+ def received message, headers={}
104
+ #do nothing
105
+ end
106
+
107
+ def unreceive message, headers={}
108
+ # do nothing
109
+ end
110
+
111
+ def close
112
+ @consumers.each {|k, c| c.stop }
113
+ @connection.stop
114
+ @session.close
115
+ @connection.close
116
+ @connection = nil
117
+ @session = nil
118
+ @consumers = {}
119
+ @producers = {}
120
+ end
121
+
122
+ def find_or_create_producer queue_name, headers={}
123
+ producer = @producers[queue_name]
124
+ if producer.nil?
125
+ destination = find_or_create_destination queue_name, headers
126
+ producer = @session.create_producer destination
127
+ end
128
+ producer
129
+ end
130
+
131
+ def find_or_create_consumer queue_name, headers={}
132
+ consumer = @consumers[queue_name]
133
+ if consumer.nil?
134
+ destination = find_or_create_destination queue_name, headers
135
+ if headers.symbolize_keys.has_key? :selector
136
+ consumer = @session.create_consumer destination, headers.symbolize_keys[:selector]
137
+ else
138
+ consumer = @session.create_consumer destination
139
+ end
140
+
141
+ @consumers[queue_name] = consumer
142
+ end
143
+ consumer
144
+ end
145
+
146
+ def find_or_create_destination queue_name, headers={}
147
+ destination = find_destination queue_name, headers[:destination_type]
148
+ if destination.nil?
149
+ if headers.symbolize_keys[:destination_type] == :topic
150
+ destination = @session.create_topic(queue_name.to_s)
151
+ @destinations << destination
152
+ elsif headers.symbolize_keys[:destination_type] == :queue
153
+ destination = @session.create_queue(queue_name.to_s)
154
+ @destinations << destination
155
+ else
156
+ raise "headers[:destination_type] must be either :queue or :topic. was #{headers[:destination_type]}"
157
+ end
158
+ end
159
+ destination
160
+ end
161
+
162
+ protected
163
+
164
+ def condition_message message
165
+ message.class.class_eval {
166
+ alias_method :body, :text unless method_defined? :body
167
+
168
+ def command
169
+ "MESSAGE"
170
+ end
171
+
172
+ def headers
173
+ destination.to_s =~ %r{(queue|topic)://(.*)}
174
+ puts "/#{$1}/#{$2}"
175
+ {'destination' => "/#{$1}/#{$2}"}
176
+ end
177
+
178
+ } unless message.nil? || message.respond_to?(:command)
179
+ message
180
+ end
181
+
182
+ def check_destination_type queue_name, headers
183
+ stringy_h = headers.stringify_keys
184
+ if queue_name =~ %r{^/(topic|queue)/(.*)$} && !stringy_h.has_key?('destination_type')
185
+ headers['destination_type'] = $1.to_sym
186
+ return $2
187
+ else
188
+ raise "Must specify destination type either with either 'headers[\'destination_type\']=[:queue|:topic]' or /[topic|queue]/destination_name for queue name '#{queue_name}'" unless [:topic, :queue].include? stringy_h['destination_type']
189
+ end
190
+ end
191
+
192
+ def find_destination queue_name, type
193
+ @destinations.find do |d|
194
+ if d.is_a?(javax.jms.Topic) && type == :topic
195
+ d.topic_name == queue_name
196
+ elsif d.is_a?(javax.jms.Queue) && type == :queue
197
+ d.queue_name == queue_name
198
+ end
199
+ end
200
+ end
201
+ end
202
+ #
203
+ # class RubyMessageListener
204
+ # include javax.jms.MessageListener
205
+ #
206
+ # def initialize(connection, destination, name)
207
+ # @connection = connection
208
+ # @destination = destination
209
+ # @name = name
210
+ # end
211
+ #
212
+ # def onMessage(msg)
213
+ # headers = {}
214
+ # enm = msg.getPropertyNames
215
+ # while enm.hasMoreElements
216
+ # key = enm.nextElement
217
+ # headers[key.to_s] = msg.getStringProperty(key)
218
+ # end
219
+ # Gateway.dispatch(JMSRecvMessage.new(headers,msg.text,@name))
220
+ # rescue => e
221
+ # STDERR.puts "something went really wrong with a message: #{e.inspect}"
222
+ # end
223
+ # end
224
+ #
225
+ # class JMSRecvMessage < ActiveMessaging::Adapters::Base::Message
226
+ # def initialize(headers, body, name, command='MESSAGE')
227
+ # @headers = headers
228
+ # @body = body
229
+ # @command = command
230
+ # @headers['destination'] = name
231
+ # end
232
+ # end
233
+ end
234
+ end
235
+ end
236
+
237
+ end
@@ -0,0 +1,190 @@
1
+ require 'reliable-msg'
2
+
3
+ module ReliableMsg
4
+
5
+ class Client
6
+
7
+ def queue_manager
8
+ qm
9
+ end
10
+
11
+ end
12
+ end
13
+
14
+
15
+ module ActiveMessaging
16
+ module Adapters
17
+ module ReliableMsg
18
+
19
+ THREAD_OLD_TXS = :a13g_reliable_msg_old_txs
20
+
21
+ QUEUE_PARAMS = [:expires,:delivery,:priority,:max_deliveries,:drb_uri,:tx_timeout,:connect_count]
22
+ TOPIC_PARAMS = [:expires,:drb_uri,:tx_timeout,:connect_count]
23
+
24
+ class Connection
25
+ include ActiveMessaging::Adapter
26
+
27
+ register :reliable_msg
28
+
29
+ #configurable params
30
+ attr_accessor :reliable, :subscriptions, :destinations, :poll_interval, :current_subscription, :tx_timeout
31
+
32
+ #generic init method needed by a13g
33
+ def initialize cfg
34
+ @poll_interval = cfg[:poll_interval] || 1
35
+ @reliable = cfg[:reliable] || true
36
+ @tx_timeout = cfg[:tx_timeout] || ::ReliableMsg::Client::DEFAULT_TX_TIMEOUT
37
+
38
+ @subscriptions = {}
39
+ @destinations = {}
40
+ @current_subscription = 0
41
+ end
42
+
43
+ # called to cleanly get rid of connection
44
+ def disconnect
45
+ nil
46
+ end
47
+
48
+ # destination_name string, headers hash
49
+ # subscribe to listen on a destination
50
+ # use '/destination-type/name' convetion, like stomp
51
+ def subscribe destination_name, message_headers={}
52
+ get_or_create_destination(destination_name, message_headers)
53
+ if subscriptions.has_key? destination_name
54
+ subscriptions[destination_name].add
55
+ else
56
+ subscriptions[destination_name] = Subscription.new(destination_name, message_headers)
57
+ end
58
+ end
59
+
60
+ # destination_name string, headers hash
61
+ # unsubscribe to listen on a destination
62
+ def unsubscribe destination_name, message_headers={}
63
+ subscriptions[destination_name].remove
64
+ subscriptions.delete(destination_name) if subscriptions[destination_name].count <= 0
65
+ end
66
+
67
+ # destination_name string, body string, headers hash
68
+ # send a single message to a destination
69
+ def send destination_name, message_body, message_headers={}
70
+ dest = get_or_create_destination(destination_name)
71
+ begin
72
+ dest.put message_body, message_headers
73
+ rescue Object=>err
74
+ raise err unless reliable
75
+ puts "send failed, will retry in #{@poll_interval} seconds"
76
+ sleep @poll_interval
77
+ end
78
+ end
79
+
80
+ def get_or_create_destination destination_name, message_headers={}
81
+ return destinations[destination_name] if destinations.has_key? destination_name
82
+ dd = /^\/(queue|topic)\/(.*)$/.match(destination_name)
83
+ rm_class = dd[1].titleize
84
+ message_headers.delete("id")
85
+ dest_headers = message_headers.reject {|k,v| rm_class == 'Queue' ? !QUEUE_PARAMS.include?(k) : !TOPIC_PARAMS.include?(k)}
86
+ rm_dest = "ReliableMsg::#{rm_class}".constantize.new(dd[2], dest_headers)
87
+ destinations[destination_name] = rm_dest
88
+ end
89
+
90
+ # receive a single message from any of the subscribed destinations
91
+ # check each destination once, then sleep for poll_interval
92
+ def receive
93
+
94
+ raise "No subscriptions to receive messages from." if (subscriptions.nil? || subscriptions.empty?)
95
+ start = current_subscription
96
+ while true
97
+ self.current_subscription = ((current_subscription < subscriptions.length-1) ? current_subscription + 1 : 0)
98
+ sleep poll_interval if (current_subscription == start)
99
+ destination_name = subscriptions.keys.sort[current_subscription]
100
+ destination = destinations[destination_name]
101
+ unless destination.nil?
102
+ # from the way we use this, assume this is the start of a transaction,
103
+ # there should be no current transaction
104
+ ctx = Thread.current[::ReliableMsg::Client::THREAD_CURRENT_TX]
105
+ raise "There should not be an existing reliable-msg transaction. #{ctx.inspect}" if ctx
106
+
107
+ # start a new transaction
108
+ @tx = {:qm=>destination.queue_manager}
109
+ @tx[:tid] = @tx[:qm].begin @tx_timeout
110
+ Thread.current[::ReliableMsg::Client::THREAD_CURRENT_TX] = @tx
111
+ begin
112
+
113
+ # now call a get on the destination - it will use the transaction
114
+ #the commit or the abort will occur in the received or unreceive methods
115
+ reliable_msg = destination.get subscriptions[destination_name].headers[:selector]
116
+ @tx[:qm].commit(@tx[:tid]) if reliable_msg.nil?
117
+
118
+ rescue Object=>err
119
+ #abort the transaction on error
120
+ @tx[:qm].abort(@tx[:tid])
121
+
122
+ raise err unless reliable
123
+ puts "receive failed, will retry in #{@poll_interval} seconds"
124
+ sleep poll_interval
125
+ end
126
+ return Message.new(reliable_msg.id, reliable_msg.object, reliable_msg.headers, destination_name, 'MESSAGE', @tx) if reliable_msg
127
+
128
+ Thread.current[::ReliableMsg::Client::THREAD_CURRENT_TX] = nil
129
+ end
130
+ end
131
+ end
132
+
133
+ # called after a message is successfully received and processed
134
+ def received message, headers={}
135
+ begin
136
+ message.transaction[:qm].commit(message.transaction[:tid])
137
+ rescue Object=>ex
138
+ puts "received failed: #{ex.message}"
139
+ ensure
140
+ Thread.current[::ReliableMsg::Client::THREAD_CURRENT_TX] = nil
141
+ end
142
+
143
+ end
144
+
145
+ # called after a message is successfully received and processed
146
+ def unreceive message, headers={}
147
+ begin
148
+ message.transaction[:qm].abort(message.transaction[:tid])
149
+ rescue Object=>ex
150
+ puts "unreceive failed: #{ex.message}"
151
+ ensure
152
+ Thread.current[::ReliableMsg::Client::THREAD_CURRENT_TX] = nil
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ class Subscription
159
+ attr_accessor :name, :headers, :count
160
+
161
+ def initialize(destination, headers={}, count=1)
162
+ @destination, @headers, @count = destination, headers, count
163
+ end
164
+
165
+ def add
166
+ @count += 1
167
+ end
168
+
169
+ def remove
170
+ @count -= 1
171
+ end
172
+
173
+ end
174
+
175
+ class Message
176
+ attr_accessor :id, :body, :headers, :command, :transaction
177
+
178
+ def initialize id, body, headers, destination_name, command='MESSAGE', transaction=nil
179
+ @id, @body, @headers, @command, @transaction = id, body, headers, command, transaction
180
+ headers['destination'] = destination_name
181
+ end
182
+
183
+ def to_s
184
+ "<ReliableMessaging::Message id='#{id}' body='#{body}' headers='#{headers.inspect}' command='#{command}' >"
185
+ end
186
+ end
187
+
188
+ end
189
+ end
190
+ end