jruby-hornetq 0.2.3.alpha → 0.2.5.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -0
- data/Rakefile +1 -1
- data/examples/advanced/transaction-failover/producer.rb +0 -1
- data/examples/client/advanced/batch_client.rb +3 -3
- data/examples/client/resque/hornetq_job.rb +197 -0
- data/examples/client/resque/processor.rb +41 -0
- data/examples/client/resque/readme.md +33 -0
- data/examples/client/resque/resque_conf.rb +1 -0
- data/examples/client/resque/resque_worker.rb +22 -0
- data/examples/client/resque/sleep_job.rb +45 -0
- data/examples/simple_worker/README +14 -0
- data/examples/simple_worker/hornetq.yml +34 -0
- data/examples/simple_worker/producer.rb +54 -0
- data/examples/simple_worker/test_object.rb +11 -0
- data/examples/simple_worker/worker.rb +49 -0
- data/lib/hornetq.rb +6 -3
- data/lib/hornetq/client.rb +16 -13
- data/lib/hornetq/client/factory.rb +420 -355
- data/lib/hornetq/client/message_handler.rb +63 -0
- data/lib/hornetq/client/org_hornetq_api_core_client_client_session.rb +157 -2
- data/lib/hornetq/client/org_hornetq_core_client_impl_client_consumer_impl.rb +94 -0
- data/lib/hornetq/client/org_hornetq_core_client_impl_client_message_impl.rb +0 -7
- data/lib/hornetq/client/{requestor.rb → requestor_pattern.rb} +3 -2
- data/lib/hornetq/client/{server.rb → server_pattern.rb} +3 -3
- data/lib/hornetq/client/session_pool.rb +0 -1
- data/lib/hornetq/common/log_delegate.rb +46 -0
- data/lib/hornetq/common/logging.rb +32 -0
- data/lib/hornetq/common/org_hornetq_core_logging_logger.rb +58 -0
- metadata +37 -9
@@ -0,0 +1,63 @@
|
|
1
|
+
module HornetQ::Client
|
2
|
+
|
3
|
+
# For internal use only
|
4
|
+
class MessageHandler
|
5
|
+
include Java::org.hornetq.api.core.client::MessageHandler
|
6
|
+
|
7
|
+
# Parameters:
|
8
|
+
# :statistics Capture statistics on how many messages have been read
|
9
|
+
# true : Capture statistics on the number of messages received
|
10
|
+
# and the time it took to process them.
|
11
|
+
# The timer starts when the listener instance is created and finishes when either the last message was received,
|
12
|
+
# or when Consumer::on_message_statistics is called. In this case on_message_statistics::statistics
|
13
|
+
# can be called several times during processing without affecting the end time.
|
14
|
+
# The start time and message count is never reset for this instance
|
15
|
+
def initialize(parms={}, &proc)
|
16
|
+
@proc = proc
|
17
|
+
|
18
|
+
if parms[:statistics]
|
19
|
+
@message_count = 0
|
20
|
+
@start_time = Time.now
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Method called for every message received on the queue
|
25
|
+
# Per the specification, this method will be called sequentially for each message on the queue.
|
26
|
+
# This method will not be called again until its prior invocation has completed.
|
27
|
+
# Must be onMessage() since on_message() does not work for interface methods that must be implemented
|
28
|
+
def onMessage(message)
|
29
|
+
begin
|
30
|
+
if @message_count
|
31
|
+
@message_count += 1
|
32
|
+
@last_time = Time.now
|
33
|
+
end
|
34
|
+
@proc.call message
|
35
|
+
rescue SyntaxError, NameError => boom
|
36
|
+
HornetQ::logger.error "Unhandled Exception processing Message. Doesn't compile: " + boom
|
37
|
+
HornetQ::logger.error "Ignoring poison message:\n#{message.inspect}"
|
38
|
+
HornetQ::logger.error boom.backtrace.join("\n")
|
39
|
+
rescue StandardError => bang
|
40
|
+
HornetQ::logger.error "Unhandled Exception processing Message. Doesn't compile: " + bang
|
41
|
+
HornetQ::logger.error "Ignoring poison message:\n#{message.inspect}"
|
42
|
+
HornetQ::logger.error boom.backtrace.join("\n")
|
43
|
+
rescue => exc
|
44
|
+
HornetQ::logger.error "Unhandled Exception processing Message. Exception occurred:\n#{exc}"
|
45
|
+
HornetQ::logger.error "Ignoring poison message:\n#{message.inspect}"
|
46
|
+
HornetQ::logger.error exc.backtrace.join("\n")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return Statistics gathered for this listener
|
51
|
+
# Note: These statistics are only useful if the queue is pre-loaded with messages
|
52
|
+
# since the timer start immediately and stops on the last message received
|
53
|
+
def statistics
|
54
|
+
raise "First call Consumer::on_message with :statistics=>true before calling MessageConsumer::statistics()" unless @message_count
|
55
|
+
duration =(@last_time || Time.now) - @start_time
|
56
|
+
{
|
57
|
+
:messages => @message_count,
|
58
|
+
:duration => duration,
|
59
|
+
:messages_per_second => (@message_count/duration).to_i
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,16 +1,171 @@
|
|
1
1
|
# Add methods to Session Interface
|
2
|
+
# For more information on methods, see:
|
3
|
+
# http://hornetq.sourceforge.net/docs/hornetq-2.1.0.Final/api/index.html?org/hornetq/api/core/client/ClientSession.html
|
2
4
|
module Java::org.hornetq.api.core.client::ClientSession
|
3
5
|
|
6
|
+
# Document Methods from Java Docs:
|
7
|
+
# create_queue(address, queue_name, durable?)
|
8
|
+
|
9
|
+
# Creates a ClientConsumer to consume or browse messages matching the filter
|
10
|
+
# from the queue with the given name, calls the supplied block, then close the
|
11
|
+
# consumer
|
12
|
+
#
|
13
|
+
# If the parameter is a String, then it must be the queue name to consume
|
14
|
+
# messages from. Otherwise, the parameters can be supplied in a Hash
|
15
|
+
#
|
16
|
+
# The parameters for creating the consumer are as follows:
|
17
|
+
# :queue_name => The name of the queue to consume messages from. Mandatory
|
18
|
+
# :filter => Only consume messages matching the filter: Default: nil
|
19
|
+
# :browse_only => Whether to just browse the queue or consume messages
|
20
|
+
# true | false. Default: false
|
21
|
+
# :window_size => The consumer window size.
|
22
|
+
# :max_rate => The maximum rate to consume messages.
|
23
|
+
#
|
24
|
+
# Note: If either :window_size or :max_rate is supplied, then BOTH are required
|
25
|
+
#
|
26
|
+
# Returns the result from the block
|
27
|
+
#
|
28
|
+
# Example
|
29
|
+
# session.consumer('my_queue') do |consumer|
|
30
|
+
# msg = consumer.receive_immediate
|
31
|
+
# p msg
|
32
|
+
# msg.acknowledge
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Example
|
36
|
+
# # Just browse the messages without consuming them
|
37
|
+
# session.consumer(:queue_name => 'my_queue', :browse_only => true) do |consumer|
|
38
|
+
# msg = consumer.receive_immediate
|
39
|
+
# p msg
|
40
|
+
# msg.acknowledge
|
41
|
+
# end
|
42
|
+
def consumer(params={}, &block)
|
43
|
+
consumer = nil
|
44
|
+
begin
|
45
|
+
consumer = create_consumer_from_params(params)
|
46
|
+
block.call(consumer)
|
47
|
+
ensure
|
48
|
+
consumer.close if consumer
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Deprecated
|
53
|
+
def create_consumer_from_params(params={})
|
54
|
+
queue_name = params.kind_of?(Hash) ? params[:queue_name] : params
|
55
|
+
raise("Missing mandatory parameter :queue_name") unless queue_name
|
56
|
+
|
57
|
+
if params[:max_rate] || params[:window_size]
|
58
|
+
self.create_consumer(
|
59
|
+
queue_name,
|
60
|
+
params[:filter],
|
61
|
+
params[:window_size],
|
62
|
+
params[:max_rate],
|
63
|
+
params[:browse_only].nil? ? false : params[:browse_only])
|
64
|
+
else
|
65
|
+
self.create_consumer(
|
66
|
+
queue_name,
|
67
|
+
params[:filter],
|
68
|
+
params[:browse_only].nil? ? false : params[:browse_only])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Creates a ClientProducer to send messages, calls the supplied block,
|
73
|
+
# then close the consumer
|
74
|
+
#
|
75
|
+
# If the parameter is a String, then it must be the address to send messages to
|
76
|
+
# Otherwise, the parameters can be supplied in a Hash
|
77
|
+
#
|
78
|
+
# The parameters for creating the consumer are as follows:
|
79
|
+
# :address => The address to which to send messages. If not supplied here,
|
80
|
+
# then the destination address must be supplied with every message
|
81
|
+
# :rate => The producer rate
|
82
|
+
#
|
83
|
+
# Returns the result from the block
|
84
|
+
#
|
85
|
+
# Example
|
86
|
+
# session.producer('MyAddress') do |producer|
|
87
|
+
# msg = session.create_message
|
88
|
+
# msg.type = :text
|
89
|
+
# producer.send(msg)
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# Example
|
93
|
+
# # Send to a different address with each message
|
94
|
+
# session.producer do |producer|
|
95
|
+
# msg = session.create_message
|
96
|
+
# msg.type = :text
|
97
|
+
# producer.send('Another address', msg)
|
98
|
+
# end
|
99
|
+
def producer(params=nil, &block)
|
100
|
+
address = nil
|
101
|
+
rate = nil
|
102
|
+
if params.kind_of?(Hash)
|
103
|
+
address = params[:address]
|
104
|
+
rate = params[:rate]
|
105
|
+
else
|
106
|
+
address = params
|
107
|
+
end
|
108
|
+
|
109
|
+
producer = nil
|
110
|
+
begin
|
111
|
+
producer = if rate
|
112
|
+
self.create_producer(address, rate)
|
113
|
+
elsif address
|
114
|
+
self.create_producer(address)
|
115
|
+
else
|
116
|
+
self.create_producer
|
117
|
+
end
|
118
|
+
block.call(producer)
|
119
|
+
ensure
|
120
|
+
producer.close if producer
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
4
124
|
# To be consistent create Requestor from Session
|
5
125
|
def create_requestor(request_address)
|
6
126
|
#Java::org.hornetq.api.core.client::ClientRequestor.new(self, request_address);
|
7
|
-
HornetQ::Client::
|
127
|
+
HornetQ::Client::RequestorPattern.new(self, request_address)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Creates a RequestorPattern to send a request and to synchronously wait for
|
131
|
+
# the reply, call the supplied block, then close the requestor
|
132
|
+
# Returns the result from the block
|
133
|
+
def requestor(request_address)
|
134
|
+
requestor = nil
|
135
|
+
begin
|
136
|
+
requestor = self.create_requestor(request_address)
|
137
|
+
block.call(requestor)
|
138
|
+
ensure
|
139
|
+
requestor.close if requestor
|
140
|
+
end
|
8
141
|
end
|
9
142
|
|
10
143
|
# Create a server handler for receiving requests and responding with
|
11
144
|
# replies to the supplied address
|
12
145
|
def create_server(input_queue, timeout=0)
|
13
|
-
HornetQ::Client::
|
146
|
+
HornetQ::Client::ServerPattern.new(self, input_queue, timeout)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Creates a ServerPattern to send messages to consume messages and send
|
150
|
+
# replies, call the supplied block, then close the server
|
151
|
+
# Returns the result from the block
|
152
|
+
def server(input_queue, timeout=0, &block)
|
153
|
+
server = nil
|
154
|
+
begin
|
155
|
+
server = self.create_server(input_queue, timeout)
|
156
|
+
block.call(server)
|
157
|
+
ensure
|
158
|
+
server.close if server
|
159
|
+
end
|
14
160
|
end
|
161
|
+
|
162
|
+
def create_queue_ignore_exists(address, queue, durable)
|
163
|
+
begin
|
164
|
+
create_queue(address, queue, durable)
|
165
|
+
rescue Java::org.hornetq.api.core.HornetQException => e
|
166
|
+
raise unless e.cause.code == Java::org.hornetq.api.core.HornetQException::QUEUE_EXISTS
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
15
170
|
end
|
16
171
|
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Consumer Class
|
2
|
+
|
3
|
+
# For the HornetQ Java documentation for this class see:
|
4
|
+
# http://hornetq.sourceforge.net/docs/hornetq-2.1.0.Final/api/index.html?org/hornetq/api/core/client/ClientConsumer.html
|
5
|
+
|
6
|
+
class Java::org.hornetq.core.client.impl::ClientConsumerImpl
|
7
|
+
|
8
|
+
# For each message available to be consumed call the block supplied
|
9
|
+
#
|
10
|
+
# Returns the statistics gathered when :statistics => true, otherwise nil
|
11
|
+
#
|
12
|
+
# Parameters:
|
13
|
+
# :timeout How to timeout waiting for messages
|
14
|
+
# -1 : Wait forever
|
15
|
+
# 0 : Return immediately if no message is available (default)
|
16
|
+
# x : Wait for x milli-seconds for a message to be received from the server
|
17
|
+
# Note: Messages may still be on the queue, but the server has not supplied any messages
|
18
|
+
# in the time interval specified
|
19
|
+
# Default: 0
|
20
|
+
#
|
21
|
+
# :statistics Capture statistics on how many messages have been read
|
22
|
+
# true : This method will capture statistics on the number of messages received
|
23
|
+
# and the time it took to process them.
|
24
|
+
# Statistics are cumulative between calls to ::each and will only be
|
25
|
+
# reset when ::each is called again with :statistics => true
|
26
|
+
def each(parms={}, &proc)
|
27
|
+
raise "Consumer::each requires a code block to be executed for each message received" unless proc
|
28
|
+
|
29
|
+
message_count = nil
|
30
|
+
start_time = nil
|
31
|
+
timeout = (parms[:timeout] || 0).to_i
|
32
|
+
|
33
|
+
if parms[:statistics]
|
34
|
+
message_count = 0
|
35
|
+
start_time = Time.now
|
36
|
+
end
|
37
|
+
|
38
|
+
# Receive messages according to timeout
|
39
|
+
while message = self.receive_with_timeout(timeout) do
|
40
|
+
proc.call(message)
|
41
|
+
message_count += 1 if message_count
|
42
|
+
end
|
43
|
+
|
44
|
+
unless message_count.nil?
|
45
|
+
duration = Time.now - start_time
|
46
|
+
{:messages => message_count,
|
47
|
+
:duration => duration,
|
48
|
+
:messages_per_second => (message_count/duration).to_i}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Receive messages in a separate thread when they arrive
|
53
|
+
# Allows messages to be received in a separate thread. I.e. Asynchronously
|
54
|
+
# This method will return to the caller before messages are processed.
|
55
|
+
# It is then the callers responsibility to keep the program active so that messages
|
56
|
+
# can then be processed.
|
57
|
+
#
|
58
|
+
# Parameters:
|
59
|
+
# :statistics Capture statistics on how many messages have been read
|
60
|
+
# true : This method will capture statistics on the number of messages received
|
61
|
+
# and the time it took to process them.
|
62
|
+
# The timer starts when each() is called and finishes when either the last message was received,
|
63
|
+
# or when Destination::statistics is called. In this case MessageConsumer::statistics
|
64
|
+
# can be called several times during processing without affecting the end time.
|
65
|
+
# Also, the start time and message count is not reset until MessageConsumer::each
|
66
|
+
# is called again with :statistics => true
|
67
|
+
#
|
68
|
+
# The statistics gathered are returned when :statistics => true and :async => false
|
69
|
+
#
|
70
|
+
def on_message(parms={}, &proc)
|
71
|
+
raise "Consumer::on_message requires a code block to be executed for each message received" unless proc
|
72
|
+
|
73
|
+
@listener = HornetQ::Client::MessageHandler.new(parms, &proc)
|
74
|
+
self.setMessageListener @listener
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return the current statistics for a running ::on_message
|
78
|
+
def on_message_statistics
|
79
|
+
stats = @listener.statistics if @listener
|
80
|
+
raise "First call Consumer::on_message with :statistics=>true before calling Consumer::statistics()" unless stats
|
81
|
+
stats
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def receive_with_timeout(timeout)
|
86
|
+
if timeout == -1
|
87
|
+
self.receive
|
88
|
+
elsif timeout == 0
|
89
|
+
self.receive_immediate
|
90
|
+
else
|
91
|
+
self.receive(timeout)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -32,15 +32,8 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
|
|
32
32
|
put_string_property(Java::OrgHornetqCoreClientImpl::ClientMessageImpl::REPLYTO_HEADER_NAME, val)
|
33
33
|
end
|
34
34
|
|
35
|
-
# Return the size of the encoded message
|
36
|
-
# attr_reader :encode_size
|
37
|
-
|
38
35
|
# Return the body for this message
|
39
36
|
# TODO: Do remaining message Types
|
40
|
-
#
|
41
|
-
# WARNING: This method can only be called ONCE!
|
42
|
-
# WARNING: Do not call after setting the body otherwise the send will have
|
43
|
-
# an empty body
|
44
37
|
def body
|
45
38
|
# Allow this buffer to be read multiple times
|
46
39
|
body_buffer.reset_reader_index
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module HornetQ::Client
|
2
2
|
|
3
|
-
#
|
4
|
-
|
3
|
+
# Implements the Requestor Pattern
|
4
|
+
# Send a request to a server and wait for a reply
|
5
|
+
class RequestorPattern
|
5
6
|
def initialize(session, request_address)
|
6
7
|
@session = session
|
7
8
|
@producer = session.create_producer(request_address)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module HornetQ::Client
|
2
|
-
# Create a Server for receiving requests and
|
3
|
-
#
|
2
|
+
# Create a Server following the ServerPattern for receiving requests and
|
3
|
+
# replying to arbitrary queues
|
4
4
|
# Create an instance of this class per thread
|
5
|
-
class
|
5
|
+
class ServerPattern
|
6
6
|
def initialize(session, request_queue, timeout)
|
7
7
|
@session = session
|
8
8
|
@consumer = session.create_consumer(request_queue)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module HornetQ
|
2
|
+
# Internal use only class for delegating HornetQ logging into the Rails or Ruby
|
3
|
+
# loggers
|
4
|
+
#
|
5
|
+
private
|
6
|
+
|
7
|
+
# HornetQ requires a factory from which it can create a logger per thread and/or class
|
8
|
+
class LogDelegateFactory
|
9
|
+
include
|
10
|
+
|
11
|
+
def createDelegate(klass)
|
12
|
+
LogDelegate.new(klass.name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Delegate HornetQ log calls to Rails, Ruby or custom logger
|
17
|
+
class LogDelegate
|
18
|
+
include Java::org.hornetq.spi.core.logging::LogDelegate
|
19
|
+
|
20
|
+
# TODO: Carry class_name over into logging entries depending on implementation
|
21
|
+
def initialize(class_name)
|
22
|
+
@class_name = class_name
|
23
|
+
end
|
24
|
+
|
25
|
+
# DRY, generate a method for each required log level
|
26
|
+
['debug', 'error', 'fatal', 'info', 'trace', 'warn'].each do |level|
|
27
|
+
eval <<-LOG_LEVEL_METHOD
|
28
|
+
def #{level}(message)
|
29
|
+
@logger.#{level}("[\#{@class_name}] \#{message}") if is#{level.capitalize}Enabled
|
30
|
+
end
|
31
|
+
|
32
|
+
def #{level}(message, t)
|
33
|
+
if is#{level.capitalize}Enabled
|
34
|
+
@logger.#{level}("[\#{@class_name}] \#{message}. \#{t.to_string}")
|
35
|
+
@logger.#{level}(t.stack_trace.to_string)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def is#{level.capitalize}Enabled
|
40
|
+
@logger.#{level}?
|
41
|
+
end
|
42
|
+
LOG_LEVEL_METHOD
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Add HornetQ logging capabilities
|
2
|
+
module HornetQ
|
3
|
+
# Returns the logger being used by both HornetQ and jruby-hornetq
|
4
|
+
def self.logger
|
5
|
+
@logger ||= (rails_logger || default_logger)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Replace the logger for both HornetQ and jruby-hornetq
|
9
|
+
# TODO Directly support Log4J as logger since HornetQ has direct support for Log4J
|
10
|
+
def self.logger=(logger)
|
11
|
+
@logger = logger
|
12
|
+
# Also replace the HornetQ logger
|
13
|
+
Java::org.hornetq.core.logging::Logger.setDelegateFactory(HornetQ::LogDelegateFactory.new)
|
14
|
+
# TODO org.hornetq.core.logging.Logger.setDelegateFactory(org.hornetq.integration.logging.Log4jLogDelegateFactory.new)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def self.rails_logger
|
19
|
+
(defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
|
20
|
+
(defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
|
21
|
+
end
|
22
|
+
|
23
|
+
# By default we use the HornetQ Logger
|
24
|
+
def self.default_logger
|
25
|
+
Java::org.hornetq.core.logging::Logger.getLogger(org.hornetq.api.core.client::HornetQClient)
|
26
|
+
# require 'logger'
|
27
|
+
# l = Logger.new(STDOUT)
|
28
|
+
# l.level = Logger::INFO
|
29
|
+
# l
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|