jruby-jms 0.9.0 → 0.10.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/README.md CHANGED
@@ -5,11 +5,6 @@ jruby-jms
5
5
 
6
6
  ### Current Activities & Backward Compatibility
7
7
 
8
- Currently reviewing Logging which uses Apache Commons logging by default.
9
- For now add the following Apache Commons and log4j libraries to your classpath
10
- * commons-logging-1.1.1.jar
11
- * log4j-1.2.16.jar
12
-
13
8
  Please read the source files for now for documentation. Looking into rdoc doc
14
9
  generation issue.
15
10
 
@@ -18,13 +13,12 @@ There may still be some changes to the API to make things better and/or simpler.
18
13
  Once the code goes to V1.0.0 I will make every effort to not break the
19
14
  existing interface in any way.
20
15
 
21
- Feedback is welcome and appreciated :)
22
- * Need input on Logging and any other tips and tricks on extending Interfaces
23
- ** For example, currently cannot override Interface methods with JRuby versions
24
- (See Session::create_session, which is Session::session at this time)
25
- * Looking for ideas on the best way to specify jar files so that they can be
26
- 'required' by jruby-jms rather than having to use the classpath
27
- * Need to get rdoc working for
16
+ ### Feedback is welcome and appreciated :)
17
+
18
+ ### Todo
19
+
20
+ * Need to get rdoc working
21
+ * More tests, especially pub/sub
28
22
 
29
23
  ### Introduction
30
24
 
@@ -47,7 +41,7 @@ API is available to you at any time.
47
41
 
48
42
  ### Install
49
43
 
50
- gem install jruby-jms
44
+ gem install jruby-jms
51
45
 
52
46
  ### Simplification
53
47
 
@@ -106,58 +100,46 @@ topic they are interested in
106
100
  Producers write message to a queue or topic
107
101
 
108
102
  ActiveMQ Example:
109
- require 'rubygems'
110
-
111
- # Include Active MQ Jar files (Not required if already in classpath)
112
- require '~/Applications/apache-activemq-5.4.2/activemq-all-5.4.2.jar'
113
-
114
- # Include Apache Commons Logging Jar file (For now, if not already in classpath)
115
- require '~/Applications/apache-activemq-5.4.2/lib/commons-logging-1.1.jar'
116
- require '~/Applications/apache-activemq-5.4.2/lib/optional/log4j-1.2.14.jar'
117
-
118
- # Include JMS after ActiveMQ
119
- require 'jms'
120
-
121
- # Connect to ActiveMQ
122
- config = {
123
- :factory => 'org.apache.activemq.ActiveMQConnectionFactory',
124
- :broker_url => 'tcp://localhost:61616'
125
- }
126
-
127
- JMS::Connection.session(config) do |session|
128
- session.producer(:q_name => 'ExampleQueue') do |producer|
129
- producer.send(session.message("Hello World"))
103
+ require 'rubygems'
104
+
105
+ # Include JMS after ActiveMQ
106
+ require 'jms'
107
+
108
+ # Connect to ActiveMQ
109
+ config = {
110
+ :factory => 'org.apache.activemq.ActiveMQConnectionFactory',
111
+ :broker_url => 'tcp://localhost:61616',
112
+ :require_jars => ["~/Applications/apache-activemq-5.4.2/activemq-all-5.4.2.jar"]
113
+ }
114
+
115
+ JMS::Connection.session(config) do |session|
116
+ session.producer(:q_name => 'ExampleQueue') do |producer|
117
+ producer.send(session.message("Hello World"))
118
+ end
130
119
  end
131
- end
132
120
 
133
121
  ### Consumer
134
122
 
135
123
  Consumers read message from a queue or topic
136
124
 
