ruby-aws 1.6.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.
Files changed (76) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/History.txt +75 -0
  4. data/LICENSE.txt +202 -0
  5. data/Manifest.txt +72 -0
  6. data/NOTICE.txt +4 -0
  7. data/README.txt +105 -0
  8. data/Rakefile +33 -0
  9. data/bin/ruby-aws +9 -0
  10. data/lib/amazon/util.rb +10 -0
  11. data/lib/amazon/util/binder.rb +48 -0
  12. data/lib/amazon/util/data_reader.rb +169 -0
  13. data/lib/amazon/util/filter_chain.rb +79 -0
  14. data/lib/amazon/util/hash_nesting.rb +93 -0
  15. data/lib/amazon/util/lazy_results.rb +59 -0
  16. data/lib/amazon/util/logging.rb +23 -0
  17. data/lib/amazon/util/paginated_iterator.rb +70 -0
  18. data/lib/amazon/util/proactive_results.rb +116 -0
  19. data/lib/amazon/util/threadpool.rb +129 -0
  20. data/lib/amazon/util/user_data_store.rb +100 -0
  21. data/lib/amazon/webservices/mechanical_turk.rb +123 -0
  22. data/lib/amazon/webservices/mechanical_turk_requester.rb +274 -0
  23. data/lib/amazon/webservices/mturk/mechanical_turk_error_handler.rb +150 -0
  24. data/lib/amazon/webservices/mturk/question_generator.rb +58 -0
  25. data/lib/amazon/webservices/util/amazon_authentication_relay.rb +72 -0
  26. data/lib/amazon/webservices/util/command_line.rb +157 -0
  27. data/lib/amazon/webservices/util/convenience_wrapper.rb +90 -0
  28. data/lib/amazon/webservices/util/filter_proxy.rb +45 -0
  29. data/lib/amazon/webservices/util/mock_transport.rb +70 -0
  30. data/lib/amazon/webservices/util/request_signer.rb +42 -0
  31. data/lib/amazon/webservices/util/rest_transport.rb +120 -0
  32. data/lib/amazon/webservices/util/soap_simplifier.rb +48 -0
  33. data/lib/amazon/webservices/util/soap_transport.rb +20 -0
  34. data/lib/amazon/webservices/util/soap_transport_header_handler.rb +27 -0
  35. data/lib/amazon/webservices/util/unknown_result_exception.rb +27 -0
  36. data/lib/amazon/webservices/util/validation_exception.rb +55 -0
  37. data/lib/amazon/webservices/util/xml_simplifier.rb +61 -0
  38. data/lib/ruby-aws.rb +19 -0
  39. data/lib/ruby-aws/version.rb +6 -0
  40. data/run_rcov.sh +1 -0
  41. data/samples/mturk/best_image/BestImage.rb +61 -0
  42. data/samples/mturk/best_image/best_image.properties +39 -0
  43. data/samples/mturk/best_image/best_image.question +82 -0
  44. data/samples/mturk/blank_slate/BlankSlate.rb +63 -0
  45. data/samples/mturk/blank_slate/BlankSlate_multithreaded.rb +67 -0
  46. data/samples/mturk/helloworld/MTurkHelloWorld.rb +56 -0
  47. data/samples/mturk/helloworld/mturk.yml +8 -0
  48. data/samples/mturk/review_policy/ReviewPolicy.rb +139 -0
  49. data/samples/mturk/review_policy/review_policy.question +30 -0
  50. data/samples/mturk/reviewer/Reviewer.rb +103 -0
  51. data/samples/mturk/reviewer/mturk.yml +8 -0
  52. data/samples/mturk/simple_survey/SimpleSurvey.rb +90 -0
  53. data/samples/mturk/simple_survey/simple_survey.question +30 -0
  54. data/samples/mturk/site_category/SiteCategory.rb +87 -0
  55. data/samples/mturk/site_category/externalpage.htm +71 -0
  56. data/samples/mturk/site_category/site_category.input +6 -0
  57. data/samples/mturk/site_category/site_category.properties +45 -0
  58. data/samples/mturk/site_category/site_category.question +9 -0
  59. data/test/mturk/test_changehittypeofhit.rb +130 -0
  60. data/test/mturk/test_error_handler.rb +137 -0
  61. data/test/mturk/test_mechanical_turk_requester.rb +178 -0
  62. data/test/mturk/test_mock_mechanical_turk_requester.rb +205 -0
  63. data/test/test_ruby-aws.rb +24 -0
  64. data/test/unit/test_binder.rb +89 -0
  65. data/test/unit/test_data_reader.rb +135 -0
  66. data/test/unit/test_exceptions.rb +32 -0
  67. data/test/unit/test_hash_nesting.rb +99 -0
  68. data/test/unit/test_lazy_results.rb +89 -0
  69. data/test/unit/test_mock_transport.rb +132 -0
  70. data/test/unit/test_paginated_iterator.rb +58 -0
  71. data/test/unit/test_proactive_results.rb +108 -0
  72. data/test/unit/test_question_generator.rb +55 -0
  73. data/test/unit/test_threadpool.rb +50 -0
  74. data/test/unit/test_user_data_store.rb +80 -0
  75. metadata +238 -0
  76. metadata.gz.sig +0 -0
