march_hare 2.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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