jruby-jms 0.9.0

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/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ raise "jruby-jms must be built with JRuby: try again with `jruby -S rake'" unless defined?(JRUBY_VERSION)
2
+
3
+ require 'rake/clean'
4
+ require 'date'
5
+ require 'java'
6
+
7
+ desc "Build gem"
8
+ task :gem do |t|
9
+ gemspec = Gem::Specification.new do |s|
10
+ s.name = 'jruby-jms'
11
+ s.version = '0.9.0'
12
+ s.author = 'Reid Morrison'
13
+ s.email = 'rubywmq@gmail.com'
14
+ s.homepage = 'https://github.com/reidmorrison/jruby-jms'
15
+ s.date = Date.today.to_s
16
+ s.description = 'JRuby-JMS is a Java and Ruby library that exposes the Java JMS API in a ruby friendly way. For JRuby only.'
17
+ s.summary = 'JRuby interface into JMS'
18
+ s.files = FileList["./**/*"].exclude('*.gem', './nbproject/*').map{|f| f.sub(/^\.\//, '')}
19
+ s.has_rdoc = true
20
+ end
21
+ Gem::Builder.new(gemspec).build
22
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Sample Consumer:
3
+ # Retrieve all messages from a queue
4
+ #
5
+
6
+ # Allow examples to be run in-place without requiring a gem install
7
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
8
+
9
+ require 'rubygems'
10
+ require 'jms'
11
+ require 'yaml'
12
+
13
+ jms_provider = ARGV[0] || 'activemq'
14
+
15
+ # Load Connection parameters from configuration file
16
+ config = YAML.load_file(File.join(File.dirname(__FILE__), 'jms.yml'))[jms_provider]
17
+ raise "JMS Provider option:#{jms_provider} not found in jms.yml file" unless config
18
+
19
+ JMS::Connection.session(config) do |session|
20
+ session.consume(:q_name => 'ExampleQueue', :timeout=>1000) do |message|
21
+ p message
22
+ end
23
+ end
data/examples/jms.yml ADDED
@@ -0,0 +1,22 @@
1
+ # This YAML file contains the configuration options for several different
2
+ # JMS Providers
3
+ #
4
+ # The Examples that ship with jruby-jms will use the entry 'default' unless
5
+ # overriden at the command line. For example:
6
+ # jruby producer.rb activemq
7
+ #
8
+ ---
9
+ activemq:
10
+ :factory: org.apache.activemq.ActiveMQConnectionFactory
11
+ :broker_url: tcp://localhost:61616
12
+
13
+ hornetq:
14
+ # Connect to a local HornetQ Broker using JNDI
15
+ :jndi_name: /ConnectionFactory
16
+ :jndi_context:
17
+ java.naming.factory.initial: org.jnp.interfaces.NamingContextFactory
18
+ java.naming.provider.url: jnp://localhost:1099
19
+ java.naming.factory.url.pkgs: org.jboss.naming:org.jnp.interfaces
20
+ java.naming.security.principal: guest
21
+ java.naming.security.credentials: guest
22
+
@@ -0,0 +1,31 @@
1
+ #
2
+ # log4j properties
3
+ #
4
+
5
+ # Screen only logging
6
+ log4j.rootLogger=INFO, terminal
7
+
8
+ # To enable file logging
9
+ #log4j.rootLogger=INFO, terminal, out
10
+
11
+ # Set logging level for jruby-jms.
12
+ # Valid values: WARN, INFO, DEBUG, TRACE
13
+ # TRACE will log every message sent or received. Use with caution
14
+ log4j.logger.JMS=TRACE
15
+
16
+ #The logging properties used during tests..
17
+ # CONSOLE appender not used by default
18
+ log4j.appender.terminal=org.apache.log4j.ConsoleAppender
19
+ log4j.appender.terminal.layout=org.apache.log4j.PatternLayout
20
+ log4j.appender.terminal.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c{1}] %m%n
21
+ log4j.appender.terminal.target=System.out
22
+ #log4j.appender.terminal.threshold=INFO
23
+
24
+ # File appender
25
+ log4j.appender.out=org.apache.log4j.RollingFileAppender
26
+ log4j.appender.out.file=jruby-jms.log
27
+ log4j.appender.out.maxFileSize=16384KB
28
+ log4j.appender.out.maxBackupIndex=5
29
+ log4j.appender.out.append=true
30
+ log4j.appender.out.layout=org.apache.log4j.PatternLayout
31
+ log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] %-5p %-40.40c - %m%n
@@ -0,0 +1,27 @@
1
+ #
2
+ # Sample Consumer:
3
+ # Retrieve all messages from a queue
4
+ #
5
+
6
+ # Allow examples to be run in-place without requiring a gem install
7
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
8
+
9
+ require 'rubygems'
10
+ require 'yaml'
11
+ require 'jms'
12
+
13
+ jms_provider = ARGV[1] || 'actvemq'
14
+
15
+ # Load Connection parameters from configuration file
16
+ config = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'jms.yml'))[jms_provider]
17
+ raise "JMS Provider option:#{jms_provider} not found in jms.yml file" unless config
18
+
19
+ JMS::Connection.session(config) do |session|
20
+ session.consumer(:q_name => 'ExampleQueue') do |consumer|
21
+ stats = consumer.each(:statistics => true) do |message|
22
+ puts "=================================="
23
+ p message
24
+ end
25
+ puts "STATISTICS :" + stats.inspect
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ #
2
+ # Sample Producer:
3
+ # Write messages to the queue
4
+ #
5
+
6
+ # Allow examples to be run in-place without requiring a gem install
7
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
8
+
9
+ require 'rubygems'
10
+ require 'yaml'
11
+ require 'jms'
12
+
13
+ jms_provider = ARGV[0] || 'activemq'
14
+ count = (ARGV[1] || 5).to_i
15
+
16
+ # Load Connection parameters from configuration file
17
+ config = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'jms.yml'))[jms_provider]
18
+ raise "JMS Provider option:#{jms_provider} not found in jms.yml file" unless config
19
+
20
+ JMS::Connection.session(config) do |session|
21
+ start_time = Time.now
22
+
23
+ session.producer(:q_name => 'ExampleQueue') do |producer|
24
+ count.times do |i|
25
+ producer.send(session.message("Hello Producer #{i}"))
26
+ end
27
+ end
28
+
29
+ duration = Time.now - start_time
30
+ puts "Delivered #{count} messages in #{duration} seconds at #{count/duration} messages per second"
31
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Sample Producer:
3
+ # Write messages to the queue
4
+ #
5
+
6
+ # Allow examples to be run in-place without requiring a gem install
7
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
8
+
9
+ require 'rubygems'
10
+ require 'yaml'
11
+ require 'jms'
12
+
13
+ jms_provider = ARGV[0] || 'activemq'
14
+
15
+ # Load Connection parameters from configuration file
16
+ config = YAML.load_file(File.join(File.dirname(__FILE__), 'jms.yml'))[jms_provider]
17
+ raise "JMS Provider option:#{jms_provider} not found in jms.yml file" unless config
18
+
19
+ JMS::Connection.session(config) do |session|
20
+ session.producer(:q_name => 'ExampleQueue') do |producer|
21
+ producer.send(session.message("Hello World"))
22
+ end
23
+ end
data/lib/jms.rb ADDED
@@ -0,0 +1,25 @@
1
+ ################################################################################
2
+ # Copyright 2008, 2009, 2010, 2011 J. Reid Morrison
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ################################################################################
16
+
17
+ require 'java'
18
+ require 'jms/connection'
19
+ require 'jms/javax_jms_message'
20
+ require 'jms/javax_jms_text_message'
21
+ require 'jms/javax_jms_map_message'
22
+ require 'jms/javax_jms_object_message'
23
+ require 'jms/javax_jms_session'
24
+ require 'jms/javax_jms_message_consumer'
25
+ require 'jms/javax_jms_queue_browser'
@@ -0,0 +1,480 @@
1
+ ################################################################################
2
+ # Copyright 2008, 2009, 2010, 2011 J. Reid Morrison
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ################################################################################
16
+
17
+ # Module: Java Messaging System (JMS) Interface
18
+ module JMS
19
+ # Every JMS session must have at least one Connection instance
20
+ # A Connection instance represents a connection between this client application
21
+ # and the JMS Provider (server/queue manager/broker).
22
+ # A connection is distinct from a Session, in that multiple Sessions can share a
23
+ # single connection. Also, unit of work control (commit/rollback) is performed
24
+ # at the Session level.
25
+ #
26
+ # Since many JRuby applications will only have one connection and one session
27
+ # several convenience methods have been added to support creating both the
28
+ # Session and Connection objects automatically.
29
+ #
30
+ # For Example, to read all messages from a queue and then terminate:
31
+ # require 'rubygems'
32
+ # require 'jms'
33
+ #
34
+ # JMS::Connection.create_session(
35
+ # :queue_manager=>'REID', # Should be :q_mgr_name
36
+ # :host_name=>'localhost',
37
+ # :channel=>'MY.CLIENT.CHL',
38
+ # :port=>1414,
39
+ # :factory => com.ibm.mq.jms.MQQueueConnectionFactory,
40
+ # :transport_type => com.ibm.mq.jms.JMSC::MQJMS_TP_CLIENT_MQ_TCPIP,
41
+ # :username => 'mqm'
42
+ # ) do |session|
43
+ # session.consumer(:q_name=>'TEST', :mode=>:input) do |consumer|
44
+ # if message = consumer.receive_no_wait
45
+ # puts "Data Received: #{message.data}"
46
+ # else
47
+ # puts 'No message available'
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # The above code creates a Connection and then a Session. Once the block completes
53
+ # the session is closed and the Connection disconnected.
54
+ #
55
+ # TODO: Multithreaded example:
56
+ #
57
+
58
+ class Connection
59
+ # Create a connection to the JMS provider, start the connection,
60
+ # call the supplied code block, then close the connection upon completion
61
+ #
62
+ # Returns the result of the supplied block
63
+ def self.start(parms = {}, &proc)
64
+ raise "Missing mandatory Block when calling JMS::Connection.start" unless proc
65
+ connection = Connection.new(parms)
66
+ connection.start
67
+ begin
68
+ proc.call(connection)
69
+ ensure
70
+ connection.close
71
+ end
72
+ end
73
+
74
+ # Connect to a JMS Broker, create a session and call the code block passing in the session
75
+ # Both the Session and Connection are closed on termination of the block
76
+ #
77
+ # Shortcut convenience method to both connect to the broker and create a session
78
+ # Useful when only a single session is required in the current thread
79
+ #
80
+ # Note: It is important that each thread have its own session to support transactions
81
+ def self.session(parms = {}, &proc)
82
+ self.start(parms) do |connection|
83
+ connection.session(parms, &proc)
84
+ end
85
+ end
86
+
87
+ # Replace the default logger
88
+ #
89
+ # The supplied logger must respond to the following methods
90
+ # TODO Add required methods list ...
91
+ def self.log=(logger)
92
+ @@log = logger
93
+ end
94
+
95
+ # Class level logger
96
+ def self.log
97
+ @@log ||= org.apache.commons.logging.LogFactory.getLog('JMS.Connection')
98
+ end
99
+
100
+ # Create a connection to the JMS provider
101
+ #
102
+ # Note: Connection::start must be called before any consumers will be
103
+ # able to receive messages
104
+ #
105
+ # In JMS we need to start by obtaining the JMS Factory class that is supplied
106
+ # by the JMS Vendor.
107
+ #
108
+ # There are 3 ways to establish a connection to a JMS Provider
109
+ # 1. Supply the name of the JMS Providers Factory Class
110
+ # 2. Supply an instance of the JMS Provider class itself
111
+ # 3. Use a JNDI lookup to return the JMS Provider Factory class
112
+ # Parameters:
113
+ # :factory => String: Name of JMS Provider Factory class
114
+ # => Class: JMS Provider Factory class itself
115
+ #
116
+ # :jndi_name => String: Name of JNDI entry at which the Factory can be found
117
+ # :jndi_context => Mandatory if jndi lookup is being used, contains details
118
+ # on how to connect to JNDI server etc.
119
+ #
120
+ # :factory and :jndi_name are mutually exclusive, both cannot be supplied at the
121
+ # same time. :factory takes precedence over :jndi_name
122
+ #
123
+ # JMS Provider specific properties can be set if the JMS Factory itself
124
+ # has setters for those properties. Some known examples:
125
+ #
126
+ # For HornetQ
127
+ # :factory => 'org.hornetq.jms.client.HornetQConnectionFactory',
128
+ # :discovery_address => '127.0.0.1',
129
+ # :discovery_port => '5445',
130
+ # :username => 'guest',
131
+ # :password => 'guest'
132
+ #
133
+ # For HornetQ using JNDI lookup technique
134
+ # :jndi_name => '/ConnectionFactory',
135
+ # :jndi_context => {
136
+ # 'java.naming.factory.initial' => 'org.jnp.interfaces.NamingContextFactory',
137
+ # 'java.naming.provider.url' => 'jnp://localhost:1099',
138
+ # 'java.naming.factory.url.pkgs' => 'org.jboss.naming:org.jnp.interfaces',
139
+ # 'java.naming.security.principal' => 'guest',
140
+ # 'java.naming.security.credentials' => 'guest'
141
+ # }
142
+ #
143
+ # On Java 6, HornetQ needs the following jar files on your CLASSPATH:
144
+ # hornetq-core-client.jar
145
+ # netty.jar
146
+ # hornetq-jms-client.jar
147
+ # jboss-jms-api.jar
148
+ # jnp-client.jar
149
+ #
150
+ # On Java 5, HornetQ needs the following jar files on your CLASSPATH:
151
+ # hornetq-core-client-java5.jar
152
+ # netty.jar
153
+ # hornetq-jms-client-java5.jar
154
+ # jboss-jms-api.jar
155
+ # jnp-client.jar
156
+ #
157
+ # For: WebSphere MQ
158
+ # :factory => 'com.ibm.mq.jms.MQQueueConnectionFactory',
159
+ # :queue_manager=>'REID',
160
+ # :host_name=>'localhost',
161
+ # :channel=>'MY.CLIENT.CHL',
162
+ # :port=>1414,
163
+ # :transport_type => com.ibm.mq.jms.JMSC::MQJMS_TP_CLIENT_MQ_TCPIP,
164
+ # :username => 'mqm'
165
+ #
166
+ # For: Active MQ
167
+ # :factory => 'org.apache.activemq.ActiveMQConnectionFactory',
168
+ # :broker_url => 'tcp://localhost:61616'
169
+ #
170
+ # ActiveMQ requires the following jar files on your CLASSPATH
171
+ #
172
+ # For Oracle AQ 9 Server
173
+ # :factory => 'JMS::OracleAQConnectionFactory',
174
+ # :url => 'jdbc:oracle:thin:@hostname:1521:instanceid',
175
+ # :username => 'aquser',
176
+ # :password => 'mypassword'
177
+ #
178
+ # For JBoss, which uses JNDI lookup technique
179
+ # :jndi_name => 'ConnectionFactory',
180
+ # :jndi_context => {
181
+ # 'java.naming.factory.initial' => 'org.jnp.interfaces.NamingContextFactory',
182
+ # 'java.naming.provider.url' => 'jnp://localhost:1099'
183
+ # 'java.naming.security.principal' => 'user',
184
+ # 'java.naming.security.credentials' => 'pwd'
185
+ # }
186
+ #
187
+ # For Apache Qpid / Redhat Messaging, using Factory class directly
188
+ # :factory: org.apache.qpid.client.AMQConnectionFactory
189
+ # :broker_url: tcp://localhost:5672
190
+ #
191
+ # For Apache Qpid / Redhat Messaging, via JNDI lookup
192
+ # :jndi_name => 'local',
193
+ # :jndi_context => {
194
+ # 'java.naming.factory.initial' => 'org.apache.qpid.jndi.PropertiesFileInitialContextFactory',
195
+ # 'connectionfactory.local' => "amqp://guest:guest@clientid/testpath?brokerlist='tcp://localhost:5672'"
196
+ # }
197
+ #
198
+ def initialize(params = {})
199
+ # Used by ::on_message
200
+ @sessions = []
201
+ @consumers = []
202
+
203
+ connection_factory = nil
204
+ factory = params[:factory]
205
+ if factory
206
+ # If factory is a string, then it is the name of a class, not the class itself
207
+ factory = eval(factory) if factory.respond_to? :to_str
208
+ connection_factory = factory.new
209
+ elsif jndi_name = params[:jndi_name]
210
+ raise "Missing mandatory parameter :jndi_context missing in call to Connection::connect" unless jndi_context = params[:jndi_context]
211
+ jndi = javax.naming.InitialContext.new(java.util.Hashtable.new(jndi_context))
212
+ begin
213
+ connection_factory = jndi.lookup jndi_name
214
+ ensure
215
+ jndi.close
216
+ end
217
+ else
218
+ raise "Missing mandatory parameter :factory or :jndi_name missing in call to Connection::connect"
219
+ end
220
+
221
+ Connection.log.debug "Using Factory: #{connection_factory.java_class}" if connection_factory.respond_to? :java_class
222
+ params.each_pair do |key, val|
223
+ method = key.to_s+'='
224
+ if connection_factory.respond_to? method
225
+ connection_factory.send method, val
226
+ Connection.log.debug " #{key} = #{connection_factory.send key}" if connection_factory.respond_to? key.to_sym
227
+ end
228
+ end
229
+ if params[:username]
230
+ @jms_connection = connection_factory.create_connection(params[:username], params[:password])
231
+ else
232
+ @jms_connection = connection_factory.create_connection
233
+ end
234
+ end
235
+
236
+ # Start delivery of messages over this connection.
237
+ # By default no messages are delivered until this method is called explicitly
238
+ # Delivery of messages to any asynchronous Destination::each() call will only
239
+ # start after Connection::start is called
240
+ # Corresponds to JMS start call
241
+ def start
242
+ @jms_connection.start
243
+ end
244
+
245
+ # Stop delivery of messages to any asynchronous Destination::each() calls
246
+ # Useful during a hot code update or other changes that need to be completed
247
+ # without any new messages being processed
248
+ def stop
249
+ @jms_connection.stop
250
+ end
251
+
252
+ # Create a session over this connection.
253
+ # It is recommended to create separate sessions for each thread
254
+ # If a block of code is passed in, it will be called and then the session is automatically
255
+ # closed on completion of the code block
256
+ #
257
+ # Parameters:
258
+ # :transacted => true or false
259
+ # Determines whether transactions are supported within this session.
260
+ # I.e. Whether commit or rollback can be called
261
+ # Default: false
262
+ # :options => any of the javax.jms.Session constants
263
+ # Default: javax.jms.Session::AUTO_ACKNOWLEDGE
264
+ #
265
+ def session(parms={}, &proc)
266
+ raise "Missing mandatory Block when calling JMS::Connection#session" unless proc
267
+ session = self.create_session(parms)
268
+ begin
269
+ proc.call(session)
270
+ ensure
271
+ session.close
272
+ end
273
+ end
274
+
275
+ # Create a session over this connection.
276
+ # It is recommended to create separate sessions for each thread
277
+ #
278
+ # Note: Remember to call close on the returned session when it is no longer
279
+ # needed. Rather use JMS::Connection#session with a block whenever
280
+ # possible
281
+ #
282
+ # Parameters:
283
+ # :transacted => true or false
284
+ # Determines whether transactions are supported within this session.
285
+ # I.e. Whether commit or rollback can be called
286
+ # Default: false
287
+ # :options => any of the javax.jms.Session constants
288
+ # Default: javax.jms.Session::AUTO_ACKNOWLEDGE
289
+ #
290
+ def create_session(parms={}, &proc)
291
+ transacted = parms[:transacted] || false
292
+ options = parms[:options] || javax.jms.Session::AUTO_ACKNOWLEDGE
293
+ @jms_connection.create_session(transacted, options)
294
+ end
295
+
296
+ # Close connection with the JMS Provider
297
+ # First close any consumers or sessions that are active as a result of JMS::Connection::on_message
298
+ def close
299
+ @consumers.each {|consumer| consumer.close } if @consumers
300
+ @consumers = []
301
+
302
+ @sessions.each {|session| session.close} if @sessions
303
+ @session=[]
304
+
305
+ @jms_connection.close if @jms_connection
306
+ end
307
+
308
+ # TODO: Return a pretty print version of the current JMS Connection
309
+ # def to_s
310
+ # "Connected to " + metaData.getJMSProviderName() +
311
+ # " version " + metaData.getProviderVersion() + " (" +
312
+ # metaData.getProviderMajorVersion() + "." + metaData.getProviderMinorVersion() +
313
+ # ")";
314
+ # end
315
+
316
+ # Receive messages in a separate thread when they arrive
317
+ # Allows messages to be recieved in a separate thread. I.e. Asynchronously
318
+ # This method will return to the caller before messages are processed.
319
+ # It is then the callers responsibility to keep the program active so that messages
320
+ # can then be processed.
321
+ #
322
+ # Session Parameters:
323
+ # :transacted => true or false
324
+ # Determines whether transactions are supported within this session.
325
+ # I.e. Whether commit or rollback can be called
326
+ # Default: false
327
+ # :options => any of the javax.jms.Session constants
328
+ # Default: javax.jms.Session::AUTO_ACKNOWLEDGE
329
+ #
330
+ # :session_count : Number of sessions to create, each with their own consumer which
331
+ # in turn will call the supplied Proc.
332
+ # Note: The supplied Proc must be thread safe since it will be called
333
+ # by several threads at the same time.
334
+ # I.e. Don't change instance variables etc. without the
335
+ # necessary semaphores etc.
336
+ # Default: 1
337
+ #
338
+ # Consumer Parameters:
339
+ # :q_name => String: Name of the Queue to return
340
+ # Symbol: :temporary => Create temporary queue
341
+ # Mandatory unless :topic_name is supplied
342
+ # Or,
343
+ # :topic_name => String: Name of the Topic to write to or subscribe to
344
+ # Symbol: :temporary => Create temporary topic
345
+ # Mandatory unless :q_name is supplied
346
+ # Or,
347
+ # :destination=> Explicit javaxJms::Destination to use
348
+ #
349
+ # :selector => Filter which messages should be returned from the queue
350
+ # Default: All messages
351
+ # :no_local => Determine whether messages published by its own connection
352
+ # should be delivered to it
353
+ # Default: false
354
+ #
355
+ # :statistics Capture statistics on how many messages have been read
356
+ # true : This method will capture statistics on the number of messages received
357
+ # and the time it took to process them.
358
+ # The timer starts when each() is called and finishes when either the last message was received,
359
+ # or when Destination::statistics is called. In this case MessageConsumer::statistics
360
+ # can be called several times during processing without affecting the end time.
361
+ # Also, the start time and message count is not reset until MessageConsumer::each
362
+ # is called again with :statistics => true
363
+ #
364
+ # The statistics gathered are returned when :statistics => true and :async => false
365
+ #
366
+ # Usage: For transacted sessions (the default) the Proc supplied must return
367
+ # either true or false:
368
+ # true => The session is committed
369
+ # false => The session is rolled back
370
+ # Any Exception => The session is rolled back
371
+ #
372
+ def on_message(parms, &proc)
373
+ raise "JMS::Connection must be connected prior to calling JMS::Connection::on_message" unless @sessions && @consumers
374
+
375
+ consumer_count = parms[:session_count] || 1
376
+ consumer_count.times do
377
+ session = self.create_session(parms)
378
+ consumer = session.consumer(parms)
379
+ if session.transacted?
380
+ consumer.on_message(parms) do |message|
381
+ begin
382
+ proc.call(message) ? session.commit : session.rollback
383
+ rescue => exc
384
+ session.rollback
385
+ throw exc
386
+ end
387
+ end
388
+ else
389
+ consumer.on_message(parms, &proc)
390
+ end
391
+ @consumers << consumer
392
+ @sessions << session
393
+ end
394
+ end
395
+
396
+ def on_message_statistics
397
+ @consumers.collect{|consumer| consumer.on_message_statistics}
398
+ end
399
+
400
+ end
401
+
402
+ # For internal use only
403
+ private
404
+ class MessageListener
405
+ include javax.jms::MessageListener
406
+
407
+ # Parameters:
408
+ # :statistics Capture statistics on how many messages have been read
409
+ # true : This method will capture statistics on the number of messages received
410
+ # and the time it took to process them.
411
+ # The timer starts when the listener instance is created and finishes when either the last message was received,
412
+ # or when Destination::statistics is called. In this case MessageConsumer::statistics
413
+ # can be called several times during processing without affecting the end time.
414
+ # Also, the start time and message count is not reset until MessageConsumer::each
415
+ # is called again with :statistics => true
416
+ #
417
+ # The statistics gathered are returned when :statistics => true and :async => false
418
+ def initialize(parms={}, &proc)
419
+ @proc = proc
420
+ @log = org.apache.commons.logging.LogFactory.getLog('JMS.MessageListener')
421
+
422
+ if parms[:statistics]
423
+ @message_count = 0
424
+ @start_time = Time.now
425
+ end
426
+ end
427
+
428
+ # Method called for every message received on the queue
429
+ # Per the JMS specification, this method will be called sequentially for each message on the queue.
430
+ # This method will not be called again until its prior invocation has completed.
431
+ # Must be onMessage() since on_message() does not work for interface methods that must be implemented
432
+ def onMessage(message)
433
+ begin
434
+ if @message_count
435
+ @message_count += 1
436
+ @last_time = Time.now
437
+ end
438
+ @proc.call message
439
+ rescue SyntaxError, NameError => boom
440
+ @log.error "Unhandled Exception processing JMS Message. Doesn't compile: " + boom
441
+ @log.error "Ignoring poison message:\n#{message.inspect}"
442
+ @log.error boom.backtrace.join("\n")
443
+ rescue StandardError => bang
444
+ @log.error "Unhandled Exception processing JMS Message. Doesn't compile: " + bang
445
+ @log.error "Ignoring poison message:\n#{message.inspect}"
446
+ @log.error boom.backtrace.join("\n")
447
+ rescue => exc
448
+ @log.error "Unhandled Exception processing JMS Message. Exception occurred:\n#{exc}"
449
+ @log.error "Ignoring poison message:\n#{message.inspect}"
450
+ @log.error exc.backtrace.join("\n")
451
+ end
452
+ end
453
+
454
+ # Return Statistics gathered for this listener
455
+ def statistics
456
+ raise "First call MessageConsumer::on_message with :statistics=>true before calling MessageConsumer::statistics()" unless @message_count
457
+ duration =(@last_time || Time.now) - @start_time
458
+ {:messages => @message_count,
459
+ :duration => duration,
460
+ :messages_per_second => (@message_count/duration).to_i}
461
+ end
462
+ end
463
+
464
+ # Wrapper to support Oracle AQ
465
+ class OracleAQConnectionFactory
466
+ attr_accessor :username, :url
467
+ attr_writer :password
468
+
469
+ # Creates a connection per standard JMS 1.1 techniques from the Oracle AQ JMS Interface
470
+ def create_connection
471
+ cf = oracle.jms.AQjmsFactory.getConnectionFactory(@url, java.util.Properties.new)
472
+ if username
473
+ cf.createConnection(@username, @password)
474
+ else
475
+ cf.createConnection()
476
+ end
477
+ end
478
+ end
479
+
480
+ end