jruby-jms 0.9.0

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