diff options
Diffstat (limited to 'vendor/plugins/actionwebservice/lib')
37 files changed, 3317 insertions, 0 deletions
diff --git a/vendor/plugins/actionwebservice/lib/action_web_service.rb b/vendor/plugins/actionwebservice/lib/action_web_service.rb new file mode 100644 index 000000000..0632dd1ec --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service.rb @@ -0,0 +1,66 @@ +#-- +# 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' + gem 'activesupport', '>= 1.0.2' + gem 'actionpack', '>= 1.6.0' + gem 'activerecord', '>= 1.9.0' +end + +$:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/") + +require 'action_web_service/support/class_inheritable_options' +require 'action_web_service/support/signature_types' +require 'action_web_service/base' +require 'action_web_service/client' +require 'action_web_service/invocation' +require 'action_web_service/api' +require 'action_web_service/casting' +require 'action_web_service/struct' +require 'action_web_service/container' +require 'action_web_service/protocol' +require 'action_web_service/dispatcher' +require 'action_web_service/scaffolding' + +ActionWebService::Base.class_eval do + include ActionWebService::Container::Direct + include ActionWebService::Invocation +end + +ActionController::Base.class_eval do + include ActionWebService::Protocol::Discovery + include ActionWebService::Protocol::Soap + include ActionWebService::Protocol::XmlRpc + include ActionWebService::Container::Direct + include ActionWebService::Container::Delegated + include ActionWebService::Container::ActionController + include ActionWebService::Invocation + include ActionWebService::Dispatcher + include ActionWebService::Dispatcher::ActionController + include ActionWebService::Scaffolding +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/api.rb b/vendor/plugins/actionwebservice/lib/action_web_service/api.rb new file mode 100644 index 000000000..d16dc420d --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/api.rb @@ -0,0 +1,297 @@ +module ActionWebService # :nodoc: + module API # :nodoc: + # 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 + # <tt>container.web_service_api</tt>, where <tt>container</tt> is an + # ActionController::Base or a ActionWebService::Base. + # + # See ActionWebService::Container::Direct::ClassMethods for an example + # of use. + class Base + # Whether to transform the public API method names into camel-cased names + class_inheritable_option :inflect_names, true + + # By default only HTTP POST requests are processed + class_inheritable_option :allowed_http_methods, [ :post ] + + # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>. + # The default is +false+; you should be aware of the security implications + # of allowing this, and ensure that you don't allow remote callers to + # easily overwrite data they should not have access to. + class_inheritable_option :allow_active_record_expects, false + + # 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::SignatureTypes + + # 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 representing one of the Action Web Service base types. + # See ActionWebService::SignatureTypes 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: + # [<tt>:expects</tt>] Signature for the method input parameters + # [<tt>:returns</tt>] Signature for the method return value + # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value + def api_method(name, options={}) + unless options.is_a?(Hash) + raise(ActionWebServiceError, "Expected a Hash for options") + end + 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) + returns = canonical_signature(returns) + if expects + expects.each do |type| + type = type.element_type if type.is_a?(ArrayType) + if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects + raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects") + end + end + end + name = name.to_sym + public_name = public_api_method_name(name) + method = Method.new(name, public_name, expects, returns) + write_inheritable_hash("api_methods", name => method) + write_inheritable_hash("api_public_method_names", public_name => name) + end + + # Whether the given method name is a service method on this API + # + # class ProjectsApi < ActionWebService::API::Base + # api_method :getCount, :returns => [:int] + # end + # + # ProjectsApi.has_api_method?('GetCount') #=> false + # ProjectsApi.has_api_method?(:getCount) #=> true + 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 + # + # class ProjectsApi < ActionWebService::API::Base + # api_method :getCount, :returns => [:int] + # end + # + # ProjectsApi.has_api_method?(:getCount) #=> false + # ProjectsApi.has_api_method?('GetCount') #=> true + 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 + # + # ProjectsApi.public_api_method_name('GetCount') #=> "GetCount" + # ProjectsApi.public_api_method_name(:getCount) #=> "GetCount" + 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 + # + # class ProjectsApi < ActionWebService::API::Base + # api_method :getCount, :returns => [:int] + # end + # + # ProjectsApi.api_method_name('GetCount') #=> :getCount + 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. + # + # class ProjectsApi < ActionWebService::API::Base + # api_method :getCount, :returns => [:int] + # api_method :getCompletedCount, :returns => [:int] + # end + # + # ProjectsApi.api_methods #=> + # {:getCount=>#<ActionWebService::API::Method:0x24379d8 ...>, + # :getCompletedCount=>#<ActionWebService::API::Method:0x2437794 ...>} + # ProjectsApi.api_methods[:getCount].public_name #=> "GetCount" + def api_methods + read_inheritable_attribute("api_methods") || {} + end + + # The Method instance for the given public API method name, if any + # + # class ProjectsApi < ActionWebService::API::Base + # api_method :getCount, :returns => [:int] + # api_method :getCompletedCount, :returns => [:int] + # end + # + # ProjectsApi.public_api_method_instance('GetCount') #=> <#<ActionWebService::API::Method:0x24379d8 ...> + # ProjectsApi.public_api_method_instance(:getCount) #=> nil + def public_api_method_instance(public_method_name) + api_method_instance(api_method_name(public_method_name)) + end + + # The Method instance for the given API method name, if any + # + # class ProjectsApi < ActionWebService::API::Base + # api_method :getCount, :returns => [:int] + # api_method :getCompletedCount, :returns => [:int] + # end + # + # ProjectsApi.api_method_instance(:getCount) #=> <ActionWebService::API::Method:0x24379d8 ...> + # ProjectsApi.api_method_instance('GetCount') #=> <ActionWebService::API::Method:0x24379d8 ...> + def api_method_instance(method_name) + api_methods[method_name] + end + + # The Method instance for the default API method, if any + def default_api_method_instance + return nil unless name = default_api_method + instance = read_inheritable_attribute("default_api_method_instance") + if instance && instance.name == name + return instance + end + instance = Method.new(name, public_api_method_name(name), nil, nil) + write_inheritable_attribute("default_api_method_instance", instance) + instance + 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 + + # Represents an API method and its associated metadata, and provides functionality + # to assist in commonly performed API method tasks. + class Method + attr :name + attr :public_name + attr :expects + attr :returns + + def initialize(name, public_name, expects, returns) + @name = name + @public_name = public_name + @expects = expects + @returns = returns + @caster = ActionWebService::Casting::BaseCaster.new(self) + end + + # The list of parameter names for this method + def param_names + return [] unless @expects + @expects.map{ |type| type.name } + end + + # Casts a set of Ruby values into the expected Ruby values + def cast_expects(params) + @caster.cast_expects(params) + end + + # Cast a Ruby return value into the expected Ruby value + def cast_returns(return_value) + @caster.cast_returns(return_value) + end + + # Returns the index of the first expected parameter + # with the given name + def expects_index_of(param_name) + return -1 if @expects.nil? + (0..(@expects.length-1)).each do |i| + return i if @expects[i].name.to_s == param_name.to_s + end + -1 + end + + # Returns a hash keyed by parameter name for the given + # parameter list + def expects_to_hash(params) + return {} if @expects.nil? + h = {} + @expects.zip(params){ |type, param| h[type.name] = param } + h + end + + # Backwards compatibility with previous API + def [](sig_type) + case sig_type + when :expects + @expects.map{|x| compat_signature_entry(x)} + when :returns + @returns.map{|x| compat_signature_entry(x)} + end + end + + # String representation of this method + def to_s + fqn = "" + fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ") + fqn << "#{@public_name}(" + fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects + fqn << ")" + fqn + end + + private + def compat_signature_entry(entry) + if entry.array? + [compat_signature_entry(entry.element_type)] + else + if entry.spec.is_a?(Hash) + {entry.spec.keys.first => entry.type_class} + else + entry.type_class + end + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/base.rb b/vendor/plugins/actionwebservice/lib/action_web_service/base.rb new file mode 100644 index 000000000..6282061d8 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/base.rb @@ -0,0 +1,38 @@ +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 < ActionWebService::Struct + # 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/vendor/plugins/actionwebservice/lib/action_web_service/casting.rb b/vendor/plugins/actionwebservice/lib/action_web_service/casting.rb new file mode 100644 index 000000000..71f422eae --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/casting.rb @@ -0,0 +1,138 @@ +require 'time' +require 'date' +require 'xmlrpc/datetime' + +module ActionWebService # :nodoc: + module Casting # :nodoc: + class CastingError < ActionWebServiceError # :nodoc: + end + + # Performs casting of arbitrary values into the correct types for the signature + class BaseCaster # :nodoc: + def initialize(api_method) + @api_method = api_method + end + + # Coerces the parameters in +params+ (an Enumerable) into the types + # this method expects + def cast_expects(params) + self.class.cast_expects(@api_method, params) + end + + # Coerces the given +return_value+ into the type returned by this + # method + def cast_returns(return_value) + self.class.cast_returns(@api_method, return_value) + end + + class << self + include ActionWebService::SignatureTypes + + def cast_expects(api_method, params) # :nodoc: + return [] if api_method.expects.nil? + api_method.expects.zip(params).map{ |type, param| cast(param, type) } + end + + def cast_returns(api_method, return_value) # :nodoc: + return nil if api_method.returns.nil? + cast(return_value, api_method.returns[0]) + end + + def cast(value, signature_type) # :nodoc: + return value if signature_type.nil? # signature.length != params.length + return nil if value.nil? + # XMLRPC protocol doesn't support nil values. It uses false instead. + # It should never happen for SOAP. + if signature_type.structured? && value.equal?(false) + return nil + end + unless signature_type.array? || signature_type.structured? + return value if canonical_type(value.class) == signature_type.type + end + if signature_type.array? + unless value.respond_to?(:entries) && !value.is_a?(String) + raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}" + end + value.entries.map do |entry| + cast(entry, signature_type.element_type) + end + elsif signature_type.structured? + cast_to_structured_type(value, signature_type) + elsif !signature_type.custom? + cast_base_type(value, signature_type) + end + end + + def cast_base_type(value, signature_type) # :nodoc: + # This is a work-around for the fact that XML-RPC special-cases DateTime values into its own DateTime type + # in order to support iso8601 dates. This doesn't work too well for us, so we'll convert it into a Time, + # with the caveat that we won't be able to handle pre-1970 dates that are sent to us. + # + # See http://dev.rubyonrails.com/ticket/2516 + value = value.to_time if value.is_a?(XMLRPC::DateTime) + + case signature_type.type + when :int + Integer(value) + when :string + value.to_s + when :base64 + if value.is_a?(ActionWebService::Base64) + value + else + ActionWebService::Base64.new(value.to_s) + end + when :bool + return false if value.nil? + return value if value == true || value == false + case value.to_s.downcase + when '1', 'true', 'y', 'yes' + true + when '0', 'false', 'n', 'no' + false + else + raise CastingError, "Don't know how to cast #{value.class} into Boolean" + end + when :float + Float(value) + when :decimal + BigDecimal(value.to_s) + when :time + value = "%s/%s/%s %s:%s:%s" % value.values_at(*%w[2 3 1 4 5 6]) if value.kind_of?(Hash) + value.kind_of?(Time) ? value : Time.parse(value.to_s) + when :date + value = "%s/%s/%s" % value.values_at(*%w[2 3 1]) if value.kind_of?(Hash) + value.kind_of?(Date) ? value : Date.parse(value.to_s) + when :datetime + value = "%s/%s/%s %s:%s:%s" % value.values_at(*%w[2 3 1 4 5 6]) if value.kind_of?(Hash) + value.kind_of?(DateTime) ? value : DateTime.parse(value.to_s) + end + end + + def cast_to_structured_type(value, signature_type) # :nodoc: + obj = nil + obj = value if canonical_type(value.class) == canonical_type(signature_type.type) + obj ||= signature_type.type_class.new + if value.respond_to?(:each_pair) + klass = signature_type.type_class + value.each_pair do |name, val| + type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil + val = cast(val, type) if type + # See http://dev.rubyonrails.com/ticket/3567 + val = val.to_time if val.is_a?(XMLRPC::DateTime) + obj.__send__("#{name}=", val) if obj.respond_to?(name) + end + elsif value.respond_to?(:attributes) + signature_type.each_member do |name, type| + val = value.__send__(name) + obj.__send__("#{name}=", cast(val, type)) if obj.respond_to?(name) + end + else + raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}" + end + obj + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/client.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client.rb new file mode 100644 index 000000000..2a1e33054 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/client.rb @@ -0,0 +1,3 @@ +require 'action_web_service/client/base' +require 'action_web_service/client/soap_client' +require 'action_web_service/client/xmlrpc_client' diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb new file mode 100644 index 000000000..9dada7bf9 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/client/base.rb @@ -0,0 +1,28 @@ +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? + self.perform_invocation(call_name, args) + 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 + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb new file mode 100644 index 000000000..ebabd8ea8 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/client/soap_client.rb @@ -0,0 +1,113 @@ +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 + # provides access to the underlying soap driver + attr_reader :driver + + # 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: + # [<tt>:namespace</tt>] If the remote server has used a custom namespace to + # declare its custom types, you can specify it here. This would + # be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute + # in .NET, for example. + # [<tt>:driver_options</tt>] If you want to supply any custom SOAP RPC driver + # options, you can provide them as a Hash here + # + # The <tt>:driver_options</tt> option can be used to configure the backend SOAP + # RPC driver. An example of configuring the SOAP backend to do + # client-certificate authenticated SSL connections to the server: + # + # opts = {} + # opts['protocol.http.ssl_config.verify_mode'] = 'OpenSSL::SSL::VERIFY_PEER' + # opts['protocol.http.ssl_config.client_cert'] = client_cert_file_path + # opts['protocol.http.ssl_config.client_key'] = client_key_file_path + # opts['protocol.http.ssl_config.ca_file'] = ca_cert_file_path + # client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts) + def initialize(api, endpoint_uri, options={}) + super(api, endpoint_uri) + @namespace = options[:namespace] || 'urn:ActionWebService' + @driver_options = options[:driver_options] || {} + @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace + @soap_action_base = options[:soap_action_base] + @soap_action_base ||= URI.parse(endpoint_uri).path + @driver = create_soap_rpc_driver(api, endpoint_uri) + @driver_options.each do |name, value| + @driver.options[name.to_s] = value + end + end + + protected + def perform_invocation(method_name, args) + method = @api.api_methods[method_name.to_sym] + args = method.cast_expects(args.dup) rescue args + return_value = @driver.send(method_name, *args) + method.cast_returns(return_value.dup) rescue return_value + end + + def soap_action(method_name) + "#{@soap_action_base}/#{method_name}" + end + + private + def create_soap_rpc_driver(api, endpoint_uri) + @protocol.register_api(api) + driver = SoapDriver.new(endpoint_uri, nil) + driver.mapping_registry = @protocol.marshaler.registry + api.api_methods.each do |name, method| + qname = XSD::QName.new(@namespace, method.public_name) + action = soap_action(method.public_name) + expects = method.expects + returns = method.returns + param_def = [] + if expects + expects.each do |type| + type_binding = @protocol.marshaler.lookup_type(type) + if SOAP::Version >= "1.5.5" + param_def << ['in', type.name.to_s, [type_binding.type.type_class.to_s]] + else + param_def << ['in', type.name, type_binding.mapping] + end + end + end + if returns + type_binding = @protocol.marshaler.lookup_type(returns[0]) + if SOAP::Version >= "1.5.5" + param_def << ['retval', 'return', [type_binding.type.type_class.to_s]] + else + param_def << ['retval', 'return', type_binding.mapping] + end + end + driver.add_method(qname, action, method.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/vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb b/vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb new file mode 100644 index 000000000..42b5c5d4f --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb @@ -0,0 +1,58 @@ +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: + # [<tt>:handler_name</tt>] If the remote server defines its services inside special + # handler (the Blogger API uses a <tt>"blogger"</tt> 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] + @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new + @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout]) + end + + protected + def perform_invocation(method_name, args) + method = @api.api_methods[method_name.to_sym] + if method.expects && method.expects.length != args.length + raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})") + end + args = method.cast_expects(args.dup) rescue args + if method.expects + method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) } + end + ok, return_value = @client.call2(public_name(method_name), *args) + return (method.cast_returns(return_value.dup) rescue return_value) if ok + raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}") + 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/vendor/plugins/actionwebservice/lib/action_web_service/container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container.rb new file mode 100644 index 000000000..13d9d8ab5 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/container.rb @@ -0,0 +1,3 @@ +require 'action_web_service/container/direct_container' +require 'action_web_service/container/delegated_container' +require 'action_web_service/container/action_controller_container' diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb new file mode 100644 index 000000000..bbc28083c --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/container/action_controller_container.rb @@ -0,0 +1,93 @@ +module ActionWebService # :nodoc: + module Container # :nodoc: + module ActionController # :nodoc: + def self.included(base) # :nodoc: + class << base + include ClassMethods + alias_method_chain :inherited, :api + alias_method_chain :web_service_api, :require + end + 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 <tt>blogger</tt> 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 in + # the API definition class via +options+, using a key of <tt>:api</tt> + 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 + create_web_service_client(api_klass, protocol, endpoint_uri, options) + end + protected name + end + end + end + + def web_service_api_with_require(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] + msg = requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}" + raise LoadError.new(msg).copy_blame!(load_error) + 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_with_api(child) + inherited_without_api(child) + begin child.web_service_api(child.controller_path) + rescue MissingSourceFile => e + raise unless e.is_missing?("apis/#{child.controller_path}_api") + end + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb new file mode 100644 index 000000000..5477f8d10 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/container/delegated_container.rb @@ -0,0 +1,86 @@ +module ActionWebService # :nodoc: + module Container # :nodoc: + module Delegated # :nodoc: + class ContainerError < ActionWebServiceError # :nodoc: + end + + def self.included(base) # :nodoc: + base.extend(ClassMethods) + base.send(:include, ActionWebService::Container::Delegated::InstanceMethods) + end + + module ClassMethods + # Declares a web service that will provide 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 ? self.instance_eval(&service) : info[:object] + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb b/vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb new file mode 100644 index 000000000..8818d8f45 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/container/direct_container.rb @@ -0,0 +1,69 @@ +module ActionWebService # :nodoc: + module Container # :nodoc: + module Direct # :nodoc: + class ContainerError < ActionWebServiceError # :nodoc: + end + + def self.included(base) # :nodoc: + base.extend(ClassMethods) + end + + module ClassMethods + # Attaches ActionWebService API +definition+ to the calling class. + # + # Action Controllers can have a default associated API, removing the need + # to call this method if you follow the Action Web Service naming conventions. + # + # A controller with a class name of GoogleSearchController will + # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the + # API definition class to be named <tt>GoogleSearchAPI</tt> or + # <tt>GoogleSearchApi</tt>. + # + # ==== Service class example + # + # class MyService < ActionWebService::Base + # web_service_api MyAPI + # end + # + # class MyAPI < ActionWebService::API::Base + # ... + # end + # + # ==== Controller class example + # + # class MyController < ActionController::Base + # web_service_api MyAPI + # end + # + # class MyAPI < ActionWebService::API::Base + # ... + # end + def web_service_api(definition=nil) + if definition.nil? + read_inheritable_attribute("web_service_api") + else + if definition.is_a?(Symbol) + raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller") + end + unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base) + raise(ContainerError, "#{definition.to_s} is not a valid API definition") + end + write_inheritable_attribute("web_service_api", definition) + call_web_service_api_callbacks(self, definition) + end + end + + def add_web_service_api_callback(&block) # :nodoc: + write_inheritable_array("web_service_api_callbacks", [block]) + end + + private + def call_web_service_api_callbacks(container_class, definition) + (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block| + block.call(container_class, definition) + end + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb new file mode 100644 index 000000000..601d83137 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher.rb @@ -0,0 +1,2 @@ +require 'action_web_service/dispatcher/abstract' +require 'action_web_service/dispatcher/action_controller_dispatcher' diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb new file mode 100644 index 000000000..cb94d649e --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -0,0 +1,207 @@ +require 'benchmark' + +module ActionWebService # :nodoc: + module Dispatcher # :nodoc: + class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc: + def initialize(*args) + super + set_backtrace(caller) + end + end + + def self.included(base) # :nodoc: + base.class_inheritable_option(:web_service_dispatching_mode, :direct) + base.class_inheritable_option(:web_service_exception_reporting, true) + base.send(:include, ActionWebService::Dispatcher::InstanceMethods) + end + + module InstanceMethods # :nodoc: + private + def invoke_web_service_request(protocol_request) + invocation = web_service_invocation(protocol_request) + if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol) + xmlrpc_multicall_invoke(invocation) + else + web_service_invoke(invocation) + end + end + + def web_service_direct_invoke(invocation) + @method_params = invocation.method_ordered_params + arity = method(invocation.api_method.name).arity rescue 0 + if arity < 0 || arity > 0 + params = @method_params + else + params = [] + end + web_service_filtered_invoke(invocation, params) + end + + def web_service_delegated_invoke(invocation) + web_service_filtered_invoke(invocation, invocation.method_ordered_params) + end + + def web_service_filtered_invoke(invocation, params) + cancellation_reason = nil + return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x| + cancellation_reason = x + end + if cancellation_reason + raise(DispatcherError, "request canceled: #{cancellation_reason}") + end + return_value + end + + def web_service_invoke(invocation) + case web_service_dispatching_mode + when :direct + return_value = web_service_direct_invoke(invocation) + when :delegated, :layered + return_value = web_service_delegated_invoke(invocation) + end + web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value) + end + + def xmlrpc_multicall_invoke(invocations) + responses = [] + invocations.each do |invocation| + if invocation.is_a?(Hash) + responses << [invocation, nil] + next + end + begin + case web_service_dispatching_mode + when :direct + return_value = web_service_direct_invoke(invocation) + when :delegated, :layered + return_value = web_service_delegated_invoke(invocation) + end + api_method = invocation.api_method + if invocation.api.has_api_method?(api_method.name) + response_type = (api_method.returns ? api_method.returns[0] : nil) + return_value = api_method.cast_returns(return_value) + else + response_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0) + end + responses << [return_value, response_type] + rescue Exception => e + responses << [{ 'faultCode' => 3, 'faultString' => e.message }, nil] + end + end + invocation = invocations[0] + invocation.protocol.encode_multicall_response(responses, invocation.protocol_options) + end + + def web_service_invocation(request, level = 0) + public_method_name = request.method_name + invocation = Invocation.new + invocation.protocol = request.protocol + invocation.protocol_options = request.protocol_options + invocation.service_name = request.service_name + if web_service_dispatching_mode == :layered + case invocation.protocol + when Protocol::Soap::SoapProtocol + soap_action = request.protocol_options[:soap_action] + if soap_action && soap_action =~ /^\/\w+\/(\w+)\// + invocation.service_name = $1 + end + when Protocol::XmlRpc::XmlRpcProtocol + if request.method_name =~ /^([^\.]+)\.(.*)$/ + public_method_name = $2 + invocation.service_name = $1 + end + end + end + if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol + if public_method_name == 'multicall' && invocation.service_name == 'system' + if level > 0 + raise(DispatcherError, "Recursive system.multicall invocations not allowed") + end + multicall = request.method_params.dup + unless multicall.is_a?(Array) && multicall[0].is_a?(Array) + raise(DispatcherError, "Malformed multicall (expected array of Hash elements)") + end + multicall = multicall[0] + return multicall.map do |item| + raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash) + raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName') + method_name = item['methodName'] + params = item.has_key?('params') ? item['params'] : [] + multicall_request = request.dup + multicall_request.method_name = method_name + multicall_request.method_params = params + begin + web_service_invocation(multicall_request, level + 1) + rescue Exception => e + {'faultCode' => 4, 'faultMessage' => e.message} + end + end + end + end + case web_service_dispatching_mode + when :direct + invocation.api = self.class.web_service_api + invocation.service = self + when :delegated, :layered + invocation.service = web_service_object(invocation.service_name) + invocation.api = invocation.service.class.web_service_api + end + if invocation.api.nil? + raise(DispatcherError, "no API attached to #{invocation.service.class}") + end + invocation.protocol.register_api(invocation.api) + request.api = invocation.api + if invocation.api.has_public_api_method?(public_method_name) + invocation.api_method = invocation.api.public_api_method_instance(public_method_name) + else + if invocation.api.default_api_method.nil? + raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}") + else + invocation.api_method = invocation.api.default_api_method_instance + end + end + if invocation.service.nil? + raise(DispatcherError, "no service available for service name #{invocation.service_name}") + end + unless invocation.service.respond_to?(invocation.api_method.name) + raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})") + end + request.api_method = invocation.api_method + begin + invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup) + rescue + logger.warn "Casting of method parameters failed" unless logger.nil? + invocation.method_ordered_params = request.method_params + end + request.method_params = invocation.method_ordered_params + invocation.method_named_params = {} + invocation.api_method.param_names.inject(0) do |m, n| + invocation.method_named_params[n] = invocation.method_ordered_params[m] + m + 1 + end + invocation + end + + def web_service_create_response(protocol, protocol_options, api, api_method, return_value) + if api.has_api_method?(api_method.name) + return_type = api_method.returns ? api_method.returns[0] : nil + return_value = api_method.cast_returns(return_value) + else + return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0) + end + protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options) + end + + class Invocation # :nodoc: + attr_accessor :protocol + attr_accessor :protocol_options + attr_accessor :service_name + attr_accessor :api + attr_accessor :api_method + attr_accessor :method_ordered_params + attr_accessor :method_named_params + attr_accessor :service + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb new file mode 100644 index 000000000..f9995197a --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -0,0 +1,379 @@ +require 'benchmark' +require 'builder/xmlmarkup' + +module ActionWebService # :nodoc: + module Dispatcher # :nodoc: + module ActionController # :nodoc: + def self.included(base) # :nodoc: + class << base + include ClassMethods + alias_method_chain :inherited, :action_controller + end + base.class_eval do + alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke + end + base.add_web_service_api_callback do |klass, api| + if klass.web_service_dispatching_mode == :direct + klass.class_eval 'def api; dispatch_web_service_request; end' + end + end + base.add_web_service_definition_callback do |klass, name, info| + if klass.web_service_dispatching_mode == :delegated + klass.class_eval "def #{name}; dispatch_web_service_request; end" + elsif klass.web_service_dispatching_mode == :layered + klass.class_eval 'def api; dispatch_web_service_request; end' + end + end + base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods) + end + + module ClassMethods # :nodoc: + def inherited_with_action_controller(child) + inherited_without_action_controller(child) + child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction) + end + end + + module InstanceMethods # :nodoc: + private + def dispatch_web_service_request + method = request.method.to_s.upcase + allowed_methods = self.class.web_service_api ? (self.class.web_service_api.allowed_http_methods || []) : [ :post ] + allowed_methods = allowed_methods.map{|m| m.to_s.upcase } + if !allowed_methods.include?(method) + render :text => "#{method} not supported", :status=>500 + return + end + exception = nil + begin + ws_request = discover_web_service_request(request) + rescue Exception => e + exception = e + end + if ws_request + ws_response = nil + exception = nil + bm = Benchmark.measure do + begin + ws_response = invoke_web_service_request(ws_request) + rescue Exception => e + exception = e + end + end + log_request(ws_request, request.raw_post) + if exception + log_error(exception) unless logger.nil? + send_web_service_error_response(ws_request, exception) + else + send_web_service_response(ws_response, bm.real) + end + else + exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message") + log_error(exception) unless logger.nil? + send_web_service_error_response(ws_request, exception) + end + rescue Exception => e + log_error(e) unless logger.nil? + send_web_service_error_response(ws_request, e) + end + + def send_web_service_response(ws_response, elapsed=nil) + log_response(ws_response, elapsed) + options = { :type => ws_response.content_type, :disposition => 'inline' } + send_data(ws_response.body, options) + end + + def send_web_service_error_response(ws_request, exception) + if ws_request + unless self.class.web_service_exception_reporting + exception = DispatcherError.new("Internal server error (exception raised)") + end + api_method = ws_request.api_method + public_method_name = api_method ? api_method.public_name : ws_request.method_name + return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0) + ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options) + send_web_service_response(ws_response) + else + if self.class.web_service_exception_reporting + message = exception.message + backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}" + else + message = "Exception raised" + backtrace = "" + end + render :text => "Internal protocol error: #{message}#{backtrace}", :status => 500 + end + end + + def web_service_direct_invoke(invocation) + invocation.method_named_params.each do |name, value| + params[name] = value + end + web_service_direct_invoke_without_controller(invocation) + end + + def log_request(ws_request, body) + unless logger.nil? + name = ws_request.method_name + api_method = ws_request.api_method + params = ws_request.method_params + if api_method && api_method.expects + params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" } + else + params = params.map{ |param| param.inspect } + end + service = ws_request.service_name + logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}") + logger.debug(indent(body)) + end + end + + def log_response(ws_response, elapsed=nil) + unless logger.nil? + elapsed = (elapsed ? " (%f):" % elapsed : ":") + logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}") + logger.debug(indent(ws_response.body)) + end + end + + def indent(body) + body.split(/\n/).map{|x| " #{x}"}.join("\n") + end + end + + module WsdlAction # :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 + options = { :type => 'text/xml', :disposition => 'inline' } + send_data(to_wsdl, options) + rescue Exception => e + log_error(e) unless logger.nil? + end + when :post + render :text => 'POST not supported', :status => 500 + end + end + + private + def base_uri + host = request.host_with_port + relative_url_root = request.relative_url_root + scheme = request.ssl? ? 'https' : 'http' + '%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path] + end + + def to_wsdl + xml = '' + dispatching_mode = web_service_dispatching_mode + global_service_name = wsdl_service_name + namespace = wsdl_namespace || 'urn:ActionWebService' + soap_action_base = "/#{controller_name}" + + marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace) + apis = {} + case dispatching_mode + when :direct + api = self.class.web_service_api + web_service_name = controller_class_name.sub(/Controller$/, '').underscore + apis[web_service_name] = [api, register_api(api, marshaler)] + when :delegated, :layered + self.class.web_services.each do |web_service_name, info| + service = web_service_object(web_service_name) + api = service.class.web_service_api + apis[web_service_name] = [api, register_api(api, marshaler)] + end + end + custom_types = [] + apis.values.each do |api, bindings| + bindings.each do |b| + custom_types << b unless custom_types.include?(b) + end + end + + xm = Builder::XmlMarkup.new(:target => xml, :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 + # Generate XSD + if custom_types.size > 0 + xm.types do + xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do + custom_types.each do |binding| + case + when binding.type.array? + xm.xsd(:complexType, 'name' => binding.type_name) do + xm.xsd(:complexContent) do + xm.xsd(:restriction, 'base' => 'soapenc:Array') do + xm.xsd(:attribute, 'ref' => 'soapenc:arrayType', + 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]') + end + end + end + when binding.type.structured? + xm.xsd(:complexType, 'name' => binding.type_name) do + xm.xsd(:all) do + binding.type.each_member do |name, type| + b = marshaler.register_type(type) + xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens')) + end + end + end + end + end + end + end + end + + # APIs + apis.each do |api_name, values| + api = values[0] + api.api_methods.each do |name, method| + gen = lambda do |msg_name, direction| + xm.message('name' => message_name_for(api_name, msg_name)) do + sym = nil + if direction == :out + returns = method.returns + if returns + binding = marshaler.register_type(returns[0]) + xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens')) + end + else + expects = method.expects + expects.each do |type| + binding = marshaler.register_type(type) + xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens')) + end if expects + end + end + end + public_name = method.public_name + gen.call(public_name, :in) + gen.call("#{public_name}Response", :out) + end + + # Port + port_name = port_name_for(global_service_name, api_name) + xm.portType('name' => port_name) do + api.api_methods.each do |name, method| + xm.operation('name' => method.public_name) do + xm.input('message' => "typens:" + message_name_for(api_name, method.public_name)) + xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response")) + end + end + end + + # Bind it + binding_name = binding_name_for(global_service_name, api_name) + xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do + xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport) + api.api_methods.each do |name, method| + xm.operation('name' => method.public_name) do + case web_service_dispatching_mode + when :direct + soap_action = soap_action_base + "/api/" + method.public_name + when :delegated, :layered + soap_action = soap_action_base \ + + "/" + api_name.to_s \ + + "/" + method.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 it + xm.service('name' => "#{global_service_name}Service") do + apis.each do |api_name, values| + port_name = port_name_for(global_service_name, api_name) + binding_name = binding_name_for(global_service_name, api_name) + case web_service_dispatching_mode + when :direct, :layered + binding_target = 'api' + when :delegated + binding_target = api_name.to_s + end + xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do + xm.soap(:address, 'location' => "#{base_uri}#{binding_target}") + end + end + end + end + end + + def port_name_for(global_service, service) + "#{global_service}#{service.to_s.camelize}Port" + end + + def binding_name_for(global_service, service) + "#{global_service}#{service.to_s.camelize}Binding" + end + + def message_name_for(api_name, message_name) + mode = web_service_dispatching_mode + if mode == :layered || mode == :delegated + api_name.to_s + '-' + message_name + else + message_name + end + end + + def register_api(api, marshaler) + bindings = {} + traverse_custom_types(api, marshaler, bindings) do |binding| + bindings[binding] = nil unless bindings.has_key?(binding) + element_binding = binding.element_binding + bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding) + end + bindings.keys + end + + def traverse_custom_types(api, marshaler, bindings, &block) + api.api_methods.each do |name, method| + expects, returns = method.expects, method.returns + expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects + returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns + end + end + + def traverse_type(marshaler, type, bindings, &block) + binding = marshaler.register_type(type) + return if bindings.has_key?(binding) + bindings[binding] = nil + yield binding + if type.array? + yield marshaler.register_type(type.element_type) + type = type.element_type + end + type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured? + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb b/vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb new file mode 100644 index 000000000..2a9121ee2 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/invocation.rb @@ -0,0 +1,202 @@ +module ActionWebService # :nodoc: + module Invocation # :nodoc: + class InvocationError < ActionWebService::ActionWebServiceError # :nodoc: + end + + def self.included(base) # :nodoc: + 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 <tt>[false, "cancel reason"]</tt> 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: + # [<tt>:except</tt>] A list of methods for which the interceptor will NOT be called + # [<tt>:only</tt>] 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.included(base) + base.class_eval do + alias_method_chain :perform_invocation, :interception + end + end + + def perform_invocation_with_interception(method_name, params, &block) + return if before_invocation(method_name, params, &block) == false + return_value = perform_invocation_without_interception(method_name, params) + after_invocation(method_name, params, return_value) + return_value + end + + def perform_invocation(method_name, params) + send(method_name, *params) + 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 + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb new file mode 100644 index 000000000..053e9cb4b --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/protocol.rb @@ -0,0 +1,4 @@ +require 'action_web_service/protocol/abstract' +require 'action_web_service/protocol/discovery' +require 'action_web_service/protocol/soap_protocol' +require 'action_web_service/protocol/xmlrpc_protocol' diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb new file mode 100644 index 000000000..fff5f622c --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -0,0 +1,112 @@ +module ActionWebService # :nodoc: + module Protocol # :nodoc: + class ProtocolError < ActionWebServiceError # :nodoc: + end + + class AbstractProtocol # :nodoc: + def setup(controller) + end + + def decode_action_pack_request(action_pack_request) + end + + def encode_action_pack_request(service_name, public_method_name, raw_body, options={}) + klass = options[:request_class] || SimpleActionPackRequest + request = klass.new + request.request_parameters['action'] = service_name.to_s + request.env['RAW_POST_DATA'] = raw_body + request.env['REQUEST_METHOD'] = 'POST' + request.env['HTTP_CONTENT_TYPE'] = 'text/xml' + request + end + + def decode_request(raw_request, service_name, protocol_options={}) + end + + def encode_request(method_name, params, param_types) + end + + def decode_response(raw_response) + end + + def encode_response(method_name, return_value, return_type, protocol_options={}) + end + + def protocol_client(api, protocol_name, endpoint_uri, options) + end + + def register_api(api) + end + end + + class Request # :nodoc: + attr :protocol + attr_accessor :method_name + attr_accessor :method_params + attr :service_name + attr_accessor :api + attr_accessor :api_method + attr :protocol_options + + def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil, protocol_options=nil) + @protocol = protocol + @method_name = method_name + @method_params = method_params + @service_name = service_name + @api = api + @api_method = api_method + @protocol_options = protocol_options || {} + end + end + + class Response # :nodoc: + attr :body + attr :content_type + attr :return_value + + def initialize(body, content_type, return_value) + @body = body + @content_type = content_type + @return_value = return_value + end + end + + class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc: + def initialize + @env = {} + @qparams = {} + @rparams = {} + @cookies = {} + reset_session + end + + def query_parameters + @qparams + end + + def request_parameters + @rparams + end + + def env + @env + end + + def host + '' + end + + def cookies + @cookies + end + + def session + @session + end + + def reset_session + @session = {} + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb new file mode 100644 index 000000000..3d4e0818d --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/discovery.rb @@ -0,0 +1,37 @@ +module ActionWebService # :nodoc: + module Protocol # :nodoc: + module Discovery # :nodoc: + def self.included(base) + base.extend(ClassMethods) + base.send(:include, ActionWebService::Protocol::Discovery::InstanceMethods) + end + + module ClassMethods # :nodoc: + def register_protocol(klass) + write_inheritable_array("web_service_protocols", [klass]) + end + end + + module InstanceMethods # :nodoc: + private + def discover_web_service_request(action_pack_request) + (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| + protocol = protocol.create(self) + request = protocol.decode_action_pack_request(action_pack_request) + return request unless request.nil? + end + nil + end + + def create_web_service_client(api, protocol_name, endpoint_uri, options) + (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| + protocol = protocol.create(self) + client = protocol.protocol_client(api, protocol_name, endpoint_uri, options) + return client unless client.nil? + end + nil + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb new file mode 100644 index 000000000..1bce496a7 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -0,0 +1,176 @@ +require 'action_web_service/protocol/soap_protocol/marshaler' +require 'soap/streamHandler' +require 'action_web_service/client/soap_client' + +module ActionWebService # :nodoc: + module API # :nodoc: + class Base # :nodoc: + def self.soap_client(endpoint_uri, options={}) + ActionWebService::Client::Soap.new self, endpoint_uri, options + end + end + end + + module Protocol # :nodoc: + module Soap # :nodoc: + def self.included(base) + base.register_protocol(SoapProtocol) + base.class_inheritable_option(:wsdl_service_name) + base.class_inheritable_option(:wsdl_namespace) + end + + class SoapProtocol < AbstractProtocol # :nodoc: + AWSEncoding = 'UTF-8' + XSDEncoding = 'UTF8' + + attr :marshaler + + def initialize(namespace=nil) + namespace ||= 'urn:ActionWebService' + @marshaler = SoapMarshaler.new namespace + end + + def self.create(controller) + SoapProtocol.new(controller.wsdl_namespace) + end + + def decode_action_pack_request(action_pack_request) + return nil unless soap_action = has_valid_soap_action?(action_pack_request) + service_name = action_pack_request.parameters['action'] + input_encoding = parse_charset(action_pack_request.env['HTTP_CONTENT_TYPE']) + protocol_options = { + :soap_action => soap_action, + :charset => input_encoding + } + decode_request(action_pack_request.raw_post, service_name, protocol_options) + end + + def encode_action_pack_request(service_name, public_method_name, raw_body, options={}) + request = super + request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name] + request + end + + def decode_request(raw_request, service_name, protocol_options={}) + envelope = SOAP::Processor.unmarshal(raw_request, :charset => protocol_options[:charset]) + unless envelope + raise ProtocolError, "Failed to parse SOAP request message" + end + request = envelope.body.request + method_name = request.elename.name + params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) } + Request.new(self, method_name, params, service_name, nil, nil, protocol_options) + end + + def encode_request(method_name, params, param_types) + param_types.each{ |type| marshaler.register_type(type) } if param_types + qname = XSD::QName.new(marshaler.namespace, method_name) + param_def = [] + if param_types + params = param_types.zip(params).map do |type, param| + param_def << ['in', type.name, marshaler.lookup_type(type).mapping] + [type.name, marshaler.ruby_to_soap(param)] + end + else + params = [] + end + request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def) + request.set_param(params) + envelope = create_soap_envelope(request) + SOAP::Processor.marshal(envelope) + end + + def decode_response(raw_response) + envelope = SOAP::Processor.unmarshal(raw_response) + unless envelope + raise ProtocolError, "Failed to parse SOAP request message" + end + method_name = envelope.body.request.elename.name + return_value = envelope.body.response + return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil? + [method_name, return_value] + end + + def encode_response(method_name, return_value, return_type, protocol_options={}) + if return_type + return_binding = marshaler.register_type(return_type) + marshaler.annotate_arrays(return_binding, return_value) + end + qname = XSD::QName.new(marshaler.namespace, method_name) + if return_value.nil? + response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) + else + if return_value.is_a?(Exception) + detail = SOAP::Mapping::SOAPException.new(return_value) + response = SOAP::SOAPFault.new( + SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']), + SOAP::SOAPString.new(return_value.to_s), + SOAP::SOAPString.new(self.class.name), + marshaler.ruby_to_soap(detail)) + else + if return_type + param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]] + response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def) + response.retval = marshaler.ruby_to_soap(return_value) + else + response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) + end + end + end + envelope = create_soap_envelope(response) + + # FIXME: This is not thread-safe, but StringFactory_ in SOAP4R only + # reads target encoding from the XSD::Charset.encoding variable. + # This is required to ensure $KCODE strings are converted + # correctly to UTF-8 for any values of $KCODE. + previous_encoding = XSD::Charset.encoding + XSD::Charset.encoding = XSDEncoding + response_body = SOAP::Processor.marshal(envelope, :charset => AWSEncoding) + XSD::Charset.encoding = previous_encoding + + Response.new(response_body, "text/xml; charset=#{AWSEncoding}", return_value) + end + + def protocol_client(api, protocol_name, endpoint_uri, options={}) + return nil unless protocol_name == :soap + ActionWebService::Client::Soap.new(api, endpoint_uri, options) + end + + def register_api(api) + api.api_methods.each do |name, method| + method.expects.each{ |type| marshaler.register_type(type) } if method.expects + method.returns.each{ |type| marshaler.register_type(type) } if method.returns + end + end + + private + def has_valid_soap_action?(request) + return nil unless request.method == :post + soap_action = request.env['HTTP_SOAPACTION'] + return nil unless soap_action + soap_action = soap_action.dup + soap_action.gsub!(/^"/, '') + soap_action.gsub!(/"$/, '') + soap_action.strip! + return nil if soap_action.empty? + soap_action + end + + def create_soap_envelope(body) + header = SOAP::SOAPHeader.new + body = SOAP::SOAPBody.new(body) + SOAP::SOAPEnvelope.new(header, body) + end + + def parse_charset(content_type) + return AWSEncoding if content_type.nil? + if /^text\/xml(?:\s*;\s*charset=([^"]+|"[^"]+"))$/i =~ content_type + $1 + else + AWSEncoding + end + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb new file mode 100644 index 000000000..187339627 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb @@ -0,0 +1,235 @@ +require 'soap/mapping' + +module ActionWebService + module Protocol + module Soap + # Workaround for SOAP4R return values changing + class Registry < SOAP::Mapping::Registry + if SOAP::Version >= "1.5.4" + def find_mapped_soap_class(obj_class) + return @map.instance_eval { @obj2soap[obj_class][0] } + end + + def find_mapped_obj_class(soap_class) + return @map.instance_eval { @soap2obj[soap_class][0] } + end + end + end + + class SoapMarshaler + attr :namespace + attr :registry + + def initialize(namespace=nil) + @namespace = namespace || 'urn:ActionWebService' + @registry = Registry.new + @type2binding = {} + register_static_factories + end + + def soap_to_ruby(obj) + SOAP::Mapping.soap2obj(obj, @registry) + end + + def ruby_to_soap(obj) + soap = SOAP::Mapping.obj2soap(obj, @registry) + soap.elename = XSD::QName.new if SOAP::Version >= "1.5.5" && soap.elename == XSD::QName::EMPTY + soap + end + + def register_type(type) + return @type2binding[type] if @type2binding.has_key?(type) + + if type.array? + array_mapping = @registry.find_mapped_soap_class(Array) + qname = XSD::QName.new(@namespace, soap_type_name(type.element_type.type_class.name) + 'Array') + element_type_binding = register_type(type.element_type) + @type2binding[type] = SoapBinding.new(self, qname, type, array_mapping, element_type_binding) + elsif (mapping = @registry.find_mapped_soap_class(type.type_class) rescue nil) + qname = mapping[2] ? mapping[2][:type] : nil + qname ||= soap_base_type_name(mapping[0]) + @type2binding[type] = SoapBinding.new(self, qname, type, mapping) + else + qname = XSD::QName.new(@namespace, soap_type_name(type.type_class.name)) + @registry.add(type.type_class, + SOAP::SOAPStruct, + typed_struct_factory(type.type_class), + { :type => qname }) + mapping = @registry.find_mapped_soap_class(type.type_class) + @type2binding[type] = SoapBinding.new(self, qname, type, mapping) + end + + if type.structured? + type.each_member do |m_name, m_type| + register_type(m_type) + end + end + + @type2binding[type] + end + alias :lookup_type :register_type + + def annotate_arrays(binding, value) + if value.nil? + return + elsif binding.type.array? + mark_typed_array(value, binding.element_binding.qname) + if binding.element_binding.type.custom? + value.each do |element| + annotate_arrays(binding.element_binding, element) + end + end + elsif binding.type.structured? + binding.type.each_member do |name, type| + member_binding = register_type(type) + member_value = value.respond_to?('[]') ? value[name] : value.send(name) + annotate_arrays(member_binding, member_value) if type.custom? + end + end + end + + private + def typed_struct_factory(type_class) + if Object.const_defined?('ActiveRecord') + if type_class.ancestors.include?(ActiveRecord::Base) + qname = XSD::QName.new(@namespace, soap_type_name(type_class.name)) + type_class.instance_variable_set('@qname', qname) + return SoapActiveRecordStructFactory.new + end + end + SOAP::Mapping::Registry::TypedStructFactory + end + + def mark_typed_array(array, qname) + (class << array; self; end).class_eval do + define_method(:arytype) do + qname + end + end + end + + def soap_base_type_name(type) + xsd_type = type.ancestors.find{ |c| c.const_defined? 'Type' } + xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type + end + + def soap_type_name(type_name) + type_name.gsub(/::/, '..') + end + + def register_static_factories + @registry.add(ActionWebService::Base64, SOAP::SOAPBase64, SoapBase64Factory.new, nil) + mapping = @registry.find_mapped_soap_class(ActionWebService::Base64) + @type2binding[ActionWebService::Base64] = + SoapBinding.new(self, SOAP::SOAPBase64::Type, ActionWebService::Base64, mapping) + @registry.add(Array, SOAP::SOAPArray, SoapTypedArrayFactory.new, nil) + @registry.add(::BigDecimal, SOAP::SOAPDouble, SOAP::Mapping::Registry::BasetypeFactory, {:derived_class => true}) + end + end + + class SoapBinding + attr :qname + attr :type + attr :mapping + attr :element_binding + + def initialize(marshaler, qname, type, mapping, element_binding=nil) + @marshaler = marshaler + @qname = qname + @type = type + @mapping = mapping + @element_binding = element_binding + end + + def type_name + @type.custom? ? @qname.name : nil + end + + def qualified_type_name(ns=nil) + if @type.custom? + "#{ns ? ns : @qname.namespace}:#{@qname.name}" + else + ns = XSD::NS.new + ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag) + ns.assign(SOAP::EncodingNamespace, "soapenc") + xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')} + return ns.name(XSD::AnyTypeName) unless xsd_klass + ns.name(xsd_klass.const_get('Type')) + end + end + + def eql?(other) + @qname == other.qname + end + alias :== :eql? + + def hash + @qname.hash + end + end + + class SoapActiveRecordStructFactory < SOAP::Mapping::Factory + def obj2soap(soap_class, obj, info, map) + unless obj.is_a?(ActiveRecord::Base) + return nil + end + soap_obj = soap_class.new(obj.class.instance_variable_get('@qname')) + obj.class.columns.each do |column| + key = column.name.to_s + value = obj.send(key) + soap_obj[key] = SOAP::Mapping._obj2soap(value, map) + end + soap_obj + end + + def soap2obj(obj_class, node, info, map) + unless node.type == obj_class.instance_variable_get('@qname') + return false + end + obj = obj_class.new + node.each do |key, value| + obj[key] = value.data + end + obj.instance_variable_set('@new_record', false) + return true, obj + end + end + + class SoapTypedArrayFactory < SOAP::Mapping::Factory + def obj2soap(soap_class, obj, info, map) + unless obj.respond_to?(:arytype) + return nil + end + soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype) + mark_marshalled_obj(obj, soap_obj) + obj.each do |item| + child = SOAP::Mapping._obj2soap(item, map) + soap_obj.add(child) + end + soap_obj + end + + def soap2obj(obj_class, node, info, map) + return false + end + end + + class SoapBase64Factory < SOAP::Mapping::Factory + def obj2soap(soap_class, obj, info, map) + unless obj.is_a?(ActionWebService::Base64) + return nil + end + return soap_class.new(obj) + end + + def soap2obj(obj_class, node, info, map) + unless node.type == SOAP::SOAPBase64::Type + return false + end + return true, obj_class.new(node.string) + end + end + + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb new file mode 100644 index 000000000..dfa4afc67 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -0,0 +1,122 @@ +require 'xmlrpc/marshal' +require 'action_web_service/client/xmlrpc_client' + +module XMLRPC # :nodoc: + class FaultException # :nodoc: + alias :message :faultString + end + + class Create + def wrong_type(value) + if BigDecimal === value + [true, value.to_f] + else + false + end + end + end +end + +module ActionWebService # :nodoc: + module API # :nodoc: + class Base # :nodoc: + def self.xmlrpc_client(endpoint_uri, options={}) + ActionWebService::Client::XmlRpc.new self, endpoint_uri, options + end + end + end + + module Protocol # :nodoc: + module XmlRpc # :nodoc: + def self.included(base) + base.register_protocol(XmlRpcProtocol) + end + + class XmlRpcProtocol < AbstractProtocol # :nodoc: + def self.create(controller) + XmlRpcProtocol.new + end + + def decode_action_pack_request(action_pack_request) + service_name = action_pack_request.parameters['action'] + decode_request(action_pack_request.raw_post, service_name) + end + + def decode_request(raw_request, service_name) + method_name, params = XMLRPC::Marshal.load_call(raw_request) + Request.new(self, method_name, params, service_name) + rescue + return nil + end + + def encode_request(method_name, params, param_types) + if param_types + params = params.dup + param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) } + end + XMLRPC::Marshal.dump_call(method_name, *params) + end + + def decode_response(raw_response) + [nil, XMLRPC::Marshal.load_response(raw_response)] + end + + def encode_response(method_name, return_value, return_type, protocol_options={}) + if return_value && return_type + return_value = value_to_xmlrpc_wire_format(return_value, return_type) + end + return_value = false if return_value.nil? + raw_response = XMLRPC::Marshal.dump_response(return_value) + Response.new(raw_response, 'text/xml', return_value) + end + + def encode_multicall_response(responses, protocol_options={}) + result = responses.map do |return_value, return_type| + if return_value && return_type + return_value = value_to_xmlrpc_wire_format(return_value, return_type) + return_value = [return_value] unless return_value.nil? + end + return_value = false if return_value.nil? + return_value + end + raw_response = XMLRPC::Marshal.dump_response(result) + Response.new(raw_response, 'text/xml', result) + end + + def protocol_client(api, protocol_name, endpoint_uri, options={}) + return nil unless protocol_name == :xmlrpc + ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options) + end + + def value_to_xmlrpc_wire_format(value, value_type) + if value_type.array? + value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) } + else + if value.is_a?(ActionWebService::Struct) + struct = {} + value.class.members.each do |name, type| + member_value = value[name] + next if member_value.nil? + struct[name.to_s] = value_to_xmlrpc_wire_format(member_value, type) + end + struct + elsif value.is_a?(ActiveRecord::Base) + struct = {} + value.attributes.each do |key, member_value| + next if member_value.nil? + struct[key.to_s] = member_value + end + struct + elsif value.is_a?(ActionWebService::Base64) + XMLRPC::Base64.new(value) + elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException) + XMLRPC::FaultException.new(2, value.message) + else + value + end + end + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb b/vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb new file mode 100644 index 000000000..f94a7ee91 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/scaffolding.rb @@ -0,0 +1,283 @@ +require 'benchmark' +require 'pathname' + +module ActionWebService + module Scaffolding # :nodoc: + class ScaffoldingError < ActionWebServiceError # :nodoc: + end + + def self.included(base) + base.extend(ClassMethods) + end + + # Web service invocation scaffolding provides a way to quickly invoke web service methods in a controller. The + # generated scaffold actions have default views to let you enter the method parameters and view the + # results. + # + # Example: + # + # class ApiController < ActionController + # web_service_scaffold :invoke + # end + # + # This example generates an +invoke+ action in the +ApiController+ that you can navigate to from + # your browser, select the API method, enter its parameters, and perform the invocation. + # + # If you want to customize the default views, create the following views in "app/views": + # + # * <tt>action_name/methods.erb</tt> + # * <tt>action_name/parameters.erb</tt> + # * <tt>action_name/result.erb</tt> + # * <tt>action_name/layout.erb</tt> + # + # Where <tt>action_name</tt> is the name of the action you gave to ClassMethods#web_service_scaffold. + # + # You can use the default views in <tt>RAILS_DIR/lib/action_web_service/templates/scaffolds</tt> as + # a guide. + module ClassMethods + # Generates web service invocation scaffolding for the current controller. The given action name + # can then be used as the entry point for invoking API methods from a web browser. + def web_service_scaffold(action_name) + add_template_helper(Helpers) + module_eval <<-"end_eval", __FILE__, __LINE__ + 1 + def #{action_name} + if request.method == :get + setup_invocation_assigns + render_invocation_scaffold 'methods' + end + end + + def #{action_name}_method_params + if request.method == :get + setup_invocation_assigns + render_invocation_scaffold 'parameters' + end + end + + def #{action_name}_submit + if request.method == :post + setup_invocation_assigns + protocol_name = params['protocol'] ? params['protocol'].to_sym : :soap + case protocol_name + when :soap + @protocol = Protocol::Soap::SoapProtocol.create(self) + when :xmlrpc + @protocol = Protocol::XmlRpc::XmlRpcProtocol.create(self) + end + bm = Benchmark.measure do + @protocol.register_api(@scaffold_service.api) + post_params = params['method_params'] ? params['method_params'].dup : nil + params = [] + @scaffold_method.expects.each_with_index do |spec, i| + params << post_params[i.to_s] + end if @scaffold_method.expects + params = @scaffold_method.cast_expects(params) + method_name = public_method_name(@scaffold_service.name, @scaffold_method.public_name) + @method_request_xml = @protocol.encode_request(method_name, params, @scaffold_method.expects) + new_request = @protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml) + prepare_request(new_request, @scaffold_service.name, @scaffold_method.public_name) + self.request = new_request + if @scaffold_container.dispatching_mode != :direct + request.parameters['action'] = @scaffold_service.name + end + dispatch_web_service_request + @method_response_xml = response.body + method_name, obj = @protocol.decode_response(@method_response_xml) + return if handle_invocation_exception(obj) + @method_return_value = @scaffold_method.cast_returns(obj) + end + @method_elapsed = bm.real + add_instance_variables_to_assigns + reset_invocation_response + render_invocation_scaffold 'result' + end + end + + private + def setup_invocation_assigns + @scaffold_class = self.class + @scaffold_action_name = "#{action_name}" + @scaffold_container = WebServiceModel::Container.new(self) + if params['service'] && params['method'] + @scaffold_service = @scaffold_container.services.find{ |x| x.name == params['service'] } + @scaffold_method = @scaffold_service.api_methods[params['method']] + end + add_instance_variables_to_assigns + end + + def render_invocation_scaffold(action) + customized_template = "\#{self.class.controller_path}/#{action_name}/\#{action}" + default_template = scaffold_path(action) + if template_exists?(customized_template) + content = @template.render :file => customized_template + else + content = @template.render :file => default_template + end + @template.instance_variable_set("@content_for_layout", content) + if self.active_layout.nil? + render :file => scaffold_path("layout") + else + render :file => self.active_layout + end + end + + def scaffold_path(template_name) + File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".erb" + end + + def reset_invocation_response + erase_render_results + response.headers = ::ActionController::AbstractResponse::DEFAULT_HEADERS.merge("cookie" => []) + end + + def public_method_name(service_name, method_name) + if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol) + service_name + '.' + method_name + else + method_name + end + end + + def prepare_request(new_request, service_name, method_name) + new_request.parameters.update(request.parameters) + request.env.each{ |k, v| new_request.env[k] = v unless new_request.env.has_key?(k) } + if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol) + new_request.env['HTTP_SOAPACTION'] = "/\#{controller_name()}/\#{service_name}/\#{method_name}" + end + end + + def handle_invocation_exception(obj) + exception = nil + if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception) + exception = obj.detail.cause + elsif obj.is_a?(XMLRPC::FaultException) + exception = obj + end + return unless exception + reset_invocation_response + rescue_action(exception) + true + end + end_eval + end + end + + module Helpers # :nodoc: + def method_parameter_input_fields(method, type, field_name_base, idx, was_structured=false) + if type.array? + return content_tag('em', "Typed array input fields not supported yet (#{type.name})") + end + if type.structured? + return content_tag('em', "Nested structural types not supported yet (#{type.name})") if was_structured + parameters = "" + type.each_member do |member_name, member_type| + label = method_parameter_label(member_name, member_type) + nested_content = method_parameter_input_fields( + method, + member_type, + "#{field_name_base}[#{idx}][#{member_name}]", + idx, + true) + if member_type.custom? + parameters << content_tag('li', label) + parameters << content_tag('ul', nested_content) + else + parameters << content_tag('li', label + ' ' + nested_content) + end + end + content_tag('ul', parameters) + else + # If the data source was structured previously we already have the index set + field_name_base = "#{field_name_base}[#{idx}]" unless was_structured + + case type.type + when :int + text_field_tag "#{field_name_base}" + when :string + text_field_tag "#{field_name_base}" + when :base64 + text_area_tag "#{field_name_base}", nil, :size => "40x5" + when :bool + radio_button_tag("#{field_name_base}", "true") + " True" + + radio_button_tag("#{field_name_base}", "false") + "False" + when :float + text_field_tag "#{field_name_base}" + when :time, :datetime + time = Time.now + i = 0 + %w|year month day hour minute second|.map do |name| + i += 1 + send("select_#{name}", time, :prefix => "#{field_name_base}[#{i}]", :discard_type => true) + end.join + when :date + date = Date.today + i = 0 + %w|year month day|.map do |name| + i += 1 + send("select_#{name}", date, :prefix => "#{field_name_base}[#{i}]", :discard_type => true) + end.join + end + end + end + + def method_parameter_label(name, type) + name.to_s.capitalize + ' (' + type.human_name(false) + ')' + end + + def service_method_list(service) + action = @scaffold_action_name + '_method_params' + methods = service.api_methods_full.map do |desc, name| + content_tag("li", link_to(desc, :action => action, :service => service.name, :method => name)) + end + content_tag("ul", methods.join("\n")) + end + end + + module WebServiceModel # :nodoc: + class Container # :nodoc: + attr :services + attr :dispatching_mode + + def initialize(real_container) + @real_container = real_container + @dispatching_mode = @real_container.class.web_service_dispatching_mode + @services = [] + if @dispatching_mode == :direct + @services << Service.new(@real_container.controller_name, @real_container) + else + @real_container.class.web_services.each do |name, obj| + @services << Service.new(name, @real_container.instance_eval{ web_service_object(name) }) + end + end + end + end + + class Service # :nodoc: + attr :name + attr :object + attr :api + attr :api_methods + attr :api_methods_full + + def initialize(name, real_service) + @name = name.to_s + @object = real_service + @api = @object.class.web_service_api + if @api.nil? + raise ScaffoldingError, "No web service API attached to #{object.class}" + end + @api_methods = {} + @api_methods_full = [] + @api.api_methods.each do |name, method| + @api_methods[method.public_name.to_s] = method + @api_methods_full << [method.to_s, method.public_name.to_s] + end + end + + def to_s + self.name.camelize + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/struct.rb b/vendor/plugins/actionwebservice/lib/action_web_service/struct.rb new file mode 100644 index 000000000..00eafc169 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/struct.rb @@ -0,0 +1,64 @@ +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 in method + # signatures. + 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 + + # Iterates through each member + def each_pair(&block) + self.class.members.each do |name, type| + yield name, self.__send__(name) + end + end + + class << self + # Creates a structure member with the specified +name+ and +type+. Generates + # accessor methods for reading and writing the member value. + def member(name, type) + name = name.to_sym + type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0) + write_inheritable_hash("struct_members", name => type) + class_eval <<-END + def #{name}; @#{name}; end + def #{name}=(value); @#{name} = value; end + END + end + + def members # :nodoc: + read_inheritable_attribute("struct_members") || {} + end + + def member_type(name) # :nodoc: + members[name.to_sym] + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb b/vendor/plugins/actionwebservice/lib/action_web_service/support/class_inheritable_options.rb new file mode 100644 index 000000000..4d1c2ed47 --- /dev/null +++ b/vendor/plugins/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/vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb b/vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb new file mode 100644 index 000000000..66c86bf6d --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/support/signature_types.rb @@ -0,0 +1,226 @@ +module ActionWebService # :nodoc: + # Action Web Service supports the following base types in a signature: + # + # [<tt>:int</tt>] Represents an integer value, will be cast to an integer using <tt>Integer(value)</tt> + # [<tt>:string</tt>] Represents a string value, will be cast to an string using the <tt>to_s</tt> method on an object + # [<tt>:base64</tt>] Represents a Base 64 value, will contain the binary bytes of a Base 64 value sent by the caller + # [<tt>:bool</tt>] Represents a boolean value, whatever is passed will be cast to boolean (<tt>true</tt>, '1', 'true', 'y', 'yes' are taken to represent true; <tt>false</tt>, '0', 'false', 'n', 'no' and <tt>nil</tt> represent false) + # [<tt>:float</tt>] Represents a floating point value, will be cast to a float using <tt>Float(value)</tt> + # [<tt>:time</tt>] Represents a timestamp, will be cast to a <tt>Time</tt> object + # [<tt>:datetime</tt>] Represents a timestamp, will be cast to a <tt>DateTime</tt> object + # [<tt>:date</tt>] Represents a date, will be cast to a <tt>Date</tt> object + # + # For structured types, you'll need to pass in the Class objects of + # ActionWebService::Struct and ActiveRecord::Base derivatives. + module SignatureTypes + def canonical_signature(signature) # :nodoc: + return nil if signature.nil? + unless signature.is_a?(Array) + raise(ActionWebServiceError, "Expected signature to be an Array") + end + i = -1 + signature.map{ |spec| canonical_signature_entry(spec, i += 1) } + end + + def canonical_signature_entry(spec, i) # :nodoc: + orig_spec = spec + name = "param#{i}" + if spec.is_a?(Hash) + name, spec = spec.keys.first, spec.values.first + end + type = spec + if spec.is_a?(Array) + ArrayType.new(orig_spec, canonical_signature_entry(spec[0], 0), name) + else + type = canonical_type(type) + if type.is_a?(Symbol) + BaseType.new(orig_spec, type, name) + else + StructuredType.new(orig_spec, type, name) + end + end + end + + def canonical_type(type) # :nodoc: + type_name = symbol_name(type) || class_to_type_name(type) + type = type_name || type + return canonical_type_name(type) if type.is_a?(Symbol) + type + end + + def canonical_type_name(name) # :nodoc: + name = name.to_sym + case name + when :int, :integer, :fixnum, :bignum + :int + when :string, :text + :string + when :base64, :binary + :base64 + when :bool, :boolean + :bool + when :float, :double + :float + when :decimal + :decimal + when :time, :timestamp + :time + when :datetime + :datetime + when :date + :date + else + raise(TypeError, "#{name} is not a valid base type") + end + end + + def canonical_type_class(type) # :nodoc: + type = canonical_type(type) + type.is_a?(Symbol) ? type_name_to_class(type) : type + end + + def symbol_name(name) # :nodoc: + return name.to_sym if name.is_a?(Symbol) || name.is_a?(String) + nil + end + + def class_to_type_name(klass) # :nodoc: + klass = klass.class unless klass.is_a?(Class) + if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass) + :int + elsif klass == String + :string + elsif klass == Base64 + :base64 + elsif klass == TrueClass || klass == FalseClass + :bool + elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass) + :float + elsif klass == Time + :time + elsif klass == DateTime + :datetime + elsif klass == Date + :date + else + nil + end + end + + def type_name_to_class(name) # :nodoc: + case canonical_type_name(name) + when :int + Integer + when :string + String + when :base64 + Base64 + when :bool + TrueClass + when :float + Float + when :decimal + BigDecimal + when :time + Time + when :date + Date + when :datetime + DateTime + else + nil + end + end + + def derived_from?(ancestor, child) # :nodoc: + child.ancestors.include?(ancestor) + end + + module_function :type_name_to_class + module_function :class_to_type_name + module_function :symbol_name + module_function :canonical_type_class + module_function :canonical_type_name + module_function :canonical_type + module_function :canonical_signature_entry + module_function :canonical_signature + module_function :derived_from? + end + + class BaseType # :nodoc: + include SignatureTypes + + attr :spec + attr :type + attr :type_class + attr :name + + def initialize(spec, type, name) + @spec = spec + @type = canonical_type(type) + @type_class = canonical_type_class(@type) + @name = name + end + + def custom? + false + end + + def array? + false + end + + def structured? + false + end + + def human_name(show_name=true) + type_type = array? ? element_type.type.to_s : self.type.to_s + str = array? ? (type_type + '[]') : type_type + show_name ? (str + " " + name.to_s) : str + end + end + + class ArrayType < BaseType # :nodoc: + attr :element_type + + def initialize(spec, element_type, name) + super(spec, Array, name) + @element_type = element_type + end + + def custom? + true + end + + def array? + true + end + end + + class StructuredType < BaseType # :nodoc: + def each_member + if @type_class.respond_to?(:members) + @type_class.members.each do |name, type| + yield name, type + end + elsif @type_class.respond_to?(:columns) + i = -1 + @type_class.columns.each do |column| + yield column.name, canonical_signature_entry(column.type, i += 1) + end + end + end + + def custom? + true + end + + def structured? + true + end + end + + class Base64 < String # :nodoc: + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb new file mode 100644 index 000000000..167613f68 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.erb @@ -0,0 +1,65 @@ +<html> +<head> + <title><%= @scaffold_class.wsdl_service_name %> Web Service</title> + <style> + body { background-color: #fff; color: #333; } + + body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; + } + + pre { + background-color: #eee; + padding: 10px; + font-size: 11px; + } + + a { color: #000; } + a:visited { color: #666; } + a:hover { color: #fff; background-color:#000; } + + .fieldWithErrors { + padding: 2px; + background-color: red; + display: table; + } + + #errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; + } + + #errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; + } + + #errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; + } + + #errorExplanation ul li { + font-size: 12px; + list-style: square; + } + </style> +</head> +<body> + +<%= @content_for_layout %> + +</body> +</html> diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb new file mode 100644 index 000000000..60dfe23f0 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.erb @@ -0,0 +1,6 @@ +<% @scaffold_container.services.each do |service| %> + + <h4>API Methods for <%= service %></h4> + <%= service_method_list(service) %> + +<% end %> diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb new file mode 100644 index 000000000..767284e0d --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.erb @@ -0,0 +1,29 @@ +<h4>Method Invocation Details for <em><%= @scaffold_service %>#<%= @scaffold_method.public_name %></em></h4> + +<% form_tag(:action => @scaffold_action_name + '_submit') do -%> +<%= hidden_field_tag "service", @scaffold_service.name %> +<%= hidden_field_tag "method", @scaffold_method.public_name %> + +<p> +<label for="protocol">Protocol:</label><br /> +<%= select_tag 'protocol', options_for_select([['SOAP', 'soap'], ['XML-RPC', 'xmlrpc']], params['protocol']) %> +</p> + +<% if @scaffold_method.expects %> + +<strong>Method Parameters:</strong><br /> +<% @scaffold_method.expects.each_with_index do |type, i| %> + <p> + <label for="method_params[<%= i %>]"><%= method_parameter_label(type.name, type) %> </label><br /> + <%= method_parameter_input_fields(@scaffold_method, type, "method_params", i) %> + </p> +<% end %> + +<% end %> + +<%= submit_tag "Invoke" %> +<% end -%> + +<p> +<%= link_to "Back", :action => @scaffold_action_name %> +</p> diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb new file mode 100644 index 000000000..5317688fc --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.erb @@ -0,0 +1,30 @@ +<h4>Method Invocation Result for <em><%= @scaffold_service %>#<%= @scaffold_method.public_name %></em></h4> + +<p> +Invocation took <tt><%= '%f' % @method_elapsed %></tt> seconds +</p> + +<p> +<strong>Return Value:</strong><br /> +<pre> +<%= h @method_return_value.inspect %> +</pre> +</p> + +<p> +<strong>Request XML:</strong><br /> +<pre> +<%= h @method_request_xml %> +</pre> +</p> + +<p> +<strong>Response XML:</strong><br /> +<pre> +<%= h @method_response_xml %> +</pre> +</p> + +<p> +<%= link_to "Back", :action => @scaffold_action_name + '_method_params', :method => @scaffold_method.public_name, :service => @scaffold_service.name %> +</p> diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb b/vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb new file mode 100644 index 000000000..7e714c941 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/test_invoke.rb @@ -0,0 +1,110 @@ +require 'test/unit' + +module Test # :nodoc: + module Unit # :nodoc: + class TestCase # :nodoc: + private + # invoke the specified API method + def invoke_direct(method_name, *args) + prepare_request('api', 'api', method_name, *args) + @controller.process(@request, @response) + decode_rpc_response + end + alias_method :invoke, :invoke_direct + + # invoke the specified API method on the specified service + def invoke_delegated(service_name, method_name, *args) + prepare_request(service_name.to_s, service_name, method_name, *args) + @controller.process(@request, @response) + decode_rpc_response + end + + # invoke the specified layered API method on the correct service + def invoke_layered(service_name, method_name, *args) + prepare_request('api', service_name, method_name, *args) + @controller.process(@request, @response) + decode_rpc_response + end + + # ---------------------- internal --------------------------- + + def prepare_request(action, service_name, api_method_name, *args) + @request.recycle! + @request.request_parameters['action'] = action + @request.env['REQUEST_METHOD'] = 'POST' + @request.env['HTTP_CONTENT_TYPE'] = 'text/xml' + @request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args) + case protocol + when ActionWebService::Protocol::Soap::SoapProtocol + soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}" + @request.env['HTTP_SOAPACTION'] = soap_action + when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol + @request.env.delete('HTTP_SOAPACTION') + end + end + + def encode_rpc_call(service_name, api_method_name, *args) + case @controller.web_service_dispatching_mode + when :direct + api = @controller.class.web_service_api + when :delegated, :layered + api = @controller.web_service_object(service_name.to_sym).class.web_service_api + end + protocol.register_api(api) + method = api.api_methods[api_method_name.to_sym] + raise ArgumentError, "wrong number of arguments for rpc call (#{args.length} for #{method.expects.length})" if method && method.expects && args.length != method.expects.length + protocol.encode_request(public_method_name(service_name, api_method_name), args.dup, method.expects) + end + + def decode_rpc_response + public_method_name, return_value = protocol.decode_response(@response.body) + exception = is_exception?(return_value) + raise exception if exception + return_value + end + + def public_method_name(service_name, api_method_name) + public_name = service_api(service_name).public_api_method_name(api_method_name) + if @controller.web_service_dispatching_mode == :layered && protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol) + '%s.%s' % [service_name.to_s, public_name] + else + public_name + end + end + + def service_api(service_name) + case @controller.web_service_dispatching_mode + when :direct + @controller.class.web_service_api + when :delegated, :layered + @controller.web_service_object(service_name.to_sym).class.web_service_api + end + end + + def protocol + if @protocol.nil? + @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.create(@controller) + else + case @protocol + when :xmlrpc + @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@controller) + when :soap + @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@controller) + else + @protocol + end + end + end + + def is_exception?(obj) + case protocol + when :soap, ActionWebService::Protocol::Soap::SoapProtocol + (obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \ + obj.detail.cause.is_a?(Exception)) ? obj.detail.cause : nil + when :xmlrpc, ActionWebService::Protocol::XmlRpc::XmlRpcProtocol + obj.is_a?(XMLRPC::FaultException) ? obj : nil + end + end + end + end +end diff --git a/vendor/plugins/actionwebservice/lib/action_web_service/version.rb b/vendor/plugins/actionwebservice/lib/action_web_service/version.rb new file mode 100644 index 000000000..a1b3d5929 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/action_web_service/version.rb @@ -0,0 +1,9 @@ +module ActionWebService + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 2 + TINY = 5 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/plugins/actionwebservice/lib/actionwebservice.rb b/vendor/plugins/actionwebservice/lib/actionwebservice.rb new file mode 100644 index 000000000..25e3aa8e8 --- /dev/null +++ b/vendor/plugins/actionwebservice/lib/actionwebservice.rb @@ -0,0 +1 @@ +require 'action_web_service' |