ruby-aws 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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