jruby-hornetq 0.2.3.alpha → 0.2.5.alpha
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.
- 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
|