diff options
author | Leon Breedt <bitserf@gmail.com> | 2005-04-02 21:03:36 +0000 |
---|---|---|
committer | Leon Breedt <bitserf@gmail.com> | 2005-04-02 21:03:36 +0000 |
commit | aaea48fe9826b9e5d2d5b92795a297b8f238c58d (patch) | |
tree | e7c01c7f95d467f837c1f96d58dac74c3c902610 /actionwebservice/lib | |
parent | aa09c770e9b5400683be11952673017295246de7 (diff) | |
download | rails-aaea48fe9826b9e5d2d5b92795a297b8f238c58d.tar.gz rails-aaea48fe9826b9e5d2d5b92795a297b8f238c58d.tar.bz2 rails-aaea48fe9826b9e5d2d5b92795a297b8f238c58d.zip |
* collapse 'ws' back into protocols, it just added complexity and indirection, and was hard to extend.
* extract casting into seperate support file
* ensure casting always does the right thing for return values, should fix interoperability issues with Ecto and possibly other XML-RPC clients
* add functional unit tests for scaffolding
* represent signature items with classes instead of symbols/Class objects, much more flexible
* tweak logging to always show casted versions of parameters and return values, if possible.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1072 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionwebservice/lib')
28 files changed, 838 insertions, 1104 deletions
diff --git a/actionwebservice/lib/action_web_service.rb b/actionwebservice/lib/action_web_service.rb index d8dc132313..82be41a327 100644 --- a/actionwebservice/lib/action_web_service.rb +++ b/actionwebservice/lib/action_web_service.rb @@ -35,12 +35,12 @@ end $:.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/support/signature_types' require 'action_web_service/base' require 'action_web_service/client' require 'action_web_service/invocation' require 'action_web_service/api' +require 'action_web_service/casting' require 'action_web_service/struct' require 'action_web_service/container' require 'action_web_service/protocol' diff --git a/actionwebservice/lib/action_web_service/api/base.rb b/actionwebservice/lib/action_web_service/api/base.rb index 03e406cfc3..c9fb9f967f 100644 --- a/actionwebservice/lib/action_web_service/api/base.rb +++ b/actionwebservice/lib/action_web_service/api/base.rb @@ -1,8 +1,5 @@ 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. @@ -31,6 +28,8 @@ module ActionWebService # :nodoc: private_class_method :new, :allocate class << self + include ActionWebService::SignatureTypes + # API methods have a +name+, which must be the Ruby method name to use when # performing the invocation on the web service object. # @@ -70,10 +69,9 @@ module ActionWebService # :nodoc: expects = canonical_signature(expects) returns = canonical_signature(returns) if expects - expects.each do |param| - klass = WS::BaseTypes.canonical_param_type_class(param) - klass = klass[0] if klass.is_a?(Array) - if klass.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects + expects.each do |type| + type = type.element_type if type.is_a?(ArrayType) + if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects") end end @@ -138,16 +136,6 @@ module ActionWebService # :nodoc: 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") || {} @@ -159,11 +147,6 @@ module ActionWebService # :nodoc: raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}") end end - - def canonical_signature(signature) - return nil if signature.nil? - signature.map{|spec| WS::BaseTypes.canonical_param_type_spec(spec)} - end end end @@ -180,134 +163,41 @@ module ActionWebService # :nodoc: @public_name = public_name @expects = expects @returns = returns + @caster = ActionWebService::Casting::BaseCaster.new(self) 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 + @expects.map{ |type| type.name } end - # Encodes an RPC call for this method. Casting is performed if - # the <tt>:strict</tt> 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 <tt>:strict</tt> 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 + def cast_expects(params) + @caster.cast_expects(params) 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]) + def cast_returns(return_value) + @caster.cast_returns(return_value) end # String representation of this method def to_s fqn = "" - fqn << (@returns ? (friendly_param(@returns[0], nil) + " ") : "void ") + fqn << (@returns ? (friendly_param(@returns[0], false) + " ") : "void ") fqn << "#{@public_name}(" - if @expects - i = 0 - fqn << @expects.map{ |p| friendly_param(p, i+= 1) }.join(", ") - end + fqn << @expects.map{ |p| friendly_param(p) }.join(", ") if @expects fqn << ")" fqn end private - def response_name(encoder) - encoder.is_a?(WS::Encoding::SoapRpcEncoding) ? (@public_name + "Response") : @public_name - end - - def friendly_param(spec, i) - name = param_name(spec, i) - type = param_type(spec) - spec = spec.values.first if spec.is_a?(Hash) - type = spec.is_a?(Array) ? (type.to_s + "[]") : type.to_s - i ? (type + " " + name) : type + def friendly_param(type, show_name=true) + name = type.name.to_s + type_type = type.type.to_s + str = type.array?? (type_type + '[]') : type_type + show_name ? (str + " " + name) : str end end end diff --git a/actionwebservice/lib/action_web_service/casting.rb b/actionwebservice/lib/action_web_service/casting.rb new file mode 100644 index 0000000000..ce90c463d8 --- /dev/null +++ b/actionwebservice/lib/action_web_service/casting.rb @@ -0,0 +1,105 @@ +require 'time' +require 'date' +require 'generator' + +module ActionWebService # :nodoc: + module Casting # :nodoc: + class CastingError < ActionWebServiceError # :nodoc: + end + + # Performs casting of arbitrary values into the correct types for the signature + class BaseCaster + def initialize(api_method) + @api_method = api_method + end + + # Coerces the parameters in +params+ (an Enumerable) into the types + # this method expects + def cast_expects(params) + self.class.cast_expects(@api_method, params) + end + + # Coerces the given +return_value+ into the the type returned by this + # method + def cast_returns(return_value) + self.class.cast_returns(@api_method, return_value) + end + + class << self + include ActionWebService::SignatureTypes + + def cast_expects(api_method, params) # :nodoc: + return [] if api_method.expects.nil? + SyncEnumerator.new(params, api_method.expects).map{ |r| cast(r[0], r[1]) } + end + + def cast_returns(api_method, return_value) # :nodoc: + return nil if api_method.returns.nil? + cast(return_value, api_method.returns[0]) + end + + def cast(value, signature_type) # :nodoc: + return value if signature_type.nil? # signature.length != params.length + unless signature_type.array? + return value if canonical_type(value.class) == signature_type.type + end + if signature_type.array? + unless value.respond_to?(:entries) && !value.is_a?(String) + raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}" + end + value.entries.map do |entry| + cast(entry, signature_type.element_type) + end + elsif signature_type.structured? + cast_to_structured_type(value, signature_type) + elsif !signature_type.custom? + cast_base_type(value, signature_type) + end + end + + def cast_base_type(value, signature_type) # :nodoc: + case signature_type.type + when :int + Integer(value) + when :string + value.to_s + when :bool + return false if value.nil? + return value if value == true || value == false + case value.to_s.downcase + when '1', 'true', 'y', 'yes' + true + when '0', 'false', 'n', 'no' + false + else + raise CastingError, "Don't know how to cast #{value.class} into Boolean" + end + when :float + Float(value) + when :time + Time.parse(value.to_s) + when :date + Date.parse(value.to_s) + when :datetime + DateTime.parse(value.to_s) + end + end + + def cast_to_structured_type(value, signature_type) # :nodoc: + obj = signature_type.type_class.new + if value.respond_to?(:each_pair) + klass = signature_type.type_class + value.each_pair do |name, val| + type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil + val = cast(val, type) if type + obj.send("#{name}=", val) + end + else + raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}" + end + obj + end + 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 b9eb0d11ad..c906a71331 100644 --- a/actionwebservice/lib/action_web_service/client/soap_client.rb +++ b/actionwebservice/lib/action_web_service/client/soap_client.rb @@ -46,8 +46,7 @@ module ActionWebService # :nodoc: @type_namespace = options[:type_namespace] || 'urn:ActionWebService' @method_namespace = options[:method_namespace] || 'urn:ActionWebService' @driver_options = options[:driver_options] || {} - @marshaler = WS::Marshaling::SoapMarshaler.new @type_namespace - @encoder = WS::Encoding::SoapRpcEncoding.new @method_namespace + @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @soap_action_base = options[:soap_action_base] @soap_action_base ||= URI.parse(endpoint_uri).path @driver = create_soap_rpc_driver(api, endpoint_uri) @@ -59,9 +58,9 @@ module ActionWebService # :nodoc: protected def perform_invocation(method_name, args) method = @api.api_methods[method_name.to_sym] - args = method.cast_expects(@marshaler, args) + args = method.cast_expects(args.dup) rescue args return_value = @driver.send(method_name, *args) - method.cast_returns(@marshaler, return_value) + method.cast_returns(return_value.dup) rescue return_value end def soap_action(method_name) @@ -70,9 +69,9 @@ module ActionWebService # :nodoc: private def create_soap_rpc_driver(api, endpoint_uri) - api.api_methods.each{ |name, method| method.register_types(@marshaler) } + @protocol.register_api(api) driver = SoapDriver.new(endpoint_uri, nil) - driver.mapping_registry = @marshaler.registry + driver.mapping_registry = @protocol.marshaler.registry api.api_methods.each do |name, method| qname = XSD::QName.new(@method_namespace, method.public_name) action = soap_action(method.public_name) @@ -81,15 +80,14 @@ module ActionWebService # :nodoc: param_def = [] i = 0 if expects - expects.each do |spec| - param_name = method.param_name(spec, i) - type_binding = @marshaler.lookup_type(spec) - param_def << ['in', param_name, type_binding.mapping] + expects.each do |type| + type_binding = @protocol.marshaler.lookup_type(type) + param_def << ['in', type.name, type_binding.mapping] i += 1 end end if returns - type_binding = @marshaler.lookup_type(returns[0]) + type_binding = @protocol.marshaler.lookup_type(returns[0]) param_def << ['retval', 'return', type_binding.mapping] end driver.add_method(qname, action, method.name.to_s, 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 e0b7efc864..42b5c5d4f9 100644 --- a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb +++ b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb @@ -30,20 +30,22 @@ module ActionWebService # :nodoc: def initialize(api, endpoint_uri, options={}) @api = api @handler_name = options[:handler_name] + @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout]) - @marshaler = WS::Marshaling::XmlRpcMarshaler.new end protected def perform_invocation(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) + args = method.cast_expects(args.dup) rescue args + if method.expects + method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) } + end ok, return_value = @client.call2(public_name(method_name), *args) - return method.cast_returns(@marshaler, return_value) if ok + return (method.cast_returns(return_value.dup) rescue return_value) if ok raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}") end diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb index 69c3b9de3b..975120212f 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -12,14 +12,6 @@ module ActionWebService # :nodoc: base.send(:include, ActionWebService::Dispatcher::InstanceMethods) end - def self.layered_service_name(public_method_name) # :nodoc: - if public_method_name =~ /^([^\.]+)\.(.*)$/ - $1 - else - nil - end - end - module InstanceMethods # :nodoc: private def invoke_web_service_request(protocol_request) @@ -40,13 +32,7 @@ module ActionWebService # :nodoc: else return_value = self.__send__(invocation.api_method.name) end - if invocation.api.has_api_method?(invocation.api_method.name) - api_method = invocation.api_method - else - api_method = invocation.api_method.dup - api_method.instance_eval{ @returns = [ return_value.class ] } - end - invocation.protocol.marshal_response(api_method, return_value) + web_service_create_response(invocation.protocol, invocation.api, invocation.api_method, return_value) end def web_service_delegated_invoke(invocation) @@ -57,7 +43,7 @@ module ActionWebService # :nodoc: if cancellation_reason raise(DispatcherError, "request canceled: #{cancellation_reason}") end - invocation.protocol.marshal_response(invocation.api_method, return_value) + web_service_create_response(invocation.protocol, invocation.api, invocation.api_method, return_value) end def web_service_invocation(request) @@ -79,6 +65,7 @@ module ActionWebService # :nodoc: invocation.service = web_service_object(invocation.service_name) invocation.api = invocation.service.class.web_service_api end + invocation.protocol.register_api(invocation.api) request.api = invocation.api if invocation.api.has_public_api_method?(public_method_name) invocation.api_method = invocation.api.public_api_method_instance(public_method_name) @@ -89,15 +76,20 @@ module ActionWebService # :nodoc: invocation.api_method = invocation.api.default_api_method_instance end end + if invocation.service.nil? + raise(DispatcherError, "no service available for service name #{invocation.service_name}") + 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})") + raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})") end request.api_method = invocation.api_method begin - invocation.method_ordered_params = invocation.api_method.cast_expects_ws2ruby(request.protocol.marshaler, request.method_params) + invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup) rescue - invocation.method_ordered_params = request.method_params.map{ |x| x.value } + logger.warn "Casting of method parameters failed" unless logger.nil? + invocation.method_ordered_params = request.method_params end + request.method_params = invocation.method_ordered_params invocation.method_named_params = {} invocation.api_method.param_names.inject(0) do |m, n| invocation.method_named_params[n] = invocation.method_ordered_params[m] @@ -106,6 +98,16 @@ module ActionWebService # :nodoc: invocation end + def web_service_create_response(protocol, api, api_method, return_value) + if api.has_api_method?(api_method.name) + return_type = api_method.returns ? api_method.returns[0] : nil + return_value = api_method.cast_returns(return_value) + else + return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0) + end + protocol.encode_response(api_method.public_name + 'Response', return_value, return_type) + end + class Invocation # :nodoc: attr_accessor :protocol attr_accessor :service_name 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 0140039c49..a4659e5183 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -45,7 +45,6 @@ module ActionWebService # :nodoc: exception = e end if request - log_request(request, @request.raw_post) response = nil exception = nil bm = Benchmark.measure do @@ -55,6 +54,7 @@ module ActionWebService # :nodoc: exception = e end end + log_request(request, @request.raw_post) if exception log_error(exception) unless logger.nil? send_web_service_error_response(request, exception) @@ -82,10 +82,10 @@ module ActionWebService # :nodoc: unless self.class.web_service_exception_reporting exception = DispatcherError.new("Internal server error (exception raised)") end - 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) + api_method = request.api_method + public_method_name = api_method ? api_method.public_name : request.method_name + return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0) + response = request.protocol.encode_response(public_method_name + 'Response', exception, return_type) send_web_service_response(response) else if self.class.web_service_exception_reporting @@ -118,7 +118,14 @@ module ActionWebService # :nodoc: def log_request(request, body) unless logger.nil? name = request.method_name - params = request.method_params.map{|x| "#{x.info.name}=>#{x.value.inspect}"} + api_method = request.api_method + params = request.method_params + if api_method && api_method.expects + i = 0 + params = api_method.expects.map{ |type| param = "#{type.name}=>#{params[i].inspect}"; i+= 1; param } + else + params = params.map{ |param| param.inspect } + end service = request.service_name logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}") logger.debug(indent(body)) @@ -127,7 +134,8 @@ module ActionWebService # :nodoc: def log_response(response, elapsed=nil) unless logger.nil? - logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":")) + elapsed = (elapsed ? " (%f):" % elapsed : ":") + logger.debug("\nWeb Service Response" + elapsed + " => #{response.return_value.inspect}") logger.debug(indent(response.body)) end end @@ -171,7 +179,7 @@ module ActionWebService # :nodoc: namespace = 'urn:ActionWebService' soap_action_base = "/#{controller_name}" - marshaler = WS::Marshaling::SoapMarshaler.new(namespace) + marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace) apis = {} case dispatching_mode when :direct @@ -208,7 +216,7 @@ module ActionWebService # :nodoc: xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do custom_types.each do |binding| case - when binding.is_typed_array? + when binding.type.array? xm.xsd(:complexType, 'name' => binding.type_name) do xm.xsd(:complexContent) do xm.xsd(:restriction, 'base' => 'soapenc:Array') do @@ -217,11 +225,11 @@ module ActionWebService # :nodoc: end end end - when binding.is_typed_struct? + when binding.type.structured? xm.xsd(:complexType, 'name' => binding.type_name) do xm.xsd(:all) do - binding.each_member do |name, spec| - b = marshaler.register_type(spec) + binding.type.each_member do |name, type| + b = marshaler.register_type(type) xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens')) end end @@ -249,14 +257,8 @@ module ActionWebService # :nodoc: expects = method.expects i = 1 expects.each do |type| - if type.is_a?(Hash) - param_name = type.keys.shift - type = type.values.shift - else - param_name = "param#{i}" - end binding = marshaler.register_type(type) - xm.part('name' => param_name, 'type' => binding.qualified_type_name('typens')) + xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens')) i += 1 end if expects end @@ -340,7 +342,9 @@ module ActionWebService # :nodoc: def register_api(api, marshaler) bindings = {} traverse_custom_types(api, marshaler) do |binding| - bindings[binding] = nil unless bindings.has_key?(binding.type_class) + bindings[binding] = nil unless bindings.has_key?(binding) + element_binding = binding.element_binding + bindings[binding.element_binding] = nil if element_binding && !bindings.has_key?(element_binding) end bindings.keys end @@ -348,21 +352,18 @@ module ActionWebService # :nodoc: def traverse_custom_types(api, marshaler, &block) 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 + expects.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if expects + returns.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if returns end end - def traverse_custom_type_spec(marshaler, spec, &block) - binding = marshaler.register_type(spec) - if binding.is_typed_struct? - binding.each_member do |name, member_spec| - traverse_custom_type_spec(marshaler, member_spec, &block) - end - elsif binding.is_typed_array? - traverse_custom_type_spec(marshaler, binding.element_binding.type_class, &block) + def traverse_type(marshaler, type, &block) + yield marshaler.register_type(type) + if type.array? + yield marshaler.register_type(type.element_type) + type = type.element_type end - yield binding + type.each_member{ |name, type| traverse_type(marshaler, type, &block) } if type.structured? end end end diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index 0ff4feef84..70b922ce73 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -3,22 +3,11 @@ module ActionWebService # :nodoc: class ProtocolError < ActionWebServiceError # :nodoc: end - class AbstractProtocol - attr :marshaler - attr :encoder - - def unmarshal_request(ap_request) - end - - def marshal_response(method, return_value) - body = method.encode_rpc_response(marshaler, encoder, return_value) - Response.new(body, 'text/xml') + class AbstractProtocol # :nodoc: + def decode_action_pack_request(action_pack_request) end - def protocol_client(api, protocol_name, endpoint_uri, options) - end - - def create_action_pack_request(service_name, public_method_name, raw_body, options={}) + def encode_action_pack_request(service_name, public_method_name, raw_body, options={}) klass = options[:request_class] || SimpleActionPackRequest request = klass.new request.request_parameters['action'] = service_name.to_s @@ -27,50 +16,30 @@ module ActionWebService # :nodoc: request.env['HTTP_CONTENT_TYPE'] = 'text/xml' request end - end - class SimpleActionPackRequest < ActionController::AbstractRequest - def initialize - @env = {} - @qparams = {} - @rparams = {} - @cookies = {} - reset_session + def decode_request(raw_request, service_name) end - def query_parameters - @qparams + def encode_request(method_name, params, param_types) end - def request_parameters - @rparams + def decode_response(raw_response) end - def env - @env - end - - def host - '' - end - - def cookies - @cookies + def encode_response(method_name, return_value, return_type) end - def session - @session + def protocol_client(api, protocol_name, endpoint_uri, options) end - def reset_session - @session = {} + def register_api(api) end end class Request # :nodoc: attr :protocol attr :method_name - attr :method_params + attr_accessor :method_params attr :service_name attr_accessor :api attr_accessor :api_method @@ -88,10 +57,50 @@ module ActionWebService # :nodoc: class Response # :nodoc: attr :body attr :content_type + attr :return_value - def initialize(body, content_type) + def initialize(body, content_type, return_value) @body = body @content_type = content_type + @return_value = return_value + end + end + + class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc: + def initialize + @env = {} + @qparams = {} + @rparams = {} + @cookies = {} + reset_session + end + + def query_parameters + @qparams + end + + def request_parameters + @rparams + end + + def env + @env + end + + def host + '' + end + + def cookies + @cookies + end + + def session + @session + end + + def reset_session + @session = {} end end end diff --git a/actionwebservice/lib/action_web_service/protocol/discovery.rb b/actionwebservice/lib/action_web_service/protocol/discovery.rb index 40875975bf..a911c7d017 100644 --- a/actionwebservice/lib/action_web_service/protocol/discovery.rb +++ b/actionwebservice/lib/action_web_service/protocol/discovery.rb @@ -14,10 +14,10 @@ module ActionWebService # :nodoc: module InstanceMethods # :nodoc: private - def discover_web_service_request(ap_request) + def discover_web_service_request(action_pack_request) (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| protocol = protocol.new - request = protocol.unmarshal_request(ap_request) + request = protocol.decode_action_pack_request(action_pack_request) return request unless request.nil? end nil diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb index 5e56748ae3..cc3e90a4cc 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -1,3 +1,5 @@ +require 'action_web_service/protocol/soap_protocol/marshaler' + module ActionWebService # :nodoc: module Protocol # :nodoc: module Soap # :nodoc: @@ -7,28 +9,105 @@ module ActionWebService # :nodoc: end class SoapProtocol < AbstractProtocol # :nodoc: - def initialize - @encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService' - @marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService' + def marshaler + @marshaler ||= SoapMarshaler.new + end + + def decode_action_pack_request(action_pack_request) + return nil unless has_valid_soap_action?(action_pack_request) + service_name = action_pack_request.parameters['action'] + decode_request(action_pack_request.raw_post, service_name) + end + + def encode_action_pack_request(service_name, public_method_name, raw_body, options={}) + request = super + request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name] + request 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'] + def decode_request(raw_request, service_name) + envelope = SOAP::Processor.unmarshal(raw_request) + unless envelope + raise ProtocolError, "Failed to parse SOAP request message" + end + request = envelope.body.request + method_name = request.elename.name + params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) } Request.new(self, method_name, params, service_name) end + def encode_request(method_name, params, param_types) + param_types.each{ |type| marshaler.register_type(type) } if param_types + qname = XSD::QName.new(marshaler.type_namespace, method_name) + param_def = [] + i = 0 + if param_types + params = params.map do |param| + param_type = param_types[i] + param_def << ['in', param_type.name, marshaler.lookup_type(param_type).mapping] + i += 1 + [param_type.name, marshaler.ruby_to_soap(param)] + end + else + params = [] + 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_response(raw_response) + envelope = SOAP::Processor.unmarshal(raw_response) + unless envelope + raise ProtocolError, "Failed to parse SOAP request message" + end + method_name = envelope.body.request.elename.name + return_value = envelope.body.response + return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil? + [method_name, return_value] + end + + def encode_response(method_name, return_value, return_type) + if return_type + return_binding = marshaler.register_type(return_type) + marshaler.annotate_arrays(return_binding, return_value) + end + qname = XSD::QName.new(marshaler.type_namespace, method_name) + if return_value.nil? + response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) + else + if return_value.is_a?(Exception) + detail = SOAP::Mapping::SOAPException.new(return_value) + response = SOAP::SOAPFault.new( + SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']), + SOAP::SOAPString.new(return_value.to_s), + SOAP::SOAPString.new(self.class.name), + marshaler.ruby_to_soap(detail)) + else + if return_type + param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]] + response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def) + response.retval = marshaler.ruby_to_soap(return_value) + else + response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) + end + end + end + envelope = create_soap_envelope(response) + Response.new(SOAP::Processor.marshal(envelope), 'text/xml', return_value) + end + def protocol_client(api, protocol_name, endpoint_uri, options={}) return nil unless protocol_name == :soap ActionWebService::Client::Soap.new(api, endpoint_uri, options) end - def create_action_pack_request(service_name, public_method_name, raw_body, options={}) - request = super - request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name] - request + def register_api(api) + api.api_methods.each do |name, method| + method.expects.each{ |type| marshaler.register_type(type) } if method.expects + method.returns.each{ |type| marshaler.register_type(type) } if method.returns + end end private @@ -43,7 +122,13 @@ module ActionWebService # :nodoc: return nil if soap_action.empty? soap_action end - end + + def create_soap_envelope(body) + header = SOAP::SOAPHeader.new + body = SOAP::SOAPBody.new(body) + SOAP::SOAPEnvelope.new(header, body) + end + end end end end diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb new file mode 100644 index 0000000000..5d9a80b007 --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb @@ -0,0 +1,197 @@ +require 'soap/mapping' + +module ActionWebService + module Protocol + module Soap + class SoapMarshaler + attr :type_namespace + attr :registry + + def initialize(type_namespace=nil) + @type_namespace = type_namespace || 'urn:ActionWebService' + @registry = SOAP::Mapping::Registry.new + @type2binding = {} + end + + def soap_to_ruby(obj) + SOAP::Mapping.soap2obj(obj, @registry) + end + + def ruby_to_soap(obj) + SOAP::Mapping.obj2soap(obj, @registry) + end + + def register_type(type) + return @type2binding[type] if @type2binding.has_key?(type) + + type_class = type.array?? type.element_type.type_class : type.type_class + type_type = type.array?? type.element_type : type + 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(self, qname, type_type, 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(self, qname, type_type, mapping) + end + + array_binding = nil + if type.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.element_type.type_class.name) + 'Array') + array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding) + end + + @type2binding[type] = array_binding ? array_binding : type_binding + @type2binding[type] + end + alias :lookup_type :register_type + + def annotate_arrays(binding, value) + if binding.type.array? + mark_typed_array(value, binding.element_binding.qname) + if binding.element_binding.type.custom? + value.each do |element| + annotate_arrays(binding.element_binding, element) + end + end + elsif binding.type.structured? + binding.type.each_member do |name, type| + member_binding = register_type(type) + member_value = value.respond_to?('[]') ? value[name] : value.send(name) + annotate_arrays(member_binding, member_value) if type.custom? + end + end + end + + private + def typed_struct_factory(type_class) + if Object.const_defined?('ActiveRecord') + if type_class.ancestors.include?(ActiveRecord::Base) + 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 mark_typed_array(array, qname) + (class << array; self; end).class_eval do + define_method(:arytype) do + qname + end + end + 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 + + def soap_type_name(type_name) + type_name.gsub(/::/, '..') + end + end + + class SoapBinding + attr :qname + attr :type + attr :mapping + attr :element_binding + + def initialize(marshaler, qname, type, mapping, element_binding=nil) + @marshaler = marshaler + @qname = qname + @type = type + @mapping = mapping + @element_binding = element_binding + end + + def type_name + @type.custom? ? @qname.name : nil + end + + def qualified_type_name(ns=nil) + if @type.custom? + "#{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 + + def eql?(other) + @qname == other.qname + end + alias :== :eql? + + def hash + @qname.hash + 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.class.columns.each do |column| + key = column.name.to_s + value = obj.send(key) + 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 +end diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb index f8ff12cfa3..de6c8c8a30 100644 --- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -1,3 +1,5 @@ +require 'xmlrpc/marshal' + module ActionWebService # :nodoc: module Protocol # :nodoc: module XmlRpc # :nodoc: @@ -6,22 +8,61 @@ module ActionWebService # :nodoc: end class XmlRpcProtocol < AbstractProtocol # :nodoc: - def initialize - @encoder = WS::Encoding::XmlRpcEncoding.new - @marshaler = WS::Marshaling::XmlRpcMarshaler.new + def decode_action_pack_request(action_pack_request) + service_name = action_pack_request.parameters['action'] + decode_request(action_pack_request.raw_post, service_name) end - 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'] + def decode_request(raw_request, service_name) + method_name, params = XMLRPC::Marshal.load_call(raw_request) Request.new(self, method_name, params, service_name) end + def encode_request(method_name, params, param_types) + if param_types + params = params.dup + param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) } + end + XMLRPC::Marshal.dump_call(method_name, *params) + end + + def decode_response(raw_response) + [nil, XMLRPC::Marshal.load_response(raw_response)] + end + + def encode_response(method_name, return_value, return_type) + return_value = true if return_value.nil? + if return_type + return_value = value_to_xmlrpc_wire_format(return_value, return_type) + end + raw_response = XMLRPC::Marshal.dump_response(return_value) + Response.new(raw_response, 'text/xml', return_value) + 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 + + def value_to_xmlrpc_wire_format(value, value_type) + if value_type.array? + value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) } + else + if value.is_a?(ActionWebService::Struct) + struct = {} + value.class.members.each do |name, type| + struct[name.to_s] = value_to_xmlrpc_wire_format(value[name], type) + end + struct + elsif value.is_a?(ActiveRecord::Base) + value.attributes.dup + elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException) + XMLRPC::FaultException.new(2, value.message) + else + value + end + end + end end end end diff --git a/actionwebservice/lib/action_web_service/scaffolding.rb b/actionwebservice/lib/action_web_service/scaffolding.rb index e311515fba..0fab01eced 100644 --- a/actionwebservice/lib/action_web_service/scaffolding.rb +++ b/actionwebservice/lib/action_web_service/scaffolding.rb @@ -1,9 +1,13 @@ require 'ostruct' require 'uri' require 'benchmark' +require 'pathname' module ActionWebService module Scaffolding # :nodoc: + class ScaffoldingError < ActionWebServiceError # :nodoc: + end + def self.append_features(base) super base.extend(ClassMethods) @@ -63,17 +67,32 @@ module ActionWebService when :xmlrpc protocol = Protocol::XmlRpc::XmlRpcProtocol.new end - cgi = @request.cgi + cgi = @request.respond_to?(:cgi) ? @request.cgi : nil bm = Benchmark.measure do - @method_request_xml = @scaffold_method.encode_rpc_call(protocol.marshaler, protocol.encoder, @params['method_params'].dup) - @request = protocol.create_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml) + protocol.register_api(@scaffold_service.api) + params = @params['method_params'] ? @params['method_params'].dup : nil + params = @scaffold_method.cast_expects(params) + @method_request_xml = protocol.encode_request(@scaffold_method.public_name, params, @scaffold_method.expects) + @request = protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml) dispatch_web_service_request @method_response_xml = @response.body - @method_return_value = protocol.marshaler.unmarshal(protocol.encoder.decode_rpc_response(@method_response_xml)[1]).value + method_name, obj = protocol.decode_response(@method_response_xml) + if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception) + raise obj.detail.cause + elsif obj.is_a?(XMLRPC::FaultException) + raise obj + end + @method_return_value = @scaffold_method.cast_returns(obj) end @method_elapsed = bm.real add_instance_variables_to_assigns - @response = ::ActionController::CgiResponse.new(cgi) + template = @response.template + if cgi + @response = ::ActionController::CgiResponse.new(cgi) + else + @response = ::ActionController::TestResponse.new + end + @response.template = template @performed_render = false render_#{action_name}_scaffold 'result' end @@ -99,20 +118,19 @@ module ActionWebService end def scaffold_path(template_name) - File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml" + Pathname.new(File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml").realpath.to_s end END end end module Helpers # :nodoc: - def method_parameter_input_fields(method, param_spec, i) - klass = method.param_class(param_spec) - unless WS::BaseTypes.base_type?(klass) - name = method.param_name(param_spec, i) + def method_parameter_input_fields(method, type) + name = type.name.to_s + type_name = type.type + unless type_name.is_a?(Symbol) raise "Parameter #{name}: Structured/array types not supported in scaffolding input fields yet" end - type_name = method.param_type(param_spec) field_name = "method_params[]" case type_name when :int @@ -168,6 +186,9 @@ module ActionWebService @name = name.to_s @object = real_service @api = @object.class.web_service_api + if @api.nil? + raise ScaffoldingError, "No web service API attached to #{object.class}" + end @api_methods = {} @api_methods_full = [] @api.api_methods.each do |name, method| diff --git a/actionwebservice/lib/action_web_service/struct.rb b/actionwebservice/lib/action_web_service/struct.rb index d4e2ba9ce6..c5e6346bfa 100644 --- a/actionwebservice/lib/action_web_service/struct.rb +++ b/actionwebservice/lib/action_web_service/struct.rb @@ -33,11 +33,20 @@ module ActionWebService send(name.to_s) end + # Iterates through each member + def each_pair(&block) + self.class.members.each do |name, type| + yield name, type + end + end + class << self # 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 => WS::BaseTypes.canonical_param_type_class(type)) + name = name.to_sym + type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0) + write_inheritable_hash("struct_members", name => type) class_eval <<-END def #{name}; @#{name}; end def #{name}=(value); @#{name} = value; end @@ -47,6 +56,10 @@ module ActionWebService def members # :nodoc: read_inheritable_attribute("struct_members") || {} end + + def member_type(name) # :nodoc: + members[name.to_sym] + end end end end diff --git a/actionwebservice/lib/action_web_service/support/signature_types.rb b/actionwebservice/lib/action_web_service/support/signature_types.rb new file mode 100644 index 0000000000..5c57254bc3 --- /dev/null +++ b/actionwebservice/lib/action_web_service/support/signature_types.rb @@ -0,0 +1,191 @@ +module ActionWebService # :nodoc: + module SignatureTypes # :nodoc: + def canonical_signature(signature) + return nil if signature.nil? + i = -1 + signature.map{ |spec| canonical_signature_entry(spec, i += 1) } + end + + def canonical_signature_entry(spec, i) + name = "param#{i}" + if spec.is_a?(Hash) + name = spec.keys.first + spec = spec.values.first + type = spec + else + type = spec + end + if spec.is_a?(Array) + ArrayType.new(canonical_signature_entry(spec[0], 0), name) + else + type = canonical_type(type) + if type.is_a?(Symbol) + BaseType.new(type, name) + else + StructuredType.new(type, name) + end + end + end + + def canonical_type(type) + type_name = symbol_name(type) || class_to_type_name(type) + type = type_name || type + return canonical_type_name(type) if type.is_a?(Symbol) + type + 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, :timestamp + :time + when :datetime + :datetime + when :date + :date + else + raise(TypeError, "#{name} is not a valid base type") + end + end + + def canonical_type_class(type) + type = canonical_type(type) + type.is_a?(Symbol) ? type_name_to_class(type) : type + end + + def symbol_name(name) + return name.to_sym if name.is_a?(Symbol) || name.is_a?(String) + nil + end + + def class_to_type_name(klass) + klass = klass.class unless klass.is_a?(Class) + if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass) + :int + elsif klass == String + :string + elsif klass == TrueClass || klass == FalseClass + :bool + elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass) + :float + elsif klass == Time + :time + elsif klass == DateTime + :datetime + elsif klass == Date + :date + else + nil + end + end + + 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 + when :datetime + DateTime + else + nil + end + end + + def derived_from?(ancestor, child) + child.ancestors.include?(ancestor) + end + + module_function :type_name_to_class + module_function :class_to_type_name + module_function :symbol_name + module_function :canonical_type_class + module_function :canonical_type_name + module_function :canonical_type + module_function :canonical_signature_entry + module_function :canonical_signature + module_function :derived_from? + end + + class BaseType # :nodoc: + include SignatureTypes + + attr :type + attr :type_class + attr :name + + def initialize(type, name) + @type = canonical_type(type) + @type_class = canonical_type_class(@type) + @name = name + end + + def custom? + false + end + + def array? + false + end + + def structured? + false + end + end + + class ArrayType < BaseType # :nodoc: + attr :element_type + + def initialize(element_type, name) + super(Array, name) + @element_type = element_type + end + + def custom? + true + end + + def array? + true + end + end + + class StructuredType < BaseType # :nodoc: + def each_member + if @type_class.respond_to?(:members) + @type_class.members.each do |name, type| + yield name, type + end + elsif @type_class.respond_to?(:columns) + i = 0 + @type_class.columns.each do |column| + yield column.name, canonical_signature_entry(column.klass, i += 1) + end + end + end + + def custom? + true + end + + def structured? + true + end + end +end diff --git a/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml b/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml index 0516738da6..e62d234c1a 100644 --- a/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml +++ b/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml @@ -5,10 +5,10 @@ <%= hidden_field_tag "method", @scaffold_method.public_name %> <% i = 0 %> -<% @scaffold_method.expects.each do |spec| %> +<% @scaffold_method.expects.each do |type| %> <p> - <label for="method_params[]"><%= @scaffold_method.param_name(spec, i).camelize %></label><br /> - <%= method_parameter_input_fields(@scaffold_method, spec, i) %> + <label for="method_params[]"><%= type.name.to_s.camelize %></label><br /> + <%= method_parameter_input_fields(@scaffold_method, type) %> </p> <% i += 1 %> <% end %> diff --git a/actionwebservice/lib/action_web_service/test_invoke.rb b/actionwebservice/lib/action_web_service/test_invoke.rb index 5d364e4225..c22ca618dc 100644 --- a/actionwebservice/lib/action_web_service/test_invoke.rb +++ b/actionwebservice/lib/action_web_service/test_invoke.rb @@ -21,7 +21,7 @@ module Test # :nodoc: # invoke the specified layered API method on the correct service def invoke_layered(service_name, method_name, *args) - if protocol == :soap + if protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol) raise "SOAP protocol support for :layered dispatching mode is not available" end prepare_request('api', service_name, method_name, *args) @@ -37,10 +37,10 @@ module Test # :nodoc: @request.env['HTTP_CONTENT_TYPE'] = 'text/xml' @request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args) case protocol - when :soap + when ActionWebService::Protocol::Soap::SoapProtocol soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}" @request.env['HTTP_SOAPACTION'] = soap_action - when :xmlrpc + when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol @request.env.delete('HTTP_SOAPACTION') end end @@ -52,19 +52,18 @@ module Test # :nodoc: when :delegated, :layered api = @controller.web_service_object(service_name.to_sym).class.web_service_api end + protocol.register_api(api) 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)) + protocol.encode_request(public_method_name(service_name, api_method_name), args.dup, method.expects) end def decode_rpc_response - public_method_name, return_value = encoder.decode_rpc_response(@response.body) - result = marshaler.unmarshal(return_value).value + public_method_name, return_value = protocol.decode_response(@response.body) unless @return_exceptions - exception = is_exception?(result) + exception = is_exception?(return_value) raise exception if exception end - result + return_value end def public_method_name(service_name, api_method_name) @@ -86,25 +85,7 @@ module Test # :nodoc: end def protocol - @protocol ||= :soap - end - - def marshaler - case protocol - when :soap - @soap_marshaler ||= WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService' - when :xmlrpc - @xmlrpc_marshaler ||= WS::Marshaling::XmlRpcMarshaler.new - end - end - - def encoder - case protocol - when :soap - @soap_encoder ||= WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService' - when :xmlrpc - @xmlrpc_encoder ||= WS::Encoding::XmlRpcEncoding.new - end + @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.new end def is_exception?(obj) diff --git a/actionwebservice/lib/action_web_service/vendor/ws.rb b/actionwebservice/lib/action_web_service/vendor/ws.rb deleted file mode 100644 index 18a32a555e..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws.rb +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 4266a7141d..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/common.rb +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 790317639b..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 257c7d0993..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index f4d2f5a7d6..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +++ /dev/null @@ -1,90 +0,0 @@ -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 deleted file mode 100644 index 4876c08705..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'xmlrpc/marshal' - -module WS - module Encoding - 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) - 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) - [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 deleted file mode 100644 index 3a0a2e8cc1..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index e897f62297..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb +++ /dev/null @@ -1,33 +0,0 @@ -module WS - module Marshaling - class AbstractMarshaler - def initialize - @base_type_caster = BaseTypeCaster.new - end - - def marshal(param) - raise NotImplementedError - end - - def unmarshal(param) - raise NotImplementedError - end - - 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 deleted file mode 100644 index 14c8d8401d..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +++ /dev/null @@ -1,283 +0,0 @@ -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='') - super() - @type_namespace = type_namespace - @registry = SOAP::Mapping::Registry.new - @spec2binding = {} - end - - def marshal(param) - annotate_arrays(param.info.data, param.value) - if param.value.is_a?(Exception) - detail = SOAP::Mapping::SOAPException.new(param.value) - soap_obj = SOAP::SOAPFault.new( - SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, '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(self, soap_object.arytype, Array, mapping) - else - param.info.data = SoapBinding.new(self, soap_type, value.class, 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(self, qname, type_class, 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(self, qname, type_class, 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(self, qname, Array, array_mapping, type_binding) - end - - @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) - if binding.is_typed_array? - mark_typed_array(value, binding.element_binding.qname) - if binding.element_binding.is_custom_type? - value.each do |element| - annotate_arrays(register_type(element.class), element) - end - end - elsif binding.is_typed_struct? - if binding.type_class.respond_to?(:members) - binding.type_class.members.each do |name, spec| - member_binding = register_type(spec) - member_value = value.respond_to?('[]') ? value[name] : value.send(name) - if member_binding.is_custom_type? - annotate_arrays(member_binding, member_value) - end - end - end - end - end - - def mark_typed_array(array, qname) - (class << array; self; end).class_eval do - define_method(:arytype) do - qname - end - end - end - - 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 :type_class - attr :mapping - attr :element_binding - - def initialize(marshaler, qname, type_class, mapping, element_binding=nil) - @marshaler = marshaler - @qname = qname - @type_class = type_class - @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) - if is_typed_struct? - if @mapping[1] == SOAP::Mapping::Registry::TypedStructFactory - if @type_class.respond_to?(:members) - @type_class.members.each do |name, spec| - yield name, spec - end - end - elsif @mapping[1].is_a?(WS::Marshaling::SoapActiveRecordStructFactory) - @type_class.columns.each do |column| - yield column.name, column.klass - end - end - 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.class.columns.each do |column| - key = column.name.to_s - value = obj.send(key) - 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 deleted file mode 100644 index 56cc7597fb..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +++ /dev/null @@ -1,143 +0,0 @@ -module WS - module Marshaling - class XmlRpcError < WSError - end - - class XmlRpcMarshaler < AbstractMarshaler - def initialize - super() - @spec2binding = {} - end - - def marshal(param) - value = param.value - cast_outbound_recursive(param.value, spec_for(param)) rescue value - end - - def unmarshal(obj) - obj.param - end - - def typed_unmarshal(obj, spec) - 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) - 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 - alias :lookup_type :register_type - - def cast_inbound_recursive(value, spec) - binding = lookup_type(spec) - case binding - when XmlRpcArrayBinding - value.map{ |x| cast_inbound(x, binding.element_klass) } - when XmlRpcBinding - cast_inbound(value, binding.klass) - end - end - - def cast_outbound_recursive(value, spec) - binding = lookup_type(spec) - case binding - when XmlRpcArrayBinding - value.map{ |x| cast_outbound(x, binding.element_klass) } - when XmlRpcBinding - cast_outbound(value, binding.klass) - end - end - - 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 - end - - 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 - 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 deleted file mode 100644 index 88098b5bce..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/types.rb +++ /dev/null @@ -1,165 +0,0 @@ -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, data, index=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? - int_value = Integer(value) rescue nil - return true if int_value == 1 - return false if int_value == 0 - 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 |