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/HISTORY.md +13 -0
- data/LICENSE.txt +201 -0
- data/README.md +276 -0
- data/Rakefile +22 -0
- data/examples/consumer.rb +23 -0
- data/examples/jms.yml +22 -0
- data/examples/log4j.properties +31 -0
- data/examples/performance/consumer.rb +27 -0
- data/examples/performance/producer.rb +31 -0
- data/examples/producer.rb +23 -0
- data/lib/jms.rb +25 -0
- data/lib/jms/connection.rb +480 -0
- data/lib/jms/javax_jms_map_message.rb +91 -0
- data/lib/jms/javax_jms_message.rb +264 -0
- data/lib/jms/javax_jms_message_consumer.rb +114 -0
- data/lib/jms/javax_jms_object_message.rb +26 -0
- data/lib/jms/javax_jms_queue_browser.rb +27 -0
- data/lib/jms/javax_jms_session.rb +285 -0
- data/lib/jms/javax_jms_text_message.rb +31 -0
- metadata +74 -0
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
|