jruby-jms 0.9.0 → 0.10.0

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