gz_activemessaging 0.13.1

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.
Files changed (92) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +40 -0
  3. data/Appraisals +19 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +87 -0
  6. data/README.md +27 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/activemessaging.gemspec +137 -0
  10. data/gemfiles/activesupport23.gemfile +10 -0
  11. data/gemfiles/activesupport23.gemfile.lock +51 -0
  12. data/gemfiles/activesupport30.gemfile +11 -0
  13. data/gemfiles/activesupport30.gemfile.lock +53 -0
  14. data/gemfiles/activesupport31.gemfile +11 -0
  15. data/gemfiles/activesupport31.gemfile.lock +55 -0
  16. data/gemfiles/activesupport32.gemfile +10 -0
  17. data/gemfiles/activesupport32.gemfile.lock +55 -0
  18. data/generators/a13g_test_harness/a13g_test_harness_generator.rb +19 -0
  19. data/generators/a13g_test_harness/templates/active_messaging_test.rhtml +13 -0
  20. data/generators/a13g_test_harness/templates/active_messaging_test_controller.rb +29 -0
  21. data/generators/a13g_test_harness/templates/index.rhtml +17 -0
  22. data/generators/filter/USAGE +0 -0
  23. data/generators/filter/filter_generator.rb +19 -0
  24. data/generators/filter/templates/filter.rb +12 -0
  25. data/generators/filter/templates/filter_test.rb +28 -0
  26. data/generators/processor/USAGE +8 -0
  27. data/generators/processor/processor_generator.rb +31 -0
  28. data/generators/processor/templates/application_processor.rb +18 -0
  29. data/generators/processor/templates/broker.yml +140 -0
  30. data/generators/processor/templates/jruby_poller +117 -0
  31. data/generators/processor/templates/messaging.rb +12 -0
  32. data/generators/processor/templates/poller +25 -0
  33. data/generators/processor/templates/poller.rb +26 -0
  34. data/generators/processor/templates/processor.rb +8 -0
  35. data/generators/processor/templates/processor_test.rb +20 -0
  36. data/generators/tracer/USAGE +8 -0
  37. data/generators/tracer/templates/controller.rb +14 -0
  38. data/generators/tracer/templates/helper.rb +2 -0
  39. data/generators/tracer/templates/index.rhtml +4 -0
  40. data/generators/tracer/templates/layout.rhtml +16 -0
  41. data/generators/tracer/templates/trace_processor.rb +100 -0
  42. data/generators/tracer/tracer_generator.rb +25 -0
  43. data/init.rb +1 -0
  44. data/lib/activemessaging.rb +133 -0
  45. data/lib/activemessaging/adapter.rb +20 -0
  46. data/lib/activemessaging/adapters/amqp.rb +215 -0
  47. data/lib/activemessaging/adapters/asqs.rb +487 -0
  48. data/lib/activemessaging/adapters/base.rb +71 -0
  49. data/lib/activemessaging/adapters/beanstalk.rb +88 -0
  50. data/lib/activemessaging/adapters/jms.rb +243 -0
  51. data/lib/activemessaging/adapters/reliable_msg.rb +186 -0
  52. data/lib/activemessaging/adapters/stomp.rb +212 -0
  53. data/lib/activemessaging/adapters/synch.rb +95 -0
  54. data/lib/activemessaging/adapters/test.rb +137 -0
  55. data/lib/activemessaging/adapters/wmq.rb +193 -0
  56. data/lib/activemessaging/base_message.rb +28 -0
  57. data/lib/activemessaging/filter.rb +29 -0
  58. data/lib/activemessaging/gateway.rb +429 -0
  59. data/lib/activemessaging/message_sender.rb +30 -0
  60. data/lib/activemessaging/named_base.rb +54 -0
  61. data/lib/activemessaging/processor.rb +44 -0
  62. data/lib/activemessaging/railtie.rb +26 -0
  63. data/lib/activemessaging/test_helper.rb +189 -0
  64. data/lib/activemessaging/threaded_poller.rb +234 -0
  65. data/lib/activemessaging/trace_filter.rb +34 -0
  66. data/lib/generators/active_messaging/install/USAGE +21 -0
  67. data/lib/generators/active_messaging/install/install_generator.rb +39 -0
  68. data/lib/generators/active_messaging/install/templates/application_processor.rb +18 -0
  69. data/lib/generators/active_messaging/install/templates/broker.yml +139 -0
  70. data/lib/generators/active_messaging/install/templates/poller +24 -0
  71. data/lib/generators/active_messaging/install/templates/poller.rb +22 -0
  72. data/lib/generators/active_messaging/install/templates/threaded_poller +46 -0
  73. data/lib/generators/active_messaging/processor/USAGE +2 -0
  74. data/lib/generators/active_messaging/processor/processor_generator.rb +39 -0
  75. data/lib/generators/active_messaging/processor/templates/messaging.rb +12 -0
  76. data/lib/generators/active_messaging/processor/templates/processor.rb +8 -0
  77. data/lib/generators/active_messaging/processor/templates/processor_spec.rb +24 -0
  78. data/lib/generators/active_messaging/processor/templates/processor_test.rb +20 -0
  79. data/lib/tasks/start_consumers.rake +8 -0
  80. data/poller.rb +14 -0
  81. data/test/all_tests.rb +10 -0
  82. data/test/app/config/broker.yml +4 -0
  83. data/test/asqs_test.rb +125 -0
  84. data/test/config_test.rb +42 -0
  85. data/test/filter_test.rb +131 -0
  86. data/test/gateway_test.rb +220 -0
  87. data/test/jms_test.rb +64 -0
  88. data/test/reliable_msg_test.rb +83 -0
  89. data/test/stomp_test.rb +168 -0
  90. data/test/test_helper.rb +36 -0
  91. data/test/tracer_test.rb +57 -0
  92. metadata +202 -0
