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)