Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # wadl.rb
- # http://www.crummy.com/software/wadl.rb/
- # Super cheap Ruby WADL client
- # by Leonard Richardson leonardr@segfault.org
- # v20070217
- # For more on WADL, see http://wadl.dev.java.net/
- require 'rubygems'
- require 'rest-open-uri'
- require 'delegate'
- require 'rexml/document'
- require 'set'
- require 'cgi'
- begin
- require 'rubygems'
- require 'mime/types'
- MIME_TYPES_SUPPORTED = true
- rescue LoadError
- MIME_TYPES_SUPPORTED = false
- end
- module WADL
- # A container for application-specific faults
- module Faults
- end
- #########################################################################
- #
- # A cheap way of defining an XML schema as Ruby classes and then parsing
- # documents into instances of those classes.
- class CheapSchema
- attr_accessor :index_key, :href
- @may_be_reference = false
- @contents_are_mixed_data = false
- def initialize
- @attributes = {}
- @contents = nil
- end
- def self.init
- @names = {}
- @members = {}
- @collections = {}
- @required_attributes = []
- @attributes = []
- end
- def self.inherit(from)
- init
- @names = from.names.dup if from.names
- @members = from.members.dup if from.members
- @collections = from.collections.dup if from.collections
- @required_attributes = from.required_attributes.dup if from.required_attributes
- @attributes = from.attributes.dup if from.attributes
- end
- def self.inherited(klass)
- klass.inherit(self)
- end
- def self.names
- @names
- end
- def self.members
- @members
- end
- def self.collections
- @collections
- end
- def self.required_attributes
- @required_attributes
- end
- def self.attributes
- @attributes
- end
- def attributes
- @attributes
- end
- def self.may_be_reference?
- @may_be_reference
- end
- def self.in_document(element_name)
- @names[:element] = element_name
- @names[:member] = element_name
- @names[:collection] = element_name + 's'
- end
- def self.as_collection(collection_name)
- @names[:collection] = collection_name
- end
- def self.as_member(member_name)
- @names[:member] = member_name
- end
- def self.contents_are_mixed_data
- @contents_are_mixed_data = true
- end
- def self.has_one(*classes)
- classes.each do |c|
- @members[c.names[:element]] = c
- member_name = c.names[:member]
- dereferencing_instance_accessor member_name
- end
- end
- def self.has_many(*classes)
- classes.each do |c|
- @collections[c.names[:element]] = c
- collection_name = c.names[:collection]
- dereferencing_instance_accessor collection_name
- find_method_name = "find_#{c.names[:element]}"
- # Define a method for finding a specific element of this
- # collection.
- # TODO: In Ruby 1.9, make match_block a block argument.
- define_method(find_method_name) do |name, *args|
- name = name.to_s
- if args[0].respond_to? :call
- match_block = args[0]
- else
- match_block = Proc.new { |m| m.name_matches(name) }
- end
- unless args[1].nil?
- auto_dereference = args[1]
- else
- auto_dereference = true
- end
- match = self.send(collection_name).detect do |m|
- match_block.call(m) || \
- (c.may_be_reference? && auto_dereference &&
- match_block.call(m.dereference))
- end
- match = match.dereference if match && auto_dereference
- return match
- end
- end
- end
- def self.dereferencing_instance_accessor(*symbols)
- symbols.each do |name|
- define_method(name) do
- dereference.instance_variable_get("@#{name}")
- end
- define_method(name.to_s+'=') do |value|
- dereference.instance_variable_set("@#{name}", value)
- end
- end
- end
- def self.dereferencing_attr_accessor(*symbols)
- symbols.each do |name|
- m = instance_methods
- define_method(name) do
- dereference.attributes[name.to_s]
- end
- define_method(name.to_s+'=') do |value|
- dereference.attributes[name.to_s] = value
- end
- end
- end
- def self.has_attributes(*names)
- names.each do |name|
- @attributes << name
- @index_attribute ||= name.to_s
- if name == :href
- attr_accessor name
- else
- dereferencing_attr_accessor name
- end
- end
- end
- def self.has_required(*names)
- names.each do |name|
- @required_attributes << name
- @index_attribute ||= name.to_s
- if name == :href
- attr_accessor name
- else
- dereferencing_attr_accessor name
- end
- end
- end
- def self.may_be_reference
- @may_be_reference = true
- define_method("dereference") do
- return self if not self.attributes['href']
- unless @referenced
- if self.attributes['href']
- find_method_name = "find_#{self.class.names[:element]}"
- p = self
- until @referenced or !p do
- begin
- p = p.parent
- end until !p or p.respond_to? find_method_name
- if p
- @referenced = p.send(find_method_name, self.attributes['href'], nil, false) if p
- else
- @referenced = nil
- end
- end
- end
- end
- @referenced ? dereference_with_context(@referenced) : nil
- end
- end
- # This object is a reference to another object. This method returns
- # an object that acts like the other object, but also contains any
- # neccessary context about this object. See the ResourceAndAddress
- # implementation, in which a dereferenced resource contains
- # information about the parent of the resource that referenced it
- # (otherwise, there's no way to build the URI).
- def dereference_with_context(referent)
- referent
- end
- # Turn an XML element into an instance of this class.
- def self.from_element(parent, e, need_finalization)
- attributes = e.attributes
- me = self.new
- me.parent = parent
- @collections.each do |name, clazz|
- collection_name = "@" + clazz.names[:collection].to_s
- me.instance_variable_set(collection_name, [])
- end
- if @may_be_reference and attributes['href']
- # Handle objects that are just references to other objects
- # somewhere above this one in the hierarchy
- href = attributes['href']
- if href[0] == ?#
- href = href[1..href.size]
- else
- puts "Warning: HREF #{href} should be ##{href}"
- end
- me.attributes['href'] = href
- else
- # Handle this element's attributes
- @required_attributes.each do |name|
- name = name.to_s
- unless attributes[name]
- raise ArgumentError, %{Missing required attribute "#{name}" in element: #{e}}
- end
- #puts " #{name}=#{attributes[name]}"
- me.attributes[name.to_s] = attributes[name]
- me.index_key = attributes[name] if name == @index_attribute
- end
- @attributes.each do |name|
- name = name.to_s
- #puts " #{name}=#{attributes[name]}"
- me.attributes[name.to_s] = attributes[name]
- me.index_key = attributes[name] if name == @index_attribute
- end
- end
- # Handle this element's children.
- if @contents_are_mixed_data
- me.instance_variable_set('@contents', e.children)
- else
- e.each_element do |child|
- clazz = @members[child.name] || @collections[child.name]
- if clazz
- object = clazz.from_element(me, child, need_finalization)
- if clazz == @members[child.name]
- #puts "#{self.name} can have one #{clazz.name}"
- instance_variable_name = "@" + clazz.names[:member].to_s
- if me.instance_variable_get(instance_variable_name)
- raise "#{self.name} can only have one #{clazz.name}, but several were specified in element: #{e}"
- end
- #puts "Setting its #{instance_variable_name} to a #{object.class.name}"
- me.instance_variable_set(instance_variable_name, object)
- else
- #puts "#{self.name} can have many #{clazz.name}"
- collection_name = "@" + clazz.names[:collection].to_s
- collection = me.instance_variable_get(collection_name)
- #puts "Adding a #{object.class.name} to #{collection_name} collection"
- collection << object
- end
- end
- end
- end
- need_finalization << me if me.respond_to? :finalize_creation
- return me
- end
- # Common instance methods
- attr_accessor :parent
- # A null implementation so that foo.dereference will always return the
- # "real" object.
- def dereference
- self
- end
- # Returns whether or not the given name matches this object.
- # By default, checks the index key for this class.
- def name_matches(name)
- index_key == name
- end
- def to_s(indent=0)
- s = ""
- i = " " * indent
- s << "#{i}#{self.class.name}\n"
- if self.class.may_be_reference? and self.attributes['href']
- s << "#{i} href=#{self.attributes['href']}\n"
- else
- [self.class.required_attributes, self.class.attributes].each do |list|
- list.each do |attr|
- attr = attr.to_s
- s << "#{i} #{attr}=#{self.attributes[attr]}\n" if self.attributes[attr]
- end
- end
- self.class.members.each_value do |member_class|
- o = self.send(member_class.names[:member])
- s << o.to_s(indent+1) if o
- end
- self.class.collections.each_value do |collection_class|
- c = self.send(collection_class.names[:collection])
- if c and not c.empty?
- s << "#{i} Collection of #{c.size} #{collection_class.name}(s)\n"
- c.each do |o|
- s << o.to_s(indent+2)
- end
- end
- end
- if @contents && !@contents.empty?
- s << '-' * 80 << "\n" << @contents.join(' ') << "\n" << '-' * 80 << "\n"
- end
- end
- return s
- end
- end
- #########################################################################
- # Classes to keep track of the logical structure of a URI.
- URIParts = Struct.new(:uri, :query, :headers)
- class URIParts
- def to_s
- u = uri.dup
- unless query.empty?
- u << (uri.index('?') ? '&' : '?')
- u << query_string
- end
- u
- end
- def inspect
- s = to_s
- s << " Plus headers: #{headers.inspect}" if headers
- end
- def query_string
- query.join('&')
- end
- def hash(x)
- to_str.hash
- end
- def ==(x)
- return to_str == x if x.respond_to? :to_str
- return super
- end
- alias :to_str :to_s
- end
- # The Address class keeps track of the user's path through a resource
- # graph. Values for WADL parameters may be specified at any time using
- # the bind method. An Address cannot be turned into a URI and header
- # set until all required parameters have been bound to values.
- #
- # An Address object is built up through calls to Resource#address
- class Address
- attr_reader :path_fragments, :query_vars, :headers, \
- :path_params, :query_params, :header_params
- def initialize(path_fragments=[], query_vars=[], headers={},
- path_params={}, query_params={}, header_params={})
- @path_fragments = path_fragments
- @query_vars, @headers = query_vars, headers
- @path_params, @query_params, @header_params = path_params, query_params, header_params
- end
- def _deep_copy_hash(h)
- a = h.inject({}) { |h,kv| h[kv[0]] = (kv[1] ? kv[1].dup : kv[1]); h }
- end
- def _deep_copy_array(a)
- a.inject([]) { |a,e| a << (e ? e.dup : e) }
- end
- # Perform a deep copy.
- def deep_copy
- Address.new(_deep_copy_array(@path_fragments),
- _deep_copy_array(@query_vars), _deep_copy_hash(@headers),
- @path_params.dup, @query_params.dup,
- @header_params.dup)
- end
- def to_s
- s = "Address:\n"
- s << " Path fragments: #{@path_fragments.inspect}\n"
- s << " Query variables: #{@query_vars.inspect}\n"
- s << " Header variables: #{@headers.inspect}\n"
- s << " Unbound path parameters: #{@path_params.inspect}\n"
- s << " Unbound query parameters: #{@query_params.inspect}\n"
- s << " Unbound header parameters: #{@header_params.inspect}\n"
- end
- alias :inspect :to_s
- def self.embedded_param_names(fragment)
- fragment.scan(/\{([^}]+)\}/).flatten
- end
- # Binds some or all of the unbound variables in this address to values.
- def bind!(args={})
- path_var_values = args[:path] || {}
- query_var_values = args[:query] || {}
- header_var_values = args[:headers] || {}
- # Bind variables found in the path fragments.
- if path_var_values
- path_params_to_delete = []
- path_fragments.each do |fragment|
- if fragment.respond_to? :to_str
- # This fragment is a string which might contain {} substitutions.
- # Make any substitutions available to the provided path variables.
- embedded_param_names = self.class.embedded_param_names(fragment)
- embedded_param_names.each do |param_name|
- value = path_var_values[param_name] || path_var_values[param_name.to_sym]
- param = path_params[param_name]
- if param
- value = param % value
- path_params_to_delete << param
- else
- value = Param.default.%(value, param_name)
- end
- fragment.gsub!('{' + param_name + '}', value)
- end
- else
- # This fragment is an array of Param objects (style 'matrix'
- # or 'plain') which may be bound to strings. As substitutions
- # happen, the array will become a mixed array of Param objects
- # and strings.
- fragment.each_with_index do |param, i|
- if param.respond_to? :name
- value = path_var_values[param.name] || path_var_values[param.name.to_sym]
- new_value = param % value
- fragment[i] = new_value if new_value
- path_params_to_delete << param
- end
- end
- end
- end
- # Delete any embedded path parameters that are now bound from
- # our list of unbound parameters.
- path_params_to_delete.each { |p| path_params.delete(p.name) }
- end
- # Bind query variable values to query parameters
- query_var_values.each do |name, value|
- param = query_params[name.to_s]
- if param
- query_vars << param % value
- query_params.delete(name.to_s)
- end
- end
- # Bind header variables to header parameters
- header_var_values.each do |name, value|
- param = header_params[name.to_s]
- if param
- headers[name] = param % value
- header_params.delete(name.to_s)
- end
- end
- return self
- end
- def uri(args={})
- obj = deep_copy
- obj.bind!(args)
- # Build the path
- uri = ''
- obj.path_fragments.flatten.each do |fragment|
- if fragment.respond_to? :to_str
- embedded_param_names = self.class.embedded_param_names(fragment)
- unless embedded_param_names.empty?
- raise ArgumentError, %{Missing a value for required path parameter "#{embedded_param_names[0]}"!}
- end
- unless fragment.empty?
- uri << '/' if !uri.empty? && uri[-1] != ?/
- uri << fragment
- end
- elsif fragment.required
- # This is a required Param that was never bound to a value.
- raise ArgumentError, %{Missing a value for required path parameter "#{fragment.name}"!}
- end
- end
- # Hunt for required unbound query parameters.
- obj.query_params.each do |name, value|
- if value.required
- raise ArgumentError, %{Missing a value for required query parameter "#{value.name}"!}
- end
- end
- # Hunt for required unbound header parameters.
- obj.header_params.each do |name, value|
- if value.required
- raise ArgumentError, %{Missing a value for required header parameter "#{value.name}"!}
- end
- end
- return URIParts.new(uri, obj.query_vars, obj.headers)
- end
- end
- #########################################################################
- #
- # Now we use Ruby classes to define the structure of a WADL document
- class Documentation < CheapSchema
- in_document 'doc'
- as_member 'doc'
- as_collection 'docs'
- has_attributes "xml:lang", :title
- contents_are_mixed_data
- end
- class HasDocs < CheapSchema
- has_many Documentation
- # Convenience method to define a no-argument singleton method on
- # this object.
- def define_singleton(name, contents)
- return if name =~ /[^A-Za-z0-9_]/
- instance_eval(%{def #{name}
- #{contents}
- end})
- end
- end
- class Option < HasDocs
- in_document 'option'
- as_member 'option'
- as_collection 'options'
- has_required :value
- end
- class Link < HasDocs
- in_document 'link'
- as_member 'link'
- as_collection 'links'
- has_attributes :href, :rel, :rev
- end
- class Param < HasDocs
- in_document 'param'
- as_member 'param'
- as_collection 'params'
- has_required :name
- has_attributes :type, :default, :style, :path, :required, :repeating, :fixed
- has_many Option
- has_many Link
- def inspect
- %{Param "#{name}"}
- end
- # Validates and formats a proposed value for this parameter. Returns
- # the formatted value. Raises an ArgumentError if the value
- # is invalid.
- #
- # The 'name' and 'style' arguments are used in conjunction with the
- # default Param object.
- def %(value, name=nil, style=nil)
- name ||= self.name
- style ||= self.style
- value = fixed if fixed
- unless value
- if default
- value = default
- elsif required
- raise ArgumentError, "No value provided for required param \"#{name}\"!"
- else
- return '' # No value provided and none required.
- end
- end
- if value.respond_to?(:each) && !value.respond_to?(:to_str)
- if repeating
- values = value
- else
- raise ArgumentError, "Multiple values provided for single-value param \"#{name}\""
- end
- else
- values = [value]
- end
- # If the param lists acceptable values in option tags, make sure that
- # all values are found in those tags.
- if options && !options.empty?
- values.each do |value|
- unless find_option(value)
- acceptable = options.collect { |o| o.value }.join('", "')
- raise ArgumentError, %{"#{value}" is not among the acceptable parameter values ("#{acceptable}")}
- end
- end
- end
- if style == 'query' || parent.is_a?(RequestFormat) ||
- (parent.respond_to?('is_form_representation?') \
- && parent.is_form_representation?)
- value = values.collect do |v|
- URI.escape(name) + '=' + URI.escape(v.to_s)
- end.join('&')
- elsif self.style == 'matrix'
- if type == 'xsd:boolean'
- value = values.collect { |v| (v == 'true' || v == true) ? ';' + name : '' }.join('')
- else
- value = values.collect do |v|
- v ? ';' + URI.escape(name) + '=' + URI.escape(v.to_s) : ''
- end.join('')
- end
- elsif self.style == 'header'
- value = values.join(',')
- else
- # All other cases: plain text representation.
- value = values.collect { |v| URI.escape(v.to_s) }.join(',')
- end
- return value
- end
- # A default Param object to use for a path parameter that is
- # only specified as a name in the path of a resource.
- @@default = Param.new
- @@default.required = true
- @@default.style = 'plain'
- @@default.type = 'xsd:string'
- def self.default
- @@default
- end
- end
- # A mixin for objects that contain representations
- module RepresentationContainer
- def find_representation_by_media_type(type)
- representations.detect { |r| r.mediaType == type }
- end
- def find_form
- representations.detect { |r| r.is_form_representation? }
- end
- end
- class RepresentationFormat < HasDocs
- in_document 'representation'
- as_collection 'representations'
- may_be_reference
- has_attributes :id, :mediaType, :element
- has_many Param
- def is_form_representation?
- return mediaType == 'application/x-www-form-encoded' ||
- mediaType == 'multipart/form-data'
- end
- # Creates a representation by plugging a set of parameters
- # into a representation format.
- def %(values)
- if mediaType == 'application/x-www-form-encoded'
- representation = []
- params.each do |param|
- if param.fixed
- p_values = [param.fixed]
- elsif values[param.name] || values[param.name.to_sym]
- p_values = values[param.name] || values[param.name.to_sym]
- if !param.repeating || !(p_values.respond_to?(:each) && !p_values.respond_to?(:to_str))
- p_values = [p_values]
- end
- else
- if param.required
- raise ArgumentError, "Your proposed representation is missing a value for #{param.name}"
- end
- end
- if p_values
- p_values.each do |value|
- representation << CGI::escape(param.name) + '=' + CGI::escape(value.to_s)
- end
- end
- end
- representation = representation.join('&')
- else
- raise Exception,
- "wadl.rb can't instantiate a representation of type #{mediaType}"
- end
- return representation
- end
- end
- class FaultFormat < RepresentationFormat
- in_document 'fault'
- as_collection 'faults'
- may_be_reference
- has_attributes :id, :mediaType, :element, :status
- has_many Param
- attr_writer :subclass
- def subclass
- if attributes['href']
- dereference.subclass
- else
- @subclass
- end
- end
- # Define a custom subclass for this fault, so that the programmer
- # can rescue this particular fault.
- def self.from_element(*args)
- me = super
- return me if me.attributes['href']
- name = me.attributes['id']
- if name
- begin
- c = Class.new(Fault)
- WADL::Faults.const_set(name, c) unless WADL::Faults.const_defined? name
- me.subclass = c
- rescue NameError => e
- # This fault format's ID can't be a class name. Use the
- # generic subclass of Fault.
- end
- end
- me.subclass ||= Fault
- return me
- end
- end
- class RequestFormat < HasDocs
- include RepresentationContainer
- in_document 'request'
- has_many RepresentationFormat
- has_many Param
- # Returns a URI and a set of HTTP headers for this request.
- def uri(resource, args={})
- uri = resource.uri(args)
- query_values = args[:query] || {}
- header_values = args[:headers] || {}
- params.each do |param|
- if param.style == 'header'
- value = header_values[param.name] || header_values[param.name.to_sym]
- value = param % value
- uri.headers[param.name] = value if value
- else
- value = query_values[param.name] || query_values[param.name.to_sym]
- value = param.%(value, nil, 'query')
- uri.query << value if value
- end
- end
- return uri
- end
- end
- class ResponseFormat < HasDocs
- include RepresentationContainer
- in_document 'response'
- has_many RepresentationFormat, FaultFormat
- # Builds a service response object out of an HTTPResponse object.
- def build(http_response)
- # Figure out which fault or representation to use.
- status = http_response.status[0]
- response_format = self.faults.detect do |f|
- f.dereference.status == status
- end
- unless response_format
- # Try to match the response to a response format using a media
- # type.
- response_media_type = http_response.content_type
- response_format = representations.detect do |f|
- t = f.dereference.mediaType
- t && response_media_type.index(t) == 0
- end
- # If an exact media type match fails, use the mime-types gem to
- # match the response to a response format using the underlying
- # subtype. This will match "application/xml" with "text/xml".
- if !response_format && MIME_TYPES_SUPPORTED
- mime_type = MIME::Types[response_media_type]
- raw_sub_type = mime_type[0].raw_sub_type if mime_type
- response_format = representations.detect do |f|
- t = f.dereference.mediaType
- if t
- response_mime_type = MIME::Types[t]
- response_raw_sub_type = response_mime_type[0].raw_sub_type if response_mime_type
- response_raw_sub_type == raw_sub_type
- end
- end
- end
- # If all else fails, try to find a response that specifies no
- # media type. TODO: check if this would be valid WADL.
- if !response_format
- response_format = representations.detect do |f|
- !f.dereference.mediaType
- end
- end
- end
- body = http_response.read
- if response_format && response_format.mediaType =~ /xml/
- begin
- body = REXML::Document.new(body)
- # Find the appropriate element of the document
- if response_format.element
- #TODO: don't strip the damn namespace. I'm not very good at
- #namespaces and I don't see how to deal with them here.
- element = response_format.element.gsub(/.*:/, '')
- body = REXML::XPath.first(body, "//#{element}")
- end
- rescue REXML::ParseException
- end
- body.extend(XMLRepresentation)
- body.representation_of(response_format)
- end
- clazz = response_format.is_a?(FaultFormat) ? response_format.subclass : Response
- obj = clazz.new(http_response.status, http_response, body, response_format)
- raise obj if obj.is_a? Exception
- return obj
- end
- end
- class HTTPMethod < HasDocs
- in_document 'method'
- as_collection 'http_methods'
- may_be_reference
- has_required :id, :name
- has_one RequestFormat
- has_one ResponseFormat
- # Args:
- # :path - Values for path parameters
- # :query - Values for query parameters
- # :headers - Values for header parameters
- # :send_representation
- # :expect_representation
- def call(resource, args={})
- unless parent.respond_to? :uri
- raise Exception, \
- "You can't call a method that's not attached to a resource! (You may have dereferenced a method when you shouldn't have)"
- end
- resource ||= parent
- method = self.dereference
- if method.request
- uri = method.request.uri(resource, args)
- else
- uri = resource.uri
- end
- headers = uri.headers.dup
- if args[:expect_representation]
- headers['Accept'] = expect_representation.mediaType
- end
- headers['User-Agent'] = 'Ruby WADL client' unless headers['User-Agent']
- headers[:method] = name.downcase.to_sym
- headers[:body] = args[:send_representation]
- #puts "#{headers[:method].to_s.upcase} #{uri}"
- #puts " Options: #{headers.inspect}"
- begin
- response = open(uri, headers)
- rescue OpenURI::HTTPError => e
- response = e.io
- end
- return method.response.build(response)
- end
- end
- # A mixin for objects that contain resources. If you include this, be
- # sure to alias :find_resource to :find_resource_autogenerated
- # beforehand.
- module ResourceContainer
- def resource(name_or_id)
- name_or_id = name_or_id.to_s
- find_resource(nil, Proc.new do |r|
- r.id == name_or_id || r.path == name_or_id
- end)
- end
- def find_resource_by_path(path, *args)
- path = path.to_s
- match_predicate = Proc.new { |resource| resource.path == path }
- find_resource(nil, match_predicate, *args)
- end
- def finalize_creation
- return unless resources
- resources.each do |r|
- if r.id && !r.respond_to?(r.id)
- define_singleton(r.id, "find_resource('#{r.id}')")
- end
- end
- resources.each do |r|
- if r.path && !r.respond_to?(r.path)
- define_singleton(r.path, "find_resource_by_path('#{r.path}')")
- end
- end
- end
- end
- # A type of resource. Basically a mixin of methods and params for actual
- # resources.
- class ResourceType < HasDocs
- in_document 'resource_type'
- as_collection 'resource_types'
- has_many HTTPMethod
- has_many Param
- has_attributes :id
- end
- class Resource < HasDocs
- in_document 'resource'
- as_collection 'resources'
- has_many Resource
- has_many HTTPMethod
- has_many Param
- has_many ResourceType
- has_attributes :id, :path
- include ResourceContainer
- def initialize(*args)
- super(*args)
- end
- def dereference_with_context(child)
- ResourceAndAddress.new(child, parent.address)
- end
- # Returns a ResourceAndAddress object bound to this resource
- # and the given query variables.
- def bind(args={})
- resource = ResourceAndAddress.new(self)
- resource.bind!(args)
- return resource
- end
- # Sets basic auth parameters
- def with_basic_auth(user, pass, param_name='Authorization')
- value = 'Basic ' + [user.to_s+':'+pass.to_s].pack('m')
- a = bind(:headers => {param_name => value })
- end
- def uri(args={}, working_address=nil)
- working_address = working_address.deep_copy if working_address
- address(working_address).uri(args)
- end
- # Returns an Address object refering to this resource
- def address(working_address=nil)
- if working_address
- working_address = working_address.deep_copy
- else
- if parent.respond_to? :base
- working_address = Address.new()
- working_address.path_fragments << parent.base
- else
- working_address = parent.address.deep_copy
- end
- end
- working_address.path_fragments << path.dup
- # Install path, query, and header parameters in the Address. These
- # may override existing parameters with the same names, but if
- # you've got a WADL application that works that way, you should
- # have bound parameters to values earlier.
- new_path_fragments = []
- embedded_param_names = Set.new(Address.embedded_param_names(path))
- params.each do |param|
- if embedded_param_names.member? param.name
- working_address.path_params[param.name] = param
- else
- if param.style == 'query'
- working_address.query_params[param.name] = param
- elsif param.style == 'header'
- working_address.header_params[param.name] = param
- else
- new_path_fragments << param
- working_address.path_params[param.name] = param
- end
- end
- end
- working_address.path_fragments << new_path_fragments unless new_path_fragments.empty?
- return working_address
- end
- def representation_for(http_method, request=true, all=false)
- method = find_method_by_http_method(http_method)
- if request
- container = method.request
- else
- container = method.response
- end
- representations = container.representations
- unless all
- representations = representations[0]
- end
- return representations
- end
- def find_by_id(id)
- id = id.to_s
- resources.detect { |r| r.dereference.id == id }
- end
- # Find HTTP methods in this resource and in the mixed-in types
- def each_http_method
- http_methods.each { |m| yield m }
- resource_types.each do |t|
- t.http_methods.each { |m| yield m }
- end
- end
- def find_method_by_id(id)
- id = id.to_s
- each_http_method { |m| return m if m.dereference.id == id }
- end
- def find_method_by_http_method(action)
- action = action.to_s.downcase
- each_http_method { |m| return m if m.dereference.name.downcase == action }
- end
- # Methods for reading or writing this resource
- def get(*args, &block)
- find_method_by_http_method('get').call(self, *args, &block)
- end
- def post(*args, &block)
- find_method_by_http_method('post').call(self, *args, &block)
- end
- def put(*args, &block)
- find_method_by_http_method('put').call(self, *args, &block)
- end
- def delete(*args, &block)
- find_method_by_http_method('delete').call(self, *args, &block)
- end
- end
- # A resource bound beneath a certain address. Used to keep track of a
- # path through a twisting resource hierarchy that includes references.
- class ResourceAndAddress < DelegateClass(Resource)
- def initialize(resource, address=nil, combine_address_with_resource=true)
- @resource = resource
- if combine_address_with_resource
- @address = @resource.address(address)
- else
- @address = address
- end
- super(resource)
- end
- # The id method is not delegated, because it's the name of a
- # (deprecated) built-in Ruby method. We wnat to delegate it.
- def id
- @resource.id
- end
- def to_s
- inspect
- end
- def inspect
- "ResourceAndAddress\n Resource: #{@resource.to_s}\n #{@address.inspect}"
- end
- def address
- @address
- end
- def bind(*args)
- ResourceAndAddress.new(@resource, @address.deep_copy, false).bind!(*args)
- end
- def bind!(args={})
- @address.bind!(args)
- self
- end
- def uri(args={})
- @address.deep_copy.bind!(args).uri
- end
- # method_missing is to catch generated methods that don't get delegated.
- def method_missing(name, *args, &block)
- if @resource.respond_to? name
- result = @resource.send(name, *args, &block)
- if result.is_a? Resource
- result = ResourceAndAddress.new(result, @address.dup)
- end
- return result
- else
- raise NoMethodError, "undefined method `#{name}' for #{self}:#{self.class}"
- end
- end
- # method_missing won't catch these guys because they were defined in
- # the delegation operation.
- def resource(*args, &block)
- resource = @resource.resource(*args, &block)
- resource ? ResourceAndAddress.new(resource, @address) : resource
- end
- def find_resource(*args, &block)
- resource = @resource.find_resource(*args, &block)
- resource ? ResourceAndAddress.new(resource, @address) : resource
- end
- def find_resource_by_path(*args, &block)
- resource = @resource.find_resource_by_path(*args, &block)
- resource ? ResourceAndAddress.new(resource, @address) : resource
- end
- def get(*args, &block)
- find_method_by_http_method('get').call(self, *args, &block)
- end
- def post(*args, &block)
- find_method_by_http_method('post').call(self, *args, &block)
- end
- def put(*args, &block)
- find_method_by_http_method('put').call(self, *args, &block)
- end
- def delete(*args, &block)
- find_method_by_http_method('delete').call(self, *args, &block)
- end
- end
- class Resources < HasDocs
- in_document 'resources'
- as_member 'resource_list'
- has_many Resource
- has_attributes :base
- include ResourceContainer
- end
- class Application < HasDocs
- in_document 'application'
- has_one Resources
- has_many HTTPMethod, RepresentationFormat, FaultFormat
- def Application.from_wadl(wadl)
- wadl = wadl.read if wadl.respond_to?(:read)
- doc = REXML::Document.new(wadl)
- need_finalization = []
- application = from_element(nil, doc.root, need_finalization)
- need_finalization.each { |x| x.finalize_creation }
- return application
- end
- def find_resource(symbol, *args, &block)
- resource_list.find_resource(symbol, *args, &block)
- end
- def resource(symbol)
- resource_list.resource(symbol)
- end
- def find_resource_by_path(symbol, *args, &block)
- resource_list.find_resource_by_path(symbol, *args, &block)
- end
- def finalize_creation
- return unless resource_list
- resource_list.resources.each do |r|
- if r.id && !r.respond_to?(r.id)
- define_singleton(r.id, "resource_list.find_resource('#{r.id}')")
- end
- end
- resource_list.resources.each do |r|
- if r.path && !r.respond_to?(r.path)
- define_singleton(r.path,
- "resource_list.find_resource_by_path('#{r.path}')")
- end
- end
- end
- end
- # A module mixed in to REXML documents to make them representations in the
- # WADL sense.
- module XMLRepresentation
- def representation_of(format)
- @params = format.params
- end
- def lookup_param(name)
- p = @params.detect { |p| p.name = name }
- raise ArgumentError, "No such param #{name}" unless p
- raise ArgumentError, "Param #{name} has no path!" unless p.path
- return p
- end
- # Yields up each XML element for the given Param object.
- def each_by_param(param_name)
- REXML::XPath.each(self, lookup_param(param_name).path) { |e| yield e }
- end
- # Returns an XML element for the given Param object.
- def get_by_param(param_name)
- REXML::XPath.first(self, lookup_param(param_name).path)
- end
- end
- Response = Struct.new(:code, :headers, :representation, :format)
- class Fault < Exception
- attr_accessor :code, :headers, :representation, :format
- def initialize(code, headers, representation, format)
- self.code = code
- self.headers = headers
- self.representation = representation
- self.format = format
- end
- end
- end # End WADL module
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement