march_hare 2.0.0-java

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.
@@ -0,0 +1,2 @@
1
+ require "march_hare/consumers/base"
2
+ require "march_hare/consumers/blocking"
@@ -0,0 +1,121 @@
1
+ require "march_hare/versioned_delivery_tag"
2
+
3
+ module MarchHare
4
+ import com.rabbitmq.client.DefaultConsumer
5
+
6
+ class BaseConsumer < DefaultConsumer
7
+ attr_accessor :consumer_tag
8
+ attr_accessor :auto_ack
9
+
10
+ def initialize(channel, queue, opts)
11
+ super(channel)
12
+ @channel = channel
13
+ @queue = queue
14
+ @opts = opts
15
+ @auto_ack = true
16
+
17
+ @cancelling = JavaConcurrent::AtomicBoolean.new
18
+ @cancelled = JavaConcurrent::AtomicBoolean.new
19
+
20
+ @terminated = JavaConcurrent::AtomicBoolean.new
21
+ end
22
+
23
+ def handleDelivery(consumer_tag, envelope, properties, bytes)
24
+ body = String.from_java_bytes(bytes)
25
+ headers = Headers.new(channel, consumer_tag, envelope, properties)
26
+
27
+ deliver(headers, body)
28
+ end
29
+
30
+ def handleCancel(consumer_tag)
31
+ @cancelled.set(true)
32
+ @channel.unregister_consumer(consumer_tag)
33
+
34
+ if f = @opts[:on_cancellation]
35
+ case f.arity
36
+ when 0 then
37
+ f.call
38
+ when 1 then
39
+ f.call(self)
40
+ when 2 then
41
+ f.call(@channel, self)
42
+ when 3 then
43
+ f.call(@channel, self, consumer_tag)
44
+ else
45
+ f.call(@channel, self, consumer_tag)
46
+ end
47
+ end
48
+
49
+ @terminated.set(true)
50
+ end
51
+
52
+ def handleCancelOk(consumer_tag)
53
+ @cancelled.set(true)
54
+ @channel.unregister_consumer(consumer_tag)
55
+
56
+ @terminated.set(true)
57
+ end
58
+
59
+ def start
60
+ # no-op
61
+ end
62
+
63
+ def gracefully_shut_down
64
+ # no-op
65
+ end
66
+
67
+ def deliver(headers, message)
68
+ raise NotImplementedError, 'To be implemented by a subclass'
69
+ end
70
+
71
+ def cancelled?
72
+ @cancelling.get || @cancelled.get
73
+ end
74
+
75
+ def active?
76
+ !terminated?
77
+ end
78
+
79
+ def terminated?
80
+ @terminated.get
81
+ end
82
+
83
+ # @private
84
+ def recover_from_network_failure
85
+ @terminated.set(false)
86
+ @cancelled.set(false)
87
+ @consumer_tag = @channel.basic_consume(@queue.name, @auto_ack, self)
88
+
89
+ @consumer_tag
90
+ end
91
+ end
92
+
93
+ class CallbackConsumer < BaseConsumer
94
+ def initialize(channel, queue, opts, callback)
95
+ raise ArgumentError, "callback must not be nil!" if callback.nil?
96
+
97
+ super(channel, queue, opts)
98
+ @callback = callback
99
+ @callback_arity = @callback.arity
100
+ end
101
+
102
+ def deliver(headers, message)
103
+ if @callback_arity == 2
104
+ @callback.call(headers, message)
105
+ else
106
+ @callback.call(message)
107
+ end
108
+ end
109
+
110
+ def cancel
111
+ if @cancelling.get_and_set(true)
112
+ false
113
+ else
114
+ @channel.basic_cancel(@consumer_tag)
115
+ @cancelled.set(true)
116
+ @terminated.set(true)
117
+ true
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,73 @@
1
+ require "march_hare/consumers/base"
2
+
3
+ module MarchHare
4
+ class BlockingCallbackConsumer < CallbackConsumer
5
+ POISON = :__poison__
6
+
7
+ def initialize(channel, queue, buffer_size, opts, callback)
8
+ super(channel, queue, opts, callback)
9
+ if buffer_size
10
+ @internal_queue = JavaConcurrent::ArrayBlockingQueue.new(buffer_size)
11
+ else
12
+ @internal_queue = JavaConcurrent::LinkedBlockingQueue.new
13
+ end
14
+ end
15
+
16
+ def cancel
17
+ if super
18
+ @internal_queue.offer(POISON)
19
+ end
20
+ end
21
+
22
+ def start
23
+ interrupted = false
24
+ until (@cancelling.get || @cancelled.get) || JavaConcurrent::Thread.current_thread.interrupted?
25
+ begin
26
+ pair = @internal_queue.take
27
+ if pair
28
+ if pair == POISON
29
+ @cancelling.set(true)
30
+ else
31
+ @callback.call(*pair)
32
+ end
33
+ end
34
+ rescue JavaConcurrent::InterruptedException => e
35
+ interrupted = true
36
+ end
37
+ end
38
+ while (pair = @internal_queue.poll)
39
+ if pair
40
+ if pair == POISON
41
+ @cancelling.set(true)
42
+ else
43
+ @callback.call(*pair)
44
+ end
45
+ end
46
+ end
47
+ @terminated.set(true)
48
+ if interrupted
49
+ JavaConcurrent::Thread.current_thread.interrupt
50
+ end
51
+ end
52
+
53
+ def deliver(*pair)
54
+ if (@cancelling.get || @cancelled.get) || JavaConcurrent::Thread.current_thread.interrupted?
55
+ @internal_queue.offer(pair)
56
+ else
57
+ begin
58
+ @internal_queue.put(pair)
59
+ rescue JavaConcurrent::InterruptedException => e
60
+ JavaConcurrent::Thread.current_thread.interrupt
61
+ end
62
+ end
63
+ end
64
+
65
+ def gracefully_shut_down
66
+ @cancelling.set(true)
67
+ @internal_queue.offer(POISON)
68
+
69
+ @terminated.set(true)
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,174 @@
1
+ module MarchHare
2
+ class Exception < StandardError
3
+ end
4
+
5
+ class ShutdownSignal < Exception
6
+ attr_reader :cause
7
+
8
+ def initialize(cause)
9
+ @cause = cause
10
+ end
11
+ end
12
+
13
+ class NetworkException < Exception
14
+ end
15
+
16
+ class ConnectionRefused < NetworkException
17
+ end
18
+
19
+ class ChannelLevelException < Exception
20
+ attr_reader :channel_close
21
+
22
+ def initialize(message, channel_close)
23
+ super(message)
24
+
25
+ @channel_close = channel_close
26
+ end
27
+ end
28
+
29
+ class ConnectionLevelException < Exception
30
+ attr_reader :connection_close
31
+
32
+ def initialize(message, connection_close)
33
+ super(message)
34
+
35
+ @connection_close = connection_close
36
+ end
37
+ end
38
+
39
+ # Raised when RabbitMQ closes network connection before
40
+ # finalizing the connection, typically indicating authentication failure.
41
+ #
42
+ # RabbitMQ versions beyond 3.2 use a better defined authentication failure
43
+ # notifications.
44
+ class PossibleAuthenticationFailureError < Exception
45
+ attr_reader :username, :vhost
46
+
47
+ def initialize(username, vhost, password_length)
48
+ @username = username
49
+ @vhost = vhost
50
+
51
+ super("Authentication with RabbitMQ failed or RabbitMQ version used does not support AMQP 0-9-1. Username: #{username}, vhost: #{vhost}, password length: #{password_length}. Please check your configuration.")
52
+ end
53
+ end
54
+
55
+ # Raised when RabbitMQ 3.2+ reports authentication
56
+ # failure before closing TCP connection.
57
+ class AuthenticationFailureError < PossibleAuthenticationFailureError
58
+ attr_reader :username, :vhost
59
+
60
+ def initialize(username, vhost, password_length)
61
+ super(username, vhost, password_length)
62
+ end
63
+ end
64
+
65
+ class PreconditionFailed < ChannelLevelException
66
+ end
67
+
68
+ class NotFound < ChannelLevelException
69
+ end
70
+
71
+ class ResourceLocked < ChannelLevelException
72
+ end
73
+
74
+ class AccessRefused < ChannelLevelException
75
+ end
76
+
77
+ class ChannelError < ConnectionLevelException
78
+ end
79
+
80
+ class InvalidCommand < ConnectionLevelException
81
+ end
82
+
83
+ class FrameError < ConnectionLevelException
84
+ end
85
+
86
+ class UnexpectedFrame < ConnectionLevelException
87
+ end
88
+
89
+ class ChannelAlreadyClosed < Exception
90
+ end
91
+
92
+
93
+
94
+ # Converts RabbitMQ Java client exceptions
95
+ #
96
+ # @private
97
+ class Exceptions
98
+ def self.convert(e, unwrap_io_exception = true)
99
+ case e
100
+ when java.io.IOException then
101
+ c = e.cause
102
+
103
+ if unwrap_io_exception
104
+ convert(c, false)
105
+ else
106
+ c
107
+ end
108
+ when com.rabbitmq.client.AlreadyClosedException then
109
+ ChannelAlreadyClosed.new(e.reason)
110
+ when com.rabbitmq.client.ShutdownSignalException then
111
+ cmd = e.reason
112
+
113
+ exception_for_protocol_method(cmd.method)
114
+ else
115
+ e
116
+ end
117
+ end
118
+
119
+ def self.convert_and_reraise(e)
120
+ raise convert(e)
121
+ end
122
+
123
+ def self.exception_for_protocol_method(m)
124
+ case m
125
+ # com.rabbitmq.client.AMQP.Connection.Close does not resolve the inner
126
+ # class correctly. Looks like a JRuby bug we work around by using Rubyesque
127
+ # class name. MK.
128
+ when Java::ComRabbitmqClient::AMQP::Connection::Close then
129
+ exception_for_connection_close(m)
130
+ when Java::ComRabbitmqClient::AMQP::Channel::Close then
131
+ exception_for_channel_close(m)
132
+ else
133
+ NotImplementedError.new("Exception convertion for protocol method #{m.inspect} is not implemented!")
134
+ end
135
+ end # def self
136
+
137
+
138
+ def self.exception_for_connection_close(m)
139
+ klass = case m.reply_code
140
+ when 320 then
141
+ ConnectionForced
142
+ when 501 then
143
+ FrameError
144
+ when 503 then
145
+ InvalidCommand
146
+ when 504 then
147
+ ChannelError
148
+ when 505 then
149
+ UnexpectedFrame
150
+ else
151
+ raise "Unknown reply code: #{m.reply_code}, text: #{m.reply_text}"
152
+ end
153
+
154
+ klass.new("Connection-level error: #{m.reply_text}", m)
155
+ end
156
+
157
+ def self.exception_for_channel_close(m)
158
+ klass = case m.reply_code
159
+ when 403 then
160
+ AccessRefused
161
+ when 404 then
162
+ NotFound
163
+ when 405 then
164
+ ResourceLocked
165
+ when 406 then
166
+ PreconditionFailed
167
+ else
168
+ ChannelLevelException
169
+ end
170
+
171
+ klass.new(m.reply_text, m)
172
+ end
173
+ end
174
+ end # MarchHare
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+
3
+ module MarchHare
4
+ import com.rabbitmq.client.AMQP
5
+
6
+ # Represents AMQP 0.9.1 exchanges.
7
+ #
8
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
9
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
10
+ # @see Queue#bind
11
+ class Exchange
12
+ # @return [String] Exchange name
13
+ attr_reader :name
14
+ # @return [MarchHare::Channel] Channel this exchange object uses
15
+ attr_reader :channel
16
+
17
+ # Type of this exchange (one of: :direct, :fanout, :topic, :headers).
18
+ # @return [Symbol]
19
+ attr_reader :type
20
+
21
+ # Instantiates a new exchange.
22
+ #
23
+ # @param [Channel] channel Channel to declare exchange on
24
+ # @params [String] name Exchange name
25
+ # @params [Hash] options ({}) Exchange and declaration attributes
26
+ #
27
+ # @options opts :type [Symbol, String] Exchange type
28
+ # @options opts :durable [Boolean] (false) Will the exchange be durable?
29
+ # @options opts :auto_delete [Boolean] (false) Will the exchange be auto-deleted?
30
+ # @options opts :passive [Boolean] (false) Should passive declaration be used?
31
+ #
32
+ # @see MarchHare::Channel#default_exchange
33
+ # @see MarchHare::Channel#fanout
34
+ # @see MarchHare::Channel#topic
35
+ # @see MarchHare::Channel#direct
36
+ # @see MarchHare::Channel#headers
37
+ # @see MarchHare::Channel#exchange
38
+ def initialize(channel, name, options = {})
39
+ raise ArgumentError, "exchange channel cannot be nil" if channel.nil?
40
+ raise ArgumentError, "exchange name cannot be nil" if name.nil?
41
+ raise ArgumentError, "exchange :type must be specified as an option" if options[:type].nil?
42
+
43
+ @channel = channel
44
+ @name = name
45
+ @type = options[:type]
46
+ @options = {:type => :fanout, :durable => false, :auto_delete => false, :internal => false, :passive => false}.merge(options)
47
+ end
48
+
49
+ # Publishes a message
50
+ #
51
+ # @param [String] payload Message payload. It will never be modified by MarchHare or RabbitMQ in any way.
52
+ # @param [Hash] opts Message properties (metadata) and delivery settings
53
+ #
54
+ # @option opts [String] :routing_key Routing key
55
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?
56
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
57
+ # @option opts [Hash] :properties Messages and delivery properties
58
+ #
59
+ # * :timestamp (Integer) A timestamp associated with this message
60
+ # * :expiration (Integer) Expiration time after which the message will be deleted
61
+ # * :type (String) Message type, e.g. what type of event or command this message represents. Can be any string
62
+ # * :reply_to (String) Queue name other apps should send the response to
63
+ # * :content_type (String) Message content type (e.g. application/json)
64
+ # * :content_encoding (String) Message content encoding (e.g. gzip)
65
+ # * :correlation_id (String) Message correlated to this one, e.g. what request this message is a reply for
66
+ # * :priority (Integer) Message priority, 0 to 9. Not used by RabbitMQ, only applications
67
+ # * :message_id (String) Any message identifier
68
+ # * :user_id (String) Optional user ID. Verified by RabbitMQ against the actual connection username
69
+ # * :app_id (String) Optional application ID
70
+ #
71
+ # @return [MarchHare::Exchange] Self
72
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
73
+ # @api public
74
+ def publish(body, opts = {})
75
+ options = {:routing_key => '', :mandatory => false}.merge(opts)
76
+ @channel.basic_publish(@name,
77
+ options[:routing_key],
78
+ options[:mandatory],
79
+ options.fetch(:properties, Hash.new),
80
+ body.to_java_bytes)
81
+ end
82
+
83
+ # Deletes the exchange unless it is predefined
84
+ #
85
+ # @param [Hash] options Options
86
+ #
87
+ # @option opts [Boolean] if_unused (false) Should this exchange be deleted only if it is no longer used
88
+ #
89
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
90
+ # @api public
91
+ def delete(options={})
92
+ @channel.deregister_exchange(self)
93
+ @channel.exchange_delete(@name, options.fetch(:if_unused, false)) unless predefined?
94
+ end
95
+
96
+ # Binds an exchange to another (source) exchange using exchange.bind AMQP 0.9.1 extension
97
+ # that RabbitMQ provides.
98
+ #
99
+ # @param [String] exchange Source exchange name
100
+ # @param [Hash] options Options
101
+ #
102
+ # @option opts [String] routing_key (nil) Routing key used for binding
103
+ # @option opts [Hash] arguments ({}) Optional arguments
104
+ #
105
+ # @return [MarchHare::Exchange] Self
106
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
107
+ # @see http://rubymarchhare.info/articles/bindings.html Bindings guide
108
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
109
+ # @api public
110
+ def bind(exchange, options={})
111
+ exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end
112
+ @channel.exchange_bind(@name, exchange_name, options.fetch(:routing_key, ''))
113
+ end
114
+
115
+ # Unbinds an exchange from another (source) exchange using exchange.unbind AMQP 0.9.1 extension
116
+ # that RabbitMQ provides.
117
+ #
118
+ # @param [String] source Source exchange name
119
+ # @param [Hash] opts Options
120
+ #
121
+ # @option opts [String] routing_key (nil) Routing key used for binding
122
+ # @option opts [Hash] arguments ({}) Optional arguments
123
+ #
124
+ # @return [Bunny::Exchange] Self
125
+ # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide
126
+ # @see http://rubymarchhare.info/articles/bindings.html Bindings guide
127
+ # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
128
+ # @api public
129
+ def unbind(exchange, opts = {})
130
+ exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end
131
+ @channel.exchange_unbind(@name, exchange_name, opts.fetch(:routing_key, ''), opts[:arguments])
132
+ end
133
+
134
+ # @return [Boolean] true if this exchange is a pre-defined one (amq.direct, amq.fanout, amq.match and so on)
135
+ def predefined?
136
+ @name.empty? || @name.start_with?("amq.")
137
+ end
138
+
139
+ # Waits until all outstanding publisher confirms on the channel
140
+ # arrive.
141
+ #
142
+ # This is a convenience method that delegates to {Channel#wait_for_confirms}
143
+ #
144
+ # @api public
145
+ def wait_for_confirms
146
+ @channel.wait_for_confirms
147
+ end
148
+
149
+
150
+ #
151
+ # Implementation
152
+ #
153
+
154
+ # @private
155
+ def declare!
156
+ unless predefined?
157
+ if @options[:passive]
158
+ then @channel.exchange_declare_passive(@name)
159
+ else @channel.exchange_declare(@name, @options[:type].to_s, @options[:durable], @options[:auto_delete], @options[:arguments])
160
+ end
161
+ end
162
+ end
163
+
164
+ # @private
165
+ def recover_from_network_failure
166
+ # puts "Recovering exchange #{@name} from network failure"
167
+ unless predefined?
168
+ begin
169
+ declare!
170
+
171
+ @channel.register_exchange(self)
172
+ rescue Exception => e
173
+ # TODO: use a logger
174
+ puts "Caught #{e.inspect} while redeclaring and registering exchange #{@name}!"
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end