137
- Example:
138
- require 'rubygems'
139
-
140
- # Include Active MQ Jar files (Not required if already in classpath)
141
- require '~/Applications/apache-activemq-5.4.2/activemq-all-5.4.2.jar'
142
-
143
- # Include Apache Commons Logging Jar file (For now, if not already in classpath)
144
- require '~/Applications/apache-activemq-5.4.2/lib/commons-logging-1.1.jar'
145
- require '~/Applications/apache-activemq-5.4.2/lib/optional/log4j-1.2.14.jar'
146
-
147
- # Include JMS after ActiveMQ
148
- require 'jms'
149
-
150
- # Connect to ActiveMQ
151
- config = {
152
- :factory => 'org.apache.activemq.ActiveMQConnectionFactory',
153
- :broker_url => 'tcp://localhost:61616'
154
- }
155
-
156
- JMS::Connection.session(config) do |session|
157
- session.consume(:q_name => 'ExampleQueue', :timeout=>1000) do |message|
158
- p message
125
+ ActiveMQ Example:
126
+ require 'rubygems'
127
+
128
+ # Include JMS after ActiveMQ
129
+ require 'jms'
130
+
131
+ # Connect to ActiveMQ
132
+ config = {
133
+ :factory => 'org.apache.activemq.ActiveMQConnectionFactory',
134
+ :broker_url => 'tcp://localhost:61616',
135
+ :require_jars => ["~/Applications/apache-activemq-5.4.2/activemq-all-5.4.2.jar"]
136
+ }
137
+
138
+ JMS::Connection.session(config) do |session|
139
+ session.consume(:q_name => 'ExampleQueue', :timeout=>1000) do |message|
140
+ p message
141
+ end
159
142
  end
160
- end
161
143
 
162
144
  Overview
163
145
  --------
@@ -185,15 +167,16 @@ queue or topic match will be passed to the block.
185
167
  Logging
186
168
  -------
187
169
 
188
- jruby-jms currently uses Apache Commons logging, but this is subject to changes
189
- since many users of jruby-jms will want to use the Rails logger
170
+ jruby-jms detects the logging available in the current environment.
171
+ When running under Rails it will use the Rails logger, otherwise the standard
172
+ Ruby logger. The logger can also be replaced by calling Connection.logger=
190
173
 
191
174
  Dependencies
192
175
  ------------
193
176
 
194
177
  ### JRuby
195
178
 
196
- jruby-jms has been tested against JRuby 1.5.1, but should work with any
179
+ jruby-jms has been tested against JRuby 1.5.1 and 1.6, but should work with any
197
180
  current JRuby version.
198
181
 
199
182
  ### JMS
@@ -221,7 +204,7 @@ Contributing
221
204
 
222
205
  Once you've made your great commits:
223
206
 
224
- 1. [Fork][1] Resque
207
+ 1. [Fork][1] jruby-jms
225
208
  2. Create a topic branch - `git checkout -b my_branch`
226
209
  3. Push to your branch - `git push origin my_branch`
227
210
  4. Create an [Issue][2] with a link to your branch
@@ -261,7 +244,7 @@ Reid Morrison :: rubywmq@gmail.com :: @reidmorrison
261
244
  License
262
245
  -------
263
246
 
264
- Copyright 2008, 2009, 2010, 2011 J. Reid Morrison
247
+ Copyright 2008 - 2011 J. Reid Morrison
265
248
 
266
249
  Licensed under the Apache License, Version 2.0 (the "License");
267
250
  you may not use this file except in compliance with the License.
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  raise "jruby-jms must be built with JRuby: try again with `jruby -S rake'" unless defined?(JRUBY_VERSION)
2
2
 
3
3
  require 'rake/clean'
4
+ require 'rake/testtask'
4
5
  require 'date'
5
6
  require 'java'
6
7
 
@@ -8,7 +9,7 @@ desc "Build gem"
8
9
  task :gem do |t|
9
10
  gemspec = Gem::Specification.new do |s|
10
11
  s.name = 'jruby-jms'
11
- s.version = '0.9.0'
12
+ s.version = '0.10.0'
12
13
  s.author = 'Reid Morrison'
13
14
  s.email = 'rubywmq@gmail.com'
14
15
  s.homepage = 'https://github.com/reidmorrison/jruby-jms'
@@ -20,3 +21,13 @@ task :gem do |t|
20
21
  end
21
22
  Gem::Builder.new(gemspec).build
22
23
  end
24
+
25
+ task :test do
26
+
27
+ Rake::TestTask.new(:functional) do |t|
28
+ t.test_files = FileList['test/*_test.rb']
29
+ t.verbose = true
30
+ end
31
+
32
+ Rake::Task['functional'].invoke
33
+ end
data/examples/jms.yml CHANGED
@@ -9,7 +9,9 @@
9
9
  activemq:
10
10
  :factory: org.apache.activemq.ActiveMQConnectionFactory
