qup 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.autotest +41 -0
  2. data/.gemtest +1 -0
  3. data/ADAPTER_API.rdoc +97 -0
  4. data/HISTORY.rdoc +9 -0
  5. data/Manifest.txt +52 -0
  6. data/README.rdoc +156 -0
  7. data/Rakefile +246 -0
  8. data/lib/qup.rb +48 -0
  9. data/lib/qup/adapter.rb +57 -0
  10. data/lib/qup/adapter/kestrel.rb +56 -0
  11. data/lib/qup/adapter/kestrel/destination.rb +54 -0
  12. data/lib/qup/adapter/kestrel/queue.rb +101 -0
  13. data/lib/qup/adapter/kestrel/topic.rb +68 -0
  14. data/lib/qup/adapter/maildir.rb +57 -0
  15. data/lib/qup/adapter/maildir/queue.rb +123 -0
  16. data/lib/qup/adapter/maildir/topic.rb +85 -0
  17. data/lib/qup/adapter/redis.rb +55 -0
  18. data/lib/qup/adapter/redis/connection.rb +32 -0
  19. data/lib/qup/adapter/redis/queue.rb +97 -0
  20. data/lib/qup/adapter/redis/topic.rb +76 -0
  21. data/lib/qup/consumer.rb +42 -0
  22. data/lib/qup/message.rb +18 -0
  23. data/lib/qup/producer.rb +28 -0
  24. data/lib/qup/publisher.rb +30 -0
  25. data/lib/qup/queue_api.rb +124 -0
  26. data/lib/qup/session.rb +111 -0
  27. data/lib/qup/subscriber.rb +28 -0
  28. data/lib/qup/topic_api.rb +92 -0
  29. data/spec/qup/adapter/kestrel/queue_spec.rb +9 -0
  30. data/spec/qup/adapter/kestrel/topic_spec.rb +9 -0
  31. data/spec/qup/adapter/kestrel_context.rb +8 -0
  32. data/spec/qup/adapter/kestrel_spec.rb +8 -0
  33. data/spec/qup/adapter/maildir/queue_spec.rb +9 -0
  34. data/spec/qup/adapter/maildir/topic_spec.rb +9 -0
  35. data/spec/qup/adapter/maildir_context.rb +10 -0
  36. data/spec/qup/adapter/maildir_spec.rb +8 -0
  37. data/spec/qup/adapter/redis/queue_spec.rb +9 -0
  38. data/spec/qup/adapter/redis/topic_spec.rb +9 -0
  39. data/spec/qup/adapter/redis_context.rb +6 -0
  40. data/spec/qup/adapter/redis_spec.rb +8 -0
  41. data/spec/qup/adapter_spec.rb +28 -0
  42. data/spec/qup/consumer_spec.rb +40 -0
  43. data/spec/qup/message_spec.rb +13 -0
  44. data/spec/qup/producer_spec.rb +18 -0
  45. data/spec/qup/queue_api_spec.rb +21 -0
  46. data/spec/qup/session_spec.rb +81 -0
  47. data/spec/qup/shared_adapter_examples.rb +29 -0
  48. data/spec/qup/shared_queue_examples.rb +71 -0
  49. data/spec/qup/shared_topic_examples.rb +57 -0
  50. data/spec/qup/topic_api_spec.rb +21 -0
  51. data/spec/qup_spec.rb +37 -0
  52. data/spec/spec_helper.rb +26 -0
  53. metadata +281 -0
