aboutsummaryrefslogtreecommitdiffstats
path: root/actionservice
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2005-02-18 10:35:25 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2005-02-18 10:35:25 +0000
commite7a29380292902eae4799b2658507b3cfffb9cec (patch)
tree99a7cd3c7d720ef73f998c2756be1fef77ff0ee1 /actionservice
parente39bf105941133d3d6699c52c18dbd3b9aa0bf5c (diff)
downloadrails-e7a29380292902eae4799b2658507b3cfffb9cec.tar.gz
rails-e7a29380292902eae4799b2658507b3cfffb9cec.tar.bz2
rails-e7a29380292902eae4799b2658507b3cfffb9cec.zip
Added Action Service to the repository
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@658 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionservice')
-rw-r--r--actionservice/ChangeLog41
-rw-r--r--actionservice/HACKING44
-rw-r--r--actionservice/MIT-LICENSE21
-rw-r--r--actionservice/README241
-rw-r--r--actionservice/Rakefile135
-rw-r--r--actionservice/TODO35
-rw-r--r--actionservice/examples/googlesearch/README151
-rw-r--r--actionservice/examples/googlesearch/autoloading/google_search_api.rb50
-rw-r--r--actionservice/examples/googlesearch/autoloading/google_search_controller.rb57
-rw-r--r--actionservice/examples/googlesearch/delegated/google_search_service.rb110
-rw-r--r--actionservice/examples/googlesearch/delegated/search_controller.rb7
-rw-r--r--actionservice/examples/googlesearch/direct/search_controller.rb109
-rw-r--r--actionservice/examples/metaWeblog/README28
-rw-r--r--actionservice/examples/metaWeblog/blog_controller.rb121
-rw-r--r--actionservice/lib/action_service.rb60
-rw-r--r--actionservice/lib/action_service/api.rb2
-rw-r--r--actionservice/lib/action_service/api/abstract.rb198
-rw-r--r--actionservice/lib/action_service/api/action_controller.rb97
-rw-r--r--actionservice/lib/action_service/base.rb41
-rw-r--r--actionservice/lib/action_service/client.rb3
-rw-r--r--actionservice/lib/action_service/client/base.rb35
-rw-r--r--actionservice/lib/action_service/client/soap.rb87
-rw-r--r--actionservice/lib/action_service/client/xmlrpc.rb76
-rw-r--r--actionservice/lib/action_service/container.rb232
-rw-r--r--actionservice/lib/action_service/invocation.rb252
-rw-r--r--actionservice/lib/action_service/protocol.rb4
-rw-r--r--actionservice/lib/action_service/protocol/abstract.rb128
-rw-r--r--actionservice/lib/action_service/protocol/registry.rb55
-rw-r--r--actionservice/lib/action_service/protocol/soap.rb484
-rw-r--r--actionservice/lib/action_service/protocol/xmlrpc.rb187
-rw-r--r--actionservice/lib/action_service/router.rb2
-rw-r--r--actionservice/lib/action_service/router/action_controller.rb97
-rw-r--r--actionservice/lib/action_service/router/wsdl.rb210
-rw-r--r--actionservice/lib/action_service/struct.rb56
-rw-r--r--actionservice/lib/action_service/support/class_inheritable_options.rb26
-rw-r--r--actionservice/lib/action_service/support/signature.rb100
-rw-r--r--actionservice/setup.rb1360
-rw-r--r--actionservice/test/abstract_client.rb124
-rw-r--r--actionservice/test/abstract_soap.rb58
-rw-r--r--actionservice/test/abstract_unit.rb9
-rw-r--r--actionservice/test/api_test.rb52
-rw-r--r--actionservice/test/base_test.rb42
-rw-r--r--actionservice/test/client_soap_test.rb87
-rw-r--r--actionservice/test/client_xmlrpc_test.rb86
-rw-r--r--actionservice/test/container_test.rb53
-rw-r--r--actionservice/test/invocation_test.rb158
-rw-r--r--actionservice/test/protocol_registry_test.rb53
-rw-r--r--actionservice/test/protocol_soap_test.rb226
-rw-r--r--actionservice/test/protocol_xmlrpc_test.rb157
-rw-r--r--actionservice/test/router_action_controller_test.rb139
-rw-r--r--actionservice/test/router_wsdl_test.rb100
-rw-r--r--actionservice/test/struct_test.rb40
52 files changed, 6326 insertions, 0 deletions
diff --git a/actionservice/ChangeLog b/actionservice/ChangeLog
new file mode 100644
index 0000000000..e226a0a14c
--- /dev/null
+++ b/actionservice/ChangeLog
@@ -0,0 +1,41 @@
+UNRELEASED
+
+ * lib/action_service/router/wsdl.rb: ensure that #wsdl is
+ defined in the final container class, or the new ActionPack
+ filtering will exclude it
+ * lib/action_service/struct.rb,test/struct_test.rb: create a
+ default #initialize on inherit that accepts a Hash containing
+ the default member values
+ * lib/action_service/api/action_controller.rb: add support and
+ tests for #client_api in controller
+ * test/router_wsdl_test.rb: add tests to ensure declared
+ service names don't contain ':', as ':' causes interoperability
+ issues
+ * lib/*, test/*: rename "interface" concept to "api", and change all
+ related uses to reflect this change. update all uses of Inflector
+ to call the method on String instead.
+ * test/api_test.rb: add test to ensure API definition not
+ instantiatable
+ * lib/action_service/invocation.rb: change @invocation_params to
+ @method_params
+ * lib/*: update RDoc
+ * lib/action_service/struct.rb: update to support base types
+ * lib/action_service/support/signature.rb: support the notion of
+ "base types" in signatures, with well-known unambiguous names such as :int,
+ :bool, etc, which map to the correct Ruby class. accept the same names
+ used by ActiveRecord as well as longer versions of each, as aliases.
+ * examples/*: update for seperate API definition updates
+ * lib/action_service/*, test/*: extensive refactoring: define API methods in
+ a seperate class, and specify it wherever used with 'service_api'.
+ this makes writing a client API for accessing defined API methods
+ with ActionService really easy.
+ * lib/action_service/container.rb: fix a bug in default call
+ handling for direct dispatching, and add ActionController filter
+ support for direct dispatching.
+ * test/router_action_controller_test.rb: add tests to ensure
+ ActionController filters are actually called.
+ * test/protocol_soap_test.rb: add more tests for direct dispatching.
+
+0.3.0
+
+ * First public release
diff --git a/actionservice/HACKING b/actionservice/HACKING
new file mode 100644
index 0000000000..9c0cde6313
--- /dev/null
+++ b/actionservice/HACKING
@@ -0,0 +1,44 @@
+== Coding Style
+
+Please try to follow Rails conventions and idioms.
+
+
+== Concepts
+
+ * Service
+ A service has an associated API definition, and
+ implements the methods defined in the API definition
+
+ * Container
+ A container contains zero or more services
+
+ * API
+ An API definition defines a list of methods implemented by
+ a service
+
+ * Router
+ A router takes raw wire requests, decodes them, performs the invocation on
+ the service, and generates raw wire responses from the invocation result.
+ A router is mixed into a container class.
+
+ * Protocol
+ A protocol implementation implements the unmarshaling and marshaling of
+ raw wire requests and responses. Registers with router.
+
+
+== Action Pack Integration
+
+For Action Pack, the ActionController is both container and router, and also contains
+the protocol implementations.
+
+
+== Adding support for a new protocol
+
+ 1. Add an ActionService::Protocol::YourProtocol module and any classes you need to
+ perform unmarshaling/marshaling of protocol requests. See the SOAP implementation
+ for an example of a complex mapping, and also see
+ ActionService::Protocol::AbstractProtocol for the methods you need to implement.
+
+ 2. Add unit tests for your new protocol. Be sure to test using a Action Pack test request
+ duplicating how the real requests will arrive and verify that mapping to and from Ruby
+ types works correctly.
diff --git a/actionservice/MIT-LICENSE b/actionservice/MIT-LICENSE
new file mode 100644
index 0000000000..528941e849
--- /dev/null
+++ b/actionservice/MIT-LICENSE
@@ -0,0 +1,21 @@
+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.
+
diff --git a/actionservice/README b/actionservice/README
new file mode 100644
index 0000000000..75d60abd46
--- /dev/null
+++ b/actionservice/README
@@ -0,0 +1,241 @@
+= Action Service -- Serving APIs on rails
+
+Action Service provides a way to publish interoperable web service APIs with
+Rails without spending a lot of time delving into protocol details.
+
+
+== Features
+
+* SOAP RPC protocol support
+* Dynamic WSDL generation for APIs
+* XML-RPC protocol support
+* Clients that use the same API definitions as the server for
+ easy interoperability with other Action Service based applications
+* Type signature hints to improve interoperability with static languages
+* Active Record model class support in signatures
+
+
+== Defining your APIs
+
+You specify the methods you want to make available as API methods in an
+ActionService::API::Base derivative, and then specify this API
+definition class wherever you want to use that API.
+
+The implementation of the methods is done seperately to the API
+specification.
+
+
+==== Method name inflection
+
+Action Service will camelcase the method names according to Rails Inflector
+rules for the API visible to public callers. What this means, for example
+is that the method names in generated WSDL will be camelcased, and callers will
+have to supply the camelcased name in their requests for the request to
+succeed.
+
+If you do not desire this behaviour, you can turn it off with the
+ActionService::API::Base +inflect_names+ option.
+
+
+==== Inflection examples
+
+ :add => Add
+ :find_all => FindAll
+
+
+==== Disabling inflection
+
+ class PersonAPI < ActionService::API::Base
+ inflect_names false
+ end
+
+
+==== API definition example
+
+ class PersonAPI < ActionService::API::Base
+ api_method :add, :expects => [:string, :string, :bool], :returns => [:int]
+ api_method :remove, :expects => [:int], :returns => [:bool]
+ end
+
+==== API usage example
+
+ class PersonController < ActionController::Base
+ service_api PersonAPI
+
+ def add
+ end
+
+ def remove
+ end
+ end
+
+
+== Publishing your APIs
+
+Action Service uses Action Pack to process protocol requests. There are two
+modes of dispatching protocol requests, _Direct_, and _Delegated_.
+
+
+=== Direct dispatching
+
+This is the default mode. In this mode, controller actions implement the API
+methods, and parameters for incoming method calls will be placed in
+<tt>@params</tt> (keyed by name), and <tt>@method_params</tt> (ordered list).
+
+The return value of the action is sent back as the return value to the
+caller.
+
+In this mode, a special <tt>api</tt> action is generated in the target
+controller to unwrap the protocol request, forward it on to the relevant action
+and send back the wrapped return value. <em>This action must not be
+overridden.</em>
+
+==== Direct dispatching example
+
+ class PersonController < ApplicationController
+ service_api PersonAPI
+
+ def add
+ end
+
+ def remove
+ end
+ end
+
+ class PersonAPI < ActionService::API::Base
+ ...
+ end
+
+
+For this example, protocol requests for +Add+ and +Remove+ methods sent to
+<tt>/person/api</tt> will be routed to the actions +add+ and +remove+.
+
+
+=== Delegated dispatching
+
+This mode can be turned on by setting the +service_dispatching_mode+ option
+in a controller.
+
+In this mode, the controller contains one or more service API objects (objects
+that implement an ActionService::API::Base definition). These API
+objects are each mapped onto one controller action only.
+
+==== Delegated dispatching example
+
+ class ApiController < ApplicationController
+ service_dispatching_mode :delegated
+
+ service :person, PersonService.new
+ end
+
+ class PersonService < ActionService::Base
+ service_api PersonAPI
+
+ def add
+ end
+
+ def remove
+ end
+ end
+
+ class PersonAPI < ActionService::API::Base
+ ...
+ end
+
+
+For this example, all protocol requests for +PersonService+ are
+sent to the <tt>/api/person</tt> action.
+
+The <tt>/api/person</tt> action is generated when the +service+
+method is called. <em>This action must not be overridden.</em>
+
+Other controller actions (actions that aren't the target of a +service+ call)
+are ignored for ActionService purposes, and can do normal action tasks.
+
+
+== Using the client support
+
+Action Service includes client classes that can use the same API
+definition as the server. The advantage of this approach is that your client
+will have the same support for Active Record and structured types as the
+server, and can just use them directly, and rely on the marshaling to Do The
+Right Thing.
+
+*Note*: The client support is intended for communication between Ruby on Rails
+applications that both use Action Service. It may work with other servers, but
+that is not its intended use, and interoperability can't be guaranteed, especially
+not for .NET web services.
+
+Web services protocol specifications are complex, and Action Service can only
+be guaranteed to work with a subset.
+
+If you have the need for clients for a complex service not running on Action
+Service, it is recommended that you use +wsdl2ruby+ and generate the client
+stub classes.
+
+==== Factory created client example
+
+ class BlogManagerController < ApplicationController
+ client_api :blogger, :xmlrpc, 'http://url/to/blog/api/RPC2', :handler_name => 'blogger'
+ end
+
+ class SearchingController < ApplicationController
+ client_api :google, :soap, 'http://url/to/blog/api/beta', :service_name => 'GoogleSearch'
+ end
+
+See ActionService::API::ActionController::ClassMethods for more details.
+
+==== Manually created client example
+
+ class PersonAPI < ActionService::API::Base
+ api_method :find_all, :returns => [[Person]]
+ end
+
+ soap_client = ActionService::Client::Soap.new(PersonAPI, "http://...")
+ persons = soap_client.find_all
+
+ class BloggerAPI < ActionService::API::Base
+ inflect_names false
+ api_method :getRecentPosts, :returns => [[Blog::Post]]
+ end
+
+ blog = ActionService::Client::XmlRpc.new(BloggerAPI, "http://.../xmlrpc", :handler_name => "blogger")
+ posts = blog.getRecentPosts
+
+
+See ActionService::Client::Soap and ActionService::Client::XmlRpc for more details.
+
+== Dependencies
+
+Action Service requires that the Action Pack and Active Record are either
+available to be required immediately or are accessible as GEMs.
+
+It also requires a version of Ruby that includes SOAP support in the standard
+library. At least version 1.8.2 final (2004-12-25) of Ruby is recommended, this
+is the version tested against.
+
+
+== Download
+
+The latest Action Service version can be downloaded from
+http://rubyforge.org/projects/actionservice
+
+
+== Installation
+
+You can install Action Service with the following command.
+
+ % [sudo] ruby setup.rb
+
+
+== License
+
+Action Service is released under the MIT license.
+
+
+== Support
+
+The Ruby on Rails mailing list
+
+Or, to contact the author, send mail to bitserf@gmail.com
+
diff --git a/actionservice/Rakefile b/actionservice/Rakefile
new file mode 100644
index 0000000000..9bdd25be2a
--- /dev/null
+++ b/actionservice/Rakefile
@@ -0,0 +1,135 @@
+require 'rubygems'
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rake/contrib/rubyforgepublisher'
+require 'fileutils'
+
+PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
+PKG_NAME = 'actionservice'
+PKG_VERSION = '0.4.0' + PKG_BUILD
+
+desc "Default Task"
+task :default => [ :test ]
+
+
+# Run the unit tests
+Rake::TestTask.new { |t|
+ t.libs << "test"
+ t.pattern = 'test/*_test.rb'
+ t.verbose = true
+}
+
+
+# Generate the RDoc documentation
+Rake::RDocTask.new { |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "Action Service -- Web services for Action Pack"
+ rdoc.options << '--line-numbers --inline-source --main README --accessor class_inheritable_option=RW'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/action_service.rb')
+ rdoc.rdoc_files.include('lib/action_service/*.rb')
+ rdoc.rdoc_files.include('lib/action_service/api/*.rb')
+ rdoc.rdoc_files.include('lib/action_service/client/*.rb')
+ rdoc.rdoc_files.include('lib/action_service/protocol/*.rb')
+ rdoc.rdoc_files.include('lib/action_service/router/*.rb')
+ rdoc.rdoc_files.include('lib/action_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 = "actionservice"
+ s.homepage = "http://rubyforge.org/projects/actionservice"
+
+ s.add_dependency('actionpack', '>= 1.4.0')
+ s.add_dependency('activerecord', '>= 1.6.0')
+ s.add_dependency('activesupport', '>= 0.9.0')
+
+ s.has_rdoc = true
+ s.requirements << 'none'
+ s.require_path = 'lib'
+ s.autorequire = 'action_service'
+
+ s.files = [ "Rakefile", "setup.rb", "README", "TODO", "HACKING", "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
+
+
+desc "Publish API docs to RubyForge"
+task :pdoc => [:rdoc] do
+ FileUtils.mkdir_p 'html'
+ FileUtils.mv 'doc', 'html/api'
+ Rake::RubyForgePublisher.new('actionservice', 'ljb').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_service\.rb$ lib\/action_service\/.*\.rb$]
+ excludes ||= %w[]
+ 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 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
diff --git a/actionservice/TODO b/actionservice/TODO
new file mode 100644
index 0000000000..7a78bd2654
--- /dev/null
+++ b/actionservice/TODO
@@ -0,0 +1,35 @@
+= Post-0.4.0 Tasks
+ - relax type-checking for XML-RPC, and perform casts between base types if there
+ are mismatches (i.e. String received when Integer expected, or vice-versa)
+
+ - support XML-RPC's "handler." method namespacing. perhaps something like:
+
+ class BloggingServices < ActionService::LayeredService
+ def initialize(request)
+ @request = controller.request
+ end
+
+ service :mt {MTService.new(@request)}
+ service :blogger {BloggerService.new(@request)}
+ service :metaWeblog {MetaWeblogService.new(@request)}
+ end
+
+ class ApiController < ApplicationController
+ service_dispatching_mode :delegated
+ service :xmlrpc { BloggingServices.new(@request) }
+ end
+
+
+= Low priority tasks
+ - add better type mapping tests for XML-RPC
+ - add tests for ActiveRecord support (with mock objects?)
+
+= Refactoring
+ - Find an alternative way to map interesting types for SOAP (like ActiveRecord
+ model classes) that doesn't require creation of a sanitized copy object with data
+ copied from the real one. Ideally this would let us get rid of
+ ActionService::Struct altogether and provide a block that would yield the
+ attributes and values. "Filters" ? Not sure how to integrate with SOAP though.
+
+ - Don't have clean way to go from SOAP Class object to the xsd:NAME type
+ string -- NaHi possibly looking at remedying this situation
diff --git a/actionservice/examples/googlesearch/README b/actionservice/examples/googlesearch/README
new file mode 100644
index 0000000000..20ecbc3a76
--- /dev/null
+++ b/actionservice/examples/googlesearch/README
@@ -0,0 +1,151 @@
+= Google Service example
+
+
+This example shows how one would implement an API like Google
+Search that uses lots of structured types.
+
+There are examples for "Direct" and "Delegated" dispatching
+modes.
+
+There is also an example for API definition file autoloading.
+
+= Running
+
+ 1. Ensure you have the 'actionservice' Gem installed. You can generate it using
+ this command:
+
+ $ rake package
+
+
+ 2. Edit config/environment.rb, and add the following line after the rest of the
+ require_gem statements:
+
+ require_gem 'actionservice'
+
+
+ 3. "Direct" example:
+
+ * Copy direct/search_controller.rb to "app/controllers"
+ in a Rails project.
+
+ "Delegated" example:
+
+ * Copy delegated/search_controller.rb to "app/controllers"
+ in a Rails project.
+ * Copy delegated/google_search_service.rb to "lib"
+ in a Rails project.
+
+ "Autoloading" example:
+
+ * Copy autoloading/google_search_api.rb to "app/apis" (create the directory
+ if it doesn't exist) in a Rails project.
+
+ * Copy autoloading/google_search_controller.rb "app/controllers"
+ in a Rails project.
+
+
+ 4. Go to the WSDL url in a browser, and check that it looks correct.
+
+ "Direct" and "Delegated" examples:
+ http://url_to_project/search/wsdl
+
+ "Autoloading" example:
+ http://url_to_project/google_search/wsdl
+
+ You can compare it to Google's hand-coded WSDL at http://api.google.com/GoogleSearch.wsdl
+ and see how close (or not) the generated version is.
+
+ Note that I used GoogleSearch as the canonical "best practice"
+ interoperable example when implementing WSDL/SOAP support, which might
+ explain extreme similarities :)
+
+
+ 5. Test that it works with .NET (Mono in this example):
+
+ $ wget WSDL_URL
+ $ mv wsdl GoogleSearch.wsdl
+ $ wsdl -out:GoogleSearch.cs GoogleSearch.wsdl
+
+ Add these lines to the GoogleSearchService class body (be mindful of the
+ wrapping):
+
+ public static void Main(string[] args)
+ {
+ GoogleSearchResult result;
+ GoogleSearchService service;
+
+ service = new GoogleSearchService();
+ result = service.doGoogleSearch("myApiKey", "my query", 10, 30, true, "restrict", false, "lr", "ie", "oe");
+ System.Console.WriteLine("documentFiltering: {0}", result.documentFiltering);
+ System.Console.WriteLine("searchComments: {0}", result.searchComments);
+ System.Console.WriteLine("estimatedTotalResultsCount: {0}", result.estimatedTotalResultsCount);
+ System.Console.WriteLine("estimateIsExact: {0}", result.estimateIsExact);
+ System.Console.WriteLine("resultElements:");
+ foreach (ResultElement element in result.resultElements) {
+ System.Console.WriteLine("\tsummary: {0}", element.summary);
+ System.Console.WriteLine("\tURL: {0}", element.URL);
+ System.Console.WriteLine("\tsnippet: {0}", element.snippet);
+ System.Console.WriteLine("\ttitle: {0}", element.title);
+ System.Console.WriteLine("\tcachedSize: {0}", element.cachedSize);
+ System.Console.WriteLine("\trelatedInformationPresent: {0}", element.relatedInformationPresent);
+ System.Console.WriteLine("\thostName: {0}", element.hostName);
+ System.Console.WriteLine("\tdirectoryCategory: {0}", element.directoryCategory.fullViewableName);
+ System.Console.WriteLine("\tdirectoryTitle: {0}", element.directoryTitle);
+ }
+ System.Console.WriteLine("searchQuery: {0}", result.searchQuery);
+ System.Console.WriteLine("startIndex: {0}", result.startIndex);
+ System.Console.WriteLine("endIndex: {0}", result.endIndex);
+ System.Console.WriteLine("searchTips: {0}", result.searchTips);
+ System.Console.WriteLine("directoryCategories:");
+ foreach (DirectoryCategory cat in result.directoryCategories) {
+ System.Console.WriteLine("\t{0} ({1})", cat.fullViewableName, cat.specialEncoding);
+ }
+ System.Console.WriteLine("searchTime: {0}", result.searchTime);
+ }
+
+ Now compile and run:
+
+ $ mcs -reference:System.Web.Services GoogleSearch.cs
+ $ mono GoogleSearch.exe
+
+
+ If you had the application running (on the same host you got
+ the WSDL from), you should see something like this:
+
+
+ documentFiltering: True
+ searchComments:
+ estimatedTotalResultsCount: 322000
+ estimateIsExact: False
+ resultElements:
+ summary: ONlamp.com: Rolling with Ruby on Rails
+ URL: http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html
+ snippet: Curt Hibbs shows off Ruby on Rails by building a simple ...
+ title: Teh Railz0r
+ cachedSize: Almost no lines of code!
+ relatedInformationPresent: True
+ hostName: rubyonrails.com
+ directoryCategory: Web Development
+ directoryTitle:
+ searchQuery: http://www.google.com/search?q=ruby+on+rails
+ startIndex: 10
+ endIndex: 40
+ searchTips: "on" is a very common word and was not included in your search [details]
+ directoryCategories:
+ Web Development (UTF-8)
+ Programming (US-ASCII)
+ searchTime: 1E-06
+
+
+ Also, if an API method throws an exception, it will be sent back to the
+ caller in the protocol's exception format, so they should get an exception
+ thrown on their side with a meaningful error message.
+
+ If you don't like this behaviour, you can do:
+
+ class MyController < ActionController::Base
+ service_exception_reporting false
+ end
+
+ 6. Crack open a beer. Publishing APIs for working with the same model as
+ your Rails web app should be easy from now on :)
diff --git a/actionservice/examples/googlesearch/autoloading/google_search_api.rb b/actionservice/examples/googlesearch/autoloading/google_search_api.rb
new file mode 100644
index 0000000000..e7e33a1105
--- /dev/null
+++ b/actionservice/examples/googlesearch/autoloading/google_search_api.rb
@@ -0,0 +1,50 @@
+class DirectoryCategory < ActionService::Struct
+ member :fullViewableName, :string
+ member :specialEncoding, :string
+end
+
+class ResultElement < ActionService::Struct
+ member :summary, :string
+ member :URL, :string
+ member :snippet, :string
+ member :title, :string
+ member :cachedSize, :string
+ member :relatedInformationPresent, :bool
+ member :hostName, :string
+ member :directoryCategory, DirectoryCategory
+ member :directoryTitle, :string
+end
+
+class GoogleSearchResult < ActionService::Struct
+ member :documentFiltering, :bool
+ member :searchComments, :string
+ member :estimatedTotalResultsCount, :int
+ member :estimateIsExact, :bool
+ member :resultElements, [ResultElement]
+ member :searchQuery, :string
+ member :startIndex, :int
+ member :endIndex, :int
+ member :searchTips, :string
+ member :directoryCategories, [DirectoryCategory]
+ member :searchTime, :float
+end
+
+class GoogleSearchAPI < ActionService::API::Base
+ inflect_names false
+
+ api_method :doGetCachedPage, :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
+ api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]
+
+ api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
+ {:key=>:string},
+ {:q=>:string},
+ {:start=>:int},
+ {:maxResults=>:int},
+ {:filter=>:bool},
+ {:restrict=>:string},
+ {:safeSearch=>:bool},
+ {:lr=>:string},
+ {:ie=>:string},
+ {:oe=>:string}
+ ]
+end
diff --git a/actionservice/examples/googlesearch/autoloading/google_search_controller.rb b/actionservice/examples/googlesearch/autoloading/google_search_controller.rb
new file mode 100644
index 0000000000..c62e869df5
--- /dev/null
+++ b/actionservice/examples/googlesearch/autoloading/google_search_controller.rb
@@ -0,0 +1,57 @@
+class GoogleSearchController < ApplicationController
+ wsdl_service_name 'GoogleSearch'
+
+ def doGetCachedPage
+ "<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
+ end
+
+ def doSpellingSuggestion
+ "%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
+ end
+
+ def doGoogleSearch
+ resultElement = ResultElement.new
+ resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
+ resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
+ resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
+ "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
+ resultElement.title = "Teh Railz0r"
+ resultElement.cachedSize = "Almost no lines of code!"
+ resultElement.relatedInformationPresent = true
+ resultElement.hostName = "rubyonrails.com"
+ resultElement.directoryCategory = category("Web Development", "UTF-8")
+
+ result = GoogleSearchResult.new
+ result.documentFiltering = @params['filter']
+ result.searchComments = ""
+ result.estimatedTotalResultsCount = 322000
+ result.estimateIsExact = false
+ result.resultElements = [resultElement]
+ result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
+ result.startIndex = @params['start']
+ result.endIndex = @params['start'] + @params['maxResults']
+ result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
+ result.searchTime = 0.000001
+
+ # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
+ # the Ruby SOAP collapses them into one instance and uses references all over the
+ # place, confusing Mono.
+ #
+ # This has recently been fixed:
+ # http://bugzilla.ximian.com/show_bug.cgi?id=72265
+ result.directoryCategories = [
+ category("Web Development", "UTF-8"),
+ category("Programming", "US-ASCII"),
+ ]
+
+ result
+ end
+
+ private
+ def category(name, encoding)
+ cat = DirectoryCategory.new
+ cat.fullViewableName = name.dup
+ cat.specialEncoding = encoding.dup
+ cat
+ end
+end
diff --git a/actionservice/examples/googlesearch/delegated/google_search_service.rb b/actionservice/examples/googlesearch/delegated/google_search_service.rb
new file mode 100644
index 0000000000..84faf220ae
--- /dev/null
+++ b/actionservice/examples/googlesearch/delegated/google_search_service.rb
@@ -0,0 +1,110 @@
+require 'action_service'
+
+class DirectoryCategory < ActionService::Struct
+ member :fullViewableName, :string
+ member :specialEncoding, :string
+end
+
+class ResultElement < ActionService::Struct
+ member :summary, :string
+ member :URL, :string
+ member :snippet, :string
+ member :title, :string
+ member :cachedSize, :string
+ member :relatedInformationPresent, :bool
+ member :hostName, :string
+ member :directoryCategory, DirectoryCategory
+ member :directoryTitle, :string
+end
+
+class GoogleSearchResult < ActionService::Struct
+ member :documentFiltering, :bool
+ member :searchComments, :string
+ member :estimatedTotalResultsCount, :int
+ member :estimateIsExact, :bool
+ member :resultElements, [ResultElement]
+ member :searchQuery, :string
+ member :startIndex, :int
+ member :endIndex, :int
+ member :searchTips, :string
+ member :directoryCategories, [DirectoryCategory]
+ member :searchTime, :float
+end
+
+class GoogleSearchAPI < ActionService::API::Base
+ inflect_names false
+
+ api_method :doGetCachedPage, :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
+ api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]
+
+ api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
+ {:key=>:string},
+ {:q=>:string},
+ {:start=>:int},
+ {:maxResults=>:int},
+ {:filter=>:bool},
+ {:restrict=>:string},
+ {:safeSearch=>:bool},
+ {:lr=>:string},
+ {:ie=>:string},
+ {:oe=>:string}
+ ]
+end
+
+class GoogleSearchService < ActionService::Base
+ service_api GoogleSearchAPI
+
+ def doGetCachedPage(key, url)
+ "<html><body>i am a cached page</body></html>"
+ end
+
+ def doSpellingSuggestion(key, phrase)
+ "Did you mean 'teh'?"
+ end
+
+ def doGoogleSearch(key, q, start, maxResults, filter, restrict, safeSearch, lr, ie, oe)
+ resultElement = ResultElement.new
+ resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
+ resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
+ resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
+ "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
+ resultElement.title = "Teh Railz0r"
+ resultElement.cachedSize = "Almost no lines of code!"
+ resultElement.relatedInformationPresent = true
+ resultElement.hostName = "rubyonrails.com"
+ resultElement.directoryCategory = category("Web Development", "UTF-8")
+
+ result = GoogleSearchResult.new
+ result.documentFiltering = filter
+ result.searchComments = ""
+ result.estimatedTotalResultsCount = 322000
+ result.estimateIsExact = false
+ result.resultElements = [resultElement]
+ result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
+ result.startIndex = start
+ result.endIndex = start + maxResults
+ result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
+ result.searchTime = 0.000001
+
+ # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
+ # the Ruby SOAP collapses them into one instance and uses references all over the
+ # place, confusing Mono.
+ #
+ # This has recently been fixed:
+ # http://bugzilla.ximian.com/show_bug.cgi?id=72265
+ result.directoryCategories = [
+ category("Web Development", "UTF-8"),
+ category("Programming", "US-ASCII"),
+ ]
+
+ result
+ end
+
+ private
+ def category(name, encoding)
+ cat = DirectoryCategory.new
+ cat.fullViewableName = name.dup
+ cat.specialEncoding = encoding.dup
+ cat
+ end
+end
diff --git a/actionservice/examples/googlesearch/delegated/search_controller.rb b/actionservice/examples/googlesearch/delegated/search_controller.rb
new file mode 100644
index 0000000000..58c8321ef2
--- /dev/null
+++ b/actionservice/examples/googlesearch/delegated/search_controller.rb
@@ -0,0 +1,7 @@
+require 'google_search_service'
+
+class SearchController < ApplicationController
+ wsdl_service_name 'GoogleSearch'
+ service_dispatching_mode :delegated
+ service :beta3, GoogleSearchService.new
+end
diff --git a/actionservice/examples/googlesearch/direct/search_controller.rb b/actionservice/examples/googlesearch/direct/search_controller.rb
new file mode 100644
index 0000000000..e4e1d7a0eb
--- /dev/null
+++ b/actionservice/examples/googlesearch/direct/search_controller.rb
@@ -0,0 +1,109 @@
+class DirectoryCategory < ActionService::Struct
+ member :fullViewableName, :string
+ member :specialEncoding, :string
+end
+
+class ResultElement < ActionService::Struct
+ member :summary, :string
+ member :URL, :string
+ member :snippet, :string
+ member :title, :string
+ member :cachedSize, :string
+ member :relatedInformationPresent, :bool
+ member :hostName, :string
+ member :directoryCategory, DirectoryCategory
+ member :directoryTitle, :string
+end
+
+class GoogleSearchResult < ActionService::Struct
+ member :documentFiltering, :bool
+ member :searchComments, :string
+ member :estimatedTotalResultsCount, :int
+ member :estimateIsExact, :bool
+ member :resultElements, [ResultElement]
+ member :searchQuery, :string
+ member :startIndex, :int
+ member :endIndex, :int
+ member :searchTips, :string
+ member :directoryCategories, [DirectoryCategory]
+ member :searchTime, :float
+end
+
+class GoogleSearchAPI < ActionService::API::Base
+ inflect_names false
+
+ api_method :doGetCachedPage, :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
+ api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]
+
+ api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
+ {:key=>:string},
+ {:q=>:string},
+ {:start=>:int},
+ {:maxResults=>:int},
+ {:filter=>:bool},
+ {:restrict=>:string},
+ {:safeSearch=>:bool},
+ {:lr=>:string},
+ {:ie=>:string},
+ {:oe=>:string}
+ ]
+end
+
+class SearchController < ApplicationController
+ service_api GoogleSearchAPI
+ wsdl_service_name 'GoogleSearch'
+
+ def doGetCachedPage
+ "<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
+ end
+
+ def doSpellingSuggestion
+ "%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
+ end
+
+ def doGoogleSearch
+ resultElement = ResultElement.new
+ resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
+ resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
+ resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
+ "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
+ resultElement.title = "Teh Railz0r"
+ resultElement.cachedSize = "Almost no lines of code!"
+ resultElement.relatedInformationPresent = true
+ resultElement.hostName = "rubyonrails.com"
+ resultElement.directoryCategory = category("Web Development", "UTF-8")
+
+ result = GoogleSearchResult.new
+ result.documentFiltering = @params['filter']
+ result.searchComments = ""
+ result.estimatedTotalResultsCount = 322000
+ result.estimateIsExact = false
+ result.resultElements = [resultElement]
+ result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
+ result.startIndex = @params['start']
+ result.endIndex = @params['start'] + @params['maxResults']
+ result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
+ result.searchTime = 0.000001
+
+ # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
+ # the Ruby SOAP collapses them into one instance and uses references all over the
+ # place, confusing Mono.
+ #
+ # This has recently been fixed:
+ # http://bugzilla.ximian.com/show_bug.cgi?id=72265
+ result.directoryCategories = [
+ category("Web Development", "UTF-8"),
+ category("Programming", "US-ASCII"),
+ ]
+
+ result
+ end
+
+ private
+ def category(name, encoding)
+ cat = DirectoryCategory.new
+ cat.fullViewableName = name.dup
+ cat.specialEncoding = encoding.dup
+ cat
+ end
+end
diff --git a/actionservice/examples/metaWeblog/README b/actionservice/examples/metaWeblog/README
new file mode 100644
index 0000000000..f8a56d7018
--- /dev/null
+++ b/actionservice/examples/metaWeblog/README
@@ -0,0 +1,28 @@
+= metaWeblog example
+
+
+This example shows how one might begin to go about adding metaWeblog
+(http://www.xmlrpc.com/metaWeblogApi) API support to a Rails-based
+blogging application.
+
+
+= Running
+
+ 1. Ensure you have the 'actionservice' Gem installed. You can generate it using
+ this command:
+
+ $ rake package
+
+
+ 2. Edit config/environment.rb, and add the following line after the rest of the
+ require_gem statements:
+
+ require_gem 'actionservice'
+
+
+ 3. Copy blog_controller.rb to "app/controllers" in a Rails project.
+
+
+ 4. Fire up a desktop blogging application (such as BloGTK on Linux),
+ point it at http://localhost:3000/blog/api, and try creating or
+ editing blog posts.
diff --git a/actionservice/examples/metaWeblog/blog_controller.rb b/actionservice/examples/metaWeblog/blog_controller.rb
new file mode 100644
index 0000000000..b23ccb04c6
--- /dev/null
+++ b/actionservice/examples/metaWeblog/blog_controller.rb
@@ -0,0 +1,121 @@
+# structures as defined by the metaWeblog/blogger
+# specifications.
+module Blog
+ class Enclosure < ActionService::Struct
+ member :url, :string
+ member :length, :int
+ member :type, :string
+ end
+
+ class Source < ActionService::Struct
+ member :url, :string
+ member :name, :string
+ end
+
+ class Post < ActionService::Struct
+ member :title, :string
+ member :link, :string
+ member :description, :string
+ member :author, :string
+ member :category, :string
+ member :comments, :string
+ member :enclosure, Enclosure
+ member :guid, :string
+ member :pubDate, :string
+ member :source, Source
+ end
+
+ class Blog < ActionService::Struct
+ member :url, :string
+ member :blogid, :string
+ member :blogName, :string
+ end
+end
+
+# skeleton metaWeblog API
+class MetaWeblogAPI < ActionService::API::Base
+ inflect_names false
+
+ api_method :newPost, :returns => [:string], :expects => [
+ {:blogid=>:string},
+ {:username=>:string},
+ {:password=>:string},
+ {:struct=>Blog::Post},
+ {:publish=>:bool},
+ ]
+
+ api_method :editPost, :returns => [:bool], :expects => [
+ {:postid=>:string},
+ {:username=>:string},
+ {:password=>:string},
+ {:struct=>Blog::Post},
+ {:publish=>:bool},
+ ]
+
+ api_method :getPost, :returns => [Blog::Post], :expects => [
+ {:postid=>:string},
+ {:username=>:string},
+ {:password=>:string},
+ ]
+
+ api_method :getUsersBlogs, :returns => [[Blog::Blog]], :expects => [
+ {:appkey=>:string},
+ {:username=>:string},
+ {:password=>:string},
+ ]
+
+ api_method :getRecentPosts, :returns => [[Blog::Post]], :expects => [
+ {:blogid=>:string},
+ {:username=>:string},
+ {:password=>:string},
+ {:numberOfPosts=>:int},
+ ]
+end
+
+class BlogController < ApplicationController
+ service_api MetaWeblogAPI
+
+ def initialize
+ @postid = 0
+ end
+
+ def newPost
+ $stderr.puts 'Creating post: username=%s password=%s struct=%s' % [
+ @params['username'],
+ @params['password'],
+ @params['struct'].inspect
+ ]
+ (@postid += 1).to_s
+ end
+
+ def editPost
+ $stderr.puts 'Editing post: username=%s password=%s struct=%s' % [
+ @params['username'],
+ @params['password'],
+ @params['struct'].inspect
+ ]
+ true
+ end
+
+ def getUsersBlogs
+ $stderr.puts "Returning user %s's blogs" % @params['username']
+ blog = Blog::Blog.new
+ blog.url = 'http://blog.xeraph.org'
+ blog.blogid = 'sttm'
+ blog.blogName = 'slave to the machine'
+ [blog]
+ end
+
+ def getRecentPosts
+ $stderr.puts "Returning recent posts (%d requested)" % @params['numberOfPosts']
+ post1 = Blog::Post.new
+ post1.title = 'first post!'
+ post1.link = 'http://blog.xeraph.org/testOne.html'
+ post1.description = 'this is the first post'
+ post2 = Blog::Post.new
+ post2.title = 'second post!'
+ post2.link = 'http://blog.xeraph.org/testTwo.html'
+ post2.description = 'this is the second post'
+ [post1, post2]
+ end
+end
diff --git a/actionservice/lib/action_service.rb b/actionservice/lib/action_service.rb
new file mode 100644
index 0000000000..005e829e7b
--- /dev/null
+++ b/actionservice/lib/action_service.rb
@@ -0,0 +1,60 @@
+#--
+# Copyright (C) 2005 Leon Breedt
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+begin
+ require 'active_support'
+ require 'action_controller'
+ require 'active_record'
+rescue LoadError
+ require 'rubygems'
+ require_gem 'activesupport', '>= 0.9.0'
+ require_gem 'actionpack', '>= 1.4.0'
+ require_gem 'activerecord', '>= 1.6.0'
+end
+
+$:.unshift(File.dirname(__FILE__))
+
+require 'action_service/base'
+require 'action_service/client'
+require 'action_service/invocation'
+require 'action_service/api'
+require 'action_service/struct'
+require 'action_service/container'
+require 'action_service/protocol'
+require 'action_service/router'
+
+ActionService::Base.class_eval do
+ include ActionService::API
+ include ActionService::Invocation
+end
+
+ActionController::Base.class_eval do
+ include ActionService::Container
+ include ActionService::Protocol::Registry
+ include ActionService::Protocol::Soap
+ include ActionService::Protocol::XmlRpc
+ include ActionService::API
+ include ActionService::API::ActionController
+ include ActionService::Router::ActionController
+ include ActionService::Router::Wsdl
+end
diff --git a/actionservice/lib/action_service/api.rb b/actionservice/lib/action_service/api.rb
new file mode 100644
index 0000000000..61f36fff56
--- /dev/null
+++ b/actionservice/lib/action_service/api.rb
@@ -0,0 +1,2 @@
+require 'action_service/api/abstract'
+require 'action_service/api/action_controller'
diff --git a/actionservice/lib/action_service/api/abstract.rb b/actionservice/lib/action_service/api/abstract.rb
new file mode 100644
index 0000000000..33ed603bfe
--- /dev/null
+++ b/actionservice/lib/action_service/api/abstract.rb
@@ -0,0 +1,198 @@
+module ActionService # :nodoc:
+ module API # :nodoc:
+ class APIError < ActionService::ActionServiceError # :nodoc:
+ end
+
+ def self.append_features(base) # :nodoc:
+ super
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ # Attaches ActionService API +definition+ to the calling class.
+ #
+ # If +definition+ is not an ActionService::API::Base derivative class
+ # object, it may be a symbol or a string, in which case a file named
+ # <tt>definition_api.rb</tt> will be expected to exist in the load path,
+ # containing an API definition class named <tt>DefinitionAPI</tt> or
+ # <tt>DefinitionApi</tt>.
+ #
+ # Action Controllers can have a default associated API, removing the need
+ # to call this method if you follow the Action Service naming conventions.
+ #
+ # A controller with a class name of GoogleSearchController will
+ # implicitly load <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 < ActionService::Base
+ # service_api MyAPI
+ # end
+ #
+ # class MyAPI < ActionService::API::Base
+ # ...
+ # end
+ #
+ # ==== Controller class example
+ #
+ # class MyController < ActionController::Base
+ # service_api MyAPI
+ # end
+ #
+ # class MyAPI < ActionService::API::Base
+ # ...
+ # end
+ def service_api(definition=nil)
+ if definition.nil?
+ read_inheritable_attribute("service_api")
+ else
+ if definition.is_a?(Symbol)
+ raise(APIError, "symbols can only be used for #service_api inside of a controller")
+ end
+ unless definition.respond_to?(:ancestors) && definition.ancestors.include?(Base)
+ raise(APIError, "#{definition.to_s} is not a valid API definition")
+ end
+ write_inheritable_attribute("service_api", definition)
+ call_service_api_callbacks(self, definition)
+ end
+ end
+
+ def add_service_api_callback(&block) # :nodoc:
+ write_inheritable_array("service_api_callbacks", [block])
+ end
+
+ private
+ def call_service_api_callbacks(container_class, definition)
+ (read_inheritable_attribute("service_api_callbacks") || []).each do |block|
+ block.call(container_class, definition)
+ end
+ end
+ end
+
+ # A 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 service implementation classes like ActionService::Base
+ # and ActionController::Base derivatives with ClassMethods#service_api.
+ class Base
+ # Whether to transform API method names into camel-cased
+ # names
+ class_inheritable_option :inflect_names, true
+
+ # If present, the name of a method to call when the remote caller
+ # tried to call a nonexistent method. Semantically equivalent to
+ # +method_missing+.
+ class_inheritable_option :default_api_method
+
+ # Disallow instantiation
+ private_class_method :new, :allocate
+
+ class << self
+ include ActionService::Signature
+
+ # API methods have a +name+, which must be the Ruby method name to use when
+ # performing the invocation on the service object.
+ #
+ # The type signature hints for the method input parameters and return value
+ # can by specified in +options+.
+ #
+ # A signature hint is an array of one or more parameter type specifiers.
+ # A type specifier can be one of the following:
+ #
+ # * A symbol or string of representing one of the Action Service base types.
+ # See ActionService::Signature for a canonical list of the base types.
+ # * The Class object of the parameter type
+ # * A single-element Array containing one of the two preceding items. This
+ # will cause Action Service to treat the parameter at that position
+ # as an array containing only values of the given type.
+ # * A Hash containing as key the name of the parameter, and as value
+ # one of the three preceding items
+ #
+ # If no method input parameter or method return value hints are given,
+ # the method is assumed to take no parameters and 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 hint for the method input parameters
+ # [<tt>:returns</tt>] Signature hint for the method return value
+ # [<tt>:expects_and_returns</tt>] Signature hint for both input parameters and return value
+ def api_method(name, options={})
+ validate_options([:expects, :returns, :expects_and_returns], options.keys)
+ if options[:expects_and_returns]
+ expects = options[:expects_and_returns]
+ returns = options[:expects_and_returns]
+ else
+ expects = options[:expects]
+ returns = options[:returns]
+ end
+ expects = canonical_signature(expects) if expects
+ returns = canonical_signature(returns) if returns
+ if expects && Object.const_defined?('ActiveRecord')
+ expects.each do |param|
+ klass = signature_parameter_class(param)
+ klass = klass[0] if klass.is_a?(Array)
+ if klass.ancestors.include?(ActiveRecord::Base)
+ raise(ActionServiceError, "ActiveRecord model classes not allowed in :expects")
+ end
+ end
+ end
+ name = name.to_sym
+ public_name = public_api_method_name(name)
+ info = { :expects => expects, :returns => returns }
+ write_inheritable_hash("api_methods", name => info)
+ write_inheritable_hash("api_public_method_names", public_name => name)
+ end
+
+ # Whether the given method name is a service method on this API
+ def has_api_method?(name)
+ api_methods.has_key?(name)
+ end
+
+ # Whether the given public method name has a corresponding service method
+ # on this API
+ def has_public_api_method?(public_name)
+ api_public_method_names.has_key?(public_name)
+ end
+
+ # The corresponding public method name for the given service method name
+ def public_api_method_name(name)
+ if inflect_names
+ name.to_s.camelize
+ else
+ name.to_s
+ end
+ end
+
+ # The corresponding service method name for the given public method name
+ def api_method_name(public_name)
+ api_public_method_names[public_name]
+ end
+
+ # A Hash containing all service methods on this API, and their
+ # associated metadata.
+ def api_methods
+ read_inheritable_attribute("api_methods") || {}
+ end
+
+ private
+ def api_public_method_names
+ read_inheritable_attribute("api_public_method_names") || {}
+ end
+
+ def validate_options(valid_option_keys, supplied_option_keys)
+ unknown_option_keys = supplied_option_keys - valid_option_keys
+ unless unknown_option_keys.empty?
+ raise(ActionServiceError, "Unknown options: #{unknown_option_keys}")
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/api/action_controller.rb b/actionservice/lib/action_service/api/action_controller.rb
new file mode 100644
index 0000000000..7ea0a0d3bd
--- /dev/null
+++ b/actionservice/lib/action_service/api/action_controller.rb
@@ -0,0 +1,97 @@
+module ActionService # :nodoc:
+ module API # :nodoc:
+ module ActionController # :nodoc:
+ def self.append_features(base) # :nodoc:
+ base.class_eval do
+ class << self
+ alias_method :inherited_without_api, :inherited
+ alias_method :service_api_without_require, :service_api
+ end
+ end
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ # Creates a _protected_ factory method with the given
+ # +name+. This method will create a +protocol+ client connected
+ # to the given endpoint URL.
+ #
+ # ==== Example
+ #
+ # class MyController < ActionController::Base
+ # 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.
+ #
+ # The same rules as ActionService::API::Base#service_api are
+ # used to retrieve the API definition with the given +name+.
+ #
+ # +options+ is the set of protocol client specific options,
+ # see the 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, you can
+ # pass through the API definition class in +options+, using
+ # a key of <tt>:api</tt>
+ def client_api(name, protocol, endpoint_uri, options={})
+ unless method_defined?(name)
+ api_klass = options.delete(:api) || require_api(name)
+ class_eval do
+ define_method(name) do
+ probe_protocol_client(api_klass, protocol, endpoint_uri, options)
+ end
+ protected name
+ end
+ end
+ end
+
+ def service_api(definition=nil) # :nodoc:
+ return service_api_without_require if definition.nil?
+ case definition
+ when String, Symbol
+ klass = require_api(definition)
+ else
+ klass = definition
+ end
+ service_api_without_require(klass)
+ end
+
+ def require_api(name) # :nodoc:
+ case name
+ when String, Symbol
+ file_name = name.to_s.underscore + "_api"
+ class_name = file_name.camelize
+ class_names = [class_name, class_name.sub(/Api$/, 'API')]
+ begin
+ require_dependency(file_name)
+ rescue LoadError => load_error
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
+ raise LoadError, requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}"
+ end
+ klass = nil
+ class_names.each do |name|
+ klass = name.constantize rescue nil
+ break unless klass.nil?
+ end
+ unless klass
+ raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found")
+ end
+ klass
+ else
+ raise(ArgumentError, "expected String or Symbol argument")
+ end
+ end
+
+ private
+ def inherited(child)
+ inherited_without_api(child)
+ child.service_api(child.controller_path)
+ rescue Exception => e
+ end
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/base.rb b/actionservice/lib/action_service/base.rb
new file mode 100644
index 0000000000..c3a1747106
--- /dev/null
+++ b/actionservice/lib/action_service/base.rb
@@ -0,0 +1,41 @@
+require 'action_service/support/class_inheritable_options'
+require 'action_service/support/signature'
+
+module ActionService # :nodoc:
+ class ActionServiceError < StandardError # :nodoc:
+ end
+
+ # An Action Service object implements a specified API.
+ #
+ # Used by controllers operating in _Delegated_ dispatching mode.
+ #
+ # ==== Example
+ #
+ # class PersonService < ActionService::Base
+ # service_api PersonAPI
+ #
+ # def find_person(criteria)
+ # Person.find_all [...]
+ # end
+ #
+ # def delete_person(id)
+ # Person.find_by_id(id).destroy
+ # end
+ # end
+ #
+ # class PersonAPI < ActionService::API::Base
+ # api_method :find_person, :expects => [SearchCriteria], :returns => [[Person]]
+ # api_method :delete_person, :expects => [:int]
+ # end
+ #
+ # class SearchCriteria < ActionStruct::Base
+ # member :firstname, :string
+ # member :lastname, :string
+ # member :email, :string
+ # end
+ class Base
+ # Whether to report exceptions back to the caller in the protocol's exception
+ # format
+ class_inheritable_option :service_exception_reporting, true
+ end
+end
diff --git a/actionservice/lib/action_service/client.rb b/actionservice/lib/action_service/client.rb
new file mode 100644
index 0000000000..ce91529f20
--- /dev/null
+++ b/actionservice/lib/action_service/client.rb
@@ -0,0 +1,3 @@
+require 'action_service/client/base'
+require 'action_service/client/soap'
+require 'action_service/client/xmlrpc'
diff --git a/actionservice/lib/action_service/client/base.rb b/actionservice/lib/action_service/client/base.rb
new file mode 100644
index 0000000000..955887a4d8
--- /dev/null
+++ b/actionservice/lib/action_service/client/base.rb
@@ -0,0 +1,35 @@
+module ActionService # :nodoc:
+ module Client # :nodoc:
+ class ClientError < StandardError # :nodoc:
+ end
+
+ class Base # :nodoc:
+ def initialize(api, endpoint_uri)
+ @api = api
+ @endpoint_uri = endpoint_uri
+ end
+
+ def method_missing(name, *args) # :nodoc:
+ call_name = method_name(name)
+ return super(name, *args) if call_name.nil?
+ perform_invocation(call_name, args)
+ end
+
+ protected
+ def perform_invocation(method_name, args) # :nodoc:
+ raise NotImplementedError, "use a protocol-specific client"
+ end
+
+ private
+ def method_name(name)
+ if @api.has_api_method?(name.to_sym)
+ name.to_s
+ elsif @api.has_public_api_method?(name.to_s)
+ @api.api_method_name(name.to_s).to_s
+ else
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/client/soap.rb b/actionservice/lib/action_service/client/soap.rb
new file mode 100644
index 0000000000..c617f36589
--- /dev/null
+++ b/actionservice/lib/action_service/client/soap.rb
@@ -0,0 +1,87 @@
+require 'soap/rpc/driver'
+require 'uri'
+
+module ActionService # :nodoc:
+ module Client # :nodoc:
+
+ # Implements SOAP client support (using RPC encoding for the messages).
+ #
+ # ==== Example Usage
+ #
+ # class PersonAPI < ActionService::API::Base
+ # api_method :find_all, :returns => [[Person]]
+ # end
+ #
+ # soap_client = ActionService::Client::Soap.new(PersonAPI, "http://...")
+ # persons = soap_client.find_all
+ #
+ class Soap < Base
+
+ # Creates a new web service client using the SOAP RPC protocol.
+ #
+ # +api+ must be an ActionService::API::Base derivative, and
+ # +endpoint_uri+ must point at the relevant URL to which protocol requests
+ # will be sent with HTTP POST.
+ #
+ # Valid options:
+ # [<tt>:service_name</tt>] If the remote server has used a custom +wsdl_service_name+
+ # option, you must specify it here
+ def initialize(api, endpoint_uri, options={})
+ super(api, endpoint_uri)
+ @service_name = options[:service_name] || 'ActionService'
+ @namespace = "urn:#{@service_name}"
+ @mapper = ActionService::Protocol::Soap::SoapMapper.new(@namespace)
+ @protocol = ActionService::Protocol::Soap::SoapProtocol.new(@mapper)
+ @soap_action_base = options[:soap_action_base]
+ @soap_action_base ||= URI.parse(endpoint_uri).path
+ @driver = create_soap_rpc_driver(api, endpoint_uri)
+ end
+
+ protected
+ def perform_invocation(method_name, args)
+ @driver.send(method_name, *args)
+ end
+
+ def soap_action(method_name)
+ "#{@soap_action_base}/#{method_name}"
+ end
+
+ private
+ def create_soap_rpc_driver(api, endpoint_uri)
+ @mapper.map_api(api)
+ driver = SoapDriver.new(endpoint_uri, nil)
+ driver.mapping_registry = @mapper.registry
+ api.api_methods.each do |name, info|
+ public_name = api.public_api_method_name(name)
+ qname = XSD::QName.new(@namespace, public_name)
+ action = soap_action(public_name)
+ expects = info[:expects]
+ returns = info[:returns]
+ param_def = []
+ i = 1
+ if expects
+ expects.each do |klass|
+ param_name = klass.is_a?(Hash) ? klass.keys[0] : "param#{i}"
+ mapping = @mapper.lookup(klass)
+ param_def << ['in', param_name, mapping.registry_mapping]
+ i += 1
+ end
+ end
+ if returns
+ mapping = @mapper.lookup(returns[0])
+ param_def << ['retval', 'return', mapping.registry_mapping]
+ end
+ driver.add_method(qname, action, name.to_s, param_def)
+ end
+ driver
+ end
+
+ class SoapDriver < SOAP::RPC::Driver # :nodoc:
+ def add_method(qname, soapaction, name, param_def)
+ @proxy.add_rpc_method(qname, soapaction, name, param_def)
+ add_rpc_method_interface(name, param_def)
+ end
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/client/xmlrpc.rb b/actionservice/lib/action_service/client/xmlrpc.rb
new file mode 100644
index 0000000000..d0d007f871
--- /dev/null
+++ b/actionservice/lib/action_service/client/xmlrpc.rb
@@ -0,0 +1,76 @@
+require 'uri'
+require 'xmlrpc/client'
+
+module ActionService # :nodoc:
+ module Client # :nodoc:
+
+ # Implements XML-RPC client support
+ #
+ # ==== Example Usage
+ #
+ # class BloggerAPI < ActionService::API::Base
+ # inflect_names false
+ # api_method :getRecentPosts, :returns => [[Blog::Post]]
+ # end
+ #
+ # blog = ActionService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger")
+ # posts = blog.getRecentPosts
+ class XmlRpc < Base
+
+ # Creates a new web service client using the XML-RPC protocol.
+ #
+ # +api+ must be an ActionService::API::Base derivative, and
+ # +endpoint_uri+ must point at the relevant URL to which protocol requests
+ # will be sent with HTTP POST.
+ #
+ # Valid options:
+ # [<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]
+ @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
+ end
+
+ protected
+ def perform_invocation(method_name, args)
+ args = transform_outgoing_method_params(method_name, args)
+ ok, return_value = @client.call2(public_name(method_name), *args)
+ return transform_return_value(method_name, return_value) if ok
+ raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
+ end
+
+ def transform_outgoing_method_params(method_name, params)
+ info = @api.api_methods[method_name.to_sym]
+ signature = info[:expects]
+ signature_length = signature.nil?? 0 : signature.length
+ if signature_length != params.length
+ raise(ProtocolError, "API declares #{public_name(method_name)} to accept " +
+ "#{signature_length} parameters, but #{params.length} parameters " +
+ "were supplied")
+ end
+ if signature_length > 0
+ signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature)
+ (1..signature.size).each do |i|
+ i -= 1
+ params[i] = Protocol::XmlRpc::XmlRpcProtocol.ruby_to_xmlrpc(params[i], signature[i])
+ end
+ end
+ params
+ end
+
+ def transform_return_value(method_name, return_value)
+ info = @api.api_methods[method_name.to_sym]
+ return true unless signature = info[:returns]
+ signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature)
+ Protocol::XmlRpc::XmlRpcProtocol.xmlrpc_to_ruby(return_value, signature[0])
+ end
+
+ def public_name(method_name)
+ public_name = @api.public_api_method_name(method_name)
+ @handler_name ? "#{@handler_name}.#{public_name}" : public_name
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/container.rb b/actionservice/lib/action_service/container.rb
new file mode 100644
index 0000000000..b2317fc941
--- /dev/null
+++ b/actionservice/lib/action_service/container.rb
@@ -0,0 +1,232 @@
+module ActionService # :nodoc:
+ module Container # :nodoc:
+ class ContainerError < ActionService::ActionServiceError # :nodoc:
+ end
+
+ def self.append_features(base) # :nodoc:
+ super
+ base.class_inheritable_option(:service_dispatching_mode, :direct)
+ base.class_inheritable_option(:service_exception_reporting, true)
+ base.extend(ClassMethods)
+ base.send(:include, ActionService::Container::InstanceMethods)
+ end
+
+ module ClassMethods
+ # Declares a service that will provides access to the API of the given
+ # service +object+. +object+ must be an ActionService::Base derivative.
+ #
+ # 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 service object example
+ #
+ # class ApiController < ApplicationController
+ # service_dispatching_mode :delegated
+ #
+ # 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 service object example
+ #
+ # class ApiController < ApplicationController
+ # service_dispatching_mode :delegated
+ #
+ # service(:person) { PersonService.new(@request.env) }
+ # end
+ def 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("action_services", info)
+ call_service_definition_callbacks(self, name, info)
+ end
+
+ # Whether this service contains a service with the given +name+
+ def has_service?(name)
+ services.has_key?(name.to_sym)
+ end
+
+ def services # :nodoc:
+ read_inheritable_attribute("action_services") || {}
+ end
+
+ def add_service_definition_callback(&block) # :nodoc:
+ write_inheritable_array("service_definition_callbacks", [block])
+ end
+
+ private
+ def call_service_definition_callbacks(container_class, service_name, service_info)
+ (read_inheritable_attribute("service_definition_callbacks") || []).each do |block|
+ block.call(container_class, service_name, service_info)
+ end
+ end
+ end
+
+ module InstanceMethods # :nodoc:
+ def service_object(service_name)
+ info = self.class.services[service_name.to_sym]
+ unless info
+ raise(ContainerError, "no such service '#{service_name}'")
+ end
+ service = info[:block]
+ service ? instance_eval(&service) : info[:object]
+ end
+
+ private
+ def dispatch_service_request(protocol_request)
+ case service_dispatching_mode
+ when :direct
+ dispatch_direct_service_request(protocol_request)
+ when :delegated
+ dispatch_delegated_service_request(protocol_request)
+ else
+ raise(ContainerError, "unsupported dispatching mode '#{service_dispatching_mode}'")
+ end
+ end
+
+ def dispatch_direct_service_request(protocol_request)
+ public_method_name = protocol_request.public_method_name
+ api = self.class.service_api
+ method_name = api.api_method_name(public_method_name)
+ block = nil
+ expects = nil
+ if method_name
+ signature = api.api_methods[method_name]
+ expects = signature[:expects]
+ protocol_request.type = Protocol::CheckedMessage
+ protocol_request.signature = expects
+ protocol_request.return_signature = signature[:returns]
+ else
+ protocol_request.type = Protocol::UncheckedMessage
+ system_methods = self.class.read_inheritable_attribute('default_system_methods') || {}
+ protocol = protocol_request.protocol
+ block = system_methods[protocol.class]
+ unless block
+ method_name = api.default_api_method
+ unless method_name && respond_to?(method_name)
+ raise(ContainerError, "no such method ##{public_method_name}")
+ end
+ end
+ end
+
+ @method_params = protocol_request.unmarshal
+ @params ||= {}
+ if expects
+ (1..@method_params.size).each do |i|
+ i -= 1
+ if expects[i].is_a?(Hash)
+ @params[expects[i].keys.shift.to_s] = @method_params[i]
+ else
+ @params["param#{i}"] = @method_params[i]
+ end
+ end
+ end
+
+ if respond_to?(:before_action)
+ @params['action'] = method_name.to_s
+ return protocol_request.marshal(nil) if before_action == false
+ end
+
+ perform_invoke = lambda do
+ if block
+ block.call(public_method_name, self.class, *@method_params)
+ else
+ send(method_name)
+ end
+ end
+ try_default = true
+ result = nil
+ catch(:try_default) do
+ result = perform_invoke.call
+ try_default = false
+ end
+ if try_default
+ method_name = api.default_api_method
+ if method_name
+ protocol_request.type = Protocol::UncheckedMessage
+ else
+ raise(ContainerError, "no such method ##{public_method_name}")
+ end
+ result = perform_invoke.call
+ end
+ after_action if respond_to?(:after_action)
+ protocol_request.marshal(result)
+ end
+
+ def dispatch_delegated_service_request(protocol_request)
+ service_name = protocol_request.service_name
+ service = service_object(service_name)
+ api = service.class.service_api
+ public_method_name = protocol_request.public_method_name
+ method_name = api.api_method_name(public_method_name)
+
+ invocation = ActionService::Invocation::InvocationRequest.new(
+ ActionService::Invocation::ConcreteInvocation,
+ public_method_name,
+ method_name)
+
+ if method_name
+ protocol_request.type = Protocol::CheckedMessage
+ signature = api.api_methods[method_name]
+ protocol_request.signature = signature[:expects]
+ protocol_request.return_signature = signature[:returns]
+ invocation.params = protocol_request.unmarshal
+ else
+ protocol_request.type = Protocol::UncheckedMessage
+ invocation.type = ActionService::Invocation::VirtualInvocation
+ system_methods = self.class.read_inheritable_attribute('default_system_methods') || {}
+ protocol = protocol_request.protocol
+ block = system_methods[protocol.class]
+ if block
+ invocation.block = block
+ invocation.block_params << service.class
+ else
+ method_name = api.default_api_method
+ if method_name && service.respond_to?(method_name)
+ invocation.params = protocol_request.unmarshal
+ invocation.method_name = method_name.to_sym
+ else
+ raise(ContainerError, "no such method /#{service_name}##{public_method_name}")
+ end
+ end
+ end
+
+ canceled_reason = nil
+ canceled_block = lambda{|r| canceled_reason = r}
+ perform_invoke = lambda do
+ service.perform_invocation(invocation, &canceled_block)
+ end
+ try_default = true
+ result = nil
+ catch(:try_default) do
+ result = perform_invoke.call
+ try_default = false
+ end
+ if try_default
+ method_name = api.default_api_method
+ if method_name
+ protocol_request.type = Protocol::UncheckedMessage
+ invocation.params = protocol_request.unmarshal
+ invocation.method_name = method_name.to_sym
+ invocation.type = ActionService::Invocation::UnpublishedConcreteInvocation
+ else
+ raise(ContainerError, "no such method /#{service_name}##{public_method_name}")
+ end
+ result = perform_invoke.call
+ end
+ protocol_request.marshal(result)
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/invocation.rb b/actionservice/lib/action_service/invocation.rb
new file mode 100644
index 0000000000..f20b546c5a
--- /dev/null
+++ b/actionservice/lib/action_service/invocation.rb
@@ -0,0 +1,252 @@
+module ActionService # :nodoc:
+ module Invocation # :nodoc:
+ ConcreteInvocation = :concrete
+ VirtualInvocation = :virtual
+ UnpublishedConcreteInvocation = :unpublished_concrete
+
+ class InvocationError < ActionService::ActionServiceError # :nodoc:
+ end
+
+ def self.append_features(base) # :nodoc:
+ super
+ base.extend(ClassMethods)
+ base.send(:include, ActionService::Invocation::InstanceMethods)
+ end
+
+ # Invocation interceptors provide a means to execute custom code before
+ # and after method invocations on ActionService::Base objects.
+ #
+ # When running in _Direct_ dispatching mode, ActionController filters
+ # should be used for this functionality.
+ #
+ # 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 < ActionService::Base
+ # before_invocation :intercept_add, :only => [:add]
+ #
+ # def add(a, b)
+ # a + b
+ # end
+ #
+ # private
+ # def intercept_add
+ # return [false, "permission denied"] # cancel it
+ # end
+ # end
+ #
+ # Options:
+ # [<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.append_features(base)
+ super
+ base.class_eval do
+ alias_method :perform_invocation_without_interception, :perform_invocation
+ alias_method :perform_invocation, :perform_invocation_with_interception
+ end
+ end
+
+ def perform_invocation_with_interception(invocation, &block)
+ return if before_invocation(invocation.method_name, invocation.params, &block) == false
+ result = perform_invocation_without_interception(invocation)
+ after_invocation(invocation.method_name, invocation.params, result)
+ result
+ end
+
+ def perform_invocation(invocation)
+ if invocation.concrete?
+ unless self.respond_to?(invocation.method_name) && \
+ self.class.service_api.has_api_method?(invocation.method_name)
+ raise InvocationError, "no such service method '#{invocation.method_name}'"
+ end
+ end
+ params = invocation.params
+ if invocation.concrete? || invocation.unpublished_concrete?
+ self.send(invocation.method_name, *params)
+ else
+ if invocation.block
+ params = invocation.block_params + params
+ invocation.block.call(invocation.public_method_name, *params)
+ else
+ self.send(invocation.method_name, *params)
+ end
+ end
+ end
+
+ def before_invocation(name, args, &block)
+ call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
+ end
+
+ def after_invocation(name, args, result)
+ call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
+ end
+
+ private
+
+ def call_interceptors(interceptors, interceptor_args, &block)
+ if interceptors and not interceptors.empty?
+ interceptors.each do |interceptor|
+ next if method_exempted?(interceptor, interceptor_args[0].to_s)
+ result = case
+ when interceptor.is_a?(Symbol)
+ self.send(interceptor, *interceptor_args)
+ when interceptor_block?(interceptor)
+ interceptor.call(self, *interceptor_args)
+ when interceptor_class?(interceptor)
+ interceptor.intercept(self, *interceptor_args)
+ else
+ raise(
+ InvocationError,
+ "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
+ )
+ end
+ reason = nil
+ if result.is_a?(Array)
+ reason = result[1] if result[1]
+ result = result[0]
+ end
+ if result == false
+ block.call(reason) if block && reason
+ return false
+ end
+ end
+ end
+ end
+
+ def interceptor_block?(interceptor)
+ interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
+ end
+
+ def interceptor_class?(interceptor)
+ interceptor.respond_to?("intercept")
+ end
+
+ def method_exempted?(interceptor, method_name)
+ case
+ when self.class.included_intercepted_methods[interceptor]
+ !self.class.included_intercepted_methods[interceptor].include?(method_name)
+ when self.class.excluded_intercepted_methods[interceptor]
+ self.class.excluded_intercepted_methods[interceptor].include?(method_name)
+ end
+ end
+ end
+
+ class InvocationRequest # :nodoc:
+ attr_accessor :type
+ attr :public_method_name
+ attr_accessor :method_name
+ attr_accessor :params
+ attr_accessor :block
+ attr :block_params
+
+ def initialize(type, public_method_name, method_name, params=nil)
+ @type = type
+ @public_method_name = public_method_name
+ @method_name = method_name
+ @params = params || []
+ @block = nil
+ @block_params = []
+ end
+
+ def concrete?
+ @type == ConcreteInvocation ? true : false
+ end
+
+ def unpublished_concrete?
+ @type == UnpublishedConcreteInvocation ? true : false
+ end
+ end
+
+ end
+end
diff --git a/actionservice/lib/action_service/protocol.rb b/actionservice/lib/action_service/protocol.rb
new file mode 100644
index 0000000000..5e71b2bcfd
--- /dev/null
+++ b/actionservice/lib/action_service/protocol.rb
@@ -0,0 +1,4 @@
+require 'action_service/protocol/abstract'
+require 'action_service/protocol/registry'
+require 'action_service/protocol/soap'
+require 'action_service/protocol/xmlrpc'
diff --git a/actionservice/lib/action_service/protocol/abstract.rb b/actionservice/lib/action_service/protocol/abstract.rb
new file mode 100644
index 0000000000..ed41c49951
--- /dev/null
+++ b/actionservice/lib/action_service/protocol/abstract.rb
@@ -0,0 +1,128 @@
+module ActionService # :nodoc:
+ module Protocol # :nodoc:
+ CheckedMessage = :checked
+ UncheckedMessage = :unchecked
+
+ class ProtocolError < ActionService::ActionServiceError # :nodoc:
+ end
+
+ class AbstractProtocol # :nodoc:
+ attr :container_class
+
+ def initialize(container_class)
+ @container_class = container_class
+ end
+
+ def unmarshal_request(protocol_request)
+ raise NotImplementedError
+ end
+
+ def marshal_response(protocol_request, return_value)
+ raise NotImplementedError
+ end
+
+ def marshal_exception(exception)
+ raise NotImplementedError
+ end
+
+ def self.create_protocol_request(container_class, action_pack_request)
+ nil
+ end
+
+ def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
+ nil
+ end
+ end
+
+ class AbstractProtocolMessage # :nodoc:
+ attr_accessor :signature
+ attr_accessor :return_signature
+ attr_accessor :type
+ attr :options
+
+ def initialize(options={})
+ @signature = @return_signature = nil
+ @options = options
+ @type = @options[:type] || CheckedMessage
+ end
+
+ def signature=(value)
+ return if value.nil?
+ @signature = []
+ value.each do |klass|
+ if klass.is_a?(Hash)
+ @signature << klass.values.shift
+ else
+ @signature << klass
+ end
+ end
+ @signature
+ end
+
+ def checked?
+ @type == CheckedMessage
+ end
+
+ def check_parameter_types(values, signature)
+ return unless checked? && signature
+ unless signature.length == values.length
+ raise(ProtocolError, "Signature and parameter lengths mismatch")
+ end
+ (1..signature.length).each do |i|
+ check_compatibility(signature[i-1], values[i-1].class)
+ end
+ end
+
+ def check_compatibility(expected_class, received_class)
+ return if \
+ (expected_class == TrueClass or expected_class == FalseClass) and \
+ (received_class == TrueClass or received_class == FalseClass)
+ unless received_class.ancestors.include?(expected_class) or \
+ expected_class.ancestors.include?(received_class)
+ raise(ProtocolError, "value of type #{received_class.name} is not " +
+ "compatible with expected type #{expected_class.name}")
+ end
+ end
+ end
+
+ class ProtocolRequest < AbstractProtocolMessage # :nodoc:
+ attr :protocol
+ attr :raw_body
+
+ attr_accessor :service_name
+ attr_accessor :public_method_name
+ attr_accessor :content_type
+
+ def initialize(protocol, raw_body, service_name, public_method_name, content_type, options={})
+ super(options)
+ @protocol = protocol
+ @raw_body = raw_body
+ @service_name = service_name
+ @public_method_name = public_method_name
+ @content_type = content_type
+ end
+
+ def unmarshal
+ @protocol.unmarshal_request(self)
+ end
+
+ def marshal(return_value)
+ @protocol.marshal_response(self, return_value)
+ end
+ end
+
+ class ProtocolResponse < AbstractProtocolMessage # :nodoc:
+ attr :protocol
+ attr :raw_body
+
+ attr_accessor :content_type
+
+ def initialize(protocol, raw_body, content_type, options={})
+ super(options)
+ @protocol = protocol
+ @raw_body = raw_body
+ @content_type = content_type
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/protocol/registry.rb b/actionservice/lib/action_service/protocol/registry.rb
new file mode 100644
index 0000000000..e06361f916
--- /dev/null
+++ b/actionservice/lib/action_service/protocol/registry.rb
@@ -0,0 +1,55 @@
+module ActionService # :nodoc:
+ module Protocol # :nodoc:
+ HeaderAndBody = :header_and_body
+ BodyOnly = :body_only
+
+ module Registry # :nodoc:
+ def self.append_features(base) # :nodoc:
+ super
+ base.extend(ClassMethods)
+ base.send(:include, ActionService::Protocol::Registry::InstanceMethods)
+ end
+
+ module ClassMethods # :nodoc:
+ def register_protocol(type, klass) # :nodoc:
+ case type
+ when HeaderAndBody
+ write_inheritable_array("header_and_body_protocols", [klass])
+ when BodyOnly
+ write_inheritable_array("body_only_protocols", [klass])
+ else
+ raise(ProtocolError, "unknown protocol type #{type}")
+ end
+ end
+ end
+
+ module InstanceMethods # :nodoc:
+ private
+ def probe_request_protocol(action_pack_request)
+ (header_and_body_protocols + body_only_protocols).each do |protocol|
+ protocol_request = protocol.create_protocol_request(self.class, action_pack_request)
+ return protocol_request if protocol_request
+ end
+ raise(ProtocolError, "unsupported request message format")
+ end
+
+ def probe_protocol_client(api, protocol_name, endpoint_uri, options)
+ (header_and_body_protocols + body_only_protocols).each do |protocol|
+ protocol_client = protocol.create_protocol_client(api, protocol_name, endpoint_uri, options)
+ return protocol_client if protocol_client
+ end
+ raise(ProtocolError, "unsupported client protocol :#{protocol_name}")
+ end
+
+ def header_and_body_protocols
+ self.class.read_inheritable_attribute("header_and_body_protocols") || []
+ end
+
+ def body_only_protocols
+ self.class.read_inheritable_attribute("body_only_protocols") || []
+ end
+ end
+
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/protocol/soap.rb b/actionservice/lib/action_service/protocol/soap.rb
new file mode 100644
index 0000000000..24cc554b05
--- /dev/null
+++ b/actionservice/lib/action_service/protocol/soap.rb
@@ -0,0 +1,484 @@
+require 'soap/processor'
+require 'soap/mapping'
+require 'soap/rpc/element'
+require 'xsd/datatypes'
+require 'xsd/ns'
+require 'singleton'
+
+module ActionService # :nodoc:
+ module Protocol # :nodoc:
+ module Soap # :nodoc:
+ class ProtocolError < ActionService::ActionServiceError # :nodoc:
+ end
+
+ def self.append_features(base) # :nodoc:
+ super
+ base.register_protocol(HeaderAndBody, SoapProtocol)
+ base.extend(ClassMethods)
+ base.wsdl_service_name('ActionService')
+ end
+
+ module ClassMethods
+ # Specifies the WSDL service name to use when generating WSDL. Highly
+ # recommended that you set this value, or code generators may generate
+ # classes with very generic names.
+ #
+ # === Example
+ # class MyController < ActionController::Base
+ # wsdl_service_name 'MyService'
+ # end
+ def wsdl_service_name(name)
+ write_inheritable_attribute("soap_mapper", SoapMapper.new("urn:#{name}"))
+ end
+
+ def soap_mapper # :nodoc:
+ read_inheritable_attribute("soap_mapper")
+ end
+ end
+
+ class SoapProtocol < AbstractProtocol # :nodoc:
+ attr :mapper
+
+ def initialize(mapper)
+ @mapper = mapper
+ end
+
+ def self.create_protocol_request(container_class, action_pack_request)
+ soap_action = extract_soap_action(action_pack_request)
+ return nil unless soap_action
+ service_name = action_pack_request.parameters['action']
+ public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1]
+ content_type = action_pack_request.env['HTTP_CONTENT_TYPE']
+ content_type ||= 'text/xml'
+ protocol = SoapProtocol.new(container_class.soap_mapper)
+ ProtocolRequest.new(protocol,
+ action_pack_request.raw_post,
+ service_name.to_sym,
+ public_method_name,
+ content_type)
+ end
+
+ def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
+ return nil unless protocol_name.to_s.downcase.to_sym == :soap
+ ActionService::Client::Soap.new(api, endpoint_uri, options)
+ end
+
+ def unmarshal_request(protocol_request)
+ unmarshal = lambda do
+ envelope = SOAP::Processor.unmarshal(protocol_request.raw_body)
+ request = envelope.body.request
+ values = request.collect{|k, v| request[k]}
+ soap_to_ruby_array(values)
+ end
+ signature = protocol_request.signature
+ if signature
+ map_signature_types(signature)
+ values = unmarshal.call
+ signature = signature.map{|x|mapper.lookup(x).ruby_klass}
+ protocol_request.check_parameter_types(values, signature)
+ values
+ else
+ if protocol_request.checked?
+ []
+ else
+ unmarshal.call
+ end
+ end
+ end
+
+ def marshal_response(protocol_request, return_value)
+ marshal = lambda do |signature|
+ mapping = mapper.lookup(signature[0])
+ return_value = fixup_array_types(mapping, return_value)
+ signature = signature.map{|x|mapper.lookup(x).ruby_klass}
+ protocol_request.check_parameter_types([return_value], signature)
+ param_def = [['retval', 'return', mapping.registry_mapping]]
+ [param_def, ruby_to_soap(return_value)]
+ end
+ signature = protocol_request.return_signature
+ param_def = nil
+ if signature
+ param_def, return_value = marshal.call(signature)
+ else
+ if protocol_request.checked?
+ param_def, return_value = nil, nil
+ else
+ param_def, return_value = marshal.call([return_value.class])
+ end
+ end
+ qname = XSD::QName.new(mapper.custom_namespace,
+ protocol_request.public_method_name)
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
+ response.retval = return_value unless return_value.nil?
+ ProtocolResponse.new(self, create_response(response), 'text/xml')
+ end
+
+ def marshal_exception(exc)
+ ProtocolResponse.new(self, create_exception_response(exc), 'text/xml')
+ end
+
+ private
+ def self.extract_soap_action(request)
+ return nil unless request.method == :post
+ content_type = request.env['HTTP_CONTENT_TYPE'] || 'text/xml'
+ return nil unless content_type
+ soap_action = request.env['HTTP_SOAPACTION']
+ return nil unless soap_action
+ soap_action.gsub!(/^"/, '')
+ soap_action.gsub!(/"$/, '')
+ soap_action.strip!
+ return nil if soap_action.empty?
+ soap_action
+ end
+
+ def fixup_array_types(mapping, obj)
+ mapping.each_attribute do |name, type, attr_mapping|
+ if attr_mapping.custom_type?
+ attr_obj = obj.send(name)
+ new_obj = fixup_array_types(attr_mapping, attr_obj)
+ obj.send("#{name}=", new_obj) unless new_obj.equal?(attr_obj)
+ end
+ end
+ if mapping.is_a?(SoapArrayMapping)
+ obj = mapping.ruby_klass.new(obj)
+ # man, this is going to be slow for big arrays :(
+ (1..obj.size).each do |i|
+ i -= 1
+ obj[i] = fixup_array_types(mapping.element_mapping, obj[i])
+ end
+ else
+ if !mapping.generated_klass.nil? && mapping.generated_klass.respond_to?(:members)
+ # have to map the publically visible structure of the class
+ new_obj = mapping.generated_klass.new
+ mapping.generated_klass.members.each do |name, klass|
+ new_obj.send("#{name}=", obj.send(name))
+ end
+ obj = new_obj
+ end
+ end
+ obj
+ end
+
+ def map_signature_types(types)
+ types.collect{|type| mapper.map(type)}
+ end
+
+ def create_response(body)
+ header = SOAP::SOAPHeader.new
+ body = SOAP::SOAPBody.new(body)
+ envelope = SOAP::SOAPEnvelope.new(header, body)
+ SOAP::Processor.marshal(envelope)
+ end
+
+ def create_exception_response(exc)
+ detail = SOAP::Mapping::SOAPException.new(exc)
+ body = SOAP::SOAPFault.new(
+ SOAP::SOAPString.new('Server'),
+ SOAP::SOAPString.new(exc.to_s),
+ SOAP::SOAPString.new(self.class.name),
+ SOAP::Mapping.obj2soap(detail))
+ create_response(body)
+ end
+
+ def ruby_to_soap(obj)
+ SOAP::Mapping.obj2soap(obj, mapper.registry)
+ end
+
+ def soap_to_ruby(obj)
+ SOAP::Mapping.soap2obj(obj, mapper.registry)
+ end
+
+ def soap_to_ruby_array(array)
+ array.map{|x| soap_to_ruby(x)}
+ end
+ end
+
+ class SoapMapper # :nodoc:
+ attr :registry
+ attr :custom_namespace
+ attr :custom_types
+
+ def initialize(custom_namespace)
+ @custom_namespace = custom_namespace
+ @registry = SOAP::Mapping::Registry.new
+ @klass2map = {}
+ @custom_types = {}
+ @ar2klass = {}
+ end
+
+ def lookup(klass)
+ lookup_klass = klass.is_a?(Array) ? klass[0] : klass
+ generated_klass = nil
+ unless lookup_klass.respond_to?(:ancestors)
+ raise(ProtocolError, "expected parameter type definition to be a Class")
+ end
+ if lookup_klass.ancestors.include?(ActiveRecord::Base)
+ generated_klass = @ar2klass.has_key?(klass) ? @ar2klass[klass] : nil
+ klass = generated_klass if generated_klass
+ end
+ return @klass2map[klass] if @klass2map.has_key?(klass)
+
+ custom_type = false
+
+ ruby_klass = select_class(lookup_klass)
+ generated_klass = @ar2klass[lookup_klass] if @ar2klass.has_key?(lookup_klass)
+ type_name = ruby_klass.name
+
+ # Array signatures generate a double-mapping and require generation
+ # of an Array subclass to represent the mapping in the SOAP
+ # registry
+ array_klass = nil
+ if klass.is_a?(Array)
+ array_klass = Class.new(Array) do
+ module_eval <<-END
+ def self.name
+ "#{type_name}Array"
+ end
+ END
+ end
+ end
+
+ mapping = @registry.find_mapped_soap_class(ruby_klass) rescue nil
+ unless mapping
+ # Custom structured type, generate a mapping
+ info = { :type => XSD::QName.new(@custom_namespace, type_name) }
+ @registry.add(ruby_klass,
+ SOAP::SOAPStruct,
+ SOAP::Mapping::Registry::TypedStructFactory,
+ info)
+ mapping = ensure_mapped(ruby_klass)
+ custom_type = true
+ end
+
+ array_mapping = nil
+ if array_klass
+ # Typed array always requires a custom type. The info of the array
+ # is the info of its element type (in mapping[2]), falling back
+ # to SOAP base types.
+ info = mapping[2]
+ info ||= {}
+ info[:type] ||= soap_base_type_qname(mapping[0])
+ @registry.add(array_klass,
+ SOAP::SOAPArray,
+ SOAP::Mapping::Registry::TypedArrayFactory,
+ info)
+ array_mapping = ensure_mapped(array_klass)
+ end
+
+ if array_mapping
+ @klass2map[ruby_klass] = SoapMapping.new(self,
+ type_name,
+ ruby_klass,
+ generated_klass,
+ mapping[0],
+ mapping,
+ custom_type)
+ @klass2map[klass] = SoapArrayMapping.new(self,
+ type_name,
+ array_klass,
+ array_mapping[0],
+ array_mapping,
+ @klass2map[ruby_klass])
+ @custom_types[klass] = @klass2map[klass]
+ @custom_types[ruby_klass] = @klass2map[ruby_klass] if custom_type
+ else
+ @klass2map[klass] = SoapMapping.new(self,
+ type_name,
+ ruby_klass,
+ generated_klass,
+ mapping[0],
+ mapping,
+ custom_type)
+ @custom_types[klass] = @klass2map[klass] if custom_type
+ end
+
+ @klass2map[klass]
+ end
+ alias :map :lookup
+
+ def map_container_services(container, &block)
+ dispatching_mode = container.service_dispatching_mode
+ services = nil
+ case dispatching_mode
+ when :direct
+ api = container.class.service_api
+ if container.respond_to?(:controller_class_name)
+ service_name = container.controller_class_name.sub(/Controller$/, '').underscore
+ else
+ service_name = container.class.name.demodulize.underscore
+ end
+ services = { service_name => api }
+ when :delegated
+ services = {}
+ container.class.services.each do |service_name, service_info|
+ begin
+ object = container.service_object(service_name)
+ rescue Exception => e
+ raise(ProtocolError, "failed to retrieve service object for mapping: #{e.message}")
+ end
+ services[service_name] = object.class.service_api
+ end
+ end
+ services.each do |service_name, api|
+ if api.nil?
+ raise(ProtocolError, "no service API set while in :#{dispatching_mode} mode")
+ end
+ map_api(api) do |api_methods|
+ yield service_name, api, api_methods if block_given?
+ end
+ end
+ end
+
+ def map_api(api, &block)
+ lookup_proc = lambda do |klass|
+ mapping = lookup(klass)
+ custom_mapping = nil
+ if mapping.respond_to?(:element_mapping)
+ custom_mapping = mapping.element_mapping
+ else
+ custom_mapping = mapping
+ end
+ if custom_mapping && custom_mapping.custom_type?
+ # What gives? This is required so that structure types
+ # referenced only by structures (and not signatures) still
+ # have a custom type mapping in the registry (needed for WSDL
+ # generation).
+ custom_mapping.each_attribute{}
+ end
+ mapping
+ end
+ api_methods = block.nil?? nil : {}
+ api.api_methods.each do |method_name, method_info|
+ expects = method_info[:expects]
+ expects_signature = nil
+ if expects
+ expects_signature = block ? [] : nil
+ expects.each do |klass|
+ lookup_klass = nil
+ if klass.is_a?(Hash)
+ lookup_klass = lookup_proc.call(klass.values[0])
+ expects_signature << {klass.keys[0]=>lookup_klass} if block
+ else
+ lookup_klass = lookup_proc.call(klass)
+ expects_signature << lookup_klass if block
+ end
+ end
+ end
+ returns = method_info[:returns]
+ returns_signature = returns ? returns.map{|klass| lookup_proc.call(klass)} : nil
+ if block
+ api_methods[method_name] = {
+ :expects => expects_signature,
+ :returns => returns_signature
+ }
+ end
+ end
+ yield api_methods if block
+ end
+
+ private
+ def select_class(klass)
+ return Integer if klass == Fixnum
+ if klass.ancestors.include?(ActiveRecord::Base)
+ new_klass = Class.new(ActionService::Struct)
+ new_klass.class_eval <<-EOS
+ def self.name
+ "#{klass.name}"
+ end
+ EOS
+ klass.columns.each do |column|
+ next if column.klass.nil?
+ new_klass.send(:member, column.name.to_sym, column.klass)
+ end
+ @ar2klass[klass] = new_klass
+ return new_klass
+ end
+ klass
+ end
+
+ def ensure_mapped(klass)
+ mapping = @registry.find_mapped_soap_class(klass) rescue nil
+ raise(ProtocolError, "failed to register #{klass.name}") unless mapping
+ mapping
+ end
+
+ def soap_base_type_qname(base_type)
+ xsd_type = base_type.ancestors.find{|c| c.const_defined? 'Type'}
+ xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
+ end
+ end
+
+ class SoapMapping # :nodoc:
+ attr :ruby_klass
+ attr :generated_klass
+ attr :soap_klass
+ attr :registry_mapping
+
+ def initialize(mapper, type_name, ruby_klass, generated_klass, soap_klass, registry_mapping,
+ custom_type=false)
+ @mapper = mapper
+ @type_name = type_name
+ @ruby_klass = ruby_klass
+ @generated_klass = generated_klass
+ @soap_klass = soap_klass
+ @registry_mapping = registry_mapping
+ @custom_type = custom_type
+ end
+
+ def type_name
+ @type_name
+ end
+
+ def custom_type?
+ @custom_type
+ end
+
+ def qualified_type_name
+ name = type_name
+ if custom_type?
+ "typens:#{name}"
+ else
+ xsd_type_for(@soap_klass)
+ end
+ end
+
+ def each_attribute(&block)
+ if @ruby_klass.respond_to?(:members)
+ @ruby_klass.members.each do |name, klass|
+ name = name.to_s
+ mapping = @mapper.lookup(klass)
+ yield name, mapping.qualified_type_name, mapping
+ end
+ end
+ end
+
+ def is_xsd_type?(klass)
+ klass.ancestors.include?(XSD::NSDBase)
+ end
+
+ def xsd_type_for(klass)
+ ns = XSD::NS.new
+ ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
+ xsd_klass = klass.ancestors.find{|c| c.const_defined?('Type')}
+ return ns.name(XSD::AnyTypeName) unless xsd_klass
+ ns.name(xsd_klass.const_get('Type'))
+ end
+ end
+
+ class SoapArrayMapping < SoapMapping # :nodoc:
+ attr :element_mapping
+
+ def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping, element_mapping)
+ super(mapper, type_name, ruby_klass, nil, soap_klass, registry_mapping, true)
+ @element_mapping = element_mapping
+ end
+
+ def type_name
+ super + "Array"
+ end
+
+ def each_attribute(&block); end
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/protocol/xmlrpc.rb b/actionservice/lib/action_service/protocol/xmlrpc.rb
new file mode 100644
index 0000000000..7d29868b59
--- /dev/null
+++ b/actionservice/lib/action_service/protocol/xmlrpc.rb
@@ -0,0 +1,187 @@
+require 'xmlrpc/parser'
+require 'xmlrpc/create'
+require 'xmlrpc/config'
+require 'xmlrpc/utils'
+require 'singleton'
+
+module XMLRPC # :nodoc:
+ class XmlRpcHelper # :nodoc:
+ include Singleton
+ include ParserWriterChooseMixin
+
+ def parse_method_call(message)
+ parser().parseMethodCall(message)
+ end
+
+ def create_method_response(successful, return_value)
+ create().methodResponse(successful, return_value)
+ end
+ end
+end
+
+module ActionService # :nodoc:
+ module Protocol # :nodoc:
+ module XmlRpc # :nodoc:
+ def self.append_features(base) # :nodoc:
+ super
+ base.register_protocol(BodyOnly, XmlRpcProtocol)
+ end
+
+ class XmlRpcProtocol < AbstractProtocol # :nodoc:
+
+ public
+
+ def self.create_protocol_request(container_class, action_pack_request)
+ helper = XMLRPC::XmlRpcHelper.instance
+ service_name = action_pack_request.parameters['action']
+ methodname, params = helper.parse_method_call(action_pack_request.raw_post)
+ methodname.gsub!(/^[^\.]+\./, '') unless methodname =~ /^system\./ # XXX
+ protocol = XmlRpcProtocol.new(container_class)
+ content_type = action_pack_request.env['HTTP_CONTENT_TYPE']
+ content_type ||= 'text/xml'
+ request = ProtocolRequest.new(protocol,
+ action_pack_request.raw_post,
+ service_name.to_sym,
+ methodname,
+ content_type,
+ :xmlrpc_values => params)
+ request
+ rescue
+ nil
+ end
+
+ def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
+ return nil unless protocol_name.to_s.downcase.to_sym == :xmlrpc
+ ActionService::Client::XmlRpc.new(api, endpoint_uri, options)
+ end
+
+ def initialize(container_class)
+ super(container_class)
+ container_class.write_inheritable_hash('default_system_methods', XmlRpcProtocol => method(:xmlrpc_default_system_handler))
+ end
+
+ def unmarshal_request(protocol_request)
+ values = protocol_request.options[:xmlrpc_values]
+ signature = protocol_request.signature
+ if signature
+ values = self.class.transform_incoming_method_params(self.class.transform_array_types(signature), values)
+ protocol_request.check_parameter_types(values, check_array_types(signature))
+ values
+ else
+ protocol_request.checked? ? [] : values
+ end
+ end
+
+ def marshal_response(protocol_request, return_value)
+ helper = XMLRPC::XmlRpcHelper.instance
+ signature = protocol_request.return_signature
+ if signature
+ protocol_request.check_parameter_types([return_value], check_array_types(signature))
+ return_value = self.class.transform_return_value(self.class.transform_array_types(signature), return_value)
+ raw_response = helper.create_method_response(true, return_value)
+ else
+ # XML-RPC doesn't have the concept of a void method, nor does it
+ # support a nil return value, so return true if we would have returned
+ # nil
+ if protocol_request.checked?
+ raw_response = helper.create_method_response(true, true)
+ else
+ return_value = true if return_value.nil?
+ raw_response = helper.create_method_response(true, return_value)
+ end
+ end
+ ProtocolResponse.new(self, raw_response, 'text/xml')
+ end
+
+ def marshal_exception(exception)
+ helper = XMLRPC::XmlRpcHelper.instance
+ exception = XMLRPC::FaultException.new(1, exception.message)
+ raw_response = helper.create_method_response(false, exception)
+ ProtocolResponse.new(self, raw_response, 'text/xml')
+ end
+
+ class << self
+ def transform_incoming_method_params(signature, params)
+ (1..signature.size).each do |i|
+ i -= 1
+ params[i] = xmlrpc_to_ruby(params[i], signature[i])
+ end
+ params
+ end
+
+ def transform_return_value(signature, return_value)
+ ruby_to_xmlrpc(return_value, signature[0])
+ end
+
+ def ruby_to_xmlrpc(param, param_class)
+ if param_class.is_a?(XmlRpcArray)
+ param.map{|p| ruby_to_xmlrpc(p, param_class.klass)}
+ elsif param_class.ancestors.include?(ActiveRecord::Base)
+ param.instance_variable_get('@attributes')
+ elsif param_class.ancestors.include?(ActionService::Struct)
+ struct = {}
+ param_class.members.each do |name, klass|
+ value = param.send(name)
+ next if value.nil?
+ struct[name.to_s] = value
+ end
+ struct
+ else
+ param
+ end
+ end
+
+ def xmlrpc_to_ruby(param, param_class)
+ if param_class.is_a?(XmlRpcArray)
+ param.map{|p| xmlrpc_to_ruby(p, param_class.klass)}
+ elsif param_class.ancestors.include?(ActiveRecord::Base)
+ raise(ProtocolError, "incoming ActiveRecord::Base types are not allowed")
+ elsif param_class.ancestors.include?(ActionService::Struct)
+ unless param.is_a?(Hash)
+ raise(ProtocolError, "expected parameter to be a Hash")
+ end
+ new_param = param_class.new
+ param_class.members.each do |name, klass|
+ new_param.send('%s=' % name.to_s, param[name.to_s])
+ end
+ new_param
+ else
+ param
+ end
+ end
+
+ def transform_array_types(signature)
+ signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x}
+ end
+ end
+
+ private
+ def xmlrpc_default_system_handler(name, service_class, *args)
+ case name
+ when 'system.listMethods'
+ methods = []
+ api = service_class.service_api
+ api.api_methods.each do |name, info|
+ methods << api.public_api_method_name(name)
+ end
+ methods.sort
+ else
+ throw :try_default
+ end
+ end
+
+ def check_array_types(signature)
+ signature.map{|x| x.is_a?(Array) ? Array : x}
+ end
+
+ class XmlRpcArray
+ attr :klass
+ def initialize(klass)
+ @klass = klass
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/router.rb b/actionservice/lib/action_service/router.rb
new file mode 100644
index 0000000000..16f0ae4463
--- /dev/null
+++ b/actionservice/lib/action_service/router.rb
@@ -0,0 +1,2 @@
+require 'action_service/router/action_controller'
+require 'action_service/router/wsdl'
diff --git a/actionservice/lib/action_service/router/action_controller.rb b/actionservice/lib/action_service/router/action_controller.rb
new file mode 100644
index 0000000000..01bd298bce
--- /dev/null
+++ b/actionservice/lib/action_service/router/action_controller.rb
@@ -0,0 +1,97 @@
+module ActionService # :nodoc:
+ module Router # :nodoc:
+ module ActionController # :nodoc:
+ def self.append_features(base) # :nodoc:
+ base.add_service_api_callback do |container_class, api|
+ if container_class.service_dispatching_mode == :direct && !container_class.method_defined?(:api)
+ container_class.class_eval <<-EOS
+ def api
+ process_action_service_request
+ end
+ EOS
+ end
+ end
+ base.add_service_definition_callback do |klass, name, info|
+ if klass.service_dispatching_mode == :delegated
+ klass.class_eval <<-EOS
+ def #{name}
+ process_action_service_request
+ end
+ EOS
+ end
+ end
+ base.send(:include, ActionService::Router::ActionController::InstanceMethods)
+ end
+
+ module InstanceMethods # :nodoc:
+ private
+ def process_action_service_request
+ protocol_request = nil
+ begin
+ begin
+ protocol_request = probe_request_protocol(self.request)
+ rescue Exception => e
+ logger.error "Invalid request: #{e.message}"
+ logger.error self.request.raw_post
+ raise
+ end
+ if protocol_request
+ log_request(protocol_request)
+ protocol_response = dispatch_service_request(protocol_request)
+ log_response(protocol_response)
+ response_options = {
+ :type => protocol_response.content_type,
+ :disposition => 'inline'
+ }
+ send_data(protocol_response.raw_body, response_options)
+ else
+ logger.fatal "Invalid Action Service service or method requested"
+ render_text 'Internal protocol error', "500 Invalid service/method"
+ end
+ rescue Exception => e
+ log_error e unless logger.nil?
+ exc_response = nil
+ case service_dispatching_mode
+ when :direct
+ if self.class.service_exception_reporting
+ exc_response = protocol_request.protocol.marshal_exception(e)
+ end
+ when :delegated
+ service_object = service_object(protocol_request.service_name) rescue nil
+ if service_object && service_object.class.service_exception_reporting
+ exc_response = protocol_request.protocol.marshal_exception(e) rescue nil
+ end
+ end
+ if exc_response
+ response_options = {
+ :type => exc_response.content_type,
+ :disposition => 'inline'
+ }
+ log_response exc_response
+ send_data(exc_response.raw_body, response_options)
+ else
+ render_text 'Internal protocol error', "500 #{e.message}"
+ end
+ end
+ end
+
+ def log_request(protocol_request)
+ unless logger.nil?
+ service_name = protocol_request.service_name
+ method_name = protocol_request.public_method_name
+ logger.info "\nProcessing Action Service Request: #{service_name}##{method_name}"
+ logger.info "Raw Request Body:"
+ logger.info protocol_request.raw_body
+ end
+ end
+
+ def log_response(protocol_response)
+ unless logger.nil?
+ logger.info "\nRaw Response Body:"
+ logger.info protocol_response.raw_body
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/router/wsdl.rb b/actionservice/lib/action_service/router/wsdl.rb
new file mode 100644
index 0000000000..ececa63322
--- /dev/null
+++ b/actionservice/lib/action_service/router/wsdl.rb
@@ -0,0 +1,210 @@
+module ActionService # :nodoc:
+ module Router # :nodoc:
+ module Wsdl # :nodoc:
+ def self.append_features(base) # :nodoc:
+ base.class_eval do
+ class << self
+ alias_method :inherited_without_wsdl, :inherited
+ end
+ end
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def inherited(child)
+ inherited_without_wsdl(child)
+ child.send(:include, ActionService::Router::Wsdl::InstanceMethods)
+ end
+ end
+
+ module InstanceMethods # :nodoc:
+ XsdNs = 'http://www.w3.org/2001/XMLSchema'
+ WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
+ SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
+ SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
+ SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
+
+ def wsdl
+ case @request.method
+ when :get
+ begin
+ host_name = @request.env['HTTP_HOST']||@request.env['SERVER_NAME']
+ uri = "http://#{host_name}/#{controller_name}/"
+ soap_action_base = "/#{controller_name}"
+ xml = to_wsdl(self, uri, soap_action_base)
+ send_data(xml, :type => 'text/xml', :disposition => 'inline')
+ rescue Exception => e
+ log_error e unless logger.nil?
+ render_text('', "500 #{e.message}")
+ end
+ when :post
+ render_text('', "500 POST not supported")
+ end
+ end
+
+ private
+ def to_wsdl(container, uri, soap_action_base)
+ wsdl = ""
+
+ service_dispatching_mode = container.service_dispatching_mode
+ mapper = container.class.soap_mapper
+ namespace = mapper.custom_namespace
+ wsdl_service_name = namespace.split(/:/)[1]
+
+ services = {}
+ mapper.map_container_services(container) do |name, api, api_methods|
+ services[name] = [api, api_methods]
+ end
+ custom_types = mapper.custom_types
+
+
+ xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2)
+ xm.instruct!
+
+ xm.definitions('name' => wsdl_service_name,
+ 'targetNamespace' => namespace,
+ 'xmlns:typens' => namespace,
+ 'xmlns:xsd' => XsdNs,
+ 'xmlns:soap' => SoapNs,
+ 'xmlns:soapenc' => SoapEncodingNs,
+ 'xmlns:wsdl' => WsdlNs,
+ 'xmlns' => WsdlNs) do
+
+ # Custom type XSD generation
+ if custom_types.size > 0
+ xm.types do
+ xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
+ custom_types.each do |klass, mapping|
+ case
+ when mapping.is_a?(ActionService::Protocol::Soap::SoapArrayMapping)
+ xm.xsd(:complexType, 'name' => mapping.type_name) do
+ xm.xsd(:complexContent) do
+ xm.xsd(:restriction, 'base' => 'soapenc:Array') do
+ xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
+ 'wsdl:arrayType' => mapping.element_mapping.qualified_type_name + '[]')
+ end
+ end
+ end
+ when mapping.is_a?(ActionService::Protocol::Soap::SoapMapping)
+ xm.xsd(:complexType, 'name' => mapping.type_name) do
+ xm.xsd(:all) do
+ mapping.each_attribute do |name, type_name|
+ xm.xsd(:element, 'name' => name, 'type' => type_name)
+ end
+ end
+ end
+ else
+ raise(WsdlError, "unsupported mapping type #{mapping.class.name}")
+ end
+ end
+ end
+ end
+ end
+
+ services.each do |service_name, service_values|
+ service_api, api_methods = service_values
+ # Parameter list message definitions
+ api_methods.each do |method_name, method_signature|
+ gen = lambda do |msg_name, direction|
+ xm.message('name' => msg_name) do
+ sym = nil
+ if direction == :out
+ if method_signature[:returns]
+ xm.part('name' => 'return', 'type' => method_signature[:returns][0].qualified_type_name)
+ end
+ else
+ mapping_list = method_signature[:expects]
+ i = 1
+ mapping_list.each do |mapping|
+ if mapping.is_a?(Hash)
+ param_name = mapping.keys.shift
+ mapping = mapping.values.shift
+ else
+ param_name = "param#{i}"
+ end
+ xm.part('name' => param_name, 'type' => mapping.qualified_type_name)
+ i += 1
+ end if mapping_list
+ end
+ end
+ end
+ public_name = service_api.public_api_method_name(method_name)
+ gen.call(public_name, :in)
+ gen.call("#{public_name}Response", :out)
+ end
+
+ # Declare the port
+ port_name = port_name_for(wsdl_service_name, service_name)
+ xm.portType('name' => port_name) do
+ api_methods.each do |method_name, method_signature|
+ public_name = service_api.public_api_method_name(method_name)
+ xm.operation('name' => public_name) do
+ xm.input('message' => "typens:#{public_name}")
+ xm.output('message' => "typens:#{public_name}Response")
+ end
+ end
+ end
+
+ # Bind the port to SOAP
+ binding_name = binding_name_for(wsdl_service_name, service_name)
+ xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
+ xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
+ api_methods.each do |method_name, method_signature|
+ public_name = service_api.public_api_method_name(method_name)
+ xm.operation('name' => public_name) do
+ case service_dispatching_mode
+ when :direct
+ soap_action = soap_action_base + "/api/" + public_name
+ when :delegated
+ soap_action = soap_action_base \
+ + "/" + service_name.to_s \
+ + "/" + public_name
+ end
+ xm.soap(:operation, 'soapAction' => soap_action)
+ xm.input do
+ xm.soap(:body,
+ 'use' => 'encoded',
+ 'namespace' => namespace,
+ 'encodingStyle' => SoapEncodingNs)
+ end
+ xm.output do
+ xm.soap(:body,
+ 'use' => 'encoded',
+ 'namespace' => namespace,
+ 'encodingStyle' => SoapEncodingNs)
+ end
+ end
+ end
+ end
+ end
+
+ # Define the service
+ xm.service('name' => "#{wsdl_service_name}Service") do
+ services.each do |service_name, service_values|
+ port_name = port_name_for(wsdl_service_name, service_name)
+ binding_name = binding_name_for(wsdl_service_name, service_name)
+ case service_dispatching_mode
+ when :direct
+ binding_target = 'api'
+ when :delegated
+ binding_target = service_name.to_s
+ end
+ xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
+ xm.soap(:address, 'location' => "#{uri}#{binding_target}")
+ end
+ end
+ end
+ end
+ end
+
+ def port_name_for(wsdl_service_name, service_name)
+ "#{wsdl_service_name}#{service_name.to_s.camelize}Port"
+ end
+
+ def binding_name_for(wsdl_service_name, service_name)
+ "#{wsdl_service_name}#{service_name.to_s.camelize}Binding"
+ end
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/struct.rb b/actionservice/lib/action_service/struct.rb
new file mode 100644
index 0000000000..8f2718883e
--- /dev/null
+++ b/actionservice/lib/action_service/struct.rb
@@ -0,0 +1,56 @@
+module ActionService
+ # To send structured types across the wire, derive from ActionService::Struct,
+ # and use +member+ to declare structure members.
+ #
+ # ActionService::Struct should be used in method signatures when you want to accept or return
+ # structured types that have no Active Record model class representations, or you don't
+ # want to expose your entire Active Record model to remote callers.
+ #
+ # === Example
+ #
+ # class Person < ActionService::Struct
+ # member :id, :int
+ # member :firstnames, [:string]
+ # member :lastname, :string
+ # member :email, :string
+ # end
+ #
+ # Active Record model classes are already implicitly supported for method
+ # return signatures. A structure containing its columns as members will be
+ # automatically generated if its present in a signature.
+ #
+ # The structure
+ class Struct
+
+ # If a Hash is given as argument to an ActionService::Struct constructor,
+ # containing as key the member name, and its associated initial value
+ def initialize(values={})
+ if values.is_a?(Hash)
+ values.map{|k,v| send('%s=' % k.to_s, v)}
+ end
+ end
+
+ # The member with the given name
+ def [](name)
+ send(name.to_s)
+ end
+
+ class << self
+ include ActionService::Signature
+
+ # Creates a structure member accessible using +name+. Generates
+ # accessor methods for reading and writing the member value.
+ def member(name, type)
+ write_inheritable_hash("struct_members", name => signature_parameter_class(type))
+ class_eval <<-END
+ def #{name}; @#{name}; end
+ def #{name}=(value); @#{name} = value; end
+ END
+ end
+
+ def members # :nodoc:
+ read_inheritable_attribute("struct_members") || {}
+ end
+ end
+ end
+end
diff --git a/actionservice/lib/action_service/support/class_inheritable_options.rb b/actionservice/lib/action_service/support/class_inheritable_options.rb
new file mode 100644
index 0000000000..4d1c2ed471
--- /dev/null
+++ b/actionservice/lib/action_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/actionservice/lib/action_service/support/signature.rb b/actionservice/lib/action_service/support/signature.rb
new file mode 100644
index 0000000000..f7aae61a3f
--- /dev/null
+++ b/actionservice/lib/action_service/support/signature.rb
@@ -0,0 +1,100 @@
+module ActionService # :nodoc:
+ # Action Service parameter type specifiers may contain symbols or strings
+ # instead of Class objects, for a limited set of base types.
+ #
+ # This provides an unambiguous way to specify that a given parameter
+ # contains an integer or boolean value, for example.
+ #
+ # The allowed set of symbol/string aliases:
+ #
+ # [<tt>:int</tt>] any integer value
+ # [<tt>:float</tt>] any floating point value
+ # [<tt>:string</tt>] any string value
+ # [<tt>:bool</tt>] any boolean value
+ # [<tt>:time</tt>] any value containing both date and time
+ # [<tt>:date</tt>] any value containing only a date
+ module Signature
+ class SignatureError < StandardError # :nodoc:
+ end
+
+ private
+ def canonical_signature(params)
+ return nil if params.nil?
+ params.map do |param|
+ klass = signature_parameter_class(param)
+ if param.is_a?(Hash)
+ param[param.keys[0]] = klass
+ param
+ else
+ klass
+ end
+ end
+ end
+
+ def signature_parameter_class(param)
+ param = param.is_a?(Hash) ? param.values[0] : param
+ is_array = param.is_a?(Array)
+ param = is_array ? param[0] : param
+ param = param.is_a?(String) ? param.to_sym : param
+ param = param.is_a?(Symbol) ? signature_ruby_class(param) : param
+ is_array ? [param] : param
+ end
+
+
+ def canonical_signature_base_type(base_type)
+ base_type = base_type.to_sym
+ case base_type
+ when :int, :integer, :fixnum, :bignum
+ :int
+ when :string, :base64
+ :string
+ when :bool, :boolean
+ :bool
+ when :float, :double
+ :float
+ when :time, :datetime, :timestamp
+ :time
+ when :date
+ :date
+ else
+ raise(SignatureError, ":#{base_type} is not an ActionService base type")
+ end
+ end
+
+ def signature_ruby_class(base_type)
+ case canonical_signature_base_type(base_type)
+ when :int
+ Integer
+ when :string
+ String
+ when :bool
+ TrueClass
+ when :float
+ Float
+ when :time
+ Time
+ when :date
+ Date
+ end
+ end
+
+ def signature_base_type(ruby_class)
+ case ruby_class
+ when Bignum, Integer, Fixnum
+ :int
+ when String
+ :string
+ when TrueClass, FalseClass
+ :bool
+ when Float, Numeric, Precision
+ :float
+ when Time, DateTime
+ :time
+ when Date
+ :date
+ else
+ raise(SignatureError, "#{ruby_class.name} is not an ActionService base type")
+ end
+ end
+ end
+end
diff --git a/actionservice/setup.rb b/actionservice/setup.rb
new file mode 100644
index 0000000000..0807023db2
--- /dev/null
+++ b/actionservice/setup.rb
@@ -0,0 +1,1360 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2004 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map) # Ruby 1.4.6
+ module Enumerable
+ alias map collect
+ end
+end
+
+unless File.respond_to?(:read) # Ruby 1.6
+ def File.read(fname)
+ open(fname) {|f|
+ return f.read
+ }
+ end
+end
+
+def File.binread(fname)
+ open(fname, 'rb') {|f|
+ return f.read
+ }
+end
+
+# for corrupted windows stat(2)
+def File.dir?(path)
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+ raise SetupError, msg
+end
+
+#
+# Config
+#
+
+if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+ ARGV.delete(arg)
+ require arg.split(/=/, 2)[1]
+ $".push 'rbconfig.rb'
+else
+ require 'rbconfig'
+end
+
+def multipackage_install?
+ FileTest.directory?(File.dirname($0) + '/packages')
+end
+
+
+class ConfigItem
+ def initialize(name, template, default, desc)
+ @name = name.freeze
+ @template = template
+ @value = default
+ @default = default.dup.freeze
+ @description = desc
+ end
+
+ attr_reader :name
+ attr_reader :description
+
+ attr_accessor :default
+ alias help_default default
+
+ def help_opt
+ "--#{@name}=#{@template}"
+ end
+
+ def value
+ @value
+ end
+
+ def eval(table)
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
+ end
+
+ def set(val)
+ @value = check(val)
+ end
+
+ private
+
+ def check(val)
+ setup_rb_error "config: --#{name} requires argument" unless val
+ val
+ end
+end
+
+class BoolItem < ConfigItem
+ def config_type
+ 'bool'
+ end
+
+ def help_opt
+ "--#{@name}"
+ end
+
+ private
+
+ def check(val)
+ return 'yes' unless val
+ unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+ end
+ (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
+ end
+end
+
+class PathItem < ConfigItem
+ def config_type
+ 'path'
+ end
+
+ private
+
+ def check(path)
+ setup_rb_error "config: --#{@name} requires argument" unless path
+ path[0,1] == '$' ? path : File.expand_path(path)
+ end
+end
+
+class ProgramItem < ConfigItem
+ def config_type
+ 'program'
+ end
+end
+
+class SelectItem < ConfigItem
+ def initialize(name, template, default, desc)
+ super
+ @ok = template.split('/')
+ end
+
+ def config_type
+ 'select'
+ end
+
+ private
+
+ def check(val)
+ unless @ok.include?(val.strip)
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+ end
+ val.strip
+ end
+end
+
+class PackageSelectionItem < ConfigItem
+ def initialize(name, template, default, help_default, desc)
+ super name, template, default, desc
+ @help_default = help_default
+ end
+
+ attr_reader :help_default
+
+ def config_type
+ 'package'
+ end
+
+ private
+
+ def check(val)
+ unless File.dir?("packages/#{val}")
+ setup_rb_error "config: no such package: #{val}"
+ end
+ val
+ end
+end
+
+class ConfigTable_class
+
+ def initialize(items)
+ @items = items
+ @table = {}
+ items.each do |i|
+ @table[i.name] = i
+ end
+ ALIASES.each do |ali, name|
+ @table[ali] = @table[name]
+ end
+ end
+
+ include Enumerable
+
+ def each(&block)
+ @items.each(&block)
+ end
+
+ def key?(name)
+ @table.key?(name)
+ end
+
+ def lookup(name)
+ @table[name] or raise ArgumentError, "no such config item: #{name}"
+ end
+
+ def add(item)
+ @items.push item
+ @table[item.name] = item
+ end
+
+ def remove(name)
+ item = lookup(name)
+ @items.delete_if {|i| i.name == name }
+ @table.delete_if {|name, i| i.name == name }
+ item
+ end
+
+ def new
+ dup()
+ end
+
+ def savefile
+ '.config'
+ end
+
+ def load
+ begin
+ t = dup()
+ File.foreach(savefile()) do |line|
+ k, v = *line.split(/=/, 2)
+ t[k] = v.strip
+ end
+ t
+ rescue Errno::ENOENT
+ setup_rb_error $!.message + "#{File.basename($0)} config first"
+ end
+ end
+
+ def save
+ @items.each {|i| i.value }
+ File.open(savefile(), 'w') {|f|
+ @items.each do |i|
+ f.printf "%s=%s\n", i.name, i.value if i.value
+ end
+ }
+ end
+
+ def [](key)
+ lookup(key).eval(self)
+ end
+
+ def []=(key, val)
+ lookup(key).set val
+ end
+
+end
+
+c = ::Config::CONFIG
+
+rubypath = c['bindir'] + '/' + c['ruby_install_name']
+
+major = c['MAJOR'].to_i
+minor = c['MINOR'].to_i
+teeny = c['TEENY'].to_i
+version = "#{major}.#{minor}"
+
+# ruby ver. >= 1.4.4?
+newpath_p = ((major >= 2) or
+ ((major == 1) and
+ ((minor >= 5) or
+ ((minor == 4) and (teeny >= 4)))))
+
+if c['rubylibdir']
+ # V < 1.6.3
+ _stdruby = c['rubylibdir']
+ _siteruby = c['sitedir']
+ _siterubyver = c['sitelibdir']
+ _siterubyverarch = c['sitearchdir']
+elsif newpath_p
+ # 1.4.4 <= V <= 1.6.3
+ _stdruby = "$prefix/lib/ruby/#{version}"
+ _siteruby = c['sitedir']
+ _siterubyver = "$siteruby/#{version}"
+ _siterubyverarch = "$siterubyver/#{c['arch']}"
+else
+ # V < 1.4.4
+ _stdruby = "$prefix/lib/ruby/#{version}"
+ _siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
+ _siterubyver = _siteruby
+ _siterubyverarch = "$siterubyver/#{c['arch']}"
+end
+libdir = '-* dummy libdir *-'
+stdruby = '-* dummy rubylibdir *-'
+siteruby = '-* dummy site_ruby *-'
+siterubyver = '-* dummy site_ruby version *-'
+parameterize = lambda {|path|
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\
+ .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\
+ .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\
+ .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\
+ .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver')
+}
+libdir = parameterize.call(c['libdir'])
+stdruby = parameterize.call(_stdruby)
+siteruby = parameterize.call(_siteruby)
+siterubyver = parameterize.call(_siterubyver)
+siterubyverarch = parameterize.call(_siterubyverarch)
+
+if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+else
+ makeprog = 'make'
+end
+
+common_conf = [
+ PathItem.new('prefix', 'path', c['prefix'],
+ 'path prefix of target environment'),
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+ 'the directory for commands'),
+ PathItem.new('libdir', 'path', libdir,
+ 'the directory for libraries'),
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+ 'the directory for shared data'),
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+ 'the directory for man pages'),
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+ 'the directory for man pages'),
+ PathItem.new('stdruby', 'path', stdruby,
+ 'the directory for standard ruby libraries'),
+ PathItem.new('siteruby', 'path', siteruby,
+ 'the directory for version-independent aux ruby libraries'),
+ PathItem.new('siterubyver', 'path', siterubyver,
+ 'the directory for aux ruby libraries'),
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
+ 'the directory for aux ruby binaries'),
+ PathItem.new('rbdir', 'path', '$siterubyver',
+ 'the directory for ruby scripts'),
+ PathItem.new('sodir', 'path', '$siterubyverarch',
+ 'the directory for ruby extentions'),
+ PathItem.new('rubypath', 'path', rubypath,
+ 'the path to set to #! line'),
+ ProgramItem.new('rubyprog', 'name', rubypath,
+ 'the ruby program using for installation'),
+ ProgramItem.new('makeprog', 'name', makeprog,
+ 'the make program to compile ruby extentions'),
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+ 'shebang line (#!) editing mode'),
+ BoolItem.new('without-ext', 'yes/no', 'no',
+ 'does not compile/install ruby extentions')
+]
+class ConfigTable_class # open again
+ ALIASES = {
+ 'std-ruby' => 'stdruby',
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
+ 'site-ruby' => 'siterubyver', # For backward compatibility
+ 'bin-dir' => 'bindir',
+ 'bin-dir' => 'bindir',
+ 'rb-dir' => 'rbdir',
+ 'so-dir' => 'sodir',
+ 'data-dir' => 'datadir',
+ 'ruby-path' => 'rubypath',
+ 'ruby-prog' => 'rubyprog',
+ 'ruby' => 'rubyprog',
+ 'make-prog' => 'makeprog',
+ 'make' => 'makeprog'
+ }
+end
+multipackage_conf = [
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+ 'package names that you want to install'),
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+ 'package names that you do not want to install')
+]
+if multipackage_install?
+ ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf)
+else
+ ConfigTable = ConfigTable_class.new(common_conf)
+end
+
+
+module MetaConfigAPI
+
+ def eval_file_ifexist(fname)
+ instance_eval File.read(fname), fname, 1 if File.file?(fname)
+ end
+
+ def config_names
+ ConfigTable.map {|i| i.name }
+ end
+
+ def config?(name)
+ ConfigTable.key?(name)
+ end
+
+ def bool_config?(name)
+ ConfigTable.lookup(name).config_type == 'bool'
+ end
+
+ def path_config?(name)
+ ConfigTable.lookup(name).config_type == 'path'
+ end
+
+ def value_config?(name)
+ case ConfigTable.lookup(name).config_type
+ when 'bool', 'path'
+ true
+ else
+ false
+ end
+ end
+
+ def add_config(item)
+ ConfigTable.add item
+ end
+
+ def add_bool_config(name, default, desc)
+ ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+ end
+
+ def add_path_config(name, default, desc)
+ ConfigTable.add PathItem.new(name, 'path', default, desc)
+ end
+
+ def set_config_default(name, default)
+ ConfigTable.lookup(name).default = default
+ end
+
+ def remove_config(name)
+ ConfigTable.remove(name)
+ end
+
+end
+
+
+#
+# File Operations
+#
+
+module FileOperations
+
+ def mkdir_p(dirname, prefix = nil)
+ dirname = prefix + File.expand_path(dirname) if prefix
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
+ return if no_harm?
+
+ # does not check '/'... it's too abnormal case
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
+ if /\A[a-z]:\z/i =~ dirs[0]
+ disk = dirs.shift
+ dirs[0] = disk + dirs[0]
+ end
+ dirs.each_index do |idx|
+ path = dirs[0..idx].join('')
+ Dir.mkdir path unless File.dir?(path)
+ end
+ end
+
+ def rm_f(fname)
+ $stderr.puts "rm -f #{fname}" if verbose?
+ return if no_harm?
+
+ if File.exist?(fname) or File.symlink?(fname)
+ File.chmod 0777, fname
+ File.unlink fname
+ end
+ end
+
+ def rm_rf(dn)
+ $stderr.puts "rm -rf #{dn}" if verbose?
+ return if no_harm?
+
+ Dir.chdir dn
+ Dir.foreach('.') do |fn|
+ next if fn == '.'
+ next if fn == '..'
+ if File.dir?(fn)
+ verbose_off {
+ rm_rf fn
+ }
+ else
+ verbose_off {
+ rm_f fn
+ }
+ end
+ end
+ Dir.chdir '..'
+ Dir.rmdir dn
+ end
+
+ def move_file(src, dest)
+ File.unlink dest if File.exist?(dest)
+ begin
+ File.rename src, dest
+ rescue
+ File.open(dest, 'wb') {|f| f.write File.binread(src) }
+ File.chmod File.stat(src).mode, dest
+ File.unlink src
+ end
+ end
+
+ def install(from, dest, mode, prefix = nil)
+ $stderr.puts "install #{from} #{dest}" if verbose?
+ return if no_harm?
+
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+ str = File.binread(from)
+ if diff?(str, realdest)
+ verbose_off {
+ rm_f realdest if File.exist?(realdest)
+ }
+ File.open(realdest, 'wb') {|f|
+ f.write str
+ }
+ File.chmod mode, realdest
+
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+ if prefix
+ f.puts realdest.sub(prefix, '')
+ else
+ f.puts realdest
+ end
+ }
+ end
+ end
+
+ def diff?(new_content, path)
+ return true unless File.exist?(path)
+ new_content != File.binread(path)
+ end
+
+ def command(str)
+ $stderr.puts str if verbose?
+ system str or raise RuntimeError, "'system #{str}' failed"
+ end
+
+ def ruby(str)
+ command config('rubyprog') + ' ' + str
+ end
+
+ def make(task = '')
+ command config('makeprog') + ' ' + task
+ end
+
+ def extdir?(dir)
+ File.exist?(dir + '/MANIFEST')
+ end
+
+ def all_files_in(dirname)
+ Dir.open(dirname) {|d|
+ return d.select {|ent| File.file?("#{dirname}/#{ent}") }
+ }
+ end
+
+ REJECT_DIRS = %w(
+ CVS SCCS RCS CVS.adm .svn
+ )
+
+ def all_dirs_in(dirname)
+ Dir.open(dirname) {|d|
+ return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
+ }
+ end
+
+end
+
+
+#
+# Main Installer
+#
+
+module HookUtils
+
+ def run_hook(name)
+ try_run_hook "#{curr_srcdir()}/#{name}" or
+ try_run_hook "#{curr_srcdir()}/#{name}.rb"
+ end
+
+ def try_run_hook(fname)
+ return false unless File.file?(fname)
+ begin
+ instance_eval File.read(fname), fname, 1
+ rescue
+ setup_rb_error "hook #{fname} failed:\n" + $!.message
+ end
+ true
+ end
+
+end
+
+
+module HookScriptAPI
+
+ def get_config(key)
+ @config[key]
+ end
+
+ alias config get_config
+
+ def set_config(key, val)
+ @config[key] = val
+ end
+
+ #
+ # srcdir/objdir (works only in the package directory)
+ #
+
+ #abstract srcdir_root
+ #abstract objdir_root
+ #abstract relpath
+
+ def curr_srcdir
+ "#{srcdir_root()}/#{relpath()}"
+ end
+
+ def curr_objdir
+ "#{objdir_root()}/#{relpath()}"
+ end
+
+ def srcfile(path)
+ "#{curr_srcdir()}/#{path}"
+ end
+
+ def srcexist?(path)
+ File.exist?(srcfile(path))
+ end
+
+ def srcdirectory?(path)
+ File.dir?(srcfile(path))
+ end
+
+ def srcfile?(path)
+ File.file? srcfile(path)
+ end
+
+ def srcentries(path = '.')
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
+ return d.to_a - %w(. ..)
+ }
+ end
+
+ def srcfiles(path = '.')
+ srcentries(path).select {|fname|
+ File.file?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+ def srcdirectories(path = '.')
+ srcentries(path).select {|fname|
+ File.dir?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+end
+
+
+class ToplevelInstaller
+
+ Version = '3.3.1'
+ Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
+
+ TASKS = [
+ [ 'all', 'do config, setup, then install' ],
+ [ 'config', 'saves your configurations' ],
+ [ 'show', 'shows current configuration' ],
+ [ 'setup', 'compiles ruby extentions and others' ],
+ [ 'install', 'installs files' ],
+ [ 'clean', "does `make clean' for each extention" ],
+ [ 'distclean',"does `make distclean' for each extention" ]
+ ]
+
+ def ToplevelInstaller.invoke
+ instance().invoke
+ end
+
+ @singleton = nil
+
+ def ToplevelInstaller.instance
+ @singleton ||= new(File.dirname($0))
+ @singleton
+ end
+
+ include MetaConfigAPI
+
+ def initialize(ardir_root)
+ @config = nil
+ @options = { 'verbose' => true }
+ @ardir = File.expand_path(ardir_root)
+ end
+
+ def inspect
+ "#<#{self.class} #{__id__()}>"
+ end
+
+ def invoke
+ run_metaconfigs
+ case task = parsearg_global()
+ when nil, 'all'
+ @config = load_config('config')
+ parsearg_config
+ init_installers
+ exec_config
+ exec_setup
+ exec_install
+ else
+ @config = load_config(task)
+ __send__ "parsearg_#{task}"
+ init_installers
+ __send__ "exec_#{task}"
+ end
+ end
+
+ def run_metaconfigs
+ eval_file_ifexist "#{@ardir}/metaconfig"
+ end
+
+ def load_config(task)
+ case task
+ when 'config'
+ ConfigTable.new
+ when 'clean', 'distclean'
+ if File.exist?(ConfigTable.savefile)
+ then ConfigTable.load
+ else ConfigTable.new
+ end
+ else
+ ConfigTable.load
+ end
+ end
+
+ def init_installers
+ @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
+ end
+
+ #
+ # Hook Script API bases
+ #
+
+ def srcdir_root
+ @ardir
+ end
+
+ def objdir_root
+ '.'
+ end
+
+ def relpath
+ '.'
+ end
+
+ #
+ # Option Parsing
+ #
+
+ def parsearg_global
+ valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
+
+ while arg = ARGV.shift
+ case arg
+ when /\A\w+\z/
+ setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg
+ return arg
+
+ when '-q', '--quiet'
+ @options['verbose'] = false
+
+ when '--verbose'
+ @options['verbose'] = true
+
+ when '-h', '--help'
+ print_usage $stdout
+ exit 0
+
+ when '-v', '--version'
+ puts "#{File.basename($0)} version #{Version}"
+ exit 0
+
+ when '--copyright'
+ puts Copyright
+ exit 0
+
+ else
+ setup_rb_error "unknown global option '#{arg}'"
+ end
+ end
+
+ nil
+ end
+
+
+ def parsearg_no_options
+ unless ARGV.empty?
+ setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}"
+ end
+ end
+
+ alias parsearg_show parsearg_no_options
+ alias parsearg_setup parsearg_no_options
+ alias parsearg_clean parsearg_no_options
+ alias parsearg_distclean parsearg_no_options
+
+ def parsearg_config
+ re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/
+ @options['config-opt'] = []
+
+ while i = ARGV.shift
+ if /\A--?\z/ =~ i
+ @options['config-opt'] = ARGV.dup
+ break
+ end
+ m = re.match(i) or setup_rb_error "config: unknown option #{i}"
+ name, value = *m.to_a[1,2]
+ @config[name] = value
+ end
+ end
+
+ def parsearg_install
+ @options['no-harm'] = false
+ @options['install-prefix'] = ''
+ while a = ARGV.shift
+ case a
+ when /\A--no-harm\z/
+ @options['no-harm'] = true
+ when /\A--prefix=(.*)\z/
+ path = $1
+ path = File.expand_path(path) unless path[0,1] == '/'
+ @options['install-prefix'] = path
+ else
+ setup_rb_error "install: unknown option #{a}"
+ end
+ end
+ end
+
+ def print_usage(out)
+ out.puts 'Typical Installation Procedure:'
+ out.puts " $ ruby #{File.basename $0} config"
+ out.puts " $ ruby #{File.basename $0} setup"
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
+ out.puts
+ out.puts 'Detailed Usage:'
+ out.puts " ruby #{File.basename $0} <global option>"
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+ fmt = " %-24s %s\n"
+ out.puts
+ out.puts 'Global options:'
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
+ out.printf fmt, ' --verbose', 'output messages verbosely'
+ out.printf fmt, '-h,--help', 'print this message'
+ out.printf fmt, '-v,--version', 'print version and quit'
+ out.printf fmt, ' --copyright', 'print copyright and quit'
+ out.puts
+ out.puts 'Tasks:'
+ TASKS.each do |name, desc|
+ out.printf fmt, name, desc
+ end
+
+ fmt = " %-24s %s [%s]\n"
+ out.puts
+ out.puts 'Options for CONFIG or ALL:'
+ ConfigTable.each do |item|
+ out.printf fmt, item.help_opt, item.description, item.help_default
+ end
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+ out.puts
+ out.puts 'Options for INSTALL:'
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+ out.printf fmt, '--prefix=path', 'install path prefix', '$prefix'
+ out.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ @installer.exec_config
+ @config.save # must be final
+ end
+
+ def exec_setup
+ @installer.exec_setup
+ end
+
+ def exec_install
+ @installer.exec_install
+ end
+
+ def exec_show
+ ConfigTable.each do |i|
+ printf "%-20s %s\n", i.name, i.value
+ end
+ end
+
+ def exec_clean
+ @installer.exec_clean
+ end
+
+ def exec_distclean
+ @installer.exec_distclean
+ end
+
+end
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+ include HookUtils
+ include HookScriptAPI
+ include FileOperations
+
+ def initialize(ardir)
+ super
+ @packages = all_dirs_in("#{@ardir}/packages")
+ raise 'no package exists' if @packages.empty?
+ end
+
+ def run_metaconfigs
+ eval_file_ifexist "#{@ardir}/metaconfig"
+ @packages.each do |name|
+ eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
+ end
+ end
+
+ def init_installers
+ @installers = {}
+ @packages.each do |pack|
+ @installers[pack] = Installer.new(@config, @options,
+ "#{@ardir}/packages/#{pack}",
+ "packages/#{pack}")
+ end
+
+ with = extract_selection(config('with'))
+ without = extract_selection(config('without'))
+ @selected = @installers.keys.select {|name|
+ (with.empty? or with.include?(name)) \
+ and not without.include?(name)
+ }
+ end
+
+ def extract_selection(list)
+ a = list.split(/,/)
+ a.each do |name|
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
+ end
+ a
+ end
+
+ def print_usage(f)
+ super
+ f.puts 'Inluded packages:'
+ f.puts ' ' + @packages.sort.join(' ')
+ f.puts
+ end
+
+ #
+ # multi-package metaconfig API
+ #
+
+ attr_reader :packages
+
+ def declare_packages(list)
+ raise 'package list is empty' if list.empty?
+ list.each do |name|
+ raise "directory packages/#{name} does not exist"\
+ unless File.dir?("#{@ardir}/packages/#{name}")
+ end
+ @packages = list
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ run_hook 'pre-config'
+ each_selected_installers {|inst| inst.exec_config }
+ run_hook 'post-config'
+ @config.save # must be final
+ end
+
+ def exec_setup
+ run_hook 'pre-setup'
+ each_selected_installers {|inst| inst.exec_setup }
+ run_hook 'post-setup'
+ end
+
+ def exec_install
+ run_hook 'pre-install'
+ each_selected_installers {|inst| inst.exec_install }
+ run_hook 'post-install'
+ end
+
+ def exec_clean
+ rm_f ConfigTable.savefile
+ run_hook 'pre-clean'
+ each_selected_installers {|inst| inst.exec_clean }
+ run_hook 'post-clean'
+ end
+
+ def exec_distclean
+ rm_f ConfigTable.savefile
+ run_hook 'pre-distclean'
+ each_selected_installers {|inst| inst.exec_distclean }
+ run_hook 'post-distclean'
+ end
+
+ #
+ # lib
+ #
+
+ def each_selected_installers
+ Dir.mkdir 'packages' unless File.dir?('packages')
+ @selected.each do |pack|
+ $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+ Dir.chdir "packages/#{pack}"
+ yield @installers[pack]
+ Dir.chdir '../..'
+ end
+ end
+
+ def verbose?
+ @options['verbose']
+ end
+
+ def no_harm?
+ @options['no-harm']
+ end
+
+end
+
+
+class Installer
+
+ FILETYPES = %w( bin lib ext data )
+
+ include HookScriptAPI
+ include HookUtils
+ include FileOperations
+
+ def initialize(config, opt, srcroot, objroot)
+ @config = config
+ @options = opt
+ @srcdir = File.expand_path(srcroot)
+ @objdir = File.expand_path(objroot)
+ @currdir = '.'
+ end
+
+ def inspect
+ "#<#{self.class} #{File.basename(@srcdir)}>"
+ end
+
+ #
+ # Hook Script API base methods
+ #
+
+ def srcdir_root
+ @srcdir
+ end
+
+ def objdir_root
+ @objdir
+ end
+
+ def relpath
+ @currdir
+ end
+
+ #
+ # configs/options
+ #
+
+ def no_harm?
+ @options['no-harm']
+ end
+
+ def verbose?
+ @options['verbose']
+ end
+
+ def verbose_off
+ begin
+ save, @options['verbose'] = @options['verbose'], false
+ yield
+ ensure
+ @options['verbose'] = save
+ end
+ end
+
+ #
+ # TASK config
+ #
+
+ def exec_config
+ exec_task_traverse 'config'
+ end
+
+ def config_dir_bin(rel)
+ end
+
+ def config_dir_lib(rel)
+ end
+
+ def config_dir_ext(rel)
+ extconf if extdir?(curr_srcdir())
+ end
+
+ def extconf
+ opt = @options['config-opt'].join(' ')
+ command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}"
+ end
+
+ def config_dir_data(rel)
+ end
+
+ #
+ # TASK setup
+ #
+
+ def exec_setup
+ exec_task_traverse 'setup'
+ end
+
+ def setup_dir_bin(rel)
+ all_files_in(curr_srcdir()).each do |fname|
+ adjust_shebang "#{curr_srcdir()}/#{fname}"
+ end
+ end
+
+ def adjust_shebang(path)
+ return if no_harm?
+ tmpfile = File.basename(path) + '.tmp'
+ begin
+ File.open(path, 'rb') {|r|
+ first = r.gets
+ return unless File.basename(config('rubypath')) == 'ruby'
+ return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby'
+ $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
+ File.open(tmpfile, 'wb') {|w|
+ w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath'))
+ w.write r.read
+ }
+ move_file tmpfile, File.basename(path)
+ }
+ ensure
+ File.unlink tmpfile if File.exist?(tmpfile)
+ end
+ end
+
+ def setup_dir_lib(rel)
+ end
+
+ def setup_dir_ext(rel)
+ make if extdir?(curr_srcdir())
+ end
+
+ def setup_dir_data(rel)
+ end
+
+ #
+ # TASK install
+ #
+
+ def exec_install
+ rm_f 'InstalledFiles'
+ exec_task_traverse 'install'
+ end
+
+ def install_dir_bin(rel)
+ install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755
+ end
+
+ def install_dir_lib(rel)
+ install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644
+ end
+
+ def install_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ install_files ruby_extentions('.'),
+ "#{config('sodir')}/#{File.dirname(rel)}",
+ 0555
+ end
+
+ def install_dir_data(rel)
+ install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644
+ end
+
+ def install_files(list, dest, mode)
+ mkdir_p dest, @options['install-prefix']
+ list.each do |fname|
+ install fname, dest, mode, @options['install-prefix']
+ end
+ end
+
+ def ruby_scripts
+ collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
+ end
+
+ # picked up many entries from cvs-1.11.1/src/ignore.c
+ reject_patterns = %w(
+ core RCSLOG tags TAGS .make.state
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+ *.org *.in .*
+ )
+ mapping = {
+ '.' => '\.',
+ '$' => '\$',
+ '#' => '\#',
+ '*' => '.*'
+ }
+ REJECT_PATTERNS = Regexp.new('\A(?:' +
+ reject_patterns.map {|pat|
+ pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
+ }.join('|') +
+ ')\z')
+
+ def collect_filenames_auto
+ mapdir((existfiles() - hookfiles()).reject {|fname|
+ REJECT_PATTERNS =~ fname
+ })
+ end
+
+ def existfiles
+ all_files_in(curr_srcdir()) | all_files_in('.')
+ end
+
+ def hookfiles
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+ }.flatten
+ end
+
+ def mapdir(filelist)
+ filelist.map {|fname|
+ if File.exist?(fname) # objdir
+ fname
+ else # srcdir
+ File.join(curr_srcdir(), fname)
+ end
+ }
+ end
+
+ def ruby_extentions(dir)
+ Dir.open(dir) {|d|
+ ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname }
+ if ents.empty?
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+ end
+ return ents
+ }
+ end
+
+ #
+ # TASK clean
+ #
+
+ def exec_clean
+ exec_task_traverse 'clean'
+ rm_f ConfigTable.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ def clean_dir_bin(rel)
+ end
+
+ def clean_dir_lib(rel)
+ end
+
+ def clean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'clean' if File.file?('Makefile')
+ end
+
+ def clean_dir_data(rel)
+ end
+
+ #
+ # TASK distclean
+ #
+
+ def exec_distclean
+ exec_task_traverse 'distclean'
+ rm_f ConfigTable.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ def distclean_dir_bin(rel)
+ end
+
+ def distclean_dir_lib(rel)
+ end
+
+ def distclean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'distclean' if File.file?('Makefile')
+ end
+
+ #
+ # lib
+ #
+
+ def exec_task_traverse(task)
+ run_hook "pre-#{task}"
+ FILETYPES.each do |type|
+ if config('without-ext') == 'yes' and type == 'ext'
+ $stderr.puts 'skipping ext/* by user option' if verbose?
+ next
+ end
+ traverse task, type, "#{task}_dir_#{type}"
+ end
+ run_hook "post-#{task}"
+ end
+
+ def traverse(task, rel, mid)
+ dive_into(rel) {
+ run_hook "pre-#{task}"
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+ all_dirs_in(curr_srcdir()).each do |d|
+ traverse task, "#{rel}/#{d}", mid
+ end
+ run_hook "post-#{task}"
+ }
+ end
+
+ def dive_into(rel)
+ return unless File.dir?("#{@srcdir}/#{rel}")
+
+ dir = File.basename(rel)
+ Dir.mkdir dir unless File.dir?(dir)
+ prevdir = Dir.pwd
+ Dir.chdir dir
+ $stderr.puts '---> ' + rel if verbose?
+ @currdir = rel
+ yield
+ Dir.chdir prevdir
+ $stderr.puts '<--- ' + rel if verbose?
+ @currdir = File.dirname(rel)
+ end
+
+end
+
+
+if $0 == __FILE__
+ begin
+ if multipackage_install?
+ ToplevelInstallerMulti.invoke
+ else
+ ToplevelInstaller.invoke
+ end
+ rescue SetupError
+ raise if $DEBUG
+ $stderr.puts $!.message
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+ exit 1
+ end
+end
diff --git a/actionservice/test/abstract_client.rb b/actionservice/test/abstract_client.rb
new file mode 100644
index 0000000000..662bdd9431
--- /dev/null
+++ b/actionservice/test/abstract_client.rb
@@ -0,0 +1,124 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+require 'webrick'
+require 'webrick/log'
+require 'singleton'
+
+module ClientTest
+ class Person < ActionService::Struct
+ member :firstnames, [:string]
+ member :lastname, :string
+
+ def ==(other)
+ firstnames == other.firstnames && lastname == other.lastname
+ end
+ end
+
+ class API < ActionService::API::Base
+ api_method :void
+ api_method :normal, :expects => [:int, :int], :returns => [:int]
+ api_method :array_return, :returns => [[Person]]
+ api_method :struct_pass, :expects => [[Person]], :returns => [:bool]
+ api_method :client_container, :returns => [:int]
+ end
+
+ class NullLogOut
+ def <<(*args); end
+ end
+
+ class Container < ActionController::Base
+ service_api API
+
+ attr :value_void
+ attr :value_normal
+ attr :value_array_return
+ attr :value_struct_pass
+
+ def initialize
+ @session = @assigns = {}
+ @value_void = nil
+ @value_normal = nil
+ @value_array_return = nil
+ @value_struct_pass = nil
+ end
+
+ def void
+ @value_void = @method_params
+ end
+
+ def normal
+ @value_normal = @method_params
+ 5
+ end
+
+ def array_return
+ person = Person.new
+ person.firstnames = ["one", "two"]
+ person.lastname = "last"
+ @value_array_return = [person]
+ end
+
+ def struct_pass
+ @value_struct_pass = @method_params
+ true
+ end
+
+ def client_container
+ 50
+ end
+
+ def protocol_request(request)
+ probe_request_protocol(request)
+ end
+
+ def dispatch_request(protocol_request)
+ dispatch_service_request(protocol_request)
+ end
+ end
+
+ class AbstractClientLet < WEBrick::HTTPServlet::AbstractServlet
+ def initialize(controller)
+ @controller = controller
+ end
+
+ def get_instance(*args)
+ self
+ end
+
+ def require_path_info?
+ false
+ end
+
+ def do_GET(req, res)
+ raise WEBrick::HTTPStatus::MethodNotAllowed, "GET request not allowed."
+ end
+
+ def do_POST(req, res)
+ raise NotImplementedError
+ end
+ end
+
+ class AbstractServer
+ include ClientTest
+ include Singleton
+ attr :container
+ def initialize
+ @container = Container.new
+ @clientlet = create_clientlet(@container)
+ log = WEBrick::BasicLog.new(NullLogOut.new)
+ @server = WEBrick::HTTPServer.new(:Port => server_port, :Logger => log, :AccessLog => log)
+ @server.mount('/', @clientlet)
+ @thr = Thread.new { @server.start }
+ until @server.status == :Running; end
+ at_exit { @server.stop; @thr.join }
+ end
+
+ protected
+ def create_clientlet
+ raise NotImplementedError
+ end
+
+ def server_port
+ raise NotImplementedError
+ end
+ end
+end
diff --git a/actionservice/test/abstract_soap.rb b/actionservice/test/abstract_soap.rb
new file mode 100644
index 0000000000..7454be9bdf
--- /dev/null
+++ b/actionservice/test/abstract_soap.rb
@@ -0,0 +1,58 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+require 'soap/rpc/element'
+
+class SoapTestError < StandardError
+end
+
+class AbstractSoapTest < Test::Unit::TestCase
+ def default_test
+ end
+
+ protected
+ def service_name
+ raise NotImplementedError
+ end
+
+ def do_soap_call(public_method_name, *args)
+ mapper = @container.class.soap_mapper
+ param_def = []
+ i = 1
+ args.each do |arg|
+ mapping = mapper.lookup(arg.class)
+ param_def << ["in", "param#{i}", mapping.registry_mapping]
+ i += 1
+ end
+ qname = XSD::QName.new('urn:ActionService', public_method_name)
+ request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
+ soap_args = []
+ i = 1
+ args.each do |arg|
+ soap_args << ["param#{i}", SOAP::Mapping.obj2soap(arg)]
+ i += 1
+ end
+ request.set_param(soap_args)
+ header = SOAP::SOAPHeader.new
+ body = SOAP::SOAPBody.new(request)
+ envelope = SOAP::SOAPEnvelope.new(header, body)
+ raw_request = SOAP::Processor.marshal(envelope)
+ test_request = ActionController::TestRequest.new
+ test_request.request_parameters['action'] = service_name
+ test_request.env['REQUEST_METHOD'] = "POST"
+ test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
+ test_request.env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{public_method_name}"
+ test_request.env['RAW_POST_DATA'] = raw_request
+ test_response = ActionController::TestResponse.new
+ response = yield test_request, test_response
+ raw_body = response.respond_to?(:body) ? response.body : response.raw_body
+ envelope = SOAP::Processor.unmarshal(raw_body)
+ if envelope
+ if envelope.body.response
+ SOAP::Mapping.soap2obj(envelope.body.response)
+ else
+ nil
+ end
+ else
+ raise(SoapTestError, "empty/invalid body from server")
+ end
+ end
+end
diff --git a/actionservice/test/abstract_unit.rb b/actionservice/test/abstract_unit.rb
new file mode 100644
index 0000000000..54ca73b35c
--- /dev/null
+++ b/actionservice/test/abstract_unit.rb
@@ -0,0 +1,9 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+
+require 'test/unit'
+require 'action_service'
+require 'action_controller'
+require 'action_controller/test_process'
+
+ActionController::Base.logger = nil
+ActionController::Base.ignore_missing_templates = true
diff --git a/actionservice/test/api_test.rb b/actionservice/test/api_test.rb
new file mode 100644
index 0000000000..2ef5cc7bda
--- /dev/null
+++ b/actionservice/test/api_test.rb
@@ -0,0 +1,52 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+module APITest
+ class API < ActionService::API::Base
+ api_method :void
+ api_method :expects_and_returns, :expects_and_returns => [:string]
+ api_method :expects, :expects => [:int, :bool]
+ api_method :returns, :returns => [:int, [:string]]
+ api_method :named_signature, :expects => [{:appkey=>:int}, {:publish=>:bool}]
+ api_method :string_types, :expects => ['int', 'string', 'bool']
+ api_method :class_types, :expects => [TrueClass, Bignum, String]
+ end
+end
+
+class TC_API < Test::Unit::TestCase
+ API = APITest::API
+
+ def test_api_method_declaration
+ %w(
+ void
+ expects_and_returns
+ expects
+ returns
+ named_signature
+ string_types
+ class_types
+ ).each do |name|
+ name = name.to_sym
+ public_name = API.public_api_method_name(name)
+ assert(API.has_api_method?(name))
+ assert(API.has_public_api_method?(public_name))
+ assert(API.api_method_name(public_name) == name)
+ assert(API.api_methods.has_key?(name))
+ end
+ end
+
+ def test_signature_canonicalization
+ assert_equal({:expects=>nil, :returns=>nil}, API.api_methods[:void])
+ assert_equal({:expects=>[String], :returns=>[String]}, API.api_methods[:expects_and_returns])
+ assert_equal({:expects=>[Integer, TrueClass], :returns=>nil}, API.api_methods[:expects])
+ assert_equal({:expects=>nil, :returns=>[Integer, [String]]}, API.api_methods[:returns])
+ assert_equal({:expects=>[{:appkey=>Integer}, {:publish=>TrueClass}], :returns=>nil}, API.api_methods[:named_signature])
+ assert_equal({:expects=>[Integer, String, TrueClass], :returns=>nil}, API.api_methods[:string_types])
+ assert_equal({:expects=>[TrueClass, Bignum, String], :returns=>nil}, API.api_methods[:class_types])
+ end
+
+ def test_not_instantiable
+ assert_raises(NoMethodError) do
+ API.new
+ end
+ end
+end
diff --git a/actionservice/test/base_test.rb b/actionservice/test/base_test.rb
new file mode 100644
index 0000000000..21456f6c5a
--- /dev/null
+++ b/actionservice/test/base_test.rb
@@ -0,0 +1,42 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+module BaseTest
+ class API < ActionService::API::Base
+ api_method :add, :expects => [:int, :int], :returns => [:int]
+ api_method :void
+ end
+
+ class PristineAPI < ActionService::API::Base
+ inflect_names false
+
+ api_method :add
+ api_method :under_score
+ end
+
+ class Service < ActionService::Base
+ service_api API
+
+ def add(a, b)
+ end
+
+ def void
+ end
+ end
+
+ class PristineService < ActionService::Base
+ service_api PristineAPI
+
+ def add
+ end
+
+ def under_score
+ end
+ end
+end
+
+class TC_Base < Test::Unit::TestCase
+ def test_options
+ assert(BaseTest::PristineService.service_api.inflect_names == false)
+ assert(BaseTest::Service.service_api.inflect_names == true)
+ end
+end
diff --git a/actionservice/test/client_soap_test.rb b/actionservice/test/client_soap_test.rb
new file mode 100644
index 0000000000..e802812cc1
--- /dev/null
+++ b/actionservice/test/client_soap_test.rb
@@ -0,0 +1,87 @@
+require File.dirname(__FILE__) + '/abstract_client'
+
+
+module ClientSoapTest
+ PORT = 8998
+
+ class SoapClientLet < ClientTest::AbstractClientLet
+ def do_POST(req, res)
+ test_request = ActionController::TestRequest.new
+ test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1]
+ test_request.env['REQUEST_METHOD'] = "POST"
+ test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
+ test_request.env['HTTP_SOAPACTION'] = req.header['soapaction'][0]
+ test_request.env['RAW_POST_DATA'] = req.body
+ protocol_request = @controller.protocol_request(test_request)
+ response = @controller.dispatch_request(protocol_request)
+ res.header['content-type'] = 'text/xml'
+ res.body = response.raw_body
+ rescue Exception => e
+ $stderr.puts e.message
+ $stderr.puts e.backtrace.join("\n")
+ end
+ end
+
+ class ClientContainer < ActionController::Base
+ client_api :client, :soap, "http://localhost:#{PORT}/client/api", :api => ClientTest::API
+
+ def get_client
+ client
+ end
+ end
+
+ class SoapServer < ClientTest::AbstractServer
+ def create_clientlet(controller)
+ SoapClientLet.new(controller)
+ end
+
+ def server_port
+ PORT
+ end
+ end
+end
+
+class TC_ClientSoap < Test::Unit::TestCase
+ include ClientTest
+ include ClientSoapTest
+
+ def setup
+ @server = SoapServer.instance
+ @container = @server.container
+ @client = ActionService::Client::Soap.new(API, "http://localhost:#{@server.server_port}/client/api")
+ end
+
+ def test_void
+ assert(@container.value_void.nil?)
+ @client.void
+ assert(!@container.value_void.nil?)
+ end
+
+ def test_normal
+ assert(@container.value_normal.nil?)
+ assert_equal(5, @client.normal(5, 6))
+ assert_equal([5, 6], @container.value_normal)
+ end
+
+ def test_array_return
+ assert(@container.value_array_return.nil?)
+ new_person = Person.new
+ new_person.firstnames = ["one", "two"]
+ new_person.lastname = "last"
+ assert_equal([new_person], @client.array_return)
+ assert_equal([new_person], @container.value_array_return)
+ end
+
+ def test_struct_pass
+ assert(@container.value_struct_pass.nil?)
+ new_person = Person.new
+ new_person.firstnames = ["one", "two"]
+ new_person.lastname = "last"
+ assert_equal(true, @client.struct_pass([new_person]))
+ assert_equal([[new_person]], @container.value_struct_pass)
+ end
+
+ def test_client_container
+ assert_equal(50, ClientContainer.new.get_client.client_container)
+ end
+end
diff --git a/actionservice/test/client_xmlrpc_test.rb b/actionservice/test/client_xmlrpc_test.rb
new file mode 100644
index 0000000000..9c6abc3520
--- /dev/null
+++ b/actionservice/test/client_xmlrpc_test.rb
@@ -0,0 +1,86 @@
+require File.dirname(__FILE__) + '/abstract_client'
+
+
+module ClientXmlRpcTest
+ PORT = 8999
+
+ class XmlRpcClientLet < ClientTest::AbstractClientLet
+ def do_POST(req, res)
+ test_request = ActionController::TestRequest.new
+ test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1]
+ test_request.env['REQUEST_METHOD'] = "POST"
+ test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
+ test_request.env['RAW_POST_DATA'] = req.body
+ protocol_request = @controller.protocol_request(test_request)
+ response = @controller.dispatch_request(protocol_request)
+ res.header['content-type'] = 'text/xml'
+ res.body = response.raw_body
+ rescue Exception => e
+ $stderr.puts e.message
+ $stderr.puts e.backtrace.join("\n")
+ end
+ end
+
+ class ClientContainer < ActionController::Base
+ client_api :client, :xmlrpc, "http://localhost:#{PORT}/client/api", :api => ClientTest::API
+
+ def get_client
+ client
+ end
+ end
+
+ class XmlRpcServer < ClientTest::AbstractServer
+ def create_clientlet(controller)
+ XmlRpcClientLet.new(controller)
+ end
+
+ def server_port
+ PORT
+ end
+ end
+end
+
+class TC_ClientXmlRpc < Test::Unit::TestCase
+ include ClientTest
+ include ClientXmlRpcTest
+
+ def setup
+ @server = XmlRpcServer.instance
+ @container = @server.container
+ @client = ActionService::Client::XmlRpc.new(API, "http://localhost:#{@server.server_port}/client/api")
+ end
+
+ def test_void
+ assert(@container.value_void.nil?)
+ @client.void
+ assert(!@container.value_void.nil?)
+ end
+
+ def test_normal
+ assert(@container.value_normal.nil?)
+ assert_equal(5, @client.normal(5, 6))
+ assert_equal([5, 6], @container.value_normal)
+ end
+
+ def test_array_return
+ assert(@container.value_array_return.nil?)
+ new_person = Person.new
+ new_person.firstnames = ["one", "two"]
+ new_person.lastname = "last"
+ assert_equal([new_person], @client.array_return)
+ assert_equal([new_person], @container.value_array_return)
+ end
+
+ def test_struct_pass
+ assert(@container.value_struct_pass.nil?)
+ new_person = Person.new
+ new_person.firstnames = ["one", "two"]
+ new_person.lastname = "last"
+ assert_equal(true, @client.struct_pass([new_person]))
+ assert_equal([[new_person]], @container.value_struct_pass)
+ end
+
+ def test_client_container
+ assert_equal(50, ClientContainer.new.get_client.client_container)
+ end
+end
diff --git a/actionservice/test/container_test.rb b/actionservice/test/container_test.rb
new file mode 100644
index 0000000000..b222138087
--- /dev/null
+++ b/actionservice/test/container_test.rb
@@ -0,0 +1,53 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+module ContainerTest
+
+ $immediate_service = Object.new
+ $deferred_service = Object.new
+
+ class DelegateContainer < ActionController::Base
+ service_dispatching_mode :delegated
+
+ attr :flag
+ attr :previous_flag
+
+ def initialize
+ @previous_flag = nil
+ @flag = true
+ end
+
+ service :immediate_service, $immediate_service
+ service(:deferred_service) { @previous_flag = @flag; @flag = false; $deferred_service }
+ end
+
+ class DirectContainer < ActionController::Base
+ service_dispatching_mode :direct
+ end
+end
+
+class TC_Container < Test::Unit::TestCase
+ def setup
+ @delegate_container = ContainerTest::DelegateContainer.new
+ @direct_container = ContainerTest::DirectContainer.new
+ end
+
+ def test_registration
+ assert(ContainerTest::DelegateContainer.has_service?(:immediate_service))
+ assert(ContainerTest::DelegateContainer.has_service?(:deferred_service))
+ assert(!ContainerTest::DelegateContainer.has_service?(:fake_service))
+ end
+
+ def test_service_object
+ assert(@delegate_container.flag == true)
+ assert(@delegate_container.service_object(:immediate_service) == $immediate_service)
+ assert(@delegate_container.previous_flag.nil?)
+ assert(@delegate_container.flag == true)
+ assert(@delegate_container.service_object(:deferred_service) == $deferred_service)
+ assert(@delegate_container.previous_flag == true)
+ assert(@delegate_container.flag == false)
+ end
+
+ def test_direct_container
+ assert(ContainerTest::DirectContainer.service_dispatching_mode == :direct)
+ end
+end
diff --git a/actionservice/test/invocation_test.rb b/actionservice/test/invocation_test.rb
new file mode 100644
index 0000000000..d8ecdc4e0b
--- /dev/null
+++ b/actionservice/test/invocation_test.rb
@@ -0,0 +1,158 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+module InvocationTest
+ class API < ActionService::API::Base
+ api_method :add, :expects => [:int, :int], :returns => [:int]
+ api_method :transmogrify, :expects_and_returns => [:string]
+ api_method :fail_with_reason
+ api_method :fail_generic
+ api_method :no_before
+ api_method :no_after
+ api_method :only_one
+ api_method :only_two
+ end
+
+ class Service < ActionService::Base
+ service_api API
+
+ before_invocation :intercept_before, :except => [:no_before]
+ after_invocation :intercept_after, :except => [:no_after]
+ before_invocation :intercept_only, :only => [:only_one, :only_two]
+
+ attr_accessor :before_invoked
+ attr_accessor :after_invoked
+ attr_accessor :only_invoked
+ attr_accessor :invocation_result
+
+ def initialize
+ @before_invoked = nil
+ @after_invoked = nil
+ @only_invoked = nil
+ @invocation_result = nil
+ end
+
+ def add(a, b)
+ a + b
+ end
+
+ def transmogrify(str)
+ str.upcase
+ end
+
+ def fail_with_reason
+ end
+
+ def fail_generic
+ end
+
+ def no_before
+ 5
+ end
+
+ def no_after
+ end
+
+ def only_one
+ end
+
+ def only_two
+ end
+
+ def not_public
+ end
+
+ protected
+ def intercept_before(name, args)
+ @before_invoked = name
+ return [false, "permission denied"] if name == :fail_with_reason
+ return false if name == :fail_generic
+ end
+
+ def intercept_after(name, args, result)
+ @after_invoked = name
+ @invocation_result = result
+ end
+
+ def intercept_only(name, args)
+ raise "Interception error" unless name == :only_one || name == :only_two
+ @only_invoked = name
+ end
+ end
+end
+
+class TC_Invocation < Test::Unit::TestCase
+ include ActionService::Invocation
+
+ def setup
+ @service = InvocationTest::Service.new
+ end
+
+ def test_invocation
+ assert(perform_invocation(:add, 5, 10) == 15)
+ assert(perform_invocation(:transmogrify, "hello") == "HELLO")
+ assert_raises(InvocationError) do
+ perform_invocation(:not_public)
+ end
+ assert_raises(InvocationError) do
+ perform_invocation(:nonexistent_method_xyzzy)
+ end
+ end
+
+ def test_interceptor_registration
+ assert(InvocationTest::Service.before_invocation_interceptors.length == 2)
+ assert(InvocationTest::Service.after_invocation_interceptors.length == 1)
+ end
+
+ def test_interception
+ assert(@service.before_invoked.nil? && @service.after_invoked.nil? && @service.only_invoked.nil? && @service.invocation_result.nil?)
+ perform_invocation(:add, 20, 50)
+ assert(@service.before_invoked == :add)
+ assert(@service.after_invoked == :add)
+ assert(@service.invocation_result == 70)
+ end
+
+ def test_interception_canceling
+ reason = nil
+ perform_invocation(:fail_with_reason){|r| reason = r}
+ assert(@service.before_invoked == :fail_with_reason)
+ assert(@service.after_invoked.nil?)
+ assert(@service.invocation_result.nil?)
+ assert(reason == "permission denied")
+ reason = true
+ @service.before_invoked = @service.after_invoked = @service.invocation_result = nil
+ perform_invocation(:fail_generic){|r| reason = r}
+ assert(@service.before_invoked == :fail_generic)
+ assert(@service.after_invoked.nil?)
+ assert(@service.invocation_result.nil?)
+ assert(reason == true)
+ end
+
+ def test_interception_except_conditions
+ perform_invocation(:no_before)
+ assert(@service.before_invoked.nil?)
+ assert(@service.after_invoked == :no_before)
+ assert(@service.invocation_result == 5)
+ @service.before_invoked = @service.after_invoked = @service.invocation_result = nil
+ perform_invocation(:no_after)
+ assert(@service.before_invoked == :no_after)
+ assert(@service.after_invoked.nil?)
+ assert(@service.invocation_result.nil?)
+ end
+
+ def test_interception_only_conditions
+ assert(@service.only_invoked.nil?)
+ perform_invocation(:only_one)
+ assert(@service.only_invoked == :only_one)
+ @service.only_invoked = nil
+ perform_invocation(:only_two)
+ assert(@service.only_invoked == :only_two)
+ end
+
+ private
+ def perform_invocation(method_name, *args, &block)
+ public_method_name = @service.class.service_api.public_api_method_name(method_name)
+ args ||= []
+ request = InvocationRequest.new(ConcreteInvocation, public_method_name, method_name, args)
+ @service.perform_invocation(request, &block)
+ end
+end
diff --git a/actionservice/test/protocol_registry_test.rb b/actionservice/test/protocol_registry_test.rb
new file mode 100644
index 0000000000..8e2b9659a6
--- /dev/null
+++ b/actionservice/test/protocol_registry_test.rb
@@ -0,0 +1,53 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+
+module Foo
+ include ActionService::Protocol
+
+ def self.append_features(base)
+ super
+ base.register_protocol(BodyOnly, FooMinimalProtocol)
+ base.register_protocol(HeaderAndBody, FooMinimalProtocolTwo)
+ base.register_protocol(HeaderAndBody, FooMinimalProtocolTwo)
+ base.register_protocol(HeaderAndBody, FooFullProtocol)
+ end
+
+ class FooFullProtocol < AbstractProtocol
+ def self.create_protocol_request(klass, request)
+ protocol = FooFullProtocol.new klass
+ ActionService::Protocol::ProtocolRequest.new(protocol, '', '', '', '')
+ end
+ end
+
+ class FooMinimalProtocol < AbstractProtocol
+ def self.create_protocol_request(klass, request)
+ protocol = FooMinimalProtocol.new klass
+ ActionService::Protocol::ProtocolRequest.new(protocol, '', '', '', '')
+ end
+ end
+
+ class FooMinimalProtocolTwo < AbstractProtocol
+ end
+end
+
+class ProtocolRegistry
+ include ActionService::Protocol::Registry
+ include Foo
+
+ def all_protocols
+ header_and_body_protocols + body_only_protocols
+ end
+
+ def protocol_request
+ probe_request_protocol(nil)
+ end
+end
+
+
+class TC_ProtocolRegistry < Test::Unit::TestCase
+ def test_registration
+ registry = ProtocolRegistry.new
+ assert(registry.all_protocols.length == 4)
+ assert(registry.protocol_request.protocol.is_a?(Foo::FooFullProtocol))
+ end
+end
diff --git a/actionservice/test/protocol_soap_test.rb b/actionservice/test/protocol_soap_test.rb
new file mode 100644
index 0000000000..eaccd64e4e
--- /dev/null
+++ b/actionservice/test/protocol_soap_test.rb
@@ -0,0 +1,226 @@
+require File.dirname(__FILE__) + '/abstract_soap'
+
+module ProtocolSoapTest
+ class Person < ActionService::Struct
+ member :id, Integer
+ member :names, [String]
+ member :lastname, String
+ member :deleted, TrueClass
+
+ def ==(other)
+ id == other.id && names == other.names && lastname == other.lastname && deleted == other.deleted
+ end
+ end
+
+ class API < ActionService::API::Base
+ api_method :argument_passing, :expects => [{:int=>:int}, {:string=>:string}, {:array=>[:int]}], :returns => [:bool]
+ api_method :array_returner, :returns => [[:int]]
+ api_method :nil_returner
+ api_method :struct_array_returner, :returns => [[Person]]
+ api_method :exception_thrower
+
+ default_api_method :default
+ end
+
+ class Service < ActionService::Base
+ service_api API
+
+ attr :int
+ attr :string
+ attr :array
+ attr :values
+ attr :person
+ attr :default_args
+
+ def initialize
+ @int = 20
+ @string = "wrong string value"
+ @default_args = nil
+ end
+
+ def argument_passing(int, string, array)
+ @int = int
+ @string = string
+ @array = array
+ true
+ end
+
+ def array_returner
+ @values = [1, 2, 3]
+ end
+
+ def nil_returner
+ nil
+ end
+
+ def struct_array_returner
+ @person = Person.new
+ @person.id = 5
+ @person.names = ["one", "two"]
+ @person.lastname = "test"
+ @person.deleted = false
+ [@person]
+ end
+
+ def exception_thrower
+ raise "Hi, I'm a SOAP error"
+ end
+
+ def default(*args)
+ @default_args = args
+ nil
+ end
+ end
+
+ class AbstractContainer
+ include ActionService::API
+ include ActionService::Container
+ include ActionService::Protocol::Registry
+ include ActionService::Protocol::Soap
+
+ wsdl_service_name 'Test'
+
+ def protocol_request(request)
+ probe_request_protocol(request)
+ end
+
+ def dispatch_request(protocol_request)
+ dispatch_service_request(protocol_request)
+ end
+ end
+
+ class DelegatedContainer < AbstractContainer
+ service_dispatching_mode :delegated
+ service :protocol_soap_service, Service.new
+ end
+
+ class DirectContainer < AbstractContainer
+ service_api API
+ service_dispatching_mode :direct
+
+ attr :int
+ attr :string
+ attr :array
+ attr :values
+ attr :person
+ attr :default_args
+
+ def initialize
+ @int = 20
+ @string = "wrong string value"
+ @default_args = nil
+ end
+
+ def argument_passing
+ @int = @params['int']
+ @string = @params['string']
+ @array = @params['array']
+ true
+ end
+
+ def array_returner
+ @values = [1, 2, 3]
+ end
+
+ def nil_returner
+ nil
+ end
+
+ def struct_array_returner
+ @person = Person.new
+ @person.id = 5
+ @person.names = ["one", "two"]
+ @person.lastname = "test"
+ @person.deleted = false
+ [@person]
+ end
+
+ def exception_thrower
+ raise "Hi, I'm a SOAP error"
+ end
+
+ def default
+ @default_args = @method_params
+ nil
+ end
+ end
+end
+
+class TC_ProtocolSoap < AbstractSoapTest
+ def setup
+ @delegated_container = ProtocolSoapTest::DelegatedContainer.new
+ @direct_container = ProtocolSoapTest::DirectContainer.new
+ end
+
+ def test_argument_passing
+ in_all_containers do
+ assert(do_soap_call('ArgumentPassing', 5, "test string", [true, false]) == true)
+ assert(service.int == 5)
+ assert(service.string == "test string")
+ assert(service.array == [true, false])
+ end
+ end
+
+ def test_array_returner
+ in_all_containers do
+ assert(do_soap_call('ArrayReturner') == [1, 2, 3])
+ assert(service.values == [1, 2, 3])
+ end
+ end
+
+ def test_nil_returner
+ in_all_containers do
+ assert(do_soap_call('NilReturner') == nil)
+ end
+ end
+
+ def test_struct_array_returner
+ in_all_containers do
+ assert(do_soap_call('StructArrayReturner') == [service.person])
+ end
+ end
+
+ def test_exception_thrower
+ in_all_containers do
+ assert_raises(RuntimeError) do
+ do_soap_call('ExceptionThrower')
+ end
+ end
+ end
+
+ def test_default_api_method
+ in_all_containers do
+ assert(do_soap_call('NonExistentMethodName', 50, false).nil?)
+ assert(service.default_args == [50, false])
+ end
+ end
+
+ def test_service_name_setting
+ in_all_containers do
+ assert(ProtocolSoapTest::DelegatedContainer.soap_mapper.custom_namespace == 'urn:Test')
+ end
+ end
+
+ protected
+ def service_name
+ @container == @direct_container ? 'api' : 'protocol_soap_service'
+ end
+
+ def service
+ @container == @direct_container ? @container : @container.service_object(:protocol_soap_service)
+ end
+
+ def in_all_containers(&block)
+ [@direct_container].each do |container|
+ @container = container
+ block.call
+ end
+ end
+
+ def do_soap_call(public_method_name, *args)
+ super(public_method_name, *args) do |test_request, test_response|
+ protocol_request = @container.protocol_request(test_request)
+ @container.dispatch_request(protocol_request)
+ end
+ end
+end
diff --git a/actionservice/test/protocol_xmlrpc_test.rb b/actionservice/test/protocol_xmlrpc_test.rb
new file mode 100644
index 0000000000..a9cafc0a73
--- /dev/null
+++ b/actionservice/test/protocol_xmlrpc_test.rb
@@ -0,0 +1,157 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+require 'xmlrpc/parser'
+require 'xmlrpc/create'
+require 'xmlrpc/config'
+
+module XMLRPC
+ class XmlRpcTestHelper
+ include ParserWriterChooseMixin
+
+ def create_request(methodName, *args)
+ create().methodCall(methodName, *args)
+ end
+
+ def parse_response(response)
+ parser().parseMethodResponse(response)
+ end
+ end
+end
+
+module ProtocolXmlRpcTest
+ class Person < ActionService::Struct
+ member :firstname, String
+ member :lastname, String
+ member :active, TrueClass
+ end
+
+ class API < ActionService::API::Base
+ api_method :add, :expects => [Integer, Integer], :returns => [Integer]
+ api_method :hash_returner, :returns => [Hash]
+ api_method :array_returner, :returns => [[Integer]]
+ api_method :something_hash, :expects => [Hash]
+ api_method :struct_array_returner, :returns => [[Person]]
+
+ default_api_method :default
+ end
+
+ class Service < ActionService::Base
+ service_api API
+
+ attr :result
+ attr :hashvalue
+ attr :default_args
+
+ def initialize
+ @result = nil
+ @hashvalue = nil
+ @default_args = nil
+ end
+
+ def add(a, b)
+ @result = a + b
+ end
+
+ def something_hash(hash)
+ @hashvalue = hash
+ end
+
+ def array_returner
+ [1, 2, 3]
+ end
+
+ def hash_returner
+ {'name' => 1, 'value' => 2}
+ end
+
+ def struct_array_returner
+ person = Person.new
+ person.firstname = "John"
+ person.lastname = "Doe"
+ person.active = true
+ [person]
+ end
+
+ def default(*args)
+ @default_args = args
+ nil
+ end
+ end
+
+ $service = Service.new
+
+ class Container
+ include ActionService::Container
+ include ActionService::Protocol::Registry
+ include ActionService::Protocol::Soap
+ include ActionService::Protocol::XmlRpc
+
+ def protocol_request(request)
+ probe_request_protocol(request)
+ end
+
+ def dispatch_request(protocol_request)
+ dispatch_service_request(protocol_request)
+ end
+
+ service :xmlrpc, $service
+ service_dispatching_mode :delegated
+ end
+end
+
+class TC_ProtocolXmlRpc < Test::Unit::TestCase
+ def setup
+ @helper = XMLRPC::XmlRpcTestHelper.new
+ @container = ProtocolXmlRpcTest::Container.new
+ end
+
+ def test_xmlrpc_request_dispatching
+ retval = do_xmlrpc_call('Add', 50, 30)
+ assert(retval == [true, 80])
+ end
+
+ def test_array_returning
+ retval = do_xmlrpc_call('ArrayReturner')
+ assert(retval == [true, [1, 2, 3]])
+ end
+
+ def test_hash_returning
+ retval = do_xmlrpc_call('HashReturner')
+ assert(retval == [true, {'name' => 1, 'value' => 2}])
+ end
+
+ def test_struct_array_returning
+ retval = do_xmlrpc_call('StructArrayReturner')
+ assert(retval == [true, [{"firstname"=>"John", "lastname"=>"Doe", "active"=>true}]])
+ end
+
+ def test_hash_parameter
+ retval = do_xmlrpc_call('SomethingHash', {'name' => 1, 'value' => 2})
+ assert(retval == [true, true])
+ assert($service.hashvalue == {'name' => 1, 'value' => 2})
+ end
+
+ def test_default_api_method
+ retval = do_xmlrpc_call('SomeNonexistentMethod', 'test', [1, 2], {'name'=>'value'})
+ assert(retval == [true, true])
+ assert($service.default_args == ['test', [1, 2], {'name'=>'value'}])
+ end
+
+ def test_xmlrpc_introspection
+ retval = do_xmlrpc_call('system.listMethods', 'test', [1, 2], {'name'=>'value'})
+ assert(retval == [true, ["Add", "ArrayReturner", "HashReturner", "SomethingHash", "StructArrayReturner"]])
+ end
+
+ private
+ def do_xmlrpc_call(public_method_name, *args)
+ service_name = 'xmlrpc'
+ raw_request = @helper.create_request(public_method_name, *args)
+ test_request = ActionController::TestRequest.new
+ test_request.request_parameters['action'] = service_name
+ test_request.env['REQUEST_METHOD'] = "POST"
+ test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
+ test_request.env['RAW_POST_DATA'] = raw_request
+ protocol_request = @container.protocol_request(test_request)
+ response = @container.dispatch_request(protocol_request)
+ @helper.parse_response(response.raw_body)
+ end
+end
diff --git a/actionservice/test/router_action_controller_test.rb b/actionservice/test/router_action_controller_test.rb
new file mode 100644
index 0000000000..b541b46542
--- /dev/null
+++ b/actionservice/test/router_action_controller_test.rb
@@ -0,0 +1,139 @@
+require File.dirname(__FILE__) + '/abstract_soap'
+require 'wsdl/parser'
+
+module RouterActionControllerTest
+ class API < ActionService::API::Base
+ api_method :add, :expects => [:int, :int], :returns => [:int]
+ end
+
+ class Service < ActionService::Base
+ service_api API
+
+ attr :added
+
+ def add(a, b)
+ @added = a + b
+ end
+ end
+
+ class DelegatedController < ActionController::Base
+ service_dispatching_mode :delegated
+
+ service(:test_service) { @service ||= Service.new; @service }
+ end
+
+ class DirectAPI < ActionService::API::Base
+ api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
+ api_method :before_filtered
+ api_method :after_filtered, :returns => [:int]
+ api_method :thrower
+ end
+
+ class DirectController < ActionController::Base
+ service_api DirectAPI
+ service_dispatching_mode :direct
+
+ before_filter :alwaysfail, :only => [:before_filtered]
+ after_filter :alwaysok, :only => [:after_filtered]
+
+ attr :added
+ attr :before_filter_called
+ attr :before_filter_target_called
+ attr :after_filter_called
+ attr :after_filter_target_called
+
+ def initialize
+ @before_filter_called = false
+ @before_filter_target_called = false
+ @after_filter_called = false
+ @after_filter_target_called = false
+ end
+
+ def add
+ @added = @params['a'] + @params['b']
+ end
+
+ def before_filtered
+ @before_filter_target_called = true
+ end
+
+ def after_filtered
+ @after_filter_target_called = true
+ 5
+ end
+
+ def thrower
+ raise "Hi, I'm a SOAP exception"
+ end
+
+ protected
+ def alwaysfail
+ @before_filter_called = true
+ false
+ end
+
+ def alwaysok
+ @after_filter_called = true
+ end
+ end
+end
+
+class TC_RouterActionController < AbstractSoapTest
+ def test_direct_routing
+ @container = RouterActionControllerTest::DirectController.new
+ assert(do_soap_call('Add', 20, 50) == 70)
+ assert(@container.added == 70)
+ end
+
+ def test_direct_entrypoint
+ @container = RouterActionControllerTest::DirectController.new
+ assert(@container.respond_to?(:api))
+ end
+
+ def test_direct_filtering
+ @container = RouterActionControllerTest::DirectController.new
+ assert(@container.before_filter_called == false)
+ assert(@container.before_filter_target_called == false)
+ assert(do_soap_call('BeforeFiltered').nil?)
+ assert(@container.before_filter_called == true)
+ assert(@container.before_filter_target_called == false)
+ assert(@container.after_filter_called == false)
+ assert(@container.after_filter_target_called == false)
+ assert(do_soap_call('AfterFiltered') == 5)
+ assert(@container.after_filter_called == true)
+ assert(@container.after_filter_target_called == true)
+ end
+
+ def test_delegated_routing
+ @container = RouterActionControllerTest::DelegatedController.new
+ assert(do_soap_call('Add', 50, 80) == 130)
+ assert(service.added == 130)
+ end
+
+ def test_exception_marshaling
+ @container = RouterActionControllerTest::DirectController.new
+ result = do_soap_call('Thrower')
+ exception = result.detail
+ assert(exception.cause.is_a?(RuntimeError))
+ assert_equal("Hi, I'm a SOAP exception", exception.cause.message)
+ @container.service_exception_reporting = false
+ assert_raises(SoapTestError) do
+ do_soap_call('Thrower')
+ end
+ end
+
+ protected
+ def service_name
+ @container.is_a?(RouterActionControllerTest::DelegatedController) ? 'test_service' : 'api'
+ end
+
+ def service
+ @container.service_object(:test_service)
+ end
+
+ def do_soap_call(public_method_name, *args)
+ super(public_method_name, *args) do |test_request, test_response|
+ response = @container.process(test_request, test_response)
+ end
+ end
+end
diff --git a/actionservice/test/router_wsdl_test.rb b/actionservice/test/router_wsdl_test.rb
new file mode 100644
index 0000000000..b141d8304b
--- /dev/null
+++ b/actionservice/test/router_wsdl_test.rb
@@ -0,0 +1,100 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+require 'wsdl/parser'
+
+module RouterWsdlTest
+ class Person < ActionService::Struct
+ member :id, Integer
+ member :names, [String]
+ member :lastname, String
+ member :deleted, TrueClass
+ end
+
+ class API < ActionService::API::Base
+ api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
+ api_method :find_people, :returns => [[Person]]
+ api_method :nil_returner
+ end
+
+ class Service < ActionService::Base
+ service_api API
+
+ def add(a, b)
+ a + b
+ end
+
+ def find_people
+ []
+ end
+
+ def nil_returner
+ end
+ end
+
+ class AbstractController < ActionController::Base
+ def generate_wsdl(container, uri, soap_action_base)
+ to_wsdl(container, uri, soap_action_base)
+ end
+ end
+
+ class DirectController < AbstractController
+ service_api API
+
+ def add
+ end
+
+ def find_people
+ end
+
+ def nil_returner
+ end
+ end
+
+ class DelegatedController < AbstractController
+ service_dispatching_mode :delegated
+ service(:test_service) { Service.new }
+ end
+end
+
+class TC_RouterWsdl < Test::Unit::TestCase
+ include RouterWsdlTest
+
+ def test_wsdl_generation
+ ensure_valid_generation DelegatedController.new
+ ensure_valid_generation DirectController.new
+ end
+
+ def
+
+ def test_wsdl_action
+ ensure_valid_wsdl_action DelegatedController.new
+ ensure_valid_wsdl_action DirectController.new
+ end
+
+ protected
+ def ensure_valid_generation(controller)
+ wsdl = controller.generate_wsdl(controller, 'http://localhost:3000/test/', '/test')
+ ensure_valid_wsdl(wsdl)
+ end
+
+ def ensure_valid_wsdl(wsdl)
+ definitions = WSDL::Parser.new.parse(wsdl)
+ assert(definitions.is_a?(WSDL::Definitions))
+ definitions.bindings.each do |binding|
+ assert(binding.name.name.index(':').nil?)
+ end
+ definitions.services.each do |service|
+ service.ports.each do |port|
+ assert(port.name.name.index(':').nil?)
+ end
+ end
+ end
+
+ def ensure_valid_wsdl_action(controller)
+ test_request = ActionController::TestRequest.new({ 'action' => 'wsdl' })
+ test_request.env['REQUEST_METHOD'] = 'GET'
+ test_request.env['HTTP_HOST'] = 'localhost:3000'
+ test_response = ActionController::TestResponse.new
+ wsdl = controller.process(test_request, test_response).body
+ ensure_valid_wsdl(wsdl)
+ end
+end
diff --git a/actionservice/test/struct_test.rb b/actionservice/test/struct_test.rb
new file mode 100644
index 0000000000..b883c6d991
--- /dev/null
+++ b/actionservice/test/struct_test.rb
@@ -0,0 +1,40 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+module StructTest
+ class Struct < ActionService::Struct
+ member :id, Integer
+ member :name, String
+ member :items, [String]
+ member :deleted, :bool
+ member :emails, [:string]
+ end
+end
+
+class TC_Struct < Test::Unit::TestCase
+ def test_members
+ assert_equal(5, StructTest::Struct.members.size)
+ assert_equal(Integer, StructTest::Struct.members[:id])
+ assert_equal(String, StructTest::Struct.members[:name])
+ assert_equal([String], StructTest::Struct.members[:items])
+ assert_equal(TrueClass, StructTest::Struct.members[:deleted])
+ assert_equal([String], StructTest::Struct.members[:emails])
+ end
+
+ def test_initializer_and_lookup
+ s = StructTest::Struct.new(:id => 5,
+ :name => 'hello',
+ :items => ['one', 'two'],
+ :deleted => true,
+ :emails => ['test@test.com'])
+ assert_equal(5, s.id)
+ assert_equal('hello', s.name)
+ assert_equal(['one', 'two'], s.items)
+ assert_equal(true, s.deleted)
+ assert_equal(['test@test.com'], s.emails)
+ assert_equal(5, s['id'])
+ assert_equal('hello', s['name'])
+ assert_equal(['one', 'two'], s['items'])
+ assert_equal(true, s['deleted'])
+ assert_equal(['test@test.com'], s['emails'])
+ end
+end