diff --git a/actionwebservice/ChangeLog b/actionwebservice/CHANGELOG similarity index 74% rename from actionwebservice/ChangeLog rename to actionwebservice/CHANGELOG index fdb2cc89daa5c0b075736cad3062a9f380e11c4d..b2fe8fe3de695004eba01262d986373e6e5c5d0c 100644 --- a/actionwebservice/ChangeLog +++ b/actionwebservice/CHANGELOG @@ -1,11 +1,14 @@ *0.6.0* (Unreleased) - * lib/*, test/*: refactored SOAP and XML-RPC protocol specifics into - a small seperate library named 'ws', and drop it in vendor. be - more relaxed about the type of received parameters, perform casting - for XML-RPC if possible, but fallback to the received parameters. - performed extensive cleanup of the way we use SOAP, so that marshaling - of custom and array types should somewhat faster. +* Improve error message reporting. Bugs in either AWS or the web service itself will send back a protocol-specific error report message if possible, otherwise, provide as much detail as possible. + +* Removed type checking of received parameters, and perform casting for XML-RPC if possible, but fallback to the received parameters if casting fails, closes #677 + +* Refactored SOAP and XML-RPC marshaling and encoding into a small library devoted exclusively to protocol specifics, also cleaned up the SOAP marshaling approach, so that array and custom type marshaling should be a bit faster. + +* Add namespaced XML-RPC method name support, closes #678 + +* Replace '::' with '..' in fully qualified type names for marshaling and WSDL. This improves interoperability with .NET, and closes #676. *0.5.0* (24th February, 2005) diff --git a/actionwebservice/README b/actionwebservice/README index c8a7da1c5714c19da00901f19dfbe7a36b632ae6..1d93bde02ffc1985609ea8d1d3194670c17eee8e 100644 --- a/actionwebservice/README +++ b/actionwebservice/README @@ -114,7 +114,7 @@ For this example, protocol requests for +Add+ and +Remove+ methods sent to === Delegated dispatching This mode can be turned on by setting the +web_service_dispatching_mode+ option -in a controller. +in a controller to :delegated. In this mode, the controller contains one or more web service objects (objects that implement an ActionWebService::API::Base definition). These web service @@ -153,6 +153,50 @@ Other controller actions (actions that aren't the target of a +web_service+ call are ignored for ActionWebService purposes, and can do normal action tasks. +=== Layered dispatching + +This mode can be turned on by setting the +web_service_dispatching_mode+ option +in a controller to :layered. + +This mode is similar to _delegated_ mode, in that multiple web service objects +can be attached to one controller, however, all protocol requests are sent to a +single endpoint. + +This mode is only usable by XML-RPC. In this mode, method names can contain +_prefixes_, which will indicate which web service object implements the API +identified by that prefix. + +The _prefix_ can be any word, followed by a period. + +==== Layered dispatching example + + + class ApiController < ApplicationController + web_service_dispatching_mode :layered + + web_service :mt, MovableTypeService.new + web_service :blogger, BloggerService.new + web_service :metaWeblog, MetaWeblogService.new + end + + class MovableTypeService < ActionWebService::Base + ... + end + + class BloggerService < ActionWebService::Base + ... + end + + class MetaWeblogService < ActionWebService::API::Base + ... + end + + +For this example, a remote call for a method with a name like +mt.getCategories will be dispatched as the getCategories +method on the :mt service. + + == Using the client support Action Web Service includes client classes that can use the same API diff --git a/actionwebservice/Rakefile b/actionwebservice/Rakefile index fff80c3ff5abc26f8b58df0b04dfd8c1b381a84e..72c767976d93aa52b89fe8551e611cdaa2f2628b 100644 --- a/actionwebservice/Rakefile +++ b/actionwebservice/Rakefile @@ -31,12 +31,14 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Action Web Service -- Web services for Action Pack" rdoc.options << '--line-numbers --inline-source --main README --accessor class_inheritable_option=RW' rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('CHANGELOG') rdoc.rdoc_files.include('lib/action_web_service.rb') rdoc.rdoc_files.include('lib/action_web_service/*.rb') rdoc.rdoc_files.include('lib/action_web_service/api/*.rb') rdoc.rdoc_files.include('lib/action_web_service/client/*.rb') - rdoc.rdoc_files.include('lib/action_web_service/protocol/*.rb') + rdoc.rdoc_files.include('lib/action_web_service/container/*.rb') rdoc.rdoc_files.include('lib/action_web_service/dispatcher/*.rb') + rdoc.rdoc_files.include('lib/action_web_service/protocol/*.rb') rdoc.rdoc_files.include('lib/action_web_service/support/*.rb') } @@ -63,7 +65,7 @@ spec = Gem::Specification.new do |s| s.require_path = 'lib' s.autorequire = 'action_web_service' - s.files = [ "Rakefile", "setup.rb", "README", "TODO", "ChangeLog", "MIT-LICENSE" ] + s.files = [ "Rakefile", "setup.rb", "README", "TODO", "CHANGELOG", "MIT-LICENSE" ] s.files = s.files + Dir.glob( "examples/**/*" ).delete_if { |item| item.include?( "\.svn" ) } s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) } s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) } diff --git a/actionwebservice/TODO b/actionwebservice/TODO index b3b4ec2c00ee3bb1dc9413e814a9b9faf4cae264..a5ca99e89f0792e37cb64d560a94a9b3d7367444 100644 --- a/actionwebservice/TODO +++ b/actionwebservice/TODO @@ -1,6 +1,3 @@ -= 0.6.0 Tasks - - finish off tickets #676, #677, #678 - = Refactoring - Don't have clean way to go from SOAP Class object to the xsd:NAME type string -- NaHi possibly looking at remedying this situation diff --git a/actionwebservice/lib/action_web_service/client/soap_client.rb b/actionwebservice/lib/action_web_service/client/soap_client.rb index b93f6475d9d8e727842a44a80666ad557eb38b3b..d3975d89a2bcefa50fe9e40024f37d384f25ff35 100644 --- a/actionwebservice/lib/action_web_service/client/soap_client.rb +++ b/actionwebservice/lib/action_web_service/client/soap_client.rb @@ -24,14 +24,16 @@ class Soap < Base # will be sent with HTTP POST. # # Valid options: - # [:service_name] If the remote server has used a custom +wsdl_service_name+ - # option, you must specify it here + # [:type_namespace] If the remote server has used a custom namespace to + # declare its custom types, you can specify it here + # [:method_namespace] If the remote server has used a custom namespace to + # declare its methods, you can specify it here def initialize(api, endpoint_uri, options={}) super(api, endpoint_uri) - @service_name = options[:service_name] - @namespace = @service_name ? '' : "urn:#{@service_name}" - @marshaler = WS::Marshaling::SoapMarshaler.new - @encoder = WS::Encoding::SoapRpcEncoding.new + @type_namespace = options[:type_namespace] || 'urn:ActionWebService' + @method_namespace = options[:method_namespace] || 'urn:ActionWebService' + @marshaler = WS::Marshaling::SoapMarshaler.new @type_namespace + @encoder = WS::Encoding::SoapRpcEncoding.new @method_namespace @soap_action_base = options[:soap_action_base] @soap_action_base ||= URI.parse(endpoint_uri).path @driver = create_soap_rpc_driver(api, endpoint_uri) @@ -53,7 +55,7 @@ def create_soap_rpc_driver(api, endpoint_uri) driver.mapping_registry = @marshaler.registry api.api_methods.each do |name, info| public_name = api.public_api_method_name(name) - qname = XSD::QName.new(@namespace, public_name) + qname = XSD::QName.new(@method_namespace, public_name) action = soap_action(public_name) expects = info[:expects] returns = info[:returns] diff --git a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb index dc7ad1517fea91338e2f334767db835e3bb57fc5..27fe537404076baec54739b4b9801ac7d07b4332 100644 --- a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb +++ b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb @@ -56,7 +56,7 @@ def transform_outgoing_method_params(method_name, params) i = 0 expects.each do |spec| type_binding = @marshaler.register_type(spec) - info = WS::ParamInfo.create(spec, i, type_binding) + info = WS::ParamInfo.create(spec, type_binding, i) params[i] = @marshaler.marshal(WS::Param.new(params[i], info)) i += 1 end @@ -68,7 +68,7 @@ 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], 0, type_binding) + info = WS::ParamInfo.create(returns[0], type_binding, 0) info.name = 'return' @marshaler.transform_inbound(WS::Param.new(return_value, info)) end diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb index b7560afc87d46b9a119b71a039da0088ea5fd31e..641f291533984d66b55b4f9df38e5326627a3ee4 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -19,7 +19,7 @@ def invoke_web_service_request(protocol_request) case web_service_dispatching_mode when :direct web_service_direct_invoke(invocation) - when :delegated + when :delegated, :layered web_service_delegated_invoke(invocation) end end @@ -27,7 +27,11 @@ def invoke_web_service_request(protocol_request) def web_service_direct_invoke(invocation) @method_params = invocation.method_ordered_params return_value = self.__send__(invocation.api_method_name) - returns = invocation.returns ? invocation.returns[0] : nil + if invocation.api.has_api_method?(invocation.api_method_name) + returns = invocation.returns ? invocation.returns[0] : nil + else + returns = return_value.class + end invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns) end @@ -44,29 +48,43 @@ def web_service_delegated_invoke(invocation) end def web_service_invocation(request) + public_method_name = request.method_name invocation = Invocation.new invocation.protocol = request.protocol invocation.service_name = request.service_name + if web_service_dispatching_mode == :layered + if request.method_name =~ /^([^\.]+)\.(.*)$/ + public_method_name = $2 + 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 invocation.service = self - when :delegated - invocation.service = web_service_object(request.service_name) rescue nil + when :delegated, :layered + invocation.service = web_service_object(invocation.service_name) rescue nil unless invocation.service - raise(DispatcherError, "failed to instantiate service #{invocation.service_name}") + raise(DispatcherError, "service #{invocation.service_name} not available") end invocation.api = invocation.service.class.web_service_api end - public_method_name = request.method_name - unless invocation.api.has_public_api_method?(public_method_name) - raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}") + if invocation.api.has_public_api_method?(public_method_name) + invocation.api_method_name = invocation.api.api_method_name(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 + 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})") end - invocation.public_method_name = public_method_name - invocation.api_method_name = invocation.api.api_method_name(public_method_name) info = invocation.api.api_methods[invocation.api_method_name] - invocation.expects = info[:expects] - invocation.returns = info[:returns] + 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| @@ -83,7 +101,7 @@ def web_service_invocation(request) params = [] invocation.expects.each do |spec| type_binding = invocation.protocol.register_signature_type(spec) - info = WS::ParamInfo.create(spec, i, type_binding) + info = WS::ParamInfo.create(spec, type_binding, i) params << WS::Param.new(invocation.method_ordered_params[i], info) i += 1 end @@ -96,10 +114,15 @@ def web_service_invocation(request) invocation.method_ordered_params = [] invocation.method_named_params = {} end + if invocation.returns + invocation.returns.each do |spec| + invocation.protocol.register_signature_type(spec) + end + end invocation end - class Invocation + class Invocation # :nodoc: attr_accessor :protocol attr_accessor :service_name attr_accessor :api 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 a35029b40d93e2c3a50a46a70e44d2ca806eb710..7080f813d490ddf54607c053601f9bcc1d58a5fb 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -20,20 +20,22 @@ class << self base.add_web_service_definition_callback do |klass, name, info| if klass.web_service_dispatching_mode == :delegated klass.class_eval "def #{name}; dispatch_web_service_request; end" + elsif klass.web_service_dispatching_mode == :layered + klass.class_eval 'def api; dispatch_web_service_request; end' end end base.extend(ClassMethods) base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods) end - module ClassMethods + module ClassMethods # :nodoc: def inherited(child) inherited_without_action_controller(child) child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction) end end - module InstanceMethods + module InstanceMethods # :nodoc: private def dispatch_web_service_request request = discover_web_service_request(@request) @@ -105,16 +107,16 @@ def web_service_direct_invoke(invocation) def log_request(request, body) unless logger.nil? name = request.method_name - params = request.method_params.map{|x| x.value.inspect} + params = request.method_params.map{|x| "#{x.info.name}=>#{x.value.inspect}"} service = request.service_name - logger.debug("\nWeb Service Request: #{name}(#{params}) #{service}") + logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}") logger.debug(indent(body)) end end def log_response(response, elapsed=nil) unless logger.nil? - logger.debug("\nWeb Service Response (%f):" + (elapsed ? " (%f):" % elapsed : ":")) + logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":")) logger.debug(indent(response.body)) end end @@ -124,7 +126,7 @@ def indent(body) end end - module WsdlAction + module WsdlAction # :nodoc: XsdNs = 'http://www.w3.org/2001/XMLSchema' WsdlNs = 'http://schemas.xmlsoap.org/wsdl/' SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/' @@ -155,7 +157,7 @@ def to_wsdl xml = '' dispatching_mode = web_service_dispatching_mode global_service_name = wsdl_service_name - namespace = "urn:#{global_service_name}" + namespace = 'urn:ActionWebService' soap_action_base = "/#{controller_name}" marshaler = WS::Marshaling::SoapMarshaler.new(namespace) @@ -164,18 +166,18 @@ def to_wsdl when :direct api = self.class.web_service_api web_service_name = controller_class_name.sub(/Controller$/, '').underscore - apis[web_service_name] = [api, register_api(marshaler, api)] + apis[web_service_name] = [api, register_api(api, marshaler)] when :delegated self.class.web_services.each do |web_service_name, info| service = web_service_object(web_service_name) api = service.class.web_service_api - apis[web_service_name] = [api, register_api(marshaler, api)] + apis[web_service_name] = [api, register_api(api, marshaler)] end end custom_types = [] apis.values.each do |api, bindings| bindings.each do |b| - custom_types << b if b.is_custom_type? + custom_types << b end end @@ -200,15 +202,16 @@ def to_wsdl xm.xsd(:complexContent) do xm.xsd(:restriction, 'base' => 'soapenc:Array') do xm.xsd(:attribute, 'ref' => 'soapenc:arrayType', - 'wsdl:arrayType' => binding.element_binding.qualified_type_name + '[]') + 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]') end end end when binding.is_typed_struct? xm.xsd(:complexType, 'name' => binding.type_name) do xm.xsd(:all) do - binding.each_member do |name, type_name| - xm.xsd(:element, 'name' => name, 'type' => type_name) + binding.each_member do |name, spec| + b = marshaler.register_type(spec) + xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens')) end end end @@ -229,7 +232,7 @@ def to_wsdl returns = info[:returns] if returns binding = marshaler.register_type(returns[0]) - xm.part('name' => 'return', 'type' => binding.qualified_type_name) + xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens')) end else expects = info[:expects] @@ -242,7 +245,7 @@ def to_wsdl param_name = "param#{i}" end binding = marshaler.register_type(type) - xm.part('name' => param_name, 'type' => binding.qualified_type_name) + xm.part('name' => param_name, 'type' => binding.qualified_type_name('typens')) i += 1 end if expects end @@ -273,7 +276,7 @@ def to_wsdl public_name = api.public_api_method_name(name) xm.operation('name' => public_name) do case web_service_dispatching_mode - when :direct + when :direct, :layered soap_action = soap_action_base + "/api/" + public_name when :delegated soap_action = soap_action_base \ @@ -325,20 +328,34 @@ def binding_name_for(global_service, service) "#{global_service}#{service.to_s.camelize}Binding" end - def register_api(marshaler, api) - type_bindings = [] + def register_api(api, marshaler) + bindings = {} + traverse_custom_types(api, marshaler) do |binding| + bindings[binding] = nil unless bindings.has_key?(binding.type_class) + end + bindings.keys + end + + def traverse_custom_types(api, marshaler, &block) 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)} + 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 + 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) end - type_bindings + yield binding end - end + end end end end diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index f628fc4aee5495aee4c3b7eab4b48b60a423dc2f..7526539d5336dd54ac8c8e0f5a7b8cff50ce7a68 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -1,9 +1,9 @@ module ActionWebService # :nodoc: module Protocol # :nodoc: - class ProtocolError < ActionWebService::ActionWebServiceError + class ProtocolError < ActionWebServiceError # :nodoc: end - class Request + class Request # :nodoc: attr :protocol attr :method_name attr :method_params @@ -17,7 +17,7 @@ def initialize(protocol, method_name, method_params, service_name) end end - class Response + class Response # :nodoc: attr :body attr :content_type diff --git a/actionwebservice/lib/action_web_service/protocol/discovery.rb b/actionwebservice/lib/action_web_service/protocol/discovery.rb index ab51958ed903fdbc822767e2294378545c54aa82..40875975bf2a94a4d8842d5525d94365a5088581 100644 --- a/actionwebservice/lib/action_web_service/protocol/discovery.rb +++ b/actionwebservice/lib/action_web_service/protocol/discovery.rb @@ -1,18 +1,18 @@ -module ActionWebService - module Protocol - module Discovery +module ActionWebService # :nodoc: + module Protocol # :nodoc: + module Discovery # :nodoc: def self.included(base) base.extend(ClassMethods) base.send(:include, ActionWebService::Protocol::Discovery::InstanceMethods) end - module ClassMethods + module ClassMethods # :nodoc: def register_protocol(klass) write_inheritable_array("web_service_protocols", [klass]) end end - module InstanceMethods + module InstanceMethods # :nodoc: private def discover_web_service_request(ap_request) (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb index f2e761f4310a70f6e0598410573dd7b94330c705..6e3df54b0066936472dce630b10911d865a26fc9 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -1,15 +1,15 @@ -module ActionWebService - module Protocol - module Soap +module ActionWebService # :nodoc: + module Protocol # :nodoc: + module Soap # :nodoc: def self.included(base) base.register_protocol(SoapProtocol) base.class_inheritable_option(:wsdl_service_name) end - class SoapProtocol + class SoapProtocol # :nodoc: def initialize - @encoder = WS::Encoding::SoapRpcEncoding.new - @marshaler = WS::Marshaling::SoapMarshaler.new + @encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService' + @marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService' end def unmarshal_request(ap_request) @@ -23,7 +23,7 @@ def unmarshal_request(ap_request) def marshal_response(method_name, return_value, signature_type) if !return_value.nil? && signature_type type_binding = @marshaler.register_type(signature_type) - info = WS::ParamInfo.create(signature_type, 0, type_binding) + info = WS::ParamInfo.create(signature_type, type_binding, 0) return_value = @marshaler.marshal(WS::Param.new(return_value, info)) else return_value = nil diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb index 1533a2bdb9f94a64aa027a03c9b2c14eff273b63..8d6af246ec3b69ba8426a1268d23cfa915f8e2aa 100644 --- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -1,11 +1,11 @@ -module ActionWebService - module Protocol - module XmlRpc +module ActionWebService # :nodoc: + module Protocol # :nodoc: + module XmlRpc # :nodoc: def self.included(base) base.register_protocol(XmlRpcProtocol) end - class XmlRpcProtocol + class XmlRpcProtocol # :nodoc: attr :marshaler def initialize @@ -25,7 +25,7 @@ def unmarshal_request(ap_request) def marshal_response(method_name, return_value, signature_type) if !return_value.nil? && signature_type type_binding = @marshaler.register_type(signature_type) - info = WS::ParamInfo.create(signature_type, 0, type_binding) + info = WS::ParamInfo.create(signature_type, type_binding, 0) return_value = @marshaler.marshal(WS::Param.new(return_value, info)) else return_value = nil diff --git a/actionwebservice/lib/action_web_service/struct.rb b/actionwebservice/lib/action_web_service/struct.rb index 77f4fbf4aa8158ad8f1271ed98e530c304e42363..d4e2ba9ce6eea266d9029e537681f2854a27d25e 100644 --- a/actionwebservice/lib/action_web_service/struct.rb +++ b/actionwebservice/lib/action_web_service/struct.rb @@ -17,8 +17,7 @@ module ActionWebService # person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe') # # Active Record model classes are already implicitly supported for method - # return signatures. A structure containing its columns as members will be - # automatically generated if its present in a signature. + # return signatures. class Struct # If a Hash is given as argument to an ActionWebService::Struct constructor, 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 99e2a7ff2818b824b85ba934de1581f092d22a08..3032639510f730c4e25597b536df503c089cfcb1 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 @@ -19,13 +19,7 @@ def initialize(type_namespace='') end def marshal(param) - if param.info.type.is_a?(Array) - (class << param.value; self; end).class_eval do - define_method(:arytype) do - param.info.data.qname - end - end - end + 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( @@ -48,9 +42,9 @@ def unmarshal(obj) param.info.type = value.class mapping = @registry.find_mapped_soap_class(param.info.type) rescue nil if soap_type && soap_type.name == 'Array' && soap_type.namespace == SoapEncodingNS - param.info.data = SoapBinding.new(soap_object.arytype, mapping) + param.info.data = SoapBinding.new(self, soap_object.arytype, Array, mapping) else - param.info.data = SoapBinding.new(soap_type, mapping) + param.info.data = SoapBinding.new(self, soap_type, value.class, mapping) end param end @@ -71,7 +65,7 @@ def register_type(spec) if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil) qname = mapping[2] ? mapping[2][:type] : nil qname ||= soap_base_type_name(mapping[0]) - type_binding = SoapBinding.new(qname, mapping) + 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, @@ -79,7 +73,7 @@ def register_type(spec) typed_struct_factory(type_class), { :type => qname }) mapping = @registry.find_mapped_soap_class(type_class) - type_binding = SoapBinding.new(qname, mapping) + type_binding = SoapBinding.new(self, qname, type_class, mapping) end array_binding = nil @@ -92,13 +86,43 @@ def register_type(spec) array_mapping = @registry.find_mapped_soap_class(Array) end qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name) + 'Array') - array_binding = SoapBinding.new(qname, array_mapping, type_binding) + array_binding = SoapBinding.new(self, qname, Array, array_mapping, type_binding) end @spec2binding[spec] = array_binding ? array_binding : type_binding + @spec2binding[spec] 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.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) @@ -132,11 +156,14 @@ def initialize(param, soap_object) class SoapBinding attr :qname + attr :type_class attr :mapping attr :element_binding - def initialize(qname, mapping, element_binding=nil) + 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 @@ -155,8 +182,18 @@ def is_typed_struct? end def each_member(&block) - unless is_typed_struct? - raise(SoapError, "not a structured type") + 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 @@ -220,5 +257,19 @@ def soap2obj(obj_class, node, info, map) return false end end + + module ActiveRecordSoapMarshallable + def allocate + obj = super + attrs = {} + self.columns.each{|c| attrs[c.name.to_s] = c.default} + obj.instance_variable_set('@attributes', attrs) + obj + end + end + + if Object.const_defined?('ActiveRecord') + ActiveRecord::Base.extend(ActiveRecordSoapMarshallable) + 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 index 24b96dc3270ec22c673e3bfa06dd630d1bd097f1..650cdb0848debff967d48a39cd8fee805aa92372 100644 --- a/actionwebservice/lib/action_web_service/vendor/ws/types.rb +++ b/actionwebservice/lib/action_web_service/vendor/ws/types.rb @@ -105,7 +105,7 @@ def initialize(name, type, data=nil) @data = data end - def self.create(spec, index=nil, data=nil) + 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) diff --git a/actionwebservice/test/abstract_dispatcher.rb b/actionwebservice/test/abstract_dispatcher.rb index b743afce4ce952f0262a8f1f1299fa6dc0025ae9..da07d2cf8c481597c55334dd3429886c8024acda 100644 --- a/actionwebservice/test/abstract_dispatcher.rb +++ b/actionwebservice/test/abstract_dispatcher.rb @@ -9,7 +9,7 @@ def initialize(*args) class << self def name - "Node" + "DispatcherTest::Node" end def columns(*args) @@ -26,6 +26,11 @@ def connection end end + class Person < ActionWebService::Struct + member :id, :int + member :name, :string + end + class API < ActionWebService::API::Base api_method :add, :expects => [:int, :int], :returns => [:int] api_method :interceptee @@ -38,9 +43,14 @@ class DirectAPI < ActionWebService::API::Base api_method :before_filtered api_method :after_filtered, :returns => [[:int]] api_method :struct_return, :returns => [[Node]] + api_method :base_struct_return, :returns => [[Person]] api_method :thrower api_method :void end + + class VirtualAPI < ActionWebService::API::Base + default_api_method :fallback + end class Service < ActionWebService::Base web_service_api API @@ -78,6 +88,32 @@ def do_intercept(name, args) end end + class MTAPI < ActionWebService::API::Base + inflect_names false + api_method :getCategories, :returns => [[:string]] + end + + class BloggerAPI < ActionWebService::API::Base + inflect_names false + api_method :getCategories, :returns => [[:string]] + end + + class MTService < ActionWebService::Base + web_service_api MTAPI + + def getCategories + ["mtCat1", "mtCat2"] + end + end + + class BloggerService < ActionWebService::Base + web_service_api BloggerAPI + + def getCategories + ["bloggerCat1", "bloggerCat2"] + end + end + class AbstractController < ActionController::Base def generate_wsdl to_wsdl @@ -89,6 +125,13 @@ class DelegatedController < AbstractController web_service(:test_service) { @service ||= Service.new; @service } end + + class LayeredController < AbstractController + web_service_dispatching_mode :layered + + web_service(:mt) { @mt_service ||= MTService.new; @mt_service } + web_service(:blogger) { @blogger_service ||= BloggerService.new; @blogger_service } + end class DirectController < AbstractController web_service_api DirectAPI @@ -134,6 +177,12 @@ def struct_return n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2') [n1, n2] end + + def base_struct_return + p1 = Person.new('id' => 1, 'name' => 'person1') + p2 = Person.new('id' => 2, 'name' => 'person2') + [p1, p2] + end def void @void_called = @method_params @@ -149,6 +198,14 @@ def alwaysok @after_filter_called = true end end + + class VirtualController < AbstractController + web_service_api VirtualAPI + + def fallback + "fallback!" + end + end end module DispatcherCommonTests @@ -163,11 +220,25 @@ def test_direct_dispatching assert(do_method_call(@direct_controller, 'Void', 3, 4, 5) == true) end assert(@direct_controller.void_called == []) + result = do_method_call(@direct_controller, 'BaseStructReturn') + case @encoder + when WS::Encoding::SoapRpcEncoding + assert(result[0].is_a?(DispatcherTest::Person)) + assert(result[1].is_a?(DispatcherTest::Person)) + when WS::Encoding::XmlRpcEncoding + assert(result[0].is_a?(Hash)) + assert(result[1].is_a?(Hash)) + end end def test_direct_entrypoint assert(@direct_controller.respond_to?(:api)) end + + def test_virtual_dispatching + assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualOne')) + assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualTwo')) + end def test_direct_filtering assert_equal(false, @direct_controller.before_filter_called) @@ -269,8 +340,13 @@ def do_method_call(container, public_method_name, *params) api = container.class.web_service_api when :delegated api = container.web_service_object(service_name(container)).class.web_service_api + when :layered + service_name = nil + if public_method_name =~ /^([^\.]+)\.(.*)$/ + service_name = $1 + end + api = container.web_service_object(service_name.to_sym).class.web_service_api end - method_name = api.api_method_name(public_method_name) info = api.api_methods[method_name] || {} params = params.dup ((info[:expects] || []) + (info[:returns] || [])).each do |spec| @@ -279,7 +355,7 @@ def do_method_call(container, public_method_name, *params) expects = info[:expects] (0..(params.length-1)).each do |i| type_binding = @marshaler.register_type(expects ? expects[i] : params[i].class) - info = WS::ParamInfo.create(expects ? expects[i] : params[i].class, i, type_binding) + info = WS::ParamInfo.create(expects ? expects[i] : params[i].class, type_binding, i) params[i] = @marshaler.marshal(WS::Param.new(params[i], info)) end body = @encoder.encode_rpc_call(public_method_name, params) diff --git a/actionwebservice/test/dispatcher_action_controller_soap_test.rb b/actionwebservice/test/dispatcher_action_controller_soap_test.rb index 9cb99be78d20ebe35cd026d301eb4b60f10a0659..6d50bbba8a2d86d60f0d2351fc6ca7f92557ec1a 100644 --- a/actionwebservice/test/dispatcher_action_controller_soap_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_soap_test.rb @@ -11,10 +11,11 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase include DispatcherCommonTests def setup - @encoder = WS::Encoding::SoapRpcEncoding.new - @marshaler = WS::Marshaling::SoapMarshaler.new + @encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService' + @marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService' @direct_controller = DirectController.new @delegated_controller = DelegatedController.new + @virtual_controller = VirtualController.new end def test_wsdl_generation @@ -23,8 +24,15 @@ def test_wsdl_generation end def test_wsdl_action - ensure_valid_wsdl_action DelegatedController.new - ensure_valid_wsdl_action DirectController.new + delegated_types = ensure_valid_wsdl_action DelegatedController.new + delegated_names = delegated_types.map{|x| x.name.name} + assert(delegated_names.include?('DispatcherTest..NodeArray')) + assert(delegated_names.include?('DispatcherTest..Node')) + direct_types = ensure_valid_wsdl_action DirectController.new + direct_names = direct_types.map{|x| x.name.name} + assert(direct_names.include?('DispatcherTest..NodeArray')) + assert(direct_names.include?('DispatcherTest..Node')) + assert(direct_names.include?('IntegerArray')) end def test_autoloading @@ -80,6 +88,11 @@ def ensure_valid_wsdl(wsdl) assert(port.name.name.index(':').nil?) end end + types = definitions.collect_complextypes.map{|x| x.name} + types.each do |type| + assert(type.namespace == 'urn:ActionWebService') + end + definitions.collect_complextypes end def ensure_valid_wsdl_action(controller) diff --git a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb index 13f193e2c5772d41a17b90b6c2087866d9c70f8f..87677dec3eb0874b0496547a80d0aa7021e0a557 100644 --- a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb @@ -9,6 +9,15 @@ def setup @marshaler = WS::Marshaling::XmlRpcMarshaler.new @direct_controller = DirectController.new @delegated_controller = DelegatedController.new + @layered_controller = LayeredController.new + @virtual_controller = VirtualController.new + end + + def test_layered_dispatching + mt_cats = do_method_call(@layered_controller, 'mt.getCategories') + assert_equal(["mtCat1", "mtCat2"], mt_cats) + blogger_cats = do_method_call(@layered_controller, 'blogger.getCategories') + assert_equal(["bloggerCat1", "bloggerCat2"], blogger_cats) end protected diff --git a/actionwebservice/test/ws/abstract_encoding.rb b/actionwebservice/test/ws/abstract_encoding.rb index 9a6aec44e0974f0b203ee09429e0b002477ccc62..6032d94c48ce52f21f117353579b5938937450ab 100644 --- a/actionwebservice/test/ws/abstract_encoding.rb +++ b/actionwebservice/test/ws/abstract_encoding.rb @@ -45,7 +45,7 @@ def encode_rpc_call(method_name, signature, params) params = params.dup (0..(signature.length-1)).each do |i| type_binding = @marshaler.register_type(signature[i]) - info = WS::ParamInfo.create(signature[i], i, type_binding) + info = WS::ParamInfo.create(signature[i], type_binding, i) params[i] = @marshaler.marshal(WS::Param.new(params[i], info)) end @encoder.encode_rpc_call(method_name, params) @@ -57,7 +57,7 @@ def decode_rpc_call(obj) def encode_rpc_response(method_name, signature, param) type_binding = @marshaler.register_type(signature[0]) - info = WS::ParamInfo.create(signature[0], 0, type_binding) + info = WS::ParamInfo.create(signature[0], type_binding, 0) param = @marshaler.marshal(WS::Param.new(param, info)) @encoder.encode_rpc_response(method_name, param) end diff --git a/actionwebservice/test/ws/abstract_unit.rb b/actionwebservice/test/ws/abstract_unit.rb index f5015bea691fcb7b769832c954787537f001c15e..5d4f5ce8564ba8a86125ad989ab241e2f9b624e7 100644 --- a/actionwebservice/test/ws/abstract_unit.rb +++ b/actionwebservice/test/ws/abstract_unit.rb @@ -1,6 +1,5 @@ -$:.unshift(File.dirname(File.dirname(__FILE__)) + '/../lib') -$:.unshift(File.dirname(File.dirname(__FILE__)) + '/../lib/action_web_service/vendor') -puts $:.inspect +require 'pathname' +$:.unshift(Pathname.new(File.dirname(__FILE__)).realpath.to_s + '/../../lib/action_web_service/vendor') require 'test/unit' require 'ws' begin diff --git a/actionwebservice/test/ws/soap_marshaling_test.rb b/actionwebservice/test/ws/soap_marshaling_test.rb index ee6413478d85869bbec5a796b085f9cc9b41bae1..7c7413190e807d82e9a9c0f4ea7d62fc16c401e5 100644 --- a/actionwebservice/test/ws/soap_marshaling_test.rb +++ b/actionwebservice/test/ws/soap_marshaling_test.rb @@ -33,14 +33,14 @@ def test_abstract end def test_marshaling - info = WS::ParamInfo.create(Nested::MyClass) + info = WS::ParamInfo.create(Nested::MyClass, @marshaler.register_type(Nested::MyClass)) param = WS::Param.new(Nested::MyClass.new(2, "name"), info) new_param = @marshaler.unmarshal(@marshaler.marshal(param)) assert(param == new_param) end def test_exception_marshaling - info = WS::ParamInfo.create(RuntimeError) + info = WS::ParamInfo.create(RuntimeError, @marshaler.register_type(RuntimeError)) param = WS::Param.new(RuntimeError.new("hello, world"), info) new_param = @marshaler.unmarshal(@marshaler.marshal(param)) assert_equal("hello, world", new_param.value.detail.cause.message) @@ -78,7 +78,7 @@ def connection end end end - info = WS::ParamInfo.create(node_class, 0, @marshaler.register_type(node_class)) + info = WS::ParamInfo.create(node_class, @marshaler.register_type(node_class), 0) ar_obj = node_class.new('name' => 'hello', 'email' => 'test@test.com') param = WS::Param.new(ar_obj, info) obj = @marshaler.marshal(param)