Skip to content

Commit

Permalink
caching architecture and writes response to filesystem
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Pickett committed Nov 4, 2008
1 parent 6a02c98 commit 02741b5
Show file tree
Hide file tree
Showing 12 changed files with 372 additions and 216 deletions.
5 changes: 5 additions & 0 deletions lib/amazon.rb
@@ -1,2 +1,7 @@
require "amazon/ecs"
require "amazon/element"
require "amazon/response"
require "amazon/cache_factory"

require "amazon/caching_strategy"
require "amazon/configuration_error"
20 changes: 20 additions & 0 deletions lib/amazon/cache_factory.rb
@@ -0,0 +1,20 @@
class Amazon::CacheFactory
def self.cache(request, response, options)
strategy_class_hash[options[:caching_strategy]].cache(request, response, options)
end

def self.validate(options)
#check for a valid caching strategy
unless self.strategy_class_hash.keys.include?(options[:caching_strategy])
raise Amazon::ConfigurationError, "Invalid caching strategy"
end
strategy_class_hash[options[:caching_strategy]].validate(options)
end

private
def self.strategy_class_hash
{
:filesystem => Amazon::CachingStrategy::Filesystem
}
end
end
4 changes: 4 additions & 0 deletions lib/amazon/caching_strategy.rb
@@ -0,0 +1,4 @@
module CachingStrategy
require File.join(File.dirname(__FILE__), "caching_strategy/base")
require File.join(File.dirname(__FILE__), "caching_strategy/filesystem")
end
11 changes: 11 additions & 0 deletions lib/amazon/caching_strategy/base.rb
@@ -0,0 +1,11 @@
module Amazon::CachingStrategy
class Base
def self.cache(request, response, options)
raise "This method must be overwritten by a caching strategy"
end

def self.validate(options)
raise "This method must be overwritten by a caching strategy"
end
end
end
23 changes: 23 additions & 0 deletions lib/amazon/caching_strategy/filesystem.rb
@@ -0,0 +1,23 @@
module Amazon::CachingStrategy
class Filesystem < Amazon::CachingStrategy::Base
def self.cache(request, response, options)
path = options[:caching_options][:cache_path]
cached_filename = Digest::SHA1.hexdigest(response.request_url)

cached_file = File.open(File.join(path, cached_filename), "w")
cached_file.puts response.doc.to_s
cached_file.close
end

def self.validate(options)
#check for required options
if options[:caching_options].nil?
raise Amazon::ConfigurationError, "You must specify caching options for filesystem caching: :cache_path is required"
end

if options[:caching_options][:cache_path].nil? || !File.directory?(options[:caching_options][:cache_path])
raise Amazon::ConfigurationError, "You must specify a cache path for filesystem caching"
end
end
end
end
198 changes: 18 additions & 180 deletions lib/amazon/ecs.rb
Expand Up @@ -2,6 +2,12 @@
require "hpricot"
require "cgi"

begin
require 'md5'
rescue LoadError
require 'digest/md5'
end

#--
# Copyright (c) 2006 Herryanto Siatono, Pluit Solutions
#
Expand All @@ -26,6 +32,7 @@
#++
module Amazon
class Ecs