11
11
  :broker_url: tcp://localhost:61616
12
-
12
+ :require_jars:
13
+ - ~/Applications/apache-activemq-5.4.2/activemq-all-5.4.2.jar
14
+
13
15
  hornetq:
14
16
  # Connect to a local HornetQ Broker using JNDI
15
17
  :jndi_name: /ConnectionFactory
@@ -19,4 +21,10 @@ hornetq:
19
21
  java.naming.factory.url.pkgs: org.jboss.naming:org.jnp.interfaces
20
22
  java.naming.security.principal: guest
21
23
  java.naming.security.credentials: guest
22
-
24
+ :require_jars:
25
+ - ~/Applications/hornetq-2.1.2.Final/lib/hornetq-core-client.jar
26
+ - ~/Applications/hornetq-2.1.2.Final/lib/hornetq-core.jar
27
+ - ~/Applications/hornetq-2.1.2.Final/lib/hornetq-jms-client.jar
28
+ - ~/Applications/hornetq-2.1.2.Final/lib/jboss-jms-api.jar
29
+ - ~/Applications/hornetq-2.1.2.Final/lib/jnp-client.jar
30
+ - ~/Applications/hornetq-2.1.2.Final/lib/netty.jar
data/lib/jms.rb CHANGED
@@ -15,11 +15,5 @@
15
15
  ################################################################################
16
16
 
17
17
  require 'java'
18
+ require 'jms/logging'
18
19
  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'
@@ -52,13 +52,12 @@ module JMS
52
52
  # The above code creates a Connection and then a Session. Once the block completes
53
53
  # the session is closed and the Connection disconnected.
54
54
  #
55
- # TODO: Multithreaded example:
55
+ # See: http://download.oracle.com/javaee/6/api/javax/jms/Connection.html
56
56
  #
57
-
58
57
  class Connection
59
58
  # Create a connection to the JMS provider, start the connection,
60
59
  # call the supplied code block, then close the connection upon completion
61
- #
60
+ #
62
61
  # Returns the result of the supplied block
63
62
  def self.start(parms = {}, &proc)
64
63
  raise "Missing mandatory Block when calling JMS::Connection.start" unless proc
@@ -70,35 +69,57 @@ module JMS
70
69
  connection.close
71
70
  end
72
71
  end
73
-
72
+
74
73
  # Connect to a JMS Broker, create a session and call the code block passing in the session
75
74
  # Both the Session and Connection are closed on termination of the block
76
- #
75
+ #
77
76
  # Shortcut convenience method to both connect to the broker and create a session
78
77
  # Useful when only a single session is required in the current thread
79
- #
78
+ #
80
79
  # Note: It is important that each thread have its own session to support transactions
81
80
  def self.session(parms = {}, &proc)
82
81
  self.start(parms) do |connection|
83
82
  connection.session(parms, &proc)
84
83
  end
85
84
  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')
85
+
86
+ # Load the required jar files for this JMS Provider and
87
+ # load JRuby extensions for those classes
88
+ #
89
+ # Rather than copying the JMS jar files into the JRuby lib, load them
90
+ # on demand. JRuby JMS extensions are only loaded once the jar files have been
91
+ # loaded.
92
+ #
93
+ # Can be called multiple times if required, although it would not be performant
94
+ # to do so regularly.
95
+ #
96
+ # Parameter: jar_list is an Array of the path and filenames to jar files
97
+ # to load for this JMS Provider
98
+ #
99
+ # Returns nil
100
+ #
101
+ # TODO make this a class method
102
+ def fetch_dependencies(jar_list)
103
+ jar_list.each do |jar|
104
+ JMS::logger.info "Loading Jar File:#{jar}"
105
+ begin
106
+ require jar
107
+ rescue Exception => exc
108
+ JMS::logger.error "Failed to Load Jar File:#{jar}. #{exc.to_s}"
109
+ end
110
+ end
111
+ require 'jms/message_listener'
112
+ require 'jms/javax_jms_message'
113
+ require 'jms/javax_jms_text_message'
114
+ require 'jms/javax_jms_map_message'
115
+ require 'jms/javax_jms_object_message'
116
+ require 'jms/javax_jms_session'
117
+ require 'jms/javax_jms_message_consumer'
118
+ require 'jms/javax_jms_queue_browser'
98
119
  end
99
120
 
