papercavalier-ruby-aaws 0.8.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 (59) hide show
  1. data/.gitignore +3 -0
  2. data/COPYING +340 -0
  3. data/INSTALL +260 -0
  4. data/NEWS +808 -0
  5. data/README +679 -0
  6. data/README.rdoc +140 -0
  7. data/Rakefile +17 -0
  8. data/VERSION.yml +5 -0
  9. data/example/batch_operation +28 -0
  10. data/example/browse_node_lookup1 +46 -0
  11. data/example/customer_content_lookup1 +27 -0
  12. data/example/customer_content_search1 +21 -0
  13. data/example/example1 +78 -0
  14. data/example/help1 +24 -0
  15. data/example/item_lookup1 +56 -0
  16. data/example/item_lookup2 +56 -0
  17. data/example/item_search1 +30 -0
  18. data/example/item_search2 +37 -0
  19. data/example/item_search3 +23 -0
  20. data/example/list_lookup1 +29 -0
  21. data/example/list_search1 +30 -0
  22. data/example/multiple_operation1 +69 -0
  23. data/example/seller_listing_lookup1 +30 -0
  24. data/example/seller_listing_search1 +29 -0
  25. data/example/seller_lookup1 +45 -0
  26. data/example/shopping_cart1 +42 -0
  27. data/example/similarity_lookup1 +48 -0
  28. data/example/tag_lookup1 +34 -0
  29. data/example/transaction_lookup1 +25 -0
  30. data/example/vehicle_search +22 -0
  31. data/lib/amazon.rb +165 -0
  32. data/lib/amazon/aws.rb +1493 -0
  33. data/lib/amazon/aws/cache.rb +141 -0
  34. data/lib/amazon/aws/search.rb +464 -0
  35. data/lib/amazon/aws/shoppingcart.rb +537 -0
  36. data/lib/amazon/locale.rb +102 -0
  37. data/test/setup.rb +56 -0
  38. data/test/tc_amazon.rb +20 -0
  39. data/test/tc_aws.rb +160 -0
  40. data/test/tc_browse_node_lookup.rb +49 -0
  41. data/test/tc_customer_content_lookup.rb +49 -0
  42. data/test/tc_help.rb +44 -0
  43. data/test/tc_item_lookup.rb +47 -0
  44. data/test/tc_item_search.rb +105 -0
  45. data/test/tc_list_lookup.rb +60 -0
  46. data/test/tc_list_search.rb +44 -0
  47. data/test/tc_multiple_operation.rb +375 -0
  48. data/test/tc_operation_request.rb +64 -0
  49. data/test/tc_seller_listing_lookup.rb +47 -0
  50. data/test/tc_seller_listing_search.rb +55 -0
  51. data/test/tc_seller_lookup.rb +44 -0
  52. data/test/tc_serialisation.rb +107 -0
  53. data/test/tc_shopping_cart.rb +214 -0
  54. data/test/tc_similarity_lookup.rb +48 -0
  55. data/test/tc_tag_lookup.rb +24 -0
  56. data/test/tc_transaction_lookup.rb +24 -0
  57. data/test/tc_vehicle_operations.rb +118 -0
  58. data/test/ts_aws.rb +24 -0
  59. metadata +141 -0
