From 594063f23cf8e7cecd24329e801992784f420b55 Mon Sep 17 00:00:00 2001 From: Leon Breedt Date: Mon, 28 Mar 2005 03:20:13 +0000 Subject: generalize casting code to be used by both SOAP and XML-RPC (previously only XML-RPC). switch to better model for API methods, and improve the ability to generate protocol requests/response, will be required by upcoming scaffolding. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1030 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- .../lib/action_web_service/api/base.rb | 164 ++++++++++++++++++++- .../lib/action_web_service/client/soap_client.rb | 40 ++--- .../lib/action_web_service/client/xmlrpc_client.rb | 40 +---- .../lib/action_web_service/dispatcher/abstract.rb | 80 ++++------ .../dispatcher/action_controller_dispatcher.rb | 37 ++--- .../lib/action_web_service/protocol/abstract.rb | 16 +- .../action_web_service/protocol/soap_protocol.rb | 18 +-- .../action_web_service/protocol/xmlrpc_protocol.rb | 20 +-- .../lib/action_web_service/test_invoke.rb | 15 +- .../vendor/ws/marshaling/abstract.rb | 16 ++ .../vendor/ws/marshaling/soap_marshaling.rb | 22 ++- .../vendor/ws/marshaling/xmlrpc_marshaling.rb | 113 ++++++++------ 12 files changed, 353 insertions(+), 228 deletions(-) (limited to 'actionwebservice/lib/action_web_service') diff --git a/actionwebservice/lib/action_web_service/api/base.rb b/actionwebservice/lib/action_web_service/api/base.rb index c30c833f9d..e440a8b1bd 100644 --- a/actionwebservice/lib/action_web_service/api/base.rb +++ b/actionwebservice/lib/action_web_service/api/base.rb @@ -1,5 +1,8 @@ module ActionWebService # :nodoc: module API # :nodoc: + class CastingError < ActionWebService::ActionWebServiceError + 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. @@ -77,8 +80,8 @@ module ActionWebService # :nodoc: end name = name.to_sym public_name = public_api_method_name(name) - info = { :expects => expects, :returns => returns } - write_inheritable_hash("api_methods", name => info) + method = Method.new(name, public_name, expects, returns) + write_inheritable_hash("api_methods", name => method) write_inheritable_hash("api_public_method_names", public_name => name) end @@ -112,7 +115,39 @@ module ActionWebService # :nodoc: def api_methods read_inheritable_attribute("api_methods") || {} end - + + # The Method instance for the given public API method name, if any + def public_api_method_instance(public_method_name) + api_method_instance(api_method_name(public_method_name)) + end + + # The Method instance for the given API method name, if any + def api_method_instance(method_name) + api_methods[method_name] + end + + # The Method instance for the default API method, if any + def default_api_method_instance + return nil unless name = default_api_method + instance = read_inheritable_attribute("default_api_method_instance") + if instance && instance.name == name + return instance + end + instance = Method.new(name, public_api_method_name(name), nil, nil) + write_inheritable_attribute("default_api_method_instance", instance) + instance + end + + # Creates a dummy API Method instance for the given public method name + def dummy_public_api_method_instance(public_method_name) + Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil) + end + + # Creates a dummy API Method instance for the given method name + def dummy_api_method_instance(method_name) + Method.new(method_name, public_api_method_name(method_name), nil, nil) + end + private def api_public_method_names read_inheritable_attribute("api_public_method_names") || {} @@ -131,5 +166,128 @@ module ActionWebService # :nodoc: end end end + + # Represents an API method and its associated metadata, and provides functionality + # to assist in commonly performed API method tasks. + class Method + attr :name + attr :public_name + attr :expects + attr :returns + + def initialize(name, public_name, expects, returns) + @name = name + @public_name = public_name + @expects = expects + @returns = returns + end + + # The list of parameter names for this method + def param_names + return [] unless @expects + i = 0 + @expects.map{ |spec| param_name(spec, i += 1) } + end + + # The name for the given parameter + def param_name(spec, i=1) + spec.is_a?(Hash) ? spec.keys.first.to_s : "p#{i}" + end + + # The type of the parameter declared in +spec+. Is either + # the Class of the parameter, or its canonical name (if its a + # base type). Typed array specifications will return the type of + # their elements. + def param_type(spec) + spec = spec.values.first if spec.is_a?(Hash) + param_type = spec.is_a?(Array) ? spec[0] : spec + WS::BaseTypes::class_to_type_name(param_type) rescue param_type + end + + # The Class of the parameter declared in +spec+. + def param_class(spec) + type = param_type(spec) + type.is_a?(Symbol) ? WS::BaseTypes.type_name_to_class(type) : type + end + + # Registers all types known to this method with the given marshaler + def register_types(marshaler) + @expects.each{ |x| marshaler.register_type(x) } if @expects + @returns.each{ |x| marshaler.register_type(x) } if @returns + end + + # Encodes an RPC call for this method. Casting is performed if + # the :strict option is given. + def encode_rpc_call(marshaler, encoder, params, options={}) + name = options[:method_name] || @public_name + expects = @expects || [] + returns = @returns || [] + (expects + returns).each { |spec| marshaler.register_type spec } + (0..(params.length-1)).each do |i| + spec = expects[i] || params[i].class + type_binding = marshaler.lookup_type(spec) + param_info = WS::ParamInfo.create(spec, type_binding, i) + if options[:strict] + value = marshaler.cast_outbound_recursive(params[i], spec) + else + value = params[i] + end + param = WS::Param.new(value, param_info) + params[i] = marshaler.marshal(param) + end + encoder.encode_rpc_call(name, params) + end + + # Encodes an RPC response for this method. Casting is performed if + # the :strict option is given. + def encode_rpc_response(marshaler, encoder, return_value, options={}) + if !return_value.nil? && @returns + return_type = @returns[0] + type_binding = marshaler.register_type(return_type) + param_info = WS::ParamInfo.create(return_type, type_binding, 0) + if options[:strict] + return_value = marshaler.cast_inbound_recursive(return_value, return_type) + end + return_value = marshaler.marshal(WS::Param.new(return_value, param_info)) + else + return_value = nil + end + encoder.encode_rpc_response(response_name(encoder), return_value) + end + + # Casts a set of WS::Param values into the appropriate + # Ruby values + def cast_expects_ws2ruby(marshaler, params) + return [] if @expects.nil? + i = 0 + @expects.map do |spec| + value = marshaler.cast_inbound_recursive(params[i].value, spec) + i += 1 + value + end + end + + # Casts a set of Ruby values into the expected Ruby values + def cast_expects(marshaler, params) + return [] if @expects.nil? + i = 0 + @expects.map do |spec| + value = marshaler.cast_outbound_recursive(params[i], spec) + i += 1 + value + end + end + + # Cast a Ruby return value into the expected Ruby value + def cast_returns(marshaler, return_value) + return nil if @returns.nil? + marshaler.cast_inbound_recursive(return_value, @returns[0]) + end + + private + def response_name(encoder) + encoder.is_a?(WS::Encoding::SoapRpcEncoding) ? (@public_name + "Response") : @public_name + 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 b8a20bb40e..b9eb0d11ad 100644 --- a/actionwebservice/lib/action_web_service/client/soap_client.rb +++ b/actionwebservice/lib/action_web_service/client/soap_client.rb @@ -58,7 +58,10 @@ module ActionWebService # :nodoc: protected def perform_invocation(method_name, args) - @driver.send(method_name, *args) + method = @api.api_methods[method_name.to_sym] + args = method.cast_expects(@marshaler, args) + return_value = @driver.send(method_name, *args) + method.cast_returns(@marshaler, return_value) end def soap_action(method_name) @@ -67,48 +70,33 @@ module ActionWebService # :nodoc: private def create_soap_rpc_driver(api, endpoint_uri) - register_api(@marshaler, api) + api.api_methods.each{ |name, method| method.register_types(@marshaler) } driver = SoapDriver.new(endpoint_uri, nil) driver.mapping_registry = @marshaler.registry - api.api_methods.each do |name, info| - public_name = api.public_api_method_name(name) - qname = XSD::QName.new(@method_namespace, public_name) - action = soap_action(public_name) - expects = info[:expects] - returns = info[:returns] + api.api_methods.each do |name, method| + qname = XSD::QName.new(@method_namespace, method.public_name) + action = soap_action(method.public_name) + expects = method.expects + returns = method.returns param_def = [] i = 0 if expects expects.each do |spec| - param_name = spec.is_a?(Hash) ? spec.keys[0].to_s : "param#{i}" - type_binding = @marshaler.register_type(spec) + param_name = method.param_name(spec, i) + type_binding = @marshaler.lookup_type(spec) param_def << ['in', param_name, type_binding.mapping] i += 1 end end if returns - type_binding = @marshaler.register_type(returns[0]) + type_binding = @marshaler.lookup_type(returns[0]) param_def << ['retval', 'return', type_binding.mapping] end - driver.add_method(qname, action, name.to_s, param_def) + driver.add_method(qname, action, method.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 27fe537404..e0b7efc864 100644 --- a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb +++ b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb @@ -36,43 +36,17 @@ module ActionWebService # :nodoc: protected def perform_invocation(method_name, args) - args = transform_outgoing_method_params(method_name, args) + method = @api.api_methods[method_name.to_sym] + method.register_types(@marshaler) + if method.expects && method.expects.length != args.length + raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})") + end + args = method.cast_expects(@marshaler, args) ok, return_value = @client.call2(public_name(method_name), *args) - return transform_return_value(method_name, return_value) if ok + return method.cast_returns(@marshaler, return_value) if ok raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}") end - def transform_outgoing_method_params(method_name, params) - info = @api.api_methods[method_name.to_sym] - 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 - params = params.dup - if expects_length > 0 - i = 0 - expects.each do |spec| - type_binding = @marshaler.register_type(spec) - info = WS::ParamInfo.create(spec, type_binding, i) - params[i] = @marshaler.marshal(WS::Param.new(params[i], info)) - i += 1 - end - end - params - end - - def transform_return_value(method_name, return_value) - info = @api.api_methods[method_name.to_sym] - return true unless returns = info[:returns] - type_binding = @marshaler.register_type(returns[0]) - info = WS::ParamInfo.create(returns[0], type_binding, 0) - info.name = 'return' - @marshaler.transform_inbound(WS::Param.new(return_value, info)) - end - def public_name(method_name) public_name = @api.public_api_method_name(method_name) @handler_name ? "#{@handler_name}.#{public_name}" : public_name diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb index b63fe65ce1..4c184fb140 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -34,30 +34,30 @@ module ActionWebService # :nodoc: def web_service_direct_invoke(invocation) @method_params = invocation.method_ordered_params - arity = method(invocation.api_method_name).arity rescue 0 + arity = method(invocation.api_method.name).arity rescue 0 if arity < 0 || arity > 0 - return_value = self.__send__(invocation.api_method_name, *@method_params) + return_value = self.__send__(invocation.api_method.name, *@method_params) else - return_value = self.__send__(invocation.api_method_name) + return_value = self.__send__(invocation.api_method.name) end - if invocation.api.has_api_method?(invocation.api_method_name) - returns = invocation.returns ? invocation.returns[0] : nil + if invocation.api.has_api_method?(invocation.api_method.name) + api_method = invocation.api_method else - returns = return_value.class + api_method = invocation.api_method.dup + api_method.instance_eval{ @returns = [ return_value.class ] } end - invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns) + invocation.protocol.marshal_response(api_method, return_value) end def web_service_delegated_invoke(invocation) cancellation_reason = nil - return_value = invocation.service.perform_invocation(invocation.api_method_name, invocation.method_ordered_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 - returns = invocation.returns ? invocation.returns[0] : nil - invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns) + invocation.protocol.marshal_response(invocation.api_method, return_value) end def web_service_invocation(request) @@ -71,7 +71,6 @@ module ActionWebService # :nodoc: invocation.service_name = $1 end end - invocation.public_method_name = public_method_name case web_service_dispatching_mode when :direct invocation.api = self.class.web_service_api @@ -83,54 +82,29 @@ module ActionWebService # :nodoc: end invocation.api = invocation.service.class.web_service_api end + request.api = invocation.api if invocation.api.has_public_api_method?(public_method_name) - invocation.api_method_name = invocation.api.api_method_name(public_method_name) + invocation.api_method = invocation.api.public_api_method_instance(public_method_name) else if invocation.api.default_api_method.nil? raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}") else - invocation.api_method_name = invocation.api.default_api_method.to_s.to_sym + invocation.api_method = invocation.api.default_api_method_instance end end - unless invocation.service.respond_to?(invocation.api_method_name) - raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method_name})") + unless invocation.service.respond_to?(invocation.api_method.name) + raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})") end - info = invocation.api.api_methods[invocation.api_method_name] - invocation.expects = info ? info[:expects] : nil - invocation.returns = info ? info[:returns] : nil - 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, type_binding, i) - 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 - invocation.method_ordered_params = [] - invocation.method_named_params = {} + request.api_method = invocation.api_method + begin + invocation.method_ordered_params = invocation.api_method.cast_expects_ws2ruby(request.protocol.marshaler, request.method_params) + rescue + invocation.method_ordered_params = request.method_params.map{ |x| x.value } end - if invocation.returns - invocation.returns.each do |spec| - invocation.protocol.register_signature_type(spec) - end + invocation.method_named_params = {} + invocation.api_method.param_names.inject(0) do |m, n| + invocation.method_named_params[n] = invocation.method_ordered_params[m] + m + 1 end invocation end @@ -139,13 +113,9 @@ module ActionWebService # :nodoc: attr_accessor :protocol attr_accessor :service_name attr_accessor :api - attr_accessor :public_method_name - attr_accessor :api_method_name + attr_accessor :api_method 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 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 7080f813d4..5289b0b84d 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -76,7 +76,10 @@ module ActionWebService # :nodoc: 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) + api_method = request.api_method ? request.api_method.dup : nil + api_method ||= request.api.dummy_api_method_instance(request.method_name) + api_method.instance_eval{ @returns = [ exception.class ] } + response = request.protocol.marshal_response(api_method, exception) send_web_service_response(response) else if self.class.web_service_exception_reporting @@ -95,7 +98,7 @@ module ActionWebService # :nodoc: end @session ||= {} @assigns ||= {} - @params['action'] = invocation.api_method_name.to_s + @params['action'] = invocation.api_method.name.to_s if before_action == false raise(DispatcherError, "Method filtered") end @@ -224,18 +227,18 @@ module ActionWebService # :nodoc: # APIs apis.each do |api_name, values| api = values[0] - api.api_methods.each do |name, info| + api.api_methods.each do |name, method| gen = lambda do |msg_name, direction| xm.message('name' => msg_name) do sym = nil if direction == :out - returns = info[:returns] + returns = method.returns if returns binding = marshaler.register_type(returns[0]) xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens')) end else - expects = info[:expects] + expects = method.expects i = 1 expects.each do |type| if type.is_a?(Hash) @@ -251,7 +254,7 @@ module ActionWebService # :nodoc: end end end - public_name = api.public_api_method_name(name) + public_name = method.public_name gen.call(public_name, :in) gen.call("#{public_name}Response", :out) end @@ -259,11 +262,10 @@ module ActionWebService # :nodoc: # Port port_name = port_name_for(global_service_name, api_name) xm.portType('name' => port_name) do - 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") + api.api_methods.each do |name, method| + xm.operation('name' => method.public_name) do + xm.input('message' => "typens:#{method.public_name}") + xm.output('message' => "typens:#{method.public_name}Response") end end end @@ -272,16 +274,15 @@ module ActionWebService # :nodoc: 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.api_methods.each do |name, info| - public_name = api.public_api_method_name(name) - xm.operation('name' => public_name) do + api.api_methods.each do |name, method| + xm.operation('name' => method.public_name) do case web_service_dispatching_mode when :direct, :layered - soap_action = soap_action_base + "/api/" + public_name + soap_action = soap_action_base + "/api/" + method.public_name when :delegated soap_action = soap_action_base \ + "/" + api_name.to_s \ - + "/" + public_name + + "/" + method.public_name end xm.soap(:operation, 'soapAction' => soap_action) xm.input do @@ -337,8 +338,8 @@ module ActionWebService # :nodoc: end def traverse_custom_types(api, marshaler, &block) - api.api_methods.each do |name, info| - expects, returns = info[:expects], info[:returns] + api.api_methods.each do |name, method| + expects, returns = method.expects, method.returns expects.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if expects returns.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if returns end diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index 7526539d53..ed50a6ffde 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -3,17 +3,31 @@ module ActionWebService # :nodoc: class ProtocolError < ActionWebServiceError # :nodoc: end + class AbstractProtocol + attr :marshaler + attr :encoder + + def marshal_response(method, return_value) + body = method.encode_rpc_response(marshaler, encoder, return_value) + Response.new(body, 'text/xml') + end + end + class Request # :nodoc: attr :protocol attr :method_name attr :method_params attr :service_name + attr_accessor :api + attr_accessor :api_method - def initialize(protocol, method_name, method_params, service_name) + def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil) @protocol = protocol @method_name = method_name @method_params = method_params @service_name = service_name + @api = api + @api_method = api_method 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 2dab7954f9..253812b5e2 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -6,7 +6,7 @@ module ActionWebService # :nodoc: base.class_inheritable_option(:wsdl_service_name) end - class SoapProtocol # :nodoc: + class SoapProtocol < AbstractProtocol # :nodoc: def initialize @encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService' @marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService' @@ -20,22 +20,6 @@ module ActionWebService # :nodoc: 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, type_binding, 0) - return_value = @marshaler.marshal(WS::Param.new(return_value, info)) - else - return_value = nil - end - body = @encoder.encode_rpc_response(method_name + 'Response', return_value) - Response.new(body, 'text/xml') - end - - def register_signature_type(spec) - @marshaler.register_type(spec) - end - def protocol_client(api, protocol_name, endpoint_uri, options) return nil unless protocol_name == :soap ActionWebService::Client::Soap.new(api, endpoint_uri, options) diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb index 8d6af246ec..f3e4a23b4b 100644 --- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -5,9 +5,7 @@ module ActionWebService # :nodoc: base.register_protocol(XmlRpcProtocol) end - class XmlRpcProtocol # :nodoc: - attr :marshaler - + class XmlRpcProtocol < AbstractProtocol # :nodoc: def initialize @encoder = WS::Encoding::XmlRpcEncoding.new @marshaler = WS::Marshaling::XmlRpcMarshaler.new @@ -22,22 +20,6 @@ module ActionWebService # :nodoc: nil 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, type_binding, 0) - return_value = @marshaler.marshal(WS::Param.new(return_value, info)) - else - return_value = nil - end - body = @encoder.encode_rpc_response(method_name, return_value) - Response.new(body, 'text/xml') - end - - def register_signature_type(spec) - nil - end - def protocol_client(api, protocol_name, endpoint_uri, options) return nil unless protocol_name == :xmlrpc ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options) diff --git a/actionwebservice/lib/action_web_service/test_invoke.rb b/actionwebservice/lib/action_web_service/test_invoke.rb index aaa03cdb30..5d364e4225 100644 --- a/actionwebservice/lib/action_web_service/test_invoke.rb +++ b/actionwebservice/lib/action_web_service/test_invoke.rb @@ -52,18 +52,9 @@ module Test # :nodoc: when :delegated, :layered api = @controller.web_service_object(service_name.to_sym).class.web_service_api end - info = api.api_methods[api_method_name.to_sym] - ((info[:expects] || []) + (info[:returns] || [])).each do |spec| - marshaler.register_type spec - end - expects = info[:expects] - args = args.dup - (0..(args.length-1)).each do |i| - type_binding = marshaler.register_type(expects ? expects[i] : args[i].class) - info = WS::ParamInfo.create(expects ? expects[i] : args[i].class, type_binding, i) - args[i] = marshaler.marshal(WS::Param.new(args[i], info)) - end - encoder.encode_rpc_call(public_method_name(service_name, api_method_name), args) + method = api.api_methods[api_method_name.to_sym] + method.register_types(marshaler) + method.encode_rpc_call(marshaler, encoder, args.dup, :method_name => public_method_name(service_name, api_method_name)) end def decode_rpc_response diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb index 53120e1447..e897f62297 100644 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb +++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb @@ -1,6 +1,10 @@ module WS module Marshaling class AbstractMarshaler + def initialize + @base_type_caster = BaseTypeCaster.new + end + def marshal(param) raise NotImplementedError end @@ -12,6 +16,18 @@ module WS def register_type(type) nil end + alias :lookup_type :register_type + + def cast_inbound_recursive(value, spec) + raise NotImplementedError + end + + def cast_outbound_recursive(value, spec) + raise NotImplementedError + end + + attr :base_type_caster + protected :base_type_caster 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 index 4d29c78f2c..287a64291b 100644 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb @@ -13,6 +13,7 @@ module WS attr_accessor :type_namespace def initialize(type_namespace='') + super() @type_namespace = type_namespace @registry = SOAP::Mapping::Registry.new @spec2binding = {} @@ -92,6 +93,25 @@ module WS @spec2binding[spec] = array_binding ? array_binding : type_binding @spec2binding[spec] end + alias :lookup_type :register_type + + def cast_inbound_recursive(value, spec) + binding = lookup_type(spec) + if binding.is_custom_type? + value + else + base_type_caster.cast(value, binding.type_class) + end + end + + def cast_outbound_recursive(value, spec) + binding = lookup_type(spec) + if binding.is_custom_type? + value + else + base_type_caster.cast(value, binding.type_class) + end + end protected def annotate_arrays(binding, value) @@ -106,7 +126,7 @@ module WS if binding.type_class.respond_to?(:members) binding.type_class.members.each do |name, spec| member_binding = register_type(spec) - member_value = value.send(name) + member_value = value.respond_to?('[]') ? value[name] : value.send(name) if member_binding.is_custom_type? annotate_arrays(member_binding, member_value) 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 index 87154f87e1..56cc7597fb 100644 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb @@ -5,24 +5,24 @@ module WS class XmlRpcMarshaler < AbstractMarshaler def initialize - @caster = BaseTypeCaster.new + super() @spec2binding = {} end def marshal(param) - transform_outbound(param) + value = param.value + cast_outbound_recursive(param.value, spec_for(param)) rescue value 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 + obj.param.info.data = lookup_type(spec) + value = obj.param.value + obj.param.value = cast_inbound_recursive(value, spec) rescue value + obj.param end def register_type(spec) @@ -40,60 +40,87 @@ module WS @spec2binding[spec] = type_binding end + alias :lookup_type :register_type - def transform_outbound(param) - binding = param.info.data + def cast_inbound_recursive(value, spec) + binding = lookup_type(spec) case binding when XmlRpcArrayBinding - param.value.map{|x| cast_outbound(x, binding.element_klass)} + value.map{ |x| cast_inbound(x, binding.element_klass) } when XmlRpcBinding - cast_outbound(param.value, param.info.type) + cast_inbound(value, binding.klass) end end - def transform_inbound(param) - return param.value if param.info.data.nil? - binding = param.info.data - param.info.type = binding.klass + def cast_outbound_recursive(value, spec) + binding = lookup_type(spec) case binding when XmlRpcArrayBinding - param.value.map{|x| cast_inbound(x, binding.element_klass)} + value.map{ |x| cast_outbound(x, binding.element_klass) } when XmlRpcBinding - cast_inbound(param.value, param.info.type) + cast_outbound(value, binding.klass) 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) + private + def spec_for(param) + binding = param.info.data + binding.is_a?(XmlRpcArrayBinding) ? [binding.element_klass] : binding.klass + end + + def cast_inbound(value, klass) + if BaseTypes.base_type?(klass) + value = value.to_time if value.is_a?(XMLRPC::DateTime) + base_type_caster.cast(value, klass) + elsif value.is_a?(XMLRPC::FaultException) + value + elsif klass.ancestors.include?(ActionWebService::Struct) + obj = klass.new + klass.members.each do |name, klass| + name = name.to_s + obj.send('%s=' % name, cast_inbound_recursive(value[name], klass)) + end + obj + else + obj = klass.new + if obj.respond_to?(:update) + obj.update(value) + else + value.each do |name, val| + obj.send('%s=' % name.to_s, val) + end + end + obj 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) + def cast_outbound(value, klass) + if BaseTypes.base_type?(klass) + base_type_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 + elsif value.is_a?(ActionWebService::Struct) + struct = {} + value.class.members.each do |name, klass| + name = name.to_s + struct[name] = cast_outbound_recursive(value[name], klass) + end + struct + else + struct = {} + if value.respond_to?(:each_pair) + value.each_pair{ |key, value| struct[key] = value } + else + value.instance_variables.each do |name| + key = name.sub(/^@/, '') + struct[key] = value.instance_variable_get(name) + end + end + struct end - obj end - end end class XmlRpcBinding -- cgit v1.2.3