Commit 8cf3d7a4 authored by Jean-Philippe Lang's avatar Jean-Philippe Lang

Replaces the repositories management SOAP API with a simple REST API.

reposman usage is unchanged but the script now requires activeresource.
actionwebservice is now longer used and thus removed from plugins.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2435 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent cf5658d7
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
......@@ -16,31 +16,35 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class SysController < ActionController::Base
wsdl_service_name 'Sys'
web_service_api SysApi
web_service_scaffold :invoke
before_filter :check_enabled
before_invocation :check_enabled
# Returns the projects list, with their repositories
def projects_with_repository_enabled
Project.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
def projects
p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
render :xml => p.to_xml(:include => :repository)
end
# Registers a repository for the given project identifier
def repository_created(identifier, vendor, url)
project = Project.find_by_identifier(identifier)
# Do not create the repository if the project has already one
return 0 unless project && project.repository.nil?
logger.debug "Repository for #{project.name} was created"
repository = Repository.factory(vendor, :project => project, :url => url)
repository.save
repository.id || 0
def create_project_repository
project = Project.find(params[:id])
if project.repository
render :nothing => true, :status => 409
else
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
project.repository = Repository.factory(params[:vendor], params[:repository])
if project.repository && project.repository.save
render :xml => project.repository, :status => 201
else
render :nothing => true, :status => 422
end
end
end
protected
protected
def check_enabled(name, args)
Setting.sys_api_enabled?
def check_enabled
User.current = nil
unless Setting.sys_api_enabled?
render :nothing => 'Access denied. Repository management WS is disabled.', :status => 403
return false
end
end
end
......@@ -246,10 +246,11 @@ ActionController::Routing::Routes.draw do |map|
omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
end
# Allow downloading Web Service WSDL as a file with an extension
# instead of a file named 'wsdl'
map.connect ':controller/service.wsdl', :action => 'wsdl'
map.with_options :controller => 'sys' do |sys|
sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
end
# Install the default route as the lowest priority.
map.connect ':controller/:action/:id'
......
......@@ -57,11 +57,10 @@
require 'getoptlong'
require 'rdoc/usage'
require 'soap/wsdlDriver'
require 'find'
require 'etc'
Version = "1.1"
Version = "1.2"
SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
opts = GetoptLong.new(
......@@ -164,21 +163,28 @@ unless File.directory?($repos_base)
log("directory '#{$repos_base}' doesn't exists", :exit => true)
end
begin
require 'activeresource'
rescue LoadError
log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
end
class Project < ActiveResource::Base; end
log("querying Redmine for projects...", :level => 1);
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
$redmine_host.gsub!(/\/$/, '')
wsdl_url = "#{$redmine_host}/sys/service.wsdl";
Project.site = "#{$redmine_host}/sys";
begin
soap = SOAP::WSDLDriverFactory.new(wsdl_url).create_rpc_driver
# Get all active projects that have the Repository module enabled
projects = Project.find(:all)
rescue => e
log("Unable to connect to #{wsdl_url} : #{e}", :exit => true)
log("Unable to connect to #{Project.site}: #{e}", :exit => true)
end
projects = soap.ProjectsWithRepositoryEnabled
if projects.nil?
log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
end
......@@ -247,7 +253,7 @@ projects.each do |project|
else
# if repository is already declared in redmine, we don't create
# unless user use -f with reposman
if $force == false and not project.repository.nil?
if $force == false and project.respond_to?(:repository)
log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
next
end
......@@ -274,11 +280,11 @@ projects.each do |project|
end
if $svn_url
ret = soap.RepositoryCreated project.identifier, $scm, "#{$svn_url}#{project.identifier}"
if ret > 0
begin
project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"})
log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
else
log("\trepository #{repos_path} not registered in Redmine. Look in your log to find why.");
rescue => e
log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
end
end
......
require File.dirname(__FILE__) + '/../test_helper'
require 'sys_controller'
# Re-raise errors caught by the controller.
class SysController; def rescue_action(e) raise e end; end
class SysControllerTest < Test::Unit::TestCase
fixtures :projects, :enabled_modules, :repositories
def setup
@controller = SysController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
# Enable WS
Setting.sys_api_enabled = 1
end
def test_projects_with_repository_enabled
result = invoke :projects_with_repository_enabled
assert_equal EnabledModule.count(:all, :conditions => {:name => 'repository'}), result.size
project = result.first
assert project.is_a?(AWSProjectWithRepository)
assert project.respond_to?(:id)
assert_equal 1, project.id
assert project.respond_to?(:identifier)
assert_equal 'ecookbook', project.identifier
assert project.respond_to?(:name)
assert_equal 'eCookbook', project.name
assert project.respond_to?(:is_public)
assert project.is_public
assert project.respond_to?(:repository)
assert project.repository.is_a?(Repository)
end
def test_repository_created
project = Project.find(3)
assert_nil project.repository
assert invoke(:repository_created, project.identifier, 'Subversion', 'http://localhost/svn')
project.reload
assert_not_nil project.repository
assert project.repository.is_a?(Repository::Subversion)
assert_equal 'http://localhost/svn', project.repository.url
end
end
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
......@@ -15,19 +15,41 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AWSProjectWithRepository < ActionWebService::Struct
member :id, :int
member :identifier, :string
member :name, :string
member :is_public, :bool
member :repository, Repository
end
require File.dirname(__FILE__) + '/../test_helper'
require 'sys_controller'
# Re-raise errors caught by the controller.
class SysController; def rescue_action(e) raise e end; end
class SysControllerTest < Test::Unit::TestCase
fixtures :projects, :repositories
def setup
@controller = SysController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
Setting.sys_api_enabled = '1'
end
def test_projects_with_repository_enabled
get :projects
assert_response :success
assert_equal 'application/xml', @response.content_type
with_options :tag => 'projects' do |test|
test.assert_tag :children => { :count => Project.active.has_module(:repository).count }
end
end
class SysApi < ActionWebService::API::Base
api_method :projects_with_repository_enabled,
:expects => [],
:returns => [[AWSProjectWithRepository]]
api_method :repository_created,
:expects => [:string, :string, :string],
:returns => [:int]
def test_create_project_repository
assert_nil Project.find(4).repository
post :create_project_repository, :id => 4,
:vendor => 'Subversion',
:repository => { :url => 'file:///create/project/repository/subproject2'}
assert_response :created
r = Project.find(4).repository
assert r.is_a?(Repository::Subversion)
assert_equal 'file:///create/project/repository/subproject2', r.url
end
end
This diff is collapsed.
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.
This diff is collapsed.
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/rubyforgepublisher'
require 'fileutils'
require File.join(File.dirname(__FILE__), 'lib', 'action_web_service', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = 'actionwebservice'
PKG_VERSION = ActionWebService::VERSION::STRING + PKG_BUILD
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}"
RELEASE_NAME = "REL #{PKG_VERSION}"
RUBY_FORGE_PROJECT = "aws"
RUBY_FORGE_USER = "webster132"
desc "Default Task"
task :default => [ :test ]
# Run the unit tests
Rake::TestTask.new { |t|
t.libs << "test"
t.test_files = Dir['test/*_test.rb']
t.verbose = true
}
SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
desc 'Build the MySQL test database'
task :build_database do
%x( mysqladmin create actionwebservice_unittest )
%x( mysql actionwebservice_unittest < #{File.join(SCHEMA_PATH, 'mysql.sql')} )
end
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Action Web Service -- Web services for Action Pack"
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('CHANGELOG')
rdoc.rdoc_files.include('lib/action_web_service.rb')
rdoc.rdoc_files.include('lib/action_web_service/*.rb')
rdoc.rdoc_files.include('lib/action_web_service/api/*.rb')
rdoc.rdoc_files.include('lib/action_web_service/client/*.rb')
rdoc.rdoc_files.include('lib/action_web_service/container/*.rb')
rdoc.rdoc_files.include('lib/action_web_service/dispatcher/*.rb')
rdoc.rdoc_files.include('lib/action_web_service/protocol/*.rb')
rdoc.rdoc_files.include('lib/action_web_service/support/*.rb')
}
# Create compressed packages
spec = Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = PKG_NAME
s.summary = "Web service support for Action Pack."
s.description = %q{Adds WSDL/SOAP and XML-RPC web service support to Action Pack}
s.version = PKG_VERSION
s.author = "Leon Breedt"
s.email = "bitserf@gmail.com"
s.rubyforge_project = "aws"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 1.13.5' + PKG_BUILD)
s.add_dependency('activerecord', '= 1.15.5' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'
s.require_path = 'lib'
s.autorequire = 'action_web_service'
s.files = [ "Rakefile", "setup.rb", "README", "TODO", "CHANGELOG", "MIT-LICENSE" ]
s.files = s.files + Dir.glob( "examples/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true
end
# Publish beta gem
desc "Publish the API documentation"
task :pgem => [:package] do
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
end
# Publish documentation
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/aws", "doc").upload
end
def each_source_file(*args)
prefix, includes, excludes, open_file = args
prefix ||= File.dirname(__FILE__)
open_file = true if open_file.nil?
includes ||= %w[lib\/action_web_service\.rb$ lib\/action_web_service\/.*\.rb$]
excludes ||= %w[lib\/action_web_service\/vendor]
Find.find(prefix) do |file_name|
next if file_name =~ /\.svn/
file_name.gsub!(/^\.\//, '')
continue = false
includes.each do |inc|
if file_name.match(/#{inc}/)
continue = true
break
end
end
next unless continue
excludes.each do |exc|
if file_name.match(/#{exc}/)
continue = false
break
end
end
next unless continue
if open_file
File.open(file_name) do |f|
yield file_name, f
end
else
yield file_name
end
end
end
desc "Count lines of the AWS source code"
task :lines do
total_lines = total_loc = 0
puts "Per File:"
each_source_file do |file_name, f|
file_lines = file_loc = 0
while line = f.gets
file_lines += 1
next if line =~ /^\s*$/
next if line =~ /^\s*#/
file_loc += 1
end
puts " #{file_name}: Lines #{file_lines}, LOC #{file_loc}"
total_lines += file_lines
total_loc += file_loc
end
puts "Total:"
puts " Lines #{total_lines}, LOC #{total_loc}"
end
desc "Publish the release files to RubyForge."
task :release => [ :package ] do
require 'rubyforge'
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
rubyforge = RubyForge.new
rubyforge.login
rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
end
= Post-1.0
- Document/Literal SOAP support
- URL-based dispatching, URL identifies method
- Add :rest dispatching mode, a.l.a. Backpack API. Clean up dispatching
in general. Support vanilla XML-format as a "Rails" protocol?
XML::Simple deserialization into params?
web_service_dispatching_mode :rest
def method1(params)
end
def method2(params)
end
/ws/method1
<xml>
/ws/method2
<yaml>
- Allow locking down a controller to only accept messages for a particular
protocol. This will allow us to generate fully conformant error messages
in cases where we currently fudge it if we don't know the protocol.
- Allow AWS user to participate in typecasting, so they can centralize
workarounds for buggy input in one place
= Refactoring
- Don't have clean way to go from SOAP Class object to the xsd:NAME type
string -- NaHi possibly looking at remedying this situation
require 'action_web_service'
# These need to be in the load path for action_web_service to work
ActiveSupport::Dependencies.load_paths += ["#{RAILS_ROOT}/app/apis"]
# AWS Test helpers
require 'action_web_service/test_invoke' if ENV['RAILS_ENV'] && ENV['RAILS_ENV'] =~ /^test/
require 'rbconfig'
require 'find'
require 'ftools'
include Config
# this was adapted from rdoc's install.rb by way of Log4r
$sitedir = CONFIG["sitelibdir"]
unless $sitedir
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
$libdir = File.join(CONFIG["libdir"], "ruby", version)
$sitedir = $:.find {|x| x =~ /site_ruby/ }
if !$sitedir
$sitedir = File.join($libdir, "site_ruby")
elsif $sitedir !~ Regexp.quote(version)
$sitedir = File.join($sitedir, version)
end
end
# the actual gruntwork
Dir.chdir("lib")
Find.find("action_web_service", "action_web_service.rb") { |f|
if f[-3..-1] == ".rb"
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
else
File::makedirs(File.join($sitedir, *f.split(/\//)))
end
}
#--
# 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
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
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
require 'action_web_service/client/base'
require 'action_web_service/client/soap_client'
require 'action_web_service/client/xmlrpc_client'
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
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
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
require 'action_web_service/container/direct_container'
require 'action_web_service/container/delegated_container'
require 'action_web_service/container/action_controller_container'
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
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
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
require 'action_web_service/dispatcher/abstract'
require 'action_web_service/dispatcher/action_controller_dispatcher'
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
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
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'
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
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
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
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
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
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
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
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
<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>
<% @scaffold_container.services.each do |service| %>
<h4>API Methods for <%= service %></h4>
<%= service_method_list(service) %>
<% end %>
<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>
<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>
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
module ActionWebService
module VERSION #:nodoc:
MAJOR = 1
MINOR = 2
TINY = 5
STRING = [MAJOR, MINOR, TINY].join('.')
end
end
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment