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