SERVICE_URLS = {:us => "http://webservices.amazon.com/onca/xml?Service=AWSECommerceService",
:uk => "http://webservices.amazon.co.uk/onca/xml?Service=AWSECommerceService",
:ca => "http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService",
Expand Down Expand Up @@ -173,19 +180,7 @@ def self.configure(&proc)
yield @@options

if !@@options[:caching_strategy].nil?
#check for a valid caching strategy
unless ["filesystem"].include?(@@options[:caching_strategy])
raise Amazon::ConfigurationError, "Invalid caching strategy"
end

#check for required options
@@options[:caching_options] ||= {}
if @@options[:caching_strategy] == "filesystem"
#verify that a valid path was specified for filesystem caching
if options[:caching_options][:cache_path].nil? || !File.directory?(@@options[:caching_options][:cache_path])
raise Amazon::ConfigurationError, "You must specify a cache path for filesystem caching"
end
end
CacheFactory.validate(@@options)
end
end

Expand Down Expand Up @@ -223,76 +218,10 @@ def self.send_request(opts)
unless res.kind_of? Net::HTTPSuccess
raise Amazon::RequestError, "HTTP Response: #{res.code} #{res.message}"
end
Response.new(res.body)
end

# Response object returned after a REST call to Amazon service.
class Response
# XML input is in string format
def initialize(xml)
@doc = Hpricot(xml)
@items = nil
@item_page = nil
@total_results = nil
@total_pages = nil
end

# Return Hpricot object.
def doc
@doc
end

# Return true if request is valid.
def is_valid_request?
(@doc/"isvalid").inner_html == "True"
end

# Return true if response has an error.
def has_error?
!(error.nil? || error.empty?)
end

# Return error message.
def error
Element.get(@doc, "error/message")
end

# Return an array of Amazon::Element item objects.
def items
unless @items
@items = (@doc/"item").collect {|item| Element.new(item)}
end
@items
end

# Return the first item (Amazon::Element)
def first_item
items.first
end

# Return current page no if :item_page option is when initiating the request.
def item_page
unless @item_page
@item_page = (@doc/"itemsearchrequest/itempage").inner_html.to_i
end
@item_page
end

# Return total results.
def total_results
unless @total_results
@total_results = (@doc/"totalresults").inner_html.to_i
end
@total_results
end

# Return total pages.
def total_pages
unless @total_pages
@total_pages = (@doc/"totalpages").inner_html.to_i
end
@total_pages
end

response = Response.new(res.body, request_url)
cache_response(request_url, response, self.options) if caching_enabled?
response
end

protected
Expand Down Expand Up @@ -326,104 +255,13 @@ def self.prepare_url(opts)
def self.camelize(s)
s.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
end
end

# Internal wrapper class to provide convenient method to access Hpricot element value.
class Element
# Pass Hpricot::Elements object
def initialize(element)
@element = element
end

# Returns Hpricot::Elments object
def elem
@element
end

# Find Hpricot::Elements matching the given path. Example: element/"author".
def /(path)
elements = @element/path
return nil if elements.size == 0
elements
end

# Find Hpricot::Elements matching the given path, and convert to Amazon::Element.
# Returns an array Amazon::Elements if more than Hpricot::Elements size is greater than 1.
def search_and_convert(path)
elements = self./(path)
return unless elements
elements = elements.map{|element| Element.new(element)}
return elements.first if elements.size == 1
elements
end

# Get the text value of the given path, leave empty to retrieve current element value.
def get(path="")
Element.get(@element, path)
end

# Get the unescaped HTML text of the given path.
def get_unescaped(path="")
Element.get_unescaped(@element, path)
end

# Get the array values of the given path.
def get_array(path="")
Element.get_array(@element, path)
end

# Get the children element text values in hash format with the element names as the hash keys.
def get_hash(path="")
Element.get_hash(@element, path)
end

# Similar to #get, except an element object must be passed-in.
def self.get(element, path="")
return unless element
result = element.at(path)
result = result.inner_html if result
result
end

# Similar to #get_unescaped, except an element object must be passed-in.
def self.get_unescaped(element, path="")
result = get(element, path)
CGI::unescapeHTML(result) if result
end

# Similar to #get_array, except an element object must be passed-in.
def self.get_array(element, path="")
return unless element

result = element/path
if (result.is_a? Hpricot::Elements) || (result.is_a? Array)
parsed_result = []
result.each {|item|
parsed_result << Element.get(item)
}
parsed_result
else
[Element.get(result)]

def self.caching_enabled?
!self.options[:caching_strategy].nil?
end
end

# Similar to #get_hash, except an element object must be passed-in.
def self.get_hash(element, path="")
return unless element

result = element.at(path)
if result
hash = {}
result = result.children
result.each do |item|
hash[item.name.to_sym] = item.inner_html
end
hash

def self.cache_response(request, response, options)
Amazon::CacheFactory.cache(request, response, options)
end
end

def to_s
elem.to_s if elem
end
end
end

0 comments on commit 02741b5

Please sign in to comment.