From ec03a601811587dd4265667aadd50c101c23b116 Mon Sep 17 00:00:00 2001 From: Leon Breedt Date: Fri, 18 Feb 2005 23:55:29 +0000 Subject: rename entire package to Action Web Service git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@672 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionwebservice/lib/action_service.rb | 60 --- actionwebservice/lib/action_service/api.rb | 2 - .../lib/action_service/api/abstract.rb | 192 -------- .../lib/action_service/api/action_controller.rb | 92 ---- actionwebservice/lib/action_service/base.rb | 41 -- actionwebservice/lib/action_service/client.rb | 3 - actionwebservice/lib/action_service/client/base.rb | 35 -- actionwebservice/lib/action_service/client/soap.rb | 87 ---- .../lib/action_service/client/xmlrpc.rb | 76 ---- actionwebservice/lib/action_service/container.rb | 232 ---------- actionwebservice/lib/action_service/invocation.rb | 252 ----------- actionwebservice/lib/action_service/protocol.rb | 4 - .../lib/action_service/protocol/abstract.rb | 128 ------ .../lib/action_service/protocol/registry.rb | 55 --- .../lib/action_service/protocol/soap.rb | 484 --------------------- .../lib/action_service/protocol/xmlrpc.rb | 183 -------- actionwebservice/lib/action_service/router.rb | 2 - .../lib/action_service/router/action_controller.rb | 99 ----- actionwebservice/lib/action_service/router/wsdl.rb | 210 --------- actionwebservice/lib/action_service/struct.rb | 55 --- .../support/class_inheritable_options.rb | 26 -- .../lib/action_service/support/signature.rb | 100 ----- actionwebservice/lib/action_web_service.rb | 60 +++ actionwebservice/lib/action_web_service/api.rb | 2 + .../lib/action_web_service/api/abstract.rb | 192 ++++++++ .../action_web_service/api/action_controller.rb | 92 ++++ actionwebservice/lib/action_web_service/base.rb | 41 ++ actionwebservice/lib/action_web_service/client.rb | 3 + .../lib/action_web_service/client/base.rb | 35 ++ .../lib/action_web_service/client/soap.rb | 87 ++++ .../lib/action_web_service/client/xmlrpc.rb | 76 ++++ .../lib/action_web_service/container.rb | 232 ++++++++++ .../lib/action_web_service/invocation.rb | 252 +++++++++++ .../lib/action_web_service/protocol.rb | 4 + .../lib/action_web_service/protocol/abstract.rb | 128 ++++++ .../lib/action_web_service/protocol/registry.rb | 55 +++ .../lib/action_web_service/protocol/soap.rb | 484 +++++++++++++++++++++ .../lib/action_web_service/protocol/xmlrpc.rb | 183 ++++++++ actionwebservice/lib/action_web_service/router.rb | 2 + .../action_web_service/router/action_controller.rb | 99 +++++ .../lib/action_web_service/router/wsdl.rb | 210 +++++++++ actionwebservice/lib/action_web_service/struct.rb | 55 +++ .../support/class_inheritable_options.rb | 26 ++ .../lib/action_web_service/support/signature.rb | 100 +++++ 44 files changed, 2418 insertions(+), 2418 deletions(-) delete mode 100644 actionwebservice/lib/action_service.rb delete mode 100644 actionwebservice/lib/action_service/api.rb delete mode 100644 actionwebservice/lib/action_service/api/abstract.rb delete mode 100644 actionwebservice/lib/action_service/api/action_controller.rb delete mode 100644 actionwebservice/lib/action_service/base.rb delete mode 100644 actionwebservice/lib/action_service/client.rb delete mode 100644 actionwebservice/lib/action_service/client/base.rb delete mode 100644 actionwebservice/lib/action_service/client/soap.rb delete mode 100644 actionwebservice/lib/action_service/client/xmlrpc.rb delete mode 100644 actionwebservice/lib/action_service/container.rb delete mode 100644 actionwebservice/lib/action_service/invocation.rb delete mode 100644 actionwebservice/lib/action_service/protocol.rb delete mode 100644 actionwebservice/lib/action_service/protocol/abstract.rb delete mode 100644 actionwebservice/lib/action_service/protocol/registry.rb delete mode 100644 actionwebservice/lib/action_service/protocol/soap.rb delete mode 100644 actionwebservice/lib/action_service/protocol/xmlrpc.rb delete mode 100644 actionwebservice/lib/action_service/router.rb delete mode 100644 actionwebservice/lib/action_service/router/action_controller.rb delete mode 100644 actionwebservice/lib/action_service/router/wsdl.rb delete mode 100644 actionwebservice/lib/action_service/struct.rb delete mode 100644 actionwebservice/lib/action_service/support/class_inheritable_options.rb delete mode 100644 actionwebservice/lib/action_service/support/signature.rb create mode 100644 actionwebservice/lib/action_web_service.rb create mode 100644 actionwebservice/lib/action_web_service/api.rb create mode 100644 actionwebservice/lib/action_web_service/api/abstract.rb create mode 100644 actionwebservice/lib/action_web_service/api/action_controller.rb create mode 100644 actionwebservice/lib/action_web_service/base.rb create mode 100644 actionwebservice/lib/action_web_service/client.rb create mode 100644 actionwebservice/lib/action_web_service/client/base.rb create mode 100644 actionwebservice/lib/action_web_service/client/soap.rb create mode 100644 actionwebservice/lib/action_web_service/client/xmlrpc.rb create mode 100644 actionwebservice/lib/action_web_service/container.rb create mode 100644 actionwebservice/lib/action_web_service/invocation.rb create mode 100644 actionwebservice/lib/action_web_service/protocol.rb create mode 100644 actionwebservice/lib/action_web_service/protocol/abstract.rb create mode 100644 actionwebservice/lib/action_web_service/protocol/registry.rb create mode 100644 actionwebservice/lib/action_web_service/protocol/soap.rb create mode 100644 actionwebservice/lib/action_web_service/protocol/xmlrpc.rb create mode 100644 actionwebservice/lib/action_web_service/router.rb create mode 100644 actionwebservice/lib/action_web_service/router/action_controller.rb create mode 100644 actionwebservice/lib/action_web_service/router/wsdl.rb create mode 100644 actionwebservice/lib/action_web_service/struct.rb create mode 100644 actionwebservice/lib/action_web_service/support/class_inheritable_options.rb create mode 100644 actionwebservice/lib/action_web_service/support/signature.rb (limited to 'actionwebservice/lib') diff --git a/actionwebservice/lib/action_service.rb b/actionwebservice/lib/action_service.rb deleted file mode 100644 index 005e829e7b..0000000000 --- a/actionwebservice/lib/action_service.rb +++ /dev/null @@ -1,60 +0,0 @@ -#-- -# Copyright (C) 2005 Leon Breedt -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -begin - require 'active_support' - require 'action_controller' - require 'active_record' -rescue LoadError - require 'rubygems' - require_gem 'activesupport', '>= 0.9.0' - require_gem 'actionpack', '>= 1.4.0' - require_gem 'activerecord', '>= 1.6.0' -end - -$:.unshift(File.dirname(__FILE__)) - -require 'action_service/base' -require 'action_service/client' -require 'action_service/invocation' -require 'action_service/api' -require 'action_service/struct' -require 'action_service/container' -require 'action_service/protocol' -require 'action_service/router' - -ActionService::Base.class_eval do - include ActionService::API - include ActionService::Invocation -end - -ActionController::Base.class_eval do - include ActionService::Container - include ActionService::Protocol::Registry - include ActionService::Protocol::Soap - include ActionService::Protocol::XmlRpc - include ActionService::API - include ActionService::API::ActionController - include ActionService::Router::ActionController - include ActionService::Router::Wsdl -end diff --git a/actionwebservice/lib/action_service/api.rb b/actionwebservice/lib/action_service/api.rb deleted file mode 100644 index 61f36fff56..0000000000 --- a/actionwebservice/lib/action_service/api.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'action_service/api/abstract' -require 'action_service/api/action_controller' diff --git a/actionwebservice/lib/action_service/api/abstract.rb b/actionwebservice/lib/action_service/api/abstract.rb deleted file mode 100644 index aab37a285d..0000000000 --- a/actionwebservice/lib/action_service/api/abstract.rb +++ /dev/null @@ -1,192 +0,0 @@ -module ActionService # :nodoc: - module API # :nodoc: - class APIError < ActionService::ActionServiceError # :nodoc: - end - - def self.append_features(base) # :nodoc: - super - base.extend(ClassMethods) - end - - module ClassMethods - # Attaches ActionService 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 Service naming conventions. - # - # A controller with a class name of GoogleSearchController will - # implicitly load app/apis/google_search_api.rb, and expect the - # API definition class to be named GoogleSearchAPI or - # GoogleSearchApi. - # - # ==== Service class example - # - # class MyService < ActionService::Base - # web_service_api MyAPI - # end - # - # class MyAPI < ActionService::API::Base - # ... - # end - # - # ==== Controller class example - # - # class MyController < ActionController::Base - # web_service_api MyAPI - # end - # - # class MyAPI < ActionService::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. - # - # It is not intended to be instantiated. - # - # It is attached to web service implementation classes like - # ActionService::Base and ActionController::Base derivatives by using - # ClassMethods#web_service_api. - class Base - # Whether to transform the public API method names into camel-cased names - class_inheritable_option :inflect_names, true - - # If present, the name of a method to call when the remote caller - # tried to call a nonexistent method. Semantically equivalent to - # +method_missing+. - class_inheritable_option :default_api_method - - # Disallow instantiation - private_class_method :new, :allocate - - class << self - include ActionService::Signature - - # API methods have a +name+, which must be the Ruby method name to use when - # performing the invocation on the web service object. - # - # The signatures for the method input parameters and return value can - # by specified in +options+. - # - # A signature is an array of one or more parameter specifiers. - # A parameter specifier can be one of the following: - # - # * A symbol or string of representing one of the Action Service base types. - # See ActionService::Signature for a canonical list of the base types. - # * The Class object of the parameter type - # * A single-element Array containing one of the two preceding items. This - # will cause Action Service to treat the parameter at that position - # as an array containing only values of the given type. - # * A Hash containing as key the name of the parameter, and as value - # one of the three preceding items - # - # If no method input parameter or method return value signatures are given, - # the method is assumed to take no parameters and/or return no values of - # interest, and any values that are received by the server will be - # discarded and ignored. - # - # Valid options: - # [:expects] Signature for the method input parameters - # [:returns] Signature for the method return value - # [:expects_and_returns] Signature for both input parameters and return value - def api_method(name, options={}) - validate_options([:expects, :returns, :expects_and_returns], options.keys) - if options[:expects_and_returns] - expects = options[:expects_and_returns] - returns = options[:expects_and_returns] - else - expects = options[:expects] - returns = options[:returns] - end - expects = canonical_signature(expects) if expects - returns = canonical_signature(returns) if returns - if expects - expects.each do |param| - klass = signature_parameter_class(param) - klass = klass[0] if klass.is_a?(Array) - if klass.ancestors.include?(ActiveRecord::Base) - raise(ActionServiceError, "ActiveRecord model classes not allowed in :expects") - end - end - end - name = name.to_sym - public_name = public_api_method_name(name) - info = { :expects => expects, :returns => returns } - write_inheritable_hash("api_methods", name => info) - write_inheritable_hash("api_public_method_names", public_name => name) - end - - # Whether the given method name is a service method on this API - def has_api_method?(name) - api_methods.has_key?(name) - end - - # Whether the given public method name has a corresponding service method - # on this API - def has_public_api_method?(public_name) - api_public_method_names.has_key?(public_name) - end - - # The corresponding public method name for the given service method name - def public_api_method_name(name) - if inflect_names - name.to_s.camelize - else - name.to_s - end - end - - # The corresponding service method name for the given public method name - def api_method_name(public_name) - api_public_method_names[public_name] - end - - # A Hash containing all service methods on this API, and their - # associated metadata. - def api_methods - read_inheritable_attribute("api_methods") || {} - end - - private - def api_public_method_names - read_inheritable_attribute("api_public_method_names") || {} - end - - def validate_options(valid_option_keys, supplied_option_keys) - unknown_option_keys = supplied_option_keys - valid_option_keys - unless unknown_option_keys.empty? - raise(ActionServiceError, "Unknown options: #{unknown_option_keys}") - end - end - - end - end - end -end diff --git a/actionwebservice/lib/action_service/api/action_controller.rb b/actionwebservice/lib/action_service/api/action_controller.rb deleted file mode 100644 index d603f3a570..0000000000 --- a/actionwebservice/lib/action_service/api/action_controller.rb +++ /dev/null @@ -1,92 +0,0 @@ -module ActionService # :nodoc: - module API # :nodoc: - module ActionController # :nodoc: - def self.append_features(base) # :nodoc: - base.class_eval do - class << self - alias_method :inherited_without_api, :inherited - alias_method :web_service_api_without_require, :web_service_api - end - end - base.extend(ClassMethods) - end - - module ClassMethods - # Creates a client for accessing remote web services, using the - # given +protocol+ to communicate with the +endpoint_uri+. - # - # ==== Example - # - # class MyController < ActionController::Base - # web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger' - # end - # - # In this example, a protected method named blogger will - # now exist on the controller, and calling it will return the - # XML-RPC client object for working with that remote service. - # - # +options+ is the set of protocol client specific options, - # see a protocol client class for details. - # - # If your API definition does not exist on the load path with the - # correct rules for it to be found using +name+, you can pass through - # the API definition class in +options+, using a key of :api - def web_client_api(name, protocol, endpoint_uri, options={}) - unless method_defined?(name) - 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) - end - protected name - end - end - end - - def web_service_api(definition=nil) # :nodoc: - return web_service_api_without_require if definition.nil? - case definition - when String, Symbol - klass = require_web_service_api(definition) - else - klass = definition - end - web_service_api_without_require(klass) - end - - def require_web_service_api(name) # :nodoc: - case name - when String, Symbol - file_name = name.to_s.underscore + "_api" - class_name = file_name.camelize - class_names = [class_name, class_name.sub(/Api$/, 'API')] - begin - require_dependency(file_name) - rescue LoadError => load_error - requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1] - raise LoadError, requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}" - end - klass = nil - class_names.each do |name| - klass = name.constantize rescue nil - break unless klass.nil? - end - unless klass - raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found") - end - klass - else - raise(ArgumentError, "expected String or Symbol argument") - end - end - - private - def inherited(child) - inherited_without_api(child) - child.web_service_api(child.controller_path) - rescue Exception => e - end - end - end - end -end diff --git a/actionwebservice/lib/action_service/base.rb b/actionwebservice/lib/action_service/base.rb deleted file mode 100644 index 05fd2afd34..0000000000 --- a/actionwebservice/lib/action_service/base.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'action_service/support/class_inheritable_options' -require 'action_service/support/signature' - -module ActionService # :nodoc: - class ActionServiceError < StandardError # :nodoc: - end - - # An Action Service object implements a specified API. - # - # Used by controllers operating in _Delegated_ dispatching mode. - # - # ==== Example - # - # class PersonService < ActionService::Base - # web_service_api PersonAPI - # - # def find_person(criteria) - # Person.find_all [...] - # end - # - # def delete_person(id) - # Person.find_by_id(id).destroy - # end - # end - # - # class PersonAPI < ActionService::API::Base - # api_method :find_person, :expects => [SearchCriteria], :returns => [[Person]] - # api_method :delete_person, :expects => [:int] - # end - # - # class SearchCriteria < ActionStruct::Base - # member :firstname, :string - # member :lastname, :string - # member :email, :string - # end - class Base - # Whether to report exceptions back to the caller in the protocol's exception - # format - class_inheritable_option :web_service_exception_reporting, true - end -end diff --git a/actionwebservice/lib/action_service/client.rb b/actionwebservice/lib/action_service/client.rb deleted file mode 100644 index ce91529f20..0000000000 --- a/actionwebservice/lib/action_service/client.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'action_service/client/base' -require 'action_service/client/soap' -require 'action_service/client/xmlrpc' diff --git a/actionwebservice/lib/action_service/client/base.rb b/actionwebservice/lib/action_service/client/base.rb deleted file mode 100644 index 955887a4d8..0000000000 --- a/actionwebservice/lib/action_service/client/base.rb +++ /dev/null @@ -1,35 +0,0 @@ -module ActionService # :nodoc: - module Client # :nodoc: - class ClientError < StandardError # :nodoc: - end - - class Base # :nodoc: - def initialize(api, endpoint_uri) - @api = api - @endpoint_uri = endpoint_uri - end - - def method_missing(name, *args) # :nodoc: - call_name = method_name(name) - return super(name, *args) if call_name.nil? - 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 - end - end -end diff --git a/actionwebservice/lib/action_service/client/soap.rb b/actionwebservice/lib/action_service/client/soap.rb deleted file mode 100644 index c617f36589..0000000000 --- a/actionwebservice/lib/action_service/client/soap.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'soap/rpc/driver' -require 'uri' - -module ActionService # :nodoc: - module Client # :nodoc: - - # Implements SOAP client support (using RPC encoding for the messages). - # - # ==== Example Usage - # - # class PersonAPI < ActionService::API::Base - # api_method :find_all, :returns => [[Person]] - # end - # - # soap_client = ActionService::Client::Soap.new(PersonAPI, "http://...") - # persons = soap_client.find_all - # - class Soap < Base - - # Creates a new web service client using the SOAP RPC protocol. - # - # +api+ must be an ActionService::API::Base derivative, and - # +endpoint_uri+ must point at the relevant URL to which protocol requests - # will be sent with HTTP POST. - # - # Valid options: - # [:service_name] If the remote server has used a custom +wsdl_service_name+ - # option, you must specify it here - def initialize(api, endpoint_uri, options={}) - super(api, endpoint_uri) - @service_name = options[:service_name] || 'ActionService' - @namespace = "urn:#{@service_name}" - @mapper = ActionService::Protocol::Soap::SoapMapper.new(@namespace) - @protocol = ActionService::Protocol::Soap::SoapProtocol.new(@mapper) - @soap_action_base = options[:soap_action_base] - @soap_action_base ||= URI.parse(endpoint_uri).path - @driver = create_soap_rpc_driver(api, endpoint_uri) - end - - protected - def perform_invocation(method_name, args) - @driver.send(method_name, *args) - end - - def soap_action(method_name) - "#{@soap_action_base}/#{method_name}" - end - - private - def create_soap_rpc_driver(api, endpoint_uri) - @mapper.map_api(api) - driver = SoapDriver.new(endpoint_uri, nil) - driver.mapping_registry = @mapper.registry - api.api_methods.each do |name, info| - public_name = api.public_api_method_name(name) - qname = XSD::QName.new(@namespace, public_name) - action = soap_action(public_name) - expects = info[:expects] - returns = info[:returns] - param_def = [] - i = 1 - if expects - expects.each do |klass| - param_name = klass.is_a?(Hash) ? klass.keys[0] : "param#{i}" - mapping = @mapper.lookup(klass) - param_def << ['in', param_name, mapping.registry_mapping] - i += 1 - end - end - if returns - mapping = @mapper.lookup(returns[0]) - param_def << ['retval', 'return', mapping.registry_mapping] - end - driver.add_method(qname, action, name.to_s, param_def) - end - driver - end - - class SoapDriver < SOAP::RPC::Driver # :nodoc: - def add_method(qname, soapaction, name, param_def) - @proxy.add_rpc_method(qname, soapaction, name, param_def) - add_rpc_method_interface(name, param_def) - end - end - end - end -end diff --git a/actionwebservice/lib/action_service/client/xmlrpc.rb b/actionwebservice/lib/action_service/client/xmlrpc.rb deleted file mode 100644 index d0d007f871..0000000000 --- a/actionwebservice/lib/action_service/client/xmlrpc.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'uri' -require 'xmlrpc/client' - -module ActionService # :nodoc: - module Client # :nodoc: - - # Implements XML-RPC client support - # - # ==== Example Usage - # - # class BloggerAPI < ActionService::API::Base - # inflect_names false - # api_method :getRecentPosts, :returns => [[Blog::Post]] - # end - # - # blog = ActionService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger") - # posts = blog.getRecentPosts - class XmlRpc < Base - - # Creates a new web service client using the XML-RPC protocol. - # - # +api+ must be an ActionService::API::Base derivative, and - # +endpoint_uri+ must point at the relevant URL to which protocol requests - # will be sent with HTTP POST. - # - # Valid options: - # [:handler_name] If the remote server defines its services inside special - # handler (the Blogger API uses a "blogger" handler name for example), - # provide it here, or your method calls will fail - def initialize(api, endpoint_uri, options={}) - @api = api - @handler_name = options[:handler_name] - @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout]) - end - - protected - def perform_invocation(method_name, args) - args = transform_outgoing_method_params(method_name, args) - ok, return_value = @client.call2(public_name(method_name), *args) - return transform_return_value(method_name, return_value) if ok - raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}") - end - - 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") - 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], signature[i]) - end - end - params - end - - def transform_return_value(method_name, return_value) - info = @api.api_methods[method_name.to_sym] - return true unless signature = info[:returns] - signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature) - Protocol::XmlRpc::XmlRpcProtocol.xmlrpc_to_ruby(return_value, signature[0]) - end - - def public_name(method_name) - public_name = @api.public_api_method_name(method_name) - @handler_name ? "#{@handler_name}.#{public_name}" : public_name - end - end - end -end diff --git a/actionwebservice/lib/action_service/container.rb b/actionwebservice/lib/action_service/container.rb deleted file mode 100644 index 282e6ad928..0000000000 --- a/actionwebservice/lib/action_service/container.rb +++ /dev/null @@ -1,232 +0,0 @@ -module ActionService # :nodoc: - module Container # :nodoc: - class ContainerError < ActionService::ActionServiceError # :nodoc: - end - - def self.append_features(base) # :nodoc: - super - base.class_inheritable_option(:web_service_dispatching_mode, :direct) - base.class_inheritable_option(:web_service_exception_reporting, true) - base.extend(ClassMethods) - base.send(:include, ActionService::Container::InstanceMethods) - end - - module ClassMethods - # Declares a web service that will provides access to the API of the given - # +object+. +object+ must be an ActionService::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 - - private - def dispatch_web_service_request(protocol_request) - case web_service_dispatching_mode - when :direct - dispatch_direct_web_service_request(protocol_request) - when :delegated - dispatch_delegated_web_service_request(protocol_request) - else - raise(ContainerError, "unsupported dispatching mode :#{web_service_dispatching_mode}") - end - end - - def dispatch_direct_web_service_request(protocol_request) - public_method_name = protocol_request.public_method_name - api = self.class.web_service_api - method_name = api.api_method_name(public_method_name) - block = nil - expects = nil - if method_name - signature = api.api_methods[method_name] - expects = signature[:expects] - protocol_request.type = Protocol::CheckedMessage - protocol_request.signature = expects - protocol_request.return_signature = signature[:returns] - else - protocol_request.type = Protocol::UncheckedMessage - system_methods = self.class.read_inheritable_attribute('default_system_methods') || {} - protocol = protocol_request.protocol - block = system_methods[protocol.class] - unless block - method_name = api.default_api_method - unless method_name && respond_to?(method_name) - raise(ContainerError, "no such method ##{public_method_name}") - end - end - end - - @method_params = protocol_request.unmarshal - @params ||= {} - if expects - (1..@method_params.size).each do |i| - i -= 1 - if expects[i].is_a?(Hash) - @params[expects[i].keys.shift.to_s] = @method_params[i] - else - @params["param#{i}"] = @method_params[i] - end - end - end - - if respond_to?(:before_action) - @params['action'] = method_name.to_s - return protocol_request.marshal(nil) if before_action == false - end - - perform_invoke = lambda do - if block - block.call(public_method_name, self.class, *@method_params) - else - send(method_name) - end - end - try_default = true - result = nil - catch(:try_default) do - result = perform_invoke.call - try_default = false - end - if try_default - method_name = api.default_api_method - if method_name - protocol_request.type = Protocol::UncheckedMessage - else - raise(ContainerError, "no such method ##{public_method_name}") - end - result = perform_invoke.call - end - after_action if respond_to?(:after_action) - protocol_request.marshal(result) - end - - def dispatch_delegated_web_service_request(protocol_request) - web_service_name = protocol_request.web_service_name - service = web_service_object(web_service_name) - api = service.class.web_service_api - public_method_name = protocol_request.public_method_name - method_name = api.api_method_name(public_method_name) - - invocation = ActionService::Invocation::InvocationRequest.new( - ActionService::Invocation::ConcreteInvocation, - public_method_name, - method_name) - - if method_name - protocol_request.type = Protocol::CheckedMessage - signature = api.api_methods[method_name] - protocol_request.signature = signature[:expects] - protocol_request.return_signature = signature[:returns] - invocation.params = protocol_request.unmarshal - else - protocol_request.type = Protocol::UncheckedMessage - invocation.type = ActionService::Invocation::VirtualInvocation - system_methods = self.class.read_inheritable_attribute('default_system_methods') || {} - protocol = protocol_request.protocol - block = system_methods[protocol.class] - if block - invocation.block = block - invocation.block_params << service.class - else - method_name = api.default_api_method - if method_name && service.respond_to?(method_name) - invocation.params = protocol_request.unmarshal - invocation.method_name = method_name.to_sym - else - raise(ContainerError, "no such method /#{web_service_name}##{public_method_name}") - end - end - end - - canceled_reason = nil - canceled_block = lambda{|r| canceled_reason = r} - perform_invoke = lambda do - service.perform_invocation(invocation, &canceled_block) - end - try_default = true - result = nil - catch(:try_default) do - result = perform_invoke.call - try_default = false - end - if try_default - method_name = api.default_api_method - if method_name - protocol_request.type = Protocol::UncheckedMessage - invocation.params = protocol_request.unmarshal - invocation.method_name = method_name.to_sym - invocation.type = ActionService::Invocation::UnpublishedConcreteInvocation - else - raise(ContainerError, "no such method /#{web_service_name}##{public_method_name}") - end - result = perform_invoke.call - end - protocol_request.marshal(result) - end - end - end -end diff --git a/actionwebservice/lib/action_service/invocation.rb b/actionwebservice/lib/action_service/invocation.rb deleted file mode 100644 index f35ab76386..0000000000 --- a/actionwebservice/lib/action_service/invocation.rb +++ /dev/null @@ -1,252 +0,0 @@ -module ActionService # :nodoc: - module Invocation # :nodoc: - ConcreteInvocation = :concrete - VirtualInvocation = :virtual - UnpublishedConcreteInvocation = :unpublished_concrete - - class InvocationError < ActionService::ActionServiceError # :nodoc: - end - - def self.append_features(base) # :nodoc: - super - base.extend(ClassMethods) - base.send(:include, ActionService::Invocation::InstanceMethods) - end - - # Invocation interceptors provide a means to execute custom code before - # and after method invocations on ActionService::Base objects. - # - # When running in _Direct_ dispatching mode, ActionController filters - # should be used for this functionality instead. - # - # The semantics of invocation interceptors are the same as ActionController - # filters, and accept the same parameters and options. - # - # A _before_ interceptor can also cancel execution by returning +false+, - # or returning a [false, "cancel reason"] array if it wishes to supply - # a reason for canceling the request. - # - # === Example - # - # class CustomService < ActionService::Base - # before_invocation :intercept_add, :only => [:add] - # - # def add(a, b) - # a + b - # end - # - # private - # def intercept_add - # return [false, "permission denied"] # cancel it - # end - # end - # - # Options: - # [:except] A list of methods for which the interceptor will NOT be called - # [:only] A list of methods for which the interceptor WILL be called - module ClassMethods - # Appends the given +interceptors+ to be called - # _before_ method invocation. - def append_before_invocation(*interceptors, &block) - conditions = extract_conditions!(interceptors) - interceptors << block if block_given? - add_interception_conditions(interceptors, conditions) - append_interceptors_to_chain("before", interceptors) - end - - # Prepends the given +interceptors+ to be called - # _before_ method invocation. - def prepend_before_invocation(*interceptors, &block) - conditions = extract_conditions!(interceptors) - interceptors << block if block_given? - add_interception_conditions(interceptors, conditions) - prepend_interceptors_to_chain("before", interceptors) - end - - alias :before_invocation :append_before_invocation - - # Appends the given +interceptors+ to be called - # _after_ method invocation. - def append_after_invocation(*interceptors, &block) - conditions = extract_conditions!(interceptors) - interceptors << block if block_given? - add_interception_conditions(interceptors, conditions) - append_interceptors_to_chain("after", interceptors) - end - - # Prepends the given +interceptors+ to be called - # _after_ method invocation. - def prepend_after_invocation(*interceptors, &block) - conditions = extract_conditions!(interceptors) - interceptors << block if block_given? - add_interception_conditions(interceptors, conditions) - prepend_interceptors_to_chain("after", interceptors) - end - - alias :after_invocation :append_after_invocation - - def before_invocation_interceptors # :nodoc: - read_inheritable_attribute("before_invocation_interceptors") - end - - def after_invocation_interceptors # :nodoc: - read_inheritable_attribute("after_invocation_interceptors") - end - - def included_intercepted_methods # :nodoc: - read_inheritable_attribute("included_intercepted_methods") || {} - end - - def excluded_intercepted_methods # :nodoc: - read_inheritable_attribute("excluded_intercepted_methods") || {} - end - - private - def append_interceptors_to_chain(condition, interceptors) - write_inheritable_array("#{condition}_invocation_interceptors", interceptors) - end - - def prepend_interceptors_to_chain(condition, interceptors) - interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors") - write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors) - end - - def extract_conditions!(interceptors) - return nil unless interceptors.last.is_a? Hash - interceptors.pop - end - - def add_interception_conditions(interceptors, conditions) - return unless conditions - included, excluded = conditions[:only], conditions[:except] - write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included - write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded - end - - def condition_hash(interceptors, *methods) - interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})} - end - end - - module InstanceMethods # :nodoc: - def self.append_features(base) - super - base.class_eval do - alias_method :perform_invocation_without_interception, :perform_invocation - alias_method :perform_invocation, :perform_invocation_with_interception - end - end - - def perform_invocation_with_interception(invocation, &block) - return if before_invocation(invocation.method_name, invocation.params, &block) == false - result = perform_invocation_without_interception(invocation) - after_invocation(invocation.method_name, invocation.params, result) - result - end - - def perform_invocation(invocation) - if invocation.concrete? - unless self.respond_to?(invocation.method_name) && \ - self.class.web_service_api.has_api_method?(invocation.method_name) - raise InvocationError, "no such web service method '#{invocation.method_name}' on service object" - end - end - params = invocation.params - if invocation.concrete? || invocation.unpublished_concrete? - self.send(invocation.method_name, *params) - else - if invocation.block - params = invocation.block_params + params - invocation.block.call(invocation.public_method_name, *params) - else - self.send(invocation.method_name, *params) - end - end - end - - def before_invocation(name, args, &block) - call_interceptors(self.class.before_invocation_interceptors, [name, args], &block) - end - - def after_invocation(name, args, result) - call_interceptors(self.class.after_invocation_interceptors, [name, args, result]) - end - - private - - def call_interceptors(interceptors, interceptor_args, &block) - if interceptors and not interceptors.empty? - interceptors.each do |interceptor| - next if method_exempted?(interceptor, interceptor_args[0].to_s) - result = case - when interceptor.is_a?(Symbol) - self.send(interceptor, *interceptor_args) - when interceptor_block?(interceptor) - interceptor.call(self, *interceptor_args) - when interceptor_class?(interceptor) - interceptor.intercept(self, *interceptor_args) - else - raise( - InvocationError, - "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method" - ) - end - reason = nil - if result.is_a?(Array) - reason = result[1] if result[1] - result = result[0] - end - if result == false - block.call(reason) if block && reason - return false - end - end - end - end - - def interceptor_block?(interceptor) - interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1) - end - - def interceptor_class?(interceptor) - interceptor.respond_to?("intercept") - end - - def method_exempted?(interceptor, method_name) - case - when self.class.included_intercepted_methods[interceptor] - !self.class.included_intercepted_methods[interceptor].include?(method_name) - when self.class.excluded_intercepted_methods[interceptor] - self.class.excluded_intercepted_methods[interceptor].include?(method_name) - end - end - end - - class InvocationRequest # :nodoc: - attr_accessor :type - attr :public_method_name - attr_accessor :method_name - attr_accessor :params - attr_accessor :block - attr :block_params - - def initialize(type, public_method_name, method_name, params=nil) - @type = type - @public_method_name = public_method_name - @method_name = method_name - @params = params || [] - @block = nil - @block_params = [] - end - - def concrete? - @type == ConcreteInvocation ? true : false - end - - def unpublished_concrete? - @type == UnpublishedConcreteInvocation ? true : false - end - end - - end -end diff --git a/actionwebservice/lib/action_service/protocol.rb b/actionwebservice/lib/action_service/protocol.rb deleted file mode 100644 index 5e71b2bcfd..0000000000 --- a/actionwebservice/lib/action_service/protocol.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'action_service/protocol/abstract' -require 'action_service/protocol/registry' -require 'action_service/protocol/soap' -require 'action_service/protocol/xmlrpc' diff --git a/actionwebservice/lib/action_service/protocol/abstract.rb b/actionwebservice/lib/action_service/protocol/abstract.rb deleted file mode 100644 index bd02b6e829..0000000000 --- a/actionwebservice/lib/action_service/protocol/abstract.rb +++ /dev/null @@ -1,128 +0,0 @@ -module ActionService # :nodoc: - module Protocol # :nodoc: - CheckedMessage = :checked - UncheckedMessage = :unchecked - - class ProtocolError < ActionService::ActionServiceError # :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 - end - - class ProtocolRequest < AbstractProtocolMessage # :nodoc: - attr :protocol - attr :raw_body - - 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) - @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) - end - end - - class ProtocolResponse < AbstractProtocolMessage # :nodoc: - attr :protocol - attr :raw_body - - attr_accessor :content_type - - def initialize(protocol, raw_body, content_type, options={}) - super(options) - @protocol = protocol - @raw_body = raw_body - @content_type = content_type - end - end - end -end diff --git a/actionwebservice/lib/action_service/protocol/registry.rb b/actionwebservice/lib/action_service/protocol/registry.rb deleted file mode 100644 index e06361f916..0000000000 --- a/actionwebservice/lib/action_service/protocol/registry.rb +++ /dev/null @@ -1,55 +0,0 @@ -module ActionService # :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, ActionService::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_service/protocol/soap.rb b/actionwebservice/lib/action_service/protocol/soap.rb deleted file mode 100644 index 993e174e52..0000000000 --- a/actionwebservice/lib/action_service/protocol/soap.rb +++ /dev/null @@ -1,484 +0,0 @@ -require 'soap/processor' -require 'soap/mapping' -require 'soap/rpc/element' -require 'xsd/datatypes' -require 'xsd/ns' -require 'singleton' - -module ActionService # :nodoc: - module Protocol # :nodoc: - module Soap # :nodoc: - class ProtocolError < ActionService::ActionServiceError # :nodoc: - end - - def self.append_features(base) # :nodoc: - super - base.register_protocol(HeaderAndBody, SoapProtocol) - base.extend(ClassMethods) - base.wsdl_service_name('ActionService') - 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 - 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 - ActionService::Client::Soap.new(api, endpoint_uri, options) - end - - def unmarshal_request(protocol_request) - unmarshal = lambda do - envelope = SOAP::Processor.unmarshal(protocol_request.raw_body) - request = envelope.body.request - values = request.collect{|k, v| request[k]} - soap_to_ruby_array(values) - end - signature = protocol_request.signature - if signature - map_signature_types(signature) - values = unmarshal.call - signature = signature.map{|x|mapper.lookup(x).ruby_klass} - protocol_request.check_parameter_types(values, signature) - values - else - if protocol_request.checked? - [] - else - unmarshal.call - end - end - end - - def marshal_response(protocol_request, return_value) - marshal = lambda do |signature| - mapping = mapper.lookup(signature[0]) - return_value = fixup_array_types(mapping, return_value) - signature = signature.map{|x|mapper.lookup(x).ruby_klass} - protocol_request.check_parameter_types([return_value], signature) - param_def = [['retval', 'return', mapping.registry_mapping]] - [param_def, ruby_to_soap(return_value)] - end - signature = protocol_request.return_signature - param_def = nil - if signature - param_def, return_value = marshal.call(signature) - else - if protocol_request.checked? - param_def, return_value = nil, nil - else - param_def, return_value = marshal.call([return_value.class]) - end - end - qname = XSD::QName.new(mapper.custom_namespace, - protocol_request.public_method_name) - response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def) - response.retval = return_value unless return_value.nil? - ProtocolResponse.new(self, create_response(response), 'text/xml') - end - - def marshal_exception(exc) - ProtocolResponse.new(self, create_exception_response(exc), 'text/xml') - end - - private - def self.extract_soap_action(request) - 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!(/^"/, '') - soap_action.gsub!(/"$/, '') - soap_action.strip! - 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(ActionService::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_service/protocol/xmlrpc.rb b/actionwebservice/lib/action_service/protocol/xmlrpc.rb deleted file mode 100644 index 32b8e00327..0000000000 --- a/actionwebservice/lib/action_service/protocol/xmlrpc.rb +++ /dev/null @@ -1,183 +0,0 @@ -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 ActionService # :nodoc: - module Protocol # :nodoc: - module XmlRpc # :nodoc: - def self.append_features(base) # :nodoc: - super - base.register_protocol(BodyOnly, XmlRpcProtocol) - end - - 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 - ActionService::Client::XmlRpc.new(api, endpoint_uri, options) - end - - def initialize(container_class) - super(container_class) - container_class.write_inheritable_hash('default_system_methods', XmlRpcProtocol => method(:xmlrpc_default_system_handler)) - end - - def unmarshal_request(protocol_request) - values = protocol_request.options[:xmlrpc_values] - signature = protocol_request.signature - if signature - values = self.class.transform_incoming_method_params(self.class.transform_array_types(signature), values) - protocol_request.check_parameter_types(values, check_array_types(signature)) - values - else - protocol_request.checked? ? [] : values - end - end - - def marshal_response(protocol_request, return_value) - helper = XMLRPC::XmlRpcHelper.instance - signature = protocol_request.return_signature - if signature - protocol_request.check_parameter_types([return_value], check_array_types(signature)) - return_value = self.class.transform_return_value(self.class.transform_array_types(signature), return_value) - raw_response = helper.create_method_response(true, return_value) - else - # XML-RPC doesn't have the concept of a void method, nor does it - # support a nil return value, so return true if we would have returned - # nil - if protocol_request.checked? - raw_response = helper.create_method_response(true, true) - else - return_value = true if return_value.nil? - raw_response = helper.create_method_response(true, return_value) - end - end - ProtocolResponse.new(self, raw_response, 'text/xml') - end - - def marshal_exception(exception) - helper = XMLRPC::XmlRpcHelper.instance - exception = XMLRPC::FaultException.new(1, exception.message) - raw_response = helper.create_method_response(false, exception) - ProtocolResponse.new(self, raw_response, 'text/xml') - end - - class << self - def transform_incoming_method_params(signature, params) - (1..signature.size).each do |i| - i -= 1 - params[i] = xmlrpc_to_ruby(params[i], signature[i]) - end - params - end - - def transform_return_value(signature, return_value) - ruby_to_xmlrpc(return_value, signature[0]) - end - - def ruby_to_xmlrpc(param, param_class) - if param_class.is_a?(XmlRpcArray) - param.map{|p| ruby_to_xmlrpc(p, param_class.klass)} - elsif param_class.ancestors.include?(ActiveRecord::Base) - param.instance_variable_get('@attributes') - elsif param_class.ancestors.include?(ActionService::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?(ActionService::Struct) - unless param.is_a?(Hash) - raise(ProtocolError, "expected parameter to be a Hash") - end - new_param = param_class.new - param_class.members.each do |name, klass| - new_param.send('%s=' % name.to_s, param[name.to_s]) - end - new_param - else - param - end - end - - def transform_array_types(signature) - signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x} - end - end - - private - def xmlrpc_default_system_handler(name, service_class, *args) - case name - when 'system.listMethods' - methods = [] - api = service_class.web_service_api - api.api_methods.each do |name, info| - methods << api.public_api_method_name(name) - end - methods.sort - else - throw :try_default - end - end - - 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 -end diff --git a/actionwebservice/lib/action_service/router.rb b/actionwebservice/lib/action_service/router.rb deleted file mode 100644 index 16f0ae4463..0000000000 --- a/actionwebservice/lib/action_service/router.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'action_service/router/action_controller' -require 'action_service/router/wsdl' diff --git a/actionwebservice/lib/action_service/router/action_controller.rb b/actionwebservice/lib/action_service/router/action_controller.rb deleted file mode 100644 index ca9c94e35c..0000000000 --- a/actionwebservice/lib/action_service/router/action_controller.rb +++ /dev/null @@ -1,99 +0,0 @@ -module ActionService # :nodoc: - module Router # :nodoc: - module ActionController # :nodoc: - def self.append_features(base) # :nodoc: - base.add_web_service_api_callback do |container_class, api| - if container_class.web_service_dispatching_mode == :direct - container_class.class_eval <<-EOS - def api - process_action_service_request - end - EOS - 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} - process_action_service_request - end - EOS - end - end - base.send(:include, ActionService::Router::ActionController::InstanceMethods) - end - - module InstanceMethods # :nodoc: - private - def process_action_service_request - protocol_request = nil - begin - begin - protocol_request = probe_request_protocol(self.request) - rescue Exception => e - unless logger.nil? - logger.error "Invalid request: #{e.message}" - logger.error self.request.raw_post - end - raise - end - if protocol_request - log_request(protocol_request) - protocol_response = dispatch_web_service_request(protocol_request) - log_response(protocol_response) - response_options = { - :type => protocol_response.content_type, - :disposition => 'inline' - } - send_data(protocol_response.raw_body, response_options) - else - logger.fatal "Invalid Action Service service or method requested" unless logger.nil? - render_text 'Internal protocol error', "500 Invalid service/method" - end - rescue Exception => e - log_error e unless logger.nil? - exc_response = nil - case web_service_dispatching_mode - when :direct - if self.class.web_service_exception_reporting - exc_response = protocol_request.protocol.marshal_exception(e) - end - when :delegated - web_service = web_service_object(protocol_request.service_name) rescue nil - if web_service && web_service.class.web_service_exception_reporting - exc_response = protocol_request.protocol.marshal_exception(e) rescue nil - end - end - if exc_response - response_options = { - :type => exc_response.content_type, - :disposition => 'inline' - } - log_response exc_response - send_data(exc_response.raw_body, response_options) - else - render_text 'Internal protocol error', "500 #{e.message}" - end - end - end - - def log_request(protocol_request) - unless logger.nil? - web_service_name = protocol_request.web_service_name - method_name = protocol_request.public_method_name - logger.info "\nProcessing Action Service Request: #{web_service_name}##{method_name}" - logger.info "Raw Request Body:" - logger.info protocol_request.raw_body - end - end - - def log_response(protocol_response) - unless logger.nil? - logger.info "\nRaw Response Body:" - logger.info protocol_response.raw_body - end - end - end - end - end -end diff --git a/actionwebservice/lib/action_service/router/wsdl.rb b/actionwebservice/lib/action_service/router/wsdl.rb deleted file mode 100644 index c2f29da0b0..0000000000 --- a/actionwebservice/lib/action_service/router/wsdl.rb +++ /dev/null @@ -1,210 +0,0 @@ -module ActionService # :nodoc: - module Router # :nodoc: - module Wsdl # :nodoc: - def self.append_features(base) # :nodoc: - base.class_eval do - class << self - alias_method :inherited_without_wsdl, :inherited - end - end - base.extend(ClassMethods) - end - - module ClassMethods - def inherited(child) - inherited_without_wsdl(child) - child.send(:include, ActionService::Router::Wsdl::InstanceMethods) - end - end - - module InstanceMethods # :nodoc: - XsdNs = 'http://www.w3.org/2001/XMLSchema' - WsdlNs = 'http://schemas.xmlsoap.org/wsdl/' - SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/' - SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/' - SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http' - - def wsdl - 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') - rescue Exception => e - log_error e unless logger.nil? - render_text('', "500 #{e.message}") - end - when :post - render_text('', "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] - end - custom_types = mapper.custom_types - - - xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2) - xm.instruct! - - xm.definitions('name' => wsdl_service_name, - 'targetNamespace' => namespace, - 'xmlns:typens' => namespace, - 'xmlns:xsd' => XsdNs, - 'xmlns:soap' => SoapNs, - 'xmlns:soapenc' => SoapEncodingNs, - 'xmlns:wsdl' => WsdlNs, - 'xmlns' => WsdlNs) do - - # Custom type XSD generation - if custom_types.size > 0 - xm.types do - xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do - custom_types.each do |klass, mapping| - case - when mapping.is_a?(ActionService::Protocol::Soap::SoapArrayMapping) - xm.xsd(:complexType, 'name' => mapping.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 + '[]') - end - end - end - when mapping.is_a?(ActionService::Protocol::Soap::SoapMapping) - xm.xsd(:complexType, 'name' => mapping.type_name) do - xm.xsd(:all) do - mapping.each_attribute 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| - 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) - end - else - mapping_list = method_signature[:expects] - i = 1 - mapping_list.each do |mapping| - if mapping.is_a?(Hash) - param_name = mapping.keys.shift - mapping = mapping.values.shift - else - param_name = "param#{i}" - end - xm.part('name' => param_name, 'type' => mapping.qualified_type_name) - i += 1 - end if mapping_list - end - end - end - public_name = service_api.public_api_method_name(method_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) - xm.portType('name' => port_name) do - api_methods.each do |method_name, method_signature| - public_name = service_api.public_api_method_name(method_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) - 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) - 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 \ - + "/" + public_name - end - xm.soap(:operation, 'soapAction' => soap_action) - xm.input do - xm.soap(:body, - 'use' => 'encoded', - 'namespace' => namespace, - 'encodingStyle' => SoapEncodingNs) - end - xm.output do - xm.soap(:body, - 'use' => 'encoded', - 'namespace' => namespace, - 'encodingStyle' => SoapEncodingNs) - end - end - 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) - case web_service_dispatching_mode - when :direct - binding_target = 'api' - when :delegated - binding_target = service_name.to_s - end - xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do - xm.soap(:address, 'location' => "#{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" - end - - def binding_name_for(wsdl_service_name, service_name) - "#{wsdl_service_name}#{service_name.to_s.camelize}Binding" - end - end - end - end -end diff --git a/actionwebservice/lib/action_service/struct.rb b/actionwebservice/lib/action_service/struct.rb deleted file mode 100644 index 142127b052..0000000000 --- a/actionwebservice/lib/action_service/struct.rb +++ /dev/null @@ -1,55 +0,0 @@ -module ActionService - # To send structured types across the wire, derive from ActionService::Struct, - # and use +member+ to declare structure members. - # - # ActionService::Struct should be used in method signatures when you want to accept or return - # structured types that have no Active Record model class representations, or you don't - # want to expose your entire Active Record model to remote callers. - # - # === Example - # - # class Person < ActionService::Struct - # member :id, :int - # member :firstnames, [:string] - # member :lastname, :string - # member :email, :string - # end - # person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe') - # - # Active Record model classes are already implicitly supported for method - # return signatures. A structure containing its columns as members will be - # automatically generated if its present in a signature. - class Struct - - # If a Hash is given as argument to an ActionService::Struct constructor, - # it can contain initial values for the structure member. - def initialize(values={}) - if values.is_a?(Hash) - values.map{|k,v| send('%s=' % k.to_s, v)} - end - end - - # The member with the given name - def [](name) - send(name.to_s) - end - - class << self - include ActionService::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)) - class_eval <<-END - def #{name}; @#{name}; end - def #{name}=(value); @#{name} = value; end - END - end - - def members # :nodoc: - read_inheritable_attribute("struct_members") || {} - end - end - end -end diff --git a/actionwebservice/lib/action_service/support/class_inheritable_options.rb b/actionwebservice/lib/action_service/support/class_inheritable_options.rb deleted file mode 100644 index 4d1c2ed471..0000000000 --- a/actionwebservice/lib/action_service/support/class_inheritable_options.rb +++ /dev/null @@ -1,26 +0,0 @@ -class Class # :nodoc: - def class_inheritable_option(sym, default_value=nil) - write_inheritable_attribute sym, default_value - class_eval <<-EOS - def self.#{sym}(value=nil) - if !value.nil? - write_inheritable_attribute(:#{sym}, value) - else - read_inheritable_attribute(:#{sym}) - end - end - - def self.#{sym}=(value) - write_inheritable_attribute(:#{sym}, value) - end - - def #{sym} - self.class.#{sym} - end - - def #{sym}=(value) - self.class.#{sym} = value - end - EOS - end -end diff --git a/actionwebservice/lib/action_service/support/signature.rb b/actionwebservice/lib/action_service/support/signature.rb deleted file mode 100644 index 946118c523..0000000000 --- a/actionwebservice/lib/action_service/support/signature.rb +++ /dev/null @@ -1,100 +0,0 @@ -module ActionService # :nodoc: - # Action 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: - # - # [:int] any integer value - # [:float] any floating point value - # [:string] any string value - # [:bool] any boolean value - # [:time] any value containing both date and time - # [:date] 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 ActionService 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 ActionService base type") - end - end - end -end diff --git a/actionwebservice/lib/action_web_service.rb b/actionwebservice/lib/action_web_service.rb new file mode 100644 index 0000000000..a55afc2244 --- /dev/null +++ b/actionwebservice/lib/action_web_service.rb @@ -0,0 +1,60 @@ +#-- +# Copyright (C) 2005 Leon Breedt +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +begin + require 'active_support' + require 'action_controller' + require 'active_record' +rescue LoadError + require 'rubygems' + require_gem 'activesupport', '>= 0.9.0' + require_gem 'actionpack', '>= 1.4.0' + require_gem 'activerecord', '>= 1.6.0' +end + +$:.unshift(File.dirname(__FILE__)) + +require 'action_web_service/base' +require 'action_web_service/client' +require 'action_web_service/invocation' +require 'action_web_service/api' +require 'action_web_service/struct' +require 'action_web_service/container' +require 'action_web_service/protocol' +require 'action_web_service/router' + +ActionWebService::Base.class_eval do + include ActionWebService::API + include ActionWebService::Invocation +end + +ActionController::Base.class_eval do + include ActionWebService::Container + include ActionWebService::Protocol::Registry + include ActionWebService::Protocol::Soap + include ActionWebService::Protocol::XmlRpc + include ActionWebService::API + include ActionWebService::API::ActionController + include ActionWebService::Router::ActionController + include ActionWebService::Router::Wsdl +end diff --git a/actionwebservice/lib/action_web_service/api.rb b/actionwebservice/lib/action_web_service/api.rb new file mode 100644 index 0000000000..0c71de5654 --- /dev/null +++ b/actionwebservice/lib/action_web_service/api.rb @@ -0,0 +1,2 @@ +require 'action_web_service/api/abstract' +require 'action_web_service/api/action_controller' diff --git a/actionwebservice/lib/action_web_service/api/abstract.rb b/actionwebservice/lib/action_web_service/api/abstract.rb new file mode 100644 index 0000000000..0ce68d10f7 --- /dev/null +++ b/actionwebservice/lib/action_web_service/api/abstract.rb @@ -0,0 +1,192 @@ +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 app/apis/google_search_api.rb, and expect the + # API definition class to be named GoogleSearchAPI or + # GoogleSearchApi. + # + # ==== 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. + # + # It is not intended to be instantiated. + # + # It is attached to web service implementation classes like + # ActionWebService::Base and ActionController::Base derivatives by using + # ClassMethods#web_service_api. + class Base + # Whether to transform the public API method names into camel-cased names + class_inheritable_option :inflect_names, true + + # If present, the name of a method to call when the remote caller + # tried to call a nonexistent method. Semantically equivalent to + # +method_missing+. + class_inheritable_option :default_api_method + + # Disallow instantiation + 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. + # + # The signatures for the method input parameters and return value can + # by specified in +options+. + # + # A signature is an array of one or more parameter specifiers. + # A parameter specifier can be one of the following: + # + # * A symbol or string of representing one of the Action Web Service base types. + # See ActionWebService::Signature for a canonical list of the base types. + # * The Class object of the parameter type + # * A single-element Array containing one of the two preceding items. This + # will cause Action Web Service to treat the parameter at that position + # as an array containing only values of the given type. + # * A Hash containing as key the name of the parameter, and as value + # one of the three preceding items + # + # If no method input parameter or method return value signatures are given, + # the method is assumed to take no parameters and/or return no values of + # interest, and any values that are received by the server will be + # discarded and ignored. + # + # Valid options: + # [:expects] Signature for the method input parameters + # [:returns] Signature for the method return value + # [:expects_and_returns] Signature for both input parameters and return value + def api_method(name, options={}) + validate_options([:expects, :returns, :expects_and_returns], options.keys) + if options[:expects_and_returns] + expects = options[:expects_and_returns] + returns = options[:expects_and_returns] + else + expects = options[:expects] + returns = options[:returns] + end + expects = canonical_signature(expects) if expects + returns = canonical_signature(returns) if returns + if expects + expects.each do |param| + klass = signature_parameter_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") + end + end + end + name = name.to_sym + public_name = public_api_method_name(name) + info = { :expects => expects, :returns => returns } + write_inheritable_hash("api_methods", name => info) + write_inheritable_hash("api_public_method_names", public_name => name) + end + + # Whether the given method name is a service method on this API + def has_api_method?(name) + api_methods.has_key?(name) + end + + # Whether the given public method name has a corresponding service method + # on this API + def has_public_api_method?(public_name) + api_public_method_names.has_key?(public_name) + end + + # The corresponding public method name for the given service method name + def public_api_method_name(name) + if inflect_names + name.to_s.camelize + else + name.to_s + end + end + + # The corresponding service method name for the given public method name + def api_method_name(public_name) + api_public_method_names[public_name] + end + + # A Hash containing all service methods on this API, and their + # associated metadata. + def api_methods + read_inheritable_attribute("api_methods") || {} + end + + private + def api_public_method_names + read_inheritable_attribute("api_public_method_names") || {} + end + + def validate_options(valid_option_keys, supplied_option_keys) + unknown_option_keys = supplied_option_keys - valid_option_keys + unless unknown_option_keys.empty? + raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}") + end + end + + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/api/action_controller.rb b/actionwebservice/lib/action_web_service/api/action_controller.rb new file mode 100644 index 0000000000..604cbfe704 --- /dev/null +++ b/actionwebservice/lib/action_web_service/api/action_controller.rb @@ -0,0 +1,92 @@ +module ActionWebService # :nodoc: + module API # :nodoc: + module ActionController # :nodoc: + def self.append_features(base) # :nodoc: + base.class_eval do + class << self + alias_method :inherited_without_api, :inherited + alias_method :web_service_api_without_require, :web_service_api + end + end + base.extend(ClassMethods) + end + + module ClassMethods + # Creates a client for accessing remote web services, using the + # given +protocol+ to communicate with the +endpoint_uri+. + # + # ==== Example + # + # class MyController < ActionController::Base + # web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger' + # end + # + # In this example, a protected method named blogger will + # now exist on the controller, and calling it will return the + # XML-RPC client object for working with that remote service. + # + # +options+ is the set of protocol client specific options, + # see a protocol client class for details. + # + # If your API definition does not exist on the load path with the + # correct rules for it to be found using +name+, you can pass through + # the API definition class in +options+, using a key of :api + def web_client_api(name, protocol, endpoint_uri, options={}) + unless method_defined?(name) + 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) + end + protected name + end + end + end + + def web_service_api(definition=nil) # :nodoc: + return web_service_api_without_require if definition.nil? + case definition + when String, Symbol + klass = require_web_service_api(definition) + else + klass = definition + end + web_service_api_without_require(klass) + end + + def require_web_service_api(name) # :nodoc: + case name + when String, Symbol + file_name = name.to_s.underscore + "_api" + class_name = file_name.camelize + class_names = [class_name, class_name.sub(/Api$/, 'API')] + begin + require_dependency(file_name) + rescue LoadError => load_error + requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1] + raise LoadError, requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}" + end + klass = nil + class_names.each do |name| + klass = name.constantize rescue nil + break unless klass.nil? + end + unless klass + raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found") + end + klass + else + raise(ArgumentError, "expected String or Symbol argument") + end + end + + private + def inherited(child) + inherited_without_api(child) + child.web_service_api(child.controller_path) + rescue Exception => e + end + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/base.rb b/actionwebservice/lib/action_web_service/base.rb new file mode 100644 index 0000000000..42a514716a --- /dev/null +++ b/actionwebservice/lib/action_web_service/base.rb @@ -0,0 +1,41 @@ +require 'action_web_service/support/class_inheritable_options' +require 'action_web_service/support/signature' + +module ActionWebService # :nodoc: + class ActionWebServiceError < StandardError # :nodoc: + end + + # An Action Web Service object implements a specified API. + # + # Used by controllers operating in _Delegated_ dispatching mode. + # + # ==== Example + # + # class PersonService < ActionWebService::Base + # web_service_api PersonAPI + # + # def find_person(criteria) + # Person.find_all [...] + # end + # + # def delete_person(id) + # Person.find_by_id(id).destroy + # end + # end + # + # class PersonAPI < ActionWebService::API::Base + # api_method :find_person, :expects => [SearchCriteria], :returns => [[Person]] + # api_method :delete_person, :expects => [:int] + # end + # + # class SearchCriteria < ActionStruct::Base + # member :firstname, :string + # member :lastname, :string + # member :email, :string + # end + class Base + # Whether to report exceptions back to the caller in the protocol's exception + # format + class_inheritable_option :web_service_exception_reporting, true + end +end diff --git a/actionwebservice/lib/action_web_service/client.rb b/actionwebservice/lib/action_web_service/client.rb new file mode 100644 index 0000000000..77f934882c --- /dev/null +++ b/actionwebservice/lib/action_web_service/client.rb @@ -0,0 +1,3 @@ +require 'action_web_service/client/base' +require 'action_web_service/client/soap' +require 'action_web_service/client/xmlrpc' diff --git a/actionwebservice/lib/action_web_service/client/base.rb b/actionwebservice/lib/action_web_service/client/base.rb new file mode 100644 index 0000000000..d01cffcd56 --- /dev/null +++ b/actionwebservice/lib/action_web_service/client/base.rb @@ -0,0 +1,35 @@ +module ActionWebService # :nodoc: + module Client # :nodoc: + class ClientError < StandardError # :nodoc: + end + + class Base # :nodoc: + def initialize(api, endpoint_uri) + @api = api + @endpoint_uri = endpoint_uri + end + + def method_missing(name, *args) # :nodoc: + call_name = method_name(name) + return super(name, *args) if call_name.nil? + 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 + end + end +end diff --git a/actionwebservice/lib/action_web_service/client/soap.rb b/actionwebservice/lib/action_web_service/client/soap.rb new file mode 100644 index 0000000000..3557f88594 --- /dev/null +++ b/actionwebservice/lib/action_web_service/client/soap.rb @@ -0,0 +1,87 @@ +require 'soap/rpc/driver' +require 'uri' + +module ActionWebService # :nodoc: + module Client # :nodoc: + + # Implements SOAP client support (using RPC encoding for the messages). + # + # ==== Example Usage + # + # class PersonAPI < ActionWebService::API::Base + # api_method :find_all, :returns => [[Person]] + # end + # + # soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...") + # persons = soap_client.find_all + # + class Soap < Base + + # Creates a new web service client using the SOAP RPC protocol. + # + # +api+ must be an ActionWebService::API::Base derivative, and + # +endpoint_uri+ must point at the relevant URL to which protocol requests + # will be sent with HTTP POST. + # + # Valid options: + # [:service_name] If the remote server has used a custom +wsdl_service_name+ + # option, you must specify it here + 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) + @soap_action_base = options[:soap_action_base] + @soap_action_base ||= URI.parse(endpoint_uri).path + @driver = create_soap_rpc_driver(api, endpoint_uri) + end + + protected + def perform_invocation(method_name, args) + @driver.send(method_name, *args) + end + + def soap_action(method_name) + "#{@soap_action_base}/#{method_name}" + end + + private + def create_soap_rpc_driver(api, endpoint_uri) + @mapper.map_api(api) + driver = SoapDriver.new(endpoint_uri, nil) + driver.mapping_registry = @mapper.registry + api.api_methods.each do |name, info| + public_name = api.public_api_method_name(name) + qname = XSD::QName.new(@namespace, public_name) + action = soap_action(public_name) + expects = info[:expects] + returns = info[:returns] + param_def = [] + i = 1 + if expects + expects.each do |klass| + param_name = klass.is_a?(Hash) ? klass.keys[0] : "param#{i}" + mapping = @mapper.lookup(klass) + param_def << ['in', param_name, mapping.registry_mapping] + i += 1 + end + end + if returns + mapping = @mapper.lookup(returns[0]) + param_def << ['retval', 'return', mapping.registry_mapping] + end + driver.add_method(qname, action, name.to_s, param_def) + end + driver + end + + class SoapDriver < SOAP::RPC::Driver # :nodoc: + def add_method(qname, soapaction, name, param_def) + @proxy.add_rpc_method(qname, soapaction, name, param_def) + add_rpc_method_interface(name, param_def) + end + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/client/xmlrpc.rb b/actionwebservice/lib/action_web_service/client/xmlrpc.rb new file mode 100644 index 0000000000..df51230b81 --- /dev/null +++ b/actionwebservice/lib/action_web_service/client/xmlrpc.rb @@ -0,0 +1,76 @@ +require 'uri' +require 'xmlrpc/client' + +module ActionWebService # :nodoc: + module Client # :nodoc: + + # Implements XML-RPC client support + # + # ==== Example Usage + # + # class BloggerAPI < ActionWebService::API::Base + # inflect_names false + # api_method :getRecentPosts, :returns => [[Blog::Post]] + # end + # + # blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger") + # posts = blog.getRecentPosts + class XmlRpc < Base + + # Creates a new web service client using the XML-RPC protocol. + # + # +api+ must be an ActionWebService::API::Base derivative, and + # +endpoint_uri+ must point at the relevant URL to which protocol requests + # will be sent with HTTP POST. + # + # Valid options: + # [:handler_name] If the remote server defines its services inside special + # handler (the Blogger API uses a "blogger" handler name for example), + # provide it here, or your method calls will fail + def initialize(api, endpoint_uri, options={}) + @api = api + @handler_name = options[:handler_name] + @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout]) + end + + protected + def perform_invocation(method_name, args) + args = transform_outgoing_method_params(method_name, args) + ok, return_value = @client.call2(public_name(method_name), *args) + return transform_return_value(method_name, return_value) if ok + raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}") + end + + 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") + 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], signature[i]) + end + end + params + end + + def transform_return_value(method_name, return_value) + info = @api.api_methods[method_name.to_sym] + return true unless signature = info[:returns] + signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature) + Protocol::XmlRpc::XmlRpcProtocol.xmlrpc_to_ruby(return_value, signature[0]) + end + + def public_name(method_name) + public_name = @api.public_api_method_name(method_name) + @handler_name ? "#{@handler_name}.#{public_name}" : public_name + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/container.rb b/actionwebservice/lib/action_web_service/container.rb new file mode 100644 index 0000000000..6fa14def56 --- /dev/null +++ b/actionwebservice/lib/action_web_service/container.rb @@ -0,0 +1,232 @@ +module ActionWebService # :nodoc: + module Container # :nodoc: + class ContainerError < ActionWebService::ActionWebServiceError # :nodoc: + end + + def self.append_features(base) # :nodoc: + super + base.class_inheritable_option(:web_service_dispatching_mode, :direct) + base.class_inheritable_option(:web_service_exception_reporting, true) + 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 + + private + def dispatch_web_service_request(protocol_request) + case web_service_dispatching_mode + when :direct + dispatch_direct_web_service_request(protocol_request) + when :delegated + dispatch_delegated_web_service_request(protocol_request) + else + raise(ContainerError, "unsupported dispatching mode :#{web_service_dispatching_mode}") + end + end + + def dispatch_direct_web_service_request(protocol_request) + public_method_name = protocol_request.public_method_name + api = self.class.web_service_api + method_name = api.api_method_name(public_method_name) + block = nil + expects = nil + if method_name + signature = api.api_methods[method_name] + expects = signature[:expects] + protocol_request.type = Protocol::CheckedMessage + protocol_request.signature = expects + protocol_request.return_signature = signature[:returns] + else + protocol_request.type = Protocol::UncheckedMessage + system_methods = self.class.read_inheritable_attribute('default_system_methods') || {} + protocol = protocol_request.protocol + block = system_methods[protocol.class] + unless block + method_name = api.default_api_method + unless method_name && respond_to?(method_name) + raise(ContainerError, "no such method ##{public_method_name}") + end + end + end + + @method_params = protocol_request.unmarshal + @params ||= {} + if expects + (1..@method_params.size).each do |i| + i -= 1 + if expects[i].is_a?(Hash) + @params[expects[i].keys.shift.to_s] = @method_params[i] + else + @params["param#{i}"] = @method_params[i] + end + end + end + + if respond_to?(:before_action) + @params['action'] = method_name.to_s + return protocol_request.marshal(nil) if before_action == false + end + + perform_invoke = lambda do + if block + block.call(public_method_name, self.class, *@method_params) + else + send(method_name) + end + end + try_default = true + result = nil + catch(:try_default) do + result = perform_invoke.call + try_default = false + end + if try_default + method_name = api.default_api_method + if method_name + protocol_request.type = Protocol::UncheckedMessage + else + raise(ContainerError, "no such method ##{public_method_name}") + end + result = perform_invoke.call + end + after_action if respond_to?(:after_action) + protocol_request.marshal(result) + end + + def dispatch_delegated_web_service_request(protocol_request) + web_service_name = protocol_request.web_service_name + service = web_service_object(web_service_name) + api = service.class.web_service_api + public_method_name = protocol_request.public_method_name + method_name = api.api_method_name(public_method_name) + + invocation = ActionWebService::Invocation::InvocationRequest.new( + ActionWebService::Invocation::ConcreteInvocation, + public_method_name, + method_name) + + if method_name + protocol_request.type = Protocol::CheckedMessage + signature = api.api_methods[method_name] + protocol_request.signature = signature[:expects] + protocol_request.return_signature = signature[:returns] + invocation.params = protocol_request.unmarshal + else + protocol_request.type = Protocol::UncheckedMessage + invocation.type = ActionWebService::Invocation::VirtualInvocation + system_methods = self.class.read_inheritable_attribute('default_system_methods') || {} + protocol = protocol_request.protocol + block = system_methods[protocol.class] + if block + invocation.block = block + invocation.block_params << service.class + else + method_name = api.default_api_method + if method_name && service.respond_to?(method_name) + invocation.params = protocol_request.unmarshal + invocation.method_name = method_name.to_sym + else + raise(ContainerError, "no such method /#{web_service_name}##{public_method_name}") + end + end + end + + canceled_reason = nil + canceled_block = lambda{|r| canceled_reason = r} + perform_invoke = lambda do + service.perform_invocation(invocation, &canceled_block) + end + try_default = true + result = nil + catch(:try_default) do + result = perform_invoke.call + try_default = false + end + if try_default + method_name = api.default_api_method + if method_name + protocol_request.type = Protocol::UncheckedMessage + invocation.params = protocol_request.unmarshal + invocation.method_name = method_name.to_sym + invocation.type = ActionWebService::Invocation::UnpublishedConcreteInvocation + else + raise(ContainerError, "no such method /#{web_service_name}##{public_method_name}") + end + result = perform_invoke.call + end + protocol_request.marshal(result) + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/invocation.rb b/actionwebservice/lib/action_web_service/invocation.rb new file mode 100644 index 0000000000..64d3e8d524 --- /dev/null +++ b/actionwebservice/lib/action_web_service/invocation.rb @@ -0,0 +1,252 @@ +module ActionWebService # :nodoc: + module Invocation # :nodoc: + ConcreteInvocation = :concrete + VirtualInvocation = :virtual + UnpublishedConcreteInvocation = :unpublished_concrete + + class InvocationError < ActionWebService::ActionWebServiceError # :nodoc: + end + + def self.append_features(base) # :nodoc: + super + base.extend(ClassMethods) + base.send(:include, ActionWebService::Invocation::InstanceMethods) + end + + # Invocation interceptors provide a means to execute custom code before + # and after method invocations on ActionWebService::Base objects. + # + # When running in _Direct_ dispatching mode, ActionController filters + # should be used for this functionality instead. + # + # The semantics of invocation interceptors are the same as ActionController + # filters, and accept the same parameters and options. + # + # A _before_ interceptor can also cancel execution by returning +false+, + # or returning a [false, "cancel reason"] array if it wishes to supply + # a reason for canceling the request. + # + # === Example + # + # class CustomService < ActionWebService::Base + # before_invocation :intercept_add, :only => [:add] + # + # def add(a, b) + # a + b + # end + # + # private + # def intercept_add + # return [false, "permission denied"] # cancel it + # end + # end + # + # Options: + # [:except] A list of methods for which the interceptor will NOT be called + # [:only] A list of methods for which the interceptor WILL be called + module ClassMethods + # Appends the given +interceptors+ to be called + # _before_ method invocation. + def append_before_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + append_interceptors_to_chain("before", interceptors) + end + + # Prepends the given +interceptors+ to be called + # _before_ method invocation. + def prepend_before_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + prepend_interceptors_to_chain("before", interceptors) + end + + alias :before_invocation :append_before_invocation + + # Appends the given +interceptors+ to be called + # _after_ method invocation. + def append_after_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + append_interceptors_to_chain("after", interceptors) + end + + # Prepends the given +interceptors+ to be called + # _after_ method invocation. + def prepend_after_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + prepend_interceptors_to_chain("after", interceptors) + end + + alias :after_invocation :append_after_invocation + + def before_invocation_interceptors # :nodoc: + read_inheritable_attribute("before_invocation_interceptors") + end + + def after_invocation_interceptors # :nodoc: + read_inheritable_attribute("after_invocation_interceptors") + end + + def included_intercepted_methods # :nodoc: + read_inheritable_attribute("included_intercepted_methods") || {} + end + + def excluded_intercepted_methods # :nodoc: + read_inheritable_attribute("excluded_intercepted_methods") || {} + end + + private + def append_interceptors_to_chain(condition, interceptors) + write_inheritable_array("#{condition}_invocation_interceptors", interceptors) + end + + def prepend_interceptors_to_chain(condition, interceptors) + interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors") + write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors) + end + + def extract_conditions!(interceptors) + return nil unless interceptors.last.is_a? Hash + interceptors.pop + end + + def add_interception_conditions(interceptors, conditions) + return unless conditions + included, excluded = conditions[:only], conditions[:except] + write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included + write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded + end + + def condition_hash(interceptors, *methods) + interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})} + end + end + + module InstanceMethods # :nodoc: + def self.append_features(base) + super + base.class_eval do + alias_method :perform_invocation_without_interception, :perform_invocation + alias_method :perform_invocation, :perform_invocation_with_interception + end + end + + def perform_invocation_with_interception(invocation, &block) + return if before_invocation(invocation.method_name, invocation.params, &block) == false + result = perform_invocation_without_interception(invocation) + after_invocation(invocation.method_name, invocation.params, result) + result + end + + def perform_invocation(invocation) + if invocation.concrete? + unless self.respond_to?(invocation.method_name) && \ + self.class.web_service_api.has_api_method?(invocation.method_name) + raise InvocationError, "no such web service method '#{invocation.method_name}' on service object" + end + end + params = invocation.params + if invocation.concrete? || invocation.unpublished_concrete? + self.send(invocation.method_name, *params) + else + if invocation.block + params = invocation.block_params + params + invocation.block.call(invocation.public_method_name, *params) + else + self.send(invocation.method_name, *params) + end + end + end + + def before_invocation(name, args, &block) + call_interceptors(self.class.before_invocation_interceptors, [name, args], &block) + end + + def after_invocation(name, args, result) + call_interceptors(self.class.after_invocation_interceptors, [name, args, result]) + end + + private + + def call_interceptors(interceptors, interceptor_args, &block) + if interceptors and not interceptors.empty? + interceptors.each do |interceptor| + next if method_exempted?(interceptor, interceptor_args[0].to_s) + result = case + when interceptor.is_a?(Symbol) + self.send(interceptor, *interceptor_args) + when interceptor_block?(interceptor) + interceptor.call(self, *interceptor_args) + when interceptor_class?(interceptor) + interceptor.intercept(self, *interceptor_args) + else + raise( + InvocationError, + "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method" + ) + end + reason = nil + if result.is_a?(Array) + reason = result[1] if result[1] + result = result[0] + end + if result == false + block.call(reason) if block && reason + return false + end + end + end + end + + def interceptor_block?(interceptor) + interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1) + end + + def interceptor_class?(interceptor) + interceptor.respond_to?("intercept") + end + + def method_exempted?(interceptor, method_name) + case + when self.class.included_intercepted_methods[interceptor] + !self.class.included_intercepted_methods[interceptor].include?(method_name) + when self.class.excluded_intercepted_methods[interceptor] + self.class.excluded_intercepted_methods[interceptor].include?(method_name) + end + end + end + + class InvocationRequest # :nodoc: + attr_accessor :type + attr :public_method_name + attr_accessor :method_name + attr_accessor :params + attr_accessor :block + attr :block_params + + def initialize(type, public_method_name, method_name, params=nil) + @type = type + @public_method_name = public_method_name + @method_name = method_name + @params = params || [] + @block = nil + @block_params = [] + end + + def concrete? + @type == ConcreteInvocation ? true : false + end + + def unpublished_concrete? + @type == UnpublishedConcreteInvocation ? true : false + end + end + + end +end diff --git a/actionwebservice/lib/action_web_service/protocol.rb b/actionwebservice/lib/action_web_service/protocol.rb new file mode 100644 index 0000000000..733787136a --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol.rb @@ -0,0 +1,4 @@ +require 'action_web_service/protocol/abstract' +require 'action_web_service/protocol/registry' +require 'action_web_service/protocol/soap' +require 'action_web_service/protocol/xmlrpc' diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb new file mode 100644 index 0000000000..9199dfe33f --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -0,0 +1,128 @@ +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 + end + + class ProtocolRequest < AbstractProtocolMessage # :nodoc: + attr :protocol + attr :raw_body + + 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) + @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) + end + end + + class ProtocolResponse < AbstractProtocolMessage # :nodoc: + attr :protocol + attr :raw_body + + attr_accessor :content_type + + def initialize(protocol, raw_body, content_type, options={}) + super(options) + @protocol = protocol + @raw_body = raw_body + @content_type = content_type + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/protocol/registry.rb b/actionwebservice/lib/action_web_service/protocol/registry.rb new file mode 100644 index 0000000000..0173673556 --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/registry.rb @@ -0,0 +1,55 @@ +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.rb b/actionwebservice/lib/action_web_service/protocol/soap.rb new file mode 100644 index 0000000000..3c527fea93 --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/soap.rb @@ -0,0 +1,484 @@ +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 + 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 + else + if protocol_request.checked? + [] + else + unmarshal.call + end + end + end + + def marshal_response(protocol_request, return_value) + marshal = lambda do |signature| + mapping = mapper.lookup(signature[0]) + return_value = fixup_array_types(mapping, return_value) + signature = signature.map{|x|mapper.lookup(x).ruby_klass} + protocol_request.check_parameter_types([return_value], signature) + param_def = [['retval', 'return', mapping.registry_mapping]] + [param_def, ruby_to_soap(return_value)] + end + signature = protocol_request.return_signature + param_def = nil + if signature + param_def, return_value = marshal.call(signature) + else + if protocol_request.checked? + param_def, return_value = nil, nil + else + param_def, return_value = marshal.call([return_value.class]) + end + end + qname = XSD::QName.new(mapper.custom_namespace, + protocol_request.public_method_name) + response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def) + response.retval = return_value unless return_value.nil? + ProtocolResponse.new(self, create_response(response), 'text/xml') + end + + def marshal_exception(exc) + ProtocolResponse.new(self, create_exception_response(exc), 'text/xml') + end + + private + def self.extract_soap_action(request) + 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!(/^"/, '') + soap_action.gsub!(/"$/, '') + soap_action.strip! + 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.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc.rb new file mode 100644 index 0000000000..414bcfdbf7 --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc.rb @@ -0,0 +1,183 @@ +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) + end + + 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) + end + + def initialize(container_class) + super(container_class) + container_class.write_inheritable_hash('default_system_methods', XmlRpcProtocol => method(:xmlrpc_default_system_handler)) + end + + def unmarshal_request(protocol_request) + values = protocol_request.options[:xmlrpc_values] + signature = protocol_request.signature + if signature + values = self.class.transform_incoming_method_params(self.class.transform_array_types(signature), values) + protocol_request.check_parameter_types(values, check_array_types(signature)) + values + else + protocol_request.checked? ? [] : values + end + end + + def marshal_response(protocol_request, return_value) + helper = XMLRPC::XmlRpcHelper.instance + signature = protocol_request.return_signature + if signature + protocol_request.check_parameter_types([return_value], check_array_types(signature)) + return_value = self.class.transform_return_value(self.class.transform_array_types(signature), return_value) + raw_response = helper.create_method_response(true, return_value) + else + # XML-RPC doesn't have the concept of a void method, nor does it + # support a nil return value, so return true if we would have returned + # nil + if protocol_request.checked? + raw_response = helper.create_method_response(true, true) + else + return_value = true if return_value.nil? + raw_response = helper.create_method_response(true, return_value) + end + end + ProtocolResponse.new(self, raw_response, 'text/xml') + end + + def marshal_exception(exception) + helper = XMLRPC::XmlRpcHelper.instance + exception = XMLRPC::FaultException.new(1, exception.message) + raw_response = helper.create_method_response(false, exception) + ProtocolResponse.new(self, raw_response, 'text/xml') + end + + class << self + def transform_incoming_method_params(signature, params) + (1..signature.size).each do |i| + i -= 1 + params[i] = xmlrpc_to_ruby(params[i], signature[i]) + end + params + end + + def transform_return_value(signature, return_value) + ruby_to_xmlrpc(return_value, signature[0]) + end + + def ruby_to_xmlrpc(param, param_class) + if param_class.is_a?(XmlRpcArray) + param.map{|p| ruby_to_xmlrpc(p, param_class.klass)} + elsif param_class.ancestors.include?(ActiveRecord::Base) + param.instance_variable_get('@attributes') + elsif param_class.ancestors.include?(ActionWebService::Struct) + struct = {} + param_class.members.each do |name, klass| + value = param.send(name) + next if value.nil? + struct[name.to_s] = value + end + struct + else + param + end + end + + def xmlrpc_to_ruby(param, param_class) + if param_class.is_a?(XmlRpcArray) + param.map{|p| xmlrpc_to_ruby(p, param_class.klass)} + elsif param_class.ancestors.include?(ActiveRecord::Base) + raise(ProtocolError, "incoming ActiveRecord::Base types are not allowed") + elsif param_class.ancestors.include?(ActionWebService::Struct) + unless param.is_a?(Hash) + raise(ProtocolError, "expected parameter to be a Hash") + end + new_param = param_class.new + param_class.members.each do |name, klass| + new_param.send('%s=' % name.to_s, param[name.to_s]) + end + new_param + else + param + end + end + + def transform_array_types(signature) + signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x} + end + end + + private + def xmlrpc_default_system_handler(name, service_class, *args) + case name + when 'system.listMethods' + methods = [] + api = service_class.web_service_api + api.api_methods.each do |name, info| + methods << api.public_api_method_name(name) + end + methods.sort + else + throw :try_default + end + end + + 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 +end diff --git a/actionwebservice/lib/action_web_service/router.rb b/actionwebservice/lib/action_web_service/router.rb new file mode 100644 index 0000000000..4bfb0bc8b7 --- /dev/null +++ b/actionwebservice/lib/action_web_service/router.rb @@ -0,0 +1,2 @@ +require 'action_web_service/router/action_controller' +require 'action_web_service/router/wsdl' diff --git a/actionwebservice/lib/action_web_service/router/action_controller.rb b/actionwebservice/lib/action_web_service/router/action_controller.rb new file mode 100644 index 0000000000..591fe4e232 --- /dev/null +++ b/actionwebservice/lib/action_web_service/router/action_controller.rb @@ -0,0 +1,99 @@ +module ActionWebService # :nodoc: + module Router # :nodoc: + module ActionController # :nodoc: + def self.append_features(base) # :nodoc: + base.add_web_service_api_callback do |container_class, api| + if container_class.web_service_dispatching_mode == :direct + container_class.class_eval <<-EOS + def api + process_action_service_request + end + EOS + 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} + process_action_service_request + end + EOS + end + end + base.send(:include, ActionWebService::Router::ActionController::InstanceMethods) + end + + module InstanceMethods # :nodoc: + private + def process_action_service_request + protocol_request = nil + begin + begin + protocol_request = probe_request_protocol(self.request) + rescue Exception => e + unless logger.nil? + logger.error "Invalid request: #{e.message}" + logger.error self.request.raw_post + end + raise + end + if protocol_request + log_request(protocol_request) + protocol_response = dispatch_web_service_request(protocol_request) + log_response(protocol_response) + response_options = { + :type => protocol_response.content_type, + :disposition => 'inline' + } + send_data(protocol_response.raw_body, response_options) + else + logger.fatal "Invalid Action Web Service service or method requested" unless logger.nil? + render_text 'Internal protocol error', "500 Invalid service/method" + end + rescue Exception => e + log_error e unless logger.nil? + exc_response = nil + case web_service_dispatching_mode + when :direct + if self.class.web_service_exception_reporting + exc_response = protocol_request.protocol.marshal_exception(e) + end + when :delegated + web_service = web_service_object(protocol_request.service_name) rescue nil + if web_service && web_service.class.web_service_exception_reporting + exc_response = protocol_request.protocol.marshal_exception(e) rescue nil + end + end + if exc_response + response_options = { + :type => exc_response.content_type, + :disposition => 'inline' + } + log_response exc_response + send_data(exc_response.raw_body, response_options) + else + render_text 'Internal protocol error', "500 #{e.message}" + end + end + end + + def log_request(protocol_request) + unless logger.nil? + web_service_name = protocol_request.web_service_name + method_name = protocol_request.public_method_name + logger.info "\nProcessing Action Web Service Request: #{web_service_name}##{method_name}" + logger.info "Raw Request Body:" + logger.info protocol_request.raw_body + end + end + + def log_response(protocol_response) + unless logger.nil? + logger.info "\nRaw Response Body:" + logger.info protocol_response.raw_body + end + end + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/router/wsdl.rb b/actionwebservice/lib/action_web_service/router/wsdl.rb new file mode 100644 index 0000000000..6963334818 --- /dev/null +++ b/actionwebservice/lib/action_web_service/router/wsdl.rb @@ -0,0 +1,210 @@ +module ActionWebService # :nodoc: + module Router # :nodoc: + module Wsdl # :nodoc: + def self.append_features(base) # :nodoc: + base.class_eval do + class << self + alias_method :inherited_without_wsdl, :inherited + end + end + base.extend(ClassMethods) + end + + module ClassMethods + def inherited(child) + inherited_without_wsdl(child) + child.send(:include, ActionWebService::Router::Wsdl::InstanceMethods) + end + end + + module InstanceMethods # :nodoc: + XsdNs = 'http://www.w3.org/2001/XMLSchema' + WsdlNs = 'http://schemas.xmlsoap.org/wsdl/' + SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/' + SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/' + SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http' + + def wsdl + 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') + rescue Exception => e + log_error e unless logger.nil? + render_text('', "500 #{e.message}") + end + when :post + render_text('', "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] + end + custom_types = mapper.custom_types + + + xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2) + xm.instruct! + + xm.definitions('name' => wsdl_service_name, + 'targetNamespace' => namespace, + 'xmlns:typens' => namespace, + 'xmlns:xsd' => XsdNs, + 'xmlns:soap' => SoapNs, + 'xmlns:soapenc' => SoapEncodingNs, + 'xmlns:wsdl' => WsdlNs, + 'xmlns' => WsdlNs) do + + # Custom type XSD generation + if custom_types.size > 0 + xm.types do + xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do + custom_types.each do |klass, mapping| + case + when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping) + xm.xsd(:complexType, 'name' => mapping.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 + '[]') + end + end + end + when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping) + xm.xsd(:complexType, 'name' => mapping.type_name) do + xm.xsd(:all) do + mapping.each_attribute 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| + 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) + end + else + mapping_list = method_signature[:expects] + i = 1 + mapping_list.each do |mapping| + if mapping.is_a?(Hash) + param_name = mapping.keys.shift + mapping = mapping.values.shift + else + param_name = "param#{i}" + end + xm.part('name' => param_name, 'type' => mapping.qualified_type_name) + i += 1 + end if mapping_list + end + end + end + public_name = service_api.public_api_method_name(method_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) + xm.portType('name' => port_name) do + api_methods.each do |method_name, method_signature| + public_name = service_api.public_api_method_name(method_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) + 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) + 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 \ + + "/" + public_name + end + xm.soap(:operation, 'soapAction' => soap_action) + xm.input do + xm.soap(:body, + 'use' => 'encoded', + 'namespace' => namespace, + 'encodingStyle' => SoapEncodingNs) + end + xm.output do + xm.soap(:body, + 'use' => 'encoded', + 'namespace' => namespace, + 'encodingStyle' => SoapEncodingNs) + end + end + 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) + case web_service_dispatching_mode + when :direct + binding_target = 'api' + when :delegated + binding_target = service_name.to_s + end + xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do + xm.soap(:address, 'location' => "#{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" + end + + def binding_name_for(wsdl_service_name, service_name) + "#{wsdl_service_name}#{service_name.to_s.camelize}Binding" + end + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/struct.rb b/actionwebservice/lib/action_web_service/struct.rb new file mode 100644 index 0000000000..5420f4cf49 --- /dev/null +++ b/actionwebservice/lib/action_web_service/struct.rb @@ -0,0 +1,55 @@ +module ActionWebService + # To send structured types across the wire, derive from ActionWebService::Struct, + # and use +member+ to declare structure members. + # + # ActionWebService::Struct should be used in method signatures when you want to accept or return + # structured types that have no Active Record model class representations, or you don't + # want to expose your entire Active Record model to remote callers. + # + # === Example + # + # class Person < ActionWebService::Struct + # member :id, :int + # member :firstnames, [:string] + # member :lastname, :string + # member :email, :string + # end + # person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe') + # + # Active Record model classes are already implicitly supported for method + # return signatures. A structure containing its columns as members will be + # automatically generated if its present in a signature. + class Struct + + # If a Hash is given as argument to an ActionWebService::Struct constructor, + # it can contain initial values for the structure member. + def initialize(values={}) + if values.is_a?(Hash) + values.map{|k,v| send('%s=' % k.to_s, v)} + end + end + + # The member with the given name + def [](name) + send(name.to_s) + 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)) + class_eval <<-END + def #{name}; @#{name}; end + def #{name}=(value); @#{name} = value; end + END + end + + def members # :nodoc: + read_inheritable_attribute("struct_members") || {} + end + end + end +end diff --git a/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb b/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb new file mode 100644 index 0000000000..4d1c2ed471 --- /dev/null +++ b/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb @@ -0,0 +1,26 @@ +class Class # :nodoc: + def class_inheritable_option(sym, default_value=nil) + write_inheritable_attribute sym, default_value + class_eval <<-EOS + def self.#{sym}(value=nil) + if !value.nil? + write_inheritable_attribute(:#{sym}, value) + else + read_inheritable_attribute(:#{sym}) + end + end + + def self.#{sym}=(value) + write_inheritable_attribute(:#{sym}, value) + end + + def #{sym} + self.class.#{sym} + end + + def #{sym}=(value) + self.class.#{sym} = value + end + EOS + end +end diff --git a/actionwebservice/lib/action_web_service/support/signature.rb b/actionwebservice/lib/action_web_service/support/signature.rb new file mode 100644 index 0000000000..00c62a2232 --- /dev/null +++ b/actionwebservice/lib/action_web_service/support/signature.rb @@ -0,0 +1,100 @@ +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: + # + # [:int] any integer value + # [:float] any floating point value + # [:string] any string value + # [:bool] any boolean value + # [:time] any value containing both date and time + # [:date] 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 -- cgit v1.2.3