@@ -0,0 +1,42 @@
1
+ module Qup
2
+ # Public: Consumes items for a queue
3
+ #
4
+ # A Consumer is created from a Queue and consumes messages from the queue.
5
+ #
6
+ #
7
+ class Consumer
8
+ # Public: Create a new Consumer
9
+ #
10
+ # queue - the Queue this producer is for
11
+ #
12
+ # Returns a new Consumer
13
+ def initialize( queue )
14
+ @queue = queue
15
+ end
16
+
17
+ # Public: Consume a message for the queue
18
+ #
19
+ # Yields the message
20
+ #
21
+ # Most of the time you will want to call this message with a block as the
22
+ # Message will be auto-acknowledged. If you do not consume messages with a
23
+ # block, then you are required to acknowledge the messages on your own.
24
+ #
25
+ # Returns a Message
26
+ def consume(&block)
27
+ @queue.consume(&block)
28
+ end
29
+
30
+ # Public: Acknowledge a consumed message
31
+ #
32
+ # message - The message you are acknowledging
33
+ #
34
+ # A consumed message must be acknowledge so the back end system can
35
+ # be assured that the message has been a fully processed.
36
+ #
37
+ # Returns nothing.
38
+ def acknowledge( message )
39
+ @queue.acknowledge( message )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module Qup
2
+ # Public: A Message is an item that may be put on and taken off the queue.
3
+ #
4
+ class Message
5
+
6
+ # Public: The unique identifier of this message
7
+ attr_reader :key
8
+
9
+ # Public: The data in this Message
10
+ attr_reader :data
11
+
12
+ # Public: Create a Message from the given data
13
+ def initialize( key, data )
14
+ @key = key
15
+ @data = data
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module Qup
2
+ # Public: Produces items for a queue
3
+ #
4
+ # Examples:
5
+ #
6
+ # producer = queue.producer
7
+ # producer.produce( my_message )
8
+ #
9
+ class Producer
10
+ # Public: Create a new Producer
11
+ #
12
+ # queue - the Queue this producer is for
13
+ #
14
+ # Returns a new Producer
15
+ def initialize( queue )
16
+ @queue = queue
17
+ end
18
+
19
+ # Public: Produce a message for the queue
20
+ #
21
+ # message - the Object to put onto the queue
22
+ #
23
+ # Returns nothing.
24
+ def produce( message )
25
+ @queue.produce( message )
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Qup
2
+ # Public: A Publisher produces messages on a Topic
3
+ #
4
+ # Example:
5
+ #
6
+ # pub = topic.publisher
7
+ # pub.publish( my_message )
8
+ #
9
+ class Publisher
10
+
11
+ # Public: The Topic this Publisher publishes to.
12
+ attr_reader :topic
13
+
14
+ # Public: Create a new Publisher for a Topic
15
+ #
16
+ # Returns a Publisher
17
+ def initialize( topic )
18
+ @topic = topic
19
+ end
20
+
21
+ # Public: Publish a Message to all the Subscribers
22
+ #
23
+ # message - the Object to send to all Subscribers.
24
+ #
25
+ # Returns nothing
26
+ def publish( message )
27
+ @topic.publish( message )
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,124 @@
1
+ require 'qup/producer'
2
+ require 'qup/consumer'
3
+ module Qup
4
+ #
5
+ # Public: A QueueAPI for use in a point-to-point messaging pattern.
6
+ #
7
+ # The Queue guarantees that each Message that is on the Queue is delivered and
8
+ # acknowledged only once.
9
+ #
10
+ # A very common pattern for Queue usage is a worker pattern where you have a
11
+ # Producer putting job Messages on the Queue and a collection of Consumers
12
+ # which work on those job Messages.
13
+ #
14
+ # This is the API that MUST be implemented in the adapter.
15
+ #
16
+ # Example:
17
+ #
18
+ # session = Session.new( uri )
19
+ # queue = session.queue( 'my_queue' )
20
+ #
21
+ # queue.producer # => Producer
22
+ # queue.consumer # => Consumer
23
+ # queue.depth # => 4
24
+ #
25
+ module QueueAPI
26
+
27
+ # Public: create a Producer for this Queue
28
+ #
29
+ # Returns a new Producer
30
+ def producer
31
+ Producer.new( self )
32
+ end
33
+
34
+ # Public: create a Consumer for this Queue
35
+ #
36
+ # Returns a new Consumer
37
+ def consumer
38
+ Consumer.new( self )
39
+ end
40
+
41
+
42
+ #--------------------------------------------------------------------------
43
+ # The API that Adapters must implement
44
+ #--------------------------------------------------------------------------
45
+
46
+ # Public: the name of the Queue
47
+ #
48
+ # Returns the String name
49
+ def name
50
+ super
51
+ rescue NoMethodError
52
+ raise NotImplementedError, "please implement 'name'"
53
+ end
54
+
55
+
56
+ # Public: return the number of Messages on the Queue
57
+ #
58
+ # Returns an integer of the Queue depth
59
+ def depth
60
+ raise NotImplementedError, "please implement 'depth'"
61
+ end
62
+
63
+
64
+ # Public: empty all the messages from the Queue, this does not consume them,
65
+ # this removes the from the Queue
66
+ #
67
+ # Returns nothing
68
+ def flush
69
+ raise NotImplementedError, "please implement 'flush'"
70
+ end
71
+
72
+
73
+ # Public: destroy the Queue if possible
74
+ #
75
+ # This will clear the Queue and remove it from the system if possible
76
+ #
77
+ # Returns nothing.
78
+ def destroy
79
+ super
80
+ rescue NoMethodError
81
+ raise NotImplementedError, "please implement 'destroy'"
82
+ end
83
+
84
+
85
+ # Internal: Put an item onto the Queue
86
+ #
87
+ # message - the data to put onto the queue
88
+ #
89
+ # A user of the Qup API should use a Producer instance to put items onto the
90
+ # queue.
91
+ #
92
+ # Returns the Message that was put onto the Queue
93
+ def produce( message )
94
+ raise NotImplementedError, "please implement 'produce'"
95
+ end
96
+
97
+
98
+ # Internal: Retrieve an item from the Queue
99
+ #
100
+ # options - a Hash of options determining how long to wait for a Message
101
+ # :block - should we block until a Message is available
102
+ # (default: true )
103
+ # :timeout - how long to wait for a Message, only valid if
104
+ # :block is false
105
+ #
106
+ # A user of the Qup API should use a Consumer instance to retrieve items
107
+ # from the Queue.
108
+ #
109
+ # Returns a Message
110
+ def consume(&block)
111
+ raise NotImplementedError, "please implement 'consume'"
112
+ end
113
+
114
+
115
+ # Internal: Acknowledge that message is completed and remove it from the
116
+ # Queue.
117
+ #
118
+ # Returns nothing
119
+ def acknowledge( message )
120
+ raise NotImplementedError, "please implement 'acknowledge'"
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,111 @@
1
+ require 'uri'
2
+ require 'pathname'
3
+ module Qup
4
+ # Public: Manage communicating with a provider
5
+ #
6
+ # Examples:
7
+ #
8
+ # Session.new( 'maildir:///tmp/qup' )
9
+ # Session.new( 'kestrel://user:pass@host:port/' )
10
+ #
11
+ # session.queue( 'foo' ) # => Queue
12
+ # session.topic( 'bar' ) # => Topic
13
+ #
14
+ # At the moment, a Session is not considered thread safe, so each Thread should
15
+ # create its own Session.
16
+ class Session
17
+ class ClosedError < Qup::Error; end
18
+
19
+ # Public: The URI of this Session
20
+ attr_reader :uri
21
+
22
+ # Public: Create a new Session
23
+ #
24
+ # uri - The connection String used to connect to appropriate provider
25
+ # options - The Hash of options that are passed to the underlying Adapter
26
+ #
27
+ # Yields the created Session
28
+ #
29
+ # If a session is yielded, it is closed at the end of the block
30
+ #
31
+ # Returns a new Session
32
+ def self.open( uri, options = {}, &block )
33
+ session = ::Qup::Session.new( uri, options )
34
+ return session unless block_given?
35
+ yield session
36
+ ensure
37
+ session.close if block_given?
38
+ end
39
+
40
+ # Public: Create a new Session
41
+ #
42
+ # uri - The connection String used to connect to appropriate provider
43
+ # options - The Hash of options that are passed to the underlying Adapter
44
+ #
45
+ # Returns a new Session
46
+ def initialize( uri, options = {} )
47
+ @uri = URI.parse( uri )
48
+ @root_path = Pathname.new( @uri.path )
49
+
50
+ adapter_klass = Qup::Adapters[@uri.scheme]
51
+ @adapter = adapter_klass.new( @uri, options )
52
+
53
+ @queues = Hash.new
54
+ @topics = Hash.new
55
+ end
56
+
57
+ # Public: Allocate a new Queue
58
+ #
59
+ # Connect to an existing, or Create a new Queue with the given name and
60
+ # options.
61
+ #
62
+ # Yields the Queue.
63
+ #
64
+ # name - The String name of the Queue to connect/create
65
+ #
66
+ # Returns a new Queue instance
67
+ def queue( name, &block )
68
+ destination( name, :queue, @queues, &block )
69
+ end
70
+
71
+ # Public: Allocate a new Topic
72
+ #
73
+ # Connect to an existing, or Create a new Topic with the given name and
74
+ # options.
75
+ #
76
+ # Yields the Topic.
77
+ #
78
+ # name - The String name of the Topic to connect/create
79
+ #
80
+ # Returns a new Topic instance
81
+ def topic( name, &block )
82
+ destination( name, :topic, @topics, &block )
83
+ end
84
+
85
+ # Public: Close the Session
86
+ #
87
+ # Calling closed on an already closed Session does nothing.
88
+ #
89
+ # Returns nothing
90
+ def close
91
+ @adapter.close
92
+ end
93
+
94
+ # Public: Is the Session closed?
95
+ #
96
+ # Returns true if the session is closed, false otherwise.
97
+ def closed?
98
+ @adapter.closed?
99
+ end
100
+
101
+ #######
102
+ private
103
+ #######
104
+ def destination( name, type, collection, &block )
105
+ raise Qup::Session::ClosedError, "Session connected to #{@uri} is closed" if closed?
106
+ d = (collection[name] ||= @adapter.send( type, name ))
107
+ return d unless block_given?
108
+ yield d
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,28 @@
1
+ module Qup
2
+ # Public: Receives Messages from a Topic
3
+ #
4
+ # All Subscribers to a Topic will receive each Message that is published to
5
+ # the Topic.
6
+ #
7
+ class Subscriber < Consumer
8
+ # Public: The name of this subscriber
9
+ attr_reader :name
10
+
11
+ # Public: Create a new Subscriber on a Topic
12
+ #
13
+ # Returns a Subscriber
14
+ def initialize( topic, queue )
15
+ super( queue )
16
+ @topic = topic
17
+ @name = @queue.name
18
+ end
19
+
20
+ # Public: Remove the Subscriber from the Topic
21
+ #
22
+ # This just means to destroy the Queue that it is attached to
23
+ #
24
+ def unsubscribe
25
+ @queue.destroy
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,92 @@
1
+ module Qup
2
+ #
3
+ # Public: A TopicAPI for use in a publish-subscribe Messaging
4
+ #
5
+ # The Topic delivers each Message that it is give to each and every Subscriber
6
+ #
7
+ # This API MUST be implemented in each Adapter
8
+ #
9
+ # Example:
10
+ #
11
+ # session = Qup::Session.new( uri )
12
+ # topic = session.topic( 'news' )
13
+ #
14
+ # pub = topic.publisher
15
+ #
16
+ # sub1 = topic.subscriber( 'sub1' )
17
+ # sub2 = topic.subscriber( 'sub2' )
18
+ #
19
+ # pub.publish( 'some news' )
20
+ #
21
+ # message1 = sub1.consume
22
+ # message2 = sub2.consume
23
+ #
24
+ module TopicAPI
25
+ #--------------------------------------------------------------------------
26
+ # The API that Adapters must implement
27
+ #--------------------------------------------------------------------------
28
+
29
+ # Public: Creates a Publisher for the Topic
30
+ #
31
+ # Returns a new Publisher
32
+ def publisher
33
+ raise NotImplementedError, "please implement 'publisher'"
34
+ end
35
+
36
+
37
+ # Public: Create a subscriber for the Topic
38
+ #
39
+ # name - the String name of the subscriber
40
+ #
41
+ # Creating a subscriber creates a new Subscriber that will receive a copy of
42
+ # every message that is published to the Topic.
43
+ #
44
+ # Subscribers are unique by name, two subscribers with the same name will
45
+ # act as individual Consumers on a queue of their name.
46
+ #
47
+ # Returns a Subscriber
48
+ def subscriber( name )
49
+ raise NotImplementedError, "please implement 'subscriber'"
50
+ end
51
+
52
+
53
+ # Public: the name of the Topic
54
+ #
55
+ # Returns the String name
56
+ def name
57
+ super
58
+ rescue NoMethodError
59
+ raise NotImplementedError, "please implement 'name'"
60
+ end
61
+
62
+
63
+ # Public: destroy the Topic if possible
64
+ #
65
+ # This will remove the Topic from the system if possible
66
+ #
67
+ # Returns nothing.
68
+ def destroy
69
+ super
70
+ rescue NoMethodError
71
+ raise NotImplementedError, "please implement 'destroy'"
72
+ end
73
+
74
+
75
+ # Public: Return the number of Subscribers to this Topic
76
+ #
77
+ # Returns integer
78
+ def subscriber_count
79
+ raise NotImplementedError, "please implement 'subscriber_count'"
80
+ end
81
+
82
+
83
+ # Internal: Publish a Message to all the Subscribers
84
+ #
85
+ # message - the Object to send to all subscribers
86
+ #
87
+ # Returns nothing
88
+ def publish( message )
89
+ raise NotImplementedError, "please implement 'publish'"
90
+ end
91
+ end
92
+ end