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,28 @@
1
+ module ActiveMessaging
2
+
3
+ class BaseMessage
4
+ attr_accessor :body, :id, :headers, :destination
5
+
6
+ def initialize(body=nil, id=nil, headers={}, destination=nil)
7
+ @body, @id, @headers, @destination = body, id, headers, destination
8
+ end
9
+
10
+ def matches_subscription?(subscription)
11
+ self.destination.to_s == subscription.destination.value.to_s
12
+ end
13
+
14
+ def dup
15
+ super.tap do |copy|
16
+ [:body, :id, :headers, :destination].each do |field_name|
17
+ value = send field_name
18
+ copy.send "#{field_name}=", (value.duplicable? ? value.dup : value)
19
+ end
20
+ end
21
+ end
22
+
23
+ def to_s
24
+ "<#{self.class.name} id='#{id}' headers='#{headers.inspect}' body='#{body}' >"
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,29 @@
1
+ # 'abstract' base class for ActiveMessaging filter classes
2
+ module ActiveMessaging
3
+ class Filter
4
+
5
+ #automatically make it so filters are message senders
6
+ include MessageSender
7
+
8
+ # give filters easy access to the logger
9
+ def logger()
10
+ @@logger = ActiveMessaging.logger unless defined?(@@logger)
11
+ @@logger
12
+ end
13
+
14
+ # these are the headers available for a message from the 'details' hash
15
+ # :receiver=>processor
16
+ # :destination=>destination object
17
+ # :direction => :incoming
18
+
19
+ # :publisher => publisher - optional
20
+ # :destination => destination object
21
+ # :direction => :outgoing
22
+
23
+ # if you raise a StopProcessingException, it will cause this to be the last filter to be processed, and will prevent any further processing
24
+ def process(message, routing)
25
+ raise NotImplementedError.new("Implement the process method in your own filter class that extends ActiveMessaging::Filter")
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,429 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+ require 'erb'
4
+ require 'thread'
5
+
6
+ module ActiveMessaging
7
+
8
+ class Gateway
9
+
10
+ @adapters = {}
11
+ @subscriptions = {}
12
+ @named_destinations = {}
13
+ @filters = []
14
+ @connections = {}
15
+ @processor_groups = {}
16
+ @current_processor_group = nil
17
+
18
+ # these are used to manage the running connection threads
19
+ @running = true
20
+ @connection_threads = {}
21
+ @guard = Mutex.new
22
+
23
+ class << self
24
+
25
+ attr_accessor :adapters, :subscriptions, :named_destinations, :filters, :processor_groups, :connections
26
+
27
+ # Starts up an message listener to start polling for messages on each configured connection, and dispatching processing
28
+ def start
29
+
30
+ # subscribe - creating connections along the way
31
+ subscribe
32
+
33
+ # for each connection, start a thread
34
+ @connections.each do |name, conn|
35
+ @connection_threads[name] = Thread.start do
36
+ while @running
37
+ begin
38
+ Thread.current[:message] = nil
39
+ Thread.current[:message] = conn.receive
40
+ #catch these but then stop looping
41
+ rescue StopProcessingException=>spe
42
+ ActiveMessaging.logger.error "ActiveMessaging: thread[#{name}]: Processing Stopped - receive interrupted, will process last message if already received"
43
+ # break
44
+ #catch all others, but go back and try and recieve again
45
+ rescue Object=>exception
46
+ ActiveMessaging.logger.error "ActiveMessaging: thread[#{name}]: Exception from connection.receive: #{exception.message}\n" + exception.backtrace.join("\n\t")
47
+ ensure
48
+ if Thread.current[:message]
49
+ @guard.synchronize {
50
+ dispatch Thread.current[:message]
51
+ }
52
+ Thread.current[:message] = nil
53
+ else
54
+ # if there is no message at all, sleep
55
+ # maybe this should be configurable
56
+ sleep(1)
57
+ end
58
+ end
59
+ Thread.pass
60
+ end
61
+ ActiveMessaging.logger.error "ActiveMessaging: thread[#{name}]: receive loop terminated"
62
+ end
63
+ end
64
+
65
+ while @running
66
+ trap("TERM", "EXIT")
67
+ living = false
68
+ @connection_threads.each { |name, thread| living ||= thread.alive? }
69
+ @running = living
70
+ sleep(1)
71
+ end
72
+ ActiveMessaging.logger.error "All connection threads have died..."
73
+ rescue Interrupt
74
+ ActiveMessaging.logger.error "\n<<Interrupt received>>\n"
75
+ rescue Object=>exception
76
+ ActiveMessaging.logger.error "#{exception.class.name}: #{exception.message}\n\t#{exception.backtrace.join("\n\t")}"
77
+ raise exception
78
+ ensure
79
+ ActiveMessaging.logger.error "Cleaning up..."
80
+ stop
81
+ ActiveMessaging.logger.error "=> END"
82
+ end
83
+
84
+ def stop
85
+ # first tell the threads to stop their looping, so they'll stop when next complete a receive/dispatch cycle
86
+ @running = false
87
+
88
+ # if they are dispatching (i.e. !thread[:message].nil?), wait for them to finish
89
+ # if they are receiving (i.e. thread[:message].nil?), stop them by raising exception
90
+ dispatching = true
91
+ while dispatching
92
+ dispatching = false
93
+ @connection_threads.each do |name, thread|
94
+ if thread[:message]
95
+ dispatching = true
96
+ # if thread got killed, but dispatch not done, try it again
97
+ if thread.alive?
98
+ ActiveMessaging.logger.error "Waiting on thread #{name} to finish processing last message..."
99
+ else
100
+ ActiveMessaging.logger.error "Starting thread #{name} to finish processing last message..."
101
+ msg = thread[:message]
102
+ thread.exit
103
+ thread = Thread.start do
104
+ begin
105
+ Thread.current[:message] = msg
106
+ dispatch Thread.current[:message]
107
+ ensure
108
+ Thread.current[:message] = nil
109
+ end
110
+ end
111
+ end
112
+ else
113
+ thread.raise StopProcessingException, "Time to stop." if thread.alive?
114
+ end
115
+ end
116
+ sleep(1)
117
+ end
118
+ unsubscribe
119
+ disconnect
120
+ end
121
+
122
+ def connection broker_name='default'
123
+ return @connections[broker_name] if @connections.has_key?(broker_name)
124
+ config = load_connection_configuration(broker_name)
125
+ adapter_class = Gateway.adapters[config[:adapter]]
126
+ raise "Unknown messaging adapter #{config[:adapter].inspect}!" if adapter_class.nil?
127
+ @connections[broker_name] = adapter_class.new(config)
128
+ end
129
+
130
+ def register_adapter adapter_name, adapter_class
131
+ Gateway.adapters[adapter_name] = adapter_class
132
+ end
133
+
134
+ def filter filter, options = {}
135
+ options[:direction] = :bidirectional if options[:direction].nil?
136
+ filters << [filter, options]
137
+ end
138
+
139
+ def subscribe
140
+ subscriptions.each { |key, subscription| subscription.subscribe }
141
+ end
142
+
143
+ def unsubscribe
144
+ subscriptions.each { |key, subscription| subscription.unsubscribe }
145
+ end
146
+
147
+ def disconnect
148
+ @connections.each { |key,connection| connection.disconnect }
149
+ @connections = {}
150
+ end
151
+
152
+ def execute_filter_chain(direction, message, details={})
153
+ filters.each do |filter, options|
154
+ if apply_filter?(direction, details, options)
155
+ begin
156
+ filter_obj = create_filter(filter, options)
157
+ filter_obj.process(message, details)
158
+ rescue ActiveMessaging::StopFilterException => sfe
159
+ ActiveMessaging.logger.error "Filter: #{filter_obj.inspect} threw StopFilterException: #{sfe.message}"
160
+ return
161
+ end
162
+ end
163
+ end
164
+ yield(message)
165
+ end
166
+
167
+ def apply_filter?(direction, details, options)
168
+ # check that it is the correct direction
169
+ result = if direction.to_sym == options[:direction] || options[:direction] == :bidirectional
170
+ if options.has_key?(:only) && [options[:only]].flatten.include?(details[:destination].name)
171
+ true
172
+ elsif options.has_key?(:except) && ![options[:except]].flatten.include?(details[:destination].name)
173
+ true
174
+ elsif !options.has_key?(:only) && !options.has_key?(:except)
175
+ true
176
+ end
177
+ end
178
+ result
179
+ end
180
+
181
+ def create_filter(filter, options)
182
+ filter_class = if filter.is_a?(String) or filter.is_a?(Symbol)
183
+ filter.to_s.camelize.constantize
184
+ elsif filter.is_a?(Class)
185
+ filter
186
+ end
187
+
188
+ if filter_class
189
+ if filter_class.respond_to?(:process) && (filter_class.method(:process).arity.abs > 0)
190
+ filter_class
191
+ elsif filter_class.instance_method(:initialize).arity.abs == 1
192
+ filter_class.new(options)
193
+ elsif filter_class.instance_method(:initialize).arity == 0
194
+ filter_class.new
195
+ else
196
+ raise "Filter #{filter} could not be created, no 'initialize' matched."
197
+ end
198
+ else
199
+ raise "Filter #{filter} could not be loaded, created, or used!"
200
+ end
201
+ end
202
+
203
+ def prepare_application
204
+ return unless defined?(ActiveRecord)
205
+
206
+ if ActiveRecord::VERSION::MAJOR >= 4
207
+ ActiveRecord::Base.connection_pool.connections.map(&:verify!)
208
+ else
209
+ ActiveRecord::Base.verify_active_connections!
210
+ end
211
+ end
212
+
213
+ def reset_application
214
+ end
215
+
216
+ def dispatch(message)
217
+ prepare_application
218
+ _dispatch(message)
219
+ rescue Object => exc
220
+ ActiveMessaging.logger.error "Dispatch exception: #{exc}"
221
+ ActiveMessaging.logger.error exc.backtrace.join("\n\t")
222
+ raise exc
223
+ ensure
224
+ ActiveMessaging.logger.flush rescue nil
225
+ reset_application
226
+ end
227
+
228
+ def _dispatch(message)
229
+ abort = false
230
+ processed = false
231
+
232
+ subscriptions.each do |key, subscription|
233
+ if message.matches_subscription?(subscription) then
234
+ processed = true
235
+ routing = {
236
+ :receiver => subscription.processor_class,
237
+ :destination => subscription.destination,
238
+ :direction => :incoming
239
+ }
240
+ begin
241
+ execute_filter_chain(:incoming, message.dup, routing) do |m|
242
+ result = subscription.processor_class.new.process!(m)
243
+ end
244
+ rescue ActiveMessaging::AbortMessageException
245
+ abort_message subscription, message
246
+ abort = true
247
+ return
248
+ ensure
249
+ acknowledge_message subscription, message unless abort
250
+ end
251
+ end
252
+ end
253
+
254
+ ActiveMessaging.logger.error("No-one responded to #{message}") unless processed
255
+
256
+ end
257
+
258
+ # acknowledge_message is called when the message has been processed w/o error by at least one processor
259
+ def acknowledge_message subscription, message
260
+ connection(subscription.destination.broker_name).received message, subscription.subscribe_headers
261
+ end
262
+
263
+ # abort_message is called when procesing the message raises a ActiveMessaging::AbortMessageException
264
+ # indicating the message should be returned to the destination so it can be tried again, later
265
+ def abort_message subscription, message
266
+ connection(subscription.destination.broker_name).unreceive message, subscription.subscribe_headers
267
+ end
268
+
269
+ def define
270
+ #run the rest of messaging.rb
271
+ yield self
272
+ end
273
+
274
+ def destination destination_name, destination, publish_headers={}, broker='default'
275
+ raise "You already defined #{destination_name} to #{named_destinations[destination_name].value}" if named_destinations.has_key?(destination_name)
276
+ named_destinations[destination_name] = Destination.new(destination_name, destination, publish_headers, broker)
277
+ end
278
+
279
+ alias queue destination
280
+
281
+ def find_destination destination_name
282
+ real_destination = named_destinations[destination_name]
283
+ raise "You have not yet defined a destination named #{destination_name}. Destinations currently defined are [#{named_destinations.keys.join(',')}]" if real_destination.nil?
284
+ real_destination
285
+ end
286
+
287
+ alias find_queue find_destination
288
+
289
+ def subscribe_to destination_name, processor, headers={}
290
+ proc_name = processor.name.underscore
291
+ proc_sym = processor.name.underscore.to_sym
292
+ if (!current_processor_group || processor_groups[current_processor_group].include?(proc_sym))
293
+ @subscriptions["#{proc_name}:#{destination_name}"]= Subscription.new(find_destination(destination_name), processor, headers)
294
+ end
295
+ end
296
+
297
+ def publish destination_name, body, publisher=nil, headers={}, timeout=10
298
+ raise "You cannot have a nil or empty destination name." if destination_name.nil?
299
+ raise "You cannot have a nil or empty message body." if (body.nil? || body.empty?)
300
+
301
+ real_destination = find_destination(destination_name)
302
+ details = {
303
+ :publisher => publisher,
304
+ :destination => real_destination,
305
+ :direction => :outgoing
306
+ }
307
+ message = OpenStruct.new(:body => body, :headers => headers.reverse_merge(real_destination.publish_headers))
308
+ begin
309
+ Timeout.timeout timeout do
310
+ execute_filter_chain(:outgoing, message, details) do |message|
311
+ connection(real_destination.broker_name).send real_destination.value, message.body, message.headers
312
+ end
313
+ end
314
+ rescue Timeout::Error=>toe
315
+ ActiveMessaging.logger.error("Timed out trying to send the message #{message} to destination #{destination_name} via broker #{real_destination.broker_name}")
316
+ raise toe
317
+ end
318
+ end
319
+
320
+ def receive destination_name, receiver=nil, subscribe_headers={}, timeout=10
321
+ raise "You cannot have a nil or empty destination name." if destination_name.nil?
322
+ conn = nil
323
+ dest = find_destination destination_name
324
+ config = load_connection_configuration(dest.broker_name)
325
+ subscribe_headers['id'] = receiver.name.underscore unless (receiver.nil? or subscribe_headers.key? 'id')
326
+ Timeout.timeout timeout do
327
+ conn = Gateway.adapters[config[:adapter]].new(config)
328
+ conn.subscribe(dest.value, subscribe_headers)
329
+ message = conn.receive
330
+ conn.received message, subscribe_headers
331
+ return message
332
+ end
333
+ rescue Timeout::Error=>toe
334
+ ActiveMessaging.logger.error("Timed out trying to receive a message on destination #{destination_name}")
335
+ raise toe
336
+ ensure
337
+ conn.disconnect unless conn.nil?
338
+ end
339
+
340
+ def processor_group group_name, *processors
341
+ if processor_groups.has_key? group_name
342
+ processor_groups[group_name] = processor_groups[group_name] + processors
343
+ else
344
+ processor_groups[group_name] = processors
345
+ end
346
+ end
347
+
348
+ def current_processor_group
349
+ if ARGV.length > 0 && !@current_processor_group
350
+ ARGV.each {|arg|
351
+ pair = arg.split('=')
352
+ if pair[0] == 'process-group'
353
+ group_sym = pair[1].to_sym
354
+ if processor_groups.has_key? group_sym
355
+ @current_processor_group = group_sym
356
+ else
357
+ ActiveMessaging.logger.error "Unrecognized process-group."
358
+ ActiveMessaging.logger.error "You specified process-group #{pair[1]}, make sure this is specified in config/messaging.rb"
359
+ ActiveMessaging.logger.error " ActiveMessaging::Gateway.define do |s|"
360
+ ActiveMessaging.logger.error " s.processor_groups = { :group1 => [:foo_bar1_processor], :group2 => [:foo_bar2_processor] }"
361
+ ActiveMessaging.logger.error " end"
362
+ exit
363
+ end
364
+ end
365
+ }
366
+ end
367
+ @current_processor_group
368
+ end
369
+
370
+ def load_connection_configuration(label='default')
371
+ @broker_yml = YAML::load(ERB.new(IO.read(File.join(ActiveMessaging.app_root, 'config', 'broker.yml'))).result) if @broker_yml.nil?
372
+ if label == 'default'
373
+ config = @broker_yml[ActiveMessaging.app_env].symbolize_keys
374
+ else
375
+ config = @broker_yml[ActiveMessaging.app_env][label].symbolize_keys
376
+ end
377
+ config[:adapter] = config[:adapter].to_sym if config[:adapter]
378
+ config[:adapter] ||= :stomp
379
+ return config
380
+ end
381
+
382
+ end
383
+
384
+ end
385
+
386
+ class Subscription
387
+ attr_accessor :destination, :processor_class, :subscribe_headers
388
+
389
+ def initialize(destination, processor_class, subscribe_headers = {})
390
+ @destination, @processor_class, @subscribe_headers = destination, processor_class, subscribe_headers
391
+ subscribe_headers['id'] = processor_class.name.underscore unless subscribe_headers.key? 'id'
392
+ end
393
+
394
+ def subscribe
395
+ ActiveMessaging.logger.error "=> Subscribing to #{destination.value} (processed by #{processor_class})"
396
+ Gateway.connection(@destination.broker_name).subscribe(@destination.value, subscribe_headers)
397
+ end
398
+
399
+ def unsubscribe
400
+ ActiveMessaging.logger.error "=> Unsubscribing from #{destination.value} (processed by #{processor_class})"
401
+ Gateway.connection(destination.broker_name).unsubscribe(destination.value, subscribe_headers)
402
+ end
403
+ end
404
+
405
+ class Destination
406
+ DEFAULT_PUBLISH_HEADERS = { :persistent=>true }
407
+
408
+ attr_accessor :name, :value, :publish_headers, :broker_name, :wildcard
409
+
410
+ def initialize(name, value, publish_headers, broker_name)
411
+ @name, @value, @publish_headers, @broker_name = name, value, publish_headers, broker_name
412
+ @publish_headers.reverse_merge! DEFAULT_PUBLISH_HEADERS
413
+ @wildcard = wildcard_match_exp_for(value) if (value =~ /\*/)
414
+ end
415
+
416
+ def to_s
417
+ "<destination: #{broker_name} :#{name}=>'#{value}'>"
418
+ end
419
+
420
+ private
421
+
422
+ def wildcard_match_exp_for(destination)
423
+ exp = destination.to_s.gsub(/[.]/, '\.').gsub(/[*]/, '[^.*]+').gsub(/([>].*$)/, '.*') + '$'
424
+ Regexp.new(exp)
425
+ end
426
+
427
+ end
428
+
429
+ end #module