aboutsummaryrefslogtreecommitdiffstats
path: root/actionwebservice/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionwebservice/lib')
-rw-r--r--actionwebservice/lib/action_web_service.rb16
-rw-r--r--actionwebservice/lib/action_web_service/api.rb3
-rw-r--r--actionwebservice/lib/action_web_service/api/base.rb (renamed from actionwebservice/lib/action_web_service/api/abstract.rb)77
-rw-r--r--actionwebservice/lib/action_web_service/base.rb3
-rw-r--r--actionwebservice/lib/action_web_service/client/base.rb13
-rw-r--r--actionwebservice/lib/action_web_service/client/soap_client.rb41
-rw-r--r--actionwebservice/lib/action_web_service/client/xmlrpc_client.rb35
-rw-r--r--actionwebservice/lib/action_web_service/container.rb88
-rw-r--r--actionwebservice/lib/action_web_service/container/action_controller_container.rb (renamed from actionwebservice/lib/action_web_service/api/action_controller.rb)4
-rw-r--r--actionwebservice/lib/action_web_service/container/delegated_container.rb87
-rw-r--r--actionwebservice/lib/action_web_service/container/direct_container.rb70
-rw-r--r--actionwebservice/lib/action_web_service/dispatcher/abstract.rb175
-rw-r--r--actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb315
-rw-r--r--actionwebservice/lib/action_web_service/protocol.rb2
-rw-r--r--actionwebservice/lib/action_web_service/protocol/abstract.rb126
-rw-r--r--actionwebservice/lib/action_web_service/protocol/discovery.rb37
-rw-r--r--actionwebservice/lib/action_web_service/protocol/registry.rb55
-rw-r--r--actionwebservice/lib/action_web_service/protocol/soap_protocol.rb496
-rw-r--r--actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb184
-rw-r--r--actionwebservice/lib/action_web_service/struct.rb4
-rw-r--r--actionwebservice/lib/action_web_service/support/signature.rb100
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws.rb4
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/common.rb8
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/encoding.rb3
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb26
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb90
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb53
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb3
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb17
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb224
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb116
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/types.rb162
32 files changed, 1305 insertions, 1332 deletions
diff --git a/actionwebservice/lib/action_web_service.rb b/actionwebservice/lib/action_web_service.rb
index 5cf988a0f8..2865dff633 100644
--- a/actionwebservice/lib/action_web_service.rb
+++ b/actionwebservice/lib/action_web_service.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/api.rb b/actionwebservice/lib/action_web_service/api.rb
index 0c71de5654..ab8b696ab4 100644
--- a/actionwebservice/lib/action_web_service/api.rb
+++ b/actionwebservice/lib/action_web_service/api.rb
@@ -1,2 +1 @@
-require 'action_web_service/api/abstract'
-require 'action_web_service/api/action_controller'
+require 'action_web_service/api/base'
diff --git a/actionwebservice/lib/action_web_service/api/abstract.rb b/actionwebservice/lib/action_web_service/api/base.rb
index 0ce68d10f7..952c6baa0d 100644
--- a/actionwebservice/lib/action_web_service/api/abstract.rb
+++ b/actionwebservice/lib/action_web_service/api/base.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/base.rb b/actionwebservice/lib/action_web_service/base.rb
index 42a514716a..16e4d87f41 100644
--- a/actionwebservice/lib/action_web_service/base.rb
+++ b/actionwebservice/lib/action_web_service/base.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/client/base.rb b/actionwebservice/lib/action_web_service/client/base.rb
index 431b78c748..9dada7bf98 100644
--- a/actionwebservice/lib/action_web_service/client/base.rb
+++ b/actionwebservice/lib/action_web_service/client/base.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/client/soap_client.rb b/actionwebservice/lib/action_web_service/client/soap_client.rb
index dfabfd86ee..b93f6475d9 100644
--- a/actionwebservice/lib/action_web_service/client/soap_client.rb
+++ b/actionwebservice/lib/action_web_service/client/soap_client.rb
@@ -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)
diff --git a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
index bb52c20453..dc7ad1517f 100644
--- a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
+++ b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
@@ -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)
diff --git a/actionwebservice/lib/action_web_service/container.rb b/actionwebservice/lib/action_web_service/container.rb
index f02717579e..13d9d8ab56 100644
--- a/actionwebservice/lib/action_web_service/container.rb
+++ b/actionwebservice/lib/action_web_service/container.rb
@@ -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'
diff --git a/actionwebservice/lib/action_web_service/api/action_controller.rb b/actionwebservice/lib/action_web_service/container/action_controller_container.rb
index 604cbfe704..4dda93ec11 100644
--- a/actionwebservice/lib/action_web_service/api/action_controller.rb
+++ b/actionwebservice/lib/action_web_service/container/action_controller_container.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/container/delegated_container.rb b/actionwebservice/lib/action_web_service/container/delegated_container.rb
new file mode 100644
index 0000000000..674141aab6
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/container/delegated_container.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/container/direct_container.rb b/actionwebservice/lib/action_web_service/container/direct_container.rb
new file mode 100644
index 0000000000..b53c542fc8
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/container/direct_container.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
index 2262cd1cdd..b7560afc87 100644
--- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
+++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
@@ -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)
- 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)
+
+ 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 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
diff --git a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
index 511d00ca44..a35029b40d 100644
--- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
+++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
@@ -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")
+ 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
- logger.error("No response available") unless logger.nil?
- render_text("Internal protocol error", "500 Internal Server Error")
+ 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 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]
- 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
- @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)
+ def web_service_direct_invoke(invocation)
+ @params ||= {}
+ invocation.method_named_params.each do |name, value|
+ @params[name] = value
+ end
+ @session ||= {}
+ @assigns ||= {}
+ @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(global_service, service)
+ "#{global_service}#{service.to_s.camelize}Binding"
end
- def binding_name_for(wsdl_service_name, service_name)
- "#{wsdl_service_name}#{service_name.to_s.camelize}Binding"
+ 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
diff --git a/actionwebservice/lib/action_web_service/protocol.rb b/actionwebservice/lib/action_web_service/protocol.rb
index b15e850676..053e9cb4be 100644
--- a/actionwebservice/lib/action_web_service/protocol.rb
+++ b/actionwebservice/lib/action_web_service/protocol.rb
@@ -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'
diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb
index 9199dfe33f..f628fc4aee 100644
--- a/actionwebservice/lib/action_web_service/protocol/abstract.rb
+++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb
@@ -1,126 +1,28 @@
module ActionWebService # :nodoc:
module Protocol # :nodoc:
- CheckedMessage = :checked
- UncheckedMessage = :unchecked
-
- class ProtocolError < ActionWebService::ActionWebServiceError # :nodoc:
- 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
+ class ProtocolError < ActionWebService::ActionWebServiceError
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
-
- attr_accessor :content_type
+ class Response
+ attr :body
+ attr :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
diff --git a/actionwebservice/lib/action_web_service/protocol/discovery.rb b/actionwebservice/lib/action_web_service/protocol/discovery.rb
new file mode 100644
index 0000000000..ab51958ed9
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/protocol/discovery.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/protocol/registry.rb b/actionwebservice/lib/action_web_service/protocol/registry.rb
deleted file mode 100644
index 0173673556..0000000000
--- a/actionwebservice/lib/action_web_service/protocol/registry.rb
+++ /dev/null
@@ -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
diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
index 3c527fea93..f2e761f431 100644
--- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
+++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
@@ -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:
- 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}"))
- end
-
- def soap_mapper # :nodoc:
- read_inheritable_attribute("soap_mapper")
- end
+module ActionWebService
+ module Protocol
+ module Soap
+ def self.included(base)
+ base.register_protocol(SoapProtocol)
+ base.class_inheritable_option(:wsdl_service_name)
end
-
- class SoapProtocol < AbstractProtocol # :nodoc:
- attr :mapper
-
- def initialize(mapper)
- @mapper = mapper
- 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)
- end
-
- def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
- return nil unless protocol_name.to_s.downcase.to_sym == :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
+
+ class SoapProtocol
+ def initialize
+ @encoder = WS::Encoding::SoapRpcEncoding.new
+ @marshaler = WS::Marshaling::SoapMarshaler.new
+ end
+
+ def unmarshal_request(ap_request)
+ return nil unless has_valid_soap_action?(ap_request)
+ method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
+ params = params.map{|x| @marshaler.unmarshal(x)}
+ service_name = ap_request.parameters['action']
+ Request.new(self, method_name, params, service_name)
+ end
+
+ 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
- if protocol_request.checked?
- []
- else
- unmarshal.call
- end
+ return_value = nil
end
+ body = @encoder.encode_rpc_response(method_name, return_value)
+ Response.new(body, 'text/xml')
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')
+ def register_signature_type(spec)
+ @marshaler.register_type(spec)
end
- def marshal_exception(exc)
- ProtocolResponse.new(self, create_exception_response(exc), 'text/xml')
+ def protocol_client(api, protocol_name, endpoint_uri, options)
+ return nil unless protocol_name == :soap
+ ActionWebService::Client::Soap.new(api, endpoint_uri, options)
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
diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
index 1addccba56..1533a2bdb9 100644
--- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
+++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
@@ -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
- 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
- ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
+ def initialize
+ @encoder = WS::Encoding::XmlRpcEncoding.new
+ @marshaler = WS::Marshaling::XmlRpcMarshaler.new
end
- def initialize(container_class)
- super(container_class)
+ 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 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)
+ 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
- # 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
+ return_value = nil
end
- ProtocolResponse.new(self, raw_response, 'text/xml')
+ body = @encoder.encode_rpc_response(method_name, return_value)
+ Response.new(body, '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 register_signature_type(spec)
+ nil
+ end
- def transform_array_types(signature)
- signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x}
- 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
-
- 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
diff --git a/actionwebservice/lib/action_web_service/struct.rb b/actionwebservice/lib/action_web_service/struct.rb
index 5420f4cf49..77f4fbf4aa 100644
--- a/actionwebservice/lib/action_web_service/struct.rb
+++ b/actionwebservice/lib/action_web_service/struct.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/support/signature.rb b/actionwebservice/lib/action_web_service/support/signature.rb
deleted file mode 100644
index 00c62a2232..0000000000
--- a/actionwebservice/lib/action_web_service/support/signature.rb
+++ /dev/null
@@ -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
diff --git a/actionwebservice/lib/action_web_service/vendor/ws.rb b/actionwebservice/lib/action_web_service/vendor/ws.rb
new file mode 100644
index 0000000000..18a32a555e
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws.rb
@@ -0,0 +1,4 @@
+require 'ws/common'
+require 'ws/types'
+require 'ws/marshaling'
+require 'ws/encoding'
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/common.rb b/actionwebservice/lib/action_web_service/vendor/ws/common.rb
new file mode 100644
index 0000000000..4266a7141d
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/common.rb
@@ -0,0 +1,8 @@
+module WS
+ class WSError < StandardError
+ end
+
+ def self.derived_from?(ancestor, child)
+ child.ancestors.include?(ancestor)
+ end
+end
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb
new file mode 100644
index 0000000000..790317639b
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb
@@ -0,0 +1,3 @@
+require 'ws/encoding/abstract'
+require 'ws/encoding/soap_rpc_encoding'
+require 'ws/encoding/xmlrpc_encoding'
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb
new file mode 100644
index 0000000000..257c7d0993
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb
new file mode 100644
index 0000000000..f4d2f5a7d6
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
new file mode 100644
index 0000000000..b38ae81abf
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb
new file mode 100644
index 0000000000..3a0a2e8cc1
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb
@@ -0,0 +1,3 @@
+require 'ws/marshaling/abstract'
+require 'ws/marshaling/soap_marshaling'
+require 'ws/marshaling/xmlrpc_marshaling'
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb
new file mode 100644
index 0000000000..53120e1447
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb
new file mode 100644
index 0000000000..99e2a7ff28
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb
new file mode 100644
index 0000000000..87154f87e1
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb
@@ -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
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/types.rb b/actionwebservice/lib/action_web_service/vendor/ws/types.rb
new file mode 100644
index 0000000000..24b96dc327
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/vendor/ws/types.rb
@@ -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