craigw-smqueue 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,231 @@
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.2.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
+ begin
205
+ require file
206
+ rescue Object => e
207
+ warn "warning: could not load adapter '#{file}'. Reason: #{e}"
208
+ end
209
+ end
210
+ end
211
+
212
+ if __FILE__ == $0
213
+ yaml = %[
214
+ :adapter: :StompAdapter
215
+ :host: localhost
216
+ :port: 61613
217
+ :name: /topic/smput.test
218
+ :reliable: true
219
+ :reconnect_delay: 5
220
+ :subscription_name: test_stomp
221
+ :client_id: hello_from_stomp_adapter
222
+ :durable: false
223
+ ]
224
+
225
+ adapter = SMQueue(:configuration => YAML.load(yaml))
226
+ adapter.get do |msg|
227
+ puts msg.body
228
+ end
229
+
230
+ end
231
+
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'smqueue'
3
+ require 'mq'
4
+
5
+ module SMQueue
6
+ class AmqpAdapter < SMQueue::Adapter
7
+ class Configuration < SMQueue::AdapterConfiguration
8
+ has :queue
9
+ end
10
+
11
+ def initialize(*args)
12
+ super
13
+ options = args.first
14
+ @configuration = options[:configuration]
15
+ end
16
+
17
+ def put(*args, &block)
18
+ AMQP.start {
19
+ channel.publish(args[0])
20
+ AMQP.stop { EM.stop }
21
+ }
22
+ end
23
+
24
+ def get(*args, &block)
25
+ if block_given?
26
+ EM.run {
27
+ channel.subscribe { |header, body|
28
+ yield ::SMQueue::Message(:headers => header, :body => body)
29
+ }
30
+ }
31
+ else
32
+ raise "TODO: Implement me"
33
+ end
34
+ end
35
+
36
+ private
37
+ def channel
38
+ MQ.queue(@configuration[:queue])
39
+ end
40
+ end
41
+ end
@@ -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,60 @@
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
+ ReadLineAdapter = ReadlineAdapter
60
+ end
@@ -0,0 +1,355 @@
1
+ require 'rstomp'
2
+ module SMQueue
3
+ class StompAdapter < Adapter
4
+ class Configuration < AdapterConfiguration
5
+ has :host, :kind => String, :default => "" do
6
+ doc <<-EDOC
7
+ The host that runs the broker you want to connect to.
8
+ EDOC
9
+ end
10
+ has :port, :kind => Integer, :default => 61613 do
11
+ doc <<-EDOC
12
+ The host that your broker is talking STOMP on.
13
+
14
+ The default port for STOMP is 61613.
15
+ EDOC
16
+ end
17
+ # TODO: document
18
+
19
+ has :secondary_host, :kind => String, :default => ""
20
+ has :secondary_port, :kind => Integer, :default => 61613
21
+ # TODO: Find out how this is used
22
+ has :name, :kind => String, :default => ""
23
+ has :user, :kind => String, :default => "" do
24
+ doc <<-EDOC
25
+ The user to attempt to authenticate at the broker with.
26
+
27
+ If your broker isn't setup for authentication just leave this blank.
28
+ EDOC
29
+ end
30
+ has :password, :kind => String, :default => "" do
31
+ doc <<-EDOC
32
+ The password to attempt to authenticate at the broker with.
33
+
34
+ If your broker isn't setup for authentication just leave this blank.
35
+ EDOC
36
+ end
37
+ # TODO: I think that reliable means the connection will be reconnected
38
+ # after a disconnect. Find out if that's the case and document it.
39
+ has :reliable, :default => false
40
+ # TODO: document this
41
+ # TODO: I think that persistent means that the message will be stored by
42
+ # the broker on a PUT before it sends an ACK.
43
+ has :persistent, :default => true
44
+ has :reconnect_delay, :default => 5 do
45
+ doc <<-EDOC
46
+ How long (in seconds) should we wait between connection attempts?
47
+
48
+ Default: 5 seconds.
49
+ EDOC
50
+ end
51
+ has :client_id, :default => nil, :kind => String do
52
+ doc <<-EDOC
53
+ A string used to identify this script to the broken.
54
+ EDOC
55
+ end
56
+ has :logfile, :default => STDERR do
57
+ doc <<-EDOC
58
+ Where should we log to?
59
+
60
+ Default: STDERR.
61
+ EDOC
62
+ end
63
+ has :logger, :default => nil do
64
+ doc <<-EDOC
65
+ A logger that's to log with. If this is left out of the options a
66
+ new Logger is built that talks to the logfile.
67
+ EDOC
68
+ end
69
+ has :subscription_name, :default => nil do
70
+ doc <<-EDOC
71
+ The subscription to consume from on the broker.
72
+
73
+ This is only used by message consumers. It doesn't make much sense
74
+ for message producers.
75
+ EDOC
76
+ end
77
+ has :home, :default => File.dirname(File.expand_path(__FILE__)) do
78
+ doc <<-EDOC
79
+ A directory to store state in.
80
+
81
+ Defaults to the directory this script is in.
82
+ EDOC
83
+ end
84
+ has :single_delivery, :default => false do
85
+ doc <<-EDOC
86
+ Note: we can't guarantee single delivery - only best effort.
87
+ Use this when receiving a message more than once is very
88
+ costly. However, be aware that you ~will~ sometimes receive
89
+ the same message more than once (so it's your responsibility
90
+ to make sure that you guard against any consequences).
91
+ EDOC
92
+ end
93
+ has :seen_messages_file do
94
+ init { File.join(home, "seen_messages.#{subscription_name}.#{client_id}.yml") }
95
+ end
96
+ has :expires, :default => 86400 * 7 do
97
+ doc <<-EDOC
98
+ Time to live in milliseconds, i.e. a relative offset not an
99
+ absolute time (as it would be in JMS).
100
+
101
+ The default time to live is one week.
102
+ EDOC
103
+ end
104
+ # to get a durable subscription, you must specify
105
+ # :durable => true
106
+ # and a :client_id (and optionally a :subscription_name)
107
+ has :durable, :default => false do
108
+ must "be true or false" do |b|
109
+ [true, false].include?(b)
110
+ end
111
+ doc <<-EDOC
112
+ Specify whether you want a durable or non-durable subscription.
113
+
114
+ Note: durable queues are ~not~ the default as this could be
115
+ v. expensive in disk usage when used thoughtlessly.
116
+ EDOC
117
+ end
118
+ must "specify client_id if durable is true" do
119
+ #pp [:durable_test, client_id, durable, !client_id.to_s.strip.empty?]
120
+ !(client_id.to_s.strip.empty? and durable)
121
+ end
122
+ end
123
+ has :connection, :default => nil
124
+
125
+ # seen_messages is used to skip over messages that have already
126
+ # been seen - only activated when :single_delivery is specified
127
+ has :seen_messages, :init => []
128
+ has :seen_message_count, :init => 10
129
+ has :seen_messages_file do
130
+ init { configuration.seen_messages_file }
131
+ end
132
+
133
+ def initialize(*args, &block)
134
+ super
135
+ restore_remembered_messages
136
+ SMQueue.dbg { [:seen_messages, seen_messages].inspect }
137
+ end
138
+
139
+ # handle an error
140
+ def handle_error(exception_class, error_message, caller)
141
+ #configuration.logger.warn error_message
142
+ raise exception_class, error_message, caller
143
+ end
144
+
145
+ # connect to message broker
146
+ def connect(*args, &block)
147
+ self.connection = RStomp::Connection.open(configuration.to_hash)
148
+ # If the connection has swapped hosts, then swap out primary and secondary
149
+ if connection.current_host != configuration.host
150
+ configuration.secondary_host = configuration.host
151
+ configuration.host = connection.current_host
152
+ end
153
+
154
+ # If the connection has swapped ports, then swap out primary and secondary
155
+ if connection.current_port != configuration.port
156
+ configuration.secondary_port = configuration.port
157
+ configuration.port = connection.current_port
158
+ end
159
+ end
160
+
161
+ # normalize hash keys (top level only)
162
+ # - normalizes keys to strings by default
163
+ # - optionally pass in name of method to use (e.g. :to_sym) to normalize keys
164
+ def normalize_keys(hash, method = :to_s)
165
+ hash = hash.dup
166
+ hash.keys.each do |k|
167
+ normalized_key = k.respond_to?(method) ? k.send(method) : k
168
+ hash[normalized_key] = hash.delete(k)
169
+ end
170
+ hash
171
+ end
172
+
173
+ # true if the message with this message_id has already been seen
174
+ def message_seen?(message_id)
175
+ self.seen_messages.include?(message_id)
176
+ end
177
+
178
+ # remember the message_id
179
+ def message_seen(message_id)
180
+ message_id = message_id.to_s.strip
181
+ if message_id != ""
182
+ self.seen_messages << message_id
183
+ SMQueue.dbg { [:smqueue, :ack, :message_seen, message_id].inspect }
184
+ if self.seen_messages.size > self.seen_message_count
185
+ self.seen_messages.shift
186
+ end
187
+ store_remembered_messages
188
+ else
189
+ SMQueue.dbg { [:smqueue, :ack, :message_seen, message_id].inspect }
190
+ end
191
+ end
192
+
193
+ # store the remembered message ids in a yaml file
194
+ def store_remembered_messages
195
+ if configuration.single_delivery
196
+ File.open(seen_messages_file, 'w') do |file|
197
+ file.write seen_messages.to_yaml
198
+ end
199
+ end
200
+ end
201
+
202
+ # reload remembered message ids from a yaml file
203
+ def restore_remembered_messages
204
+ if configuration.single_delivery
205
+ yaml = default_yaml = "--- []"
206
+ begin
207
+ File.open(seen_messages_file, 'r') do |file|
208
+ yaml = file.read
209
+ end
210
+ rescue Object
211
+ yaml = default_yaml
212
+ end
213
+ buffer = []
214
+ begin
215
+ buffer = YAML.load(yaml)
216
+ if !buffer.kind_of?(Array) or !buffer.all?{ |x| x.kind_of?(String)}
217
+ raise Exception, "Invalid seen_messages.yml file"
218
+ end
219
+ rescue Object
220
+ buffer = []
221
+ end
222
+ self.seen_messages = buffer
223
+ end
224
+ end
225
+
226
+ # acknowledge message (if headers["ack"] == "client")
227
+ def ack(subscription_headers, message)
228
+ #p [:ack, message.headers["message-id"]]
229
+ if message.headers["message-id"].to_s.strip != "" && subscription_headers["ack"].to_s == "client"
230
+ SMQueue.dbg { [:smqueue, :ack, :message, message].inspect }
231
+ connection.ack message.headers["message-id"], { }
232
+ else
233
+ SMQueue.dbg { [:smqueue, :ack, :not_acknowledging, message].inspect }
234
+ end
235
+ if ENV['PAUSE_SMQUEUE']
236
+ $stderr.print "press enter to continue> "
237
+ $stderr.flush
238
+ $stdin.gets
239
+ end
240
+ end
241
+
242
+ # get message from queue
243
+ # - if block supplied, loop forever and yield(message) for each
244
+ # message received
245
+ # default headers are:
246
+ # :ack => "client"
247
+ # :client_id => configuration.client_id
248
+ # :subscription_name => configuration.subscription_name
249
+ #
250
+ def get(headers = {}, &block)
251
+ self.connect
252
+ SMQueue.dbg { [:smqueue, :get, headers].inspect }
253
+ subscription_headers = {"ack" => "client", "activemq.prefetchSize" => 1 }
254
+ if client_id = configuration.client_id
255
+ subscription_headers["client_id"] = client_id
256
+ end
257
+ if sub_name = configuration.subscription_name
258
+ subscription_headers["subscription_name"] = sub_name
259
+ end
260
+ # if a client_id is supplied, then user wants a durable subscription
261
+ # N.B. client_id must be unique for broker
262
+ subscription_headers.update(headers)
263
+ #p [:subscription_headers_before, subscription_headers]
264
+ subscription_headers = normalize_keys(subscription_headers)
265
+ if configuration.durable and client_id = configuration.client_id || subscription_headers["client_id"]
266
+ subscription_name = configuration.subscription_name || subscription_headers["subscription_name"] || client_id
267
+ # activemq only
268
+ subscription_headers["activemq.subscriptionName"] = subscription_name
269
+ # JMS
270
+ subscription_headers["durable-subscriber-name"] = subscription_name
271
+ end
272
+ #p [:subscription_headers_after, subscription_headers]
273
+
274
+ destination = configuration.name
275
+ SMQueue.dbg { [:smqueue, :get, :subscribing, destination, :subscription_headers, subscription_headers].inspect }
276
+ connection.subscribe destination, subscription_headers
277
+ message = nil
278
+ SMQueue.dbg { [:smqueue, :get, :subscription_headers, subscription_headers].inspect }
279
+ begin
280
+ # TODO: refactor this
281
+ if block_given?
282
+ SMQueue.dbg { [:smqueue, :get, :block_given].inspect }
283
+ # todo: change to @running - (and set to false from exception handler)
284
+ # also should check to see if anything left to receive on connection before bailing out
285
+ while true
286
+ SMQueue.dbg { [:smqueue, :get, :receive].inspect }
287
+ # block until message ready
288
+ message = connection.receive
289
+ SMQueue.dbg { [:smqueue, :get, :received, message].inspect }
290
+ case message.command
291
+ when "ERROR"
292
+ SMQueue.dbg { [:smqueue, :get, :ERROR, message].inspect }
293
+ when "RECEIPT"
294
+ SMQueue.dbg { [:smqueue, :get, :RECEIPT, message].inspect }
295
+ else
296
+ SMQueue.dbg { [:smqueue, :get, :yielding].inspect }
297
+ if !message_seen?(message.headers["message-id"])
298
+ yield(message)
299
+ end
300
+ SMQueue.dbg { [:smqueue, :get, :message_seen, message.headers["message-id"]].inspect }
301
+ message_seen message.headers["message-id"]
302
+ SMQueue.dbg { [:smqueue, :get, :returned_from_yield_now_calling_ack].inspect }
303
+ ack(subscription_headers, message)
304
+ SMQueue.dbg { [:smqueue, :get, :returned_from_ack].inspect }
305
+ end
306
+ end
307
+ else
308
+ SMQueue.dbg { [:smqueue, :get, :single_shot].inspect }
309
+ message = connection.receive
310
+ SMQueue.dbg { [:smqueue, :get, :received, message].inspect }
311
+ if !(message.command == "ERROR" or message.command == "RECEIPT")
312
+ SMQueue.dbg { [:smqueue, :get, :message_seen, message.headers["message-id"]].inspect }
313
+ message_seen message.headers["message-id"]
314
+ SMQueue.dbg { [:smqueue, :get, :ack, message].inspect }
315
+ ack(subscription_headers, message)
316
+ SMQueue.dbg { [:smqueue, :get, :returned_from_ack].inspect }
317
+ end
318
+ end
319
+ rescue Object => e
320
+ SMQueue.dbg { [:smqueue, :get, :exception, e].inspect }
321
+ handle_error e, "Exception in SMQueue#get: #{e.message}", caller
322
+ ensure
323
+ SMQueue.dbg { [:smqueue, :get, :ensure].inspect }
324
+ SMQueue.dbg { [:smqueue, :unsubscribe, destination, subscription_headers].inspect }
325
+ connection.unsubscribe destination, subscription_headers
326
+ SMQueue.dbg { [:smqueue, :disconnect].inspect }
327
+ connection.disconnect
328
+ end
329
+ SMQueue.dbg { [:smqueue, :get, :return].inspect }
330
+ message
331
+ end
332
+
333
+ # put a message on the queue
334
+ # default headers are:
335
+ # :persistent => true
336
+ # :ack => "auto"
337
+ # :expires => configuration.expires
338
+ def put(body, headers = { })
339
+ SMQueue.dbg { [:smqueue, :put, body, headers].inspect }
340
+ begin
341
+ self.connect
342
+ headers = {:persistent => true, :ack => "auto", :expires => SMQueue.calc_expiry_time(configuration.expires) }.merge(headers)
343
+ destination = configuration.name
344
+ SMQueue.dbg { [:smqueue, :send, body, headers].inspect }
345
+ connection.send destination, body, headers
346
+ rescue Exception => e
347
+ SMQueue.dbg { [:smqueue, :exception, e].inspect }
348
+ handle_error e, "Exception in SMQueue#put - #{e.message}", caller
349
+ #connection.disconnect
350
+ ensure
351
+ connection.disconnect
352
+ end
353
+ end
354
+ end
355
+ end