rosetta_queue 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/History.txt +38 -0
  2. data/MIT-LICENSE.txt +19 -0
  3. data/README.rdoc +11 -0
  4. data/Rakefile +39 -0
  5. data/VERSION.yml +4 -0
  6. data/cucumber.yml +1 -0
  7. data/examples/sample_amqp_consumer.rb +45 -0
  8. data/examples/sample_amqp_fanout_consumer.rb +52 -0
  9. data/examples/sample_amqp_fanout_producer.rb +18 -0
  10. data/examples/sample_amqp_producer.rb +16 -0
  11. data/features/filtering.feature +31 -0
  12. data/features/messaging.feature +48 -0
  13. data/features/step_definitions/common_messaging_steps.rb +82 -0
  14. data/features/step_definitions/filtering_steps.rb +17 -0
  15. data/features/step_definitions/point_to_point_steps.rb +22 -0
  16. data/features/step_definitions/publish_subscribe_steps.rb +25 -0
  17. data/features/support/env.rb +25 -0
  18. data/features/support/sample_consumers.rb +29 -0
  19. data/lib/rosetta_queue.rb +23 -0
  20. data/lib/rosetta_queue/adapter.rb +39 -0
  21. data/lib/rosetta_queue/adapters/amqp.rb +48 -0
  22. data/lib/rosetta_queue/adapters/amqp_evented.rb +132 -0
  23. data/lib/rosetta_queue/adapters/amqp_synch.rb +123 -0
  24. data/lib/rosetta_queue/adapters/base.rb +27 -0
  25. data/lib/rosetta_queue/adapters/beanstalk.rb +56 -0
  26. data/lib/rosetta_queue/adapters/fake.rb +26 -0
  27. data/lib/rosetta_queue/adapters/null.rb +57 -0
  28. data/lib/rosetta_queue/adapters/stomp.rb +88 -0
  29. data/lib/rosetta_queue/base.rb +15 -0
  30. data/lib/rosetta_queue/consumer.rb +30 -0
  31. data/lib/rosetta_queue/consumer_managers/base.rb +24 -0
  32. data/lib/rosetta_queue/consumer_managers/evented.rb +43 -0
  33. data/lib/rosetta_queue/consumer_managers/threaded.rb +94 -0
  34. data/lib/rosetta_queue/core_ext/string.rb +22 -0
  35. data/lib/rosetta_queue/core_ext/time.rb +20 -0
  36. data/lib/rosetta_queue/destinations.rb +33 -0
  37. data/lib/rosetta_queue/exception_handler.rb +105 -0
  38. data/lib/rosetta_queue/exceptions.rb +10 -0
  39. data/lib/rosetta_queue/filters.rb +58 -0
  40. data/lib/rosetta_queue/logger.rb +27 -0
  41. data/lib/rosetta_queue/message_handler.rb +52 -0
  42. data/lib/rosetta_queue/producer.rb +21 -0
  43. data/lib/rosetta_queue/spec_helpers.rb +5 -0
  44. data/lib/rosetta_queue/spec_helpers/hash.rb +21 -0
  45. data/lib/rosetta_queue/spec_helpers/helpers.rb +47 -0
  46. data/lib/rosetta_queue/spec_helpers/publishing_matchers.rb +144 -0
  47. data/spec/rosetta_queue/adapter_spec.rb +101 -0
  48. data/spec/rosetta_queue/adapters/amqp_synchronous_spec.rb +277 -0
  49. data/spec/rosetta_queue/adapters/beanstalk_spec.rb +47 -0
  50. data/spec/rosetta_queue/adapters/fake_spec.rb +72 -0
  51. data/spec/rosetta_queue/adapters/null_spec.rb +31 -0
  52. data/spec/rosetta_queue/adapters/shared_adapter_behavior.rb +38 -0
  53. data/spec/rosetta_queue/adapters/shared_fanout_behavior.rb +20 -0
  54. data/spec/rosetta_queue/adapters/stomp_spec.rb +126 -0
  55. data/spec/rosetta_queue/consumer_managers/evented_spec.rb +56 -0
  56. data/spec/rosetta_queue/consumer_managers/shared_manager_behavior.rb +26 -0
  57. data/spec/rosetta_queue/consumer_managers/threaded_spec.rb +51 -0
  58. data/spec/rosetta_queue/consumer_spec.rb +99 -0
  59. data/spec/rosetta_queue/core_ext/string_spec.rb +15 -0
  60. data/spec/rosetta_queue/destinations_spec.rb +34 -0
  61. data/spec/rosetta_queue/exception_handler_spec.rb +106 -0
  62. data/spec/rosetta_queue/filters_spec.rb +57 -0
  63. data/spec/rosetta_queue/message_handler_spec.rb +47 -0
  64. data/spec/rosetta_queue/producer_spec.rb +77 -0
  65. data/spec/rosetta_queue/shared_messaging_behavior.rb +21 -0
  66. data/spec/spec.opts +4 -0
  67. data/spec/spec_helper.rb +47 -0
  68. metadata +142 -0
