提交 100015cd 编写于 作者: L Leon Breedt

Make all custom types and method calls are declared in the 'urn:ActionWebService'

namespace as a default, fixes SOAP marshaling for .NET, a regression since the merge.

Make array annotation be recursive in WS::Marshaling::SoapMarshaling, this makes
typed arrays buried in nested structures still be annotated correctly.

Support :layered dispatching mode for XML-RPC namespaced method names.

Change WS::ParamInfo.create signature to require type_binding, and update all
uses of this.

Restore #default_api_method functionality, fixes a regression since the merge.

Fix marshalling of ActiveRecord::Base derivatives, fixes a regression since the merge.

This changeset closes #676, #677, and #678.


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@811 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 19dddf24
*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)
......
......@@ -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 <tt>:delegated</tt>.
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 <tt>:layered</tt>.
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
<tt>mt.getCategories</tt> will be dispatched as the <tt>getCategories</tt>
method on the <tt>:mt</tt> service.
== Using the client support
Action Web Service includes client classes that can use the same API
......
......@@ -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" ) }
......
= 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
......@@ -24,14 +24,16 @@ class Soap < Base
# will be sent with HTTP POST.
#
# Valid options:
# [<tt>:service_name</tt>] If the remote server has used a custom +wsdl_service_name+
# option, you must specify it here
# [<tt>:type_namespace</tt>] If the remote server has used a custom namespace to
# declare its custom types, you can specify it here
# [<tt>:method_namespace</tt>] 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]
......
......@@ -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
......
......@@ -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
......
......@@ -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
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
......
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|
......
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
......
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
......
......@@ -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,
......
......@@ -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
......@@ -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)
......
......@@ -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)
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
$:.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
......
......@@ -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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册