@@ -0,0 +1,20 @@
1
+ module ActiveMessaging
2
+
3
+ # include this module to make a new adapter - will register the adapter w/gateway so an be used in connection config
4
+ module Adapter
5
+
6
+ def self.included(included_by)
7
+ class << included_by
8
+ def register adapter_name
9
+ Gateway.register_adapter adapter_name, self
10
+ end
11
+ end
12
+ end
13
+
14
+ def logger()
15
+ @@logger ||= ActiveMessaging.logger
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,215 @@
1
+ require 'carrot'
2
+ require 'digest/md5'
3
+ require 'bert'
4
+
5
+ # make sure ActiveMessaging::Processor is already loaded so we actually override it!
6
+ require 'activemessaging/processor'
7
+ require 'activemessaging/adapters/base'
8
+
9
+ require 'emissary/message'
10
+
11
+ module ActiveMessaging
12
+ class Processor
13
+ def self.subscribes_to destination_name, headers={}
14
+ # let's default to using the same exchange_type/exchange_name as defined in the messages.rb
15
+ # for the given destination. XXX: THIS IS A BIG TIME MONKEY PATCH! Might consider pushing a
16
+ # proper patch upstream instead of jury-rigging this.
17
+ d = ActiveMessaging::Gateway.find_destination(destination_name)
18
+ type, name = [ d.publish_headers[:exchange_type], d.publish_headers[:exchange_name] ]
19
+ ActiveMessaging::Gateway.subscribe_to destination_name, self, { :exchange_type => type, :exchange_name => name }.merge(headers)
20
+ end
21
+ end
22
+
23
+ module Adapters
24
+ module Amqp
25
+ class Connection
26
+ include ActiveMessaging::Adapter
27
+ register :amqp
28
+
29
+ SERVER_RETRY_MAX_ATTEMPTS = 10
30
+
31
+ DEFAULT_QUEUE_CONFIG = {
32
+ :durable => true,
33
+ :auto_delete => false,
34
+ :exclusive => true
35
+ }
36
+
37
+ class InvalidExchangeType < ArgumentError; end
38
+
39
+ def initialize config = {}
40
+ @connect_options = {
41
+ :user => config[:user] || 'guest',
42
+ :pass => config[:pass] || 'guest',
43
+ :host => config[:host] || 'localhost',
44
+ :port => config[:port] || (config[:ssl] ? 5671 : 5672),
45
+ :vhost => config[:vhost] || nil,
46
+ :ssl => config[:ssl] || false,
47
+ :ssl_verify => config[:ssl_verify] || OpenSSL::SSL::VERIFY_PEER,
48
+ }
49
+
50
+ @debug = config[:debug].to_i rescue 0
51
+
52
+ Carrot.logging = true unless @debug < 5
53
+
54
+ @auto_generated_queue = false
55
+ unless config[:queue_name]
56
+ @queue_name = Digest::MD5.hexdigest Time.now.to_s
57
+ @auto_generated_queue = true
58
+ else
59
+ @queue_name = config[:queue_name]
60
+ end
61
+
62
+ @queue_config = DEFAULT_QUEUE_CONFIG
63
+ unless @auto_generated_queue
64
+ @queue_config.merge!({
65
+ :durable => !!config[:queue_durable],
66
+ :auto_delete => !!config[:queue_auto_delete],
67
+ :exclusive => !!config[:queue_exclusive]
68
+ })
69
+ end
70
+ end
71
+
72
+ def received message, headers = {}
73
+ puts "Received Message - ACK'ing with delivery_tag '#{message.headers[:delivery_tag]}'" if @debug > 0
74
+ client.server.send_frame(::Carrot::AMQP::Protocol::Basic::Ack.new(:delivery_tag => message.headers[:delivery_tag]))
75
+ end
76
+
77
+ def unreceive message, headers = {}
78
+ puts "Un-Receiving Message - REJECTing with delivery_tag '#{message.headers[:delivery_tag]}'" if @debug > 0
79
+ client.server.send_frame(::Carrot::AMQP::Protocol::Basic::Reject.new(:delivery_tag => message.headers[:delivery_tag]))
80
+ end
81
+
82
+ def receive(options={})
83
+ while true
84
+ message = queue.pop(:ack => true)
85
+ unless message.nil?
86
+ message = AmqpMessage.decode(message).stamp_received! unless message.nil?
87
+ message.delivery_tag = queue.delivery_tag
88
+ puts "RECEIVE: #{message.inspect}" if @debug
89
+ return message
90
+ end
91
+ sleep 0.2
92
+ end
93
+ end
94
+
95
+ def send queue_name, data, headers = {}
96
+ headers[:routing_key] ||= queue_name
97
+ message = AmqpMessage.new({:headers => headers, :data => data}, queue_name)
98
+
99
+ if @debug > 0
100
+ puts "Sending the following message: "; pp message
101
+ end
102
+
103
+ begin
104
+ exchange(*exchange_info(headers)).publish(message.stamp_sent!.encode, :key => headers[:routing_key])
105
+ rescue ::Carrot::AMQP::Server::ServerDown
106
+ retry_attempts = retry_attempts.nil? ? 1 : retry_attempts + 1
107
+ sleep(retry_attempts * 0.25)
108
+ retry unless retry_attempts >= SERVER_RETRY_MAX_ATTEMPTS
109
+ raise e
110
+ end
111
+ end
112
+
113
+ def subscribe queue_name, headers = {}, subId = nil
114
+ if @debug > 1
115
+ puts "Begin Subscribe Request:"
116
+ puts " Queue Name: #{queue_name.inspect}"
117
+ puts " Headers: #{headers.inspect}"
118
+ puts " subId: #{subId.inspect}"
119
+ puts " EXCH INFO: #{exchange_info(headers).inspect}"
120
+ puts "End Subscribe Request."
121
+ end
122
+
123
+ routing_key = headers[:routing_key] || queue_name
124
+ queue.bind(exchange(*exchange_info(headers)), :key => routing_key)
125
+ end
126
+
127
+ def unsubscribe(queue_name, headers={}, subId=nil)
128
+ if @debug > 1
129
+ puts "Begin UNsubscribe Request:"
130
+ puts " Queue Name: #{queue_name.inspect}"
131
+ puts " Headers: #{headers.inspect}"
132
+ puts " subId: #{subId.inspect}"
133
+ puts "End UNsubscribe Request."
134
+ end
135
+
136
+ routing_key = headers[:routing_key] || queue_name
137
+ queue.unbind(exchange(*exchange_info(headers)), :key => routing_key)
138
+ end
139
+
140
+ def disconnect(headers={})
141
+ @client.stop
142
+ end
143
+
144
+ private
145
+
146
+ def exchange_info headers
147
+ [ (headers[:exchange_type].to_sym rescue nil) || :direct, headers[:exchange_name] || nil]
148
+ end
149
+
150
+ def exchange type, name, *args
151
+ type = type.to_sym rescue nil
152
+ unless [:topic, :fanout, :direct].include? type
153
+ raise InvalidExchangeType, "The carrot library does not support an exchange type of '#{type.inspect}'"
154
+ end
155
+
156
+ name ||= "amq.#{type}"
157
+ puts "Exchange [#{type}::#{name}]: #{args.inspect}" if @debug > 3
158
+ (@exchanges||={})[name] ||= ::Carrot::AMQP::Exchange.new client, type, name, *args
159
+ end
160
+
161
+ def queue
162
+ return @queue unless @queue.nil?
163
+ puts "Queue [#{@queue_name}]: #{@queue_config.inspect}" if @debug > 0
164
+ @queue ||= client.queue(@queue_name, @queue_config)
165
+ end
166
+
167
+ def client
168
+ return @client unless @client.nil?
169
+ puts "Client [amqp]: #{@connect_options.inspect}" if @debug > 0
170
+ @client ||= Carrot.new(@connect_options)
171
+ end
172
+
173
+ end
174
+
175
+ class AmqpMessage < Emissary::Message
176
+ attr_reader :command
177
+ attr_accessor :destination, :delivery_tag
178
+
179
+ def initialize(data, queue_name = nil)
180
+ data[:data] = data.delete(:body) unless (data[:data] && !data[:body])
181
+
182
+ super(data)
183
+
184
+ @delivery_tag ||= (data[:headers][:delivery_tag] rescue nil)
185
+ @destination ||= (data[:headers][:destination] rescue nil) || queue_name || routing_key
186
+
187
+ @command = "MESSAGE"
188
+ end
189
+
190
+ alias :body :data
191
+
192
+ def headers
193
+ super.merge({
194
+ :destination => routing_key,
195
+ :delivery_tag => @delivery_tag
196
+ })
197
+ end
198
+
199
+ def matches_subscription?(subscription)
200
+ # use routing key first, otherwise, use the defined destination value
201
+ destination = subscription.subscribe_headers[:routing_key] || subscription.destination.value.to_s
202
+
203
+ if destination.match(/(\#|\*)/)
204
+ dest_regex = ::Regexp.new(destination.gsub('.*', '[.][^.]+').gsub(/\.\#.*/, '[.].*'))
205
+ !!(headers[:destination].to_s =~ dest_regex)
206
+ else
207
+ !!(headers[:destination].to_s == destination)
208
+ end
209
+ end
210
+ end
211
+
212
+ end
213
+ end
214
+ end
215
+
@@ -0,0 +1,487 @@
1
+ require 'rubygems'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'openssl'
5
+ require 'base64'
6
+ require 'cgi'
7
+ require 'time'
8
+ require 'uri'
9
+ require 'rexml/document'
10
+
11
+ require 'activemessaging/adapters/base'
12
+
13
+ module ActiveMessaging
14
+ module Adapters
15
+ module AmazonSqs
16
+
17
+ class Connection < ActiveMessaging::Adapters::BaseConnection
18
+ register :asqs
19
+
20
+ QUEUE_NAME_LENGTH = 1..80
21
+ VISIBILITY_TIMEOUT = 0..(24 * 60 * 60)
22
+ NUMBER_OF_MESSAGES = 1..255
23
+ GET_QUEUE_ATTRIBUTES = ['All', 'ApproximateNumberOfMessages', 'VisibilityTimeout']
24
+ SET_QUEUE_ATTRIBUTES = ['VisibilityTimeout']
25
+
26
+ #configurable params
27
+ attr_accessor :reconnect_delay, :access_key_id, :secret_access_key, :aws_version, :content_type, :host, :port, :poll_interval, :cache_queue_list, :max_message_size
28
+
29
+ #generic init method needed by a13g
30
+ def initialize cfg
31
+ raise "Must specify a access_key_id" if (cfg[:access_key_id].nil? || cfg[:access_key_id].empty?)
32
+ raise "Must specify a secret_access_key" if (cfg[:secret_access_key].nil? || cfg[:secret_access_key].empty?)
33
+
34
+ @access_key_id=cfg[:access_key_id]
35
+ @secret_access_key=cfg[:secret_access_key]
36
+ @request_expires = cfg[:requestExpires] || 10
37
+ @request_retry_count = cfg[:requestRetryCount] || 5
38
+ @aws_version = cfg[:aws_version] || '2008-01-01'
39
+ @content_type = cfg[:content_type] || 'text/plain'
40
+ @host = cfg[:host] || 'queue.amazonaws.com'
41
+ @port = cfg[:port] || 80
42
+ @protocol = cfg[:protocol] || 'http'
43
+ @poll_interval = cfg[:poll_interval] || 1
44
+ @reconnect_delay = cfg[:reconnectDelay] || 5
45
+
46
+ @max_message_size = cfg[:max_message_size].to_i > 0 ? cfg[:max_message_size].to_i : 8
47
+
48
+ @aws_url="#{@protocol}://#{@host}"
49
+
50
+ @cache_queue_list = cfg[:cache_queue_list].nil? ? true : cfg[:cache_queue_list]
51
+ @reliable = cfg[:reliable].nil? ? true : cfg[:reliable]
52
+
53
+ #initialize the subscriptions and queues
54
+ @subscriptions = {}
55
+ @queues_by_priority = {}
56
+ @current_subscription = 0
57
+ queues
58
+ end
59
+
60
+ def disconnect
61
+ #it's an http request - there is no disconnect - ha!
62
+ return true
63
+ end
64
+
65
+ # queue_name string, headers hash
66
+ # for sqs, make sure queue exists, if not create, then add to list of polled queues
67
+ def subscribe queue_name, message_headers={}
68
+ # look at the existing queues, create any that are missing
69
+ queue = get_or_create_queue queue_name
70
+ if @subscriptions.has_key? queue.name
71
+ @subscriptions[queue.name].add
72
+ else
73
+ @subscriptions[queue.name] = Subscription.new(queue.name, message_headers)
74
+ end
75
+ priority = @subscriptions[queue.name].priority
76
+
77
+ @queues_by_priority[priority] = [] unless @queues_by_priority.has_key?(priority)
78
+ @queues_by_priority[priority] << queue.name unless @queues_by_priority[priority].include?(queue.name)
79
+ end
80
+
81
+ # queue_name string, headers hash
82
+ # for sqs, attempt delete the queues, won't work if not empty, that's ok
83
+ def unsubscribe queue_name, message_headers={}
84
+ if @subscriptions[queue_name]
85
+ @subscriptions[queue_name].remove
86
+ if @subscriptions[queue_name].count <= 0
87
+ sub = @subscriptions.delete(queue_name)
88
+ @queues_by_priority[sub.priority].delete(queue_name)
89
+ end
90
+ end
91
+ end
92
+
93
+ # queue_name string, body string, headers hash
94
+ # send a single message to a queue
95
+ def send queue_name, message_body, message_headers={}
96
+ queue = get_or_create_queue queue_name
97
+ send_messsage queue, message_body
98
+ end
99
+
100
+ # def receive(options={})
101
+ # while true
102
+ # # this will try all the queues once, by priority
103
+ # # returns nil if nothing there
104
+ # message = receive_message(options)
105
+
106
+ # if message
107
+ # return message
108
+ # else
109
+ # sleep(poll_interval)
110
+ # end
111
+ # end
112
+ # end
113
+
114
+ # new receive respects priorities
115
+ def receive(options={})
116
+ message = nil
117
+
118
+ only_priorities = options[:priorities]
119
+
120
+ # loop through the priorities
121
+ @queues_by_priority.keys.sort.each do |priority|
122
+
123
+ # skip this priority if there is a list, and it is not in the list
124
+ next if only_priorities && !only_priorities.include?(priority.to_i)
125
+
126
+ # puts " - priority: #{priority}"
127
+ # loop through queues for the priority in random order each time
128
+ @queues_by_priority[priority].shuffle.each do |queue_name|
129
+ # puts " - queue_name: #{queue_name}"
130
+ queue = queues[queue_name]
131
+ subscription = @subscriptions[queue_name]
132
+
133
+ next if queue.nil? || subscription.nil?
134
+ messages = retrieve_messsages(queue, 1, subscription.headers[:visibility_timeout])
135
+
136
+ if (messages && !messages.empty?)
137
+ message = messages[0]
138
+ end
139
+
140
+ break if message
141
+ end
142
+
143
+ break if message
144
+ end
145
+
146
+ # puts " - message: #{message}"
147
+ message
148
+ end
149
+
150
+ # # receive a single message from any of the subscribed queues
151
+ # # check each queue once, then sleep for poll_interval
152
+ # def receive
153
+ # raise "No subscriptions to receive messages from." if (@subscriptions.nil? || @subscriptions.empty?)
154
+ # start = @current_subscription
155
+ # while true
156
+ # # puts "calling receive..."
157
+ # @current_subscription = ((@current_subscription < @subscriptions.length-1) ? @current_subscription + 1 : 0)
158
+ # sleep poll_interval if (@current_subscription == start)
159
+ # queue_name = @subscriptions.keys.sort[@current_subscription]
160
+ # queue = queues[queue_name]
161
+ # subscription = @subscriptions[queue_name]
162
+ # unless queue.nil?
163
+ # messages = retrieve_messsages queue, 1, subscription.headers[:visibility_timeout]
164
+ # return messages[0] unless (messages.nil? or messages.empty? or messages[0].nil?)
165
+ # end
166
+ # end
167
+ # end
168
+
169
+ def received message, headers={}
170
+ begin
171
+ delete_message message
172
+ rescue Object=>exception
173
+ logger.error "Exception in ActiveMessaging::Adapters::AmazonSWS::Connection.received() logged and ignored: "
174
+ logger.error exception
175
+ end
176
+ end
177
+
178
+ # do nothing; by not deleting the message will eventually become visible again
179
+ def unreceive message, headers={}
180
+ return true
181
+ end
182
+
183
+ protected
184
+
185
+ def create_queue(name)
186
+ validate_new_queue name
187
+ response = make_request('CreateQueue', nil, {'QueueName'=>name})
188
+ add_queue(response.get_text("//QueueUrl")) unless response.nil?
189
+ end
190
+
191
+ def delete_queue queue
192
+ validate_queue queue
193
+ response = make_request('DeleteQueue', "#{queue.queue_url}")
194
+ end
195
+
196
+ def list_queues(queue_name_prefix=nil)
197
+ validate_queue_name queue_name_prefix unless queue_name_prefix.nil?
198
+ params = queue_name_prefix.nil? ? {} : {"QueueNamePrefix"=>queue_name_prefix}
199
+ response = make_request('ListQueues', nil, params)
200
+ response.nil? ? [] : response.nodes("//QueueUrl").collect{ |n| add_queue(n.text) }
201
+ end
202
+
203
+ def get_queue_attributes(queue, attribute='All')
204
+ validate_get_queue_attribute(attribute)
205
+ params = {'AttributeName'=>attribute}
206
+ response = make_request('GetQueueAttributes', "#{queue.queue_url}")
207
+ attributes = {}
208
+ response.each_node('/GetQueueAttributesResponse/GetQueueAttributesResult/Attribute') { |n|
209
+ n = n.elements['Name'].text
210
+ v = n.elements['Value'].text
211
+ attributes[n] = v
212
+ }
213
+ if attribute != 'All'
214
+ attributes[attribute]
215
+ else
216
+ attributes
217
+ end
218
+ end
219
+
220
+ def set_queue_attribute(queue, attribute, value)
221
+ validate_set_queue_attribute(attribute)
222
+ params = {'Attribute.Name'=>attribute, 'Attribute.Value'=>value.to_s}
223
+ response = make_request('SetQueueAttributes', "#{queue.queue_url}", params)
224
+ end
225
+
226
+ def delete_queue queue
227
+ validate_queue queue
228
+ response = make_request('DeleteQueue', "#{queue.queue_url}")
229
+ end
230
+
231
+ # in progress
232
+ def send_messsage queue, message
233
+ validate_queue queue
234
+ validate_message message
235
+ response = make_request('SendMessage', queue.queue_url, {'MessageBody'=>message})
236
+ response.get_text("//MessageId") unless response.nil?
237
+ end
238
+
239
+ def retrieve_messsages queue, num_messages=1, timeout=nil
240
+ validate_queue queue
241
+ validate_number_of_messages num_messages
242
+ validate_timeout timeout if timeout
243
+
244
+ params = {'MaxNumberOfMessages'=>num_messages.to_s}
245
+ params['VisibilityTimeout'] = timeout.to_s if timeout
246
+
247
+ response = make_request('ReceiveMessage', "#{queue.queue_url}", params)
248
+ response.nodes("//Message").collect{ |n| Message.from_element n, response, queue } unless response.nil?
249
+ end
250
+
251
+ def delete_message message
252
+ response = make_request('DeleteMessage', "#{message.queue.queue_url}", {'ReceiptHandle'=>message.receipt_handle})
253
+ end
254
+
255
+ def make_request(action, url=nil, params = {})
256
+ # puts "make_request a=#{action} u=#{url} p=#{params}"
257
+ url ||= @aws_url
258
+
259
+ # Add Actions
260
+ params['Action'] = action
261
+ params['Version'] = @aws_version
262
+ params['AWSAccessKeyId'] = @access_key_id
263
+ params['Expires']= (Time.now + @request_expires).gmtime.iso8601
264
+ params['SignatureVersion'] = '1'
265
+
266
+ # Sign the string
267
+ sorted_params = params.sort_by { |key,value| key.downcase }
268
+ string_to_sign = sorted_params.collect { |key, value| key.to_s + value.to_s }.join()
269
+ digest = OpenSSL::Digest::Digest.new('sha1')
270
+ hmac = OpenSSL::HMAC.digest(digest, @secret_access_key, string_to_sign)
271
+ params['Signature'] = Base64.encode64(hmac).chomp
272
+
273
+ # Construct request
274
+ query_params = params.collect { |key, value| key + "=" + CGI.escape(value.to_s) }.join("&")
275
+
276
+ # Put these together to get the request query string
277
+ request_url = "#{url}?#{query_params}"
278
+ # puts "request_url = #{request_url}"
279
+ request = Net::HTTP::Get.new(request_url)
280
+
281
+ retry_count = 0
282
+ while retry_count < @request_retry_count.to_i
283
+ retry_count = retry_count + 1
284
+ # puts "make_request try retry_count=#{retry_count}"
285
+ begin
286
+ response = SQSResponse.new(http_request(host,port,request))
287
+ check_errors(response)
288
+ return response
289
+ rescue Object=>ex
290
+ # puts "make_request caught #{ex}"
291
+ raise ex unless reliable
292
+ sleep(@reconnect_delay)
293
+ end
294
+ end
295
+ end
296
+
297
+ # I wrap this so I can move to a different client, or easily mock for testing
298
+ def http_request h, p, r
299
+ return Net::HTTP.start(h, p){ |http| http.request(r) }
300
+ end
301
+
302
+ def http_request h, p, r
303
+ http = Net::HTTP.new(h, p)
304
+ http.use_ssl = true if "https" == @protocol
305
+ # Don't carp about SSL cert verification
306
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
307
+ return http.request(r)
308
+ end
309
+
310
+ def check_errors(response)
311
+ raise "http response was nil" if (response.nil?)
312
+ raise response.errors if (response && response.errors?)
313
+ response
314
+ end
315
+
316
+ private
317
+
318
+ # internal data structure methods
319
+ def add_queue(url)
320
+ q = Queue.from_url url
321
+ queues[q.name] = q if self.cache_queue_list
322
+ return q
323
+ end
324
+
325
+ def get_or_create_queue queue_name
326
+ qs = queues
327
+ q = qs.has_key?(queue_name) ? qs[queue_name] : create_queue(queue_name)
328
+ raise "could not get or create queue: #{queue_name}" unless q
329
+ q
330
+ end
331
+
332
+ def queues
333
+ return @queues if (@queues && cache_queue_list)
334
+ @queues = {}
335
+ list_queues.each{|q| @queues[q.name]=q }
336
+ return @queues
337
+ end
338
+
339
+ # validation methods
340
+ def validate_queue_name qn
341
+ raise "Queue name, '#{qn}', must be between #{QUEUE_NAME_LENGTH.min} and #{QUEUE_NAME_LENGTH.max} characters." unless QUEUE_NAME_LENGTH.include?(qn.length)
342
+ raise "Queue name, '#{qn}', must be alphanumeric only." if (qn =~ /[^\w\-\_]/ )
343
+ end
344
+
345
+ def validate_new_queue qn
346
+ validate_queue_name qn
347
+ raise "Queue already exists: #{qn}" if queues.has_key? qn
348
+ end
349
+
350
+ def validate_queue q
351
+ raise "Never heard of queue, can't use it: #{q.name}" unless queues.has_key? q.name
352
+ end
353
+
354
+ def validate_message m
355
+ raise "Message cannot be nil." if m.nil?
356
+ raise "Message length, #{m.length}, must be between #{message_size_range.min} and #{message_size_range.max}." unless message_size_range.include?(m.length)
357
+ end
358
+
359
+ def message_size_range
360
+ @_message_size_range ||= 1..(max_message_size * 1024)
361
+ end
362
+
363
+ def validate_timeout to
364
+ raise "Timeout, #{to}, must be between #{VISIBILITY_TIMEOUT.min} and #{VISIBILITY_TIMEOUT.max}." unless VISIBILITY_TIMEOUT.include?(to)
365
+ end
366
+
367
+ def validate_get_queue_attribute qa
368
+ raise "Queue Attribute name, #{qa}, not in list of valid attributes to get: #{GET_QUEUE_ATTRIBUTES.to_sentence}." unless GET_QUEUE_ATTRIBUTES.include?(qa)
369
+ end
370
+
371
+ def validate_set_queue_attribute qa
372
+ raise "Queue Attribute name, #{qa}, not in list of valid attributes to set: #{SET_QUEUE_ATTRIBUTES.to_sentence}." unless SET_QUEUE_ATTRIBUTES.include?(qa)
373
+ end
374
+
375
+ def validate_number_of_messages nom
376
+ raise "Number of messages, #{nom}, must be between #{NUMBER_OF_MESSAGES.min} and #{NUMBER_OF_MESSAGES.max}." unless NUMBER_OF_MESSAGES.include?(nom)
377
+ end
378
+ end
379
+
380
+ class SQSResponse
381
+ attr_accessor :headers, :doc, :http_response
382
+
383
+ def initialize response
384
+ # puts "response.body = #{response.body}"
385
+ @http_response = response
386
+ @headers = response.to_hash()
387
+ @doc = REXML::Document.new(response.body)
388
+ end
389
+
390
+ def message_type
391
+ return doc ? doc.root.name : ''
392
+ end
393
+
394
+ def errors?
395
+ (not http_response.kind_of?(Net::HTTPSuccess)) or (message_type == "ErrorResponse")
396
+ end
397
+
398
+ def errors
399
+ return "HTTP Error: #{http_response.code} : #{http_response.message}" unless http_response.kind_of?(Net::HTTPSuccess)
400
+
401
+ msg = nil
402
+ each_node('//Error') { |n|
403
+ msg ||= ""
404
+ c = n.elements['Code'].text
405
+ m = n.elements['Message'].text
406
+ msg << ", " if msg != ""
407
+ msg << "#{c} : #{m}"
408
+ }
409
+
410
+ return msg
411
+ end
412
+
413
+ def get_text(xpath,default='')
414
+ e = REXML::XPath.first( doc, xpath)
415
+ e.nil? ? default : e.text
416
+ end
417
+
418
+ def each_node(xp)
419
+ REXML::XPath.each(doc.root, xp) {|n| yield n}
420
+ end
421
+
422
+ def nodes(xp)
423
+ doc.elements.to_a(xp)
424
+ end
425
+ end
426
+
427
+ class Subscription
428
+ attr_accessor :destination, :headers, :count, :priority
429
+
430
+ def initialize(destination, headers={}, count=1)
431
+ @priority = headers.delete(:priority) || 1001
432
+ @destination, @headers, @count = destination, headers, count
433
+ end
434
+
435
+ def add
436
+ @count += 1
437
+ end
438
+
439
+ def remove
440
+ @count -= 1
441
+ end
442
+ end
443
+
444
+ class Queue
445
+ attr_accessor :name, :pathinfo, :domain, :visibility_timeout
446
+
447
+ def self.from_url url
448
+ u = URI.parse(url)
449
+ name = u.path.gsub(/\//, "")
450
+ domain = u.host
451
+ return Queue.new(name,domain)
452
+ end
453
+
454
+ def queue_url
455
+ "#{pathinfo}/#{name}"
456
+ end
457
+
458
+ def initialize name, domain, vt=nil
459
+ @name, @pathinfo, @domain, @visibility_timeout = name, pathinfo, domain, vt
460
+ end
461
+
462
+ def to_s
463
+ "<AmazonSQS::Queue name='#{name}' url='#{queue_url}' domain='#{domain}'>"
464
+ end
465
+ end
466
+
467
+ # based on stomp message, has pointer to the SQSResponseObject
468
+ class Message < ActiveMessaging::BaseMessage
469
+ attr_accessor :response, :queue, :md5_of_body, :receipt_handle
470
+
471
+ def self.from_element e, response, queue
472
+ Message.new(e.elements['Body'].text, response.headers, e.elements['MessageId'].text, e.elements['MD5OfBody'].text, e.elements['ReceiptHandle'].text, response, queue)
473
+ end
474
+
475
+ def initialize body, headers, id, md5_of_body, receipt_handle, response, queue
476
+ super(body, id, headers, queue.name)
477
+ @md5_of_body, @receipt_handle, @response, @queue = md5_of_body, receipt_handle, response, queue
478
+ end
479
+
480
+ def to_s
481
+ "<AmazonSQS::Message id='#{id}' body='#{body}' headers='#{headers.inspect}' response='#{response}'>"
482
+ end
483
+ end
484
+
485
+ end
486
+ end
487
+ end