100
121
  # Create a connection to the JMS provider
101
- #
122
+ #
102
123
  # Note: Connection::start must be called before any consumers will be
103
124
  # able to receive messages
104
125
  #
@@ -166,7 +187,7 @@ module JMS
166
187
  # For: Active MQ
167
188
  # :factory => 'org.apache.activemq.ActiveMQConnectionFactory',
168
189
  # :broker_url => 'tcp://localhost:61616'
169
- #
190
+ #
170
191
  # ActiveMQ requires the following jar files on your CLASSPATH
171
192
  #
172
193
  # For Oracle AQ 9 Server
@@ -200,6 +221,10 @@ module JMS
200
221
  @sessions = []
201
222
  @consumers = []
202
223
 
224
+ # Load Jar files on demand so that they do not need to be in the CLASSPATH
225
+ # of JRuby lib directory
226
+ fetch_dependencies(params[:require_jars]) if params[:require_jars]
227
+
203
228
  connection_factory = nil
204
229
  factory = params[:factory]
205
230
  if factory
@@ -218,12 +243,12 @@ module JMS
218
243
  raise "Missing mandatory parameter :factory or :jndi_name missing in call to Connection::connect"
219
244
  end
220
245
 
221
- Connection.log.debug "Using Factory: #{connection_factory.java_class}" if connection_factory.respond_to? :java_class
246
+ JMS::logger.debug "Using Factory: #{connection_factory.java_class}" if connection_factory.respond_to? :java_class
222
247
  params.each_pair do |key, val|
223
248
  method = key.to_s+'='
224
249
  if connection_factory.respond_to? method
225
250
  connection_factory.send method, val
226
- Connection.log.debug " #{key} = #{connection_factory.send key}" if connection_factory.respond_to? key.to_sym
251
+ JMS::logger.debug " #{key} = #{connection_factory.send key}" if connection_factory.respond_to? key.to_sym
227
252
  end
228
253
  end
229
254
  if params[:username]
@@ -233,18 +258,18 @@ module JMS
233
258
  end
234
259
  end
235
260
 
236
- # Start delivery of messages over this connection.
261
+ # Start (or restart) delivery of incoming messages over this connection.
237
262
  # By default no messages are delivered until this method is called explicitly
238
263
  # Delivery of messages to any asynchronous Destination::each() call will only
239
- # start after Connection::start is called
240
- # Corresponds to JMS start call
264
+ # start after Connection::start is called, or Connection.start is used
241
265
  def start
242
266
  @jms_connection.start
243
267
  end
244
-
245
- # Stop delivery of messages to any asynchronous Destination::each() calls
268
+
269
+ # Temporarily stop delivery of incoming messages on this connection
246
270
  # Useful during a hot code update or other changes that need to be completed
247
271
  # without any new messages being processed
272
+ # Call start() to resume receiving messages
248
273
  def stop
249
274
  @jms_connection.stop
250
275
  end
@@ -274,7 +299,7 @@ module JMS
274
299
 
275
300
  # Create a session over this connection.
276
301
  # It is recommended to create separate sessions for each thread
277
- #
302
+ #
278
303
  # Note: Remember to call close on the returned session when it is no longer
279
304
  # needed. Rather use JMS::Connection#session with a block whenever
280
305
  # possible
@@ -284,10 +309,26 @@ module JMS
284
309
  # Determines whether transactions are supported within this session.
285
310
  # I.e. Whether commit or rollback can be called
286
311
  # Default: false
287
- # :options => any of the javax.jms.Session constants
288
- # Default: javax.jms.Session::AUTO_ACKNOWLEDGE
312
+ # Note: :options below is ignored if this value is set to :true
313
+ # :options => any of the javax.jms.Session constants:
314
+ # Note: :options is ignored of :transacted => true
315
+ # javax.jms.Session::AUTO_ACKNOWLEDGE
316
+ # With this acknowledgment mode, the session automatically acknowledges
317
+ # a client's receipt of a message either when the session has successfully
318
+ # returned from a call to receive or when the message listener the session has
319
+ # called to process the message successfully returns.
320
+ # javax.jms.Session::CLIENT_ACKNOWLEDGE
321
+ # With this acknowledgment mode, the client acknowledges a consumed
322
+ # message by calling the message's acknowledge method.
323
+ # javax.jms.Session::DUPS_OK_ACKNOWLEDGE
324
+ # This acknowledgment mode instructs the session to lazily acknowledge
325
+ # the delivery of messages.
326
+ # javax.jms.Session::SESSION_TRANSACTED
327
+ # This value is returned from the method getAcknowledgeMode if the
328
+ # session is transacted.
329
+ # Default: javax.jms.Session::AUTO_ACKNOWLEDGE
289
330
  #