data/History.txt ADDED
@@ -0,0 +1,38 @@
1
+ == 0.4.x (git)
2
+
3
+ == 0.4.0
4
+ === New features
5
+ * Allows for custom exception handling logic to be registered for publishing and consuming actions. See ExceptionHandler for more info. (Ben Mabey)
6
+ * RQ was doing a blanket rescue for publishing and consuming and then logging the errors. If you still want this behaviour you will need to register a block/class to do that. Again, see ExceptionHandler for more details.
7
+ * Added core time extension when ActiveSupport not present. (Chris Wyckoff)
8
+ === Bufixes
9
+ * Closing the connection after publishing message now instead of unsubscribing. (Chris Wyckoff)
10
+
11
+
12
+ == 0.2.0
13
+ === New features
14
+ * Synchronous AMQP adapter (Chris Wyckoff - Lead Media Partners)
15
+ * Uses celldee's synchronous AMQP client, 'Bunny'.
16
+ * Added .delete method to Consumer class (Chris Wyckoff - Lead Media Partners)
17
+ * Allows user to purge a queue.
18
+ * Works for AMQP adapters only.
19
+ === Bufixes
20
+ * client :ack now working. (Chris Wyckoff - Lead Media Partners)
21
+
22
+ == 0.1.4
23
+
24
+ === New features
25
+ * Removed dependency on ActiveSupport and brought over needed String methods. (Derek and Matt - Greenview Data, Inc.)
26
+ * Safer shutdown in ThreadedManager (Rob Sanheim - Relevance)
27
+ * Beanstalk Adaper (David Brady - Lead Media Partners)
28
+ * Still needs some work to have it take advantage of beanstalk's subscribe functionality.
29
+ * Synchronous AMQP adapter
30
+ * Uses celldee's synchronous AMQP client, 'Bunny'.
31
+ * Added .delete method to Consumer class
32
+ * Allows user to purge a queue.
33
+ * Works for AMQP adapters only.
34
+ * Beanstalk Adaper (David Brady)
35
+ * Still needs some work to have it take advantage of beanstalk's subscribe functionality.
36
+
37
+ == 0.1.0 / 2008-01-28 - Initial Release
38
+ RosettaQueue was realased in the wild! RQ's initial development was primarily sponsored by Alliance Health Networks (thanks!!). The original authors were Chris Wyckoff and Ben Mabey. The initial release included adapters for stomp and amqp, in addition to a null and fake adapters for testing.
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008-2009 Chris Wyckoff, Ben Mabey
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,11 @@
1
+ = Rosetta Queue
2
+
3
+ Rosetta Queue is a messaging gateway API with adapters for many messaging systems available in Ruby. Messaging systems can be easily switched out with a small configuration change. Code for testing on the object and application level is also provided.
4
+
5
+ Documentation can be found on the wiki.[http://wiki.github.com/bmabey/rosetta_queue]
6
+
7
+ == Authors
8
+
9
+ Ben Mabey [http://github.com/bmabey]
10
+
11
+ Chris Wyckoff [http://github.com/cwyckoff]
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'spec/rake/spectask'
3
+ require 'cucumber/rake/task'
4
+
5
+ Cucumber::Rake::Task.new do |t|
6
+ t.cucumber_opts = "--format pretty"
7
+ end
8
+
9
+ desc "Run the specs under spec"
10
+ Spec::Rake::SpecTask.new do |t|
11
+ t.spec_opts = ['--options', "spec/spec.opts"]
12
+ t.spec_files = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ begin
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |s|
18
+ s.name = "rosetta_queue"
19
+ s.rubyforge_project = "rosetta-queue"
20
+ s.summary = %Q{Messaging gateway API with adapters for many messaging systems available in Ruby.}
21
+ s.email = "cbwyckoff@gmail.com"
22
+ s.homepage = "http://github.com/cwyckoff/rosetta_queue"
23
+ s.description = %Q{Messaging gateway API with adapters for many messaging systems available in Ruby. Messaging systems can be easily switched out with a small configuration change. Code for testing on the object and application level is also provided.}
24
+ s.extra_rdoc_files = ["README.rdoc", "MIT-LICENSE.txt"]
25
+ s.files = FileList["[A-Z]*.*", "{bin,generators,lib,features,spec}/**/*", "Rakefile", "cucumber.yml"]
26
+ s.authors = ["Ben Mabey", "Chris Wyckoff"]
27
+ end
28
+ rescue LoadError
29
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
30
+ end
31
+
32
+ desc "Default task runs specs"
33
+ task :default => [:spec]
34
+
35
+ desc 'Removes trailing whitespace'
36
+ task :whitespace do
37
+ sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
38
+ end
39
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 4
3
+ :patch: 0
4
+ :major: 0
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: features --format pretty --require features --tags ~@unreliable
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../init.rb'
3
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/rosetta_queue/consumer_managers/threaded')
4
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
5
+
6
+ module RosettaQueue
7
+
8
+ Adapter.define do |a|
9
+ a.user = "rosetta"
10
+ a.password = "password"
11
+ a.host = "localhost"
12
+ a.type = 'amqp_synch'
13
+ end
14
+
15
+ Destinations.define do |dest|
16
+ dest.map :foo, "queue.foo"
17
+ end
18
+
19
+ class MessageHandlerFoo
20
+ include RosettaQueue::MessageHandler
21
+ subscribes_to :foo
22
+ options :ack => true
23
+ attr_reader :msg
24
+
25
+ def on_message(msg)
26
+ puts "FOO received message: #{msg}"
27
+ end
28
+
29
+ end
30
+
31
+ # consumer = RosettaQueue::Consumer.new(MessageHandlerFoo.new)
32
+ # # Thread.new(consumer) do |cons|
33
+ # consumer.receive
34
+ # # end
35
+ # puts "sleeping for 10"
36
+ # sleep 10
37
+ # puts "shutting consumer down"
38
+ # consumer.disconnect
39
+
40
+ ThreadedManager.create do |m|
41
+ m.add MessageHandlerFoo.new
42
+ m.start
43
+ end
44
+
45
+ end
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../init.rb'
3
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/rosetta_queue/consumer_managers/threaded.rb')
4
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
5
+
6
+ module RosettaQueue
7
+
8
+ Adapter.define do |a|
9
+ a.user = "rosetta"
10
+ a.password = "password"
11
+ a.host = "localhost"
12
+ a.type = 'amqp_synch'
13
+ end
14
+
15
+ Destinations.define do |dest|
16
+ dest.map :foo, "fanout.foo"
17
+ end
18
+
19
+ class MessageHandlerFoo
20
+ include RosettaQueue::MessageHandler
21
+ subscribes_to :foo
22
+ options :ack => true
23
+ attr_reader :msg
24
+
25
+ def on_message(msg)
26
+ puts "FOO received message: #{msg}"
27
+ ack
28
+ end
29
+
30
+ end
31
+
32
+ class MessageHandlerBar
33
+ include RosettaQueue::MessageHandler
34
+ subscribes_to :foo
35
+ options :ack => true
36
+ attr_reader :msg
37
+
38
+ def on_message(msg)
39
+ puts "BAR received message: #{msg}"
40
+ ack
41
+ end
42
+ end
43
+
44
+
45
+ # threaded version
46
+ ThreadedManager.create do |m|
47
+ m.add MessageHandlerFoo.new
48
+ m.add MessageHandlerBar.new
49
+ m.start
50
+ end
51
+
52
+ end
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../init.rb'
3
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
4
+
5
+ RosettaQueue::Adapter.define do |a|
6
+ a.user = "rosetta"
7
+ a.password = "password"
8
+ a.host = "localhost"
9
+ a.type = "amqp_synch"
10
+ end
11
+
12
+ RosettaQueue::Destinations.define do |dest|
13
+ dest.map :foo, "fanout.foo"
14
+ end
15
+
16
+
17
+ RosettaQueue::Producer.publish(:foo, "hello there")
18
+
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../init.rb'
3
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
4
+
5
+ RosettaQueue::Adapter.define do |a|
6
+ a.user = "rosetta"
7
+ a.password = "password"
8
+ a.host = "localhost"
9
+ a.type = "amqp_synch"
10
+ end
11
+
12
+ RosettaQueue::Destinations.define do |dest|
13
+ dest.map :foo, "queue.foo"
14
+ end
15
+
16
+ RosettaQueue::Producer.publish(:foo, "hello there")
@@ -0,0 +1,31 @@
1
+ Feature: Message Filtering
2
+ In order to save time and prevent lots of typing
3
+ As a RosettaQueue user
4
+ I want to be able to define filters for processing all of my messages
5
+
6
+
7
+ Scenario Outline: receiving filter
8
+ Given RosettaQueue is configured for '<Adapter>'
9
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
10
+ And a receiving filter is defined to prepend 'Foo' to all messages
11
+ And the message 'Hello World' is published to queue '<Queue>'
12
+ When the message on '<Queue>' is consumed
13
+ Then the consumed message should equal "Foo Hello World"
14
+
15
+ Examples:
16
+ | Adapter | Queue | QueueAddress |
17
+ | amqp_synch | foo | queue.foo |
18
+ | stomp | foo | /queue/foo |
19
+
20
+ Scenario Outline: sending filter
21
+ Given RosettaQueue is configured for '<Adapter>'
22
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
23
+ And a sending filter is defined to prepend 'Foo' to all messages
24
+ And the message 'Hello World' is published to queue '<Queue>'
25
+ When the message on '<Queue>' is consumed
26
+ Then the consumed message should equal "Foo Hello World"
27
+
28
+ Examples:
29
+ | Adapter | Queue | QueueAddress |
30
+ | amqp_synch | foo | queue.foo |
31
+ | stomp | foo | /queue/foo |
@@ -0,0 +1,48 @@
1
+ Story: Producing and Consuming
2
+
3
+ As a RosettaQueue user
4
+ I want to publish and consume point-to-point using various messaging protocols
5
+ So that I can reliably integrate my systems with a message broker
6
+
7
+ Background:
8
+ Given consumer logs have been cleared
9
+
10
+ Scenario Outline: Point-to-Point
11
+ Given RosettaQueue is configured for '<Adapter>'
12
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
13
+ And a consumer is listening to queue '<Queue>'
14
+ When a message is published to '<Queue>'
15
+ Then the message should be consumed from '<Queue>'
16
+
17
+ Examples:
18
+ | Adapter | Queue | QueueAddress |
19
+ | amqp_synch | foo | queue.foo |
20
+ | amqp_evented | bar | queue.bar |
21
+ | stomp | baz | /queue/baz |
22
+ #| beanstalk | baz | baz |
23
+
24
+ Scenario Outline: Delete queue
25
+ Given RosettaQueue is configured for '<Adapter>'
26
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
27
+ When a message is published to '<Queue>'
28
+ And the queue '<Queue>' is deleted
29
+ Then the queue '<Queue>' should no longer exist
30
+
31
+ Examples:
32
+ | Adapter | Queue | QueueAddress |
33
+ | amqp_synch | foo | queue.foo |
34
+
35
+
36
+ @unreliable
37
+ Scenario Outline: Publish-Subscribe
38
+ Given RosettaQueue is configured for '<Adapter>'
39
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
40
+ And multiple consumers are listening to queue '<Queue>'
41
+ When a message is published to '<Queue>'
42
+ Then multiple messages should be consumed from '<Queue>'
43
+
44
+ Examples:
45
+ | Adapter | QueueAddress | Queue |
46
+ | amqp_synch | fanout.baz | baz |
47
+ | amqp_evented | queue.foo | foo |
48
+ | stomp | /topic/foo | bar |
@@ -0,0 +1,82 @@
1
+ def eval_consumer_class(queue, log_file="point-to-point.log", klass_name=nil)
2
+ klass_name = "#{queue.to_s.capitalize}Consumer" if klass_name.nil?
3
+ return if Object.const_defined?(klass_name)
4
+ options = {}
5
+ unless @adapter_type == "stomp"
6
+ options = ":ack => true"
7
+ else
8
+ options = ":ack => 'client'"
9
+ end
10
+
11
+ str = <<-EOC
12
+ class #{klass_name}
13
+ include RosettaQueue::MessageHandler
14
+ subscribes_to :#{queue}
15
+ options #{options}
16
+
17
+ def on_message(msg)
18
+ begin
19
+ file_path = "#{CONSUMER_LOG_DIR}/#{log_file}"
20
+ File.open(file_path, "w+") do |f|
21
+ f << msg + " from #{klass_name}"
22
+ end
23
+ rescue Exception => e
24
+ puts e.message
25
+ end
26
+ end
27
+ end
28
+ EOC
29
+
30
+ eval(str)
31
+ Object.const_get(klass_name)
32
+ end
33
+
34
+ Given /^consumer logs have been cleared$/ do
35
+ %w[point-to-point pub-sub fooconsumer barconsumer].each do |file_name|
36
+ file_path = "#{CONSUMER_LOG_DIR}/#{file_name}.log"
37
+ File.delete(file_path) if File.exists?(file_path)
38
+ File.open(file_path, "a+")
39
+ end
40
+ end
41
+
42
+ Given /^RosettaQueue is configured for '(\w+)'$/ do |adapter_type|
43
+ @adapter_type = adapter_type
44
+ RosettaQueue::Adapter.define do |a|
45
+ a.user = "rosetta"
46
+ a.password = "password"
47
+ a.host = "localhost"
48
+ a.type = @adapter_type
49
+ a.port = case @adapter_type
50
+ when /stomp/
51
+ "61613"
52
+ when /beanstalk/
53
+ "11300"
54
+ else
55
+ nil
56
+ end
57
+ end
58
+ end
59
+
60
+ Given /^a destination is set with queue '(.*)' and queue address '(.*)'$/ do |key, queue|
61
+ RosettaQueue::Destinations.define do |dest|
62
+ dest.map key.to_sym, queue
63
+ end
64
+ end
65
+
66
+ Given /^the message '(.+)' is published to queue '(.+)'$/ do |message, queue_name|
67
+ publish_message(message, {:to => queue_name})
68
+ end
69
+
70
+ When /^a message is published to '(\w+)'$/ do |q|
71
+ RosettaQueue::Producer.publish(q.to_sym, "Hello World!")
72
+ # publish_message("Hello World!", {:options => {:to => q}})
73
+ end
74
+
75
+ When /^the queue '(.*)' is deleted$/ do |queue|
76
+ system("rabbitmqctl list_queues | grep #{queue}").should be_true
77
+ RosettaQueue::Consumer.delete(queue.to_sym)
78
+ end
79
+
80
+ Then /^the queue '(.*)' should no longer exist$/ do |queue|
81
+ system("rabbitmqctl list_queues | grep #{queue}").should be_false
82
+ end
@@ -0,0 +1,17 @@
1
+ Then /^a (receiving|sending) filter is defined to prepend 'Foo' to all messages$/ do |filter_type|
2
+ RosettaQueue::Filters.reset
3
+ RosettaQueue::Filters.define do |f|
4
+ f.send(filter_type) { |message| "Foo #{message}" }
5
+ end
6
+ end
7
+
8
+ When /^the message on '(.+)' is consumed$/ do |queue_name|
9
+ # TODO
10
+ # @consumed_message = queue(:foo_queue).pop
11
+ @consumed_message = consume_once(queue_name.to_sym)
12
+ end
13
+
14
+ Then /^the consumed message should equal "(.+)"$/ do |consumed_message|
15
+ @consumed_message.should == consumed_message
16
+ end
17
+