@@ -0,0 +1,141 @@
1
+ # $Id: cache.rb,v 1.8 2008/06/10 06:33:46 ianmacd Exp $
2
+ #
3
+
4
+ module Amazon
5
+
6
+ module AWS
7
+
8
+ # This class provides a simple results caching system for operations
9
+ # performed by AWS.
10
+ #
11
+ # To use it, set _cache_ to *true* in either <tt>/etc/amazonrc</tt> or
12
+ # <tt>~/.amazonrc</tt>.
13
+ #
14
+ # By default, the cache directory used is <tt>/tmp/amazon</tt>, but this
15
+ # can be changed by defining _cache_dir_ in either <tt>/etc/amazonrc</tt>
16
+ # or <tt>~/.amazonrc</tt>.
17
+ #
18
+ # When a cache is used, Ruby/AWS will check the cache directory for a
19
+ # recent copy of a response to the exact operation that you are
20
+ # performing. If found, the cached response will be returned instead of
21
+ # the request being forwarded to the AWS servers for processing. If no
22
+ # (recent) copy is found, the request will be forwarded to the AWS servers
23
+ # as usual. Recency is defined here as less than 24 hours old.
24
+ #
25
+ class Cache
26
+
27
+ require 'fileutils'
28
+
29
+ begin
30
+ require 'md5'
31
+ rescue LoadError
32
+ # Ruby 1.9 has moved MD5.
33
+ #
34
+ require 'digest/md5'
35
+ end
36
+
37
+ # Exception class for bad cache paths.
38
+ #
39
+ class PathError < StandardError; end
40
+
41
+ # Length of one day in seconds
42
+ #
43
+ ONE_DAY = 86400 # :nodoc:
44
+
45
+ # Age in days below which to consider cache files valid.
46
+ #
47
+ MAX_AGE = 1.0
48
+
49
+ # Default cache location.
50
+ #
51
+ DEFAULT_CACHE_DIR = '/tmp/amazon'
52
+
53
+ attr_reader :path
54
+
55
+ def initialize(path=DEFAULT_CACHE_DIR)
56
+ path ||= DEFAULT_CACHE_DIR
57
+
58
+ ::FileUtils::mkdir_p( path ) unless File.exists? path
59
+
60
+ unless File.directory? path
61
+ raise PathError, "cache path #{path} is not a directory"
62
+ end
63
+
64
+ unless File.readable? path
65
+ raise PathError, "cache path #{path} is not readable"
66
+ end
67
+
68
+ unless File.writable? path
69
+ raise PathError, "cache path #{path} is not writable"
70
+ end
71
+
72
+ @path = path
73
+ end
74
+
75
+
76
+ # Determine whether or not the the response to a given URL is cached.
77
+ # Returns *true* or *false*.
78
+ #
79
+ def cached?(url)
80
+ digest = Digest::MD5.hexdigest( url )
81
+
82
+ cache_files = Dir.glob( File.join( @path, '*' ) ).map do |d|
83
+ File.basename( d )
84
+ end
85
+
86
+ return cache_files.include?( digest ) &&
87
+ ( Time.now - File.mtime( File.join( @path, digest ) ) ) /
88
+ ONE_DAY <= MAX_AGE
89
+ end
90
+
91
+
92
+ # Retrieve the cached response associated with _url_.
93
+ #
94
+ def fetch(url)
95
+ digest = Digest::MD5.hexdigest( url )
96
+ cache_file = File.join( @path, digest )
97
+
98
+ return nil unless File.exist? cache_file
99
+
100
+ Amazon.dprintf( 'Fetching %s from cache...', digest )
101
+ File.open( File.join( cache_file ) ).readlines.to_s
102
+ end
103
+
104
+
105
+ # Cache the data from _contents_ and associate it with _url_.
106
+ #
107
+ def store(url, contents)
108
+ digest = Digest::MD5.hexdigest( url )
109
+ cache_file = File.join( @path, digest )
110
+
111
+ Amazon.dprintf( 'Caching %s...', digest )
112
+ File.open( cache_file, 'w' ) { |f| f.puts contents }
113
+ end
114
+
115
+
116
+ # This method flushes all files from the cache directory specified
117
+ # in the object's <i>@path</i> variable.
118
+ #
119
+ def flush_all
120
+ FileUtils.rm Dir.glob( File.join( @path, '*' ) )
121
+ end
122
+
123
+
124
+ # This method flushes expired files from the cache directory specified
125
+ # in the object's <i>@path</i> variable.
126
+ #
127
+ def flush_expired
128
+ now = Time.now
129
+
130
+ expired_files = Dir.glob( File.join( @path, '*' ) ).find_all do |f|
131
+ ( now - File.mtime( f ) ) / ONE_DAY > MAX_AGE
132
+ end
133
+
134
+ FileUtils.rm expired_files
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -0,0 +1,464 @@
1
+ # $Id: search.rb,v 1.49 2010/03/19 19:28:19 ianmacd Exp $
2
+ #
3
+
4
+ module Amazon
5
+
6
+ module AWS
7
+
8
+ require 'amazon/aws'
9
+ require 'net/http'
10
+ require 'rexml/document'
11
+ require 'openssl'
12
+
13
+ # Load this library with:
14
+ #
15
+ # require 'amazon/aws/search'
16
+ #
17
+ module Search
18
+
19
+ class Request
20
+
21
+ include REXML
22
+
23
+ # Exception class for bad access key ID.
24
+ #
25
+ class AccessKeyIdError < Amazon::AWS::Error::AWSError; end
26
+
27
+ # Exception class for bad locales.
28
+ #
29
+ class LocaleError < Amazon::AWS::Error::AWSError; end
30
+
31
+ # Do we have support for the SHA-256 Secure Hash Algorithm?
32
+ #
33
+ # Note that Module#constants returns Strings in Ruby 1.8 and Symbols
34
+ # in 1.9.
35
+ #
36
+ DIGEST_SUPPORT = OpenSSL::Digest.constants.include?( 'SHA256' ) ||
37
+ OpenSSL::Digest.constants.include?( :SHA256 )
38
+
39
+ # Requests are authenticated using the SHA-256 Secure Hash Algorithm.
40
+ #
41
+ DIGEST = OpenSSL::Digest::Digest.new( 'sha256' ) if DIGEST_SUPPORT
42
+
43
+ attr_reader :conn, :config, :locale, :query, :user_agent
44
+ attr_writer :cache
45
+ attr_accessor :encoding
46
+
47
+ # This method is used to generate an AWS search request object.
48
+ #
49
+ # _key_id_ is your AWS {access key
50
+ # ID}[https://aws-portal.amazon.com/gp/aws/developer/registration/index.html].
51
+ # Note that your secret key, used for signing requests, can be
52
+ # specified only in your <tt>~/.amazonrc</tt> configuration file.
53
+ #
54
+ # _associate_ is your
55
+ # Associates[http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/GSG/BecominganAssociate.html]
56
+ # tag (if any), _locale_ is the locale in which you which to work
57
+ # (*us* for amazon.com[http://www.amazon.com/], *uk* for
58
+ # amazon.co.uk[http://www.amazon.co.uk], etc.), _cache_ is whether or
59
+ # not you wish to utilise a response cache, and _user_agent_ is the
60
+ # client name to pass when performing calls to AWS. By default,
61
+ # _user_agent_ will be set to a string identifying the Ruby/AWS
62
+ # library and its version number.
63
+ #
64
+ # _locale_ and _cache_ can also be set later, if you wish to change
65
+ # the current behaviour.
66
+ #
67
+ # Example:
68
+ #
69
+ # req = Request.new( '0Y44V8FAFNM119CX4TR2', 'calibanorg-20' )
70
+ #
71
+ def initialize(key_id=nil, associate=nil, locale=nil, cache=nil,
72
+ user_agent=USER_AGENT)
73
+
74
+ @config ||= Amazon::Config.new
75
+
76
+ def_locale = locale
77
+ locale = 'us' unless locale
78
+ locale.downcase!
79
+
80
+ key_id ||= @config['key_id']
81
+ cache = @config['cache'] if cache.nil?
82
+
83
+ # Take locale from config file if no locale was passed to method.
84
+ #
85
+ if @config.key?( 'locale' ) && ! def_locale
86
+ locale = @config['locale']
87
+ end
88
+ validate_locale( locale )
89
+
90
+ if key_id.nil?
91
+ raise AccessKeyIdError, 'key_id may not be nil'
92
+ end
93
+
94
+ @key_id = key_id
95
+ @tag = associate || @config['associate'] || DEF_ASSOC[locale]
96
+ @user_agent = user_agent
97
+ @cache = unless cache == 'false' || cache == false
98
+ Amazon::AWS::Cache.new( @config['cache_dir'] )
99
+ else
100
+ nil
101
+ end
102
+
103
+ # Set the following two variables from the config file. Will be
104
+ # *nil* if not present in config file.
105
+ #
106
+ @api = @config['api']
107
+ @encoding = @config['encoding']
108
+
109
+ self.locale = locale
110
+ end
111
+
112
+
113
+ # Assign a new locale. If the locale we're coming from is using the
114
+ # default Associate ID for that locale, then we use the new locale's
115
+ # default ID, too.
116
+ #
117
+ def locale=(l) # :nodoc:
118
+ old_locale = @locale ||= nil
119
+ @locale = validate_locale( l )
120
+
121
+ # Use the new locale's default ID if the ID currently in use is the
122
+ # current locale's default ID.
123
+ #
124
+ if @tag == Amazon::AWS::DEF_ASSOC[old_locale]
125
+ @tag = Amazon::AWS::DEF_ASSOC[@locale]
126
+ end
127
+
128
+ if @config.key?( @locale ) && @config[@locale].key?( 'associate' )
129
+ @tag = @config[@locale]['associate']
130
+ end
131
+
132
+ # We must now set up a new HTTP connection to the correct server for
133
+ # this locale, unless the same server is used for both.
134
+ #
135
+ unless Amazon::AWS::ENDPOINT[@locale] ==
136
+ Amazon::AWS::ENDPOINT[old_locale]
137
+ #connect( @locale )
138
+ @conn = nil
139
+ end
140
+ end
141
+
142
+
143
+ # If @cache has simply been assigned *true* at some point in time,
144
+ # assign a proper cache object to it when it is referenced. Otherwise,
145
+ # just return its value.
146
+ #
147
+ def cache # :nodoc:
148
+ if @cache == true
149
+ @cache = Amazon::AWS::Cache.new( @config['cache_dir'] )
150
+ else
151
+ @cache
152
+ end
153
+ end
154
+
155
+
156
+ # Verify the validity of a locale string. _l_ is the locale string.
157
+ #
158
+ def validate_locale(l)
159
+ unless Amazon::AWS::ENDPOINT.has_key? l
160
+ raise LocaleError, "invalid locale: #{l}"
161
+ end
162
+ l
163
+ end
164
+ private :validate_locale
165
+
166
+
167
+ # Return an HTTP connection for the current _locale_.
168
+ #
169
+ def connect(locale)
170
+ if ENV.key? 'http_proxy'
171
+ uri = URI.parse( ENV['http_proxy'] )
172
+ proxy_user = proxy_pass = nil
173
+ proxy_user, proxy_pass = uri.userinfo.split( /:/ ) if uri.userinfo
174
+ @conn = Net::HTTP::Proxy( uri.host, uri.port, proxy_user,
175
+ proxy_pass ).start(
176
+ Amazon::AWS::ENDPOINT[locale].host )
177
+ else
178
+ @conn = Net::HTTP::start( Amazon::AWS::ENDPOINT[locale].host )
179
+ end
180
+ end
181
+ private :connect
182
+
183
+
184
+ # Reconnect to the server if our connection has been lost (due to a
185
+ # time-out, etc.).
186
+ #
187
+ def reconnect # :nodoc:
188
+ connect( self.locale )
189
+ self
190
+ end
191
+
192
+
193
+ # This method checks for errors in an XML response returned by AWS.
194
+ # _xml_ is the XML node below which to search.
195
+ #
196
+ def error_check(xml)
197
+ if ! xml.nil? && xml = xml.elements['Errors/Error']
198
+ raise Amazon::AWS::Error.exception( xml )
199
+ end
200
+ end
201
+ private :error_check
202
+
203
+
204
+ # Add a timestamp to a request object's query string.
205
+ #
206
+ def timestamp # :nodoc:
207
+ @query << '&Timestamp=%s' %
208
+ [ Amazon.url_encode(
209
+ Time.now.utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) ) ]
210
+ end
211
+ private :timestamp
212
+
213
+
214
+ # Add a signature to a request object's query string. This implicitly
215
+ # also adds a timestamp.
216
+ #
217
+ def sign # :nodoc:
218
+ return false unless DIGEST_SUPPORT
219
+
220
+ timestamp
221
+ params = @query[1..-1].split( '&' ).sort.join( '&' )
222
+
223
+ sign_str = "GET\n%s\n%s\n%s" % [ ENDPOINT[@locale].host,
224
+ ENDPOINT[@locale].path,
225
+ params ]
226
+
227
+ Amazon.dprintf( 'Calculating SHA256 HMAC of "%s"...', sign_str )
228
+
229
+ hmac = OpenSSL::HMAC.digest( DIGEST,
230
+ @config['secret_key_id'],
231
+ sign_str )
232
+ Amazon.dprintf( 'SHA256 HMAC is "%s"', hmac.inspect )
233
+
234
+ base64_hmac = [ hmac ].pack( 'm' ).chomp
235
+ Amazon.dprintf( 'Base64-encoded HMAC is "%s".', base64_hmac )
236
+
237
+ signature = Amazon.url_encode( base64_hmac )
238
+
239
+ params << '&Signature=%s' % [ signature ]
240
+ @query = '?' + params
241
+
242
+ true
243
+ end
244
+
245
+
246
+ # Perform a search of the AWS database, returning an AWSObject.
247
+ #
248
+ # _operation_ is an object of a subclass of _Operation_, such as
249
+ # _ItemSearch_, _ItemLookup_, etc. It may also be a _MultipleOperation_
250
+ # object.
251
+ #
252
+ # In versions of Ruby/AWS up to prior to 0.8.0, the second parameter to
253
+ # this method was _response_group_. This way of passing response
254
+ # groups has been deprecated since 0.7.0 and completely removed in
255
+ # 0.8.0. To pair a set of response groups with an operation, assign
256
+ # directly to the operation's @response_group attribute.
257
+ #
258
+ # _nr_pages_ is the number of results pages to return. It defaults to
259
+ # <b>1</b>. If a higher number is given, pages 1 to _nr_pages_ will be
260
+ # returned. If the special value <b>:ALL_PAGES</b> is given, all
261
+ # results pages will be returned.
262
+ #
263
+ # Note that _ItemLookup_ operations can use several different
264
+ # pagination parameters. An _ItemLookup_ will typically return just
265
+ # one results page containing a single product, but <b>:ALL_PAGES</b>
266
+ # can still be used to apply the _OfferPage_ parameter to paginate
267
+ # through multiple pages of offers.
268
+ #
269
+ # Similarly, a single product may have multiple pages of reviews
270
+ # available. In such a case, it is up to the user to manually supply
271
+ # the _ReviewPage_ parameter and an appropriate value.
272
+ #
273
+ # In the same vein, variations can be returned by using the
274
+ # _VariationPage_ parameter.
275
+ #
276
+ # The pagination parameters supported by each type of operation,
277
+ # together with the maximum page number that can be retrieved for each
278
+ # type of data, are # documented in the AWS Developer's Guide:
279
+ #
280
+ # http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/DG/index.html?MaximumNumberofPages.html
281
+ #
282
+ # The pagination parameter used by <b>:ALL_PAGES</b> can be looked up
283
+ # in the Amazon::AWS::PAGINATION hash.
284
+ #
285
+ # If _operation_ is of class _MultipleOperation_, the operations
286
+ # encapsulated within will return only the first page of results,
287
+ # regardless of whether a higher number of pages is requested.
288
+ #
289
+ # If a block is passed to this method, each successive page of results
290
+ # will be yielded to the block.
291
+ #
292
+ def search(operation, nr_pages=1)
293
+ parameters = Amazon::AWS::SERVICE.
294
+ merge( { 'AWSAccessKeyId' => @key_id,
295
+ 'AssociateTag' => @tag } ).
296
+ merge( operation.query_parameters )
297
+
298
+ if nr_pages.is_a? Amazon::AWS::ResponseGroup
299
+ raise ObsolescenceError, 'Request#search method no longer accepts response_group parameter.'
300
+ end
301
+
302
+ # Pre-0.8.0 user code may have passed *nil* as the second parameter,
303
+ # in order to use the @response_group of the operation.
304
+ #
305
+ nr_pages ||= 1
306
+
307
+ # Check to see whether a particular version of the API has been
308
+ # requested. If so, overwrite Version with the new value.
309
+ #
310
+ parameters.merge!( { 'Version' => @api } ) if @api
311
+
312
+ @query = Amazon::AWS.assemble_query( parameters, @encoding )
313
+ page = Amazon::AWS.get_page( self )
314
+
315
+ # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
316
+ #
317
+ page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
318
+
319
+ doc = Document.new( page )
320
+
321
+ # Some errors occur at the very top level of the XML. For example,
322
+ # when no Operation parameter is given. This should not be possible
323
+ # with user code, but occurred during debugging of this library.
324
+ #
325
+ error_check( doc )
326
+
327
+ # Another possible error results in a document containing nothing
328
+ # but <Result>Internal Error</Result>. This occurs when a specific
329
+ # version of the AWS API is requested, in combination with an
330
+ # operation that did not yet exist in that version of the API.
331
+ #
332
+ # For example:
333
+ #
334
+ # http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=foo&Operation=VehicleSearch&Year=2008&ResponseGroup=VehicleMakes&Service=AWSECommerceService&Version=2008-03-03
335
+ #
336
+ if xml = doc.elements['Result']
337
+ raise Amazon::AWS::Error::AWSError, xml.text
338
+ end
339
+
340
+ # Fundamental errors happen at the OperationRequest level. For
341
+ # example, if an invalid AWSAccessKeyId is used.
342
+ #
343
+ error_check( doc.elements['*/OperationRequest'] )
344
+
345
+ # Check for parameter and value errors deeper down, inside Request.
346
+ #
347
+ if operation.kind == 'MultipleOperation'
348
+
349
+ # Everything is a level deeper, because of the
350
+ # <MultiOperationResponse> container.
351
+ #
352
+ # Check for errors in the first operation.
353
+ #
354
+ error_check( doc.elements['*/*/*/Request'] )
355
+
356
+ # Check for errors in the second operation.
357
+ #
358
+ error_check( doc.elements['*/*[3]/*/Request'] )
359
+
360
+ # If second operation is batched, check for errors in its 2nd set
361
+ # of results.
362
+ #
363
+ if batched = doc.elements['*/*[3]/*[2]/Request']
364
+ error_check( batched )
365
+ end
366
+ else
367
+ error_check( doc.elements['*/*/Request'] )
368
+
369
+ # If operation is batched, check for errors in its 2nd set of
370
+ # results.
371
+ #
372
+ if batched = doc.elements['*/*[3]/Request']
373
+ error_check( batched )
374
+ end
375
+ end
376
+
377
+ if doc.elements['*/*[2]/TotalPages']
378
+ total_pages = doc.elements['*/*[2]/TotalPages'].text.to_i
379
+
380
+ # FIXME: ListLookup and MultipleOperation (and possibly others) have
381
+ # TotalPages nested one level deeper. I should take some time to
382
+ # ensure that all operations that can return multiple results pages
383
+ # are covered by either the 'if' above or the 'elsif' here.
384
+ #
385
+ elsif doc.elements['*/*[2]/*[2]/TotalPages']
386
+ total_pages = doc.elements['*/*[2]/*[2]/TotalPages'].text.to_i
387
+ else
388
+ total_pages = 1
389
+ end
390
+
391
+ # Create a root AWS object and walk the XML response tree.
392
+ #
393
+ aws = AWS::AWSObject.new( operation )
394
+ aws.walk( doc )
395
+ result = aws
396
+
397
+ # If only one page has been requested or only one page is available,
398
+ # we can stop here. First yield to the block, if given.
399
+ #
400
+ if nr_pages == 1 || ( tp = total_pages ) == 1
401
+ yield result if block_given?
402
+ return result
403
+ end
404
+
405
+ # Limit the number of pages to the maximum number available.
406
+ #
407
+ nr_pages = tp.to_i if nr_pages == :ALL_PAGES || nr_pages > tp.to_i
408
+
409
+ if PAGINATION.key? operation.kind
410
+ page_parameter = PAGINATION[operation.kind]['parameter']
411
+ max_pages = PAGINATION[operation.kind]['max_page']
412
+ else
413
+ page_parameter = 'ItemPage'
414
+ max_pages = 400
415
+ end
416
+
417
+ # Iterate over pages 2 and higher, but go no higher than MAX_PAGES.
418
+ #
419
+ 2.upto( nr_pages < max_pages ? nr_pages : max_pages ) do |page_nr|
420
+ @query = Amazon::AWS.assemble_query(
421
+ parameters.merge( { page_parameter => page_nr } ),
422
+ @encoding)
423
+ page = Amazon::AWS.get_page( self )
424
+
425
+ # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
426
+ #
427
+ page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
428
+
429
+ doc = Document.new( page )
430
+
431
+ # Check for errors.
432
+ #
433
+ error_check( doc.elements['*/OperationRequest'] )
434
+ error_check( doc.elements['*/*/Request'] )
435
+
436
+ # Create a new AWS object and walk the XML response tree.
437
+ #
438
+ aws = AWS::AWSObject.new( operation )
439
+ aws.walk( doc )
440
+
441
+ # When dealing with multiple pages, we return not just an
442
+ # AWSObject, but an array of them.
443
+ #
444
+ result = [ result ] unless result.is_a? Array
445
+
446
+ # Append the new object to the array.
447
+ #
448
+ result << aws
449
+ end
450
+
451
+ # Yield each object to the block, if given.
452
+ #
453
+ result.each { |r| yield r } if block_given?
454
+
455
+ result
456
+ end
457
+
458
+ end
459
+
460
+ end
461
+
462
+ end
463
+
464
+ end