gz_activemessaging 0.13.1

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