1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

merged the changes for the upcoming 0.6.0:

seperate out protocol marshaling into a small 'ws' library in vendor, so that
AWS itself only does integration with ActionPack, and so we can keep protocol
specific code in AWS proper to a minimum. refactor unit tests to get 95%
code coverage (for a baseline).

be far more relaxed about the types given to us by the remote side, don't do
any poor man's type checking, just try to cast and marshal to the correct types if
possible, and if not, return what they gave us anyway. this should make interoperating
with fuzzy XML-RPC clients easier.

if exception reporting is turned on, do best-effort error responses, so that
we can avoid "Internal protocol error" with no details if there is a bug in
AWS itself.

also perform extensive cleanups on AWS proper.


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@800 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Leon Breedt 2005-02-25 23:39:39 +00:00
parent 10faf204b7
commit 6f5a7b2004
62 changed files with 2187 additions and 2086 deletions

View file

@ -1,3 +1,12 @@
*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.
*0.5.0* (24th February, 2005)
* lib/action_service/dispatcher*: replace "router" fragments with

View file

@ -9,7 +9,7 @@ require 'fileutils'
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = 'actionwebservice'
PKG_VERSION = '0.5.0' + PKG_BUILD
PKG_VERSION = '0.6.0' + PKG_BUILD
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}"
@ -20,7 +20,7 @@ task :default => [ :test ]
# Run the unit tests
Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/*_test.rb'
t.test_files = Dir['test/*_test.rb'] + Dir['test/ws/*_test.rb']
t.verbose = true
}
@ -54,9 +54,9 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "aws"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 1.5.0' + PKG_BUILD)
s.add_dependency('activerecord', '= 1.7.0' + PKG_BUILD)
s.add_dependency('activesupport', '= 1.0.0' + PKG_BUILD)
s.add_dependency('actionpack', '>= 1.5.0' + PKG_BUILD)
s.add_dependency('activerecord', '>= 1.7.0' + PKG_BUILD)
s.add_dependency('activesupport', '>= 1.0.0' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'
@ -94,7 +94,7 @@ def each_source_file(*args)
prefix ||= File.dirname(__FILE__)
open_file = true if open_file.nil?
includes ||= %w[lib\/action_web_service\.rb$ lib\/action_web_service\/.*\.rb$]
excludes ||= %w[]
excludes ||= %w[lib\/action_web_service\/vendor]
Find.find(prefix) do |file_name|
next if file_name =~ /\.svn/
file_name.gsub!(/^\.\//, '')
@ -123,7 +123,7 @@ def each_source_file(*args)
end
end
desc "Count lines of the source code"
desc "Count lines of the AWS source code"
task :lines do
total_lines = total_loc = 0
puts "Per File:"

View file

@ -1,13 +1,6 @@
= Low priority tasks
- add better type mapping tests for XML-RPC
- add tests for ActiveRecord support (with mock objects?)
= 0.6.0 Tasks
- finish off tickets #676, #677, #678
= Refactoring
- Find an alternative way to map interesting types for SOAP (like ActiveRecord
model classes) that doesn't require creation of a sanitized copy object with data
copied from the real one. Ideally this would let us get rid of
ActionWebService::Struct altogether and provide a block that would yield the
attributes and values. "Filters" ? Not sure how to integrate with SOAP though.
- Don't have clean way to go from SOAP Class object to the xsd:NAME type
string -- NaHi possibly looking at remedying this situation

View file

@ -32,7 +32,10 @@ rescue LoadError
require_gem 'activerecord', '>= 1.6.0'
end
$:.unshift(File.dirname(__FILE__))
$:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/")
require 'action_web_service/support/class_inheritable_options'
require 'action_web_service/vendor/ws'
require 'action_web_service/base'
require 'action_web_service/client'
@ -41,20 +44,21 @@ require 'action_web_service/api'
require 'action_web_service/struct'
require 'action_web_service/container'
require 'action_web_service/protocol'
require 'action_web_service/struct'
require 'action_web_service/dispatcher'
ActionWebService::Base.class_eval do
include ActionWebService::API
include ActionWebService::Container::Direct
include ActionWebService::Invocation
end
ActionController::Base.class_eval do
include ActionWebService::Container
include ActionWebService::Protocol::Registry
include ActionWebService::Protocol::Discovery
include ActionWebService::Protocol::Soap
include ActionWebService::Protocol::XmlRpc
include ActionWebService::API
include ActionWebService::API::ActionController
include ActionWebService::Container::Direct
include ActionWebService::Container::Delegated
include ActionWebService::Container::ActionController
include ActionWebService::Dispatcher
include ActionWebService::Dispatcher::ActionController
end

View file

@ -1,2 +1 @@
require 'action_web_service/api/abstract'
require 'action_web_service/api/action_controller'
require 'action_web_service/api/base'

View file

@ -1,70 +1,5 @@
module ActionWebService # :nodoc:
module API # :nodoc:
class APIError < ActionWebService::ActionWebServiceError # :nodoc:
end
def self.append_features(base) # :nodoc:
super
base.extend(ClassMethods)
end
module ClassMethods
# Attaches ActionWebService API +definition+ to the calling class.
#
# Action Controllers can have a default associated API, removing the need
# to call this method if you follow the Action Web Service naming conventions.
#
# A controller with a class name of GoogleSearchController will
# implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
# API definition class to be named <tt>GoogleSearchAPI</tt> or
# <tt>GoogleSearchApi</tt>.
#
# ==== Service class example
#
# class MyService < ActionWebService::Base
# web_service_api MyAPI
# end
#
# class MyAPI < ActionWebService::API::Base
# ...
# end
#
# ==== Controller class example
#
# class MyController < ActionController::Base
# web_service_api MyAPI
# end
#
# class MyAPI < ActionWebService::API::Base
# ...
# end
def web_service_api(definition=nil)
if definition.nil?
read_inheritable_attribute("web_service_api")
else
if definition.is_a?(Symbol)
raise(APIError, "symbols can only be used for #web_service_api inside of a controller")
end
unless definition.respond_to?(:ancestors) && definition.ancestors.include?(Base)
raise(APIError, "#{definition.to_s} is not a valid API definition")
end
write_inheritable_attribute("web_service_api", definition)
call_web_service_api_callbacks(self, definition)
end
end
def add_web_service_api_callback(&block) # :nodoc:
write_inheritable_array("web_service_api_callbacks", [block])
end
private
def call_web_service_api_callbacks(container_class, definition)
(read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
block.call(container_class, definition)
end
end
end
# A web service API class specifies the methods that will be available for
# invocation for an API. It also contains metadata such as the method type
# signature hints.
@ -87,8 +22,6 @@ module ActionWebService # :nodoc:
private_class_method :new, :allocate
class << self
include ActionWebService::Signature
# API methods have a +name+, which must be the Ruby method name to use when
# performing the invocation on the web service object.
#
@ -125,11 +58,11 @@ module ActionWebService # :nodoc:
expects = options[:expects]
returns = options[:returns]
end
expects = canonical_signature(expects) if expects
returns = canonical_signature(returns) if returns
expects = canonical_signature(expects)
returns = canonical_signature(returns)
if expects
expects.each do |param|
klass = signature_parameter_class(param)
klass = WS::BaseTypes.canonical_param_type_class(param)
klass = klass[0] if klass.is_a?(Array)
if klass.ancestors.include?(ActiveRecord::Base)
raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
@ -186,6 +119,10 @@ module ActionWebService # :nodoc:
end
end
def canonical_signature(signature)
return nil if signature.nil?
signature.map{|spec| WS::BaseTypes.canonical_param_type_spec(spec)}
end
end
end
end

View file

@ -1,6 +1,3 @@
require 'action_web_service/support/class_inheritable_options'
require 'action_web_service/support/signature'
module ActionWebService # :nodoc:
class ActionWebServiceError < StandardError # :nodoc:
end

View file

@ -12,28 +12,17 @@ module ActionWebService # :nodoc:
def method_missing(name, *args) # :nodoc:
call_name = method_name(name)
return super(name, *args) if call_name.nil?
perform_invocation(call_name, args)
self.perform_invocation(call_name, args)
end
protected
def perform_invocation(method_name, args) # :nodoc:
raise NotImplementedError, "use a protocol-specific client"
end
private
def method_name(name)
if @api.has_api_method?(name.to_sym)
name.to_s
elsif @api.has_public_api_method?(name.to_s)
@api.api_method_name(name.to_s).to_s
else
nil
end
end
def lookup_class(klass)
klass.is_a?(Hash) ? klass.values[0] : klass
end
end
end
end

View file

@ -28,10 +28,10 @@ module ActionWebService # :nodoc:
# option, you must specify it here
def initialize(api, endpoint_uri, options={})
super(api, endpoint_uri)
@service_name = options[:service_name] || 'ActionWebService'
@namespace = "urn:#{@service_name}"
@mapper = ActionWebService::Protocol::Soap::SoapMapper.new(@namespace)
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.new(@mapper)
@service_name = options[:service_name]
@namespace = @service_name ? '' : "urn:#{@service_name}"
@marshaler = WS::Marshaling::SoapMarshaler.new
@encoder = WS::Encoding::SoapRpcEncoding.new
@soap_action_base = options[:soap_action_base]
@soap_action_base ||= URI.parse(endpoint_uri).path
@driver = create_soap_rpc_driver(api, endpoint_uri)
@ -48,9 +48,9 @@ module ActionWebService # :nodoc:
private
def create_soap_rpc_driver(api, endpoint_uri)
@mapper.map_api(api)
register_api(@marshaler, api)
driver = SoapDriver.new(endpoint_uri, nil)
driver.mapping_registry = @mapper.registry
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)
@ -58,25 +58,38 @@ module ActionWebService # :nodoc:
expects = info[:expects]
returns = info[:returns]
param_def = []
i = 1
i = 0
if expects
expects.each do |klass|
param_name = klass.is_a?(Hash) ? klass.keys[0] : "param#{i}"
param_klass = lookup_class(klass)
mapping = @mapper.lookup(param_klass)
param_def << ['in', param_name, mapping.registry_mapping]
expects.each do |spec|
param_name = spec.is_a?(Hash) ? spec.keys[0].to_s : "param#{i}"
type_binding = @marshaler.register_type(spec)
param_def << ['in', param_name, type_binding.mapping]
i += 1
end
end
if returns
mapping = @mapper.lookup(lookup_class(returns[0]))
param_def << ['retval', 'return', mapping.registry_mapping]
type_binding = @marshaler.register_type(returns[0])
param_def << ['retval', 'return', type_binding.mapping]
end
driver.add_method(qname, action, name.to_s, param_def)
end
driver
end
def register_api(marshaler, api)
type_bindings = []
api.api_methods.each do |name, info|
expects, returns = info[:expects], info[:returns]
if expects
expects.each{|type| type_bindings << marshaler.register_type(type)}
end
if returns
returns.each{|type| type_bindings << marshaler.register_type(type)}
end
end
type_bindings
end
class SoapDriver < SOAP::RPC::Driver # :nodoc:
def add_method(qname, soapaction, name, param_def)
@proxy.add_rpc_method(qname, soapaction, name, param_def)

View file

@ -31,6 +31,7 @@ module ActionWebService # :nodoc:
@api = api
@handler_name = options[:handler_name]
@client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
end
protected
@ -43,18 +44,21 @@ module ActionWebService # :nodoc:
def transform_outgoing_method_params(method_name, params)
info = @api.api_methods[method_name.to_sym]
signature = info[:expects]
signature_length = signature.nil?? 0 : signature.length
if signature_length != params.length
raise(ProtocolError, "API declares #{public_name(method_name)} to accept " +
"#{signature_length} parameters, but #{params.length} parameters " +
"were supplied")
expects = info[:expects]
expects_length = expects.nil?? 0 : expects.length
if expects_length != params.length
raise(ClientError, "API declares #{public_name(method_name)} to accept " +
"#{expects_length} parameters, but #{params.length} parameters " +
"were supplied")
end
if signature_length > 0
signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature)
(1..signature.size).each do |i|
i -= 1
params[i] = Protocol::XmlRpc::XmlRpcProtocol.ruby_to_xmlrpc(params[i], lookup_class(signature[i]))
params = params.dup
if expects_length > 0
i = 0
expects.each do |spec|
type_binding = @marshaler.register_type(spec)
info = WS::ParamInfo.create(spec, i, type_binding)
params[i] = @marshaler.marshal(WS::Param.new(params[i], info))
i += 1
end
end
params
@ -62,10 +66,11 @@ module ActionWebService # :nodoc:
def transform_return_value(method_name, return_value)
info = @api.api_methods[method_name.to_sym]
return true unless signature = info[:returns]
param_klass = lookup_class(signature[0])
signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types([param_klass])
Protocol::XmlRpc::XmlRpcProtocol.xmlrpc_to_ruby(return_value, signature[0])
return true unless returns = info[:returns]
type_binding = @marshaler.register_type(returns[0])
info = WS::ParamInfo.create(returns[0], 0, type_binding)
info.name = 'return'
@marshaler.transform_inbound(WS::Param.new(return_value, info))
end
def public_name(method_name)

View file

@ -1,85 +1,3 @@
module ActionWebService # :nodoc:
module Container # :nodoc:
class ContainerError < ActionWebService::ActionWebServiceError # :nodoc:
end
def self.append_features(base) # :nodoc:
super
base.extend(ClassMethods)
base.send(:include, ActionWebService::Container::InstanceMethods)
end
module ClassMethods
# Declares a web service that will provides access to the API of the given
# +object+. +object+ must be an ActionWebService::Base derivative.
#
# Web service object creation can either be _immediate_, where the object
# instance is given at class definition time, or _deferred_, where
# object instantiation is delayed until request time.
#
# ==== Immediate web service object example
#
# class ApiController < ApplicationController
# web_service_dispatching_mode :delegated
#
# web_service :person, PersonService.new
# end
#
# For deferred instantiation, a block should be given instead of an
# object instance. This block will be executed in controller instance
# context, so it can rely on controller instance variables being present.
#
# ==== Deferred web service object example
#
# class ApiController < ApplicationController
# web_service_dispatching_mode :delegated
#
# web_service(:person) { PersonService.new(@request.env) }
# end
def web_service(name, object=nil, &block)
if (object && block_given?) || (object.nil? && block.nil?)
raise(ContainerError, "either service, or a block must be given")
end
name = name.to_sym
if block_given?
info = { name => { :block => block } }
else
info = { name => { :object => object } }
end
write_inheritable_hash("web_services", info)
call_web_service_definition_callbacks(self, name, info)
end
# Whether this service contains a service with the given +name+
def has_web_service?(name)
web_services.has_key?(name.to_sym)
end
def web_services # :nodoc:
read_inheritable_attribute("web_services") || {}
end
def add_web_service_definition_callback(&block) # :nodoc:
write_inheritable_array("web_service_definition_callbacks", [block])
end
private
def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
(read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
block.call(container_class, web_service_name, service_info)
end
end
end
module InstanceMethods # :nodoc:
def web_service_object(web_service_name)
info = self.class.web_services[web_service_name.to_sym]
unless info
raise(ContainerError, "no such web service '#{web_service_name}'")
end
service = info[:block]
service ? instance_eval(&service) : info[:object]
end
end
end
end
require 'action_web_service/container/direct_container'
require 'action_web_service/container/delegated_container'
require 'action_web_service/container/action_controller_container'

View file

@ -1,5 +1,5 @@
module ActionWebService # :nodoc:
module API # :nodoc:
module Container # :nodoc:
module ActionController # :nodoc:
def self.append_features(base) # :nodoc:
base.class_eval do
@ -36,7 +36,7 @@ module ActionWebService # :nodoc:
api_klass = options.delete(:api) || require_web_service_api(name)
class_eval do
define_method(name) do
probe_protocol_client(api_klass, protocol, endpoint_uri, options)
create_web_service_client(api_klass, protocol, endpoint_uri, options)
end
protected name
end

View file

@ -0,0 +1,87 @@
module ActionWebService # :nodoc:
module Container # :nodoc:
module Delegated # :nodoc:
class ContainerError < ActionWebServiceError # :nodoc:
end
def self.append_features(base) # :nodoc:
super
base.extend(ClassMethods)
base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
end
module ClassMethods
# Declares a web service that will provides access to the API of the given
# +object+. +object+ must be an ActionWebService::Base derivative.
#
# Web service object creation can either be _immediate_, where the object
# instance is given at class definition time, or _deferred_, where
# object instantiation is delayed until request time.
#
# ==== Immediate web service object example
#
# class ApiController < ApplicationController
# web_service_dispatching_mode :delegated
#
# web_service :person, PersonService.new
# end
#
# For deferred instantiation, a block should be given instead of an
# object instance. This block will be executed in controller instance
# context, so it can rely on controller instance variables being present.
#
# ==== Deferred web service object example
#
# class ApiController < ApplicationController
# web_service_dispatching_mode :delegated
#
# web_service(:person) { PersonService.new(@request.env) }
# end
def web_service(name, object=nil, &block)
if (object && block_given?) || (object.nil? && block.nil?)
raise(ContainerError, "either service, or a block must be given")
end
name = name.to_sym
if block_given?
info = { name => { :block => block } }
else
info = { name => { :object => object } }
end
write_inheritable_hash("web_services", info)
call_web_service_definition_callbacks(self, name, info)
end
# Whether this service contains a service with the given +name+
def has_web_service?(name)
web_services.has_key?(name.to_sym)
end
def web_services # :nodoc:
read_inheritable_attribute("web_services") || {}
end
def add_web_service_definition_callback(&block) # :nodoc:
write_inheritable_array("web_service_definition_callbacks", [block])
end
private
def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
(read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
block.call(container_class, web_service_name, service_info)
end
end
end
module InstanceMethods # :nodoc:
def web_service_object(web_service_name)
info = self.class.web_services[web_service_name.to_sym]
unless info
raise(ContainerError, "no such web service '#{web_service_name}'")
end
service = info[:block]
service ? instance_eval(&service) : info[:object]
end
end
end
end
end

View file

@ -0,0 +1,70 @@
module ActionWebService # :nodoc:
module Container # :nodoc:
module Direct # :nodoc:
class ContainerError < ActionWebServiceError # :nodoc:
end
def self.append_features(base) # :nodoc:
super
base.extend(ClassMethods)
end
module ClassMethods
# Attaches ActionWebService API +definition+ to the calling class.
#
# Action Controllers can have a default associated API, removing the need
# to call this method if you follow the Action Web Service naming conventions.
#
# A controller with a class name of GoogleSearchController will
# implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
# API definition class to be named <tt>GoogleSearchAPI</tt> or
# <tt>GoogleSearchApi</tt>.
#
# ==== Service class example
#
# class MyService < ActionWebService::Base
# web_service_api MyAPI
# end
#
# class MyAPI < ActionWebService::API::Base
# ...
# end
#
# ==== Controller class example
#
# class MyController < ActionController::Base
# web_service_api MyAPI
# end
#
# class MyAPI < ActionWebService::API::Base
# ...
# end
def web_service_api(definition=nil)
if definition.nil?
read_inheritable_attribute("web_service_api")
else
if definition.is_a?(Symbol)
raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
end
unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
raise(ContainerError, "#{definition.to_s} is not a valid API definition")
end
write_inheritable_attribute("web_service_api", definition)
call_web_service_api_callbacks(self, definition)
end
end
def add_web_service_api_callback(&block) # :nodoc:
write_inheritable_array("web_service_api_callbacks", [block])
end
private
def call_web_service_api_callbacks(container_class, definition)
(read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
block.call(container_class, definition)
end
end
end
end
end
end

View file

@ -14,136 +14,103 @@ module ActionWebService # :nodoc:
module InstanceMethods # :nodoc:
private
def dispatch_web_service_request(action_pack_request)
protocol_request = protocol_response = nil
bm = Benchmark.measure do
protocol_request = probe_request_protocol(action_pack_request)
protocol_response = dispatch_protocol_request(protocol_request)
end
[protocol_request, protocol_response, bm.real, nil]
rescue Exception => e
protocol_response = prepare_exception_response(protocol_request, e)
[protocol_request, prepare_exception_response(protocol_request, e), nil, e]
end
def dispatch_protocol_request(protocol_request)
def invoke_web_service_request(protocol_request)
invocation = web_service_invocation(protocol_request)
case web_service_dispatching_mode
when :direct
dispatch_direct_request(protocol_request)
web_service_direct_invoke(invocation)
when :delegated
dispatch_delegated_request(protocol_request)
else
raise(ContainerError, "unsupported dispatching mode :#{web_service_dispatching_mode}")
web_service_delegated_invoke(invocation)
end
end
def dispatch_direct_request(protocol_request)
request = prepare_dispatch_request(protocol_request)
return_value = direct_invoke(request)
protocol_request.marshal(return_value)
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
invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns)
end
def dispatch_delegated_request(protocol_request)
request = prepare_dispatch_request(protocol_request)
return_value = delegated_invoke(request)
protocol_request.marshal(return_value)
end
def direct_invoke(request)
return nil unless before_direct_invoke(request)
return_value = send(request.method_name)
after_direct_invoke(request)
return_value
end
def before_direct_invoke(request)
@method_params = request.params
end
def after_direct_invoke(request)
end
def delegated_invoke(request)
def web_service_delegated_invoke(invocation)
cancellation_reason = nil
web_service = request.web_service
return_value = web_service.perform_invocation(request.method_name, request.params) do |x|
return_value = invocation.service.perform_invocation(invocation.api_method_name, invocation.method_ordered_params) do |x|
cancellation_reason = x
end
if cancellation_reason
raise(DispatcherError, "request canceled: #{cancellation_reason}")
end
return_value
returns = invocation.returns ? invocation.returns[0] : nil
invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns)
end
def prepare_dispatch_request(protocol_request)
api = method_name = web_service_name = web_service = params = nil
public_method_name = protocol_request.public_method_name
def web_service_invocation(request)
invocation = Invocation.new
invocation.protocol = request.protocol
invocation.service_name = request.service_name
case web_service_dispatching_mode
when :direct
api = self.class.web_service_api
invocation.api = self.class.web_service_api
invocation.service = self
when :delegated
web_service_name = protocol_request.web_service_name
web_service = web_service_object(web_service_name)
api = web_service.class.web_service_api
end
method_name = api.api_method_name(public_method_name)
signature = nil
if method_name
signature = api.api_methods[method_name]
protocol_request.type = Protocol::CheckedMessage
protocol_request.signature = signature[:expects]
protocol_request.return_signature = signature[:returns]
else
method_name = api.default_api_method
if method_name
protocol_request.type = Protocol::UncheckedMessage
else
raise(DispatcherError, "no such method #{web_service_name}##{public_method_name}")
invocation.service = web_service_object(request.service_name) rescue nil
unless invocation.service
raise(DispatcherError, "failed to instantiate service #{invocation.service_name}")
end
invocation.api = invocation.service.class.web_service_api
end
params = protocol_request.unmarshal
DispatchRequest.new(
:api => api,
:public_method_name => public_method_name,
:method_name => method_name,
:signature => signature,
:web_service_name => web_service_name,
:web_service => web_service,
:params => params)
end
def prepare_exception_response(protocol_request, exception)
if protocol_request && exception
case web_service_dispatching_mode
when :direct
if web_service_exception_reporting
return protocol_request.protocol.marshal_exception(exception)
end
when :delegated
web_service = web_service_object(protocol_request.web_service_name)
if web_service && web_service.class.web_service_exception_reporting
return protocol_request.protocol.marshal_exception(exception)
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}")
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]
if invocation.expects
i = 0
invocation.method_ordered_params = request.method_params.map do |param|
if invocation.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
marshaler = invocation.protocol.marshaler
decoded_param = WS::Encoding::XmlRpcDecodedParam.new(param.info.name, param.value)
marshaled_param = marshaler.typed_unmarshal(decoded_param, invocation.expects[i]) rescue nil
param = marshaled_param ? marshaled_param : param
end
i += 1
param.value
end
i = 0
params = []
invocation.expects.each do |spec|
type_binding = invocation.protocol.register_signature_type(spec)
info = WS::ParamInfo.create(spec, i, type_binding)
params << WS::Param.new(invocation.method_ordered_params[i], info)
i += 1
end
invocation.method_ws_params = params
invocation.method_named_params = {}
invocation.method_ws_params.each do |param|
invocation.method_named_params[param.info.name] = param.value
end
else
protocol_request.protocol.marshal_exception(RuntimeError.new("missing protocol request or exception"))
invocation.method_ordered_params = []
invocation.method_named_params = {}
end
rescue Exception
nil
invocation
end
class DispatchRequest
attr :api
attr :public_method_name
attr :method_name
attr :signature
attr :web_service_name
attr :web_service
attr :params
def initialize(values={})
values.each{|k,v| instance_variable_set("@#{k.to_s}", v)}
end
class Invocation
attr_accessor :protocol
attr_accessor :service_name
attr_accessor :api
attr_accessor :public_method_name
attr_accessor :api_method_name
attr_accessor :method_ordered_params
attr_accessor :method_named_params
attr_accessor :method_ws_params
attr_accessor :expects
attr_accessor :returns
attr_accessor :service
end
end
end

View file

@ -1,3 +1,6 @@
require 'benchmark'
require 'builder/xmlmarkup'
module ActionWebService # :nodoc:
module Dispatcher # :nodoc:
module ActionController # :nodoc:
@ -7,106 +10,121 @@ module ActionWebService # :nodoc:
class << self
alias_method :inherited_without_action_controller, :inherited
end
alias_method :before_direct_invoke_without_action_controller, :before_direct_invoke
alias_method :after_direct_invoke_without_action_controller, :after_direct_invoke
alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
end
base.add_web_service_api_callback do |klass, api|
if klass.web_service_dispatching_mode == :direct
klass.class_eval <<-EOS
def api
controller_dispatch_web_service_request
end
EOS
klass.class_eval 'def api; dispatch_web_service_request; end'
end
end
base.add_web_service_definition_callback do |klass, name, info|
if klass.web_service_dispatching_mode == :delegated
klass.class_eval <<-EOS
def #{name}
controller_dispatch_web_service_request
end
EOS
klass.class_eval "def #{name}; dispatch_web_service_request; end"
end
end
base.extend(ClassMethods)
base.send(:include, ActionWebService::Dispatcher::ActionController::Invocation)
base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
end
module ClassMethods # :nodoc:
module ClassMethods
def inherited(child)
inherited_without_action_controller(child)
child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlGeneration)
child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
end
end
module Invocation # :nodoc:
module InstanceMethods
private
def controller_dispatch_web_service_request
request, response, elapsed, exception = dispatch_web_service_request(@request)
if response
begin
log_request(request)
log_error(exception) if exception && logger
log_response(response, elapsed)
response_options = { :type => response.content_type, :disposition => 'inline' }
send_data(response.raw_body, response_options)
rescue Exception => e
log_error(e) unless logger.nil?
render_text("Internal protocol error", "500 Internal Server Error")
end
else
logger.error("No response available") unless logger.nil?
render_text("Internal protocol error", "500 Internal Server Error")
end
end
def before_direct_invoke(request)
before_direct_invoke_without_action_controller(request)
@params ||= {}
signature = request.signature
if signature && (expects = request.signature[:expects])
(0..(@method_params.size-1)).each do |i|
if expects[i].is_a?(Hash)
@params[expects[i].keys[0].to_s] = @method_params[i]
else
@params['param%d' % i] = @method_params[i]
def dispatch_web_service_request
request = discover_web_service_request(@request)
if request
log_request(request, @request.raw_post)
response = nil
exception = nil
bm = Benchmark.measure do
begin
response = invoke_web_service_request(request)
rescue Exception => e
exception = e
end
end
if exception
log_error(exception) unless logger.nil?
send_web_service_error_response(request, exception)
else
send_web_service_response(response, bm.real)
end
else
exception = DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
send_web_service_error_response(request, exception)
end
rescue Exception => e
log_error(e) unless logger.nil?
send_web_service_error_response(request, e)
end
def send_web_service_response(response, elapsed=nil)
log_response(response, elapsed)
options = { :type => response.content_type, :disposition => 'inline' }
send_data(response.body, options)
end
def send_web_service_error_response(request, exception)
if request
unless self.class.web_service_exception_reporting
exception = DispatcherError.new("Internal server error (exception raised)")
end
response = request.protocol.marshal_response(request.method_name, exception, exception.class)
send_web_service_response(response)
else
if self.class.web_service_exception_reporting
message = exception.message
else
message = "Exception raised"
end
render_text("Internal protocol error: #{message}", "500 #{message}")
end
end
def web_service_direct_invoke(invocation)
@params ||= {}
invocation.method_named_params.each do |name, value|
@params[name] = value
end
@params['action'] = request.method_name.to_s
@session ||= {}
@assigns ||= {}
return nil if before_action == false
true
end
def after_direct_invoke(request)
after_direct_invoke_without_action_controller(request)
@params['action'] = invocation.api_method_name.to_s
if before_action == false
raise(DispatcherError, "Method filtered")
end
return_value = web_service_direct_invoke_without_controller(invocation)
after_action
return_value
end
def log_request(request)
unless logger.nil? || request.nil?
logger.debug("\nWeb Service Request:")
indented = request.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
logger.debug(indented)
def log_request(request, body)
unless logger.nil?
name = request.method_name
params = request.method_params.map{|x| x.value.inspect}
service = request.service_name
logger.debug("\nWeb Service Request: #{name}(#{params}) #{service}")
logger.debug(indent(body))
end
end
def log_response(response, elapsed)
unless logger.nil? || response.nil?
logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":"))
indented = response.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
logger.debug(indented)
def log_response(response, elapsed=nil)
unless logger.nil?
logger.debug("\nWeb Service Response (%f):" + (elapsed ? " (%f):" % elapsed : ":"))
logger.debug(indent(response.body))
end
end
unless method_defined?(:logger)
def logger; @logger; end
def indent(body)
body.split(/\n/).map{|x| " #{x}"}.join("\n")
end
end
module WsdlGeneration # :nodoc:
module WsdlAction
XsdNs = 'http://www.w3.org/2001/XMLSchema'
WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
@ -117,40 +135,53 @@ module ActionWebService # :nodoc:
case @request.method
when :get
begin
host_name = @request.env['HTTP_HOST'] || @request.env['SERVER_NAME']
uri = "http://#{host_name}/#{controller_name}/"
soap_action_base = "/#{controller_name}"
xml = to_wsdl(self, uri, soap_action_base)
send_data(xml, :type => 'text/xml', :disposition => 'inline')
options = { :type => 'text/xml', :disposition => 'inline' }
send_data(to_wsdl, options)
rescue Exception => e
log_error e unless logger.nil?
render_text('', "500 #{e.message}")
log_error(e) unless logger.nil?
end
when :post
render_text('', "500 POST not supported")
render_text('POST not supported', '500 POST not supported')
end
end
private
def to_wsdl(container, uri, soap_action_base)
wsdl = ""
web_service_dispatching_mode = container.web_service_dispatching_mode
mapper = container.class.soap_mapper
namespace = mapper.custom_namespace
wsdl_service_name = namespace.split(/:/)[1]
services = {}
mapper.map_container_services(container) do |name, api, api_methods|
services[name] = [api, api_methods]
def base_uri
host = @request ? (@request.env['HTTP_HOST'] || @request.env['SERVER_NAME']) : 'localhost'
'http://%s/%s/' % [host, controller_name]
end
def to_wsdl
xml = ''
dispatching_mode = web_service_dispatching_mode
global_service_name = wsdl_service_name
namespace = "urn:#{global_service_name}"
soap_action_base = "/#{controller_name}"
marshaler = WS::Marshaling::SoapMarshaler.new(namespace)
apis = {}
case dispatching_mode
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)]
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)]
end
end
custom_types = mapper.custom_types
xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2)
custom_types = []
apis.values.each do |api, bindings|
bindings.each do |b|
custom_types << b if b.is_custom_type?
end
end
xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
xm.instruct!
xm.definitions('name' => wsdl_service_name,
xm.definitions('name' => wsdl_service_name,
'targetNamespace' => namespace,
'xmlns:typens' => namespace,
'xmlns:xsd' => XsdNs,
@ -158,95 +189,95 @@ module ActionWebService # :nodoc:
'xmlns:soapenc' => SoapEncodingNs,
'xmlns:wsdl' => WsdlNs,
'xmlns' => WsdlNs) do
# Custom type XSD generation
# Generate XSD
if custom_types.size > 0
xm.types do
xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
custom_types.each do |klass, mapping|
custom_types.each do |binding|
case
when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping)
xm.xsd(:complexType, 'name' => mapping.type_name) do
when binding.is_typed_array?
xm.xsd(:complexType, 'name' => binding.type_name) do
xm.xsd(:complexContent) do
xm.xsd(:restriction, 'base' => 'soapenc:Array') do
xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
'wsdl:arrayType' => mapping.element_mapping.qualified_type_name + '[]')
'wsdl:arrayType' => binding.element_binding.qualified_type_name + '[]')
end
end
end
when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping)
xm.xsd(:complexType, 'name' => mapping.type_name) do
when binding.is_typed_struct?
xm.xsd(:complexType, 'name' => binding.type_name) do
xm.xsd(:all) do
mapping.each_attribute do |name, type_name|
binding.each_member do |name, type_name|
xm.xsd(:element, 'name' => name, 'type' => type_name)
end
end
end
else
raise(WsdlError, "unsupported mapping type #{mapping.class.name}")
end
end
end
end
end
services.each do |service_name, service_values|
service_api, api_methods = service_values
# Parameter list message definitions
api_methods.each do |method_name, method_signature|
# APIs
apis.each do |api_name, values|
api = values[0]
api.api_methods.each do |name, info|
gen = lambda do |msg_name, direction|
xm.message('name' => msg_name) do
sym = nil
if direction == :out
if method_signature[:returns]
xm.part('name' => 'return', 'type' => method_signature[:returns][0].qualified_type_name)
returns = info[:returns]
if returns
binding = marshaler.register_type(returns[0])
xm.part('name' => 'return', 'type' => binding.qualified_type_name)
end
else
mapping_list = method_signature[:expects]
expects = info[:expects]
i = 1
mapping_list.each do |mapping|
if mapping.is_a?(Hash)
param_name = mapping.keys.shift
mapping = mapping.values.shift
expects.each do |type|
if type.is_a?(Hash)
param_name = type.keys.shift
type = type.values.shift
else
param_name = "param#{i}"
end
xm.part('name' => param_name, 'type' => mapping.qualified_type_name)
binding = marshaler.register_type(type)
xm.part('name' => param_name, 'type' => binding.qualified_type_name)
i += 1
end if mapping_list
end if expects
end
end
end
public_name = service_api.public_api_method_name(method_name)
public_name = api.public_api_method_name(name)
gen.call(public_name, :in)
gen.call("#{public_name}Response", :out)
end
# Declare the port
port_name = port_name_for(wsdl_service_name, service_name)
# Port
port_name = port_name_for(global_service_name, api_name)
xm.portType('name' => port_name) do
api_methods.each do |method_name, method_signature|
public_name = service_api.public_api_method_name(method_name)
api.api_methods.each do |name, info|
public_name = api.public_api_method_name(name)
xm.operation('name' => public_name) do
xm.input('message' => "typens:#{public_name}")
xm.output('message' => "typens:#{public_name}Response")
end
end
end
# Bind the port to SOAP
binding_name = binding_name_for(wsdl_service_name, service_name)
# Bind it
binding_name = binding_name_for(global_service_name, api_name)
xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
api_methods.each do |method_name, method_signature|
public_name = service_api.public_api_method_name(method_name)
api.api_methods.each do |name, info|
public_name = api.public_api_method_name(name)
xm.operation('name' => public_name) do
case web_service_dispatching_mode
when :direct
soap_action = soap_action_base + "/api/" + public_name
when :delegated
soap_action = soap_action_base \
+ "/" + service_name.to_s \
+ "/" + api_name.to_s \
+ "/" + public_name
end
xm.soap(:operation, 'soapAction' => soap_action)
@ -266,32 +297,46 @@ module ActionWebService # :nodoc:
end
end
end
# Define the service
xm.service('name' => "#{wsdl_service_name}Service") do
services.each do |service_name, service_values|
port_name = port_name_for(wsdl_service_name, service_name)
binding_name = binding_name_for(wsdl_service_name, service_name)
# Define it
xm.service('name' => "#{global_service_name}Service") do
apis.each do |api_name, values|
port_name = port_name_for(global_service_name, api_name)
binding_name = binding_name_for(global_service_name, api_name)
case web_service_dispatching_mode
when :direct
binding_target = 'api'
when :delegated
binding_target = service_name.to_s
binding_target = api_name.to_s
end
xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
xm.soap(:address, 'location' => "#{uri}#{binding_target}")
xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
end
end
end
end
end
def port_name_for(wsdl_service_name, service_name)
"#{wsdl_service_name}#{service_name.to_s.camelize}Port"
def port_name_for(global_service, service)
"#{global_service}#{service.to_s.camelize}Port"
end
def binding_name_for(wsdl_service_name, service_name)
"#{wsdl_service_name}#{service_name.to_s.camelize}Binding"
def binding_name_for(global_service, service)
"#{global_service}#{service.to_s.camelize}Binding"
end
def register_api(marshaler, api)
type_bindings = []
api.api_methods.each do |name, info|
expects, returns = info[:expects], info[:returns]
if expects
expects.each{|type| type_bindings << marshaler.register_type(type)}
end
if returns
returns.each{|type| type_bindings << marshaler.register_type(type)}
end
end
type_bindings
end
end
end

View file

@ -1,4 +1,4 @@
require 'action_web_service/protocol/abstract'
require 'action_web_service/protocol/registry'
require 'action_web_service/protocol/discovery'
require 'action_web_service/protocol/soap_protocol'
require 'action_web_service/protocol/xmlrpc_protocol'

View file

@ -1,126 +1,28 @@
module ActionWebService # :nodoc:
module Protocol # :nodoc:
CheckedMessage = :checked
UncheckedMessage = :unchecked
class ProtocolError < ActionWebService::ActionWebServiceError # :nodoc:
class ProtocolError < ActionWebService::ActionWebServiceError
end
class AbstractProtocol # :nodoc:
attr :container_class
def initialize(container_class)
@container_class = container_class
end
def unmarshal_request(protocol_request)
raise NotImplementedError
end
def marshal_response(protocol_request, return_value)
raise NotImplementedError
end
def marshal_exception(exception)
raise NotImplementedError
end
def self.create_protocol_request(container_class, action_pack_request)
nil
end
def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
nil
end
end
class AbstractProtocolMessage # :nodoc:
attr_accessor :signature
attr_accessor :return_signature
attr_accessor :type
attr :options
def initialize(options={})
@signature = @return_signature = nil
@options = options
@type = @options[:type] || CheckedMessage
end
def signature=(value)
return if value.nil?
@signature = []
value.each do |klass|
if klass.is_a?(Hash)
@signature << klass.values.shift
else
@signature << klass
end
end
@signature
end
def checked?
@type == CheckedMessage
end
def check_parameter_types(values, signature)
return unless checked? && signature
unless signature.length == values.length
raise(ProtocolError, "Signature and parameter lengths mismatch")
end
(1..signature.length).each do |i|
check_compatibility(signature[i-1], values[i-1].class)
end
end
def check_compatibility(expected_class, received_class)
return if \
(expected_class == TrueClass or expected_class == FalseClass) and \
(received_class == TrueClass or received_class == FalseClass)
unless received_class.ancestors.include?(expected_class) or \
expected_class.ancestors.include?(received_class)
raise(ProtocolError, "value of type #{received_class.name} is not " +
"compatible with expected type #{expected_class.name}")
end
end
end
class ProtocolRequest < AbstractProtocolMessage # :nodoc:
class Request
attr :protocol
attr :raw_body
attr :method_name
attr :method_params
attr :service_name
attr_accessor :web_service_name
attr_accessor :public_method_name
attr_accessor :content_type
def initialize(protocol, raw_body, web_service_name, public_method_name, content_type, options={})
super(options)
def initialize(protocol, method_name, method_params, service_name)
@protocol = protocol
@raw_body = raw_body
@web_service_name = web_service_name
@public_method_name = public_method_name
@content_type = content_type
end
def unmarshal
@protocol.unmarshal_request(self)
end
def marshal(return_value)
@protocol.marshal_response(self, return_value)
@method_name = method_name
@method_params = method_params
@service_name = service_name
end
end
class ProtocolResponse < AbstractProtocolMessage # :nodoc:
attr :protocol
attr :raw_body
class Response
attr :body
attr :content_type
attr_accessor :content_type
def initialize(protocol, raw_body, content_type, options={})
super(options)
@protocol = protocol
@raw_body = raw_body
def initialize(body, content_type)
@body = body
@content_type = content_type
end
end

View file

@ -0,0 +1,37 @@
module ActionWebService
module Protocol
module Discovery
def self.included(base)
base.extend(ClassMethods)
base.send(:include, ActionWebService::Protocol::Discovery::InstanceMethods)
end
module ClassMethods
def register_protocol(klass)
write_inheritable_array("web_service_protocols", [klass])
end
end
module InstanceMethods
private
def discover_web_service_request(ap_request)
(self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
protocol = protocol.new
request = protocol.unmarshal_request(ap_request)
return request unless request.nil?
end
nil
end
def create_web_service_client(api, protocol_name, endpoint_uri, options)
(self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
protocol = protocol.new
client = protocol.protocol_client(api, protocol_name, endpoint_uri, options)
return client unless client.nil?
end
nil
end
end
end
end
end

View file

@ -1,55 +0,0 @@
module ActionWebService # :nodoc:
module Protocol # :nodoc:
HeaderAndBody = :header_and_body
BodyOnly = :body_only
module Registry # :nodoc:
def self.append_features(base) # :nodoc:
super
base.extend(ClassMethods)
base.send(:include, ActionWebService::Protocol::Registry::InstanceMethods)
end
module ClassMethods # :nodoc:
def register_protocol(type, klass) # :nodoc:
case type
when HeaderAndBody
write_inheritable_array("header_and_body_protocols", [klass])
when BodyOnly
write_inheritable_array("body_only_protocols", [klass])
else
raise(ProtocolError, "unknown protocol type #{type}")
end
end
end
module InstanceMethods # :nodoc:
private
def probe_request_protocol(action_pack_request)
(header_and_body_protocols + body_only_protocols).each do |protocol|
protocol_request = protocol.create_protocol_request(self.class, action_pack_request)
return protocol_request if protocol_request
end
raise(ProtocolError, "unsupported request message format")
end
def probe_protocol_client(api, protocol_name, endpoint_uri, options)
(header_and_body_protocols + body_only_protocols).each do |protocol|
protocol_client = protocol.create_protocol_client(api, protocol_name, endpoint_uri, options)
return protocol_client if protocol_client
end
raise(ProtocolError, "unsupported client protocol :#{protocol_name}")
end
def header_and_body_protocols
self.class.read_inheritable_attribute("header_and_body_protocols") || []
end
def body_only_protocols
self.class.read_inheritable_attribute("body_only_protocols") || []
end
end
end
end
end

View file

@ -1,127 +1,49 @@
require 'soap/processor'
require 'soap/mapping'
require 'soap/rpc/element'
require 'xsd/datatypes'
require 'xsd/ns'
require 'singleton'
module ActionWebService # :nodoc:
module Protocol # :nodoc:
module Soap # :nodoc:
class ProtocolError < ActionWebService::ActionWebServiceError # :nodoc:
module ActionWebService
module Protocol
module Soap
def self.included(base)
base.register_protocol(SoapProtocol)
base.class_inheritable_option(:wsdl_service_name)
end
def self.append_features(base) # :nodoc:
super
base.register_protocol(HeaderAndBody, SoapProtocol)
base.extend(ClassMethods)
base.wsdl_service_name('ActionWebService')
end
module ClassMethods
# Specifies the WSDL service name to use when generating WSDL. Highly
# recommended that you set this value, or code generators may generate
# classes with very generic names.
#
# === Example
# class MyController < ActionController::Base
# wsdl_service_name 'MyService'
# end
def wsdl_service_name(name)
write_inheritable_attribute("soap_mapper", SoapMapper.new("urn:#{name}"))
class SoapProtocol
def initialize
@encoder = WS::Encoding::SoapRpcEncoding.new
@marshaler = WS::Marshaling::SoapMarshaler.new
end
def soap_mapper # :nodoc:
read_inheritable_attribute("soap_mapper")
end
end
class SoapProtocol < AbstractProtocol # :nodoc:
attr :mapper
def initialize(mapper)
@mapper = mapper
def unmarshal_request(ap_request)
return nil unless has_valid_soap_action?(ap_request)
method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
params = params.map{|x| @marshaler.unmarshal(x)}
service_name = ap_request.parameters['action']
Request.new(self, method_name, params, service_name)
end
def self.create_protocol_request(container_class, action_pack_request)
soap_action = extract_soap_action(action_pack_request)
return nil unless soap_action
service_name = action_pack_request.parameters['action']
public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1]
content_type = action_pack_request.env['HTTP_CONTENT_TYPE']
content_type ||= 'text/xml'
protocol = SoapProtocol.new(container_class.soap_mapper)
ProtocolRequest.new(protocol,
action_pack_request.raw_post,
service_name.to_sym,
public_method_name,
content_type)
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)
return_value = @marshaler.marshal(WS::Param.new(return_value, info))
else
return_value = nil
end
body = @encoder.encode_rpc_response(method_name, return_value)
Response.new(body, 'text/xml')
end
def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
return nil unless protocol_name.to_s.downcase.to_sym == :soap
def register_signature_type(spec)
@marshaler.register_type(spec)
end
def protocol_client(api, protocol_name, endpoint_uri, options)
return nil unless protocol_name == :soap
ActionWebService::Client::Soap.new(api, endpoint_uri, options)
end
def unmarshal_request(protocol_request)
unmarshal = lambda do
envelope = SOAP::Processor.unmarshal(protocol_request.raw_body)
request = envelope.body.request
values = request.collect{|k, v| request[k]}
soap_to_ruby_array(values)
end
signature = protocol_request.signature
if signature
map_signature_types(signature)
values = unmarshal.call
signature = signature.map{|x|mapper.lookup(x).ruby_klass}
protocol_request.check_parameter_types(values, signature)
values
else
if protocol_request.checked?
[]
else
unmarshal.call
end
end
end
def marshal_response(protocol_request, return_value)
marshal = lambda do |signature|
mapping = mapper.lookup(signature[0])
return_value = fixup_array_types(mapping, return_value)
signature = signature.map{|x|mapper.lookup(x).ruby_klass}
protocol_request.check_parameter_types([return_value], signature)
param_def = [['retval', 'return', mapping.registry_mapping]]
[param_def, ruby_to_soap(return_value)]
end
signature = protocol_request.return_signature
param_def = nil
if signature
param_def, return_value = marshal.call(signature)
else
if protocol_request.checked?
param_def, return_value = nil, nil
else
param_def, return_value = marshal.call([return_value.class])
end
end
qname = XSD::QName.new(mapper.custom_namespace,
protocol_request.public_method_name)
response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
response.retval = return_value unless return_value.nil?
ProtocolResponse.new(self, create_response(response), 'text/xml')
end
def marshal_exception(exc)
ProtocolResponse.new(self, create_exception_response(exc), 'text/xml')
end
private
def self.extract_soap_action(request)
def has_valid_soap_action?(request)
return nil unless request.method == :post
content_type = request.env['HTTP_CONTENT_TYPE'] || 'text/xml'
return nil unless content_type
soap_action = request.env['HTTP_SOAPACTION']
return nil unless soap_action
soap_action.gsub!(/^"/, '')
@ -130,355 +52,7 @@ module ActionWebService # :nodoc:
return nil if soap_action.empty?
soap_action
end
def fixup_array_types(mapping, obj)
mapping.each_attribute do |name, type, attr_mapping|
if attr_mapping.custom_type?
attr_obj = obj.send(name)
new_obj = fixup_array_types(attr_mapping, attr_obj)
obj.send("#{name}=", new_obj) unless new_obj.equal?(attr_obj)
end
end
if mapping.is_a?(SoapArrayMapping)
obj = mapping.ruby_klass.new(obj)
# man, this is going to be slow for big arrays :(
(1..obj.size).each do |i|
i -= 1
obj[i] = fixup_array_types(mapping.element_mapping, obj[i])
end
else
if !mapping.generated_klass.nil? && mapping.generated_klass.respond_to?(:members)
# have to map the publically visible structure of the class
new_obj = mapping.generated_klass.new
mapping.generated_klass.members.each do |name, klass|
new_obj.send("#{name}=", obj.send(name))
end
obj = new_obj
end
end
obj
end
def map_signature_types(types)
types.collect{|type| mapper.map(type)}
end
def create_response(body)
header = SOAP::SOAPHeader.new
body = SOAP::SOAPBody.new(body)
envelope = SOAP::SOAPEnvelope.new(header, body)
SOAP::Processor.marshal(envelope)
end
def create_exception_response(exc)
detail = SOAP::Mapping::SOAPException.new(exc)
body = SOAP::SOAPFault.new(
SOAP::SOAPString.new('Server'),
SOAP::SOAPString.new(exc.to_s),
SOAP::SOAPString.new(self.class.name),
SOAP::Mapping.obj2soap(detail))
create_response(body)
end
def ruby_to_soap(obj)
SOAP::Mapping.obj2soap(obj, mapper.registry)
end
def soap_to_ruby(obj)
SOAP::Mapping.soap2obj(obj, mapper.registry)
end
def soap_to_ruby_array(array)
array.map{|x| soap_to_ruby(x)}
end
end
class SoapMapper # :nodoc:
attr :registry
attr :custom_namespace
attr :custom_types
def initialize(custom_namespace)
@custom_namespace = custom_namespace
@registry = SOAP::Mapping::Registry.new
@klass2map = {}
@custom_types = {}
@ar2klass = {}
end
def lookup(klass)
lookup_klass = klass.is_a?(Array) ? klass[0] : klass
generated_klass = nil
unless lookup_klass.respond_to?(:ancestors)
raise(ProtocolError, "expected parameter type definition to be a Class")
end
if lookup_klass.ancestors.include?(ActiveRecord::Base)
generated_klass = @ar2klass.has_key?(klass) ? @ar2klass[klass] : nil
klass = generated_klass if generated_klass
end
return @klass2map[klass] if @klass2map.has_key?(klass)
custom_type = false
ruby_klass = select_class(lookup_klass)
generated_klass = @ar2klass[lookup_klass] if @ar2klass.has_key?(lookup_klass)
type_name = ruby_klass.name
# Array signatures generate a double-mapping and require generation
# of an Array subclass to represent the mapping in the SOAP
# registry
array_klass = nil
if klass.is_a?(Array)
array_klass = Class.new(Array) do
module_eval <<-END
def self.name
"#{type_name}Array"
end
END
end
end
mapping = @registry.find_mapped_soap_class(ruby_klass) rescue nil
unless mapping
# Custom structured type, generate a mapping
info = { :type => XSD::QName.new(@custom_namespace, type_name) }
@registry.add(ruby_klass,
SOAP::SOAPStruct,
SOAP::Mapping::Registry::TypedStructFactory,
info)
mapping = ensure_mapped(ruby_klass)
custom_type = true
end
array_mapping = nil
if array_klass
# Typed array always requires a custom type. The info of the array
# is the info of its element type (in mapping[2]), falling back
# to SOAP base types.
info = mapping[2]
info ||= {}
info[:type] ||= soap_base_type_qname(mapping[0])
@registry.add(array_klass,
SOAP::SOAPArray,
SOAP::Mapping::Registry::TypedArrayFactory,
info)
array_mapping = ensure_mapped(array_klass)
end
if array_mapping
@klass2map[ruby_klass] = SoapMapping.new(self,
type_name,
ruby_klass,
generated_klass,
mapping[0],
mapping,
custom_type)
@klass2map[klass] = SoapArrayMapping.new(self,
type_name,
array_klass,
array_mapping[0],
array_mapping,
@klass2map[ruby_klass])
@custom_types[klass] = @klass2map[klass]
@custom_types[ruby_klass] = @klass2map[ruby_klass] if custom_type
else
@klass2map[klass] = SoapMapping.new(self,
type_name,
ruby_klass,
generated_klass,
mapping[0],
mapping,
custom_type)
@custom_types[klass] = @klass2map[klass] if custom_type
end
@klass2map[klass]
end
alias :map :lookup
def map_container_services(container, &block)
dispatching_mode = container.web_service_dispatching_mode
web_services = nil
case dispatching_mode
when :direct
api = container.class.web_service_api
if container.respond_to?(:controller_class_name)
web_service_name = container.controller_class_name.sub(/Controller$/, '').underscore
else
web_service_name = container.class.name.demodulize.underscore
end
web_services = { web_service_name => api }
when :delegated
web_services = {}
container.class.web_services.each do |web_service_name, web_service_info|
begin
object = container.web_service_object(web_service_name)
rescue Exception => e
raise(ProtocolError, "failed to retrieve web service object for web service '#{web_service_name}': #{e.message}")
end
web_services[web_service_name] = object.class.web_service_api
end
end
web_services.each do |web_service_name, api|
if api.nil?
raise(ProtocolError, "no web service API set while in :#{dispatching_mode} mode")
end
map_api(api) do |api_methods|
yield web_service_name, api, api_methods if block_given?
end
end
end
def map_api(api, &block)
lookup_proc = lambda do |klass|
mapping = lookup(klass)
custom_mapping = nil
if mapping.respond_to?(:element_mapping)
custom_mapping = mapping.element_mapping
else
custom_mapping = mapping
end
if custom_mapping && custom_mapping.custom_type?
# What gives? This is required so that structure types
# referenced only by structures (and not signatures) still
# have a custom type mapping in the registry (needed for WSDL
# generation).
custom_mapping.each_attribute{}
end
mapping
end
api_methods = block.nil?? nil : {}
api.api_methods.each do |method_name, method_info|
expects = method_info[:expects]
expects_signature = nil
if expects
expects_signature = block ? [] : nil
expects.each do |klass|
lookup_klass = nil
if klass.is_a?(Hash)
lookup_klass = lookup_proc.call(klass.values[0])
expects_signature << {klass.keys[0]=>lookup_klass} if block
else
lookup_klass = lookup_proc.call(klass)
expects_signature << lookup_klass if block
end
end
end
returns = method_info[:returns]
returns_signature = returns ? returns.map{|klass| lookup_proc.call(klass)} : nil
if block
api_methods[method_name] = {
:expects => expects_signature,
:returns => returns_signature
}
end
end
yield api_methods if block
end
private
def select_class(klass)
return Integer if klass == Fixnum
if klass.ancestors.include?(ActiveRecord::Base)
new_klass = Class.new(ActionWebService::Struct)
new_klass.class_eval <<-EOS
def self.name
"#{klass.name}"
end
EOS
klass.columns.each do |column|
next if column.klass.nil?
new_klass.send(:member, column.name.to_sym, column.klass)
end
@ar2klass[klass] = new_klass
return new_klass
end
klass
end
def ensure_mapped(klass)
mapping = @registry.find_mapped_soap_class(klass) rescue nil
raise(ProtocolError, "failed to register #{klass.name}") unless mapping
mapping
end
def soap_base_type_qname(base_type)
xsd_type = base_type.ancestors.find{|c| c.const_defined? 'Type'}
xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
end
end
class SoapMapping # :nodoc:
attr :ruby_klass
attr :generated_klass
attr :soap_klass
attr :registry_mapping
def initialize(mapper, type_name, ruby_klass, generated_klass, soap_klass, registry_mapping,
custom_type=false)
@mapper = mapper
@type_name = type_name
@ruby_klass = ruby_klass
@generated_klass = generated_klass
@soap_klass = soap_klass
@registry_mapping = registry_mapping
@custom_type = custom_type
end
def type_name
@type_name
end
def custom_type?
@custom_type
end
def qualified_type_name
name = type_name
if custom_type?
"typens:#{name}"
else
xsd_type_for(@soap_klass)
end
end
def each_attribute(&block)
if @ruby_klass.respond_to?(:members)
@ruby_klass.members.each do |name, klass|
name = name.to_s
mapping = @mapper.lookup(klass)
yield name, mapping.qualified_type_name, mapping
end
end
end
def is_xsd_type?(klass)
klass.ancestors.include?(XSD::NSDBase)
end
def xsd_type_for(klass)
ns = XSD::NS.new
ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
xsd_klass = klass.ancestors.find{|c| c.const_defined?('Type')}
return ns.name(XSD::AnyTypeName) unless xsd_klass
ns.name(xsd_klass.const_get('Type'))
end
end
class SoapArrayMapping < SoapMapping # :nodoc:
attr :element_mapping
def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping, element_mapping)
super(mapper, type_name, ruby_klass, nil, soap_klass, registry_mapping, true)
@element_mapping = element_mapping
end
def type_name
super + "Array"
end
def each_attribute(&block); end
end
end
end
end

View file

@ -1,167 +1,47 @@
require 'xmlrpc/parser'
require 'xmlrpc/create'
require 'xmlrpc/config'
require 'xmlrpc/utils'
require 'singleton'
module XMLRPC # :nodoc:
class XmlRpcHelper # :nodoc:
include Singleton
include ParserWriterChooseMixin
def parse_method_call(message)
parser().parseMethodCall(message)
end
def create_method_response(successful, return_value)
create().methodResponse(successful, return_value)
end
end
end
module ActionWebService # :nodoc:
module Protocol # :nodoc:
module XmlRpc # :nodoc:
def self.append_features(base) # :nodoc:
super
base.register_protocol(BodyOnly, XmlRpcProtocol)
module ActionWebService
module Protocol
module XmlRpc
def self.included(base)
base.register_protocol(XmlRpcProtocol)
end
class XmlRpcProtocol
attr :marshaler
class XmlRpcProtocol < AbstractProtocol # :nodoc:
def self.create_protocol_request(container_class, action_pack_request)
helper = XMLRPC::XmlRpcHelper.instance
service_name = action_pack_request.parameters['action']
methodname, params = helper.parse_method_call(action_pack_request.raw_post)
methodname.gsub!(/^[^\.]+\./, '') unless methodname =~ /^system\./ # XXX
protocol = XmlRpcProtocol.new(container_class)
content_type = action_pack_request.env['HTTP_CONTENT_TYPE']
content_type ||= 'text/xml'
request = ProtocolRequest.new(protocol,
action_pack_request.raw_post,
service_name.to_sym,
methodname,
content_type,
:xmlrpc_values => params)
request
def initialize
@encoder = WS::Encoding::XmlRpcEncoding.new
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
end
def unmarshal_request(ap_request)
method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
params = params.map{|x| @marshaler.unmarshal(x)}
service_name = ap_request.parameters['action']
Request.new(self, method_name, params, service_name)
rescue
nil
end
def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
return nil unless protocol_name.to_s.downcase.to_sym == :xmlrpc
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)
return_value = @marshaler.marshal(WS::Param.new(return_value, info))
else
return_value = nil
end
body = @encoder.encode_rpc_response(method_name, return_value)
Response.new(body, 'text/xml')
end
def register_signature_type(spec)
nil
end
def protocol_client(api, protocol_name, endpoint_uri, options)
return nil unless protocol_name == :xmlrpc
ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
end
def initialize(container_class)
super(container_class)
end
def unmarshal_request(protocol_request)
values = protocol_request.options[:xmlrpc_values]
signature = protocol_request.signature
if signature
values = self.class.transform_incoming_method_params(self.class.transform_array_types(signature), values)
protocol_request.check_parameter_types(values, check_array_types(signature))
values
else
protocol_request.checked? ? [] : values
end
end
def marshal_response(protocol_request, return_value)
helper = XMLRPC::XmlRpcHelper.instance
signature = protocol_request.return_signature
if signature
protocol_request.check_parameter_types([return_value], check_array_types(signature))
return_value = self.class.transform_return_value(self.class.transform_array_types(signature), return_value)
raw_response = helper.create_method_response(true, return_value)
else
# XML-RPC doesn't have the concept of a void method, nor does it
# support a nil return value, so return true if we would have returned
# nil
if protocol_request.checked?
raw_response = helper.create_method_response(true, true)
else
return_value = true if return_value.nil?
raw_response = helper.create_method_response(true, return_value)
end
end
ProtocolResponse.new(self, raw_response, 'text/xml')
end
def marshal_exception(exception)
helper = XMLRPC::XmlRpcHelper.instance
exception = XMLRPC::FaultException.new(1, exception.message)
raw_response = helper.create_method_response(false, exception)
ProtocolResponse.new(self, raw_response, 'text/xml')
end
class << self
def transform_incoming_method_params(signature, params)
(1..signature.size).each do |i|
i -= 1
params[i] = xmlrpc_to_ruby(params[i], signature[i])
end
params
end
def transform_return_value(signature, return_value)
ruby_to_xmlrpc(return_value, signature[0])
end
def ruby_to_xmlrpc(param, param_class)
if param_class.is_a?(XmlRpcArray)
param.map{|p| ruby_to_xmlrpc(p, param_class.klass)}
elsif param_class.ancestors.include?(ActiveRecord::Base)
param.instance_variable_get('@attributes')
elsif param_class.ancestors.include?(ActionWebService::Struct)
struct = {}
param_class.members.each do |name, klass|
value = param.send(name)
next if value.nil?
struct[name.to_s] = value
end
struct
else
param
end
end
def xmlrpc_to_ruby(param, param_class)
if param_class.is_a?(XmlRpcArray)
param.map{|p| xmlrpc_to_ruby(p, param_class.klass)}
elsif param_class.ancestors.include?(ActiveRecord::Base)
raise(ProtocolError, "incoming ActiveRecord::Base types are not allowed")
elsif param_class.ancestors.include?(ActionWebService::Struct)
unless param.is_a?(Hash)
raise(ProtocolError, "expected parameter to be a Hash")
end
new_param = param_class.new
param_class.members.each do |name, klass|
new_param.send('%s=' % name.to_s, param[name.to_s])
end
new_param
else
param
end
end
def transform_array_types(signature)
signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x}
end
end
private
def check_array_types(signature)
signature.map{|x| x.is_a?(Array) ? Array : x}
end
class XmlRpcArray
attr :klass
def initialize(klass)
@klass = klass
end
end
end
end
end

View file

@ -35,12 +35,10 @@ module ActionWebService
end
class << self
include ActionWebService::Signature
# Creates a structure member with the specified +name+ and +type+. Generates
# accessor methods for reading and writing the member value.
def member(name, type)
write_inheritable_hash("struct_members", name => signature_parameter_class(type))
write_inheritable_hash("struct_members", name => WS::BaseTypes.canonical_param_type_class(type))
class_eval <<-END
def #{name}; @#{name}; end
def #{name}=(value); @#{name} = value; end

View file

@ -1,100 +0,0 @@
module ActionWebService # :nodoc:
# Action Web Service parameter specifiers may contain symbols or strings
# instead of Class objects, for a limited set of base types.
#
# This provides an unambiguous way to specify that a given parameter
# contains an integer or boolean value, for example.
#
# The allowed set of symbol/string aliases:
#
# [<tt>:int</tt>] any integer value
# [<tt>:float</tt>] any floating point value
# [<tt>:string</tt>] any string value
# [<tt>:bool</tt>] any boolean value
# [<tt>:time</tt>] any value containing both date and time
# [<tt>:date</tt>] any value containing only a date
module Signature
class SignatureError < StandardError # :nodoc:
end
private
def canonical_signature(params)
return nil if params.nil?
params.map do |param|
klass = signature_parameter_class(param)
if param.is_a?(Hash)
param[param.keys[0]] = klass
param
else
klass
end
end
end
def signature_parameter_class(param)
param = param.is_a?(Hash) ? param.values[0] : param
is_array = param.is_a?(Array)
param = is_array ? param[0] : param
param = param.is_a?(String) ? param.to_sym : param
param = param.is_a?(Symbol) ? signature_ruby_class(param) : param
is_array ? [param] : param
end
def canonical_signature_base_type(base_type)
base_type = base_type.to_sym
case base_type
when :int, :integer, :fixnum, :bignum
:int
when :string, :base64
:string
when :bool, :boolean
:bool
when :float, :double
:float
when :time, :datetime, :timestamp
:time
when :date
:date
else
raise(SignatureError, ":#{base_type} is not an ActionWebService base type")
end
end
def signature_ruby_class(base_type)
case canonical_signature_base_type(base_type)
when :int
Integer
when :string
String
when :bool
TrueClass
when :float
Float
when :time
Time
when :date
Date
end
end
def signature_base_type(ruby_class)
case ruby_class
when Bignum, Integer, Fixnum
:int
when String
:string
when TrueClass, FalseClass
:bool
when Float, Numeric, Precision
:float
when Time, DateTime
:time
when Date
:date
else
raise(SignatureError, "#{ruby_class.name} is not an ActionWebService base type")
end
end
end
end

View file

@ -0,0 +1,4 @@
require 'ws/common'
require 'ws/types'
require 'ws/marshaling'
require 'ws/encoding'

View file

@ -0,0 +1,8 @@
module WS
class WSError < StandardError
end
def self.derived_from?(ancestor, child)
child.ancestors.include?(ancestor)
end
end

View file

@ -0,0 +1,3 @@
require 'ws/encoding/abstract'
require 'ws/encoding/soap_rpc_encoding'
require 'ws/encoding/xmlrpc_encoding'

View file

@ -0,0 +1,26 @@
module WS
module Encoding
# Encoders operate on _foreign_ objects. That is, Ruby object
# instances that are the _marshaling format specific_ representation
# of objects. In other words, objects that have not yet been marshaled, but
# are in protocol-specific form (such as an AST or DOM element), and not
# native Ruby form.
class AbstractEncoding
def encode_rpc_call(method_name, params)
raise NotImplementedError
end
def decode_rpc_call(obj)
raise NotImplementedError
end
def encode_rpc_response(method_name, return_value)
raise NotImplementedError
end
def decode_rpc_response(obj)
raise NotImplementedError
end
end
end
end

View file

@ -0,0 +1,90 @@
require 'soap/processor'
require 'soap/mapping'
require 'soap/rpc/element'
module WS
module Encoding
class SoapRpcError < WSError
end
class SoapRpcEncoding < AbstractEncoding
attr_accessor :method_namespace
def initialize(method_namespace='')
@method_namespace = method_namespace
end
def encode_rpc_call(method_name, foreign_params)
qname = create_method_qname(method_name)
param_def = []
params = foreign_params.map do |p|
param_def << ['in', p.param.info.name, p.param.info.data.mapping]
[p.param.info.name, p.soap_object]
end
request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
request.set_param(params)
envelope = create_soap_envelope(request)
SOAP::Processor.marshal(envelope)
end
def decode_rpc_call(obj)
envelope = SOAP::Processor.unmarshal(obj)
unless envelope
raise(SoapRpcError, "Malformed SOAP request")
end
request = envelope.body.request
method_name = request.elename.name
params = request.collect do |key, value|
info = ParamInfo.new(key, nil, nil)
param = Param.new(nil, info)
Marshaling::SoapForeignObject.new(param, request[key])
end
[method_name, params]
end
def encode_rpc_response(method_name, return_value)
response = nil
qname = create_method_qname(method_name)
if return_value.nil?
response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
else
param = return_value.param
soap_object = return_value.soap_object
param_def = [['retval', 'return', param.info.data.mapping]]
if soap_object.is_a?(SOAP::SOAPFault)
response = soap_object
else
response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
response.retval = soap_object
end
end
envelope = create_soap_envelope(response)
SOAP::Processor.marshal(envelope)
end
def decode_rpc_response(obj)
envelope = SOAP::Processor.unmarshal(obj)
unless envelope
raise(SoapRpcError, "Malformed SOAP response")
end
method_name = envelope.body.request.elename.name
return_value = envelope.body.response
info = ParamInfo.new('return', nil, nil)
param = Param.new(nil, info)
return_value = Marshaling::SoapForeignObject.new(param, return_value)
[method_name, return_value]
end
private
def create_soap_envelope(body)
header = SOAP::SOAPHeader.new
body = SOAP::SOAPBody.new(body)
SOAP::SOAPEnvelope.new(header, body)
end
def create_method_qname(method_name)
XSD::QName.new(@method_namespace, method_name)
end
end
end
end

View file

@ -0,0 +1,53 @@
require 'xmlrpc/marshal'
module WS
module Encoding
class XmlRpcError < WSError
end
class XmlRpcEncoding < AbstractEncoding
def encode_rpc_call(method_name, params)
XMLRPC::Marshal.dump_call(method_name, *params)
end
def decode_rpc_call(obj)
method_name, params = XMLRPC::Marshal.load_call(obj) rescue nil
unless method_name && params
raise(XmlRpcError, "Malformed XML-RPC request")
end
i = 0
params = params.map do |value|
param = XmlRpcDecodedParam.new("param#{i}", value)
i += 1
param
end
[method_name, params]
end
def encode_rpc_response(method_name, return_value)
if return_value.nil?
XMLRPC::Marshal.dump_response(true)
else
XMLRPC::Marshal.dump_response(return_value)
end
end
def decode_rpc_response(obj)
return_value = XMLRPC::Marshal.load_response(obj) rescue nil
if return_value.nil?
raise(XmlRpcError, "Malformed XML-RPC response")
end
[nil, XmlRpcDecodedParam.new('return', return_value)]
end
end
class XmlRpcDecodedParam
attr :param
def initialize(name, value)
info = ParamInfo.new(name, value.class)
@param = Param.new(value, info)
end
end
end
end

View file

@ -0,0 +1,3 @@
require 'ws/marshaling/abstract'
require 'ws/marshaling/soap_marshaling'
require 'ws/marshaling/xmlrpc_marshaling'

View file

@ -0,0 +1,17 @@
module WS
module Marshaling
class AbstractMarshaler
def marshal(param)
raise NotImplementedError
end
def unmarshal(param)
raise NotImplementedError
end
def register_type(type)
nil
end
end
end
end

View file

@ -0,0 +1,224 @@
require 'soap/mapping'
require 'xsd/ns'
module WS
module Marshaling
SoapEncodingNS = 'http://schemas.xmlsoap.org/soap/encoding/'
class SoapError < WSError
end
class SoapMarshaler < AbstractMarshaler
attr :registry
attr_accessor :type_namespace
def initialize(type_namespace='')
@type_namespace = type_namespace
@registry = SOAP::Mapping::Registry.new
@spec2binding = {}
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
if param.value.is_a?(Exception)
detail = SOAP::Mapping::SOAPException.new(param.value)
soap_obj = SOAP::SOAPFault.new(
SOAP::SOAPString.new('Server'),
SOAP::SOAPString.new(param.value.to_s),
SOAP::SOAPString.new(self.class.name),
SOAP::Mapping.obj2soap(detail))
else
soap_obj = SOAP::Mapping.obj2soap(param.value, @registry)
end
SoapForeignObject.new(param, soap_obj)
end
def unmarshal(obj)
param = obj.param
soap_object = obj.soap_object
soap_type = soap_object ? soap_object.type : nil
value = soap_object ? SOAP::Mapping.soap2obj(soap_object, @registry) : nil
param.value = value
param.info.type = value.class
mapping = @registry.find_mapped_soap_class(param.info.type) rescue nil
if soap_type && soap_type.name == 'Array' && soap_type.namespace == SoapEncodingNS
param.info.data = SoapBinding.new(soap_object.arytype, mapping)
else
param.info.data = SoapBinding.new(soap_type, mapping)
end
param
end
def register_type(spec)
if @spec2binding.has_key?(spec)
return @spec2binding[spec]
end
klass = BaseTypes.canonical_param_type_class(spec)
if klass.is_a?(Array)
type_class = klass[0]
else
type_class = klass
end
type_binding = nil
if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil)
qname = mapping[2] ? mapping[2][:type] : nil
qname ||= soap_base_type_name(mapping[0])
type_binding = SoapBinding.new(qname, mapping)
else
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
@registry.add(type_class,
SOAP::SOAPStruct,
typed_struct_factory(type_class),
{ :type => qname })
mapping = @registry.find_mapped_soap_class(type_class)
type_binding = SoapBinding.new(qname, mapping)
end
array_binding = nil
if klass.is_a?(Array)
array_mapping = @registry.find_mapped_soap_class(Array) rescue nil
if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil?
@registry.set(Array,
SOAP::SOAPArray,
SoapTypedArrayFactory.new)
array_mapping = @registry.find_mapped_soap_class(Array)
end
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name) + 'Array')
array_binding = SoapBinding.new(qname, array_mapping, type_binding)
end
@spec2binding[spec] = array_binding ? array_binding : type_binding
end
protected
def typed_struct_factory(type_class)
if Object.const_defined?('ActiveRecord')
if WS.derived_from?(ActiveRecord::Base, type_class)
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
type_class.instance_variable_set('@qname', qname)
return SoapActiveRecordStructFactory.new
end
end
SOAP::Mapping::Registry::TypedStructFactory
end
def soap_type_name(type_name)
type_name.gsub(/::/, '..')
end
def soap_base_type_name(type)
xsd_type = type.ancestors.find{|c| c.const_defined? 'Type'}
xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
end
end
class SoapForeignObject
attr_accessor :param
attr_accessor :soap_object
def initialize(param, soap_object)
@param = param
@soap_object = soap_object
end
end
class SoapBinding
attr :qname
attr :mapping
attr :element_binding
def initialize(qname, mapping, element_binding=nil)
@qname = qname
@mapping = mapping
@element_binding = element_binding
end
def is_custom_type?
is_typed_array? || is_typed_struct?
end
def is_typed_array?
@mapping[1].is_a?(WS::Marshaling::SoapTypedArrayFactory)
end
def is_typed_struct?
@mapping[1] == SOAP::Mapping::Registry::TypedStructFactory || \
@mapping[1].is_a?(WS::Marshaling::SoapActiveRecordStructFactory)
end
def each_member(&block)
unless is_typed_struct?
raise(SoapError, "not a structured type")
end
end
def type_name
is_custom_type? ? @qname.name : nil
end
def qualified_type_name(ns=nil)
if is_custom_type?
"#{ns ? ns : @qname.namespace}:#{@qname.name}"
else
ns = XSD::NS.new
ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
return ns.name(XSD::AnyTypeName) unless xsd_klass
ns.name(xsd_klass.const_get('Type'))
end
end
end
class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
def obj2soap(soap_class, obj, info, map)
unless obj.is_a?(ActiveRecord::Base)
return nil
end
soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
obj.attributes.each do |key, value|
soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
end
soap_obj
end
def soap2obj(obj_class, node, info, map)
unless node.type == obj_class.instance_variable_get('@qname')
return false
end
obj = obj_class.new
node.each do |key, value|
obj[key] = value.data
end
obj.instance_variable_set('@new_record', false)
return true, obj
end
end
class SoapTypedArrayFactory < SOAP::Mapping::Factory
def obj2soap(soap_class, obj, info, map)
unless obj.respond_to?(:arytype)
return nil
end
soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
mark_marshalled_obj(obj, soap_obj)
obj.each do |item|
child = SOAP::Mapping._obj2soap(item, map)
soap_obj.add(child)
end
soap_obj
end
def soap2obj(obj_class, node, info, map)
return false
end
end
end
end

View file

@ -0,0 +1,116 @@
module WS
module Marshaling
class XmlRpcError < WSError
end
class XmlRpcMarshaler < AbstractMarshaler
def initialize
@caster = BaseTypeCaster.new
@spec2binding = {}
end
def marshal(param)
transform_outbound(param)
end
def unmarshal(obj)
obj.param.value = transform_inbound(obj.param)
obj.param
end
def typed_unmarshal(obj, spec)
param = obj.param
param.info.data = register_type(spec)
param.value = transform_inbound(param)
param
end
def register_type(spec)
if @spec2binding.has_key?(spec)
return @spec2binding[spec]
end
klass = BaseTypes.canonical_param_type_class(spec)
type_binding = nil
if klass.is_a?(Array)
type_binding = XmlRpcArrayBinding.new(klass[0])
else
type_binding = XmlRpcBinding.new(klass)
end
@spec2binding[spec] = type_binding
end
def transform_outbound(param)
binding = param.info.data
case binding
when XmlRpcArrayBinding
param.value.map{|x| cast_outbound(x, binding.element_klass)}
when XmlRpcBinding
cast_outbound(param.value, param.info.type)
end
end
def transform_inbound(param)
return param.value if param.info.data.nil?
binding = param.info.data
param.info.type = binding.klass
case binding
when XmlRpcArrayBinding
param.value.map{|x| cast_inbound(x, binding.element_klass)}
when XmlRpcBinding
cast_inbound(param.value, param.info.type)
end
end
def cast_outbound(value, klass)
if BaseTypes.base_type?(klass)
@caster.cast(value, klass)
elsif value.is_a?(Exception)
XMLRPC::FaultException.new(2, value.message)
elsif Object.const_defined?('ActiveRecord') && value.is_a?(ActiveRecord::Base)
value.attributes
else
struct = {}
value.instance_variables.each do |name|
key = name.sub(/^@/, '')
struct[key] = value.instance_variable_get(name)
end
struct
end
end
def cast_inbound(value, klass)
if BaseTypes.base_type?(klass)
value = value.to_time if value.is_a?(XMLRPC::DateTime)
@caster.cast(value, klass)
elsif value.is_a?(XMLRPC::FaultException)
value
else
obj = klass.new
value.each do |name, val|
obj.send('%s=' % name.to_s, val)
end
obj
end
end
end
class XmlRpcBinding
attr :klass
def initialize(klass)
@klass = klass
end
end
class XmlRpcArrayBinding < XmlRpcBinding
attr :element_klass
def initialize(element_klass)
super(Array)
@element_klass = element_klass
end
end
end
end

View file

@ -0,0 +1,162 @@
require 'time'
require 'date'
module WS
module BaseTypes
class << self
def type_name_to_class(name)
case canonical_type_name(name)
when :int
Integer
when :string
String
when :bool
TrueClass
when :float
Float
when :time
Time
when :date
Date
end
end
def class_to_type_name(klass)
if WS.derived_from?(Integer, klass) || WS.derived_from?(Fixnum, klass) || WS.derived_from?(Bignum, klass)
:int
elsif klass == String
:string
elsif klass == TrueClass || klass == FalseClass
:bool
elsif WS.derived_from?(Float, klass) || WS.derived_from?(Precision, klass) || WS.derived_from?(Numeric, klass)
:float
elsif klass == Time || klass == DateTime
:time
elsif klass == Date
:date
else
raise(TypeError, "#{klass} is not a valid base type")
end
end
def base_type?(klass)
!(canonical_type_class(klass) rescue nil).nil?
end
def canonical_type_class(klass)
type_name_to_class(class_to_type_name(klass))
end
def canonical_param_type_class(spec)
klass = spec.is_a?(Hash) ? spec.values[0] : spec
array_element_class = klass.is_a?(Array) ? klass[0] : nil
klass = array_element_class ? array_element_class : klass
klass = type_name_to_class(klass) if klass.is_a?(Symbol) || klass.is_a?(String)
base_class = canonical_type_class(klass) rescue nil
klass = base_class unless base_class.nil?
array_element_class ? [klass] : klass
end
def canonical_param_type_spec(spec)
klass = canonical_param_type_class(spec)
spec.is_a?(Hash) ? {spec.keys[0]=>klass} : klass
end
def canonical_type_name(name)
name = name.to_sym
case name
when :int, :integer, :fixnum, :bignum
:int
when :string, :base64
:string
when :bool, :boolean
:bool
when :float, :double
:float
when :time, :datetime, :timestamp
:time
when :date
:date
else
raise(TypeError, "#{name} is not a valid base type")
end
end
end
end
class Param
attr_accessor :value
attr_accessor :info
def initialize(value, info)
@value = value
@info = info
end
end
class ParamInfo
attr_accessor :name
attr_accessor :type
attr_accessor :data
def initialize(name, type, data=nil)
@name = name
@type = type
@data = data
end
def self.create(spec, index=nil, data=nil)
name = spec.is_a?(Hash) ? spec.keys[0].to_s : (index ? "param#{index}" : nil)
type = BaseTypes.canonical_param_type_class(spec)
ParamInfo.new(name, type, data)
end
end
class BaseTypeCaster
def initialize
@handlers = {}
install_handlers
end
def cast(value, klass)
type_class = BaseTypes.canonical_type_class(klass)
return value unless type_class
@handlers[type_class].call(value, type_class)
end
protected
def install_handlers
handler = method(:cast_base_type)
[:int, :string, :bool, :float, :time, :date].each do |name|
type = BaseTypes.type_name_to_class(name)
@handlers[type] = handler
end
@handlers[Fixnum] = handler
end
def cast_base_type(value, type_class)
desired_class = BaseTypes.canonical_type_class(type_class)
value_class = BaseTypes.canonical_type_class(value.class)
return value if desired_class == value_class
desired_name = BaseTypes.class_to_type_name(desired_class)
case desired_name
when :int
Integer(value)
when :string
value.to_s
when :bool
return false if value.nil?
value = value.to_s
return true if value == 'true'
return false if value == 'false'
raise(TypeError, "can't convert #{value} to boolean")
when :float
Float(value)
when :time
Time.parse(value.to_s)
when :date
Date.parse(value.to_s)
end
end
end
end

View file

@ -20,6 +20,7 @@ module ClientTest
api_method :struct_pass, :expects => [[Person]], :returns => [:bool]
api_method :client_container, :returns => [:int]
api_method :named_parameters, :expects => [{:key=>:string}, {:id=>:int}]
api_method :thrower
end
class NullLogOut
@ -29,11 +30,11 @@ module ClientTest
class Container < ActionController::Base
web_service_api API
attr :value_void
attr :value_normal
attr :value_array_return
attr :value_struct_pass
attr :value_named_parameters
attr_accessor :value_void
attr_accessor :value_normal
attr_accessor :value_array_return
attr_accessor :value_struct_pass
attr_accessor :value_named_parameters
def initialize
@session = @assigns = {}
@ -73,12 +74,8 @@ module ClientTest
@value_named_parameters = @method_params
end
def protocol_request(request)
probe_request_protocol(request)
end
def dispatch_request(protocol_request)
dispatch_protocol_request(protocol_request)
def thrower
raise "Hi"
end
end

View file

@ -0,0 +1,294 @@
require File.dirname(__FILE__) + '/abstract_unit'
module DispatcherTest
class Node < ActiveRecord::Base
def initialize(*args)
super(*args)
@new_record = false
end
class << self
def name
"Node"
end
def columns(*args)
[
ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'),
ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'),
ActiveRecord::ConnectionAdapters::Column.new('description', nil, 'string'),
]
end
def connection
self
end
end
end
class API < ActionWebService::API::Base
api_method :add, :expects => [:int, :int], :returns => [:int]
api_method :interceptee
api_method :struct_return, :returns => [[Node]]
api_method :void
end
class DirectAPI < ActionWebService::API::Base
api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
api_method :before_filtered
api_method :after_filtered, :returns => [[:int]]
api_method :struct_return, :returns => [[Node]]
api_method :thrower
api_method :void
end
class Service < ActionWebService::Base
web_service_api API
before_invocation :do_intercept, :only => [:interceptee]
attr :added
attr :intercepted
attr :void_called
def initialize
@void_called = false
end
def add(a, b)
@added = a + b
end
def interceptee
@intercepted = false
end
def struct_return
n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
[n1, n2]
end
def void(*args)
@void_called = args
end
def do_intercept(name, args)
[false, "permission denied"]
end
end
class AbstractController < ActionController::Base
def generate_wsdl
to_wsdl
end
end
class DelegatedController < AbstractController
web_service_dispatching_mode :delegated
web_service(:test_service) { @service ||= Service.new; @service }
end
class DirectController < AbstractController
web_service_api DirectAPI
web_service_dispatching_mode :direct
before_filter :alwaysfail, :only => [:before_filtered]
after_filter :alwaysok, :only => [:after_filtered]
attr :added
attr :before_filter_called
attr :before_filter_target_called
attr :after_filter_called
attr :after_filter_target_called
attr :void_called
def initialize
@before_filter_called = false
@before_filter_target_called = false
@after_filter_called = false
@after_filter_target_called = false
@void_called = false
end
def add
@added = @params['a'] + @params['b']
end
def before_filtered
@before_filter_target_called = true
end
def after_filtered
@after_filter_target_called = true
[5, 6, 7]
end
def thrower
raise "Hi, I'm an exception"
end
def struct_return
n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
[n1, n2]
end
def void
@void_called = @method_params
end
protected
def alwaysfail
@before_filter_called = true
false
end
def alwaysok
@after_filter_called = true
end
end
end
module DispatcherCommonTests
def test_direct_dispatching
assert_equal(70, do_method_call(@direct_controller, 'Add', 20, 50))
assert_equal(70, @direct_controller.added)
assert(@direct_controller.void_called == false)
case @encoder
when WS::Encoding::SoapRpcEncoding
assert(do_method_call(@direct_controller, 'Void', 3, 4, 5).nil?)
when WS::Encoding::XmlRpcEncoding
assert(do_method_call(@direct_controller, 'Void', 3, 4, 5) == true)
end
assert(@direct_controller.void_called == [])
end
def test_direct_entrypoint
assert(@direct_controller.respond_to?(:api))
end
def test_direct_filtering
assert_equal(false, @direct_controller.before_filter_called)
assert_equal(false, @direct_controller.before_filter_target_called)
do_method_call(@direct_controller, 'BeforeFiltered')
assert_equal(true, @direct_controller.before_filter_called)
assert_equal(false, @direct_controller.before_filter_target_called)
assert_equal(false, @direct_controller.after_filter_called)
assert_equal(false, @direct_controller.after_filter_target_called)
assert_equal([5, 6, 7], do_method_call(@direct_controller, 'AfterFiltered'))
assert_equal(true, @direct_controller.after_filter_called)
assert_equal(true, @direct_controller.after_filter_target_called)
end
def test_delegated_dispatching
assert_equal(130, do_method_call(@delegated_controller, 'Add', 50, 80))
service = @delegated_controller.web_service_object(:test_service)
assert_equal(130, service.added)
@delegated_controller.web_service_exception_reporting = true
assert(service.intercepted.nil?)
result = do_method_call(@delegated_controller, 'Interceptee')
assert(service.intercepted.nil?)
assert(is_exception?(result))
assert_match(/permission denied/, exception_message(result))
result = do_method_call(@delegated_controller, 'NonExistentMethod')
assert(is_exception?(result))
assert_match(/NonExistentMethod/, exception_message(result))
assert(service.void_called == false)
case @encoder
when WS::Encoding::SoapRpcEncoding
assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5).nil?)
when WS::Encoding::XmlRpcEncoding
assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5) == true)
end
assert(service.void_called == [])
end
def test_garbage_request
[@direct_controller, @delegated_controller].each do |controller|
controller.class.web_service_exception_reporting = true
send_garbage_request = lambda do
request = create_ap_request(controller, 'invalid request body', 'xxx')
response = ActionController::TestResponse.new
controller.process(request, response)
# puts response.body
assert(response.headers['Status'] =~ /^500/)
end
send_garbage_request.call
controller.class.web_service_exception_reporting = false
send_garbage_request.call
end
end
def test_exception_marshaling
@direct_controller.web_service_exception_reporting = true
result = do_method_call(@direct_controller, 'Thrower')
assert(is_exception?(result))
assert_equal("Hi, I'm an exception", exception_message(result))
@direct_controller.web_service_exception_reporting = false
result = do_method_call(@direct_controller, 'Thrower')
assert(exception_message(result) != "Hi, I'm an exception")
end
def test_ar_struct_return
[@direct_controller, @delegated_controller].each do |controller|
result = do_method_call(controller, 'StructReturn')
case @encoder
when WS::Encoding::SoapRpcEncoding
assert(result[0].is_a?(DispatcherTest::Node))
assert(result[1].is_a?(DispatcherTest::Node))
assert_equal('node1', result[0].name)
assert_equal('node2', result[1].name)
when WS::Encoding::XmlRpcEncoding
assert(result[0].is_a?(Hash))
assert(result[1].is_a?(Hash))
assert_equal('node1', result[0]['name'])
assert_equal('node2', result[1]['name'])
end
end
end
protected
def service_name(container)
raise NotImplementedError
end
def exception_message(obj)
raise NotImplementedError
end
def is_exception?(obj)
raise NotImplementedError
end
def do_method_call(container, public_method_name, *params)
mode = container.web_service_dispatching_mode
case mode
when :direct
api = container.class.web_service_api
when :delegated
api = container.web_service_object(service_name(container)).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|
@marshaler.register_type(spec)
end
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)
params[i] = @marshaler.marshal(WS::Param.new(params[i], info))
end
body = @encoder.encode_rpc_call(public_method_name, params)
# puts body
ap_request = create_ap_request(container, body, public_method_name, *params)
ap_response = ActionController::TestResponse.new
container.process(ap_request, ap_response)
# puts ap_response.body
public_method_name, return_value = @encoder.decode_rpc_response(ap_response.body)
@marshaler.unmarshal(return_value).value
end
end

View file

@ -1,58 +0,0 @@
require File.dirname(__FILE__) + '/abstract_unit'
require 'soap/rpc/element'
class SoapTestError < StandardError
end
class AbstractSoapTest < Test::Unit::TestCase
def default_test
end
protected
def service_name
raise NotImplementedError
end
def do_soap_call(public_method_name, *args)
mapper = @container.class.soap_mapper
param_def = []
i = 1
args.each do |arg|
mapping = mapper.lookup(arg.class)
param_def << ["in", "param#{i}", mapping.registry_mapping]
i += 1
end
qname = XSD::QName.new('urn:ActionWebService', public_method_name)
request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
soap_args = []
i = 1
args.each do |arg|
soap_args << ["param#{i}", SOAP::Mapping.obj2soap(arg)]
i += 1
end
request.set_param(soap_args)
header = SOAP::SOAPHeader.new
body = SOAP::SOAPBody.new(request)
envelope = SOAP::SOAPEnvelope.new(header, body)
raw_request = SOAP::Processor.marshal(envelope)
test_request = ActionController::TestRequest.new
test_request.request_parameters['action'] = service_name
test_request.env['REQUEST_METHOD'] = "POST"
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
test_request.env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{public_method_name}"
test_request.env['RAW_POST_DATA'] = raw_request
test_response = ActionController::TestResponse.new
response = yield test_request, test_response
raw_body = response.respond_to?(:body) ? response.body : response.raw_body
envelope = SOAP::Processor.unmarshal(raw_body)
if envelope
if envelope.body.response
SOAP::Mapping.soap2obj(envelope.body.response)
else
nil
end
else
raise(SoapTestError, "empty/invalid body from server")
end
end
end

View file

@ -1,4 +1,5 @@
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/../lib/action_web_service/vendor')
require 'test/unit'
require 'action_web_service'

View file

@ -41,7 +41,7 @@ class TC_API < Test::Unit::TestCase
assert_equal({:expects=>nil, :returns=>[Integer, [String]]}, API.api_methods[:returns])
assert_equal({:expects=>[{:appkey=>Integer}, {:publish=>TrueClass}], :returns=>nil}, API.api_methods[:named_signature])
assert_equal({:expects=>[Integer, String, TrueClass], :returns=>nil}, API.api_methods[:string_types])
assert_equal({:expects=>[TrueClass, Bignum, String], :returns=>nil}, API.api_methods[:class_types])
assert_equal({:expects=>[TrueClass, Integer, String], :returns=>nil}, API.api_methods[:class_types])
end
def test_not_instantiable
@ -49,4 +49,17 @@ class TC_API < Test::Unit::TestCase
API.new
end
end
def test_api_errors
assert_raises(ActionWebService::ActionWebServiceError) do
klass = Class.new(ActionWebService::API::Base) do
api_method :test, :expects => [ActiveRecord::Base]
end
end
assert_raises(ActionWebService::ActionWebServiceError) do
klass = Class.new(ActionWebService::API::Base) do
api_method :test, :invalid => [:int]
end
end
end
end

View file

@ -0,0 +1,3 @@
class AutoLoadAPI < ActionWebService::API::Base
api_method :void
end

View file

@ -0,0 +1,2 @@

View file

@ -12,10 +12,10 @@ module ClientSoapTest
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
test_request.env['HTTP_SOAPACTION'] = req.header['soapaction'][0]
test_request.env['RAW_POST_DATA'] = req.body
protocol_request = @controller.protocol_request(test_request)
response = @controller.dispatch_request(protocol_request)
response = ActionController::TestResponse.new
@controller.process(test_request, response)
res.header['content-type'] = 'text/xml'
res.body = response.raw_body
res.body = response.body
rescue Exception => e
$stderr.puts e.message
$stderr.puts e.backtrace.join("\n")
@ -24,10 +24,15 @@ module ClientSoapTest
class ClientContainer < ActionController::Base
web_client_api :client, :soap, "http://localhost:#{PORT}/client/api", :api => ClientTest::API
web_client_api :invalid, :null, "", :api => true
def get_client
client
end
def get_invalid
invalid
end
end
class SoapServer < ClientTest::AbstractServer
@ -83,6 +88,7 @@ class TC_ClientSoap < Test::Unit::TestCase
def test_client_container
assert_equal(50, ClientContainer.new.get_client.client_container)
assert(ClientContainer.new.get_invalid.nil?)
end
def test_named_parameters
@ -90,4 +96,11 @@ class TC_ClientSoap < Test::Unit::TestCase
assert(@client.named_parameters("key", 5).nil?)
assert_equal(["key", 5], @container.value_named_parameters)
end
def test_capitalized_method_name
@container.value_normal = nil
assert_equal(5, @client.Normal(5, 6))
assert_equal([5, 6], @container.value_normal)
@container.value_normal = nil
end
end

View file

@ -9,12 +9,12 @@ module ClientXmlRpcTest
test_request = ActionController::TestRequest.new
test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1]
test_request.env['REQUEST_METHOD'] = "POST"
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
test_request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
test_request.env['RAW_POST_DATA'] = req.body
protocol_request = @controller.protocol_request(test_request)
response = @controller.dispatch_request(protocol_request)
response = ActionController::TestResponse.new
@controller.process(test_request, response)
res.header['content-type'] = 'text/xml'
res.body = response.raw_body
res.body = response.body
rescue Exception => e
$stderr.puts e.message
$stderr.puts e.backtrace.join("\n")
@ -89,4 +89,16 @@ class TC_ClientXmlRpc < Test::Unit::TestCase
assert_equal(true, @client.named_parameters("xxx", 7))
assert_equal(["xxx", 7], @container.value_named_parameters)
end
def test_exception
assert_raises(ActionWebService::Client::ClientError) do
assert(@client.thrower)
end
end
def test_invalid_signature
assert_raises(ActionWebService::Client::ClientError) do
@client.normal
end
end
end

View file

@ -1,7 +1,6 @@
require File.dirname(__FILE__) + '/abstract_unit'
module ContainerTest
$immediate_service = Object.new
$deferred_service = Object.new
@ -22,22 +21,34 @@ module ContainerTest
class DirectContainer < ActionController::Base
web_service_dispatching_mode :direct
end
end
class InvalidContainer
include ActionWebService::Container::Direct
end
end
class TC_Container < Test::Unit::TestCase
include ContainerTest
def setup
@delegate_container = ContainerTest::DelegateContainer.new
@direct_container = ContainerTest::DirectContainer.new
@delegate_container = DelegateContainer.new
@direct_container = DirectContainer.new
end
def test_registration
assert(ContainerTest::DelegateContainer.has_web_service?(:immediate_service))
assert(ContainerTest::DelegateContainer.has_web_service?(:deferred_service))
assert(!ContainerTest::DelegateContainer.has_web_service?(:fake_service))
assert(DelegateContainer.has_web_service?(:immediate_service))
assert(DelegateContainer.has_web_service?(:deferred_service))
assert(!DelegateContainer.has_web_service?(:fake_service))
assert_raises(ActionWebService::Container::Delegated::ContainerError) do
DelegateContainer.web_service('invalid')
end
end
def test_service_object
assert_raises(ActionWebService::Container::Delegated::ContainerError) do
@delegate_container.web_service_object(:nonexistent)
end
assert(@delegate_container.flag == true)
assert(@delegate_container.web_service_object(:immediate_service) == $immediate_service)
assert(@delegate_container.previous_flag.nil?)
@ -48,6 +59,15 @@ class TC_Container < Test::Unit::TestCase
end
def test_direct_container
assert(ContainerTest::DirectContainer.web_service_dispatching_mode == :direct)
assert(DirectContainer.web_service_dispatching_mode == :direct)
end
def test_validity
assert_raises(ActionWebService::Container::Direct::ContainerError) do
InvalidContainer.web_service_api :test
end
assert_raises(ActionWebService::Container::Direct::ContainerError) do
InvalidContainer.web_service_api 50.0
end
end
end

View file

@ -0,0 +1,93 @@
$:.unshift(File.dirname(__FILE__) + '/apis')
require File.dirname(__FILE__) + '/abstract_dispatcher'
require 'wsdl/parser'
class AutoLoadController < ActionController::Base; end
class FailingAutoLoadController < ActionController::Base; end
class BrokenAutoLoadController < ActionController::Base; end
class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
include DispatcherTest
include DispatcherCommonTests
def setup
@encoder = WS::Encoding::SoapRpcEncoding.new
@marshaler = WS::Marshaling::SoapMarshaler.new
@direct_controller = DirectController.new
@delegated_controller = DelegatedController.new
end
def test_wsdl_generation
ensure_valid_wsdl_generation DelegatedController.new
ensure_valid_wsdl_generation DirectController.new
end
def test_wsdl_action
ensure_valid_wsdl_action DelegatedController.new
ensure_valid_wsdl_action DirectController.new
end
def test_autoloading
assert(!AutoLoadController.web_service_api.nil?)
assert(AutoLoadController.web_service_api.has_public_api_method?('Void'))
assert(FailingAutoLoadController.web_service_api.nil?)
assert_raises(LoadError, NameError) do
FailingAutoLoadController.require_web_service_api :blah
end
assert_raises(ArgumentError) do
FailingAutoLoadController.require_web_service_api 50.0
end
assert(BrokenAutoLoadController.web_service_api.nil?)
end
protected
def exception_message(soap_fault_exception)
soap_fault_exception.detail.cause.message
end
def is_exception?(obj)
obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \
obj.detail.cause.is_a?(Exception)
end
def create_ap_request(container, body, public_method_name, *args)
test_request = ActionController::TestRequest.new
test_request.request_parameters['action'] = service_name(container)
test_request.env['REQUEST_METHOD'] = "POST"
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
test_request.env['HTTP_SOAPACTION'] = "/soap/#{service_name(container)}/#{public_method_name}"
test_request.env['RAW_POST_DATA'] = body
test_request
end
def service_name(container)
container.is_a?(DelegatedController) ? 'test_service' : 'api'
end
def ensure_valid_wsdl_generation(controller)
wsdl = controller.generate_wsdl
ensure_valid_wsdl(wsdl)
end
def ensure_valid_wsdl(wsdl)
definitions = WSDL::Parser.new.parse(wsdl)
assert(definitions.is_a?(WSDL::Definitions))
definitions.bindings.each do |binding|
assert(binding.name.name.index(':').nil?)
end
definitions.services.each do |service|
service.ports.each do |port|
assert(port.name.name.index(':').nil?)
end
end
end
def ensure_valid_wsdl_action(controller)
test_request = ActionController::TestRequest.new({ 'action' => 'wsdl' })
test_request.env['REQUEST_METHOD'] = 'GET'
test_request.env['HTTP_HOST'] = 'localhost:3000'
test_response = ActionController::TestResponse.new
wsdl = controller.process(test_request, test_response).body
ensure_valid_wsdl(wsdl)
end
end

View file

@ -1,186 +0,0 @@
require File.dirname(__FILE__) + '/abstract_soap'
require 'wsdl/parser'
module DispatcherActionControllerTest
class API < ActionWebService::API::Base
api_method :add, :expects => [:int, :int], :returns => [:int]
end
class DirectAPI < ActionWebService::API::Base
api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
api_method :before_filtered
api_method :after_filtered, :returns => [:int]
api_method :thrower
end
class Service < ActionWebService::Base
web_service_api API
attr :added
def add(a, b)
@added = a + b
end
end
class AbstractController < ActionController::Base
def generate_wsdl(container, uri, soap_action_base)
to_wsdl(container, uri, soap_action_base)
end
end
class DelegatedController < AbstractController
web_service_dispatching_mode :delegated
web_service(:test_service) { @service ||= Service.new; @service }
end
class DirectController < AbstractController
web_service_api DirectAPI
web_service_dispatching_mode :direct
before_filter :alwaysfail, :only => [:before_filtered]
after_filter :alwaysok, :only => [:after_filtered]
attr :added
attr :before_filter_called
attr :before_filter_target_called
attr :after_filter_called
attr :after_filter_target_called
def initialize
@before_filter_called = false
@before_filter_target_called = false
@after_filter_called = false
@after_filter_target_called = false
end
def add
@added = @params['a'] + @params['b']
end
def before_filtered
@before_filter_target_called = true
end
def after_filtered
@after_filter_target_called = true
5
end
def thrower
raise "Hi, I'm a SOAP exception"
end
protected
def alwaysfail
@before_filter_called = true
false
end
def alwaysok
@after_filter_called = true
end
end
end
class TC_DispatcherActionController < AbstractSoapTest
include DispatcherActionControllerTest
def test_direct_dispatching
@container = DirectController.new
assert(do_soap_call('Add', 20, 50) == 70)
assert(@container.added == 70)
end
def test_direct_entrypoint
@container = DirectController.new
assert(@container.respond_to?(:api))
end
def test_direct_filtering
@container = DirectController.new
assert(@container.before_filter_called == false)
assert(@container.before_filter_target_called == false)
assert(do_soap_call('BeforeFiltered').nil?)
assert(@container.before_filter_called == true)
assert(@container.before_filter_target_called == false)
assert(@container.after_filter_called == false)
assert(@container.after_filter_target_called == false)
assert(do_soap_call('AfterFiltered') == 5)
assert(@container.after_filter_called == true)
assert(@container.after_filter_target_called == true)
end
def test_delegated_dispatching
@container = DelegatedController.new
assert(do_soap_call('Add', 50, 80) == 130)
assert(service.added == 130)
end
def test_exception_marshaling
@container = DirectController.new
result = do_soap_call('Thrower')
exception = result.detail
assert(exception.cause.is_a?(RuntimeError))
assert_equal("Hi, I'm a SOAP exception", exception.cause.message)
@container.web_service_exception_reporting = false
assert_raises(SoapTestError) do
do_soap_call('Thrower')
end
end
def test_wsdl_generation
ensure_valid_wsdl_generation DelegatedController.new
ensure_valid_wsdl_generation DirectController.new
end
def
def test_wsdl_action
ensure_valid_wsdl_action DelegatedController.new
ensure_valid_wsdl_action DirectController.new
end
protected
def service_name
@container.is_a?(DelegatedController) ? 'test_service' : 'api'
end
def service
@container.web_service_object(:test_service)
end
def do_soap_call(public_method_name, *args)
super(public_method_name, *args) do |test_request, test_response|
response = @container.process(test_request, test_response)
end
end
def ensure_valid_wsdl_generation(controller)
wsdl = controller.generate_wsdl(controller, 'http://localhost:3000/test/', '/test')
ensure_valid_wsdl(wsdl)
end
def ensure_valid_wsdl(wsdl)
definitions = WSDL::Parser.new.parse(wsdl)
assert(definitions.is_a?(WSDL::Definitions))
definitions.bindings.each do |binding|
assert(binding.name.name.index(':').nil?)
end
definitions.services.each do |service|
service.ports.each do |port|
assert(port.name.name.index(':').nil?)
end
end
end
def ensure_valid_wsdl_action(controller)
test_request = ActionController::TestRequest.new({ 'action' => 'wsdl' })
test_request.env['REQUEST_METHOD'] = 'GET'
test_request.env['HTTP_HOST'] = 'localhost:3000'
test_response = ActionController::TestResponse.new
wsdl = controller.process(test_request, test_response).body
ensure_valid_wsdl(wsdl)
end
end

View file

@ -0,0 +1,35 @@
require File.dirname(__FILE__) + '/abstract_dispatcher'
class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase
include DispatcherTest
include DispatcherCommonTests
def setup
@encoder = WS::Encoding::XmlRpcEncoding.new
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
@direct_controller = DirectController.new
@delegated_controller = DelegatedController.new
end
protected
def exception_message(xmlrpc_fault_exception)
xmlrpc_fault_exception.faultString
end
def is_exception?(obj)
obj.is_a?(XMLRPC::FaultException)
end
def create_ap_request(container, body, public_method_name, *args)
test_request = ActionController::TestRequest.new
test_request.request_parameters['action'] = service_name(container)
test_request.env['REQUEST_METHOD'] = "POST"
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
test_request.env['RAW_POST_DATA'] = body
test_request
end
def service_name(container)
container.is_a?(DelegatedController) ? 'test_service' : 'api'
end
end

View file

@ -1,3 +1,3 @@
#!/bin/sh
rcov -x '.*_test\.rb,rubygems,abstract_,/run' ./run
rcov -x '.*_test\.rb,rubygems,abstract_,/run,/apis' ./run

View file

@ -12,23 +12,46 @@ module InvocationTest
api_method :only_two
end
class Interceptor
attr :args
def initialize
@args = nil
end
def intercept(*args)
@args = args
end
end
InterceptorClass = Interceptor.new
class Service < ActionWebService::Base
web_service_api API
before_invocation :intercept_before, :except => [:no_before]
after_invocation :intercept_after, :except => [:no_after]
before_invocation :intercept_only, :only => [:only_one, :only_two]
prepend_after_invocation :intercept_after_first, :except => [:no_after]
prepend_before_invocation :intercept_only, :only => [:only_one, :only_two]
after_invocation(:only => [:only_one]) do |*args|
args[0].instance_variable_set('@block_invoked', args[1])
end
after_invocation InterceptorClass, :only => [:only_one]
attr_accessor :before_invoked
attr_accessor :after_invoked
attr_accessor :after_first_invoked
attr_accessor :only_invoked
attr_accessor :block_invoked
attr_accessor :invocation_result
def initialize
@before_invoked = nil
@after_invoked = nil
@after_first_invoked = nil
@only_invoked = nil
@invocation_result = nil
@block_invoked = nil
end
def add(a, b)
@ -69,6 +92,10 @@ module InvocationTest
@after_invoked = name
@invocation_result = result
end
def intercept_after_first(name, args, result)
@after_first_invoked = name
end
def intercept_only(name, args)
raise "Interception error" unless name == :only_one || name == :only_two
@ -94,11 +121,17 @@ class TC_Invocation < Test::Unit::TestCase
def test_interceptor_registration
assert(InvocationTest::Service.before_invocation_interceptors.length == 2)
assert(InvocationTest::Service.after_invocation_interceptors.length == 1)
assert(InvocationTest::Service.after_invocation_interceptors.length == 4)
assert_equal(:intercept_only, InvocationTest::Service.before_invocation_interceptors[0])
assert_equal(:intercept_after_first, InvocationTest::Service.after_invocation_interceptors[0])
end
def test_interception
assert(@service.before_invoked.nil? && @service.after_invoked.nil? && @service.only_invoked.nil? && @service.invocation_result.nil?)
assert(@service.before_invoked.nil?)
assert(@service.after_invoked.nil?)
assert(@service.only_invoked.nil?)
assert(@service.block_invoked.nil?)
assert(@service.invocation_result.nil?)
perform_invocation(:add, 20, 50)
assert(@service.before_invoked == :add)
assert(@service.after_invoked == :add)
@ -124,6 +157,7 @@ class TC_Invocation < Test::Unit::TestCase
def test_interception_except_conditions
perform_invocation(:no_before)
assert(@service.before_invoked.nil?)
assert(@service.after_first_invoked == :no_before)
assert(@service.after_invoked == :no_before)
assert(@service.invocation_result == 5)
@service.before_invoked = @service.after_invoked = @service.invocation_result = nil
@ -137,6 +171,8 @@ class TC_Invocation < Test::Unit::TestCase
assert(@service.only_invoked.nil?)
perform_invocation(:only_one)
assert(@service.only_invoked == :only_one)
assert(@service.block_invoked == :only_one)
assert(InvocationTest::InterceptorClass.args[1] == :only_one)
@service.only_invoked = nil
perform_invocation(:only_two)
assert(@service.only_invoked == :only_two)

View file

@ -1,53 +0,0 @@
require File.dirname(__FILE__) + '/abstract_unit'
module Foo
include ActionWebService::Protocol
def self.append_features(base)
super
base.register_protocol(BodyOnly, FooMinimalProtocol)
base.register_protocol(HeaderAndBody, FooMinimalProtocolTwo)
base.register_protocol(HeaderAndBody, FooMinimalProtocolTwo)
base.register_protocol(HeaderAndBody, FooFullProtocol)
end
class FooFullProtocol < AbstractProtocol
def self.create_protocol_request(klass, request)
protocol = FooFullProtocol.new klass
ActionWebService::Protocol::ProtocolRequest.new(protocol, '', '', '', '')
end
end
class FooMinimalProtocol < AbstractProtocol
def self.create_protocol_request(klass, request)
protocol = FooMinimalProtocol.new klass
ActionWebService::Protocol::ProtocolRequest.new(protocol, '', '', '', '')
end
end
class FooMinimalProtocolTwo < AbstractProtocol
end
end
class ProtocolRegistry
include ActionWebService::Protocol::Registry
include Foo
def all_protocols
header_and_body_protocols + body_only_protocols
end
def protocol_request
probe_request_protocol(nil)
end
end
class TC_ProtocolRegistry < Test::Unit::TestCase
def test_registration
registry = ProtocolRegistry.new
assert(registry.all_protocols.length == 4)
assert(registry.protocol_request.protocol.is_a?(Foo::FooFullProtocol))
end
end

View file

@ -1,252 +0,0 @@
require File.dirname(__FILE__) + '/abstract_soap'
module ProtocolSoapTest
class Person < ActionWebService::Struct
member :id, Integer
member :names, [String]
member :lastname, String
member :deleted, TrueClass
def ==(other)
id == other.id && names == other.names && lastname == other.lastname && deleted == other.deleted
end
end
class EmptyAPI < ActionWebService::API::Base
end
class EmptyService < ActionWebService::Base
web_service_api EmptyAPI
end
class API < ActionWebService::API::Base
api_method :argument_passing, :expects => [{:int=>:int}, {:string=>:string}, {:array=>[:int]}], :returns => [:bool]
api_method :array_returner, :returns => [[:int]]
api_method :nil_returner
api_method :struct_array_returner, :returns => [[Person]]
api_method :exception_thrower
default_api_method :default
end
class Service < ActionWebService::Base
web_service_api API
attr :int
attr :string
attr :array
attr :values
attr :person
attr :default_args
def initialize
@int = 20
@string = "wrong string value"
@default_args = nil
end
def argument_passing(int, string, array)
@int = int
@string = string
@array = array
true
end
def array_returner
@values = [1, 2, 3]
end
def nil_returner
nil
end
def struct_array_returner
@person = Person.new
@person.id = 5
@person.names = ["one", "two"]
@person.lastname = "test"
@person.deleted = false
[@person]
end
def exception_thrower
raise "Hi, I'm a SOAP error"
end
def default(*args)
@default_args = args
nil
end
end
class AbstractContainer < ActionController::Base
wsdl_service_name 'Test'
def dispatch_request(request)
protocol_request = probe_request_protocol(request)
dispatch_protocol_request(protocol_request)
end
end
class DelegatedContainer < AbstractContainer
web_service_dispatching_mode :delegated
web_service :protocol_soap_service, Service.new
web_service :empty_service, EmptyService.new
end
class DirectContainer < AbstractContainer
web_service_api API
web_service_dispatching_mode :direct
attr :int
attr :string
attr :array
attr :values
attr :person
attr :default_args
def initialize
@int = 20
@string = "wrong string value"
@default_args = nil
end
def argument_passing
@int = @params['int']
@string = @params['string']
@array = @params['array']
true
end
def array_returner
@values = [1, 2, 3]
end
def nil_returner
nil
end
def struct_array_returner
@person = Person.new
@person.id = 5
@person.names = ["one", "two"]
@person.lastname = "test"
@person.deleted = false
[@person]
end
def exception_thrower
raise "Hi, I'm a SOAP error"
end
def default
@default_args = @method_params
nil
end
end
class EmptyContainer < AbstractContainer
web_service_dispatching_mode :delegated
web_service :empty_service, EmptyService.new
end
end
class TC_ProtocolSoap < AbstractSoapTest
def setup
@delegated_container = ProtocolSoapTest::DelegatedContainer.new
@direct_container = ProtocolSoapTest::DirectContainer.new
@empty_container = ProtocolSoapTest::EmptyContainer.new
end
def test_argument_passing
in_all_containers do
assert(do_soap_call('ArgumentPassing', 5, "test string", [true, false]) == true)
assert(service.int == 5)
assert(service.string == "test string")
assert(service.array == [true, false])
end
end
def test_array_returner
in_all_containers do
assert(do_soap_call('ArrayReturner') == [1, 2, 3])
assert(service.values == [1, 2, 3])
end
end
def test_nil_returner
in_all_containers do
assert(do_soap_call('NilReturner') == nil)
end
end
def test_struct_array_returner
in_all_containers do
assert(do_soap_call('StructArrayReturner') == [service.person])
end
end
def test_nonexistent_method
@container = @empty_container
assert_raises(ActionWebService::Dispatcher::DispatcherError) do
do_soap_call('NonexistentMethod')
end
end
def test_exception_thrower
in_all_containers do
assert_raises(RuntimeError) do
do_soap_call('ExceptionThrower')
end
end
end
def test_default_api_method
in_all_containers do
assert(do_soap_call('NonExistentMethodName', 50, false).nil?)
assert(service.default_args == [50, false])
end
end
def test_service_name_setting
in_all_containers do
assert(ProtocolSoapTest::DelegatedContainer.soap_mapper.custom_namespace == 'urn:Test')
end
end
protected
def service_name
case
when @container == @direct_container
'api'
when @container == @delegated_container
'protocol_soap_service'
when @container == @empty_container
'empty_service'
end
end
def service
case
when @container == @direct_container
@container
when @container == @delegated_container
@container.web_service_object(:protocol_soap_service)
when @container == @empty_container
@container.web_service_object(:empty_service)
end
end
def in_all_containers(&block)
[@direct_container, @delegated_container].each do |container|
@container = container
block.call
end
end
def do_soap_call(public_method_name, *args)
super(public_method_name, *args) do |test_request, test_response|
@container.dispatch_request(test_request)
end
end
end

View file

@ -1,147 +0,0 @@
require File.dirname(__FILE__) + '/abstract_unit'
require 'xmlrpc/parser'
require 'xmlrpc/create'
require 'xmlrpc/config'
module XMLRPC
class XmlRpcTestHelper
include ParserWriterChooseMixin
def create_request(methodName, *args)
create().methodCall(methodName, *args)
end
def parse_response(response)
parser().parseMethodResponse(response)
end
end
end
module ProtocolXmlRpcTest
class Person < ActionWebService::Struct
member :firstname, String
member :lastname, String
member :active, TrueClass
end
class API < ActionWebService::API::Base
api_method :add, :expects => [Integer, Integer], :returns => [Integer]
api_method :hash_returner, :returns => [Hash]
api_method :array_returner, :returns => [[Integer]]
api_method :something_hash, :expects => [Hash]
api_method :struct_array_returner, :returns => [[Person]]
default_api_method :default
end
class Service < ActionWebService::Base
web_service_api API
attr :result
attr :hashvalue
attr :default_args
def initialize
@result = nil
@hashvalue = nil
@default_args = nil
end
def add(a, b)
@result = a + b
end
def something_hash(hash)
@hashvalue = hash
end
def array_returner
[1, 2, 3]
end
def hash_returner
{'name' => 1, 'value' => 2}
end
def struct_array_returner
person = Person.new
person.firstname = "John"
person.lastname = "Doe"
person.active = true
[person]
end
def default(*args)
@default_args = args
nil
end
end
$service = Service.new
class Container < ActionController::Base
def protocol_request(request)
probe_request_protocol(request)
end
def dispatch_request(protocol_request)
dispatch_protocol_request(protocol_request)
end
web_service :xmlrpc, $service
web_service_dispatching_mode :delegated
end
end
class TC_ProtocolXmlRpc < Test::Unit::TestCase
def setup
@helper = XMLRPC::XmlRpcTestHelper.new
@container = ProtocolXmlRpcTest::Container.new
end
def test_xmlrpc_request_dispatching
retval = do_xmlrpc_call('Add', 50, 30)
assert(retval == [true, 80])
end
def test_array_returning
retval = do_xmlrpc_call('ArrayReturner')
assert(retval == [true, [1, 2, 3]])
end
def test_hash_returning
retval = do_xmlrpc_call('HashReturner')
assert(retval == [true, {'name' => 1, 'value' => 2}])
end
def test_struct_array_returning
retval = do_xmlrpc_call('StructArrayReturner')
assert(retval == [true, [{"firstname"=>"John", "lastname"=>"Doe", "active"=>true}]])
end
def test_hash_parameter
retval = do_xmlrpc_call('SomethingHash', {'name' => 1, 'value' => 2})
assert(retval == [true, true])
assert($service.hashvalue == {'name' => 1, 'value' => 2})
end
def test_default_api_method
retval = do_xmlrpc_call('SomeNonexistentMethod', 'test', [1, 2], {'name'=>'value'})
assert(retval == [true, true])
assert($service.default_args == ['test', [1, 2], {'name'=>'value'}])
end
private
def do_xmlrpc_call(public_method_name, *args)
service_name = 'xmlrpc'
raw_request = @helper.create_request(public_method_name, *args)
test_request = ActionController::TestRequest.new
test_request.request_parameters['action'] = service_name
test_request.env['REQUEST_METHOD'] = "POST"
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
test_request.env['RAW_POST_DATA'] = raw_request
protocol_request = @container.protocol_request(test_request)
response = @container.dispatch_request(protocol_request)
@helper.parse_response(response.raw_body)
end
end

View file

@ -1,5 +1,5 @@
#!/usr/bin/env ruby
Dir[File.join(File.dirname(__FILE__), '*_test.rb')].each do |f|
require f
end
require 'test/unit'
args = Dir[File.join(File.dirname(__FILE__), '*_test.rb')] + Dir[File.join(File.dirname(__FILE__), 'ws/*_test.rb')]
(r = Test::Unit::AutoRunner.new(true)).process_args(args)
exit r.run

View file

@ -0,0 +1,68 @@
require File.dirname(__FILE__) + '/abstract_unit'
module Nested
class StructClass
attr_accessor :name
attr_accessor :version
def initialize
@name = 5
@version = "1.0"
end
def ==(other)
@name == other.name && @version == other.version
end
end
end
module EncodingTest
def setup
@call_signature = [:int, :bool, :string, :float, [:time], Nested::StructClass]
@call_params = [1, true, "string", 5.0, [Time.now], Nested::StructClass.new]
@response_signature = [:string]
@response_param = "hello world"
test_setup
end
def test_abstract
obj = WS::Encoding::AbstractEncoding.new
assert_raises(NotImplementedError) do
obj.encode_rpc_call(nil, nil)
end
assert_raises(NotImplementedError) do
obj.decode_rpc_call(nil)
end
assert_raises(NotImplementedError) do
obj.encode_rpc_response(nil, nil)
end
assert_raises(NotImplementedError) do
obj.decode_rpc_response(nil)
end
end
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)
params[i] = @marshaler.marshal(WS::Param.new(params[i], info))
end
@encoder.encode_rpc_call(method_name, params)
end
def decode_rpc_call(obj)
@encoder.decode_rpc_call(obj)
end
def encode_rpc_response(method_name, signature, param)
type_binding = @marshaler.register_type(signature[0])
info = WS::ParamInfo.create(signature[0], 0, type_binding)
param = @marshaler.marshal(WS::Param.new(param, info))
@encoder.encode_rpc_response(method_name, param)
end
def decode_rpc_response(obj)
@encoder.decode_rpc_response(obj)
end
end

View file

@ -0,0 +1,14 @@
$:.unshift(File.dirname(File.dirname(__FILE__)) + '/../lib')
$:.unshift(File.dirname(File.dirname(__FILE__)) + '/../lib/action_web_service/vendor')
puts $:.inspect
require 'test/unit'
require 'ws'
begin
require 'active_record'
rescue LoadError
begin
require 'rubygems'
require_gem 'activerecord', '>= 1.6.0'
rescue LoadError
end
end

View file

@ -0,0 +1,3 @@
#!/bin/sh
rcov -x '.*_test\.rb,rubygems,abstract_,/run' ./run

5
actionwebservice/test/ws/run Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env ruby
Dir[File.join(File.dirname(__FILE__), '*_test.rb')].each do |f|
require f
end

View file

@ -0,0 +1,91 @@
require File.dirname(__FILE__) + '/abstract_unit'
module Nested
class MyClass
attr_accessor :id
attr_accessor :name
def initialize(id, name)
@id = id
@name = name
end
def ==(other)
@id == other.id && @name == other.name
end
end
end
class SoapMarshalingTest < Test::Unit::TestCase
def setup
@marshaler = WS::Marshaling::SoapMarshaler.new
end
def test_abstract
marshaler = WS::Marshaling::AbstractMarshaler.new
assert_raises(NotImplementedError) do
marshaler.marshal(nil)
end
assert_raises(NotImplementedError) do
marshaler.unmarshal(nil)
end
assert_equal(nil, marshaler.register_type(nil))
end
def test_marshaling
info = WS::ParamInfo.create(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)
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)
end
def test_registration
type_binding1 = @marshaler.register_type(:int)
type_binding2 = @marshaler.register_type(:int)
assert(type_binding1.equal?(type_binding2))
end
def test_active_record
if Object.const_defined?('ActiveRecord')
node_class = Class.new(ActiveRecord::Base) do
def initialize(*args)
super(*args)
@new_record = false
end
class << self
def name
"Node"
end
def columns(*args)
[
ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'),
ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'),
ActiveRecord::ConnectionAdapters::Column.new('email', nil, 'string'),
]
end
def connection
self
end
end
end
info = WS::ParamInfo.create(node_class, 0, @marshaler.register_type(node_class))
ar_obj = node_class.new('name' => 'hello', 'email' => 'test@test.com')
param = WS::Param.new(ar_obj, info)
obj = @marshaler.marshal(param)
param = @marshaler.unmarshal(obj)
new_ar_obj = param.value
assert_equal(ar_obj, new_ar_obj)
assert(!ar_obj.equal?(new_ar_obj))
end
end
end

View file

@ -0,0 +1,47 @@
require File.dirname(__FILE__) + '/abstract_encoding'
require 'time'
class SoapRpcEncodingTest < Test::Unit::TestCase
include EncodingTest
def test_setup
@encoder = WS::Encoding::SoapRpcEncoding.new
@marshaler = WS::Marshaling::SoapMarshaler.new
end
def test_call_encoding_and_decoding
obj = encode_rpc_call('DecodeMe', @call_signature, @call_params)
method_name, decoded_params = decode_rpc_call(obj)
params = decoded_params.map{|x| @marshaler.unmarshal(x).value}
assert_equal(method_name, 'DecodeMe')
assert_equal(@call_params[0..3], params[0..3])
# XXX: DateTime not marshaled correctly yet
assert_equal(@call_params[5..-1], params[5..-1])
end
def test_response_encoding_and_decoding_simple
obj = encode_rpc_response('DecodeMe', @response_signature, @response_param)
method_name, return_value = decode_rpc_response(obj)
return_value = @marshaler.unmarshal(return_value).value
assert_equal('DecodeMe', method_name)
assert_equal(@response_param, return_value)
end
def test_response_encoding_and_decoding_struct
struct = Nested::StructClass.new
obj = encode_rpc_response('DecodeMe', [Nested::StructClass], struct)
method_name, return_value = decode_rpc_response(obj)
return_value = @marshaler.unmarshal(return_value).value
assert_equal('DecodeMe', method_name)
assert_equal(struct, return_value)
end
def test_response_encoding_and_decoding_array
struct = Nested::StructClass.new
obj = encode_rpc_response('DecodeMe', [[Nested::StructClass]], [struct])
method_name, return_value = decode_rpc_response(obj)
return_value = @marshaler.unmarshal(return_value).value
assert_equal('DecodeMe', method_name)
assert_equal([struct], return_value)
end
end

View file

@ -0,0 +1,41 @@
require File.dirname(__FILE__) + '/abstract_unit'
class TypesTest < Test::Unit::TestCase
include WS
def setup
@caster = BaseTypeCaster.new
end
def test_base_types
assert_equal(:int, BaseTypes.canonical_type_name(:integer))
assert_equal(:int, BaseTypes.canonical_type_name(:fixnum))
assert_equal(Integer, BaseTypes.type_name_to_class(:bignum))
assert_equal(Date, BaseTypes.type_name_to_class(:date))
assert_equal(Time, BaseTypes.type_name_to_class(:timestamp))
assert_equal(TrueClass, BaseTypes.type_name_to_class(:bool))
assert_equal(:int, BaseTypes.class_to_type_name(Bignum))
assert_equal(:bool, BaseTypes.class_to_type_name(FalseClass))
assert_equal(Integer, BaseTypes.canonical_type_class(Fixnum))
assert_raises(TypeError) do
BaseTypes.canonical_type_name(:fake)
end
end
def test_casting
assert_equal(5, @caster.cast("5", Fixnum))
assert_equal('50.0', @caster.cast(50.0, String))
assert_equal(true, @caster.cast('true', FalseClass))
assert_equal(false, @caster.cast('false', TrueClass))
assert_raises(TypeError) do
@caster.cast('yes', FalseClass)
end
assert_equal(3.14159, @caster.cast('3.14159', Float))
now1 = Time.new
now2 = @caster.cast("#{now1}", Time)
assert_equal(now1.tv_sec, now2.tv_sec)
date1 = Date.parse('2004-01-01')
date2 = @caster.cast("#{date1}", Date)
assert_equal(date1, date2)
end
end

View file

@ -0,0 +1,34 @@
require File.dirname(__FILE__) + '/abstract_encoding'
require 'time'
class XmlRpcEncodingTest < Test::Unit::TestCase
include EncodingTest
def test_setup
@encoder = WS::Encoding::XmlRpcEncoding.new
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
end
def test_typed_call_encoding_and_decoding
obj = encode_rpc_call('DecodeMe', @call_signature, @call_params)
method_name, params = decode_rpc_call(obj)
(0..(@call_signature.length-1)).each do |i|
params[i] = @marshaler.typed_unmarshal(params[i], @call_signature[i]).value
end
assert_equal(method_name, 'DecodeMe')
assert_equal(@call_params[0..3], params[0..3])
assert_equal(@call_params[5..-1], params[5..-1])
end
def test_untyped_call_encoding_and_decoding
obj = encode_rpc_call('DecodeMe', @call_signature, @call_params)
method_name, params = decode_rpc_call(obj)
(0..(@call_signature.length-1)).each do |i|
params[i] = @marshaler.unmarshal(params[i]).value
end
assert_equal(method_name, 'DecodeMe')
assert_equal(@call_params[0..3], params[0..3])
assert_equal(@call_params[5].name, params[5]['name'])
assert_equal(@call_params[5].version, params[5]['version'])
end
end