@@ -0,0 +1,123 @@
1
+ # Copyright:: Copyright (c) 2007 Amazon Technologies, Inc.
2
+ # License:: Apache License, Version 2.0
3
+
4
+ require 'amazon/util/logging'
5
+ require 'amazon/webservices/util/amazon_authentication_relay'
6
+ require 'amazon/webservices/mturk/mechanical_turk_error_handler'
7
+ require 'amazon/webservices/util/validation_exception'
8
+ require 'amazon/webservices/util/rest_transport'
9
+ require 'amazon/webservices/util/soap_transport'
10
+
11
+ module Amazon
12
+ module WebServices
13
+
14
+ class MechanicalTurk
15
+ include Amazon::Util::Logging
16
+
17
+ SOFTWARE_NAME = 'MTurkRubySDK'
18
+
19
+ SANDBOX = 'mechanicalturk.sandbox.amazonaws.com'
20
+ PROD = 'mechanicalturk.amazonaws.com'
21
+
22
+ # By default, MechanicalTurk will operate on the MechanicalTurk Sandbox. To run against the main site, pass +:Host => :Production+ into the constructor.
23
+ def initialize(args={})
24
+ name = args[:Name] || 'AWSMechanicalTurkRequester'
25
+ software = args.has_key?(:SoftwareName) ? "#{SOFTWARE_NAME}, #{args[:SoftwareName]}" : "#{SOFTWARE_NAME}"
26
+ @host = case args[:Host].to_s
27
+ when /^Prod/i
28
+ PROD
29
+ when /^Sandbox/i,""
30
+ SANDBOX
31
+ else
32
+ args[:Host].to_s
33
+ end
34
+ ssl = ( args[:UseSSL].nil? ? true : args[:UseSSL] )
35
+ newargs = args.merge( :Name => name, :SoftwareName => software, :Host => @host, :UseSSL => ssl)
36
+ if args[:Transport].to_s =~ /^SOAP/i
37
+ unless Util::SOAPTransport.canSOAP?
38
+ log "Unable to use SOAP transport. Falling back to REST."
39
+ args[:Transport] = :REST
40
+ end
41
+ end
42
+ transport = case args[:Transport]
43
+ when :SOAP,/^SOAP/i
44
+ getSOAPTransport(newargs)
45
+ when :REST,/^REST/i
46
+ getRESTTransport(newargs)
47
+ else
48
+ require 'amazon/webservices/util/soap_transport.rb'
49
+ allowOverride( 'Transport', args[:Transport], newargs ) { |a|
50
+ if Util::RESTTransport.canPost?
51
+ getRESTTransport(newargs)
52
+ elsif Util::SOAPTransport.canSOAP?
53
+ getSOAPTransport(newargs)
54
+ else
55
+ getRESTTransport(newargs)
56
+ end
57
+ }
58
+ end
59
+ newargs.merge!( :Transport => transport )
60
+ log "Generating relay with following args: #{newargs.inspect}"
61
+ relay = allowOverride('Relay',args[:Relay],newargs) { |a| Amazon::WebServices::Util::AmazonAuthenticationRelay.new(a) }
62
+ newargs.merge!( :Relay => relay )
63
+ log "Generating error handler with the following args: #{newargs.inspect}"
64
+ @errorHandler = allowOverride('ErrorHandler',args[:ErrorHandler],newargs) { |a| Amazon::WebServices::MTurk::MechanicalTurkErrorHandler.new(a) }
65
+ end
66
+
67
+ attr_accessor :host
68
+
69
+ def method_missing(method,*args)
70
+ log "Sending request: #{method} #{args.inspect}"
71
+ @errorHandler.dispatch(method,*args)
72
+ end
73
+
74
+ private
75
+
76
+ def allowOverride(name,override,args,&default)
77
+ newargs = args.merge( :DefaultOverride => default )
78
+ case override
79
+ when nil
80
+ yield( args )
81
+ when String,Symbol,Array,Hash,Integer
82
+ raise "Invalid #{name}: #{override.inspect}"
83
+ when Class
84
+ override.new( newargs )
85
+ else
86
+ override.configure( newargs ) if override.respond_to? :configure
87
+ override
88
+ end
89
+ end
90
+
91
+ def getRESTTransport(args)
92
+ endpoint = findRestEndpoint( args[:Name], args[:Host], args[:UseSSL] )
93
+ require 'amazon/webservices/util/rest_transport.rb'
94
+ @transport = Amazon::WebServices::Util::RESTTransport.new( args.merge( :Endpoint => endpoint ) )
95
+ end
96
+
97
+ def getSOAPTransport(args)
98
+ wsdl = findWSDL( args[:Name], args[:Host], args[:Version], args[:UseSSL] )
99
+ endpoint = findSOAPEndpoint( args[:Name], args[:Host], args[:UseSSL] )
100
+ require 'amazon/webservices/util/soap_transport.rb'
101
+ Amazon::WebServices::Util::SOAPTransport.new( args.merge( :Wsdl => wsdl, :Endpoint => endpoint ) )
102
+ end
103
+
104
+ def findWSDL( name, host, version, ssl )
105
+ if version.nil?
106
+ "#{ssl ? 'https' : 'http'}://#{host}/AWSMechanicalTurk/#{name}.wsdl"
107
+ else
108
+ "#{ssl ? 'https' : 'http'}://#{host}/AWSMechanicalTurk/#{version}/#{name}.wsdl"
109
+ end
110
+ end
111
+
112
+ def findSOAPEndpoint( name, host, ssl )
113
+ "#{ssl ? 'https' : 'http'}://#{host}/onca/soap?Service=#{name}"
114
+ end
115
+
116
+ def findRestEndpoint( name, host, ssl )
117
+ "#{ssl ? 'https' : 'http'}://#{host}/?Service=#{name}"
118
+ end
119
+
120
+ end # MTurk
121
+
122
+ end # Amazon::WebServices
123
+ end # Amazon
@@ -0,0 +1,274 @@
1
+ # Copyright:: Copyright (c) 2007 Amazon Technologies, Inc.
2
+ # License:: Apache License, Version 2.0
3
+
4
+ require 'erb'
5
+ require 'monitor'
6
+ require 'amazon/util'
7
+ require 'amazon/webservices/util/xml_simplifier'
8
+ require 'amazon/webservices/util/convenience_wrapper'
9
+ require 'amazon/webservices/mechanical_turk'
10
+
11
+ module Amazon
12
+ module WebServices
13
+
14
+ class MechanicalTurkRequester < Amazon::WebServices::Util::ConvenienceWrapper
15
+
16
+ WSDL_VERSION = "2012-03-25"
17
+
18
+ ABANDONMENT_RATE_QUALIFICATION_TYPE_ID = "00000000000000000070";
19
+ APPROVAL_RATE_QUALIFICATION_TYPE_ID = "000000000000000000L0";
20
+ REJECTION_RATE_QUALIFICATION_TYPE_ID = "000000000000000000S0";
21
+ RETURN_RATE_QUALIFICATION_TYPE_ID = "000000000000000000E0";
22
+ SUBMISSION_RATE_QUALIFICATION_TYPE_ID = "00000000000000000000";
23
+ LOCALE_QUALIFICATION_TYPE_ID = "00000000000000000071";
24
+
25
+ DEFAULT_THREADCOUNT = 10
26
+
27
+ serviceCall :RegisterHITType, :RegisterHITTypeResult, {
28
+ :AssignmentDurationInSeconds => 60*60,
29
+ :AutoApprovalDelayInSeconds => 60*60*24*7
30
+ }
31
+ serviceCall :SetHITTypeNotification, :SetHITTypeNotificationResult
32
+
33
+
34
+ serviceCall :CreateHIT, :HIT, { :MaxAssignments => 1,
35
+ :AssignmentDurationInSeconds => 60*60,
36
+ :AutoApprovalDelayInSeconds => 60*60*24*7,
37
+ :LifetimeInSeconds => 60*60*24,
38
+ }
39
+
40
+ serviceCall :DisableHIT, :DisableHITResult
41
+ serviceCall :DisposeHIT, :DisposeHITResult
42
+ serviceCall :ExtendHIT, :ExtendHITResult
43
+ serviceCall :ForceExpireHIT, :ForceExpireHITResult
44
+ serviceCall :GetHIT, :HIT, { :ResponseGroup => %w( Minimal HITDetail HITQuestion HITAssignmentSummary ) }
45
+ serviceCall :ChangeHITTypeOfHIT, :ChangeHITTypeOfHITResult
46
+
47
+ serviceCall :SearchHITs, :SearchHITsResult
48
+ serviceCall :GetReviewableHITs, :GetReviewableHITsResult
49
+ serviceCall :SetHITAsReviewing, :SetHITAsReviewingResult
50
+ serviceCall :GetAssignmentsForHIT, :GetAssignmentsForHITResult
51
+ serviceCall :GetReviewResultsForHIT, :GetReviewResultsForHITResult
52
+
53
+ paginate :SearchHITs, :HIT
54
+ paginate :GetReviewableHITs, :HIT
55
+ paginate :GetAssignmentsForHIT, :Assignment
56
+
57
+ serviceCall :GetAssignment, :GetAssignmentResult
58
+ serviceCall :ApproveAssignment, :ApproveAssignmentResult
59
+ serviceCall :RejectAssignment, :RejectAssignmentResult
60
+ serviceCall :ApproveRejectedAssignment, :ApproveRejectedAssignmentResult
61
+
62
+ serviceCall :GrantBonus, :GrantBonusResult
63
+ serviceCall :GetBonusPayments, :GetBonusPaymentsResult
64
+
65
+ serviceCall :CreateQualificationType, :QualificationType, { :QualificationTypeStatus => 'Active' }
66
+ serviceCall :GetQualificationType, :QualificationType
67
+ serviceCall :SearchQualificationTypes, :SearchQualificationTypesResult, { :MustBeRequestable => true }
68
+ serviceCall :UpdateQualificationType, :QualificationType
69
+ serviceCall :GetQualificationsForQualificationType, :GetQualificationsForQualificationTypeResult, { :Status => 'Granted' }
70
+ serviceCall :GetHITsForQualificationType, :GetHITsForQualificationTypeResult
71
+ serviceCall :DisposeQualficationType, :DisposeQualificationTypeResult
72
+
73
+ paginate :SearchQualificationTypes, :QualificationType
74
+ paginate :GetQualificationsForQualificationType, :Qualification
75
+
76
+ serviceCall :AssignQualification, :AssignQualificationResult
77
+ serviceCall :GetQualificationRequests, :GetQualificationRequestsResult
78
+ serviceCall :GrantQualification, :GrantQualificationResult
79
+ serviceCall :RejectQualificationRequest, :RejectQualificationRequestResult
80
+ serviceCall :GetQualificationScore, :Qualification
81
+ serviceCall :UpdateQualificationScore, :UpdateQualificationScoreResult
82
+ serviceCall :RevokeQualification, :RevokeQualificationResult
83
+
84
+ paginate :GetQualificationRequests, :QualificationRequest
85
+
86
+ serviceCall :GetBlockedWorkers, :GetBlockedWorkersResult
87
+ serviceCall :BlockWorker, :BlockWorkerResult
88
+ serviceCall :UnblockWorker, :UnblockWorkerResult
89
+
90
+ paginate :GetBlockedWorkers, :WorkerBlock
91
+
92
+ serviceCall :GetFileUploadURL, :GetFileUploadURLResult
93
+ serviceCall :GetAccountBalance, :GetAccountBalanceResult
94
+ serviceCall :GetRequesterStatistic, :GetStatisticResult, { :Count => 1 }
95
+ serviceCall :GetRequesterWorkerStatistic, :GetStatisticResult, { :Count => 1 }
96
+
97
+ serviceCall :NotifyWorkers, :NotifyWorkersResult
98
+
99
+ def initialize(args={})
100
+ newargs = args.dup
101
+ unless args[:Config].nil?
102
+ if args[:Config] == :Rails
103
+ rails_config = Amazon::Util::DataReader.load( File.join(::RAILS_ROOT,'config','mturk.yml'), :YAML )
104
+ newargs = args.merge rails_config[::RAILS_ENV].inject({}) {|a,b| a[b[0].to_sym] = b[1] ; a }
105
+ else
106
+ loaded = Amazon::Util::DataReader.load( args[:Config], :YAML )
107
+ newargs = args.merge loaded.inject({}) {|a,b| a[b[0].to_sym] = b[1] ; a }
108
+ end
109
+ end
110
+ @threadcount = args[:ThreadCount].to_i
111
+ @threadcount = DEFAULT_THREADCOUNT unless @threadcount >= 1
112
+ raise "Cannot override WSDL version ( #{WSDL_VERSION} )" unless args[:Version].nil? or args[:Version].equals? WSDL_VERSION
113
+ super newargs.merge( :Name => :AWSMechanicalTurkRequester,
114
+ :ServiceClass => Amazon::WebServices::MechanicalTurk,
115
+ :Version => WSDL_VERSION )
116
+ end
117
+
118
+ # Create a series of similar HITs, sharing common parameters. Utilizes HITType
119
+ # * hit_template is the array of parameters to pass to createHIT.
120
+ # * question_template will be passed as a template into ERB to generate the :Question parameter
121
+ # * the RequesterAnnotation parameter of hit_template will also be passed through ERB
122
+ # * hit_data_set should consist of an array of hashes defining unique instance variables utilized by question_template
123
+ def createHITs( hit_template, question_template, hit_data_set )
124
+ hit_template = hit_template.dup
125
+ lifetime = hit_template[:LifetimeInSeconds]
126
+ numassignments_template = hit_template[:MaxAssignments]
127
+ annotation_template = hit_template[:RequesterAnnotation]
128
+ hit_template.delete :LifetimeInSeconds
129
+ hit_template.delete :MaxAssignments
130
+ hit_template.delete :RequesterAnnotation
131
+
132
+ ht = hit_template[:HITTypeId] || registerHITType( hit_template )[:HITTypeId]
133
+
134
+ tp = Amazon::Util::ThreadPool.new @threadcount
135
+
136
+ created = [].extend(MonitorMixin)
137
+ failed = [].extend(MonitorMixin)
138
+ hit_data_set.each do |hd|
139
+ tp.addWork(hd) do |hit_data|
140
+ begin
141
+ b = Amazon::Util::Binder.new( hit_data )
142
+ annotation = annotation_template.nil? ? nil : b.erb_eval( annotation_template )
143
+ numassignments = numassignments_template.nil? ? nil : b.erb_eval( numassignments_template.to_s ).to_i
144
+ question = b.erb_eval( question_template )
145
+ result = self.createHIT( :HITTypeId => ht,
146
+ :LifetimeInSeconds => lifetime,
147
+ :MaxAssignments => ( hit_data[:MaxAssignments] || numassignments || 1 ),
148
+ :Question => question,
149
+ :RequesterAnnotation => ( hit_data[:RequesterAnnotation] || annotation || "")
150
+ )
151
+ created.synchronize do
152
+ created << result
153
+ end
154
+ rescue => e
155
+ failed.synchronize do
156
+ failed << hit_data.merge( :Error => e.message )
157
+ end
158
+ end
159
+ end # tp.addWork
160
+ end # hit_data_set.each
161
+ tp.finish
162
+
163
+ return :Created => created, :Failed => failed
164
+ end
165
+
166
+ # Update a series of HITs to belong to a new HITType
167
+ # * hit_template is the array of parameters to pass to registerHITType
168
+ # * hit_ids is a list of HITIds (strings)
169
+ def updateHITs( hit_template, hit_ids )
170
+ hit_template = hit_template.dup
171
+ hit_template.delete :LifetimeInSeconds
172
+ hit_template.delete :RequesterAnnotation
173
+
174
+ hit_type_id = registerHITType( hit_template )[:HITTypeId]
175
+
176
+ tp = Amazon::Util::ThreadPool.new @threadcount
177
+
178
+ updated = [].extend(MonitorMixin)
179
+ failed = [].extend(MonitorMixin)
180
+ hit_ids.each do |hid|
181
+ tp.addWork(hid) do |hit_id|
182
+ begin
183
+ changeHITTypeOfHIT( :HITId => hit_id, :HITTypeId => hit_type_id )
184
+ updated.synchronize do
185
+ updated << hit_id
186
+ end
187
+ rescue => e
188
+ failed.synchronize do
189
+ failed << { :HITId => hit_id, :Error => e.message }
190
+ end
191
+ end
192
+ end # tp.addWork
193
+ end # hit_ids.each
194
+ tp.finish
195
+
196
+ return :Updated => updated, :Failed => failed
197
+ end
198
+
199
+
200
+ # Update a HIT with new properties.
201
+ # hit_id:: Id of the HIT to update
202
+ # hit_template:: hash ( parameter => value ) of parameters to update
203
+ #
204
+ # Acceptable attributes:
205
+ # * Title
206
+ # * Description
207
+ # * Keywords
208
+ # * Reward
209
+ # * QualificationRequirement
210
+ # * AutoApprovalDelayInSeconds
211
+ # * AssignmentDurationInSeconds
212
+ #
213
+ # Behind the scenes, this function retrieves the HIT, merges the HITs
214
+ # current attributes with any you specify, and registers a new HIT
215
+ # Template. It then uses the new ChangeHITTypeOfHIT function to move
216
+ # your HIT to the newly-created HIT Template.
217
+ def updateHIT( hit_id, hit_template )
218
+ hit_template = hit_template.dup
219
+
220
+ hit = getHIT( :HITId => hit_id )
221
+
222
+ props = %w( Title Description Keywords Reward QualificationRequirement
223
+ AutoApprovalDelayInSeconds AssignmentDurationInSeconds
224
+ ).collect {|str| str.to_sym }
225
+
226
+ props.each do |p|
227
+ hit_template[p] = hit[p] if hit_template[p].nil?
228
+ end
229
+
230
+ hit_type_id = registerHITType( hit_template )[:HITTypeId]
231
+
232
+ changeHITTypeOfHIT( :HITId => hit_id, :HITTypeId => hit_type_id )
233
+ end
234
+
235
+
236
+ def getHITResults( list )
237
+ results = [].extend(MonitorMixin)
238
+ tp = Amazon::Util::ThreadPool.new @threadcount
239
+ list.each do |line|
240
+ tp.addWork(line) do |h|
241
+ hit = getHIT( :HITId => h[:HITId] )
242
+ getAssignmentsForHITAll( :HITId => h[:HITId] ).each {|assignment|
243
+ results.synchronize do
244
+ results << ( hit.merge( assignment ) )
245
+ end
246
+ }
247
+ end
248
+ end
249
+ tp.finish
250
+ results.flatten
251
+ end
252
+
253
+ # Returns available funds in USD
254
+ # Calls getAccountBalance and parses out the correct amount
255
+ def availableFunds
256
+ return getAccountBalance[:AvailableBalance][:Amount]
257
+ end
258
+
259
+ # helper function to simplify answer XML
260
+ def simplifyAnswer( answerXML )
261
+ answerHash = Amazon::WebServices::Util::XMLSimplifier.simplify REXML::Document.new(answerXML)
262
+ list = [answerHash[:Answer]].flatten
263
+ list.inject({}) { |answers, answer|
264
+ id = answer[:QuestionIdentifier]
265
+ result = answer[:FreeText] || answer[:SelectionIdentifier] || answer[:UploadedFileKey]
266
+ answers[id] = result
267
+ answers
268
+ }
269
+ end
270
+
271
+ end # MechanicalTurkRequester
272
+
273
+ end # Amazon::WebServices
274
+ end # Amazon
@@ -0,0 +1,150 @@
1
+ # Copyright:: Copyright (c) 2007 Amazon Technologies, Inc.
2
+ # License:: Apache License, Version 2.0
3
+
4
+ require 'amazon/util/logging'
5
+ require 'amazon/webservices/util/validation_exception'
6
+ require 'amazon/webservices/util/unknown_result_exception'
7
+
8
+ module Amazon
9
+ module WebServices
10
+ module MTurk
11
+
12
+ class MechanicalTurkErrorHandler
13
+ include Amazon::Util::Logging
14
+
15
+ REQUIRED_PARAMETERS = [:Relay]
16
+
17
+ # Commands with these prefixes can be retried if we are unsure of success
18
+ RETRY_PRE = %w( search get register update disable assign set dispose )
19
+
20
+ # Max number of times to retry a call
21
+ MAX_RETRY = 6
22
+
23
+ # Base used in Exponential Backoff retry delay
24
+ BACKOFF_BASE = 2
25
+ # Scale factor for Exponential Backoff retry delay
26
+ BACKOFF_INITIAL = 0.1
27
+
28
+ # Matching pattern to find a 'Results' element in the Response
29
+ RESULT_PATTERN = /Result/
30
+ # Additional elements to be considered a 'Result' despite not matching RESULT_PATTERN
31
+ ACCEPTABLE_RESULTS = %w( HIT Qualification QualificationType QualificationRequest Information )
32
+
33
+ def initialize( args )
34
+ missing_parameters = REQUIRED_PARAMETERS - args.keys
35
+ raise "Missing paramters: #{missing_parameters.join(',')}" unless missing_parameters.empty?
36
+ @relay = args[:Relay]
37
+ end
38
+
39
+ def dispatch(method, *args)
40
+ try = 0
41
+ begin
42
+ try += 1
43
+ log "Dispatching call to #{method} (try #{try})"
44
+ response = @relay.send(method,*args)
45
+ validateResponse( response )
46
+ return response
47
+ rescue Exception => error
48
+ case handleError( error,method )
49
+ when :RetryWithBackoff
50
+ retry if doBackoff( try )
51
+ when :RetryImmediate
52
+ retry if canRetry( try )
53
+ when :Ignore
54
+ return :IgnoredError => error
55
+ when :Unknown
56
+ raise Util::UnknownResultException.new( error, method, args )
57
+ when :Fail
58
+ raise error
59
+ else
60
+ raise "Unknown error handling method: #{handleError( error,method )}"
61
+ end
62
+ raise error
63
+ end
64
+ end
65
+
66
+ def methodRetryable( method )
67
+ RETRY_PRE.each do |pre|
68
+ return true if method.to_s =~ /^#{pre}/i
69
+ end
70
+ return false
71
+ end
72
+
73
+ def handleError( error, method )
74
+ log "Handling error: #{error.inspect}"
75
+ case error.class.to_s
76
+ when 'Timeout::Error','SOAP::HTTPStreamError'
77
+ if methodRetryable( method )
78
+ return :RetryImmediate
79
+ else
80
+ return :Unknown
81
+ end
82
+ when 'SOAP::FaultError'
83
+ case error.faultcode.data
84
+ when "aws:Server.ServiceUnavailable"
85
+ return :RetryWithBackoff
86
+ else
87
+ return :Unknown
88
+ end
89
+ when 'Amazon::WebServices::Util::ValidationException'
90
+ case error.message
91
+ when 'AWS.ServiceUnavailable'
92
+ return :RetryWithBackoff if methodRetryable( method )
93
+ end
94
+ return :Fail
95
+ when 'RuntimeError'
96
+ case error.message
97
+ when 'Throttled'
98
+ return :RetryWithBackoff
99
+ else
100
+ return :RetryImmediate
101
+ end
102
+ else
103
+ return :Unknown
104
+ end
105
+ end
106
+
107
+ def canRetry( try )
108
+ try <= MAX_RETRY
109
+ end
110
+
111
+ def doBackoff( try )
112
+ return false unless canRetry(try)
113
+ delay = BACKOFF_INITIAL * ( BACKOFF_BASE ** try )
114
+ sleep delay
115
+ return true
116
+ end
117
+
118
+ def isResultTag( tag )
119
+ tag.to_s =~ RESULT_PATTERN or ACCEPTABLE_RESULTS.include?( tag.to_s )
120
+ end
121
+
122
+ def validateResponse(response)
123
+ log "Validating response: #{response.inspect}"
124
+ if response[:Errors] and response[:Errors][:Error]
125
+ if response[:Errors][:Error][:Code] == "ServiceUnavailable"
126
+ raise 'Throttled'
127
+ else
128
+ raise Util::ValidationException.new(response)
129
+ end
130
+ end
131
+ if response[:OperationRequest] and response[:OperationRequest][:Errors]
132
+ raise Util::ValidationException.new(response)
133
+ end
134
+ resultTags = response.keys.find_all {|r| isResultTag( r ) }
135
+ raise Util::ValidationException.new(response, "Didn't get back an acceptable result tag (got back #{response.keys.join(',')})") if resultTags.empty?
136
+ resultTags.each do |resultTag|
137
+ log "using result tag <#{resultTag}>"
138
+ result = response[resultTag]
139
+ if result[:Request] and result[:Request][:Errors]
140
+ raise Util::ValidationException.new(result)
141
+ end
142
+ end
143
+ response
144
+ end
145
+
146
+ end # MechanicalTurkErrorHandler
147
+
148
+ end # Amazon::WebServices::MTurk
149
+ end # Amazon::WebServices
150
+ end # Amazon