Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

* collapse 'ws' back into protocols, it just added complexity and ind…

…irection, and was hard to extend.

* extract casting into seperate support file
* ensure casting always does the right thing for return values, should fix interoperability issues with Ecto and possibly other XML-RPC clients
* add functional unit tests for scaffolding
* represent signature items with classes instead of symbols/Class objects, much more flexible
* tweak logging to always show casted versions of parameters and return values, if possible.


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1072 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit aaea48fe9826b9e5d2d5b92795a297b8f238c58d 1 parent aa09c77
@bitserf bitserf authored
Showing with 1,090 additions and 1,511 deletions.
  1. +4 −2 actionwebservice/CHANGELOG
  2. +1 −1  actionwebservice/Rakefile
  3. +0 −5 actionwebservice/TODO
  4. +2 −2 actionwebservice/lib/action_web_service.rb
  5. +18 −128 actionwebservice/lib/action_web_service/api/base.rb
  6. +105 −0 actionwebservice/lib/action_web_service/casting.rb
  7. +9 −11 actionwebservice/lib/action_web_service/client/soap_client.rb
  8. +6 −4 actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
  9. +21 −19 actionwebservice/lib/action_web_service/dispatcher/abstract.rb
  10. +32 −31 actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
  11. +51 −42 actionwebservice/lib/action_web_service/protocol/abstract.rb
  12. +2 −2 actionwebservice/lib/action_web_service/protocol/discovery.rb
  13. +98 −13 actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
  14. +197 −0 actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb
  15. +48 −7 actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
  16. +32 −11 actionwebservice/lib/action_web_service/scaffolding.rb
  17. +14 −1 actionwebservice/lib/action_web_service/struct.rb
  18. +191 −0 actionwebservice/lib/action_web_service/support/signature_types.rb
  19. +3 −3 actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
  20. +9 −28 actionwebservice/lib/action_web_service/test_invoke.rb
  21. +0 −4 actionwebservice/lib/action_web_service/vendor/ws.rb
  22. +0 −8 actionwebservice/lib/action_web_service/vendor/ws/common.rb
  23. +0 −3  actionwebservice/lib/action_web_service/vendor/ws/encoding.rb
  24. +0 −26 actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb
  25. +0 −90 actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb
  26. +0 −44 actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
  27. +0 −3  actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb
  28. +0 −33 actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb
  29. +0 −283 actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb
  30. +0 −143 actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb
  31. +0 −165 actionwebservice/lib/action_web_service/vendor/ws/types.rb
  32. +49 −47 actionwebservice/test/abstract_dispatcher.rb
  33. +1 −1  actionwebservice/test/abstract_unit.rb
  34. +8 −8 actionwebservice/test/api_test.rb
  35. +82 −0 actionwebservice/test/casting_test.rb
  36. +1 −3 actionwebservice/test/client_soap_test.rb
  37. +3 −4 actionwebservice/test/client_xmlrpc_test.rb
  38. +0 −2  actionwebservice/test/dispatcher_action_controller_soap_test.rb
  39. +0 −2  actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb
  40. +67 −0 actionwebservice/test/scaffolded_controller_test.rb
  41. +35 −21 actionwebservice/test/struct_test.rb
  42. +1 −1  actionwebservice/test/test_invoke_test.rb
  43. +0 −68 actionwebservice/test/ws/abstract_encoding.rb
  44. +0 −13 actionwebservice/test/ws/abstract_unit.rb
  45. +0 −3  actionwebservice/test/ws/gencov
  46. +0 −5 actionwebservice/test/ws/run
  47. +0 −97 actionwebservice/test/ws/soap_marshaling_test.rb
  48. +0 −47 actionwebservice/test/ws/soap_rpc_encoding_test.rb
  49. +0 −43 actionwebservice/test/ws/types_test.rb
  50. +0 −34 actionwebservice/test/ws/xmlrpc_encoding_test.rb