290
- def create_session(parms={}, &proc)
331
+ def create_session(parms={})
291
332
  transacted = parms[:transacted] || false
292
333
  options = parms[:options] || javax.jms.Session::AUTO_ACKNOWLEDGE
293
334
  @jms_connection.create_session(transacted, options)
@@ -304,13 +345,61 @@ module JMS
304
345
 
305
346
  @jms_connection.close if @jms_connection
306
347
  end
348
+
349
+ # Gets the client identifier for this connection.
350
+ def client_id
351
+ @jms_connection.getClientID
352
+ end
353
+
354
+ # Sets the client identifier for this connection.
355
+ def client_id=(client_id)
356
+ @jms_connection.setClientID(client_id)
357
+ end
358
+
359
+ # Returns the ExceptionListener object for this connection
360
+ # Returned class implements interface javax.jms.ExceptionListener
361
+ def exception_listener
362
+ @jms_connection.getExceptionListener
363
+ end
364
+
365
+ # Sets an exception listener for this connection
366
+ # See ::on_exception to set a Ruby Listener
367
+ # Returns: nil
368
+ def exception_listener=(listener)
369
+ setExceptionListener(listener)
370
+ end
371
+
372
+ # Whenever an exception occurs the supplied block is called
373
+ # This is important when Connection::on_message has been used, since
374
+ # failures to the connection would be lost otherwise
375
+ #
376
+ # For details on the supplied parameter when the block is called,
377
+ # see: http://download.oracle.com/javaee/6/api/javax/jms/JMSException.html
378
+ #
379
+ # Example:
380
+ # connection.on_message do |jms_exception|
381
+ # puts "JMS Exception has occurred: #{jms_exception}"
382
+ # end
383
+ #
384
+ # Returns: nil
385
+ def on_exception(&block)
386
+ setExceptionListener(block)
387
+ end
388
+
389
+ # Gets the metadata for this connection
390
+ # see: http://download.oracle.com/javaee/6/api/javax/jms/ConnectionMetaData.html
391
+ def meta_data
392
+ @jms_connection.getMetaData
393
+ end
394
+
395
+ # Return a string describing the JMS provider and version
396
+ def to_s
397
+ md = @jms_connection.getMetaData
398
+ "JMS::Connection provider: #{md.getJMSProviderName} v#{md.getProviderVersion}, JMS v#{md.getJMSVersion}"
399
+ end
307
400
 
308
401
  # TODO: Return a pretty print version of the current JMS Connection
309
402
  # def to_s
310
- # "Connected to " + metaData.getJMSProviderName() +
311
- # " version " + metaData.getProviderVersion() + " (" +
312
- # metaData.getProviderMajorVersion() + "." + metaData.getProviderMinorVersion() +
313
- # ")";
314
403
  # end
315
404
 
316
405
  # Receive messages in a separate thread when they arrive
@@ -351,7 +440,7 @@ module JMS
351
440
  # :no_local => Determine whether messages published by its own connection
352
441
  # should be delivered to it
353
442
  # Default: false
354
- #
443
+ #
355
444
  # :statistics Capture statistics on how many messages have been read
356
445
  # true : This method will capture statistics on the number of messages received
357
446
  # and the time it took to process them.
@@ -369,6 +458,9 @@ module JMS
369
458
  # false => The session is rolled back
370
459
  # Any Exception => The session is rolled back
371
460
  #
461
+ # Note: Also supply connection::on_exception so that connection failures can be handled
462
+ #
463
+ #
372
464
  def on_message(parms, &proc)
373
465
  raise "JMS::Connection must be connected prior to calling JMS::Connection::on_message" unless @sessions && @consumers
374
466
 
@@ -399,68 +491,6 @@ module JMS
399
491
 
400
492
  end
401
493
 
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
494
  # Wrapper to support Oracle AQ
465
495
  class OracleAQConnectionFactory
466
496
  attr_accessor :username, :url