seanohalpin-smqueue 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/smqueue.rb ADDED
@@ -0,0 +1,227 @@
1
+ # SMQueue
2
+ # Simple Message Queue client
3
+ # Sean O'Halpin, 2008
4
+
5
+ # The high-level client API is:
6
+ # - SMQueue(config).put msg, headers = {}
7
+ # - msg = SMQueue(config).get(headers = {})
8
+ # - SMQueue(config).get(headers = {}) do |msg|
9
+ # end
10
+ # todo - [X] add :durable option (and fail if no client_id specified)
11
+ # todo - [ ] gemify - use Mr Bones
12
+ # todo - [ ] change to class (so can be subclassed) - so you're working with an SMQueue instance
13
+ # todo - [ ] write protocol (open, close, put, get) in SMQueue (so don't have to do it in adaptors)
14
+ # todo - [ ] simplify StompAdapter (get rid of sent_messages stuff)
15
+ # todo - [ ] simplify adapter interface
16
+ # todo - [ ] sort out libpath
17
+
18
+ require 'rubygems'
19
+ require 'doodle'
20
+ require 'doodle/version'
21
+ require 'yaml'
22
+
23
+ class Doodle
24
+ if Doodle::VERSION::STRING < '0.1.9'
25
+ def to_hash
26
+ doodle.attributes.inject({}) {|hash, (name, attribute)| hash[name] = send(name); hash}
27
+ end
28
+ class DoodleAttribute
29
+ has :doc, :kind => String
30
+ end
31
+ end
32
+ end
33
+
34
+ #class SMQueue < Doodle
35
+ module SMQueue
36
+
37
+ # Mr Bones project skeleton boilerplate
38
+ # :stopdoc:
39
+ VERSION = '0.1.0'
40
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
41
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
42
+ # :startdoc:
43
+
44
+ # Returns the version string for the library.
45
+ #
46
+ def self.version
47
+ VERSION
48
+ end
49
+
50
+ # Returns the library path for the module. If any arguments are given,
51
+ # they will be joined to the end of the libray path using
52
+ # <tt>File.join</tt>.
53
+ #
54
+ def self.libpath( *args )
55
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
56
+ end
57
+
58
+ # Returns the lpath for the module. If any arguments are given,
59
+ # they will be joined to the end of the path using
60
+ # <tt>File.join</tt>.
61
+ #
62
+ def self.path( *args )
63
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
64
+ end
65
+
66
+ # Utility method used to rquire all files ending in .rb that lie in the
67
+ # directory below this file that has the same name as the filename passed
68
+ # in. Optionally, a specific _directory_ name can be passed in such that
69
+ # the _filename_ does not have to be equivalent to the directory.
70
+ #
71
+ def self.require_all_libs_relative_to( fname, dir = nil )
72
+ dir ||= ::File.basename(fname, '.*')
73
+ search_me = ::File.expand_path(
74
+ ::File.join(::File.dirname(fname), dir, '*', '*.rb'))
75
+
76
+ Dir.glob(search_me).sort.each {|rb| require rb}
77
+ end
78
+
79
+ # end Bones boilerplate
80
+
81
+ class << self
82
+ def dbg(*args, &block)
83
+ if $DEBUG
84
+ if args.size > 0
85
+ STDERR.print "SMQUEUE.DBG: "
86
+ STDERR.puts(*args)
87
+ end
88
+ if block_given?
89
+ STDERR.print "SMQUEUE.DBG: "
90
+ STDERR.puts(block.call)
91
+ end
92
+ end
93
+ end
94
+
95
+ # JMS expiry time in milliseconds from now
96
+ def calc_expiry_time(seconds = 86400 * 7) # one week
97
+ ((Time.now.utc + seconds).to_f * 1000).to_i
98
+ end
99
+
100
+ # resolve a string representing a classname
101
+ def const_resolve(constant)
102
+ constant.to_s.split(/::/).reject{|x| x.empty?}.inject(self) { |prev, this| prev.const_get(this) }
103
+ end
104
+ end
105
+
106
+ class AdapterConfiguration < Doodle
107
+ has :logger, :default => nil
108
+
109
+ # need to use custom to_yaml because YAML won't serialize classes
110
+ def to_hash
111
+ doodle.attributes.inject({}) {|hash, (name, attribute)| hash[name] = send(name); hash}
112
+ end
113
+ def to_yaml(*opts)
114
+ to_hash.to_yaml(*opts)
115
+ end
116
+ def initialize(*args, &block)
117
+ #p [self.class, :initialize, args, caller]
118
+ super
119
+ end
120
+ has :adapter_class, :kind => Class do
121
+ from String, Symbol do |s|
122
+ SMQueue.const_resolve(s.to_s)
123
+ end
124
+ # Note: use closure so this is not evaluated until after NullAdapter class has been defined
125
+ default { NullAdapter }
126
+ end
127
+ has :configuration_class, :kind => Class do
128
+ init { adapter_class::Configuration }
129
+ from String do |s|
130
+ #Doodle::Utils.const_resolve(s)
131
+ SMQueue.const_resolve(s.to_s)
132
+ end
133
+ end
134
+ end
135
+
136
+ class Adapter < Doodle
137
+ has :configuration, :kind => AdapterConfiguration, :abstract => true do
138
+ from Hash do |h|
139
+ #p [:Adapter, :configuration_from_hash]
140
+ Doodle.context.last.class::Configuration.new(h)
141
+ end
142
+ from Object do |h|
143
+ #p [:Adapter, :configuration_from_object, h.inspect, h.class]
144
+ h
145
+ end
146
+ end
147
+ # these are not called anywhere...
148
+ def open(*args, &block)
149
+ end
150
+ def close(*args, &block)
151
+ end
152
+ # these are the core methods
153
+ def get(*args, &block)
154
+ end
155
+ def put(*args, &block)
156
+ end
157
+ def self.create(configuration)
158
+ # FIXME: dup config, otherwise can use it only once - prob. better way to do this
159
+ configuration = configuration.dup
160
+ adapter = configuration.delete(:adapter)
161
+ #p [:adapter, adapter]
162
+ ac = AdapterConfiguration.new(:adapter_class => adapter)
163
+ #p [:ac, ac]
164
+ klass = ac.adapter_class
165
+ #p [:class, klass]
166
+ #puts [:configuration, configuration].pretty_inspect
167
+ # klass.new(:configuration => configuration)
168
+ klass.new(:configuration => configuration)
169
+ end
170
+ end
171
+
172
+ class NullAdapter < Adapter
173
+ class Configuration < AdapterConfiguration
174
+ end
175
+ end
176
+
177
+ class Message < Doodle
178
+ has :headers, :default => { }
179
+ has :body
180
+ end
181
+
182
+ class << self
183
+ def new(*args, &block)
184
+ a = args.first
185
+ if a.kind_of?(Hash) && a.key?(:configuration)
186
+ args = [a[:configuration]]
187
+ end
188
+ Adapter.create(*args, &block)
189
+ end
190
+ end
191
+
192
+ end
193
+ def SMQueue(*args, &block)
194
+ SMQueue.new(*args, &block)
195
+ end
196
+
197
+ # SMQueue.require_all_libs_relative_to(__FILE__)
198
+
199
+ # require adapters relative to invocation path first, then from lib
200
+ [$0, __FILE__].each do |path|
201
+ base_path = File.expand_path(File.dirname(path))
202
+ adapter_path = File.join(base_path, 'smqueue', 'adapters', '*.rb')
203
+ Dir[adapter_path].each do |file|
204
+ require file
205
+ end
206
+ end
207
+
208
+ if __FILE__ == $0
209
+ yaml = %[
210
+ :adapter: :StompAdapter
211
+ :host: localhost
212
+ :port: 61613
213
+ :name: /topic/smput.test
214
+ :reliable: true
215
+ :reconnect_delay: 5
216
+ :subscription_name: test_stomp
217
+ :client_id: hello_from_stomp_adapter
218
+ :durable: false
219
+ ]
220
+
221
+ adapter = SMQueue(:configuration => YAML.load(yaml))
222
+ adapter.get do |msg|
223
+ puts msg.body
224
+ end
225
+
226
+ end
227
+
@@ -0,0 +1,101 @@
1
+ require 'spread'
2
+
3
+ module SMQueue
4
+ class SpreadAdapter < Adapter
5
+ class Configuration < AdapterConfiguration
6
+ has :channel do
7
+ rx_hostname = /[a-z_\.]+/
8
+ rx_ip = /\d+(\.\d+){3}/ # dotted quad
9
+ must 'be a name in the form "port", "port@hostname", or "port@ip"' do |s|
10
+ s =~ /\d+(@(#{rx_hostname})|(#{rx_ip}))?/
11
+ end
12
+ end
13
+ has :group do
14
+ doc "a group name or array of group names"
15
+ must "be either a String group name or an array of group names" do |s|
16
+ s.kind_of?(String) || (s.kind_of?(Array) && s.all?{ |x| x.kind_of?(String)})
17
+ end
18
+ end
19
+ has :private_name, :default => '' do
20
+ doc <<EDOC
21
+ private_name is the name of this connection. It must be unique among
22
+ all the connections to a given Spread daemon. If not specified, Spread
23
+ will assign a randomly-generated unique private name.
24
+ EDOC
25
+ end
26
+ has :all_messages, :default => false do
27
+ doc <<EDOC
28
+ all_messages indicates whether this connection should receive all
29
+ Spread messages, or just data messages.
30
+ EDOC
31
+ end
32
+ has :service_type, :default => Spread::AGREED_MESS do
33
+ service_type_map = {
34
+ :unreliable => Spread::UNRELIABLE_MESS,
35
+ :reliable => Spread::RELIABLE_MESS,
36
+ :fifo => Spread::FIFO_MESS,
37
+ :causal => Spread::CAUSAL_MESS,
38
+ :agreed => Spread::AGREED_MESS,
39
+ :safe => Spread::SAFE_MESS,
40
+ :regular => Spread::REGULAR_MESS,
41
+ }
42
+ from Symbol, String do |s|
43
+ s = s.to_s.to_sym
44
+ if service_type_map.key?(s)
45
+ service_type_map[s]
46
+ else
47
+ raise Doodle::ConversionError, "Did not recognize service_type #{s.inspect} - should be one of #{service_type_map.keys.inspect}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ has :connection, :default => nil
54
+ has :connected, :default => false
55
+
56
+ def connect
57
+ @connection = Spread::Connection.new(configuration.channel, configuration.private_name, configuration.all_messages )
58
+ connection.join(configuration.group)
59
+ configuration.private_name = connection.private_group
60
+ connected true
61
+ end
62
+ def disconnect
63
+ connection.leave group
64
+ connection.disconnect
65
+ connected false
66
+ end
67
+ def get(&block)
68
+ m = nil
69
+ connect if !connected
70
+ loop do
71
+ msg = connection.receive
72
+ if msg.data?
73
+ m = SMQueue::Message.new(
74
+ :headers => {
75
+ :private_name => configuration.private_name,
76
+ :sender => msg.sender,
77
+ :type => msg.msg_type,
78
+ :groups => msg.groups,
79
+ :reliable => msg.reliable?,
80
+ :safe => msg.safe?,
81
+ :agreed => msg.agreed?,
82
+ :causal => msg.causal?,
83
+ :fifo => msg.fifo?,
84
+ },
85
+ :body => msg.message
86
+ )
87
+ if block_given?
88
+ yield(m)
89
+ else
90
+ break
91
+ end
92
+ end
93
+ end
94
+ m
95
+ end
96
+ def put(msg)
97
+ connect if !connected
98
+ connection.multicast(msg, configuration.group, configuration.service_type, msg_type = 0, self_discard = true)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,59 @@
1
+ # set of utility adapters for SMQueue
2
+
3
+ module SMQueue
4
+ class StdioLineAdapter < Adapter
5
+ doc "reads STDIN input, creates new Message for each line of input"
6
+ class Configuration < AdapterConfiguration
7
+ end
8
+ def put(*args, &block)
9
+ STDOUT.puts *args
10
+ end
11
+ def get(*args, &block)
12
+ while input = STDIN.gets
13
+ msg = SMQueue::Message.new(:body => input)
14
+ if block_given?
15
+ yield(msg)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ module SMQueue
23
+ class StdioAdapter < StdioLineAdapter
24
+ doc "reads complete STDIN input, creates one shot Message with :body => input"
25
+ def get(*args, &block)
26
+ input = STDIN.read
27
+ msg = SMQueue::Message.new(:body => input)
28
+ if block_given?
29
+ yield(msg)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ module SMQueue
36
+ class YamlAdapter < StdioAdapter
37
+ doc "outputs message as YAML"
38
+ def put(*args)
39
+ STDOUT.puts args.to_yaml
40
+ end
41
+ end
42
+ end
43
+
44
+ require 'readline'
45
+ module SMQueue
46
+ class ReadlineAdapter < StdioLineAdapter
47
+ doc "uses readline to read input from prompt, creates new Message for each line of input"
48
+ has :prompt, :default => "> "
49
+ has :history, :default => true
50
+ def get(*args, &block)
51
+ while input = Readline.readline(prompt, history)
52
+ msg = Message.new(:body => input)
53
+ if block_given?
54
+ yield(msg)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,291 @@
1
+ require 'rstomp'
2
+ module SMQueue
3
+ class StompAdapter < Adapter
4
+ class Configuration < AdapterConfiguration
5
+ has :host, :kind => String, :default => ""
6
+ has :port, :kind => Integer, :default => 61613 # Stomp
7
+ has :secondary_host, :kind => String, :default => ""
8
+ has :secondary_port, :kind => Integer, :default => 61613
9
+ has :name, :kind => String, :default => ""
10
+ has :user, :kind => String, :default => ""
11
+ has :password, :kind => String, :default => ""
12
+ has :reliable, :default => false
13
+ has :persistent, :default => true
14
+ has :reconnect_delay, :default => 5
15
+ has :client_id, :default => nil, :kind => String
16
+ has :logfile, :default => STDERR
17
+ has :logger, :default => nil
18
+ has :subscription_name, :default => nil
19
+ has :home, :default => File.dirname(File.expand_path(__FILE__))
20
+ has :single_delivery, :default => false do
21
+ doc <<-EDOC
22
+ Note: we can't guarantee single delivery - only best effort.
23
+ Use this when receiving a message more than once is very
24
+ costly. However, be aware that you ~will~ sometimes receive
25
+ the same message more than once (so it's your responsibility
26
+ to make sure that you guard against any consequences).
27
+ EDOC
28
+ end
29
+ has :seen_messages_file do
30
+ init { File.join(home, "seen_messages.#{subscription_name}.#{client_id}.yml") }
31
+ end
32
+ has :expires, :default => 86400 * 7 do
33
+ doc <<-EDOC
34
+ Time to live in milliseconds, i.e. a relative offset not an
35
+ absolute time (as it would be in JMS).
36
+
37
+ The default time to live is one week.
38
+ EDOC
39
+ end
40
+ # to get a durable subscription, you must specify
41
+ # :durable => true
42
+ # and a :client_id (and optionally a :subscription_name)
43
+ has :durable, :default => false do
44
+ must "be true or false" do |b|
45
+ [true, false].include?(b)
46
+ end
47
+ doc <<-EDOC
48
+ Specify whether you want a durable or non-durable subscription.
49
+
50
+ Note: durable queues are ~not~ the default as this could be
51
+ v. expensive in disk usage when used thoughtlessly.
52
+ EDOC
53
+ end
54
+ must "specify client_id if durable is true" do
55
+ #pp [:durable_test, client_id, durable, !client_id.to_s.strip.empty?]
56
+ !(client_id.to_s.strip.empty? and durable)
57
+ end
58
+ end
59
+ has :connection, :default => nil
60
+
61
+ # seen_messages is used to skip over messages that have already
62
+ # been seen - only activated when :single_delivery is specified
63
+ has :seen_messages, :init => []
64
+ has :seen_message_count, :init => 10
65
+ has :seen_messages_file do
66
+ init { configuration.seen_messages_file }
67
+ end
68
+
69
+ def initialize(*args, &block)
70
+ super
71
+ restore_remembered_messages
72
+ SMQueue.dbg { [:seen_messages, seen_messages].inspect }
73
+ end
74
+
75
+ # handle an error
76
+ def handle_error(exception_class, error_message, caller)
77
+ #configuration.logger.warn error_message
78
+ raise exception_class, error_message, caller
79
+ end
80
+
81
+ # connect to message broker
82
+ def connect(*args, &block)
83
+ self.connection = RStomp::Connection.open(configuration.to_hash)
84
+ # If the connection has swapped hosts, then swap out primary and secondary
85
+ if connection.current_host != configuration.host
86
+ configuration.secondary_host = configuration.host
87
+ configuration.host = connection.current_host
88
+ end
89
+
90
+ # If the connection has swapped ports, then swap out primary and secondary
91
+ if connection.current_port != configuration.port
92
+ configuration.secondary_port = configuration.port
93
+ configuration.port = connection.current_port
94
+ end
95
+ end
96
+
97
+ # normalize hash keys (top level only)
98
+ # - normalizes keys to strings by default
99
+ # - optionally pass in name of method to use (e.g. :to_sym) to normalize keys
100
+ def normalize_keys(hash, method = :to_s)
101
+ hash = hash.dup
102
+ hash.keys.each do |k|
103
+ normalized_key = k.respond_to?(method) ? k.send(method) : k
104
+ hash[normalized_key] = hash.delete(k)
105
+ end
106
+ hash
107
+ end
108
+
109
+ # true if the message with this message_id has already been seen
110
+ def message_seen?(message_id)
111
+ self.seen_messages.include?(message_id)
112
+ end
113
+
114
+ # remember the message_id
115
+ def message_seen(message_id)
116
+ message_id = message_id.to_s.strip
117
+ if message_id != ""
118
+ self.seen_messages << message_id
119
+ SMQueue.dbg { [:smqueue, :ack, :message_seen, message_id].inspect }
120
+ if self.seen_messages.size > self.seen_message_count
121
+ self.seen_messages.shift
122
+ end
123
+ store_remembered_messages
124
+ else
125
+ SMQueue.dbg { [:smqueue, :ack, :message_seen, message_id].inspect }
126
+ end
127
+ end
128
+
129
+ # store the remembered message ids in a yaml file
130
+ def store_remembered_messages
131
+ if configuration.single_delivery
132
+ File.open(seen_messages_file, 'w') do |file|
133
+ file.write seen_messages.to_yaml
134
+ end
135
+ end
136
+ end
137
+
138
+ # reload remembered message ids from a yaml file
139
+ def restore_remembered_messages
140
+ if configuration.single_delivery
141
+ yaml = default_yaml = "--- []"
142
+ begin
143
+ File.open(seen_messages_file, 'r') do |file|
144
+ yaml = file.read
145
+ end
146
+ rescue Object
147
+ yaml = default_yaml
148
+ end
149
+ buffer = []
150
+ begin
151
+ buffer = YAML.load(yaml)
152
+ if !buffer.kind_of?(Array) or !buffer.all?{ |x| x.kind_of?(String)}
153
+ raise Exception, "Invalid seen_messages.yml file"
154
+ end
155
+ rescue Object
156
+ buffer = []
157
+ end
158
+ self.seen_messages = buffer
159
+ end
160
+ end
161
+
162
+ # acknowledge message (if headers["ack"] == "client")
163
+ def ack(subscription_headers, message)
164
+ #p [:ack, message.headers["message-id"]]
165
+ if message.headers["message-id"].to_s.strip != "" && subscription_headers["ack"].to_s == "client"
166
+ SMQueue.dbg { [:smqueue, :ack, :message, message].inspect }
167
+ connection.ack message.headers["message-id"], { }
168
+ else
169
+ SMQueue.dbg { [:smqueue, :ack, :not_acknowledging, message].inspect }
170
+ end
171
+ if ENV['PAUSE_SMQUEUE']
172
+ $stderr.print "press enter to continue> "
173
+ $stderr.flush
174
+ $stdin.gets
175
+ end
176
+ end
177
+
178
+ # get message from queue
179
+ # - if block supplied, loop forever and yield(message) for each
180
+ # message received
181
+ # default headers are:
182
+ # :ack => "client"
183
+ # :client_id => configuration.client_id
184
+ # :subscription_name => configuration.subscription_name
185
+ #
186
+ def get(headers = {}, &block)
187
+ self.connect
188
+ SMQueue.dbg { [:smqueue, :get, headers].inspect }
189
+ subscription_headers = {"ack" => "client", "activemq.prefetchSize" => 1 }
190
+ if client_id = configuration.client_id
191
+ subscription_headers["client_id"] = client_id
192
+ end
193
+ if sub_name = configuration.subscription_name
194
+ subscription_headers["subscription_name"] = sub_name
195
+ end
196
+ # if a client_id is supplied, then user wants a durable subscription
197
+ # N.B. client_id must be unique for broker
198
+ subscription_headers.update(headers)
199
+ #p [:subscription_headers_before, subscription_headers]
200
+ subscription_headers = normalize_keys(subscription_headers)
201
+ if configuration.durable and client_id = configuration.client_id || subscription_headers["client_id"]
202
+ subscription_name = configuration.subscription_name || subscription_headers["subscription_name"] || client_id
203
+ # activemq only
204
+ subscription_headers["activemq.subscriptionName"] = subscription_name
205
+ # JMS
206
+ subscription_headers["durable-subscriber-name"] = subscription_name
207
+ end
208
+ #p [:subscription_headers_after, subscription_headers]
209
+
210
+ destination = configuration.name
211
+ SMQueue.dbg { [:smqueue, :get, :subscribing, destination, :subscription_headers, subscription_headers].inspect }
212
+ connection.subscribe destination, subscription_headers
213
+ message = nil
214
+ SMQueue.dbg { [:smqueue, :get, :subscription_headers, subscription_headers].inspect }
215
+ begin
216
+ # TODO: refactor this
217
+ if block_given?
218
+ SMQueue.dbg { [:smqueue, :get, :block_given].inspect }
219
+ # todo: change to @running - (and set to false from exception handler)
220
+ # also should check to see if anything left to receive on connection before bailing out
221
+ while true
222
+ SMQueue.dbg { [:smqueue, :get, :receive].inspect }
223
+ # block until message ready
224
+ message = connection.receive
225
+ SMQueue.dbg { [:smqueue, :get, :received, message].inspect }
226
+ case message.command
227
+ when "ERROR"
228
+ SMQueue.dbg { [:smqueue, :get, :ERROR, message].inspect }
229
+ when "RECEIPT"
230
+ SMQueue.dbg { [:smqueue, :get, :RECEIPT, message].inspect }
231
+ else
232
+ SMQueue.dbg { [:smqueue, :get, :yielding].inspect }
233
+ if !message_seen?(message.headers["message-id"])
234
+ yield(message)
235
+ end
236
+ SMQueue.dbg { [:smqueue, :get, :message_seen, message.headers["message-id"]].inspect }
237
+ message_seen message.headers["message-id"]
238
+ SMQueue.dbg { [:smqueue, :get, :returned_from_yield_now_calling_ack].inspect }
239
+ ack(subscription_headers, message)
240
+ SMQueue.dbg { [:smqueue, :get, :returned_from_ack].inspect }
241
+ end
242
+ end
243
+ else
244
+ SMQueue.dbg { [:smqueue, :get, :single_shot].inspect }
245
+ message = connection.receive
246
+ SMQueue.dbg { [:smqueue, :get, :received, message].inspect }
247
+ if !(message.command == "ERROR" or message.command == "RECEIPT")
248
+ SMQueue.dbg { [:smqueue, :get, :message_seen, message.headers["message-id"]].inspect }
249
+ message_seen message.headers["message-id"]
250
+ SMQueue.dbg { [:smqueue, :get, :ack, message].inspect }
251
+ ack(subscription_headers, message)
252
+ SMQueue.dbg { [:smqueue, :get, :returned_from_ack].inspect }
253
+ end
254
+ end
255
+ rescue Object => e
256
+ SMQueue.dbg { [:smqueue, :get, :exception, e].inspect }
257
+ handle_error e, "Exception in SMQueue#get: #{e.message}", caller
258
+ ensure
259
+ SMQueue.dbg { [:smqueue, :get, :ensure].inspect }
260
+ SMQueue.dbg { [:smqueue, :unsubscribe, destination, subscription_headers].inspect }
261
+ connection.unsubscribe destination, subscription_headers
262
+ SMQueue.dbg { [:smqueue, :disconnect].inspect }
263
+ connection.disconnect
264
+ end
265
+ SMQueue.dbg { [:smqueue, :get, :return].inspect }
266
+ message
267
+ end
268
+
269
+ # put a message on the queue
270
+ # default headers are:
271
+ # :persistent => true
272
+ # :ack => "auto"
273
+ # :expires => configuration.expires
274
+ def put(body, headers = { })
275
+ SMQueue.dbg { [:smqueue, :put, body, headers].inspect }
276
+ begin
277
+ self.connect
278
+ headers = {:persistent => true, :ack => "auto", :expires => SMQueue.calc_expiry_time(configuration.expires) }.merge(headers)
279
+ destination = configuration.name
280
+ SMQueue.dbg { [:smqueue, :send, body, headers].inspect }
281
+ connection.send destination, body, headers
282
+ rescue Exception => e
283
+ SMQueue.dbg { [:smqueue, :exception, e].inspect }
284
+ handle_error e, "Exception in SMQueue#put - #{e.message}", caller
285
+ #connection.disconnect
286
+ ensure
287
+ connection.disconnect
288
+ end
289
+ end
290
+ end
291
+ end