View
6 actionwebservice/CHANGELOG
@@ -1,11 +1,13 @@
*0.7.0* (Unreleased)
-* Remove ActiveRecordSoapMarshallable workaround, see #912 for details
-
* Add scaffolding via ActionController::Base.web_service_scaffold for quick testing using a web browser
+* Remove ActiveRecordSoapMarshallable workaround, see #912 for details
+
* Generalize casting code to be used by both SOAP and XML-RPC (previously, it was only XML-RPC)
+* Ensure return value is properly cast as well, fixes XML-RPC interoperability with Ecto and possibly other clients
+
* Include backtraces in 500 error responses for failed request parsing, and remove "rescue nil" statements obscuring real errors for XML-RPC
View
2  actionwebservice/Rakefile
@@ -264,4 +264,4 @@ task :release => [:package] do
first_file = false
end
end
-end
+end
View
5 actionwebservice/TODO
@@ -2,14 +2,9 @@
- WS Dynamic Scaffolding
* add protocol selection ability
* test with XML-RPC (namespaced method name support)
- * support structured types as input parameters with the input field helper
- update manual for scaffolding and functional testing
-
-= 0.8.0
- - Consumption of WSDL services
= Refactoring
- - Port dispatcher tests to use test_invoke
- Don't have clean way to go from SOAP Class object to the xsd:NAME type
string -- NaHi possibly looking at remedying this situation
View
4 actionwebservice/lib/action_web_service.rb
@@ -35,12 +35,12 @@
$:.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/support/signature_types'
require 'action_web_service/base'
require 'action_web_service/client'
require 'action_web_service/invocation'
require 'action_web_service/api'
+require 'action_web_service/casting'
require 'action_web_service/struct'
require 'action_web_service/container'
require 'action_web_service/protocol'
View
146 actionwebservice/lib/action_web_service/api/base.rb
@@ -1,8 +1,5 @@
module ActionWebService # :nodoc:
module API # :nodoc:
- class CastingError < ActionWebService::ActionWebServiceError
- 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.
@@ -31,6 +28,8 @@ class Base
private_class_method :new, :allocate
class << self
+ include ActionWebService::SignatureTypes
+
# API methods have a +name+, which must be the Ruby method name to use when
# performing the invocation on the web service object.
#
@@ -70,10 +69,9 @@ def api_method(name, options={})
expects = canonical_signature(expects)
returns = canonical_signature(returns)
if expects
- expects.each do |param|
- klass = WS::BaseTypes.canonical_param_type_class(param)
- klass = klass[0] if klass.is_a?(Array)
- if klass.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
+ expects.each do |type|
+ type = type.element_type if type.is_a?(ArrayType)
+ if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
end
end
@@ -138,16 +136,6 @@ def default_api_method_instance
instance
end
- # Creates a dummy API Method instance for the given public method name
- def dummy_public_api_method_instance(public_method_name)
- Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil)
- end
-
- # Creates a dummy API Method instance for the given method name
- def dummy_api_method_instance(method_name)
- Method.new(method_name, public_api_method_name(method_name), nil, nil)
- end
-
private
def api_public_method_names
read_inheritable_attribute("api_public_method_names") || {}
@@ -159,11 +147,6 @@ def validate_options(valid_option_keys, supplied_option_keys)
raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
end
end
-
- def canonical_signature(signature)
- return nil if signature.nil?
- signature.map{|spec| WS::BaseTypes.canonical_param_type_spec(spec)}
- end
end
end
@@ -180,134 +163,41 @@ def initialize(name, public_name, expects, returns)
@public_name = public_name
@expects = expects
@returns = returns
+ @caster = ActionWebService::Casting::BaseCaster.new(self)
end
# The list of parameter names for this method
def param_names
return [] unless @expects
- i = 0
- @expects.map{ |spec| param_name(spec, i += 1) }
- end
-
- # The name for the given parameter
- def param_name(spec, i=1)
- spec.is_a?(Hash) ? spec.keys.first.to_s : "p#{i}"
- end
-
- # The type of the parameter declared in +spec+. Is either
- # the Class of the parameter, or its canonical name (if its a
- # base type). Typed array specifications will return the type of
- # their elements.
- def param_type(spec)
- spec = spec.values.first if spec.is_a?(Hash)
- param_type = spec.is_a?(Array) ? spec[0] : spec
- WS::BaseTypes::class_to_type_name(param_type) rescue param_type
- end
-
- # The Class of the parameter declared in +spec+.
- def param_class(spec)
- type = param_type(spec)
- type.is_a?(Symbol) ? WS::BaseTypes.type_name_to_class(type) : type
- end
-
- # Registers all types known to this method with the given marshaler
- def register_types(marshaler)
- @expects.each{ |x| marshaler.register_type(x) } if @expects
- @returns.each{ |x| marshaler.register_type(x) } if @returns
+ @expects.map{ |type| type.name }
end
- # Encodes an RPC call for this method. Casting is performed if
- # the <tt>:strict</tt> option is given.
- def encode_rpc_call(marshaler, encoder, params, options={})
- name = options[:method_name] || @public_name
- expects = @expects || []
- returns = @returns || []
- (expects + returns).each { |spec| marshaler.register_type spec }
- (0..(params.length-1)).each do |i|
- spec = expects[i] || params[i].class
- type_binding = marshaler.lookup_type(spec)
- param_info = WS::ParamInfo.create(spec, type_binding, i)
- if options[:strict]
- value = marshaler.cast_outbound_recursive(params[i], spec)
- else
- value = params[i]
- end
- param = WS::Param.new(value, param_info)
- params[i] = marshaler.marshal(param)
- end
- encoder.encode_rpc_call(name, params)
- end
-
- # Encodes an RPC response for this method. Casting is performed if
- # the <tt>:strict</tt> option is given.
- def encode_rpc_response(marshaler, encoder, return_value, options={})
- if !return_value.nil? && @returns
- return_type = @returns[0]
- type_binding = marshaler.register_type(return_type)
- param_info = WS::ParamInfo.create(return_type, type_binding, 0)
- if options[:strict]
- return_value = marshaler.cast_inbound_recursive(return_value, return_type)
- end
- return_value = marshaler.marshal(WS::Param.new(return_value, param_info))
- else
- return_value = nil
- end
- encoder.encode_rpc_response(response_name(encoder), return_value)
- end
-
- # Casts a set of WS::Param values into the appropriate
- # Ruby values
- def cast_expects_ws2ruby(marshaler, params)
- return [] if @expects.nil?
- i = 0
- @expects.map do |spec|
- value = marshaler.cast_inbound_recursive(params[i].value, spec)
- i += 1
- value
- end
- end
-
# Casts a set of Ruby values into the expected Ruby values
- def cast_expects(marshaler, params)
- return [] if @expects.nil?
- i = 0
- @expects.map do |spec|
- value = marshaler.cast_outbound_recursive(params[i], spec)
- i += 1
- value
- end
+ def cast_expects(params)
+ @caster.cast_expects(params)
end
# Cast a Ruby return value into the expected Ruby value
- def cast_returns(marshaler, return_value)
- return nil if @returns.nil?
- marshaler.cast_inbound_recursive(return_value, @returns[0])
+ def cast_returns(return_value)
+ @caster.cast_returns(return_value)
end
# String representation of this method
def to_s
fqn = ""
- fqn << (@returns ? (friendly_param(@returns[0], nil) + " ") : "void ")
+ fqn << (@returns ? (friendly_param(@returns[0], false) + " ") : "void ")
fqn << "#{@public_name}("
- if @expects
- i = 0
- fqn << @expects.map{ |p| friendly_param(p, i+= 1) }.join(", ")
- end
+ fqn << @expects.map{ |p| friendly_param(p) }.join(", ") if @expects
fqn << ")"
fqn
end
private
- def response_name(encoder)
- encoder.is_a?(WS::Encoding::SoapRpcEncoding) ? (@public_name + "Response") : @public_name
- end
-
- def friendly_param(spec, i)
- name = param_name(spec, i)
- type = param_type(spec)
- spec = spec.values.first if spec.is_a?(Hash)
- type = spec.is_a?(Array) ? (type.to_s + "[]") : type.to_s
- i ? (type + " " + name) : type
+ def friendly_param(type, show_name=true)
+ name = type.name.to_s
+ type_type = type.type.to_s
+ str = type.array?? (type_type + '[]') : type_type
+ show_name ? (str + " " + name) : str
end
end
end
View
105 actionwebservice/lib/action_web_service/casting.rb
@@ -0,0 +1,105 @@
+require 'time'
+require 'date'
+require 'generator'
+
+module ActionWebService # :nodoc:
+ module Casting # :nodoc:
+ class CastingError < ActionWebServiceError # :nodoc:
+ end
+
+ # Performs casting of arbitrary values into the correct types for the signature
+ class BaseCaster
+ def initialize(api_method)
+ @api_method = api_method
+ end
+
+ # Coerces the parameters in +params+ (an Enumerable) into the types
+ # this method expects
+ def cast_expects(params)
+ self.class.cast_expects(@api_method, params)
+ end
+
+ # Coerces the given +return_value+ into the the type returned by this
+ # method
+ def cast_returns(return_value)
+ self.class.cast_returns(@api_method, return_value)
+ end
+
+ class << self
+ include ActionWebService::SignatureTypes
+
+ def cast_expects(api_method, params) # :nodoc:
+ return [] if api_method.expects.nil?
+ SyncEnumerator.new(params, api_method.expects).map{ |r| cast(r[0], r[1]) }
+ end
+
+ def cast_returns(api_method, return_value) # :nodoc:
+ return nil if api_method.returns.nil?
+ cast(return_value, api_method.returns[0])
+ end
+
+ def cast(value, signature_type) # :nodoc:
+ return value if signature_type.nil? # signature.length != params.length
+ unless signature_type.array?
+ return value if canonical_type(value.class) == signature_type.type
+ end
+ if signature_type.array?
+ unless value.respond_to?(:entries) && !value.is_a?(String)
+ raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}"
+ end
+ value.entries.map do |entry|
+ cast(entry, signature_type.element_type)
+ end
+ elsif signature_type.structured?
+ cast_to_structured_type(value, signature_type)
+ elsif !signature_type.custom?
+ cast_base_type(value, signature_type)
+ end
+ end
+
+ def cast_base_type(value, signature_type) # :nodoc:
+ case signature_type.type
+ when :int
+ Integer(value)
+ when :string
+ value.to_s
+ when :bool
+ return false if value.nil?
+ return value if value == true || value == false
+ case value.to_s.downcase
+ when '1', 'true', 'y', 'yes'
+ true
+ when '0', 'false', 'n', 'no'
+ false
+ else
+ raise CastingError, "Don't know how to cast #{value.class} into Boolean"
+ end
+ when :float
+ Float(value)
+ when :time
+ Time.parse(value.to_s)
+ when :date
+ Date.parse(value.to_s)
+ when :datetime
+ DateTime.parse(value.to_s)
+ end
+ end
+
+ def cast_to_structured_type(value, signature_type) # :nodoc:
+ obj = signature_type.type_class.new
+ if value.respond_to?(:each_pair)
+ klass = signature_type.type_class
+ value.each_pair do |name, val|
+ type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil
+ val = cast(val, type) if type
+ obj.send("#{name}=", val)
+ end
+ else
+ raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
+ end
+ obj
+ end
+ end
+ end
+ end
+end
View
20 actionwebservice/lib/action_web_service/client/soap_client.rb
@@ -46,8 +46,7 @@ def initialize(api, endpoint_uri, options={})
@type_namespace = options[:type_namespace] || 'urn:ActionWebService'
@method_namespace = options[:method_namespace] || 'urn:ActionWebService'
@driver_options = options[:driver_options] || {}
- @marshaler = WS::Marshaling::SoapMarshaler.new @type_namespace
- @encoder = WS::Encoding::SoapRpcEncoding.new @method_namespace
+ @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new
@soap_action_base = options[:soap_action_base]
@soap_action_base ||= URI.parse(endpoint_uri).path
@driver = create_soap_rpc_driver(api, endpoint_uri)
@@ -59,9 +58,9 @@ def initialize(api, endpoint_uri, options={})
protected
def perform_invocation(method_name, args)
method = @api.api_methods[method_name.to_sym]
- args = method.cast_expects(@marshaler, args)
+ args = method.cast_expects(args.dup) rescue args
return_value = @driver.send(method_name, *args)
- method.cast_returns(@marshaler, return_value)
+ method.cast_returns(return_value.dup) rescue return_value
end
def soap_action(method_name)
@@ -70,9 +69,9 @@ def soap_action(method_name)
private
def create_soap_rpc_driver(api, endpoint_uri)
- api.api_methods.each{ |name, method| method.register_types(@marshaler) }
+ @protocol.register_api(api)
driver = SoapDriver.new(endpoint_uri, nil)
- driver.mapping_registry = @marshaler.registry
+ driver.mapping_registry = @protocol.marshaler.registry
api.api_methods.each do |name, method|
qname = XSD::QName.new(@method_namespace, method.public_name)
action = soap_action(method.public_name)
@@ -81,15 +80,14 @@ def create_soap_rpc_driver(api, endpoint_uri)
param_def = []
i = 0
if expects
- expects.each do |spec|
- param_name = method.param_name(spec, i)
- type_binding = @marshaler.lookup_type(spec)
- param_def << ['in', param_name, type_binding.mapping]
+ expects.each do |type|
+ type_binding = @protocol.marshaler.lookup_type(type)
+ param_def << ['in', type.name, type_binding.mapping]
i += 1
end
end
if returns
- type_binding = @marshaler.lookup_type(returns[0])
+ type_binding = @protocol.marshaler.lookup_type(returns[0])
param_def << ['retval', 'return', type_binding.mapping]
end
driver.add_method(qname, action, method.name.to_s, param_def)
View
10 actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
@@ -30,20 +30,22 @@ class XmlRpc < Base
def initialize(api, endpoint_uri, options={})
@api = api
@handler_name = options[:handler_name]
+ @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
@client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
- @marshaler = WS::Marshaling::XmlRpcMarshaler.new
end
protected
def perform_invocation(method_name, args)
method = @api.api_methods[method_name.to_sym]
- method.register_types(@marshaler)
if method.expects && method.expects.length != args.length
raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})")
end
- args = method.cast_expects(@marshaler, args)
+ args = method.cast_expects(args.dup) rescue args
+ if method.expects
+ method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) }
+ end
ok, return_value = @client.call2(public_name(method_name), *args)
- return method.cast_returns(@marshaler, return_value) if ok
+ return (method.cast_returns(return_value.dup) rescue return_value) if ok
raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
end
View
40 actionwebservice/lib/action_web_service/dispatcher/abstract.rb
@@ -12,14 +12,6 @@ def self.append_features(base) # :nodoc:
base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
end
- def self.layered_service_name(public_method_name) # :nodoc:
- if public_method_name =~ /^([^\.]+)\.(.*)$/
- $1
- else
- nil
- end
- end
-
module InstanceMethods # :nodoc:
private
def invoke_web_service_request(protocol_request)
@@ -40,13 +32,7 @@ def web_service_direct_invoke(invocation)
else
return_value = self.__send__(invocation.api_method.name)
end
- if invocation.api.has_api_method?(invocation.api_method.name)
- api_method = invocation.api_method
- else
- api_method = invocation.api_method.dup
- api_method.instance_eval{ @returns = [ return_value.class ] }
- end
- invocation.protocol.marshal_response(api_method, return_value)
+ web_service_create_response(invocation.protocol, invocation.api, invocation.api_method, return_value)
end
def web_service_delegated_invoke(invocation)
@@ -57,7 +43,7 @@ def web_service_delegated_invoke(invocation)
if cancellation_reason
raise(DispatcherError, "request canceled: #{cancellation_reason}")
end
- invocation.protocol.marshal_response(invocation.api_method, return_value)
+ web_service_create_response(invocation.protocol, invocation.api, invocation.api_method, return_value)
end
def web_service_invocation(request)
@@ -79,6 +65,7 @@ def web_service_invocation(request)
invocation.service = web_service_object(invocation.service_name)
invocation.api = invocation.service.class.web_service_api
end
+ invocation.protocol.register_api(invocation.api)
request.api = invocation.api
if invocation.api.has_public_api_method?(public_method_name)
invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
@@ -89,15 +76,20 @@ def web_service_invocation(request)
invocation.api_method = invocation.api.default_api_method_instance
end
end
+ if invocation.service.nil?
+ raise(DispatcherError, "no service available for service name #{invocation.service_name}")
+ end
unless invocation.service.respond_to?(invocation.api_method.name)
- raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
+ raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
end
request.api_method = invocation.api_method
begin
- invocation.method_ordered_params = invocation.api_method.cast_expects_ws2ruby(request.protocol.marshaler, request.method_params)
+ invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
rescue
- invocation.method_ordered_params = request.method_params.map{ |x| x.value }
+ logger.warn "Casting of method parameters failed" unless logger.nil?
+ invocation.method_ordered_params = request.method_params
end
+ request.method_params = invocation.method_ordered_params
invocation.method_named_params = {}
invocation.api_method.param_names.inject(0) do |m, n|
invocation.method_named_params[n] = invocation.method_ordered_params[m]
@@ -106,6 +98,16 @@ def web_service_invocation(request)
invocation
end
+ def web_service_create_response(protocol, api, api_method, return_value)
+ if api.has_api_method?(api_method.name)
+ return_type = api_method.returns ? api_method.returns[0] : nil
+ return_value = api_method.cast_returns(return_value)
+ else
+ return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
+ end
+ protocol.encode_response(api_method.public_name + 'Response', return_value, return_type)
+ end
+
class Invocation # :nodoc:
attr_accessor :protocol
attr_accessor :service_name
View
63 actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
@@ -45,7 +45,6 @@ def dispatch_web_service_request
exception = e
end
if request
- log_request(request, @request.raw_post)
response = nil
exception = nil
bm = Benchmark.measure do
@@ -55,6 +54,7 @@ def dispatch_web_service_request
exception = e
end
end
+ log_request(request, @request.raw_post)
if exception
log_error(exception) unless logger.nil?
send_web_service_error_response(request, exception)
@@ -82,10 +82,10 @@ def send_web_service_error_response(request, exception)
unless self.class.web_service_exception_reporting
exception = DispatcherError.new("Internal server error (exception raised)")
end
- api_method = request.api_method ? request.api_method.dup : nil
- api_method ||= request.api.dummy_api_method_instance(request.method_name)
- api_method.instance_eval{ @returns = [ exception.class ] }
- response = request.protocol.marshal_response(api_method, exception)
+ api_method = request.api_method
+ public_method_name = api_method ? api_method.public_name : request.method_name
+ return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
+ response = request.protocol.encode_response(public_method_name + 'Response', exception, return_type)
send_web_service_response(response)
else
if self.class.web_service_exception_reporting
@@ -118,7 +118,14 @@ def web_service_direct_invoke(invocation)
def log_request(request, body)
unless logger.nil?
name = request.method_name
- params = request.method_params.map{|x| "#{x.info.name}=>#{x.value.inspect}"}
+ api_method = request.api_method
+ params = request.method_params
+ if api_method && api_method.expects
+ i = 0
+ params = api_method.expects.map{ |type| param = "#{type.name}=>#{params[i].inspect}"; i+= 1; param }
+ else
+ params = params.map{ |param| param.inspect }
+ end
service = request.service_name
logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
logger.debug(indent(body))
@@ -127,7 +134,8 @@ def log_request(request, body)
def log_response(response, elapsed=nil)
unless logger.nil?
- logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":"))
+ elapsed = (elapsed ? " (%f):" % elapsed : ":")
+ logger.debug("\nWeb Service Response" + elapsed + " => #{response.return_value.inspect}")
logger.debug(indent(response.body))
end
end
@@ -171,7 +179,7 @@ def to_wsdl
namespace = 'urn:ActionWebService'
soap_action_base = "/#{controller_name}"
- marshaler = WS::Marshaling::SoapMarshaler.new(namespace)
+ marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
apis = {}
case dispatching_mode
when :direct
@@ -208,7 +216,7 @@ def to_wsdl
xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
custom_types.each do |binding|
case
- when binding.is_typed_array?
+ when binding.type.array?
xm.xsd(:complexType, 'name' => binding.type_name) do
xm.xsd(:complexContent) do
xm.xsd(:restriction, 'base' => 'soapenc:Array') do
@@ -217,11 +225,11 @@ def to_wsdl
end
end
end
- when binding.is_typed_struct?
+ when binding.type.structured?
xm.xsd(:complexType, 'name' => binding.type_name) do
xm.xsd(:all) do
- binding.each_member do |name, spec|
- b = marshaler.register_type(spec)
+ binding.type.each_member do |name, type|
+ b = marshaler.register_type(type)
xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
end
end
@@ -249,14 +257,8 @@ def to_wsdl
expects = method.expects
i = 1
expects.each do |type|
- if type.is_a?(Hash)
- param_name = type.keys.shift
- type = type.values.shift
- else
- param_name = "param#{i}"
- end
binding = marshaler.register_type(type)
- xm.part('name' => param_name, 'type' => binding.qualified_type_name('typens'))
+ xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
i += 1
end if expects
end
@@ -340,7 +342,9 @@ def binding_name_for(global_service, service)
def register_api(api, marshaler)
bindings = {}
traverse_custom_types(api, marshaler) do |binding|
- bindings[binding] = nil unless bindings.has_key?(binding.type_class)
+ bindings[binding] = nil unless bindings.has_key?(binding)
+ element_binding = binding.element_binding
+ bindings[binding.element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
end
bindings.keys
end
@@ -348,21 +352,18 @@ def register_api(api, marshaler)
def traverse_custom_types(api, marshaler, &block)
api.api_methods.each do |name, method|
expects, returns = method.expects, method.returns
- expects.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if expects
- returns.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if returns
+ expects.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if expects
+ returns.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if returns
end
end
- def traverse_custom_type_spec(marshaler, spec, &block)
- binding = marshaler.register_type(spec)
- if binding.is_typed_struct?
- binding.each_member do |name, member_spec|
- traverse_custom_type_spec(marshaler, member_spec, &block)
- end
- elsif binding.is_typed_array?
- traverse_custom_type_spec(marshaler, binding.element_binding.type_class, &block)
+ def traverse_type(marshaler, type, &block)
+ yield marshaler.register_type(type)
+ if type.array?
+ yield marshaler.register_type(type.element_type)
+ type = type.element_type
end
- yield binding
+ type.each_member{ |name, type| traverse_type(marshaler, type, &block) } if type.structured?
end
end
end
View
93 actionwebservice/lib/action_web_service/protocol/abstract.rb
@@ -3,22 +3,11 @@ module Protocol # :nodoc:
class ProtocolError < ActionWebServiceError # :nodoc:
end
- class AbstractProtocol
- attr :marshaler
- attr :encoder
-
- def unmarshal_request(ap_request)
- end
-
- def marshal_response(method, return_value)
- body = method.encode_rpc_response(marshaler, encoder, return_value)
- Response.new(body, 'text/xml')
+ class AbstractProtocol # :nodoc:
+ def decode_action_pack_request(action_pack_request)
end
- def protocol_client(api, protocol_name, endpoint_uri, options)
- end
-
- def create_action_pack_request(service_name, public_method_name, raw_body, options={})
+ def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
klass = options[:request_class] || SimpleActionPackRequest
request = klass.new
request.request_parameters['action'] = service_name.to_s
@@ -27,50 +16,30 @@ def create_action_pack_request(service_name, public_method_name, raw_body, optio
request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
request
end
- end
- class SimpleActionPackRequest < ActionController::AbstractRequest
- def initialize
- @env = {}
- @qparams = {}
- @rparams = {}
- @cookies = {}
- reset_session
+ def decode_request(raw_request, service_name)
end
- def query_parameters
- @qparams
+ def encode_request(method_name, params, param_types)
end
- def request_parameters
- @rparams
+ def decode_response(raw_response)
end
- def env
- @env
- end
-
- def host
- ''
- end
-
- def cookies
- @cookies
+ def encode_response(method_name, return_value, return_type)
end
- def session
- @session
+ def protocol_client(api, protocol_name, endpoint_uri, options)
end
- def reset_session
- @session = {}
+ def register_api(api)
end
end
class Request # :nodoc:
attr :protocol
attr :method_name
- attr :method_params
+ attr_accessor :method_params
attr :service_name
attr_accessor :api
attr_accessor :api_method
@@ -88,10 +57,50 @@ def initialize(protocol, method_name, method_params, service_name, api=nil, api_
class Response # :nodoc:
attr :body
attr :content_type
+ attr :return_value
- def initialize(body, content_type)
+ def initialize(body, content_type, return_value)
@body = body
@content_type = content_type
+ @return_value = return_value
+ end
+ end
+
+ class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc:
+ def initialize
+ @env = {}
+ @qparams = {}
+ @rparams = {}
+ @cookies = {}
+ reset_session
+ end
+
+ def query_parameters
+ @qparams
+ end
+
+ def request_parameters
+ @rparams
+ end
+
+ def env
+ @env
+ end
+
+ def host
+ ''
+ end
+
+ def cookies
+ @cookies
+ end
+
+ def session
+ @session
+ end
+
+ def reset_session
+ @session = {}
end
end
end
View
4 actionwebservice/lib/action_web_service/protocol/discovery.rb
@@ -14,10 +14,10 @@ def register_protocol(klass)
module InstanceMethods # :nodoc:
private
- def discover_web_service_request(ap_request)
+ def discover_web_service_request(action_pack_request)
(self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
protocol = protocol.new
- request = protocol.unmarshal_request(ap_request)
+ request = protocol.decode_action_pack_request(action_pack_request)
return request unless request.nil?
end
nil
View
111 actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
@@ -1,3 +1,5 @@
+require 'action_web_service/protocol/soap_protocol/marshaler'
+
module ActionWebService # :nodoc:
module Protocol # :nodoc:
module Soap # :nodoc:
@@ -7,28 +9,105 @@ def self.included(base)
end
class SoapProtocol < AbstractProtocol # :nodoc:
- def initialize
- @encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
- @marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
+ def marshaler
+ @marshaler ||= SoapMarshaler.new
+ end
+
+ def decode_action_pack_request(action_pack_request)
+ return nil unless has_valid_soap_action?(action_pack_request)
+ service_name = action_pack_request.parameters['action']
+ decode_request(action_pack_request.raw_post, service_name)
+ end
+
+ def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
+ request = super
+ request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
+ request
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']
+ def decode_request(raw_request, service_name)
+ envelope = SOAP::Processor.unmarshal(raw_request)
+ unless envelope
+ raise ProtocolError, "Failed to parse SOAP request message"
+ end
+ request = envelope.body.request
+ method_name = request.elename.name
+ params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) }
Request.new(self, method_name, params, service_name)
end
+ def encode_request(method_name, params, param_types)
+ param_types.each{ |type| marshaler.register_type(type) } if param_types
+ qname = XSD::QName.new(marshaler.type_namespace, method_name)
+ param_def = []
+ i = 0
+ if param_types
+ params = params.map do |param|
+ param_type = param_types[i]
+ param_def << ['in', param_type.name, marshaler.lookup_type(param_type).mapping]
+ i += 1
+ [param_type.name, marshaler.ruby_to_soap(param)]
+ end
+ else
+ params = []
+ 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_response(raw_response)
+ envelope = SOAP::Processor.unmarshal(raw_response)
+ unless envelope
+ raise ProtocolError, "Failed to parse SOAP request message"
+ end
+ method_name = envelope.body.request.elename.name
+ return_value = envelope.body.response
+ return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil?
+ [method_name, return_value]
+ end
+
+ def encode_response(method_name, return_value, return_type)
+ if return_type
+ return_binding = marshaler.register_type(return_type)
+ marshaler.annotate_arrays(return_binding, return_value)
+ end
+ qname = XSD::QName.new(marshaler.type_namespace, method_name)
+ if return_value.nil?
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
+ else
+ if return_value.is_a?(Exception)
+ detail = SOAP::Mapping::SOAPException.new(return_value)
+ response = SOAP::SOAPFault.new(
+ SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']),
+ SOAP::SOAPString.new(return_value.to_s),
+ SOAP::SOAPString.new(self.class.name),
+ marshaler.ruby_to_soap(detail))
+ else
+ if return_type
+ param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]]
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
+ response.retval = marshaler.ruby_to_soap(return_value)
+ else
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
+ end
+ end
+ end
+ envelope = create_soap_envelope(response)
+ Response.new(SOAP::Processor.marshal(envelope), 'text/xml', return_value)
+ end
+
def protocol_client(api, protocol_name, endpoint_uri, options={})
return nil unless protocol_name == :soap
ActionWebService::Client::Soap.new(api, endpoint_uri, options)
end
- def create_action_pack_request(service_name, public_method_name, raw_body, options={})
- request = super
- request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
- request
+ def register_api(api)
+ api.api_methods.each do |name, method|
+ method.expects.each{ |type| marshaler.register_type(type) } if method.expects
+ method.returns.each{ |type| marshaler.register_type(type) } if method.returns
+ end
end
private
@@ -43,7 +122,13 @@ def has_valid_soap_action?(request)
return nil if soap_action.empty?
soap_action
end
- end
+
+ def create_soap_envelope(body)
+ header = SOAP::SOAPHeader.new
+ body = SOAP::SOAPBody.new(body)
+ SOAP::SOAPEnvelope.new(header, body)
+ end
+ end
end
end
end
View
197 actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb
@@ -0,0 +1,197 @@
+require 'soap/mapping'
+
+module ActionWebService
+ module Protocol
+ module Soap
+ class SoapMarshaler
+ attr :type_namespace
+ attr :registry
+
+ def initialize(type_namespace=nil)
+ @type_namespace = type_namespace || 'urn:ActionWebService'
+ @registry = SOAP::Mapping::Registry.new
+ @type2binding = {}
+ end
+
+ def soap_to_ruby(obj)
+ SOAP::Mapping.soap2obj(obj, @registry)
+ end
+
+ def ruby_to_soap(obj)
+ SOAP::Mapping.obj2soap(obj, @registry)
+ end
+
+ def register_type(type)
+ return @type2binding[type] if @type2binding.has_key?(type)
+
+ type_class = type.array?? type.element_type.type_class : type.type_class
+ type_type = type.array?? type.element_type : type
+ 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(self, qname, type_type, 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(self, qname, type_type, mapping)
+ end
+
+ array_binding = nil
+ if type.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.element_type.type_class.name) + 'Array')
+ array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding)
+ end
+
+ @type2binding[type] = array_binding ? array_binding : type_binding
+ @type2binding[type]
+ end
+ alias :lookup_type :register_type
+
+ def annotate_arrays(binding, value)
+ if binding.type.array?
+ mark_typed_array(value, binding.element_binding.qname)
+ if binding.element_binding.type.custom?
+ value.each do |element|
+ annotate_arrays(binding.element_binding, element)
+ end
+ end
+ elsif binding.type.structured?
+ binding.type.each_member do |name, type|
+ member_binding = register_type(type)
+ member_value = value.respond_to?('[]') ? value[name] : value.send(name)
+ annotate_arrays(member_binding, member_value) if type.custom?
+ end
+ end
+ end
+
+ private
+ def typed_struct_factory(type_class)
+ if Object.const_defined?('ActiveRecord')
+ if type_class.ancestors.include?(ActiveRecord::Base)
+ 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 mark_typed_array(array, qname)
+ (class << array; self; end).class_eval do
+ define_method(:arytype) do
+ qname
+ end
+ end
+ 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
+
+ def soap_type_name(type_name)
+ type_name.gsub(/::/, '..')
+ end
+ end
+
+ class SoapBinding
+ attr :qname
+ attr :type
+ attr :mapping
+ attr :element_binding
+
+ def initialize(marshaler, qname, type, mapping, element_binding=nil)
+ @marshaler = marshaler
+ @qname = qname
+ @type = type
+ @mapping = mapping
+ @element_binding = element_binding
+ end
+
+ def type_name
+ @type.custom? ? @qname.name : nil
+ end
+
+ def qualified_type_name(ns=nil)
+ if @type.custom?
+ "#{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
+
+ def eql?(other)
+ @qname == other.qname
+ end
+ alias :== :eql?
+
+ def hash
+ @qname.hash
+ 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.class.columns.each do |column|
+ key = column.name.to_s
+ value = obj.send(key)
+ 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
+end
View
55 actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
@@ -1,3 +1,5 @@
+require 'xmlrpc/marshal'
+
module ActionWebService # :nodoc:
module Protocol # :nodoc:
module XmlRpc # :nodoc:
@@ -6,22 +8,61 @@ def self.included(base)
end
class XmlRpcProtocol < AbstractProtocol # :nodoc:
- def initialize
- @encoder = WS::Encoding::XmlRpcEncoding.new
- @marshaler = WS::Marshaling::XmlRpcMarshaler.new
+ def decode_action_pack_request(action_pack_request)
+ service_name = action_pack_request.parameters['action']
+ decode_request(action_pack_request.raw_post, service_name)
end
- def unmarshal_request(ap_request)
- method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
- params = params.map{|x| @marshaler.unmarshal(x)}
- service_name = ap_request.parameters['action']
+ def decode_request(raw_request, service_name)
+ method_name, params = XMLRPC::Marshal.load_call(raw_request)
Request.new(self, method_name, params, service_name)
end
+ def encode_request(method_name, params, param_types)
+ if param_types
+ params = params.dup
+ param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) }
+ end
+ XMLRPC::Marshal.dump_call(method_name, *params)
+ end
+
+ def decode_response(raw_response)
+ [nil, XMLRPC::Marshal.load_response(raw_response)]
+ end
+
+ def encode_response(method_name, return_value, return_type)
+ return_value = true if return_value.nil?
+ if return_type
+ return_value = value_to_xmlrpc_wire_format(return_value, return_type)
+ end
+ raw_response = XMLRPC::Marshal.dump_response(return_value)
+ Response.new(raw_response, 'text/xml', return_value)
+ end
+
def protocol_client(api, protocol_name, endpoint_uri, options={})
return nil unless protocol_name == :xmlrpc
ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
end
+
+ def value_to_xmlrpc_wire_format(value, value_type)
+ if value_type.array?
+ value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) }
+ else
+ if value.is_a?(ActionWebService::Struct)
+ struct = {}
+ value.class.members.each do |name, type|
+ struct[name.to_s] = value_to_xmlrpc_wire_format(value[name], type)
+ end
+ struct
+ elsif value.is_a?(ActiveRecord::Base)
+ value.attributes.dup
+ elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
+ XMLRPC::FaultException.new(2, value.message)
+ else
+ value
+ end
+ end
+ end
end
end
end
View
43 actionwebservice/lib/action_web_service/scaffolding.rb
@@ -1,9 +1,13 @@
require 'ostruct'
require 'uri'
require 'benchmark'
+require 'pathname'
module ActionWebService
module Scaffolding # :nodoc:
+ class ScaffoldingError < ActionWebServiceError # :nodoc:
+ end
+
def self.append_features(base)
super
base.extend(ClassMethods)
@@ -63,17 +67,32 @@ def #{action_name}_submit
when :xmlrpc
protocol = Protocol::XmlRpc::XmlRpcProtocol.new
end
- cgi = @request.cgi
+ cgi = @request.respond_to?(:cgi) ? @request.cgi : nil
bm = Benchmark.measure do
- @method_request_xml = @scaffold_method.encode_rpc_call(protocol.marshaler, protocol.encoder, @params['method_params'].dup)
- @request = protocol.create_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
+ protocol.register_api(@scaffold_service.api)
+ params = @params['method_params'] ? @params['method_params'].dup : nil
+ params = @scaffold_method.cast_expects(params)
+ @method_request_xml = protocol.encode_request(@scaffold_method.public_name, params, @scaffold_method.expects)
+ @request = protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
dispatch_web_service_request
@method_response_xml = @response.body
- @method_return_value = protocol.marshaler.unmarshal(protocol.encoder.decode_rpc_response(@method_response_xml)[1]).value
+ method_name, obj = protocol.decode_response(@method_response_xml)
+ if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception)
+ raise obj.detail.cause
+ elsif obj.is_a?(XMLRPC::FaultException)
+ raise obj
+ end
+ @method_return_value = @scaffold_method.cast_returns(obj)
end
@method_elapsed = bm.real
add_instance_variables_to_assigns
- @response = ::ActionController::CgiResponse.new(cgi)
+ template = @response.template
+ if cgi
+ @response = ::ActionController::CgiResponse.new(cgi)
+ else
+ @response = ::ActionController::TestResponse.new
+ end
+ @response.template = template
@performed_render = false
render_#{action_name}_scaffold 'result'
end
@@ -99,20 +118,19 @@ def render_#{action_name}_scaffold(action)
end
def scaffold_path(template_name)
- File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
+ Pathname.new(File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml").realpath.to_s
end
END
end
end
module Helpers # :nodoc:
- def method_parameter_input_fields(method, param_spec, i)
- klass = method.param_class(param_spec)
- unless WS::BaseTypes.base_type?(klass)
- name = method.param_name(param_spec, i)
+ def method_parameter_input_fields(method, type)
+ name = type.name.to_s
+ type_name = type.type
+ unless type_name.is_a?(Symbol)
raise "Parameter #{name}: Structured/array types not supported in scaffolding input fields yet"
end
- type_name = method.param_type(param_spec)
field_name = "method_params[]"
case type_name
when :int
@@ -168,6 +186,9 @@ def initialize(name, real_service)
@name = name.to_s
@object = real_service
@api = @object.class.web_service_api
+ if @api.nil?
+ raise ScaffoldingError, "No web service API attached to #{object.class}"
+ end
@api_methods = {}
@api_methods_full = []
@api.api_methods.each do |name, method|
View
15 actionwebservice/lib/action_web_service/struct.rb
@@ -33,11 +33,20 @@ def [](name)
send(name.to_s)
end
+ # Iterates through each member
+ def each_pair(&block)
+ self.class.members.each do |name, type|
+ yield name, type
+ end
+ end
+
class << self
# 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 => WS::BaseTypes.canonical_param_type_class(type))
+ name = name.to_sym
+ type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0)
+ write_inheritable_hash("struct_members", name => type)
class_eval <<-END
def #{name}; @#{name}; end
def #{name}=(value); @#{name} = value; end
@@ -47,6 +56,10 @@ def #{name}=(value); @#{name} = value; end
def members # :nodoc:
read_inheritable_attribute("struct_members") || {}
end
+
+ def member_type(name) # :nodoc:
+ members[name.to_sym]
+ end
end
end
end
View
191 actionwebservice/lib/action_web_service/support/signature_types.rb
@@ -0,0 +1,191 @@
+module ActionWebService # :nodoc:
+ module SignatureTypes # :nodoc:
+ def canonical_signature(signature)
+ return nil if signature.nil?
+ i = -1
+ signature.map{ |spec| canonical_signature_entry(spec, i += 1) }
+ end
+
+ def canonical_signature_entry(spec, i)
+ name = "param#{i}"
+ if spec.is_a?(Hash)
+ name = spec.keys.first
+ spec = spec.values.first
+ type = spec
+ else
+ type = spec
+ end
+ if spec.is_a?(Array)
+ ArrayType.new(canonical_signature_entry(spec[0], 0), name)
+ else
+ type = canonical_type(type)
+ if type.is_a?(Symbol)
+ BaseType.new(type, name)
+ else
+ StructuredType.new(type, name)
+ end
+ end
+ end
+
+ def canonical_type(type)
+ type_name = symbol_name(type) || class_to_type_name(type)
+ type = type_name || type
+ return canonical_type_name(type) if type.is_a?(Symbol)
+ type
+ 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, :timestamp
+ :time
+ when :datetime
+ :datetime
+ when :date
+ :date
+ else
+ raise(TypeError, "#{name} is not a valid base type")
+ end
+ end
+
+ def canonical_type_class(type)
+ type = canonical_type(type)
+ type.is_a?(Symbol) ? type_name_to_class(type) : type
+ end
+
+ def symbol_name(name)
+ return name.to_sym if name.is_a?(Symbol) || name.is_a?(String)
+ nil
+ end
+
+ def class_to_type_name(klass)
+ klass = klass.class unless klass.is_a?(Class)
+ if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass)
+ :int
+ elsif klass == String
+ :string
+ elsif klass == TrueClass || klass == FalseClass
+ :bool
+ elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass)
+ :float
+ elsif klass == Time
+ :time
+ elsif klass == DateTime
+ :datetime
+ elsif klass == Date
+ :date
+ else
+ nil
+ end
+ end
+
+ 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
+ when :datetime
+ DateTime
+ else
+ nil
+ end
+ end
+
+ def derived_from?(ancestor, child)
+ child.ancestors.include?(ancestor)
+ end
+
+ module_function :type_name_to_class
+ module_function :class_to_type_name
+ module_function :symbol_name
+ module_function :canonical_type_class
+ module_function :canonical_type_name
+ module_function :canonical_type
+ module_function :canonical_signature_entry
+ module_function :canonical_signature
+ module_function :derived_from?
+ end
+
+ class BaseType # :nodoc:
+ include SignatureTypes
+
+ attr :type
+ attr :type_class
+ attr :name
+
+ def initialize(type, name)
+ @type = canonical_type(type)
+ @type_class = canonical_type_class(@type)
+ @name = name
+ end
+
+ def custom?
+ false
+ end
+
+ def array?
+ false
+ end
+
+ def structured?
+ false
+ end
+ end
+
+ class ArrayType < BaseType # :nodoc:
+ attr :element_type
+
+ def initialize(element_type, name)
+ super(Array, name)
+ @element_type = element_type
+ end
+
+ def custom?
+ true
+ end
+
+ def array?
+ true
+ end
+ end
+
+ class StructuredType < BaseType # :nodoc:
+ def each_member
+ if @type_class.respond_to?(:members)
+ @type_class.members.each do |name, type|
+ yield name, type
+ end
+ elsif @type_class.respond_to?(:columns)
+ i = 0
+ @type_class.columns.each do |column|
+ yield column.name, canonical_signature_entry(column.klass, i += 1)
+ end
+ end
+ end
+
+ def custom?
+ true
+ end
+
+ def structured?
+ true
+ end
+ end
+end
View
6 actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
@@ -5,10 +5,10 @@
<%= hidden_field_tag "method", @scaffold_method.public_name %>
<% i = 0 %>
-<% @scaffold_method.expects.each do |spec| %>
+<% @scaffold_method.expects.each do |type| %>
<p>
- <label for="method_params[]"><%= @scaffold_method.param_name(spec, i).camelize %></label><br />
- <%= method_parameter_input_fields(@scaffold_method, spec, i) %>
+ <label for="method_params[]"><%= type.name.to_s.camelize %></label><br />
+ <%= method_parameter_input_fields(@scaffold_method, type) %>
</p>
<% i += 1 %>
<% end %>
View
37 actionwebservice/lib/action_web_service/test_invoke.rb
@@ -21,7 +21,7 @@ def invoke_delegated(service_name, method_name, *args)
# invoke the specified layered API method on the correct service
def invoke_layered(service_name, method_name, *args)
- if protocol == :soap
+ if protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
raise "SOAP protocol support for :layered dispatching mode is not available"
end
prepare_request('api', service_name, method_name, *args)
@@ -37,10 +37,10 @@ def prepare_request(action, service_name, api_method_name, *args)
@request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
@request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args)
case protocol
- when :soap
+ when ActionWebService::Protocol::Soap::SoapProtocol
soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}"
@request.env['HTTP_SOAPACTION'] = soap_action
- when :xmlrpc
+ when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
@request.env.delete('HTTP_SOAPACTION')
end
end
@@ -52,19 +52,18 @@ def encode_rpc_call(service_name, api_method_name, *args)
when :delegated, :layered
api = @controller.web_service_object(service_name.to_sym).class.web_service_api
end
+ protocol.register_api(api)
method = api.api_methods[api_method_name.to_sym]
- method.register_types(marshaler)
- method.encode_rpc_call(marshaler, encoder, args.dup, :method_name => public_method_name(service_name, api_method_name))
+ protocol.encode_request(public_method_name(service_name, api_method_name), args.dup, method.expects)
end
def decode_rpc_response
- public_method_name, return_value = encoder.decode_rpc_response(@response.body)
- result = marshaler.unmarshal(return_value).value
+ public_method_name, return_value = protocol.decode_response(@response.body)
unless @return_exceptions
- exception = is_exception?(result)
+ exception = is_exception?(return_value)
raise exception if exception
end
- result
+ return_value
end
def public_method_name(service_name, api_method_name)
@@ -86,25 +85,7 @@ def service_api(service_name)
end
def protocol
- @protocol ||= :soap
- end
-
- def marshaler
- case protocol
- when :soap
- @soap_marshaler ||= WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
- when :xmlrpc
- @xmlrpc_marshaler ||= WS::Marshaling::XmlRpcMarshaler.new
- end
- end
-
- def encoder
- case protocol
- when :soap
- @soap_encoder ||= WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
- when :xmlrpc
- @xmlrpc_encoder ||= WS::Encoding::XmlRpcEncoding.new
- end
+ @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.new
end
def is_exception?(obj)
View
4 actionwebservice/lib/action_web_service/vendor/ws.rb
@@ -1,4 +0,0 @@
-require 'ws/common'
-require 'ws/types'
-require 'ws/marshaling'
-require 'ws/encoding'
View
8 actionwebservice/lib/action_web_service/vendor/ws/common.rb
@@ -1,8 +0,0 @@
-module WS
- class WSError < StandardError
- end
-
- def self.derived_from?(ancestor, child)
- child.ancestors.include?(ancestor)
- end
-end
View
3  actionwebservice/lib/action_web_service/vendor/ws/encoding.rb
@@ -1,3 +0,0 @@
-require 'ws/encoding/abstract'
-require 'ws/encoding/soap_rpc_encoding'
-require 'ws/encoding/xmlrpc_encoding'
View
26 actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb
@@ -1,26 +0,0 @@
-module WS
- module Encoding
- # Encoders operate on _foreign_ objects. That is, Ruby object
- # instances that are the _marshaling format specific_ representation
- # of objects. In other words, objects that have not yet been marshaled, but
- # are in protocol-specific form (such as an AST or DOM element), and not
- # native Ruby form.
- class AbstractEncoding
- def encode_rpc_call(method_name, params)
- raise NotImplementedError
- end
-
- def decode_rpc_call(obj)
- raise NotImplementedError
- end
-
- def encode_rpc_response(method_name, return_value)
- raise NotImplementedError
- end
-
- def decode_rpc_response(obj)
- raise NotImplementedError
- end
- end
- end
-end
View
90 actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb
@@ -1,90 +0,0 @@
-require 'soap/processor'
-require 'soap/mapping'
-require 'soap/rpc/element'
-
-module WS
- module Encoding
- class SoapRpcError < WSError
- end
-
- class SoapRpcEncoding < AbstractEncoding
- attr_accessor :method_namespace
-
- def initialize(method_namespace='')
- @method_namespace = method_namespace
- end
-
- def encode_rpc_call(method_name, foreign_params)
- qname = create_method_qname(method_name)
- param_def = []
- params = foreign_params.map do |p|
- param_def << ['in', p.param.info.name, p.param.info.data.mapping]
- [p.param.info.name, p.soap_object]
- end
- request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
- request.set_param(params)
- envelope = create_soap_envelope(request)
- SOAP::Processor.marshal(envelope)
- end
-
- def decode_rpc_call(obj)
- envelope = SOAP::Processor.unmarshal(obj)
- unless envelope
- raise(SoapRpcError, "Malformed SOAP request")
- end
- request = envelope.body.request
- method_name = request.elename.name
- params = request.collect do |key, value|
- info = ParamInfo.new(key, nil, nil)
- param = Param.new(nil, info)
- Marshaling::SoapForeignObject.new(param, request[key])
- end
- [method_name, params]
- end
-
- def encode_rpc_response(method_name, return_value)
- response = nil
- qname = create_method_qname(method_name)
- if return_value.nil?
- response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
- else
- param = return_value.param
- soap_object = return_value.soap_object
- param_def = [['retval', 'return', param.info.data.mapping]]
- if soap_object.is_a?(SOAP::SOAPFault)
- response = soap_object
- else
- response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
- response.retval = soap_object
- end
- end
- envelope = create_soap_envelope(response)
- SOAP::Processor.marshal(envelope)
- end
-
- def decode_rpc_response(obj)
- envelope = SOAP::Processor.unmarshal(obj)
- unless envelope
- raise(SoapRpcError, "Malformed SOAP response")
- end
- method_name = envelope.body.request.elename.name
- return_value = envelope.body.response
- info = ParamInfo.new('return', nil, nil)
- param = Param.new(nil, info)
- return_value = Marshaling::SoapForeignObject.new(param, return_value)
- [method_name, return_value]
- end
-
- private
- def create_soap_envelope(body)
- header = SOAP::SOAPHeader.new
- body = SOAP::SOAPBody.new(body)
- SOAP::SOAPEnvelope.new(header, body)
- end
-
- def create_method_qname(method_name)
- XSD::QName.new(@method_namespace, method_name)
- end
- end
- end
-end
View
44 actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
@@ -1,44 +0,0 @@
-require 'xmlrpc/marshal'
-
-module WS
- module Encoding
- 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)
- 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)
- [nil, XmlRpcDecodedParam.new('return', return_value)]
- end
- end
-
- class XmlRpcDecodedParam
- attr :param
-
- def initialize(name, value)
- info = ParamInfo.new(name, value.class)
- @param = Param.new(value, info)
- end
- end
- end
-end
View
3  actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb
@@ -1,3 +0,0 @@
-require 'ws/marshaling/abstract'
-require 'ws/marshaling/soap_marshaling'
-require 'ws/marshaling/xmlrpc_marshaling'
View
33 actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb
@@ -1,33 +0,0 @@
-module WS
- module Marshaling
- class AbstractMarshaler
- def initialize
- @base_type_caster = BaseTypeCaster.new
- end
-
- def marshal(param)
- raise NotImplementedError
- end
-
- def unmarshal(param)
- raise NotImplementedError
- end
-
- def register_type(type)
- nil
- end
- alias :lookup_type :register_type
-
- def cast_inbound_recursive(value, spec)
- raise NotImplementedError
- end
-
- def cast_outbound_recursive(value, spec)
- raise NotImplementedError
- end
-
- attr :base_type_caster
- protected :base_type_caster
- end
- end
-end
View
283 actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb
@@ -1,283 +0,0 @@
-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='')
- super()
- @type_namespace = type_namespace
- @registry = SOAP::Mapping::Registry.new
- @spec2binding = {}
- end
-
- def marshal(param)
- annotate_arrays(param.info.data, param.value)
- if param.value.is_a?(Exception)
- detail = SOAP::Mapping::SOAPException.new(param.value)
- soap_obj = SOAP::SOAPFault.new(
- SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, '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(self, soap_object.arytype, Array, mapping)
- else
- param.info.data = SoapBinding.new(self, soap_type, value.class, 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(self, qname, type_class, 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(self, qname, type_class, 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(self, qname, Array, array_mapping, type_binding)
- end
-
- @spec2binding[spec] = array_binding ? array_binding : type_binding
- @spec2binding[spec]
- end
- alias :lookup_type :register_type
-
- def cast_inbound_recursive(value, spec)
- binding = lookup_type(spec)
- if binding.is_custom_type?
- value
- else
- base_type_caster.cast(value, binding.type_class)
- end
- end
-
- def cast_outbound_recursive(value, spec)
- binding = lookup_type(spec)
- if binding.is_custom_type?
- value
- else
- base_type_caster.cast(value, binding.type_class)
- end
- end
-
- protected
- def annotate_arrays(binding, value)
- if binding.is_typed_array?
- mark_typed_array(value, binding.element_binding.qname)
- if binding.element_binding.is_custom_type?
- value.each do |element|
- annotate_arrays(register_type(element.class), element)
- end
- end
- elsif binding.is_typed_struct?
- if binding.type_class.respond_to?(:members)
- binding.type_class.members.each do |name, spec|
- member_binding = register_type(spec)
- member_value = value.respond_to?('[]') ? value[name] : value.send(name)
- if member_binding.is_custom_type?
- annotate_arrays(member_binding, member_value)
- end
- end
- end
- end
- end
-
- def mark_typed_array(array, qname)
- (class << array; self; end).class_eval do
- define_method(:arytype) do
- qname
- end
- end
- end
-
- def typed_struct_factory(type_class)
- if Object.const_defined?('ActiveRecord')
- if WS.derived_from?(ActiveRecord::Base, type_class)
- 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 :type_class
- attr :mapping
- attr :element_binding
-
- def initialize(marshaler, qname, type_class, mapping, element_binding=nil)
- @marshaler = marshaler
- @qname = qname
- @type_class = type_class
- @mapping = mapping
- @element_binding