diff options
author | Leon Breedt <bitserf@gmail.com> | 2005-02-25 23:39:39 +0000 |
---|---|---|
committer | Leon Breedt <bitserf@gmail.com> | 2005-02-25 23:39:39 +0000 |
commit | 6f5a7b200443baf209d2f33c428ed4a4059782f7 (patch) | |
tree | 9c3942fe27be69c102873d9fdaa13f66dc12853d | |
parent | 10faf204b712763f05a2b3155a4fd9c5338f1fb2 (diff) | |
download | rails-6f5a7b200443baf209d2f33c428ed4a4059782f7.tar.gz rails-6f5a7b200443baf209d2f33c428ed4a4059782f7.tar.bz2 rails-6f5a7b200443baf209d2f33c428ed4a4059782f7.zip |
merged the changes for the upcoming 0.6.0:
seperate out protocol marshaling into a small 'ws' library in vendor, so that
AWS itself only does integration with ActionPack, and so we can keep protocol
specific code in AWS proper to a minimum. refactor unit tests to get 95%
code coverage (for a baseline).
be far more relaxed about the types given to us by the remote side, don't do
any poor man's type checking, just try to cast and marshal to the correct types if
possible, and if not, return what they gave us anyway. this should make interoperating
with fuzzy XML-RPC clients easier.
if exception reporting is turned on, do best-effort error responses, so that
we can avoid "Internal protocol error" with no details if there is a bug in
AWS itself.
also perform extensive cleanups on AWS proper.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@800 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
62 files changed, 2180 insertions, 2079 deletions
diff --git a/actionwebservice/ChangeLog b/actionwebservice/ChangeLog index d627625b41..fdb2cc89da 100644 --- a/actionwebservice/ChangeLog +++ b/actionwebservice/ChangeLog @@ -1,3 +1,12 @@ +*0.6.0* (Unreleased) + + * lib/*, test/*: refactored SOAP and XML-RPC protocol specifics into + a small seperate library named 'ws', and drop it in vendor. be + more relaxed about the type of received parameters, perform casting + for XML-RPC if possible, but fallback to the received parameters. + performed extensive cleanup of the way we use SOAP, so that marshaling + of custom and array types should somewhat faster. + *0.5.0* (24th February, 2005) * lib/action_service/dispatcher*: replace "router" fragments with diff --git a/actionwebservice/Rakefile b/actionwebservice/Rakefile index 76f0ad422e..fff80c3ff5 100644 --- a/actionwebservice/Rakefile +++ b/actionwebservice/Rakefile @@ -9,7 +9,7 @@ require 'fileutils' PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' PKG_NAME = 'actionwebservice' -PKG_VERSION = '0.5.0' + PKG_BUILD +PKG_VERSION = '0.6.0' + PKG_BUILD PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}" @@ -20,7 +20,7 @@ task :default => [ :test ] # Run the unit tests Rake::TestTask.new { |t| t.libs << "test" - t.pattern = 'test/*_test.rb' + t.test_files = Dir['test/*_test.rb'] + Dir['test/ws/*_test.rb'] t.verbose = true } @@ -54,9 +54,9 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "aws" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 1.5.0' + PKG_BUILD) - s.add_dependency('activerecord', '= 1.7.0' + PKG_BUILD) - s.add_dependency('activesupport', '= 1.0.0' + PKG_BUILD) + s.add_dependency('actionpack', '>= 1.5.0' + PKG_BUILD) + s.add_dependency('activerecord', '>= 1.7.0' + PKG_BUILD) + s.add_dependency('activesupport', '>= 1.0.0' + PKG_BUILD) s.has_rdoc = true s.requirements << 'none' @@ -94,7 +94,7 @@ def each_source_file(*args) prefix ||= File.dirname(__FILE__) open_file = true if open_file.nil? includes ||= %w[lib\/action_web_service\.rb$ lib\/action_web_service\/.*\.rb$] - excludes ||= %w[] + excludes ||= %w[lib\/action_web_service\/vendor] Find.find(prefix) do |file_name| next if file_name =~ /\.svn/ file_name.gsub!(/^\.\//, '') @@ -123,7 +123,7 @@ def each_source_file(*args) end end -desc "Count lines of the source code" +desc "Count lines of the AWS source code" task :lines do total_lines = total_loc = 0 puts "Per File:" diff --git a/actionwebservice/TODO b/actionwebservice/TODO index 461aa2bcda..b3b4ec2c00 100644 --- a/actionwebservice/TODO +++ b/actionwebservice/TODO @@ -1,13 +1,6 @@ -= Low priority tasks - - add better type mapping tests for XML-RPC - - add tests for ActiveRecord support (with mock objects?) += 0.6.0 Tasks + - finish off tickets #676, #677, #678 = Refactoring - - Find an alternative way to map interesting types for SOAP (like ActiveRecord - model classes) that doesn't require creation of a sanitized copy object with data - copied from the real one. Ideally this would let us get rid of - ActionWebService::Struct altogether and provide a block that would yield the - attributes and values. "Filters" ? Not sure how to integrate with SOAP though. - - Don't have clean way to go from SOAP Class object to the xsd:NAME type string -- NaHi possibly looking at remedying this situation diff --git a/actionwebservice/lib/action_web_service.rb b/actionwebservice/lib/action_web_service.rb index 5cf988a0f8..2865dff633 100644 --- a/actionwebservice/lib/action_web_service.rb +++ b/actionwebservice/lib/action_web_service.rb @@ -32,7 +32,10 @@ rescue LoadError require_gem 'activerecord', '>= 1.6.0' end -$:.unshift(File.dirname(__FILE__)) +$:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/") + +require 'action_web_service/support/class_inheritable_options' +require 'action_web_service/vendor/ws' require 'action_web_service/base' require 'action_web_service/client' @@ -41,20 +44,21 @@ require 'action_web_service/api' require 'action_web_service/struct' require 'action_web_service/container' require 'action_web_service/protocol' +require 'action_web_service/struct' require 'action_web_service/dispatcher' ActionWebService::Base.class_eval do - include ActionWebService::API + include ActionWebService::Container::Direct include ActionWebService::Invocation end ActionController::Base.class_eval do - include ActionWebService::Container - include ActionWebService::Protocol::Registry + include ActionWebService::Protocol::Discovery include ActionWebService::Protocol::Soap include ActionWebService::Protocol::XmlRpc - include ActionWebService::API - include ActionWebService::API::ActionController + include ActionWebService::Container::Direct + include ActionWebService::Container::Delegated + include ActionWebService::Container::ActionController include ActionWebService::Dispatcher include ActionWebService::Dispatcher::ActionController end diff --git a/actionwebservice/lib/action_web_service/api.rb b/actionwebservice/lib/action_web_service/api.rb index 0c71de5654..ab8b696ab4 100644 --- a/actionwebservice/lib/action_web_service/api.rb +++ b/actionwebservice/lib/action_web_service/api.rb @@ -1,2 +1 @@ -require 'action_web_service/api/abstract' -require 'action_web_service/api/action_controller' +require 'action_web_service/api/base' diff --git a/actionwebservice/lib/action_web_service/api/abstract.rb b/actionwebservice/lib/action_web_service/api/base.rb index 0ce68d10f7..952c6baa0d 100644 --- a/actionwebservice/lib/action_web_service/api/abstract.rb +++ b/actionwebservice/lib/action_web_service/api/base.rb @@ -1,70 +1,5 @@ module ActionWebService # :nodoc: module API # :nodoc: - class APIError < ActionWebService::ActionWebServiceError # :nodoc: - end - - def self.append_features(base) # :nodoc: - super - base.extend(ClassMethods) - end - - module ClassMethods - # Attaches ActionWebService API +definition+ to the calling class. - # - # Action Controllers can have a default associated API, removing the need - # to call this method if you follow the Action Web Service naming conventions. - # - # A controller with a class name of GoogleSearchController will - # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the - # API definition class to be named <tt>GoogleSearchAPI</tt> or - # <tt>GoogleSearchApi</tt>. - # - # ==== Service class example - # - # class MyService < ActionWebService::Base - # web_service_api MyAPI - # end - # - # class MyAPI < ActionWebService::API::Base - # ... - # end - # - # ==== Controller class example - # - # class MyController < ActionController::Base - # web_service_api MyAPI - # end - # - # class MyAPI < ActionWebService::API::Base - # ... - # end - def web_service_api(definition=nil) - if definition.nil? - read_inheritable_attribute("web_service_api") - else - if definition.is_a?(Symbol) - raise(APIError, "symbols can only be used for #web_service_api inside of a controller") - end - unless definition.respond_to?(:ancestors) && definition.ancestors.include?(Base) - raise(APIError, "#{definition.to_s} is not a valid API definition") - end - write_inheritable_attribute("web_service_api", definition) - call_web_service_api_callbacks(self, definition) - end - end - - def add_web_service_api_callback(&block) # :nodoc: - write_inheritable_array("web_service_api_callbacks", [block]) - end - - private - def call_web_service_api_callbacks(container_class, definition) - (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block| - block.call(container_class, definition) - end - end - end - # A web service API class specifies the methods that will be available for # invocation for an API. It also contains metadata such as the method type # signature hints. @@ -87,8 +22,6 @@ module ActionWebService # :nodoc: private_class_method :new, :allocate class << self - include ActionWebService::Signature - # API methods have a +name+, which must be the Ruby method name to use when # performing the invocation on the web service object. # @@ -125,11 +58,11 @@ module ActionWebService # :nodoc: expects = options[:expects] returns = options[:returns] end - expects = canonical_signature(expects) if expects - returns = canonical_signature(returns) if returns + expects = canonical_signature(expects) + returns = canonical_signature(returns) if expects expects.each do |param| - klass = signature_parameter_class(param) + klass = WS::BaseTypes.canonical_param_type_class(param) klass = klass[0] if klass.is_a?(Array) if klass.ancestors.include?(ActiveRecord::Base) raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects") @@ -186,6 +119,10 @@ module ActionWebService # :nodoc: end end + def canonical_signature(signature) + return nil if signature.nil? + signature.map{|spec| WS::BaseTypes.canonical_param_type_spec(spec)} + end end end end diff --git a/actionwebservice/lib/action_web_service/base.rb b/actionwebservice/lib/action_web_service/base.rb index 42a514716a..16e4d87f41 100644 --- a/actionwebservice/lib/action_web_service/base.rb +++ b/actionwebservice/lib/action_web_service/base.rb @@ -1,6 +1,3 @@ -require 'action_web_service/support/class_inheritable_options' -require 'action_web_service/support/signature' - module ActionWebService # :nodoc: class ActionWebServiceError < StandardError # :nodoc: end diff --git a/actionwebservice/lib/action_web_service/client/base.rb b/actionwebservice/lib/action_web_service/client/base.rb index 431b78c748..9dada7bf98 100644 --- a/actionwebservice/lib/action_web_service/client/base.rb +++ b/actionwebservice/lib/action_web_service/client/base.rb @@ -12,28 +12,17 @@ module ActionWebService # :nodoc: def method_missing(name, *args) # :nodoc: call_name = method_name(name) return super(name, *args) if call_name.nil? - perform_invocation(call_name, args) + self.perform_invocation(call_name, args) end - protected - def perform_invocation(method_name, args) # :nodoc: - raise NotImplementedError, "use a protocol-specific client" - end - private def method_name(name) if @api.has_api_method?(name.to_sym) name.to_s elsif @api.has_public_api_method?(name.to_s) @api.api_method_name(name.to_s).to_s - else - nil end end - - def lookup_class(klass) - klass.is_a?(Hash) ? klass.values[0] : klass - end end end end diff --git a/actionwebservice/lib/action_web_service/client/soap_client.rb b/actionwebservice/lib/action_web_service/client/soap_client.rb index dfabfd86ee..b93f6475d9 100644 --- a/actionwebservice/lib/action_web_service/client/soap_client.rb +++ b/actionwebservice/lib/action_web_service/client/soap_client.rb @@ -28,10 +28,10 @@ module ActionWebService # :nodoc: # option, you must specify it here def initialize(api, endpoint_uri, options={}) super(api, endpoint_uri) - @service_name = options[:service_name] || 'ActionWebService' - @namespace = "urn:#{@service_name}" - @mapper = ActionWebService::Protocol::Soap::SoapMapper.new(@namespace) - @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new(@mapper) + @service_name = options[:service_name] + @namespace = @service_name ? '' : "urn:#{@service_name}" + @marshaler = WS::Marshaling::SoapMarshaler.new + @encoder = WS::Encoding::SoapRpcEncoding.new @soap_action_base = options[:soap_action_base] @soap_action_base ||= URI.parse(endpoint_uri).path @driver = create_soap_rpc_driver(api, endpoint_uri) @@ -48,9 +48,9 @@ module ActionWebService # :nodoc: private def create_soap_rpc_driver(api, endpoint_uri) - @mapper.map_api(api) + register_api(@marshaler, api) driver = SoapDriver.new(endpoint_uri, nil) - driver.mapping_registry = @mapper.registry + driver.mapping_registry = @marshaler.registry api.api_methods.each do |name, info| public_name = api.public_api_method_name(name) qname = XSD::QName.new(@namespace, public_name) @@ -58,25 +58,38 @@ module ActionWebService # :nodoc: expects = info[:expects] returns = info[:returns] param_def = [] - i = 1 + i = 0 if expects - expects.each do |klass| - param_name = klass.is_a?(Hash) ? klass.keys[0] : "param#{i}" - param_klass = lookup_class(klass) - mapping = @mapper.lookup(param_klass) - param_def << ['in', param_name, mapping.registry_mapping] + expects.each do |spec| + param_name = spec.is_a?(Hash) ? spec.keys[0].to_s : "param#{i}" + type_binding = @marshaler.register_type(spec) + param_def << ['in', param_name, type_binding.mapping] i += 1 end end if returns - mapping = @mapper.lookup(lookup_class(returns[0])) - param_def << ['retval', 'return', mapping.registry_mapping] + type_binding = @marshaler.register_type(returns[0]) + param_def << ['retval', 'return', type_binding.mapping] end driver.add_method(qname, action, name.to_s, param_def) end driver end + def register_api(marshaler, api) + type_bindings = [] + api.api_methods.each do |name, info| + expects, returns = info[:expects], info[:returns] + if expects + expects.each{|type| type_bindings << marshaler.register_type(type)} + end + if returns + returns.each{|type| type_bindings << marshaler.register_type(type)} + end + end + type_bindings + end + class SoapDriver < SOAP::RPC::Driver # :nodoc: def add_method(qname, soapaction, name, param_def) @proxy.add_rpc_method(qname, soapaction, name, param_def) diff --git a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb index bb52c20453..dc7ad1517f 100644 --- a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb +++ b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb @@ -31,6 +31,7 @@ module ActionWebService # :nodoc: @api = api @handler_name = options[:handler_name] @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout]) + @marshaler = WS::Marshaling::XmlRpcMarshaler.new end protected @@ -43,18 +44,21 @@ module ActionWebService # :nodoc: def transform_outgoing_method_params(method_name, params) info = @api.api_methods[method_name.to_sym] - signature = info[:expects] - signature_length = signature.nil?? 0 : signature.length - if signature_length != params.length - raise(ProtocolError, "API declares #{public_name(method_name)} to accept " + - "#{signature_length} parameters, but #{params.length} parameters " + - "were supplied") + expects = info[:expects] + expects_length = expects.nil?? 0 : expects.length + if expects_length != params.length + raise(ClientError, "API declares #{public_name(method_name)} to accept " + + "#{expects_length} parameters, but #{params.length} parameters " + + "were supplied") end - if signature_length > 0 - signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature) - (1..signature.size).each do |i| - i -= 1 - params[i] = Protocol::XmlRpc::XmlRpcProtocol.ruby_to_xmlrpc(params[i], lookup_class(signature[i])) + params = params.dup + if expects_length > 0 + i = 0 + expects.each do |spec| + type_binding = @marshaler.register_type(spec) + info = WS::ParamInfo.create(spec, i, type_binding) + params[i] = @marshaler.marshal(WS::Param.new(params[i], info)) + i += 1 end end params @@ -62,10 +66,11 @@ module ActionWebService # :nodoc: def transform_return_value(method_name, return_value) info = @api.api_methods[method_name.to_sym] - return true unless signature = info[:returns] - param_klass = lookup_class(signature[0]) - signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types([param_klass]) - Protocol::XmlRpc::XmlRpcProtocol.xmlrpc_to_ruby(return_value, signature[0]) + return true unless returns = info[:returns] + type_binding = @marshaler.register_type(returns[0]) + info = WS::ParamInfo.create(returns[0], 0, type_binding) + info.name = 'return' + @marshaler.transform_inbound(WS::Param.new(return_value, info)) end def public_name(method_name) diff --git a/actionwebservice/lib/action_web_service/container.rb b/actionwebservice/lib/action_web_service/container.rb index f02717579e..13d9d8ab56 100644 --- a/actionwebservice/lib/action_web_service/container.rb +++ b/actionwebservice/lib/action_web_service/container.rb @@ -1,85 +1,3 @@ -module ActionWebService # :nodoc: - module Container # :nodoc: - class ContainerError < ActionWebService::ActionWebServiceError # :nodoc: - end - - def self.append_features(base) # :nodoc: - super - base.extend(ClassMethods) - base.send(:include, ActionWebService::Container::InstanceMethods) - end - - module ClassMethods - # Declares a web service that will provides access to the API of the given - # +object+. +object+ must be an ActionWebService::Base derivative. - # - # Web service object creation can either be _immediate_, where the object - # instance is given at class definition time, or _deferred_, where - # object instantiation is delayed until request time. - # - # ==== Immediate web service object example - # - # class ApiController < ApplicationController - # web_service_dispatching_mode :delegated - # - # web_service :person, PersonService.new - # end - # - # For deferred instantiation, a block should be given instead of an - # object instance. This block will be executed in controller instance - # context, so it can rely on controller instance variables being present. - # - # ==== Deferred web service object example - # - # class ApiController < ApplicationController - # web_service_dispatching_mode :delegated - # - # web_service(:person) { PersonService.new(@request.env) } - # end - def web_service(name, object=nil, &block) - if (object && block_given?) || (object.nil? && block.nil?) - raise(ContainerError, "either service, or a block must be given") - end - name = name.to_sym - if block_given? - info = { name => { :block => block } } - else - info = { name => { :object => object } } - end - write_inheritable_hash("web_services", info) - call_web_service_definition_callbacks(self, name, info) - end - - # Whether this service contains a service with the given +name+ - def has_web_service?(name) - web_services.has_key?(name.to_sym) - end - - def web_services # :nodoc: - read_inheritable_attribute("web_services") || {} - end - - def add_web_service_definition_callback(&block) # :nodoc: - write_inheritable_array("web_service_definition_callbacks", [block]) - end - - private - def call_web_service_definition_callbacks(container_class, web_service_name, service_info) - (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block| - block.call(container_class, web_service_name, service_info) - end - end - end - - module InstanceMethods # :nodoc: - def web_service_object(web_service_name) - info = self.class.web_services[web_service_name.to_sym] - unless info - raise(ContainerError, "no such web service '#{web_service_name}'") - end - service = info[:block] - service ? instance_eval(&service) : info[:object] - end - end - end -end +require 'action_web_service/container/direct_container' +require 'action_web_service/container/delegated_container' +require 'action_web_service/container/action_controller_container' diff --git a/actionwebservice/lib/action_web_service/api/action_controller.rb b/actionwebservice/lib/action_web_service/container/action_controller_container.rb index 604cbfe704..4dda93ec11 100644 --- a/actionwebservice/lib/action_web_service/api/action_controller.rb +++ b/actionwebservice/lib/action_web_service/container/action_controller_container.rb @@ -1,5 +1,5 @@ module ActionWebService # :nodoc: - module API # :nodoc: + module Container # :nodoc: module ActionController # :nodoc: def self.append_features(base) # :nodoc: base.class_eval do @@ -36,7 +36,7 @@ module ActionWebService # :nodoc: api_klass = options.delete(:api) || require_web_service_api(name) class_eval do define_method(name) do - probe_protocol_client(api_klass, protocol, endpoint_uri, options) + create_web_service_client(api_klass, protocol, endpoint_uri, options) end protected name end diff --git a/actionwebservice/lib/action_web_service/container/delegated_container.rb b/actionwebservice/lib/action_web_service/container/delegated_container.rb new file mode 100644 index 0000000000..674141aab6 --- /dev/null +++ b/actionwebservice/lib/action_web_service/container/delegated_container.rb @@ -0,0 +1,87 @@ +module ActionWebService # :nodoc: + module Container # :nodoc: + module Delegated # :nodoc: + class ContainerError < ActionWebServiceError # :nodoc: + end + + def self.append_features(base) # :nodoc: + super + base.extend(ClassMethods) + base.send(:include, ActionWebService::Container::Delegated::InstanceMethods) + end + + module ClassMethods + # Declares a web service that will provides access to the API of the given + # +object+. +object+ must be an ActionWebService::Base derivative. + # + # Web service object creation can either be _immediate_, where the object + # instance is given at class definition time, or _deferred_, where + # object instantiation is delayed until request time. + # + # ==== Immediate web service object example + # + # class ApiController < ApplicationController + # web_service_dispatching_mode :delegated + # + # web_service :person, PersonService.new + # end + # + # For deferred instantiation, a block should be given instead of an + # object instance. This block will be executed in controller instance + # context, so it can rely on controller instance variables being present. + # + # ==== Deferred web service object example + # + # class ApiController < ApplicationController + # web_service_dispatching_mode :delegated + # + # web_service(:person) { PersonService.new(@request.env) } + # end + def web_service(name, object=nil, &block) + if (object && block_given?) || (object.nil? && block.nil?) + raise(ContainerError, "either service, or a block must be given") + end + name = name.to_sym + if block_given? + info = { name => { :block => block } } + else + info = { name => { :object => object } } + end + write_inheritable_hash("web_services", info) + call_web_service_definition_callbacks(self, name, info) + end + + # Whether this service contains a service with the given +name+ + def has_web_service?(name) + web_services.has_key?(name.to_sym) + end + + def web_services # :nodoc: + read_inheritable_attribute("web_services") || {} + end + + def add_web_service_definition_callback(&block) # :nodoc: + write_inheritable_array("web_service_definition_callbacks", [block]) + end + + private + def call_web_service_definition_callbacks(container_class, web_service_name, service_info) + (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block| + block.call(container_class, web_service_name, service_info) + end + end + end + + module InstanceMethods # :nodoc: + def web_service_object(web_service_name) + info = self.class.web_services[web_service_name.to_sym] + unless info + raise(ContainerError, "no such web service '#{web_service_name}'") + end + service = info[:block] + service ? instance_eval(&service) : info[:object] + end + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/container/direct_container.rb b/actionwebservice/lib/action_web_service/container/direct_container.rb new file mode 100644 index 0000000000..b53c542fc8 --- /dev/null +++ b/actionwebservice/lib/action_web_service/container/direct_container.rb @@ -0,0 +1,70 @@ +module ActionWebService # :nodoc: + module Container # :nodoc: + module Direct # :nodoc: + class ContainerError < ActionWebServiceError # :nodoc: + end + + def self.append_features(base) # :nodoc: + super + base.extend(ClassMethods) + end + + module ClassMethods + # Attaches ActionWebService API +definition+ to the calling class. + # + # Action Controllers can have a default associated API, removing the need + # to call this method if you follow the Action Web Service naming conventions. + # + # A controller with a class name of GoogleSearchController will + # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the + # API definition class to be named <tt>GoogleSearchAPI</tt> or + # <tt>GoogleSearchApi</tt>. + # + # ==== Service class example + # + # class MyService < ActionWebService::Base + # web_service_api MyAPI + # end + # + # class MyAPI < ActionWebService::API::Base + # ... + # end + # + # ==== Controller class example + # + # class MyController < ActionController::Base + # web_service_api MyAPI + # end + # + # class MyAPI < ActionWebService::API::Base + # ... + # end + def web_service_api(definition=nil) + if definition.nil? + read_inheritable_attribute("web_service_api") + else + if definition.is_a?(Symbol) + raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller") + end + unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base) + raise(ContainerError, "#{definition.to_s} is not a valid API definition") + end + write_inheritable_attribute("web_service_api", definition) + call_web_service_api_callbacks(self, definition) + end + end + + def add_web_service_api_callback(&block) # :nodoc: + write_inheritable_array("web_service_api_callbacks", [block]) + end + + private + def call_web_service_api_callbacks(container_class, definition) + (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block| + block.call(container_class, definition) + end + end + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb index 2262cd1cdd..b7560afc87 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -14,136 +14,103 @@ module ActionWebService # :nodoc: module InstanceMethods # :nodoc: private - def dispatch_web_service_request(action_pack_request) - protocol_request = protocol_response = nil - bm = Benchmark.measure do - protocol_request = probe_request_protocol(action_pack_request) - protocol_response = dispatch_protocol_request(protocol_request) - end - [protocol_request, protocol_response, bm.real, nil] - rescue Exception => e - protocol_response = prepare_exception_response(protocol_request, e) - [protocol_request, prepare_exception_response(protocol_request, e), nil, e] - end - - def dispatch_protocol_request(protocol_request) + def invoke_web_service_request(protocol_request) + invocation = web_service_invocation(protocol_request) case web_service_dispatching_mode when :direct - dispatch_direct_request(protocol_request) + web_service_direct_invoke(invocation) when :delegated - dispatch_delegated_request(protocol_request) - else - raise(ContainerError, "unsupported dispatching mode :#{web_service_dispatching_mode}") + web_service_delegated_invoke(invocation) end end - - def dispatch_direct_request(protocol_request) - request = prepare_dispatch_request(protocol_request) - return_value = direct_invoke(request) - protocol_request.marshal(return_value) - end - - def dispatch_delegated_request(protocol_request) - request = prepare_dispatch_request(protocol_request) - return_value = delegated_invoke(request) - protocol_request.marshal(return_value) - end - - def direct_invoke(request) - return nil unless before_direct_invoke(request) - return_value = send(request.method_name) - after_direct_invoke(request) - return_value - end - - def before_direct_invoke(request) - @method_params = request.params - end - - def after_direct_invoke(request) + + def web_service_direct_invoke(invocation) + @method_params = invocation.method_ordered_params + return_value = self.__send__(invocation.api_method_name) + returns = invocation.returns ? invocation.returns[0] : nil + invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns) end - def delegated_invoke(request) + def web_service_delegated_invoke(invocation) cancellation_reason = nil - web_service = request.web_service - return_value = web_service.perform_invocation(request.method_name, request.params) do |x| + return_value = invocation.service.perform_invocation(invocation.api_method_name, invocation.method_ordered_params) do |x| cancellation_reason = x end if cancellation_reason raise(DispatcherError, "request canceled: #{cancellation_reason}") end - return_value + returns = invocation.returns ? invocation.returns[0] : nil + invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns) end - def prepare_dispatch_request(protocol_request) - api = method_name = web_service_name = web_service = params = nil - public_method_name = protocol_request.public_method_name + def web_service_invocation(request) + invocation = Invocation.new + invocation.protocol = request.protocol + invocation.service_name = request.service_name case web_service_dispatching_mode when :direct - api = self.class.web_service_api + invocation.api = self.class.web_service_api + invocation.service = self when :delegated - web_service_name = protocol_request.web_service_name - web_service = web_service_object(web_service_name) - api = web_service.class.web_service_api - end - method_name = api.api_method_name(public_method_name) - signature = nil - if method_name - signature = api.api_methods[method_name] - protocol_request.type = Protocol::CheckedMessage - protocol_request.signature = signature[:expects] - protocol_request.return_signature = signature[:returns] - else - method_name = api.default_api_method - if method_name - protocol_request.type = Protocol::UncheckedMessage - else - raise(DispatcherError, "no such method #{web_service_name}##{public_method_name}") + invocation.service = web_service_object(request.service_name) rescue nil + unless invocation.service + raise(DispatcherError, "failed to instantiate service #{invocation.service_name}") end + invocation.api = invocation.service.class.web_service_api end - params = protocol_request.unmarshal - DispatchRequest.new( - :api => api, - :public_method_name => public_method_name, - :method_name => method_name, - :signature => signature, - :web_service_name => web_service_name, - :web_service => web_service, - :params => params) - end - - def prepare_exception_response(protocol_request, exception) - if protocol_request && exception - case web_service_dispatching_mode - when :direct - if web_service_exception_reporting - return protocol_request.protocol.marshal_exception(exception) - end - when :delegated - web_service = web_service_object(protocol_request.web_service_name) - if web_service && web_service.class.web_service_exception_reporting - return protocol_request.protocol.marshal_exception(exception) + public_method_name = request.method_name + unless invocation.api.has_public_api_method?(public_method_name) + raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}") + end + invocation.public_method_name = public_method_name + invocation.api_method_name = invocation.api.api_method_name(public_method_name) + info = invocation.api.api_methods[invocation.api_method_name] + invocation.expects = info[:expects] + invocation.returns = info[:returns] + if invocation.expects + i = 0 + invocation.method_ordered_params = request.method_params.map do |param| + if invocation.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol) + marshaler = invocation.protocol.marshaler + decoded_param = WS::Encoding::XmlRpcDecodedParam.new(param.info.name, param.value) + marshaled_param = marshaler.typed_unmarshal(decoded_param, invocation.expects[i]) rescue nil + param = marshaled_param ? marshaled_param : param end + i += 1 + param.value + end + i = 0 + params = [] + invocation.expects.each do |spec| + type_binding = invocation.protocol.register_signature_type(spec) + info = WS::ParamInfo.create(spec, i, type_binding) + params << WS::Param.new(invocation.method_ordered_params[i], info) + i += 1 + end + invocation.method_ws_params = params + invocation.method_named_params = {} + invocation.method_ws_params.each do |param| + invocation.method_named_params[param.info.name] = param.value end else - protocol_request.protocol.marshal_exception(RuntimeError.new("missing protocol request or exception")) + invocation.method_ordered_params = [] + invocation.method_named_params = {} end - rescue Exception - nil + invocation end - class DispatchRequest - attr :api - attr :public_method_name - attr :method_name - attr :signature - attr :web_service_name - attr :web_service - attr :params - - def initialize(values={}) - values.each{|k,v| instance_variable_set("@#{k.to_s}", v)} - end + class Invocation + attr_accessor :protocol + attr_accessor :service_name + attr_accessor :api + attr_accessor :public_method_name + attr_accessor :api_method_name + attr_accessor :method_ordered_params + attr_accessor :method_named_params + attr_accessor :method_ws_params + attr_accessor :expects + attr_accessor :returns + attr_accessor :service end end end diff --git a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb index 511d00ca44..a35029b40d 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -1,3 +1,6 @@ +require 'benchmark' +require 'builder/xmlmarkup' + module ActionWebService # :nodoc: module Dispatcher # :nodoc: module ActionController # :nodoc: @@ -7,106 +10,121 @@ module ActionWebService # :nodoc: class << self alias_method :inherited_without_action_controller, :inherited end - alias_method :before_direct_invoke_without_action_controller, :before_direct_invoke - alias_method :after_direct_invoke_without_action_controller, :after_direct_invoke + alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke end base.add_web_service_api_callback do |klass, api| if klass.web_service_dispatching_mode == :direct - klass.class_eval <<-EOS - def api - controller_dispatch_web_service_request - end - EOS + klass.class_eval 'def api; dispatch_web_service_request; end' end end base.add_web_service_definition_callback do |klass, name, info| if klass.web_service_dispatching_mode == :delegated - klass.class_eval <<-EOS - def #{name} - controller_dispatch_web_service_request - end - EOS + klass.class_eval "def #{name}; dispatch_web_service_request; end" end end base.extend(ClassMethods) - base.send(:include, ActionWebService::Dispatcher::ActionController::Invocation) + base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods) end - module ClassMethods # :nodoc: + module ClassMethods def inherited(child) inherited_without_action_controller(child) - child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlGeneration) + child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction) end end - module Invocation # :nodoc: + module InstanceMethods private - def controller_dispatch_web_service_request - request, response, elapsed, exception = dispatch_web_service_request(@request) - if response - begin - log_request(request) - log_error(exception) if exception && logger - log_response(response, elapsed) - response_options = { :type => response.content_type, :disposition => 'inline' } - send_data(response.raw_body, response_options) - rescue Exception => e - log_error(e) unless logger.nil? - render_text("Internal protocol error", "500 Internal Server Error") + def dispatch_web_service_request + request = discover_web_service_request(@request) + if request + log_request(request, @request.raw_post) + response = nil + exception = nil + bm = Benchmark.measure do + begin + response = invoke_web_service_request(request) + rescue Exception => e + exception = e + end + end + if exception + log_error(exception) unless logger.nil? + send_web_service_error_response(request, exception) + else + send_web_service_response(response, bm.real) end else - logger.error("No response available") unless logger.nil? - render_text("Internal protocol error", "500 Internal Server Error") + exception = DispatcherError.new("Malformed SOAP or XML-RPC protocol message") + send_web_service_error_response(request, exception) end + rescue Exception => e + log_error(e) unless logger.nil? + send_web_service_error_response(request, e) end - def before_direct_invoke(request) - before_direct_invoke_without_action_controller(request) - @params ||= {} - signature = request.signature - if signature && (expects = request.signature[:expects]) - (0..(@method_params.size-1)).each do |i| - if expects[i].is_a?(Hash) - @params[expects[i].keys[0].to_s] = @method_params[i] - else - @params['param%d' % i] = @method_params[i] - end + def send_web_service_response(response, elapsed=nil) + log_response(response, elapsed) + options = { :type => response.content_type, :disposition => 'inline' } + send_data(response.body, options) + end + + def send_web_service_error_response(request, exception) + if request + unless self.class.web_service_exception_reporting + exception = DispatcherError.new("Internal server error (exception raised)") end + response = request.protocol.marshal_response(request.method_name, exception, exception.class) + send_web_service_response(response) + else + if self.class.web_service_exception_reporting + message = exception.message + else + message = "Exception raised" + end + render_text("Internal protocol error: #{message}", "500 #{message}") end - @params['action'] = request.method_name.to_s - @session ||= {} - @assigns ||= {} - return nil if before_action == false - true end - def after_direct_invoke(request) - after_direct_invoke_without_action_controller(request) + def web_service_direct_invoke(invocation) + @params ||= {} + invocation.method_named_params.each do |name, value| + @params[name] = value + end + @session ||= {} + @assigns ||= {} + @params['action'] = invocation.api_method_name.to_s + if before_action == false + raise(DispatcherError, "Method filtered") + end + return_value = web_service_direct_invoke_without_controller(invocation) after_action + return_value end - def log_request(request) - unless logger.nil? || request.nil? - logger.debug("\nWeb Service Request:") - indented = request.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n") - logger.debug(indented) + def log_request(request, body) + unless logger.nil? + name = request.method_name + params = request.method_params.map{|x| x.value.inspect} + service = request.service_name + logger.debug("\nWeb Service Request: #{name}(#{params}) #{service}") + logger.debug(indent(body)) end end - def log_response(response, elapsed) - unless logger.nil? || response.nil? - logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":")) - indented = response.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n") - logger.debug(indented) + def log_response(response, elapsed=nil) + unless logger.nil? + logger.debug("\nWeb Service Response (%f):" + (elapsed ? " (%f):" % elapsed : ":")) + logger.debug(indent(response.body)) end end - unless method_defined?(:logger) - def logger; @logger; end + def indent(body) + body.split(/\n/).map{|x| " #{x}"}.join("\n") end end - module WsdlGeneration # :nodoc: + module WsdlAction XsdNs = 'http://www.w3.org/2001/XMLSchema' WsdlNs = 'http://schemas.xmlsoap.org/wsdl/' SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/' @@ -117,40 +135,53 @@ module ActionWebService # :nodoc: case @request.method when :get begin - host_name = @request.env['HTTP_HOST'] || @request.env['SERVER_NAME'] - uri = "http://#{host_name}/#{controller_name}/" - soap_action_base = "/#{controller_name}" - xml = to_wsdl(self, uri, soap_action_base) - send_data(xml, :type => 'text/xml', :disposition => 'inline') + options = { :type => 'text/xml', :disposition => 'inline' } + send_data(to_wsdl, options) rescue Exception => e - log_error e unless logger.nil? - render_text('', "500 #{e.message}") + log_error(e) unless logger.nil? end when :post - render_text('', "500 POST not supported") + render_text('POST not supported', '500 POST not supported') end end private - def to_wsdl(container, uri, soap_action_base) - wsdl = "" - - web_service_dispatching_mode = container.web_service_dispatching_mode - mapper = container.class.soap_mapper - namespace = mapper.custom_namespace - wsdl_service_name = namespace.split(/:/)[1] - - services = {} - mapper.map_container_services(container) do |name, api, api_methods| - services[name] = [api, api_methods] + def base_uri + host = @request ? (@request.env['HTTP_HOST'] || @request.env['SERVER_NAME']) : 'localhost' + 'http://%s/%s/' % [host, controller_name] + end + + def to_wsdl + xml = '' + dispatching_mode = web_service_dispatching_mode + global_service_name = wsdl_service_name + namespace = "urn:#{global_service_name}" + soap_action_base = "/#{controller_name}" + + marshaler = WS::Marshaling::SoapMarshaler.new(namespace) + apis = {} + case dispatching_mode + when :direct + api = self.class.web_service_api + web_service_name = controller_class_name.sub(/Controller$/, '').underscore + apis[web_service_name] = [api, register_api(marshaler, api)] + when :delegated + self.class.web_services.each do |web_service_name, info| + service = web_service_object(web_service_name) + api = service.class.web_service_api + apis[web_service_name] = [api, register_api(marshaler, api)] + end end - custom_types = mapper.custom_types - - - xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2) + custom_types = [] + apis.values.each do |api, bindings| + bindings.each do |b| + custom_types << b if b.is_custom_type? + end + end + + xm = Builder::XmlMarkup.new(:target => xml, :indent => 2) xm.instruct! - - xm.definitions('name' => wsdl_service_name, + xm.definitions('name' => wsdl_service_name, 'targetNamespace' => namespace, 'xmlns:typens' => namespace, 'xmlns:xsd' => XsdNs, @@ -158,95 +189,95 @@ module ActionWebService # :nodoc: 'xmlns:soapenc' => SoapEncodingNs, 'xmlns:wsdl' => WsdlNs, 'xmlns' => WsdlNs) do - - # Custom type XSD generation + # Generate XSD if custom_types.size > 0 xm.types do xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do - custom_types.each do |klass, mapping| + custom_types.each do |binding| case - when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping) - xm.xsd(:complexType, 'name' => mapping.type_name) do + when binding.is_typed_array? + xm.xsd(:complexType, 'name' => binding.type_name) do xm.xsd(:complexContent) do xm.xsd(:restriction, 'base' => 'soapenc:Array') do xm.xsd(:attribute, 'ref' => 'soapenc:arrayType', - 'wsdl:arrayType' => mapping.element_mapping.qualified_type_name + '[]') + 'wsdl:arrayType' => binding.element_binding.qualified_type_name + '[]') end end end - when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping) - xm.xsd(:complexType, 'name' => mapping.type_name) do + when binding.is_typed_struct? + xm.xsd(:complexType, 'name' => binding.type_name) do xm.xsd(:all) do - mapping.each_attribute do |name, type_name| + binding.each_member do |name, type_name| xm.xsd(:element, 'name' => name, 'type' => type_name) end end end - else - raise(WsdlError, "unsupported mapping type #{mapping.class.name}") end end end end end - - services.each do |service_name, service_values| - service_api, api_methods = service_values - # Parameter list message definitions - api_methods.each do |method_name, method_signature| + + # APIs + apis.each do |api_name, values| + api = values[0] + api.api_methods.each do |name, info| gen = lambda do |msg_name, direction| xm.message('name' => msg_name) do sym = nil if direction == :out - if method_signature[:returns] - xm.part('name' => 'return', 'type' => method_signature[:returns][0].qualified_type_name) + returns = info[:returns] + if returns + binding = marshaler.register_type(returns[0]) + xm.part('name' => 'return', 'type' => binding.qualified_type_name) end else - mapping_list = method_signature[:expects] + expects = info[:expects] i = 1 - mapping_list.each do |mapping| - if mapping.is_a?(Hash) - param_name = mapping.keys.shift - mapping = mapping.values.shift + expects.each do |type| + if type.is_a?(Hash) + param_name = type.keys.shift + type = type.values.shift else param_name = "param#{i}" end - xm.part('name' => param_name, 'type' => mapping.qualified_type_name) + binding = marshaler.register_type(type) + xm.part('name' => param_name, 'type' => binding.qualified_type_name) i += 1 - end if mapping_list + end if expects end end end - public_name = service_api.public_api_method_name(method_name) + public_name = api.public_api_method_name(name) gen.call(public_name, :in) gen.call("#{public_name}Response", :out) end - - # Declare the port - port_name = port_name_for(wsdl_service_name, service_name) + + # Port + port_name = port_name_for(global_service_name, api_name) xm.portType('name' => port_name) do - api_methods.each do |method_name, method_signature| - public_name = service_api.public_api_method_name(method_name) + api.api_methods.each do |name, info| + public_name = api.public_api_method_name(name) xm.operation('name' => public_name) do xm.input('message' => "typens:#{public_name}") xm.output('message' => "typens:#{public_name}Response") end end end - - # Bind the port to SOAP - binding_name = binding_name_for(wsdl_service_name, service_name) + + # Bind it + binding_name = binding_name_for(global_service_name, api_name) xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport) - api_methods.each do |method_name, method_signature| - public_name = service_api.public_api_method_name(method_name) + api.api_methods.each do |name, info| + public_name = api.public_api_method_name(name) xm.operation('name' => public_name) do case web_service_dispatching_mode when :direct soap_action = soap_action_base + "/api/" + public_name when :delegated soap_action = soap_action_base \ - + "/" + service_name.to_s \ + + "/" + api_name.to_s \ + "/" + public_name end xm.soap(:operation, 'soapAction' => soap_action) @@ -266,32 +297,46 @@ module ActionWebService # :nodoc: end end end - - # Define the service - xm.service('name' => "#{wsdl_service_name}Service") do - services.each do |service_name, service_values| - port_name = port_name_for(wsdl_service_name, service_name) - binding_name = binding_name_for(wsdl_service_name, service_name) + + # Define it + xm.service('name' => "#{global_service_name}Service") do + apis.each do |api_name, values| + port_name = port_name_for(global_service_name, api_name) + binding_name = binding_name_for(global_service_name, api_name) case web_service_dispatching_mode when :direct binding_target = 'api' when :delegated - binding_target = service_name.to_s + binding_target = api_name.to_s end xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do - xm.soap(:address, 'location' => "#{uri}#{binding_target}") + xm.soap(:address, 'location' => "#{base_uri}#{binding_target}") end end end end end - def port_name_for(wsdl_service_name, service_name) - "#{wsdl_service_name}#{service_name.to_s.camelize}Port" + def port_name_for(global_service, service) + "#{global_service}#{service.to_s.camelize}Port" + end + + def binding_name_for(global_service, service) + "#{global_service}#{service.to_s.camelize}Binding" end - def binding_name_for(wsdl_service_name, service_name) - "#{wsdl_service_name}#{service_name.to_s.camelize}Binding" + def register_api(marshaler, api) + type_bindings = [] + api.api_methods.each do |name, info| + expects, returns = info[:expects], info[:returns] + if expects + expects.each{|type| type_bindings << marshaler.register_type(type)} + end + if returns + returns.each{|type| type_bindings << marshaler.register_type(type)} + end + end + type_bindings end end end diff --git a/actionwebservice/lib/action_web_service/protocol.rb b/actionwebservice/lib/action_web_service/protocol.rb index b15e850676..053e9cb4be 100644 --- a/actionwebservice/lib/action_web_service/protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol.rb @@ -1,4 +1,4 @@ require 'action_web_service/protocol/abstract' -require 'action_web_service/protocol/registry' +require 'action_web_service/protocol/discovery' require 'action_web_service/protocol/soap_protocol' require 'action_web_service/protocol/xmlrpc_protocol' diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index 9199dfe33f..f628fc4aee 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -1,126 +1,28 @@ module ActionWebService # :nodoc: module Protocol # :nodoc: - CheckedMessage = :checked - UncheckedMessage = :unchecked - - class ProtocolError < ActionWebService::ActionWebServiceError # :nodoc: - end - - class AbstractProtocol # :nodoc: - attr :container_class - - def initialize(container_class) - @container_class = container_class - end - - def unmarshal_request(protocol_request) - raise NotImplementedError - end - - def marshal_response(protocol_request, return_value) - raise NotImplementedError - end - - def marshal_exception(exception) - raise NotImplementedError - end - - def self.create_protocol_request(container_class, action_pack_request) - nil - end - - def self.create_protocol_client(api, protocol_name, endpoint_uri, options) - nil - end - end - - class AbstractProtocolMessage # :nodoc: - attr_accessor :signature - attr_accessor :return_signature - attr_accessor :type - attr :options - - def initialize(options={}) - @signature = @return_signature = nil - @options = options - @type = @options[:type] || CheckedMessage - end - - def signature=(value) - return if value.nil? - @signature = [] - value.each do |klass| - if klass.is_a?(Hash) - @signature << klass.values.shift - else - @signature << klass - end - end - @signature - end - - def checked? - @type == CheckedMessage - end - - def check_parameter_types(values, signature) - return unless checked? && signature - unless signature.length == values.length - raise(ProtocolError, "Signature and parameter lengths mismatch") - end - (1..signature.length).each do |i| - check_compatibility(signature[i-1], values[i-1].class) - end - end - - def check_compatibility(expected_class, received_class) - return if \ - (expected_class == TrueClass or expected_class == FalseClass) and \ - (received_class == TrueClass or received_class == FalseClass) - unless received_class.ancestors.include?(expected_class) or \ - expected_class.ancestors.include?(received_class) - raise(ProtocolError, "value of type #{received_class.name} is not " + - "compatible with expected type #{expected_class.name}") - end - end + class ProtocolError < ActionWebService::ActionWebServiceError end - class ProtocolRequest < AbstractProtocolMessage # :nodoc: + class Request attr :protocol - attr :raw_body + attr :method_name + attr :method_params + attr :service_name - attr_accessor :web_service_name - attr_accessor :public_method_name - attr_accessor :content_type - - def initialize(protocol, raw_body, web_service_name, public_method_name, content_type, options={}) - super(options) + def initialize(protocol, method_name, method_params, service_name) @protocol = protocol - @raw_body = raw_body - @web_service_name = web_service_name - @public_method_name = public_method_name - @content_type = content_type - end - - def unmarshal - @protocol.unmarshal_request(self) - end - - def marshal(return_value) - @protocol.marshal_response(self, return_value) + @method_name = method_name + @method_params = method_params + @service_name = service_name end end - class ProtocolResponse < AbstractProtocolMessage # :nodoc: - attr :protocol - attr :raw_body - - attr_accessor :content_type + class Response + attr :body + attr :content_type - def initialize(protocol, raw_body, content_type, options={}) - super(options) - @protocol = protocol - @raw_body = raw_body + def initialize(body, content_type) + @body = body @content_type = content_type end end diff --git a/actionwebservice/lib/action_web_service/protocol/discovery.rb b/actionwebservice/lib/action_web_service/protocol/discovery.rb new file mode 100644 index 0000000000..ab51958ed9 --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/discovery.rb @@ -0,0 +1,37 @@ +module ActionWebService + module Protocol + module Discovery + def self.included(base) + base.extend(ClassMethods) + base.send(:include, ActionWebService::Protocol::Discovery::InstanceMethods) + end + + module ClassMethods + def register_protocol(klass) + write_inheritable_array("web_service_protocols", [klass]) + end + end + + module InstanceMethods + private + def discover_web_service_request(ap_request) + (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| + protocol = protocol.new + request = protocol.unmarshal_request(ap_request) + return request unless request.nil? + end + nil + end + + def create_web_service_client(api, protocol_name, endpoint_uri, options) + (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| + protocol = protocol.new + client = protocol.protocol_client(api, protocol_name, endpoint_uri, options) + return client unless client.nil? + end + nil + end + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/protocol/registry.rb b/actionwebservice/lib/action_web_service/protocol/registry.rb deleted file mode 100644 index 0173673556..0000000000 --- a/actionwebservice/lib/action_web_service/protocol/registry.rb +++ /dev/null @@ -1,55 +0,0 @@ -module ActionWebService # :nodoc: - module Protocol # :nodoc: - HeaderAndBody = :header_and_body - BodyOnly = :body_only - - module Registry # :nodoc: - def self.append_features(base) # :nodoc: - super - base.extend(ClassMethods) - base.send(:include, ActionWebService::Protocol::Registry::InstanceMethods) - end - - module ClassMethods # :nodoc: - def register_protocol(type, klass) # :nodoc: - case type - when HeaderAndBody - write_inheritable_array("header_and_body_protocols", [klass]) - when BodyOnly - write_inheritable_array("body_only_protocols", [klass]) - else - raise(ProtocolError, "unknown protocol type #{type}") - end - end - end - - module InstanceMethods # :nodoc: - private - def probe_request_protocol(action_pack_request) - (header_and_body_protocols + body_only_protocols).each do |protocol| - protocol_request = protocol.create_protocol_request(self.class, action_pack_request) - return protocol_request if protocol_request - end - raise(ProtocolError, "unsupported request message format") - end - - def probe_protocol_client(api, protocol_name, endpoint_uri, options) - (header_and_body_protocols + body_only_protocols).each do |protocol| - protocol_client = protocol.create_protocol_client(api, protocol_name, endpoint_uri, options) - return protocol_client if protocol_client - end - raise(ProtocolError, "unsupported client protocol :#{protocol_name}") - end - - def header_and_body_protocols - self.class.read_inheritable_attribute("header_and_body_protocols") || [] - end - - def body_only_protocols - self.class.read_inheritable_attribute("body_only_protocols") || [] - end - end - - end - end -end diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb index 3c527fea93..f2e761f431 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -1,127 +1,49 @@ -require 'soap/processor' -require 'soap/mapping' -require 'soap/rpc/element' -require 'xsd/datatypes' -require 'xsd/ns' -require 'singleton' - -module ActionWebService # :nodoc: - module Protocol # :nodoc: - module Soap # :nodoc: - class ProtocolError < ActionWebService::ActionWebServiceError # :nodoc: - end - - def self.append_features(base) # :nodoc: - super - base.register_protocol(HeaderAndBody, SoapProtocol) - base.extend(ClassMethods) - base.wsdl_service_name('ActionWebService') - end - - module ClassMethods - # Specifies the WSDL service name to use when generating WSDL. Highly - # recommended that you set this value, or code generators may generate - # classes with very generic names. - # - # === Example - # class MyController < ActionController::Base - # wsdl_service_name 'MyService' - # end - def wsdl_service_name(name) - write_inheritable_attribute("soap_mapper", SoapMapper.new("urn:#{name}")) - end - - def soap_mapper # :nodoc: - read_inheritable_attribute("soap_mapper") - end +module ActionWebService + module Protocol + module Soap + def self.included(base) + base.register_protocol(SoapProtocol) + base.class_inheritable_option(:wsdl_service_name) end - - class SoapProtocol < AbstractProtocol # :nodoc: - attr :mapper - - def initialize(mapper) - @mapper = mapper - end - - def self.create_protocol_request(container_class, action_pack_request) - soap_action = extract_soap_action(action_pack_request) - return nil unless soap_action - service_name = action_pack_request.parameters['action'] - public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1] - content_type = action_pack_request.env['HTTP_CONTENT_TYPE'] - content_type ||= 'text/xml' - protocol = SoapProtocol.new(container_class.soap_mapper) - ProtocolRequest.new(protocol, - action_pack_request.raw_post, - service_name.to_sym, - public_method_name, - content_type) - end - - def self.create_protocol_client(api, protocol_name, endpoint_uri, options) - return nil unless protocol_name.to_s.downcase.to_sym == :soap - ActionWebService::Client::Soap.new(api, endpoint_uri, options) - end - - def unmarshal_request(protocol_request) - unmarshal = lambda do - envelope = SOAP::Processor.unmarshal(protocol_request.raw_body) - request = envelope.body.request - values = request.collect{|k, v| request[k]} - soap_to_ruby_array(values) - end - signature = protocol_request.signature - if signature - map_signature_types(signature) - values = unmarshal.call - signature = signature.map{|x|mapper.lookup(x).ruby_klass} - protocol_request.check_parameter_types(values, signature) - values + + class SoapProtocol + def initialize + @encoder = WS::Encoding::SoapRpcEncoding.new + @marshaler = WS::Marshaling::SoapMarshaler.new + end + + def unmarshal_request(ap_request) + return nil unless has_valid_soap_action?(ap_request) + method_name, params = @encoder.decode_rpc_call(ap_request.raw_post) + params = params.map{|x| @marshaler.unmarshal(x)} + service_name = ap_request.parameters['action'] + Request.new(self, method_name, params, service_name) + end + + def marshal_response(method_name, return_value, signature_type) + if !return_value.nil? && signature_type + type_binding = @marshaler.register_type(signature_type) + info = WS::ParamInfo.create(signature_type, 0, type_binding) + return_value = @marshaler.marshal(WS::Param.new(return_value, info)) else - if protocol_request.checked? - [] - else - unmarshal.call - end + return_value = nil end + body = @encoder.encode_rpc_response(method_name, return_value) + Response.new(body, 'text/xml') end - def marshal_response(protocol_request, return_value) - marshal = lambda do |signature| - mapping = mapper.lookup(signature[0]) - return_value = fixup_array_types(mapping, return_value) - signature = signature.map{|x|mapper.lookup(x).ruby_klass} - protocol_request.check_parameter_types([return_value], signature) - param_def = [['retval', 'return', mapping.registry_mapping]] - [param_def, ruby_to_soap(return_value)] - end - signature = protocol_request.return_signature - param_def = nil - if signature - param_def, return_value = marshal.call(signature) - else - if protocol_request.checked? - param_def, return_value = nil, nil - else - param_def, return_value = marshal.call([return_value.class]) - end - end - qname = XSD::QName.new(mapper.custom_namespace, - protocol_request.public_method_name) - response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def) - response.retval = return_value unless return_value.nil? - ProtocolResponse.new(self, create_response(response), 'text/xml') + def register_signature_type(spec) + @marshaler.register_type(spec) end - def marshal_exception(exc) - ProtocolResponse.new(self, create_exception_response(exc), 'text/xml') + def protocol_client(api, protocol_name, endpoint_uri, options) + return nil unless protocol_name == :soap + ActionWebService::Client::Soap.new(api, endpoint_uri, options) end private - def self.extract_soap_action(request) + def has_valid_soap_action?(request) return nil unless request.method == :post - content_type = request.env['HTTP_CONTENT_TYPE'] || 'text/xml' - return nil unless content_type soap_action = request.env['HTTP_SOAPACTION'] return nil unless soap_action soap_action.gsub!(/^"/, '') @@ -130,355 +52,7 @@ module ActionWebService # :nodoc: return nil if soap_action.empty? soap_action end - - def fixup_array_types(mapping, obj) - mapping.each_attribute do |name, type, attr_mapping| - if attr_mapping.custom_type? - attr_obj = obj.send(name) - new_obj = fixup_array_types(attr_mapping, attr_obj) - obj.send("#{name}=", new_obj) unless new_obj.equal?(attr_obj) - end - end - if mapping.is_a?(SoapArrayMapping) - obj = mapping.ruby_klass.new(obj) - # man, this is going to be slow for big arrays :( - (1..obj.size).each do |i| - i -= 1 - obj[i] = fixup_array_types(mapping.element_mapping, obj[i]) - end - else - if !mapping.generated_klass.nil? && mapping.generated_klass.respond_to?(:members) - # have to map the publically visible structure of the class - new_obj = mapping.generated_klass.new - mapping.generated_klass.members.each do |name, klass| - new_obj.send("#{name}=", obj.send(name)) - end - obj = new_obj - end - end - obj - end - - def map_signature_types(types) - types.collect{|type| mapper.map(type)} - end - - def create_response(body) - header = SOAP::SOAPHeader.new - body = SOAP::SOAPBody.new(body) - envelope = SOAP::SOAPEnvelope.new(header, body) - SOAP::Processor.marshal(envelope) - end - - def create_exception_response(exc) - detail = SOAP::Mapping::SOAPException.new(exc) - body = SOAP::SOAPFault.new( - SOAP::SOAPString.new('Server'), - SOAP::SOAPString.new(exc.to_s), - SOAP::SOAPString.new(self.class.name), - SOAP::Mapping.obj2soap(detail)) - create_response(body) - end - - def ruby_to_soap(obj) - SOAP::Mapping.obj2soap(obj, mapper.registry) - end - - def soap_to_ruby(obj) - SOAP::Mapping.soap2obj(obj, mapper.registry) - end - - def soap_to_ruby_array(array) - array.map{|x| soap_to_ruby(x)} - end - end - - class SoapMapper # :nodoc: - attr :registry - attr :custom_namespace - attr :custom_types - - def initialize(custom_namespace) - @custom_namespace = custom_namespace - @registry = SOAP::Mapping::Registry.new - @klass2map = {} - @custom_types = {} - @ar2klass = {} end - - def lookup(klass) - lookup_klass = klass.is_a?(Array) ? klass[0] : klass - generated_klass = nil - unless lookup_klass.respond_to?(:ancestors) - raise(ProtocolError, "expected parameter type definition to be a Class") - end - if lookup_klass.ancestors.include?(ActiveRecord::Base) - generated_klass = @ar2klass.has_key?(klass) ? @ar2klass[klass] : nil - klass = generated_klass if generated_klass - end - return @klass2map[klass] if @klass2map.has_key?(klass) - - custom_type = false - - ruby_klass = select_class(lookup_klass) - generated_klass = @ar2klass[lookup_klass] if @ar2klass.has_key?(lookup_klass) - type_name = ruby_klass.name - - # Array signatures generate a double-mapping and require generation - # of an Array subclass to represent the mapping in the SOAP - # registry - array_klass = nil - if klass.is_a?(Array) - array_klass = Class.new(Array) do - module_eval <<-END - def self.name - "#{type_name}Array" - end - END - end - end - - mapping = @registry.find_mapped_soap_class(ruby_klass) rescue nil - unless mapping - # Custom structured type, generate a mapping - info = { :type => XSD::QName.new(@custom_namespace, type_name) } - @registry.add(ruby_klass, - SOAP::SOAPStruct, - SOAP::Mapping::Registry::TypedStructFactory, - info) - mapping = ensure_mapped(ruby_klass) - custom_type = true - end - - array_mapping = nil - if array_klass - # Typed array always requires a custom type. The info of the array - # is the info of its element type (in mapping[2]), falling back - # to SOAP base types. - info = mapping[2] - info ||= {} - info[:type] ||= soap_base_type_qname(mapping[0]) - @registry.add(array_klass, - SOAP::SOAPArray, - SOAP::Mapping::Registry::TypedArrayFactory, - info) - array_mapping = ensure_mapped(array_klass) - end - - if array_mapping - @klass2map[ruby_klass] = SoapMapping.new(self, - type_name, - ruby_klass, - generated_klass, - mapping[0], - mapping, - custom_type) - @klass2map[klass] = SoapArrayMapping.new(self, - type_name, - array_klass, - array_mapping[0], - array_mapping, - @klass2map[ruby_klass]) - @custom_types[klass] = @klass2map[klass] - @custom_types[ruby_klass] = @klass2map[ruby_klass] if custom_type - else - @klass2map[klass] = SoapMapping.new(self, - type_name, - ruby_klass, - generated_klass, - mapping[0], - mapping, - custom_type) - @custom_types[klass] = @klass2map[klass] if custom_type - end - - @klass2map[klass] - end - alias :map :lookup - - def map_container_services(container, &block) - dispatching_mode = container.web_service_dispatching_mode - web_services = nil - case dispatching_mode - when :direct - api = container.class.web_service_api - if container.respond_to?(:controller_class_name) - web_service_name = container.controller_class_name.sub(/Controller$/, '').underscore - else - web_service_name = container.class.name.demodulize.underscore - end - web_services = { web_service_name => api } - when :delegated - web_services = {} - container.class.web_services.each do |web_service_name, web_service_info| - begin - object = container.web_service_object(web_service_name) - rescue Exception => e - raise(ProtocolError, "failed to retrieve web service object for web service '#{web_service_name}': #{e.message}") - end - web_services[web_service_name] = object.class.web_service_api - end - end - web_services.each do |web_service_name, api| - if api.nil? - raise(ProtocolError, "no web service API set while in :#{dispatching_mode} mode") - end - map_api(api) do |api_methods| - yield web_service_name, api, api_methods if block_given? - end - end - end - - def map_api(api, &block) - lookup_proc = lambda do |klass| - mapping = lookup(klass) - custom_mapping = nil - if mapping.respond_to?(:element_mapping) - custom_mapping = mapping.element_mapping - else - custom_mapping = mapping - end - if custom_mapping && custom_mapping.custom_type? - # What gives? This is required so that structure types - # referenced only by structures (and not signatures) still - # have a custom type mapping in the registry (needed for WSDL - # generation). - custom_mapping.each_attribute{} - end - mapping - end - api_methods = block.nil?? nil : {} - api.api_methods.each do |method_name, method_info| - expects = method_info[:expects] - expects_signature = nil - if expects - expects_signature = block ? [] : nil - expects.each do |klass| - lookup_klass = nil - if klass.is_a?(Hash) - lookup_klass = lookup_proc.call(klass.values[0]) - expects_signature << {klass.keys[0]=>lookup_klass} if block - else - lookup_klass = lookup_proc.call(klass) - expects_signature << lookup_klass if block - end - end - end - returns = method_info[:returns] - returns_signature = returns ? returns.map{|klass| lookup_proc.call(klass)} : nil - if block - api_methods[method_name] = { - :expects => expects_signature, - :returns => returns_signature - } - end - end - yield api_methods if block - end - - private - def select_class(klass) - return Integer if klass == Fixnum - if klass.ancestors.include?(ActiveRecord::Base) - new_klass = Class.new(ActionWebService::Struct) - new_klass.class_eval <<-EOS - def self.name - "#{klass.name}" - end - EOS - klass.columns.each do |column| - next if column.klass.nil? - new_klass.send(:member, column.name.to_sym, column.klass) - end - @ar2klass[klass] = new_klass - return new_klass - end - klass - end - - def ensure_mapped(klass) - mapping = @registry.find_mapped_soap_class(klass) rescue nil - raise(ProtocolError, "failed to register #{klass.name}") unless mapping - mapping - end - - def soap_base_type_qname(base_type) - xsd_type = base_type.ancestors.find{|c| c.const_defined? 'Type'} - xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type - end - end - - class SoapMapping # :nodoc: - attr :ruby_klass - attr :generated_klass - attr :soap_klass - attr :registry_mapping - - def initialize(mapper, type_name, ruby_klass, generated_klass, soap_klass, registry_mapping, - custom_type=false) - @mapper = mapper - @type_name = type_name - @ruby_klass = ruby_klass - @generated_klass = generated_klass - @soap_klass = soap_klass - @registry_mapping = registry_mapping - @custom_type = custom_type - end - - def type_name - @type_name - end - - def custom_type? - @custom_type - end - - def qualified_type_name - name = type_name - if custom_type? - "typens:#{name}" - else - xsd_type_for(@soap_klass) - end - end - - def each_attribute(&block) - if @ruby_klass.respond_to?(:members) - @ruby_klass.members.each do |name, klass| - name = name.to_s - mapping = @mapper.lookup(klass) - yield name, mapping.qualified_type_name, mapping - end - end - end - - def is_xsd_type?(klass) - klass.ancestors.include?(XSD::NSDBase) - end - - def xsd_type_for(klass) - ns = XSD::NS.new - ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag) - xsd_klass = klass.ancestors.find{|c| c.const_defined?('Type')} - return ns.name(XSD::AnyTypeName) unless xsd_klass - ns.name(xsd_klass.const_get('Type')) - end - end - - class SoapArrayMapping < SoapMapping # :nodoc: - attr :element_mapping - - def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping, element_mapping) - super(mapper, type_name, ruby_klass, nil, soap_klass, registry_mapping, true) - @element_mapping = element_mapping - end - - def type_name - super + "Array" - end - - def each_attribute(&block); end - end end end end diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb index 1addccba56..1533a2bdb9 100644 --- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -1,167 +1,47 @@ -require 'xmlrpc/parser' -require 'xmlrpc/create' -require 'xmlrpc/config' -require 'xmlrpc/utils' -require 'singleton' - -module XMLRPC # :nodoc: - class XmlRpcHelper # :nodoc: - include Singleton - include ParserWriterChooseMixin - - def parse_method_call(message) - parser().parseMethodCall(message) - end - - def create_method_response(successful, return_value) - create().methodResponse(successful, return_value) - end - end -end - -module ActionWebService # :nodoc: - module Protocol # :nodoc: - module XmlRpc # :nodoc: - def self.append_features(base) # :nodoc: - super - base.register_protocol(BodyOnly, XmlRpcProtocol) +module ActionWebService + module Protocol + module XmlRpc + def self.included(base) + base.register_protocol(XmlRpcProtocol) end + + class XmlRpcProtocol + attr :marshaler - class XmlRpcProtocol < AbstractProtocol # :nodoc: - def self.create_protocol_request(container_class, action_pack_request) - helper = XMLRPC::XmlRpcHelper.instance - service_name = action_pack_request.parameters['action'] - methodname, params = helper.parse_method_call(action_pack_request.raw_post) - methodname.gsub!(/^[^\.]+\./, '') unless methodname =~ /^system\./ # XXX - protocol = XmlRpcProtocol.new(container_class) - content_type = action_pack_request.env['HTTP_CONTENT_TYPE'] - content_type ||= 'text/xml' - request = ProtocolRequest.new(protocol, - action_pack_request.raw_post, - service_name.to_sym, - methodname, - content_type, - :xmlrpc_values => params) - request - rescue - nil - end - - def self.create_protocol_client(api, protocol_name, endpoint_uri, options) - return nil unless protocol_name.to_s.downcase.to_sym == :xmlrpc - ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options) + def initialize + @encoder = WS::Encoding::XmlRpcEncoding.new + @marshaler = WS::Marshaling::XmlRpcMarshaler.new end - def initialize(container_class) - super(container_class) + def unmarshal_request(ap_request) + method_name, params = @encoder.decode_rpc_call(ap_request.raw_post) + params = params.map{|x| @marshaler.unmarshal(x)} + service_name = ap_request.parameters['action'] + Request.new(self, method_name, params, service_name) + rescue + nil end - def unmarshal_request(protocol_request) - values = protocol_request.options[:xmlrpc_values] - signature = protocol_request.signature - if signature - values = self.class.transform_incoming_method_params(self.class.transform_array_types(signature), values) - protocol_request.check_parameter_types(values, check_array_types(signature)) - values - else - protocol_request.checked? ? [] : values - end - end - - def marshal_response(protocol_request, return_value) - helper = XMLRPC::XmlRpcHelper.instance - signature = protocol_request.return_signature - if signature - protocol_request.check_parameter_types([return_value], check_array_types(signature)) - return_value = self.class.transform_return_value(self.class.transform_array_types(signature), return_value) - raw_response = helper.create_method_response(true, return_value) + def marshal_response(method_name, return_value, signature_type) + if !return_value.nil? && signature_type + type_binding = @marshaler.register_type(signature_type) + info = WS::ParamInfo.create(signature_type, 0, type_binding) + return_value = @marshaler.marshal(WS::Param.new(return_value, info)) else - # XML-RPC doesn't have the concept of a void method, nor does it - # support a nil return value, so return true if we would have returned - # nil - if protocol_request.checked? - raw_response = helper.create_method_response(true, true) - else - return_value = true if return_value.nil? - raw_response = helper.create_method_response(true, return_value) - end + return_value = nil end - ProtocolResponse.new(self, raw_response, 'text/xml') + body = @encoder.encode_rpc_response(method_name, return_value) + Response.new(body, 'text/xml') end - - def marshal_exception(exception) - helper = XMLRPC::XmlRpcHelper.instance - exception = XMLRPC::FaultException.new(1, exception.message) - raw_response = helper.create_method_response(false, exception) - ProtocolResponse.new(self, raw_response, 'text/xml') - end - - class << self - def transform_incoming_method_params(signature, params) - (1..signature.size).each do |i| - i -= 1 - params[i] = xmlrpc_to_ruby(params[i], signature[i]) - end - params - end - - def transform_return_value(signature, return_value) - ruby_to_xmlrpc(return_value, signature[0]) - end - - def ruby_to_xmlrpc(param, param_class) - if param_class.is_a?(XmlRpcArray) - param.map{|p| ruby_to_xmlrpc(p, param_class.klass)} - elsif param_class.ancestors.include?(ActiveRecord::Base) - param.instance_variable_get('@attributes') - elsif param_class.ancestors.include?(ActionWebService::Struct) - struct = {} - param_class.members.each do |name, klass| - value = param.send(name) - next if value.nil? - struct[name.to_s] = value - end - struct - else - param - end - end - def xmlrpc_to_ruby(param, param_class) - if param_class.is_a?(XmlRpcArray) - param.map{|p| xmlrpc_to_ruby(p, param_class.klass)} - elsif param_class.ancestors.include?(ActiveRecord::Base) - raise(ProtocolError, "incoming ActiveRecord::Base types are not allowed") - elsif param_class.ancestors.include?(ActionWebService::Struct) - unless param.is_a?(Hash) - raise(ProtocolError, "expected parameter to be a Hash") - end - new_param = param_class.new - param_class.members.each do |name, klass| - new_param.send('%s=' % name.to_s, param[name.to_s]) - end - new_param - else - param - end - end + def register_signature_type(spec) + nil + end - def transform_array_types(signature) - signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x} - end + def protocol_client(api, protocol_name, endpoint_uri, options) + return nil unless protocol_name == :xmlrpc + ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options) end - - private - def check_array_types(signature) - signature.map{|x| x.is_a?(Array) ? Array : x} - end - - class XmlRpcArray - attr :klass - def initialize(klass) - @klass = klass - end - end end end end diff --git a/actionwebservice/lib/action_web_service/struct.rb b/actionwebservice/lib/action_web_service/struct.rb index 5420f4cf49..77f4fbf4aa 100644 --- a/actionwebservice/lib/action_web_service/struct.rb +++ b/actionwebservice/lib/action_web_service/struct.rb @@ -35,12 +35,10 @@ module ActionWebService end class << self - include ActionWebService::Signature - # Creates a structure member with the specified +name+ and +type+. Generates # accessor methods for reading and writing the member value. def member(name, type) - write_inheritable_hash("struct_members", name => signature_parameter_class(type)) + write_inheritable_hash("struct_members", name => WS::BaseTypes.canonical_param_type_class(type)) class_eval <<-END def #{name}; @#{name}; end def #{name}=(value); @#{name} = value; end diff --git a/actionwebservice/lib/action_web_service/support/signature.rb b/actionwebservice/lib/action_web_service/support/signature.rb deleted file mode 100644 index 00c62a2232..0000000000 --- a/actionwebservice/lib/action_web_service/support/signature.rb +++ /dev/null @@ -1,100 +0,0 @@ -module ActionWebService # :nodoc: - # Action Web Service parameter specifiers may contain symbols or strings - # instead of Class objects, for a limited set of base types. - # - # This provides an unambiguous way to specify that a given parameter - # contains an integer or boolean value, for example. - # - # The allowed set of symbol/string aliases: - # - # [<tt>:int</tt>] any integer value - # [<tt>:float</tt>] any floating point value - # [<tt>:string</tt>] any string value - # [<tt>:bool</tt>] any boolean value - # [<tt>:time</tt>] any value containing both date and time - # [<tt>:date</tt>] any value containing only a date - module Signature - class SignatureError < StandardError # :nodoc: - end - - private - def canonical_signature(params) - return nil if params.nil? - params.map do |param| - klass = signature_parameter_class(param) - if param.is_a?(Hash) - param[param.keys[0]] = klass - param - else - klass - end - end - end - - def signature_parameter_class(param) - param = param.is_a?(Hash) ? param.values[0] : param - is_array = param.is_a?(Array) - param = is_array ? param[0] : param - param = param.is_a?(String) ? param.to_sym : param - param = param.is_a?(Symbol) ? signature_ruby_class(param) : param - is_array ? [param] : param - end - - - def canonical_signature_base_type(base_type) - base_type = base_type.to_sym - case base_type - when :int, :integer, :fixnum, :bignum - :int - when :string, :base64 - :string - when :bool, :boolean - :bool - when :float, :double - :float - when :time, :datetime, :timestamp - :time - when :date - :date - else - raise(SignatureError, ":#{base_type} is not an ActionWebService base type") - end - end - - def signature_ruby_class(base_type) - case canonical_signature_base_type(base_type) - when :int - Integer - when :string - String - when :bool - TrueClass - when :float - Float - when :time - Time - when :date - Date - end - end - - def signature_base_type(ruby_class) - case ruby_class - when Bignum, Integer, Fixnum - :int - when String - :string - when TrueClass, FalseClass - :bool - when Float, Numeric, Precision - :float - when Time, DateTime - :time - when Date - :date - else - raise(SignatureError, "#{ruby_class.name} is not an ActionWebService base type") - end - end - end -end diff --git a/actionwebservice/lib/action_web_service/vendor/ws.rb b/actionwebservice/lib/action_web_service/vendor/ws.rb new file mode 100644 index 0000000000..18a32a555e --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws.rb @@ -0,0 +1,4 @@ +require 'ws/common' +require 'ws/types' +require 'ws/marshaling' +require 'ws/encoding' diff --git a/actionwebservice/lib/action_web_service/vendor/ws/common.rb b/actionwebservice/lib/action_web_service/vendor/ws/common.rb new file mode 100644 index 0000000000..4266a7141d --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/common.rb @@ -0,0 +1,8 @@ +module WS + class WSError < StandardError + end + + def self.derived_from?(ancestor, child) + child.ancestors.include?(ancestor) + end +end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb new file mode 100644 index 0000000000..790317639b --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb @@ -0,0 +1,3 @@ +require 'ws/encoding/abstract' +require 'ws/encoding/soap_rpc_encoding' +require 'ws/encoding/xmlrpc_encoding' diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb new file mode 100644 index 0000000000..257c7d0993 --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb @@ -0,0 +1,26 @@ +module WS + module Encoding + # Encoders operate on _foreign_ objects. That is, Ruby object + # instances that are the _marshaling format specific_ representation + # of objects. In other words, objects that have not yet been marshaled, but + # are in protocol-specific form (such as an AST or DOM element), and not + # native Ruby form. + class AbstractEncoding + def encode_rpc_call(method_name, params) + raise NotImplementedError + end + + def decode_rpc_call(obj) + raise NotImplementedError + end + + def encode_rpc_response(method_name, return_value) + raise NotImplementedError + end + + def decode_rpc_response(obj) + raise NotImplementedError + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb new file mode 100644 index 0000000000..f4d2f5a7d6 --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb @@ -0,0 +1,90 @@ +require 'soap/processor' +require 'soap/mapping' +require 'soap/rpc/element' + +module WS + module Encoding + class SoapRpcError < WSError + end + + class SoapRpcEncoding < AbstractEncoding + attr_accessor :method_namespace + + def initialize(method_namespace='') + @method_namespace = method_namespace + end + + def encode_rpc_call(method_name, foreign_params) + qname = create_method_qname(method_name) + param_def = [] + params = foreign_params.map do |p| + param_def << ['in', p.param.info.name, p.param.info.data.mapping] + [p.param.info.name, p.soap_object] + end + request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def) + request.set_param(params) + envelope = create_soap_envelope(request) + SOAP::Processor.marshal(envelope) + end + + def decode_rpc_call(obj) + envelope = SOAP::Processor.unmarshal(obj) + unless envelope + raise(SoapRpcError, "Malformed SOAP request") + end + request = envelope.body.request + method_name = request.elename.name + params = request.collect do |key, value| + info = ParamInfo.new(key, nil, nil) + param = Param.new(nil, info) + Marshaling::SoapForeignObject.new(param, request[key]) + end + [method_name, params] + end + + def encode_rpc_response(method_name, return_value) + response = nil + qname = create_method_qname(method_name) + if return_value.nil? + response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) + else + param = return_value.param + soap_object = return_value.soap_object + param_def = [['retval', 'return', param.info.data.mapping]] + if soap_object.is_a?(SOAP::SOAPFault) + response = soap_object + else + response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def) + response.retval = soap_object + end + end + envelope = create_soap_envelope(response) + SOAP::Processor.marshal(envelope) + end + + def decode_rpc_response(obj) + envelope = SOAP::Processor.unmarshal(obj) + unless envelope + raise(SoapRpcError, "Malformed SOAP response") + end + method_name = envelope.body.request.elename.name + return_value = envelope.body.response + info = ParamInfo.new('return', nil, nil) + param = Param.new(nil, info) + return_value = Marshaling::SoapForeignObject.new(param, return_value) + [method_name, return_value] + end + + private + def create_soap_envelope(body) + header = SOAP::SOAPHeader.new + body = SOAP::SOAPBody.new(body) + SOAP::SOAPEnvelope.new(header, body) + end + + def create_method_qname(method_name) + XSD::QName.new(@method_namespace, method_name) + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb new file mode 100644 index 0000000000..b38ae81abf --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb @@ -0,0 +1,53 @@ +require 'xmlrpc/marshal' + +module WS + module Encoding + class XmlRpcError < WSError + end + + class XmlRpcEncoding < AbstractEncoding + def encode_rpc_call(method_name, params) + XMLRPC::Marshal.dump_call(method_name, *params) + end + + def decode_rpc_call(obj) + method_name, params = XMLRPC::Marshal.load_call(obj) rescue nil + unless method_name && params + raise(XmlRpcError, "Malformed XML-RPC request") + end + i = 0 + params = params.map do |value| + param = XmlRpcDecodedParam.new("param#{i}", value) + i += 1 + param + end + [method_name, params] + end + + def encode_rpc_response(method_name, return_value) + if return_value.nil? + XMLRPC::Marshal.dump_response(true) + else + XMLRPC::Marshal.dump_response(return_value) + end + end + + def decode_rpc_response(obj) + return_value = XMLRPC::Marshal.load_response(obj) rescue nil + if return_value.nil? + raise(XmlRpcError, "Malformed XML-RPC response") + end + [nil, XmlRpcDecodedParam.new('return', return_value)] + end + end + + class XmlRpcDecodedParam + attr :param + + def initialize(name, value) + info = ParamInfo.new(name, value.class) + @param = Param.new(value, info) + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb new file mode 100644 index 0000000000..3a0a2e8cc1 --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb @@ -0,0 +1,3 @@ +require 'ws/marshaling/abstract' +require 'ws/marshaling/soap_marshaling' +require 'ws/marshaling/xmlrpc_marshaling' diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb new file mode 100644 index 0000000000..53120e1447 --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb @@ -0,0 +1,17 @@ +module WS + module Marshaling + class AbstractMarshaler + def marshal(param) + raise NotImplementedError + end + + def unmarshal(param) + raise NotImplementedError + end + + def register_type(type) + nil + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb new file mode 100644 index 0000000000..99e2a7ff28 --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb @@ -0,0 +1,224 @@ +require 'soap/mapping' +require 'xsd/ns' + +module WS + module Marshaling + SoapEncodingNS = 'http://schemas.xmlsoap.org/soap/encoding/' + + class SoapError < WSError + end + + class SoapMarshaler < AbstractMarshaler + attr :registry + attr_accessor :type_namespace + + def initialize(type_namespace='') + @type_namespace = type_namespace + @registry = SOAP::Mapping::Registry.new + @spec2binding = {} + end + + def marshal(param) + if param.info.type.is_a?(Array) + (class << param.value; self; end).class_eval do + define_method(:arytype) do + param.info.data.qname + end + end + end + if param.value.is_a?(Exception) + detail = SOAP::Mapping::SOAPException.new(param.value) + soap_obj = SOAP::SOAPFault.new( + SOAP::SOAPString.new('Server'), + SOAP::SOAPString.new(param.value.to_s), + SOAP::SOAPString.new(self.class.name), + SOAP::Mapping.obj2soap(detail)) + else + soap_obj = SOAP::Mapping.obj2soap(param.value, @registry) + end + SoapForeignObject.new(param, soap_obj) + end + + def unmarshal(obj) + param = obj.param + soap_object = obj.soap_object + soap_type = soap_object ? soap_object.type : nil + value = soap_object ? SOAP::Mapping.soap2obj(soap_object, @registry) : nil + param.value = value + param.info.type = value.class + mapping = @registry.find_mapped_soap_class(param.info.type) rescue nil + if soap_type && soap_type.name == 'Array' && soap_type.namespace == SoapEncodingNS + param.info.data = SoapBinding.new(soap_object.arytype, mapping) + else + param.info.data = SoapBinding.new(soap_type, mapping) + end + param + end + + def register_type(spec) + if @spec2binding.has_key?(spec) + return @spec2binding[spec] + end + + klass = BaseTypes.canonical_param_type_class(spec) + if klass.is_a?(Array) + type_class = klass[0] + else + type_class = klass + end + + type_binding = nil + if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil) + qname = mapping[2] ? mapping[2][:type] : nil + qname ||= soap_base_type_name(mapping[0]) + type_binding = SoapBinding.new(qname, mapping) + else + qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name)) + @registry.add(type_class, + SOAP::SOAPStruct, + typed_struct_factory(type_class), + { :type => qname }) + mapping = @registry.find_mapped_soap_class(type_class) + type_binding = SoapBinding.new(qname, mapping) + end + + array_binding = nil + if klass.is_a?(Array) + array_mapping = @registry.find_mapped_soap_class(Array) rescue nil + if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil? + @registry.set(Array, + SOAP::SOAPArray, + SoapTypedArrayFactory.new) + array_mapping = @registry.find_mapped_soap_class(Array) + end + qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name) + 'Array') + array_binding = SoapBinding.new(qname, array_mapping, type_binding) + end + + @spec2binding[spec] = array_binding ? array_binding : type_binding + end + + protected + def typed_struct_factory(type_class) + if Object.const_defined?('ActiveRecord') + if WS.derived_from?(ActiveRecord::Base, type_class) + qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name)) + type_class.instance_variable_set('@qname', qname) + return SoapActiveRecordStructFactory.new + end + end + SOAP::Mapping::Registry::TypedStructFactory + end + + def soap_type_name(type_name) + type_name.gsub(/::/, '..') + end + + def soap_base_type_name(type) + xsd_type = type.ancestors.find{|c| c.const_defined? 'Type'} + xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type + end + end + + class SoapForeignObject + attr_accessor :param + attr_accessor :soap_object + + def initialize(param, soap_object) + @param = param + @soap_object = soap_object + end + end + + class SoapBinding + attr :qname + attr :mapping + attr :element_binding + + def initialize(qname, mapping, element_binding=nil) + @qname = qname + @mapping = mapping + @element_binding = element_binding + end + + def is_custom_type? + is_typed_array? || is_typed_struct? + end + + def is_typed_array? + @mapping[1].is_a?(WS::Marshaling::SoapTypedArrayFactory) + end + + def is_typed_struct? + @mapping[1] == SOAP::Mapping::Registry::TypedStructFactory || \ + @mapping[1].is_a?(WS::Marshaling::SoapActiveRecordStructFactory) + end + + def each_member(&block) + unless is_typed_struct? + raise(SoapError, "not a structured type") + end + end + + def type_name + is_custom_type? ? @qname.name : nil + end + + def qualified_type_name(ns=nil) + if is_custom_type? + "#{ns ? ns : @qname.namespace}:#{@qname.name}" + else + ns = XSD::NS.new + ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag) + xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')} + return ns.name(XSD::AnyTypeName) unless xsd_klass + ns.name(xsd_klass.const_get('Type')) + end + end + end + + class SoapActiveRecordStructFactory < SOAP::Mapping::Factory + def obj2soap(soap_class, obj, info, map) + unless obj.is_a?(ActiveRecord::Base) + return nil + end + soap_obj = soap_class.new(obj.class.instance_variable_get('@qname')) + obj.attributes.each do |key, value| + soap_obj[key] = SOAP::Mapping._obj2soap(value, map) + end + soap_obj + end + + def soap2obj(obj_class, node, info, map) + unless node.type == obj_class.instance_variable_get('@qname') + return false + end + obj = obj_class.new + node.each do |key, value| + obj[key] = value.data + end + obj.instance_variable_set('@new_record', false) + return true, obj + end + end + + class SoapTypedArrayFactory < SOAP::Mapping::Factory + def obj2soap(soap_class, obj, info, map) + unless obj.respond_to?(:arytype) + return nil + end + soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype) + mark_marshalled_obj(obj, soap_obj) + obj.each do |item| + child = SOAP::Mapping._obj2soap(item, map) + soap_obj.add(child) + end + soap_obj + end + + def soap2obj(obj_class, node, info, map) + return false + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb new file mode 100644 index 0000000000..87154f87e1 --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb @@ -0,0 +1,116 @@ +module WS + module Marshaling + class XmlRpcError < WSError + end + + class XmlRpcMarshaler < AbstractMarshaler + def initialize + @caster = BaseTypeCaster.new + @spec2binding = {} + end + + def marshal(param) + transform_outbound(param) + end + + def unmarshal(obj) + obj.param.value = transform_inbound(obj.param) + obj.param + end + + def typed_unmarshal(obj, spec) + param = obj.param + param.info.data = register_type(spec) + param.value = transform_inbound(param) + param + end + + def register_type(spec) + if @spec2binding.has_key?(spec) + return @spec2binding[spec] + end + + klass = BaseTypes.canonical_param_type_class(spec) + type_binding = nil + if klass.is_a?(Array) + type_binding = XmlRpcArrayBinding.new(klass[0]) + else + type_binding = XmlRpcBinding.new(klass) + end + + @spec2binding[spec] = type_binding + end + + def transform_outbound(param) + binding = param.info.data + case binding + when XmlRpcArrayBinding + param.value.map{|x| cast_outbound(x, binding.element_klass)} + when XmlRpcBinding + cast_outbound(param.value, param.info.type) + end + end + + def transform_inbound(param) + return param.value if param.info.data.nil? + binding = param.info.data + param.info.type = binding.klass + case binding + when XmlRpcArrayBinding + param.value.map{|x| cast_inbound(x, binding.element_klass)} + when XmlRpcBinding + cast_inbound(param.value, param.info.type) + end + end + + def cast_outbound(value, klass) + if BaseTypes.base_type?(klass) + @caster.cast(value, klass) + elsif value.is_a?(Exception) + XMLRPC::FaultException.new(2, value.message) + elsif Object.const_defined?('ActiveRecord') && value.is_a?(ActiveRecord::Base) + value.attributes + else + struct = {} + value.instance_variables.each do |name| + key = name.sub(/^@/, '') + struct[key] = value.instance_variable_get(name) + end + struct + end + end + + def cast_inbound(value, klass) + if BaseTypes.base_type?(klass) + value = value.to_time if value.is_a?(XMLRPC::DateTime) + @caster.cast(value, klass) + elsif value.is_a?(XMLRPC::FaultException) + value + else + obj = klass.new + value.each do |name, val| + obj.send('%s=' % name.to_s, val) + end + obj + end + end + end + + class XmlRpcBinding + attr :klass + + def initialize(klass) + @klass = klass + end + end + + class XmlRpcArrayBinding < XmlRpcBinding + attr :element_klass + + def initialize(element_klass) + super(Array) + @element_klass = element_klass + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/types.rb b/actionwebservice/lib/action_web_service/vendor/ws/types.rb new file mode 100644 index 0000000000..24b96dc327 --- /dev/null +++ b/actionwebservice/lib/action_web_service/vendor/ws/types.rb @@ -0,0 +1,162 @@ +require 'time' +require 'date' + +module WS + module BaseTypes + class << self + def type_name_to_class(name) + case canonical_type_name(name) + when :int + Integer + when :string + String + when :bool + TrueClass + when :float + Float + when :time + Time + when :date + Date + end + end + + def class_to_type_name(klass) + if WS.derived_from?(Integer, klass) || WS.derived_from?(Fixnum, klass) || WS.derived_from?(Bignum, klass) + :int + elsif klass == String + :string + elsif klass == TrueClass || klass == FalseClass + :bool + elsif WS.derived_from?(Float, klass) || WS.derived_from?(Precision, klass) || WS.derived_from?(Numeric, klass) + :float + elsif klass == Time || klass == DateTime + :time + elsif klass == Date + :date + else + raise(TypeError, "#{klass} is not a valid base type") + end + end + + def base_type?(klass) + !(canonical_type_class(klass) rescue nil).nil? + end + + def canonical_type_class(klass) + type_name_to_class(class_to_type_name(klass)) + end + + def canonical_param_type_class(spec) + klass = spec.is_a?(Hash) ? spec.values[0] : spec + array_element_class = klass.is_a?(Array) ? klass[0] : nil + klass = array_element_class ? array_element_class : klass + klass = type_name_to_class(klass) if klass.is_a?(Symbol) || klass.is_a?(String) + base_class = canonical_type_class(klass) rescue nil + klass = base_class unless base_class.nil? + array_element_class ? [klass] : klass + end + + def canonical_param_type_spec(spec) + klass = canonical_param_type_class(spec) + spec.is_a?(Hash) ? {spec.keys[0]=>klass} : klass + end + + def canonical_type_name(name) + name = name.to_sym + case name + when :int, :integer, :fixnum, :bignum + :int + when :string, :base64 + :string + when :bool, :boolean + :bool + when :float, :double + :float + when :time, :datetime, :timestamp + :time + when :date + :date + else + raise(TypeError, "#{name} is not a valid base type") + end + end + end + end + + class Param + attr_accessor :value + attr_accessor :info + + def initialize(value, info) + @value = value + @info = info + end + end + + class ParamInfo + attr_accessor :name + attr_accessor :type + attr_accessor :data + + def initialize(name, type, data=nil) + @name = name + @type = type + @data = data + end + + def self.create(spec, index=nil, data=nil) + name = spec.is_a?(Hash) ? spec.keys[0].to_s : (index ? "param#{index}" : nil) + type = BaseTypes.canonical_param_type_class(spec) + ParamInfo.new(name, type, data) + end + end + + class BaseTypeCaster + def initialize + @handlers = {} + install_handlers + end + + def cast(value, klass) + type_class = BaseTypes.canonical_type_class(klass) + return value unless type_class + @handlers[type_class].call(value, type_class) + end + + protected + def install_handlers + handler = method(:cast_base_type) + [:int, :string, :bool, :float, :time, :date].each do |name| + type = BaseTypes.type_name_to_class(name) + @handlers[type] = handler + end + @handlers[Fixnum] = handler + end + + def cast_base_type(value, type_class) + desired_class = BaseTypes.canonical_type_class(type_class) + value_class = BaseTypes.canonical_type_class(value.class) + return value if desired_class == value_class + desired_name = BaseTypes.class_to_type_name(desired_class) + case desired_name + when :int + Integer(value) + when :string + value.to_s + when :bool + return false if value.nil? + value = value.to_s + return true if value == 'true' + return false if value == 'false' + raise(TypeError, "can't convert #{value} to boolean") + when :float + Float(value) + when :time + Time.parse(value.to_s) + when :date + Date.parse(value.to_s) + end + end + end +end diff --git a/actionwebservice/test/abstract_client.rb b/actionwebservice/test/abstract_client.rb index 88f886f05f..0efa1d7ec1 100644 --- a/actionwebservice/test/abstract_client.rb +++ b/actionwebservice/test/abstract_client.rb @@ -20,6 +20,7 @@ module ClientTest api_method :struct_pass, :expects => [[Person]], :returns => [:bool] api_method :client_container, :returns => [:int] api_method :named_parameters, :expects => [{:key=>:string}, {:id=>:int}] + api_method :thrower end class NullLogOut @@ -29,11 +30,11 @@ module ClientTest class Container < ActionController::Base web_service_api API - attr :value_void - attr :value_normal - attr :value_array_return - attr :value_struct_pass - attr :value_named_parameters + attr_accessor :value_void + attr_accessor :value_normal + attr_accessor :value_array_return + attr_accessor :value_struct_pass + attr_accessor :value_named_parameters def initialize @session = @assigns = {} @@ -73,12 +74,8 @@ module ClientTest @value_named_parameters = @method_params end - def protocol_request(request) - probe_request_protocol(request) - end - - def dispatch_request(protocol_request) - dispatch_protocol_request(protocol_request) + def thrower + raise "Hi" end end diff --git a/actionwebservice/test/abstract_dispatcher.rb b/actionwebservice/test/abstract_dispatcher.rb new file mode 100644 index 0000000000..b743afce4c --- /dev/null +++ b/actionwebservice/test/abstract_dispatcher.rb @@ -0,0 +1,294 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +module DispatcherTest + class Node < ActiveRecord::Base + def initialize(*args) + super(*args) + @new_record = false + end + + class << self + def name + "Node" + end + + def columns(*args) + [ + ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'), + ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'), + ActiveRecord::ConnectionAdapters::Column.new('description', nil, 'string'), + ] + end + + def connection + self + end + end + end + + class API < ActionWebService::API::Base + api_method :add, :expects => [:int, :int], :returns => [:int] + api_method :interceptee + api_method :struct_return, :returns => [[Node]] + api_method :void + end + + class DirectAPI < ActionWebService::API::Base + api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int] + api_method :before_filtered + api_method :after_filtered, :returns => [[:int]] + api_method :struct_return, :returns => [[Node]] + api_method :thrower + api_method :void + end + + class Service < ActionWebService::Base + web_service_api API + + before_invocation :do_intercept, :only => [:interceptee] + + attr :added + attr :intercepted + attr :void_called + + def initialize + @void_called = false + end + + def add(a, b) + @added = a + b + end + + def interceptee + @intercepted = false + end + + def struct_return + n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1') + n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2') + [n1, n2] + end + + def void(*args) + @void_called = args + end + + def do_intercept(name, args) + [false, "permission denied"] + end + end + + class AbstractController < ActionController::Base + def generate_wsdl + to_wsdl + end + end + + class DelegatedController < AbstractController + web_service_dispatching_mode :delegated + + web_service(:test_service) { @service ||= Service.new; @service } + end + + class DirectController < AbstractController + web_service_api DirectAPI + web_service_dispatching_mode :direct + + before_filter :alwaysfail, :only => [:before_filtered] + after_filter :alwaysok, :only => [:after_filtered] + + attr :added + attr :before_filter_called + attr :before_filter_target_called + attr :after_filter_called + attr :after_filter_target_called + attr :void_called + + def initialize + @before_filter_called = false + @before_filter_target_called = false + @after_filter_called = false + @after_filter_target_called = false + @void_called = false + end + + def add + @added = @params['a'] + @params['b'] + end + + def before_filtered + @before_filter_target_called = true + end + + def after_filtered + @after_filter_target_called = true + [5, 6, 7] + end + + def thrower + raise "Hi, I'm an exception" + end + + def struct_return + n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1') + n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2') + [n1, n2] + end + + def void + @void_called = @method_params + end + + protected + def alwaysfail + @before_filter_called = true + false + end + + def alwaysok + @after_filter_called = true + end + end +end + +module DispatcherCommonTests + def test_direct_dispatching + assert_equal(70, do_method_call(@direct_controller, 'Add', 20, 50)) + assert_equal(70, @direct_controller.added) + assert(@direct_controller.void_called == false) + case @encoder + when WS::Encoding::SoapRpcEncoding + assert(do_method_call(@direct_controller, 'Void', 3, 4, 5).nil?) + when WS::Encoding::XmlRpcEncoding + assert(do_method_call(@direct_controller, 'Void', 3, 4, 5) == true) + end + assert(@direct_controller.void_called == []) + end + + def test_direct_entrypoint + assert(@direct_controller.respond_to?(:api)) + end + + def test_direct_filtering + assert_equal(false, @direct_controller.before_filter_called) + assert_equal(false, @direct_controller.before_filter_target_called) + do_method_call(@direct_controller, 'BeforeFiltered') + assert_equal(true, @direct_controller.before_filter_called) + assert_equal(false, @direct_controller.before_filter_target_called) + assert_equal(false, @direct_controller.after_filter_called) + assert_equal(false, @direct_controller.after_filter_target_called) + assert_equal([5, 6, 7], do_method_call(@direct_controller, 'AfterFiltered')) + assert_equal(true, @direct_controller.after_filter_called) + assert_equal(true, @direct_controller.after_filter_target_called) + end + + def test_delegated_dispatching + assert_equal(130, do_method_call(@delegated_controller, 'Add', 50, 80)) + service = @delegated_controller.web_service_object(:test_service) + assert_equal(130, service.added) + @delegated_controller.web_service_exception_reporting = true + assert(service.intercepted.nil?) + result = do_method_call(@delegated_controller, 'Interceptee') + assert(service.intercepted.nil?) + assert(is_exception?(result)) + assert_match(/permission denied/, exception_message(result)) + result = do_method_call(@delegated_controller, 'NonExistentMethod') + assert(is_exception?(result)) + assert_match(/NonExistentMethod/, exception_message(result)) + assert(service.void_called == false) + case @encoder + when WS::Encoding::SoapRpcEncoding + assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5).nil?) + when WS::Encoding::XmlRpcEncoding + assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5) == true) + end + assert(service.void_called == []) + end + + def test_garbage_request + [@direct_controller, @delegated_controller].each do |controller| + controller.class.web_service_exception_reporting = true + send_garbage_request = lambda do + request = create_ap_request(controller, 'invalid request body', 'xxx') + response = ActionController::TestResponse.new + controller.process(request, response) + # puts response.body + assert(response.headers['Status'] =~ /^500/) + end + send_garbage_request.call + controller.class.web_service_exception_reporting = false + send_garbage_request.call + end + end + + def test_exception_marshaling + @direct_controller.web_service_exception_reporting = true + result = do_method_call(@direct_controller, 'Thrower') + assert(is_exception?(result)) + assert_equal("Hi, I'm an exception", exception_message(result)) + @direct_controller.web_service_exception_reporting = false + result = do_method_call(@direct_controller, 'Thrower') + assert(exception_message(result) != "Hi, I'm an exception") + end + + def test_ar_struct_return + [@direct_controller, @delegated_controller].each do |controller| + result = do_method_call(controller, 'StructReturn') + case @encoder + when WS::Encoding::SoapRpcEncoding + assert(result[0].is_a?(DispatcherTest::Node)) + assert(result[1].is_a?(DispatcherTest::Node)) + assert_equal('node1', result[0].name) + assert_equal('node2', result[1].name) + when WS::Encoding::XmlRpcEncoding + assert(result[0].is_a?(Hash)) + assert(result[1].is_a?(Hash)) + assert_equal('node1', result[0]['name']) + assert_equal('node2', result[1]['name']) + end + end + end + + protected + def service_name(container) + raise NotImplementedError + end + + def exception_message(obj) + raise NotImplementedError + end + + def is_exception?(obj) + raise NotImplementedError + end + + def do_method_call(container, public_method_name, *params) + mode = container.web_service_dispatching_mode + case mode + when :direct + api = container.class.web_service_api + when :delegated + api = container.web_service_object(service_name(container)).class.web_service_api + end + method_name = api.api_method_name(public_method_name) + info = api.api_methods[method_name] || {} + params = params.dup + ((info[:expects] || []) + (info[:returns] || [])).each do |spec| + @marshaler.register_type(spec) + end + expects = info[:expects] + (0..(params.length-1)).each do |i| + type_binding = @marshaler.register_type(expects ? expects[i] : params[i].class) + info = WS::ParamInfo.create(expects ? expects[i] : params[i].class, i, type_binding) + params[i] = @marshaler.marshal(WS::Param.new(params[i], info)) + end + body = @encoder.encode_rpc_call(public_method_name, params) + # puts body + ap_request = create_ap_request(container, body, public_method_name, *params) + ap_response = ActionController::TestResponse.new + container.process(ap_request, ap_response) + # puts ap_response.body + public_method_name, return_value = @encoder.decode_rpc_response(ap_response.body) + @marshaler.unmarshal(return_value).value + end +end diff --git a/actionwebservice/test/abstract_soap.rb b/actionwebservice/test/abstract_soap.rb deleted file mode 100644 index 351a4f8479..0000000000 --- a/actionwebservice/test/abstract_soap.rb +++ /dev/null @@ -1,58 +0,0 @@ -require File.dirname(__FILE__) + '/abstract_unit' -require 'soap/rpc/element' - -class SoapTestError < StandardError -end - -class AbstractSoapTest < Test::Unit::TestCase - def default_test - end - - protected - def service_name - raise NotImplementedError - end - - def do_soap_call(public_method_name, *args) - mapper = @container.class.soap_mapper - param_def = [] - i = 1 - args.each do |arg| - mapping = mapper.lookup(arg.class) - param_def << ["in", "param#{i}", mapping.registry_mapping] - i += 1 - end - qname = XSD::QName.new('urn:ActionWebService', public_method_name) - request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def) - soap_args = [] - i = 1 - args.each do |arg| - soap_args << ["param#{i}", SOAP::Mapping.obj2soap(arg)] - i += 1 - end - request.set_param(soap_args) - header = SOAP::SOAPHeader.new - body = SOAP::SOAPBody.new(request) - envelope = SOAP::SOAPEnvelope.new(header, body) - raw_request = SOAP::Processor.marshal(envelope) - test_request = ActionController::TestRequest.new - test_request.request_parameters['action'] = service_name - test_request.env['REQUEST_METHOD'] = "POST" - test_request.env['HTTP_CONTENTTYPE'] = 'text/xml' - test_request.env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{public_method_name}" - test_request.env['RAW_POST_DATA'] = raw_request - test_response = ActionController::TestResponse.new - response = yield test_request, test_response - raw_body = response.respond_to?(:body) ? response.body : response.raw_body - envelope = SOAP::Processor.unmarshal(raw_body) - if envelope - if envelope.body.response - SOAP::Mapping.soap2obj(envelope.body.response) - else - nil - end - else - raise(SoapTestError, "empty/invalid body from server") - end - end -end diff --git a/actionwebservice/test/abstract_unit.rb b/actionwebservice/test/abstract_unit.rb index e33c5c9bbe..e8304e3790 100644 --- a/actionwebservice/test/abstract_unit.rb +++ b/actionwebservice/test/abstract_unit.rb @@ -1,4 +1,5 @@ $:.unshift(File.dirname(__FILE__) + '/../lib') +$:.unshift(File.dirname(__FILE__) + '/../lib/action_web_service/vendor') require 'test/unit' require 'action_web_service' diff --git a/actionwebservice/test/api_test.rb b/actionwebservice/test/api_test.rb index b61a4b57c5..a84726f0b5 100644 --- a/actionwebservice/test/api_test.rb +++ b/actionwebservice/test/api_test.rb @@ -41,7 +41,7 @@ class TC_API < Test::Unit::TestCase assert_equal({:expects=>nil, :returns=>[Integer, [String]]}, API.api_methods[:returns]) assert_equal({:expects=>[{:appkey=>Integer}, {:publish=>TrueClass}], :returns=>nil}, API.api_methods[:named_signature]) assert_equal({:expects=>[Integer, String, TrueClass], :returns=>nil}, API.api_methods[:string_types]) - assert_equal({:expects=>[TrueClass, Bignum, String], :returns=>nil}, API.api_methods[:class_types]) + assert_equal({:expects=>[TrueClass, Integer, String], :returns=>nil}, API.api_methods[:class_types]) end def test_not_instantiable @@ -49,4 +49,17 @@ class TC_API < Test::Unit::TestCase API.new end end + + def test_api_errors + assert_raises(ActionWebService::ActionWebServiceError) do + klass = Class.new(ActionWebService::API::Base) do + api_method :test, :expects => [ActiveRecord::Base] + end + end + assert_raises(ActionWebService::ActionWebServiceError) do + klass = Class.new(ActionWebService::API::Base) do + api_method :test, :invalid => [:int] + end + end + end end diff --git a/actionwebservice/test/apis/auto_load_api.rb b/actionwebservice/test/apis/auto_load_api.rb new file mode 100644 index 0000000000..a35bbe3ff7 --- /dev/null +++ b/actionwebservice/test/apis/auto_load_api.rb @@ -0,0 +1,3 @@ +class AutoLoadAPI < ActionWebService::API::Base + api_method :void +end diff --git a/actionwebservice/test/apis/broken_auto_load_api.rb b/actionwebservice/test/apis/broken_auto_load_api.rb new file mode 100644 index 0000000000..139597f9cb --- /dev/null +++ b/actionwebservice/test/apis/broken_auto_load_api.rb @@ -0,0 +1,2 @@ + + diff --git a/actionwebservice/test/client_soap_test.rb b/actionwebservice/test/client_soap_test.rb index 5d62b05c82..94a4f24c26 100644 --- a/actionwebservice/test/client_soap_test.rb +++ b/actionwebservice/test/client_soap_test.rb @@ -12,10 +12,10 @@ module ClientSoapTest test_request.env['HTTP_CONTENTTYPE'] = 'text/xml' test_request.env['HTTP_SOAPACTION'] = req.header['soapaction'][0] test_request.env['RAW_POST_DATA'] = req.body - protocol_request = @controller.protocol_request(test_request) - response = @controller.dispatch_request(protocol_request) + response = ActionController::TestResponse.new + @controller.process(test_request, response) res.header['content-type'] = 'text/xml' - res.body = response.raw_body + res.body = response.body rescue Exception => e $stderr.puts e.message $stderr.puts e.backtrace.join("\n") @@ -24,10 +24,15 @@ module ClientSoapTest class ClientContainer < ActionController::Base web_client_api :client, :soap, "http://localhost:#{PORT}/client/api", :api => ClientTest::API + web_client_api :invalid, :null, "", :api => true def get_client client end + + def get_invalid + invalid + end end class SoapServer < ClientTest::AbstractServer @@ -83,6 +88,7 @@ class TC_ClientSoap < Test::Unit::TestCase def test_client_container assert_equal(50, ClientContainer.new.get_client.client_container) + assert(ClientContainer.new.get_invalid.nil?) end def test_named_parameters @@ -90,4 +96,11 @@ class TC_ClientSoap < Test::Unit::TestCase assert(@client.named_parameters("key", 5).nil?) assert_equal(["key", 5], @container.value_named_parameters) end + + def test_capitalized_method_name + @container.value_normal = nil + assert_equal(5, @client.Normal(5, 6)) + assert_equal([5, 6], @container.value_normal) + @container.value_normal = nil + end end diff --git a/actionwebservice/test/client_xmlrpc_test.rb b/actionwebservice/test/client_xmlrpc_test.rb index cd393fbad6..53b6de51e1 100644 --- a/actionwebservice/test/client_xmlrpc_test.rb +++ b/actionwebservice/test/client_xmlrpc_test.rb @@ -9,12 +9,12 @@ module ClientXmlRpcTest test_request = ActionController::TestRequest.new test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1] test_request.env['REQUEST_METHOD'] = "POST" - test_request.env['HTTP_CONTENTTYPE'] = 'text/xml' + test_request.env['HTTP_CONTENT_TYPE'] = 'text/xml' test_request.env['RAW_POST_DATA'] = req.body - protocol_request = @controller.protocol_request(test_request) - response = @controller.dispatch_request(protocol_request) + response = ActionController::TestResponse.new + @controller.process(test_request, response) res.header['content-type'] = 'text/xml' - res.body = response.raw_body + res.body = response.body rescue Exception => e $stderr.puts e.message $stderr.puts e.backtrace.join("\n") @@ -89,4 +89,16 @@ class TC_ClientXmlRpc < Test::Unit::TestCase assert_equal(true, @client.named_parameters("xxx", 7)) assert_equal(["xxx", 7], @container.value_named_parameters) end + + def test_exception + assert_raises(ActionWebService::Client::ClientError) do + assert(@client.thrower) + end + end + + def test_invalid_signature + assert_raises(ActionWebService::Client::ClientError) do + @client.normal + end + end end diff --git a/actionwebservice/test/container_test.rb b/actionwebservice/test/container_test.rb index 8c66651b64..325d420f24 100644 --- a/actionwebservice/test/container_test.rb +++ b/actionwebservice/test/container_test.rb @@ -1,7 +1,6 @@ require File.dirname(__FILE__) + '/abstract_unit' module ContainerTest - $immediate_service = Object.new $deferred_service = Object.new @@ -22,22 +21,34 @@ module ContainerTest class DirectContainer < ActionController::Base web_service_dispatching_mode :direct - end + end + + class InvalidContainer + include ActionWebService::Container::Direct + end end class TC_Container < Test::Unit::TestCase + include ContainerTest + def setup - @delegate_container = ContainerTest::DelegateContainer.new - @direct_container = ContainerTest::DirectContainer.new + @delegate_container = DelegateContainer.new + @direct_container = DirectContainer.new end def test_registration - assert(ContainerTest::DelegateContainer.has_web_service?(:immediate_service)) - assert(ContainerTest::DelegateContainer.has_web_service?(:deferred_service)) - assert(!ContainerTest::DelegateContainer.has_web_service?(:fake_service)) + assert(DelegateContainer.has_web_service?(:immediate_service)) + assert(DelegateContainer.has_web_service?(:deferred_service)) + assert(!DelegateContainer.has_web_service?(:fake_service)) + assert_raises(ActionWebService::Container::Delegated::ContainerError) do + DelegateContainer.web_service('invalid') + end end def test_service_object + assert_raises(ActionWebService::Container::Delegated::ContainerError) do + @delegate_container.web_service_object(:nonexistent) + end assert(@delegate_container.flag == true) assert(@delegate_container.web_service_object(:immediate_service) == $immediate_service) assert(@delegate_container.previous_flag.nil?) @@ -48,6 +59,15 @@ class TC_Container < Test::Unit::TestCase end def test_direct_container - assert(ContainerTest::DirectContainer.web_service_dispatching_mode == :direct) + assert(DirectContainer.web_service_dispatching_mode == :direct) + end + + def test_validity + assert_raises(ActionWebService::Container::Direct::ContainerError) do + InvalidContainer.web_service_api :test + end + assert_raises(ActionWebService::Container::Direct::ContainerError) do + InvalidContainer.web_service_api 50.0 + end end end diff --git a/actionwebservice/test/dispatcher_action_controller_soap_test.rb b/actionwebservice/test/dispatcher_action_controller_soap_test.rb new file mode 100644 index 0000000000..9cb99be78d --- /dev/null +++ b/actionwebservice/test/dispatcher_action_controller_soap_test.rb @@ -0,0 +1,93 @@ +$:.unshift(File.dirname(__FILE__) + '/apis') +require File.dirname(__FILE__) + '/abstract_dispatcher' +require 'wsdl/parser' + +class AutoLoadController < ActionController::Base; end +class FailingAutoLoadController < ActionController::Base; end +class BrokenAutoLoadController < ActionController::Base; end + +class TC_DispatcherActionControllerSoap < Test::Unit::TestCase + include DispatcherTest + include DispatcherCommonTests + + def setup + @encoder = WS::Encoding::SoapRpcEncoding.new + @marshaler = WS::Marshaling::SoapMarshaler.new + @direct_controller = DirectController.new + @delegated_controller = DelegatedController.new + end + + def test_wsdl_generation + ensure_valid_wsdl_generation DelegatedController.new + ensure_valid_wsdl_generation DirectController.new + end + + def test_wsdl_action + ensure_valid_wsdl_action DelegatedController.new + ensure_valid_wsdl_action DirectController.new + end + + def test_autoloading + assert(!AutoLoadController.web_service_api.nil?) + assert(AutoLoadController.web_service_api.has_public_api_method?('Void')) + assert(FailingAutoLoadController.web_service_api.nil?) + assert_raises(LoadError, NameError) do + FailingAutoLoadController.require_web_service_api :blah + end + assert_raises(ArgumentError) do + FailingAutoLoadController.require_web_service_api 50.0 + end + assert(BrokenAutoLoadController.web_service_api.nil?) + end + + protected + def exception_message(soap_fault_exception) + soap_fault_exception.detail.cause.message + end + + def is_exception?(obj) + obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \ + obj.detail.cause.is_a?(Exception) + end + + def create_ap_request(container, body, public_method_name, *args) + test_request = ActionController::TestRequest.new + test_request.request_parameters['action'] = service_name(container) + test_request.env['REQUEST_METHOD'] = "POST" + test_request.env['HTTP_CONTENTTYPE'] = 'text/xml' + test_request.env['HTTP_SOAPACTION'] = "/soap/#{service_name(container)}/#{public_method_name}" + test_request.env['RAW_POST_DATA'] = body + test_request + end + + def service_name(container) + container.is_a?(DelegatedController) ? 'test_service' : 'api' + end + + def ensure_valid_wsdl_generation(controller) + wsdl = controller.generate_wsdl + ensure_valid_wsdl(wsdl) + end + + def ensure_valid_wsdl(wsdl) + definitions = WSDL::Parser.new.parse(wsdl) + assert(definitions.is_a?(WSDL::Definitions)) + definitions.bindings.each do |binding| + assert(binding.name.name.index(':').nil?) + end + definitions.services.each do |service| + service.ports.each do |port| + assert(port.name.name.index(':').nil?) + end + end + end + + def ensure_valid_wsdl_action(controller) + test_request = ActionController::TestRequest.new({ 'action' => 'wsdl' }) + test_request.env['REQUEST_METHOD'] = 'GET' + test_request.env['HTTP_HOST'] = 'localhost:3000' + test_response = ActionController::TestResponse.new + wsdl = controller.process(test_request, test_response).body + ensure_valid_wsdl(wsdl) + end +end diff --git a/actionwebservice/test/dispatcher_action_controller_test.rb b/actionwebservice/test/dispatcher_action_controller_test.rb deleted file mode 100644 index 11ab21c6a6..0000000000 --- a/actionwebservice/test/dispatcher_action_controller_test.rb +++ /dev/null @@ -1,186 +0,0 @@ -require File.dirname(__FILE__) + '/abstract_soap' -require 'wsdl/parser' - -module DispatcherActionControllerTest - class API < ActionWebService::API::Base - api_method :add, :expects => [:int, :int], :returns => [:int] - end - - class DirectAPI < ActionWebService::API::Base - api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int] - api_method :before_filtered - api_method :after_filtered, :returns => [:int] - api_method :thrower - end - - class Service < ActionWebService::Base - web_service_api API - - attr :added - - def add(a, b) - @added = a + b - end - end - - class AbstractController < ActionController::Base - def generate_wsdl(container, uri, soap_action_base) - to_wsdl(container, uri, soap_action_base) - end - end - - class DelegatedController < AbstractController - web_service_dispatching_mode :delegated - - web_service(:test_service) { @service ||= Service.new; @service } - end - - class DirectController < AbstractController - web_service_api DirectAPI - web_service_dispatching_mode :direct - - before_filter :alwaysfail, :only => [:before_filtered] - after_filter :alwaysok, :only => [:after_filtered] - - attr :added - attr :before_filter_called - attr :before_filter_target_called - attr :after_filter_called - attr :after_filter_target_called - - def initialize - @before_filter_called = false - @before_filter_target_called = false - @after_filter_called = false - @after_filter_target_called = false - end - - def add - @added = @params['a'] + @params['b'] - end - - def before_filtered - @before_filter_target_called = true - end - - def after_filtered - @after_filter_target_called = true - 5 - end - - def thrower - raise "Hi, I'm a SOAP exception" - end - - protected - def alwaysfail - @before_filter_called = true - false - end - - def alwaysok - @after_filter_called = true - end - end -end - -class TC_DispatcherActionController < AbstractSoapTest - include DispatcherActionControllerTest - - def test_direct_dispatching - @container = DirectController.new - assert(do_soap_call('Add', 20, 50) == 70) - assert(@container.added == 70) - end - - def test_direct_entrypoint - @container = DirectController.new - assert(@container.respond_to?(:api)) - end - - def test_direct_filtering - @container = DirectController.new - assert(@container.before_filter_called == false) - assert(@container.before_filter_target_called == false) - assert(do_soap_call('BeforeFiltered').nil?) - assert(@container.before_filter_called == true) - assert(@container.before_filter_target_called == false) - assert(@container.after_filter_called == false) - assert(@container.after_filter_target_called == false) - assert(do_soap_call('AfterFiltered') == 5) - assert(@container.after_filter_called == true) - assert(@container.after_filter_target_called == true) - end - - def test_delegated_dispatching - @container = DelegatedController.new - assert(do_soap_call('Add', 50, 80) == 130) - assert(service.added == 130) - end - - def test_exception_marshaling - @container = DirectController.new - result = do_soap_call('Thrower') - exception = result.detail - assert(exception.cause.is_a?(RuntimeError)) - assert_equal("Hi, I'm a SOAP exception", exception.cause.message) - @container.web_service_exception_reporting = false - assert_raises(SoapTestError) do - do_soap_call('Thrower') - end - end - - def test_wsdl_generation - ensure_valid_wsdl_generation DelegatedController.new - ensure_valid_wsdl_generation DirectController.new - end - - def - - def test_wsdl_action - ensure_valid_wsdl_action DelegatedController.new - ensure_valid_wsdl_action DirectController.new - end - - protected - def service_name - @container.is_a?(DelegatedController) ? 'test_service' : 'api' - end - - def service - @container.web_service_object(:test_service) - end - - def do_soap_call(public_method_name, *args) - super(public_method_name, *args) do |test_request, test_response| - response = @container.process(test_request, test_response) - end - end - - def ensure_valid_wsdl_generation(controller) - wsdl = controller.generate_wsdl(controller, 'http://localhost:3000/test/', '/test') - ensure_valid_wsdl(wsdl) - end - - def ensure_valid_wsdl(wsdl) - definitions = WSDL::Parser.new.parse(wsdl) - assert(definitions.is_a?(WSDL::Definitions)) - definitions.bindings.each do |binding| - assert(binding.name.name.index(':').nil?) - end - definitions.services.each do |service| - service.ports.each do |port| - assert(port.name.name.index(':').nil?) - end - end - end - - def ensure_valid_wsdl_action(controller) - test_request = ActionController::TestRequest.new({ 'action' => 'wsdl' }) - test_request.env['REQUEST_METHOD'] = 'GET' - test_request.env['HTTP_HOST'] = 'localhost:3000' - test_response = ActionController::TestResponse.new - wsdl = controller.process(test_request, test_response).body - ensure_valid_wsdl(wsdl) - end -end diff --git a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb new file mode 100644 index 0000000000..13f193e2c5 --- /dev/null +++ b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb @@ -0,0 +1,35 @@ +require File.dirname(__FILE__) + '/abstract_dispatcher' + +class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase + include DispatcherTest + include DispatcherCommonTests + + def setup + @encoder = WS::Encoding::XmlRpcEncoding.new + @marshaler = WS::Marshaling::XmlRpcMarshaler.new + @direct_controller = DirectController.new + @delegated_controller = DelegatedController.new + end + + protected + def exception_message(xmlrpc_fault_exception) + xmlrpc_fault_exception.faultString + end + + def is_exception?(obj) + obj.is_a?(XMLRPC::FaultException) + end + + def create_ap_request(container, body, public_method_name, *args) + test_request = ActionController::TestRequest.new + test_request.request_parameters['action'] = service_name(container) + test_request.env['REQUEST_METHOD'] = "POST" + test_request.env['HTTP_CONTENTTYPE'] = 'text/xml' + test_request.env['RAW_POST_DATA'] = body + test_request + end + + def service_name(container) + container.is_a?(DelegatedController) ? 'test_service' : 'api' + end +end diff --git a/actionwebservice/test/gencov b/actionwebservice/test/gencov index 144233107a..1faab34c07 100755 --- a/actionwebservice/test/gencov +++ b/actionwebservice/test/gencov @@ -1,3 +1,3 @@ #!/bin/sh -rcov -x '.*_test\.rb,rubygems,abstract_,/run' ./run +rcov -x '.*_test\.rb,rubygems,abstract_,/run,/apis' ./run diff --git a/actionwebservice/test/invocation_test.rb b/actionwebservice/test/invocation_test.rb index 0d519bf770..22752fefaf 100644 --- a/actionwebservice/test/invocation_test.rb +++ b/actionwebservice/test/invocation_test.rb @@ -12,23 +12,46 @@ module InvocationTest api_method :only_two end + class Interceptor + attr :args + + def initialize + @args = nil + end + + def intercept(*args) + @args = args + end + end + + InterceptorClass = Interceptor.new + class Service < ActionWebService::Base web_service_api API before_invocation :intercept_before, :except => [:no_before] after_invocation :intercept_after, :except => [:no_after] - before_invocation :intercept_only, :only => [:only_one, :only_two] + prepend_after_invocation :intercept_after_first, :except => [:no_after] + prepend_before_invocation :intercept_only, :only => [:only_one, :only_two] + after_invocation(:only => [:only_one]) do |*args| + args[0].instance_variable_set('@block_invoked', args[1]) + end + after_invocation InterceptorClass, :only => [:only_one] attr_accessor :before_invoked attr_accessor :after_invoked + attr_accessor :after_first_invoked attr_accessor :only_invoked + attr_accessor :block_invoked attr_accessor :invocation_result def initialize @before_invoked = nil @after_invoked = nil + @after_first_invoked = nil @only_invoked = nil @invocation_result = nil + @block_invoked = nil end def add(a, b) @@ -69,6 +92,10 @@ module InvocationTest @after_invoked = name @invocation_result = result end + + def intercept_after_first(name, args, result) + @after_first_invoked = name + end def intercept_only(name, args) raise "Interception error" unless name == :only_one || name == :only_two @@ -94,11 +121,17 @@ class TC_Invocation < Test::Unit::TestCase def test_interceptor_registration assert(InvocationTest::Service.before_invocation_interceptors.length == 2) - assert(InvocationTest::Service.after_invocation_interceptors.length == 1) + assert(InvocationTest::Service.after_invocation_interceptors.length == 4) + assert_equal(:intercept_only, InvocationTest::Service.before_invocation_interceptors[0]) + assert_equal(:intercept_after_first, InvocationTest::Service.after_invocation_interceptors[0]) end def test_interception - assert(@service.before_invoked.nil? && @service.after_invoked.nil? && @service.only_invoked.nil? && @service.invocation_result.nil?) + assert(@service.before_invoked.nil?) + assert(@service.after_invoked.nil?) + assert(@service.only_invoked.nil?) + assert(@service.block_invoked.nil?) + assert(@service.invocation_result.nil?) perform_invocation(:add, 20, 50) assert(@service.before_invoked == :add) assert(@service.after_invoked == :add) @@ -124,6 +157,7 @@ class TC_Invocation < Test::Unit::TestCase def test_interception_except_conditions perform_invocation(:no_before) assert(@service.before_invoked.nil?) + assert(@service.after_first_invoked == :no_before) assert(@service.after_invoked == :no_before) assert(@service.invocation_result == 5) @service.before_invoked = @service.after_invoked = @service.invocation_result = nil @@ -137,6 +171,8 @@ class TC_Invocation < Test::Unit::TestCase assert(@service.only_invoked.nil?) perform_invocation(:only_one) assert(@service.only_invoked == :only_one) + assert(@service.block_invoked == :only_one) + assert(InvocationTest::InterceptorClass.args[1] == :only_one) @service.only_invoked = nil perform_invocation(:only_two) assert(@service.only_invoked == :only_two) diff --git a/actionwebservice/test/protocol_registry_test.rb b/actionwebservice/test/protocol_registry_test.rb deleted file mode 100644 index 261f6f400e..0000000000 --- a/actionwebservice/test/protocol_registry_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require File.dirname(__FILE__) + '/abstract_unit' - - -module Foo - include ActionWebService::Protocol - - def self.append_features(base) - super - base.register_protocol(BodyOnly, FooMinimalProtocol) - base.register_protocol(HeaderAndBody, FooMinimalProtocolTwo) - base.register_protocol(HeaderAndBody, FooMinimalProtocolTwo) - base.register_protocol(HeaderAndBody, FooFullProtocol) - end - - class FooFullProtocol < AbstractProtocol - def self.create_protocol_request(klass, request) - protocol = FooFullProtocol.new klass - ActionWebService::Protocol::ProtocolRequest.new(protocol, '', '', '', '') - end - end - - class FooMinimalProtocol < AbstractProtocol - def self.create_protocol_request(klass, request) - protocol = FooMinimalProtocol.new klass - ActionWebService::Protocol::ProtocolRequest.new(protocol, '', '', '', '') - end - end - - class FooMinimalProtocolTwo < AbstractProtocol - end -end - -class ProtocolRegistry - include ActionWebService::Protocol::Registry - include Foo - - def all_protocols - header_and_body_protocols + body_only_protocols - end - - def protocol_request - probe_request_protocol(nil) - end -end - - -class TC_ProtocolRegistry < Test::Unit::TestCase - def test_registration - registry = ProtocolRegistry.new - assert(registry.all_protocols.length == 4) - assert(registry.protocol_request.protocol.is_a?(Foo::FooFullProtocol)) - end -end diff --git a/actionwebservice/test/protocol_soap_test.rb b/actionwebservice/test/protocol_soap_test.rb deleted file mode 100644 index c55b7f55af..0000000000 --- a/actionwebservice/test/protocol_soap_test.rb +++ /dev/null @@ -1,252 +0,0 @@ -require File.dirname(__FILE__) + '/abstract_soap' - -module ProtocolSoapTest - class Person < ActionWebService::Struct - member :id, Integer - member :names, [String] - member :lastname, String - member :deleted, TrueClass - - def ==(other) - id == other.id && names == other.names && lastname == other.lastname && deleted == other.deleted - end - end - - class EmptyAPI < ActionWebService::API::Base - end - - class EmptyService < ActionWebService::Base - web_service_api EmptyAPI - end - - class API < ActionWebService::API::Base - api_method :argument_passing, :expects => [{:int=>:int}, {:string=>:string}, {:array=>[:int]}], :returns => [:bool] - api_method :array_returner, :returns => [[:int]] - api_method :nil_returner - api_method :struct_array_returner, :returns => [[Person]] - api_method :exception_thrower - - default_api_method :default - end - - class Service < ActionWebService::Base - web_service_api API - - attr :int - attr :string - attr :array - attr :values - attr :person - attr :default_args - - def initialize - @int = 20 - @string = "wrong string value" - @default_args = nil - end - - def argument_passing(int, string, array) - @int = int - @string = string - @array = array - true - end - - def array_returner - @values = [1, 2, 3] - end - - def nil_returner - nil - end - - def struct_array_returner - @person = Person.new - @person.id = 5 - @person.names = ["one", "two"] - @person.lastname = "test" - @person.deleted = false - [@person] - end - - def exception_thrower - raise "Hi, I'm a SOAP error" - end - - def default(*args) - @default_args = args - nil - end - end - - class AbstractContainer < ActionController::Base - wsdl_service_name 'Test' - - def dispatch_request(request) - protocol_request = probe_request_protocol(request) - dispatch_protocol_request(protocol_request) - end - end - - class DelegatedContainer < AbstractContainer - web_service_dispatching_mode :delegated - web_service :protocol_soap_service, Service.new - web_service :empty_service, EmptyService.new - end - - class DirectContainer < AbstractContainer - web_service_api API - web_service_dispatching_mode :direct - - attr :int - attr :string - attr :array - attr :values - attr :person - attr :default_args - - def initialize - @int = 20 - @string = "wrong string value" - @default_args = nil - end - - def argument_passing - @int = @params['int'] - @string = @params['string'] - @array = @params['array'] - true - end - - def array_returner - @values = [1, 2, 3] - end - - def nil_returner - nil - end - - def struct_array_returner - @person = Person.new - @person.id = 5 - @person.names = ["one", "two"] - @person.lastname = "test" - @person.deleted = false - [@person] - end - - def exception_thrower - raise "Hi, I'm a SOAP error" - end - - def default - @default_args = @method_params - nil - end - end - - class EmptyContainer < AbstractContainer - web_service_dispatching_mode :delegated - web_service :empty_service, EmptyService.new - end -end - -class TC_ProtocolSoap < AbstractSoapTest - def setup - @delegated_container = ProtocolSoapTest::DelegatedContainer.new - @direct_container = ProtocolSoapTest::DirectContainer.new - @empty_container = ProtocolSoapTest::EmptyContainer.new - end - - def test_argument_passing - in_all_containers do - assert(do_soap_call('ArgumentPassing', 5, "test string", [true, false]) == true) - assert(service.int == 5) - assert(service.string == "test string") - assert(service.array == [true, false]) - end - end - - def test_array_returner - in_all_containers do - assert(do_soap_call('ArrayReturner') == [1, 2, 3]) - assert(service.values == [1, 2, 3]) - end - end - - def test_nil_returner - in_all_containers do - assert(do_soap_call('NilReturner') == nil) - end - end - - def test_struct_array_returner - in_all_containers do - assert(do_soap_call('StructArrayReturner') == [service.person]) - end - end - - def test_nonexistent_method - @container = @empty_container - assert_raises(ActionWebService::Dispatcher::DispatcherError) do - do_soap_call('NonexistentMethod') - end - end - - def test_exception_thrower - in_all_containers do - assert_raises(RuntimeError) do - do_soap_call('ExceptionThrower') - end - end - end - - def test_default_api_method - in_all_containers do - assert(do_soap_call('NonExistentMethodName', 50, false).nil?) - assert(service.default_args == [50, false]) - end - end - - def test_service_name_setting - in_all_containers do - assert(ProtocolSoapTest::DelegatedContainer.soap_mapper.custom_namespace == 'urn:Test') - end - end - - protected - def service_name - case - when @container == @direct_container - 'api' - when @container == @delegated_container - 'protocol_soap_service' - when @container == @empty_container - 'empty_service' - end - end - - def service - case - when @container == @direct_container - @container - when @container == @delegated_container - @container.web_service_object(:protocol_soap_service) - when @container == @empty_container - @container.web_service_object(:empty_service) - end - end - - def in_all_containers(&block) - [@direct_container, @delegated_container].each do |container| - @container = container - block.call - end - end - - def do_soap_call(public_method_name, *args) - super(public_method_name, *args) do |test_request, test_response| - @container.dispatch_request(test_request) - end - end -end diff --git a/actionwebservice/test/protocol_xmlrpc_test.rb b/actionwebservice/test/protocol_xmlrpc_test.rb deleted file mode 100644 index cda0bba6d3..0000000000 --- a/actionwebservice/test/protocol_xmlrpc_test.rb +++ /dev/null @@ -1,147 +0,0 @@ -require File.dirname(__FILE__) + '/abstract_unit' -require 'xmlrpc/parser' -require 'xmlrpc/create' -require 'xmlrpc/config' - -module XMLRPC - class XmlRpcTestHelper - include ParserWriterChooseMixin - - def create_request(methodName, *args) - create().methodCall(methodName, *args) - end - - def parse_response(response) - parser().parseMethodResponse(response) - end - end -end - -module ProtocolXmlRpcTest - class Person < ActionWebService::Struct - member :firstname, String - member :lastname, String - member :active, TrueClass - end - - class API < ActionWebService::API::Base - api_method :add, :expects => [Integer, Integer], :returns => [Integer] - api_method :hash_returner, :returns => [Hash] - api_method :array_returner, :returns => [[Integer]] - api_method :something_hash, :expects => [Hash] - api_method :struct_array_returner, :returns => [[Person]] - - default_api_method :default - end - - class Service < ActionWebService::Base - web_service_api API - - attr :result - attr :hashvalue - attr :default_args - - def initialize - @result = nil - @hashvalue = nil - @default_args = nil - end - - def add(a, b) - @result = a + b - end - - def something_hash(hash) - @hashvalue = hash - end - - def array_returner - [1, 2, 3] - end - - def hash_returner - {'name' => 1, 'value' => 2} - end - - def struct_array_returner - person = Person.new - person.firstname = "John" - person.lastname = "Doe" - person.active = true - [person] - end - - def default(*args) - @default_args = args - nil - end - end - - $service = Service.new - - class Container < ActionController::Base - def protocol_request(request) - probe_request_protocol(request) - end - - def dispatch_request(protocol_request) - dispatch_protocol_request(protocol_request) - end - - web_service :xmlrpc, $service - web_service_dispatching_mode :delegated - end -end - -class TC_ProtocolXmlRpc < Test::Unit::TestCase - def setup - @helper = XMLRPC::XmlRpcTestHelper.new - @container = ProtocolXmlRpcTest::Container.new - end - - def test_xmlrpc_request_dispatching - retval = do_xmlrpc_call('Add', 50, 30) - assert(retval == [true, 80]) - end - - def test_array_returning - retval = do_xmlrpc_call('ArrayReturner') - assert(retval == [true, [1, 2, 3]]) - end - - def test_hash_returning - retval = do_xmlrpc_call('HashReturner') - assert(retval == [true, {'name' => 1, 'value' => 2}]) - end - - def test_struct_array_returning - retval = do_xmlrpc_call('StructArrayReturner') - assert(retval == [true, [{"firstname"=>"John", "lastname"=>"Doe", "active"=>true}]]) - end - - def test_hash_parameter - retval = do_xmlrpc_call('SomethingHash', {'name' => 1, 'value' => 2}) - assert(retval == [true, true]) - assert($service.hashvalue == {'name' => 1, 'value' => 2}) - end - - def test_default_api_method - retval = do_xmlrpc_call('SomeNonexistentMethod', 'test', [1, 2], {'name'=>'value'}) - assert(retval == [true, true]) - assert($service.default_args == ['test', [1, 2], {'name'=>'value'}]) - end - - private - def do_xmlrpc_call(public_method_name, *args) - service_name = 'xmlrpc' - raw_request = @helper.create_request(public_method_name, *args) - test_request = ActionController::TestRequest.new - test_request.request_parameters['action'] = service_name - test_request.env['REQUEST_METHOD'] = "POST" - test_request.env['HTTP_CONTENTTYPE'] = 'text/xml' - test_request.env['RAW_POST_DATA'] = raw_request - protocol_request = @container.protocol_request(test_request) - response = @container.dispatch_request(protocol_request) - @helper.parse_response(response.raw_body) - end -end diff --git a/actionwebservice/test/run b/actionwebservice/test/run index 5c6f8b2bc2..90ad85fff5 100755 --- a/actionwebservice/test/run +++ b/actionwebservice/test/run @@ -1,5 +1,5 @@ #!/usr/bin/env ruby - -Dir[File.join(File.dirname(__FILE__), '*_test.rb')].each do |f| - require f -end +require 'test/unit' +args = Dir[File.join(File.dirname(__FILE__), '*_test.rb')] + Dir[File.join(File.dirname(__FILE__), 'ws/*_test.rb')] +(r = Test::Unit::AutoRunner.new(true)).process_args(args) +exit r.run diff --git a/actionwebservice/test/ws/abstract_encoding.rb b/actionwebservice/test/ws/abstract_encoding.rb new file mode 100644 index 0000000000..9a6aec44e0 --- /dev/null +++ b/actionwebservice/test/ws/abstract_encoding.rb @@ -0,0 +1,68 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +module Nested + class StructClass + attr_accessor :name + attr_accessor :version + + def initialize + @name = 5 + @version = "1.0" + end + + def ==(other) + @name == other.name && @version == other.version + end + end +end + +module EncodingTest + def setup + @call_signature = [:int, :bool, :string, :float, [:time], Nested::StructClass] + @call_params = [1, true, "string", 5.0, [Time.now], Nested::StructClass.new] + @response_signature = [:string] + @response_param = "hello world" + test_setup + end + + def test_abstract + obj = WS::Encoding::AbstractEncoding.new + assert_raises(NotImplementedError) do + obj.encode_rpc_call(nil, nil) + end + assert_raises(NotImplementedError) do + obj.decode_rpc_call(nil) + end + assert_raises(NotImplementedError) do + obj.encode_rpc_response(nil, nil) + end + assert_raises(NotImplementedError) do + obj.decode_rpc_response(nil) + end + end + + def encode_rpc_call(method_name, signature, params) + params = params.dup + (0..(signature.length-1)).each do |i| + type_binding = @marshaler.register_type(signature[i]) + info = WS::ParamInfo.create(signature[i], i, type_binding) + params[i] = @marshaler.marshal(WS::Param.new(params[i], info)) + end + @encoder.encode_rpc_call(method_name, params) + end + + def decode_rpc_call(obj) + @encoder.decode_rpc_call(obj) + end + + def encode_rpc_response(method_name, signature, param) + type_binding = @marshaler.register_type(signature[0]) + info = WS::ParamInfo.create(signature[0], 0, type_binding) + param = @marshaler.marshal(WS::Param.new(param, info)) + @encoder.encode_rpc_response(method_name, param) + end + + def decode_rpc_response(obj) + @encoder.decode_rpc_response(obj) + end +end diff --git a/actionwebservice/test/ws/abstract_unit.rb b/actionwebservice/test/ws/abstract_unit.rb new file mode 100644 index 0000000000..f5015bea69 --- /dev/null +++ b/actionwebservice/test/ws/abstract_unit.rb @@ -0,0 +1,14 @@ +$:.unshift(File.dirname(File.dirname(__FILE__)) + '/../lib') +$:.unshift(File.dirname(File.dirname(__FILE__)) + '/../lib/action_web_service/vendor') +puts $:.inspect +require 'test/unit' +require 'ws' +begin + require 'active_record' +rescue LoadError + begin + require 'rubygems' + require_gem 'activerecord', '>= 1.6.0' + rescue LoadError + end +end diff --git a/actionwebservice/test/ws/gencov b/actionwebservice/test/ws/gencov new file mode 100755 index 0000000000..144233107a --- /dev/null +++ b/actionwebservice/test/ws/gencov @@ -0,0 +1,3 @@ +#!/bin/sh + +rcov -x '.*_test\.rb,rubygems,abstract_,/run' ./run diff --git a/actionwebservice/test/ws/run b/actionwebservice/test/ws/run new file mode 100755 index 0000000000..5c6f8b2bc2 --- /dev/null +++ b/actionwebservice/test/ws/run @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +Dir[File.join(File.dirname(__FILE__), '*_test.rb')].each do |f| + require f +end diff --git a/actionwebservice/test/ws/soap_marshaling_test.rb b/actionwebservice/test/ws/soap_marshaling_test.rb new file mode 100644 index 0000000000..ee6413478d --- /dev/null +++ b/actionwebservice/test/ws/soap_marshaling_test.rb @@ -0,0 +1,91 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +module Nested + class MyClass + attr_accessor :id + attr_accessor :name + + def initialize(id, name) + @id = id + @name = name + end + + def ==(other) + @id == other.id && @name == other.name + end + end +end + +class SoapMarshalingTest < Test::Unit::TestCase + def setup + @marshaler = WS::Marshaling::SoapMarshaler.new + end + + def test_abstract + marshaler = WS::Marshaling::AbstractMarshaler.new + assert_raises(NotImplementedError) do + marshaler.marshal(nil) + end + assert_raises(NotImplementedError) do + marshaler.unmarshal(nil) + end + assert_equal(nil, marshaler.register_type(nil)) + end + + def test_marshaling + info = WS::ParamInfo.create(Nested::MyClass) + param = WS::Param.new(Nested::MyClass.new(2, "name"), info) + new_param = @marshaler.unmarshal(@marshaler.marshal(param)) + assert(param == new_param) + end + + def test_exception_marshaling + info = WS::ParamInfo.create(RuntimeError) + param = WS::Param.new(RuntimeError.new("hello, world"), info) + new_param = @marshaler.unmarshal(@marshaler.marshal(param)) + assert_equal("hello, world", new_param.value.detail.cause.message) + end + + def test_registration + type_binding1 = @marshaler.register_type(:int) + type_binding2 = @marshaler.register_type(:int) + assert(type_binding1.equal?(type_binding2)) + end + + def test_active_record + if Object.const_defined?('ActiveRecord') + node_class = Class.new(ActiveRecord::Base) do + def initialize(*args) + super(*args) + @new_record = false + end + + class << self + def name + "Node" + end + + def columns(*args) + [ + ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'), + ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'), + ActiveRecord::ConnectionAdapters::Column.new('email', nil, 'string'), + ] + end + + def connection + self + end + end + end + info = WS::ParamInfo.create(node_class, 0, @marshaler.register_type(node_class)) + ar_obj = node_class.new('name' => 'hello', 'email' => 'test@test.com') + param = WS::Param.new(ar_obj, info) + obj = @marshaler.marshal(param) + param = @marshaler.unmarshal(obj) + new_ar_obj = param.value + assert_equal(ar_obj, new_ar_obj) + assert(!ar_obj.equal?(new_ar_obj)) + end + end +end diff --git a/actionwebservice/test/ws/soap_rpc_encoding_test.rb b/actionwebservice/test/ws/soap_rpc_encoding_test.rb new file mode 100644 index 0000000000..e5dcbfeb1b --- /dev/null +++ b/actionwebservice/test/ws/soap_rpc_encoding_test.rb @@ -0,0 +1,47 @@ +require File.dirname(__FILE__) + '/abstract_encoding' +require 'time' + +class SoapRpcEncodingTest < Test::Unit::TestCase + include EncodingTest + + def test_setup + @encoder = WS::Encoding::SoapRpcEncoding.new + @marshaler = WS::Marshaling::SoapMarshaler.new + end + + def test_call_encoding_and_decoding + obj = encode_rpc_call('DecodeMe', @call_signature, @call_params) + method_name, decoded_params = decode_rpc_call(obj) + params = decoded_params.map{|x| @marshaler.unmarshal(x).value} + assert_equal(method_name, 'DecodeMe') + assert_equal(@call_params[0..3], params[0..3]) + # XXX: DateTime not marshaled correctly yet + assert_equal(@call_params[5..-1], params[5..-1]) + end + + def test_response_encoding_and_decoding_simple + obj = encode_rpc_response('DecodeMe', @response_signature, @response_param) + method_name, return_value = decode_rpc_response(obj) + return_value = @marshaler.unmarshal(return_value).value + assert_equal('DecodeMe', method_name) + assert_equal(@response_param, return_value) + end + + def test_response_encoding_and_decoding_struct + struct = Nested::StructClass.new + obj = encode_rpc_response('DecodeMe', [Nested::StructClass], struct) + method_name, return_value = decode_rpc_response(obj) + return_value = @marshaler.unmarshal(return_value).value + assert_equal('DecodeMe', method_name) + assert_equal(struct, return_value) + end + + def test_response_encoding_and_decoding_array + struct = Nested::StructClass.new + obj = encode_rpc_response('DecodeMe', [[Nested::StructClass]], [struct]) + method_name, return_value = decode_rpc_response(obj) + return_value = @marshaler.unmarshal(return_value).value + assert_equal('DecodeMe', method_name) + assert_equal([struct], return_value) + end +end diff --git a/actionwebservice/test/ws/types_test.rb b/actionwebservice/test/ws/types_test.rb new file mode 100644 index 0000000000..649fdbad7c --- /dev/null +++ b/actionwebservice/test/ws/types_test.rb @@ -0,0 +1,41 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class TypesTest < Test::Unit::TestCase + include WS + + def setup + @caster = BaseTypeCaster.new + end + + def test_base_types + assert_equal(:int, BaseTypes.canonical_type_name(:integer)) + assert_equal(:int, BaseTypes.canonical_type_name(:fixnum)) + assert_equal(Integer, BaseTypes.type_name_to_class(:bignum)) + assert_equal(Date, BaseTypes.type_name_to_class(:date)) + assert_equal(Time, BaseTypes.type_name_to_class(:timestamp)) + assert_equal(TrueClass, BaseTypes.type_name_to_class(:bool)) + assert_equal(:int, BaseTypes.class_to_type_name(Bignum)) + assert_equal(:bool, BaseTypes.class_to_type_name(FalseClass)) + assert_equal(Integer, BaseTypes.canonical_type_class(Fixnum)) + assert_raises(TypeError) do + BaseTypes.canonical_type_name(:fake) + end + end + + def test_casting + assert_equal(5, @caster.cast("5", Fixnum)) + assert_equal('50.0', @caster.cast(50.0, String)) + assert_equal(true, @caster.cast('true', FalseClass)) + assert_equal(false, @caster.cast('false', TrueClass)) + assert_raises(TypeError) do + @caster.cast('yes', FalseClass) + end + assert_equal(3.14159, @caster.cast('3.14159', Float)) + now1 = Time.new + now2 = @caster.cast("#{now1}", Time) + assert_equal(now1.tv_sec, now2.tv_sec) + date1 = Date.parse('2004-01-01') + date2 = @caster.cast("#{date1}", Date) + assert_equal(date1, date2) + end +end diff --git a/actionwebservice/test/ws/xmlrpc_encoding_test.rb b/actionwebservice/test/ws/xmlrpc_encoding_test.rb new file mode 100644 index 0000000000..45aaa901bd --- /dev/null +++ b/actionwebservice/test/ws/xmlrpc_encoding_test.rb @@ -0,0 +1,34 @@ +require File.dirname(__FILE__) + '/abstract_encoding' +require 'time' + +class XmlRpcEncodingTest < Test::Unit::TestCase + include EncodingTest + + def test_setup + @encoder = WS::Encoding::XmlRpcEncoding.new + @marshaler = WS::Marshaling::XmlRpcMarshaler.new + end + + def test_typed_call_encoding_and_decoding + obj = encode_rpc_call('DecodeMe', @call_signature, @call_params) + method_name, params = decode_rpc_call(obj) + (0..(@call_signature.length-1)).each do |i| + params[i] = @marshaler.typed_unmarshal(params[i], @call_signature[i]).value + end + assert_equal(method_name, 'DecodeMe') + assert_equal(@call_params[0..3], params[0..3]) + assert_equal(@call_params[5..-1], params[5..-1]) + end + + def test_untyped_call_encoding_and_decoding + obj = encode_rpc_call('DecodeMe', @call_signature, @call_params) + method_name, params = decode_rpc_call(obj) + (0..(@call_signature.length-1)).each do |i| + params[i] = @marshaler.unmarshal(params[i]).value + end + assert_equal(method_name, 'DecodeMe') + assert_equal(@call_params[0..3], params[0..3]) + assert_equal(@call_params[5].name, params[5]['name']) + assert_equal(@call_params[5].version, params[5]['version']) + end +end |