diff options
Diffstat (limited to 'actionwebservice')
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 |