actionwebservice 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/ChangeLog +47 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +238 -0
  4. data/Rakefile +144 -0
  5. data/TODO +13 -0
  6. data/examples/googlesearch/README +143 -0
  7. data/examples/googlesearch/autoloading/google_search_api.rb +50 -0
  8. data/examples/googlesearch/autoloading/google_search_controller.rb +57 -0
  9. data/examples/googlesearch/delegated/google_search_service.rb +108 -0
  10. data/examples/googlesearch/delegated/search_controller.rb +7 -0
  11. data/examples/googlesearch/direct/google_search_api.rb +50 -0
  12. data/examples/googlesearch/direct/search_controller.rb +58 -0
  13. data/examples/metaWeblog/README +16 -0
  14. data/examples/metaWeblog/blog_controller.rb +127 -0
  15. data/lib/action_web_service.rb +60 -0
  16. data/lib/action_web_service/api.rb +2 -0
  17. data/lib/action_web_service/api/abstract.rb +192 -0
  18. data/lib/action_web_service/api/action_controller.rb +92 -0
  19. data/lib/action_web_service/base.rb +41 -0
  20. data/lib/action_web_service/client.rb +3 -0
  21. data/lib/action_web_service/client/base.rb +39 -0
  22. data/lib/action_web_service/client/soap_client.rb +88 -0
  23. data/lib/action_web_service/client/xmlrpc_client.rb +77 -0
  24. data/lib/action_web_service/container.rb +85 -0
  25. data/lib/action_web_service/dispatcher.rb +2 -0
  26. data/lib/action_web_service/dispatcher/abstract.rb +150 -0
  27. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +299 -0
  28. data/lib/action_web_service/invocation.rb +205 -0
  29. data/lib/action_web_service/protocol.rb +4 -0
  30. data/lib/action_web_service/protocol/abstract.rb +128 -0
  31. data/lib/action_web_service/protocol/registry.rb +55 -0
  32. data/lib/action_web_service/protocol/soap_protocol.rb +484 -0
  33. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +168 -0
  34. data/lib/action_web_service/struct.rb +55 -0
  35. data/lib/action_web_service/support/class_inheritable_options.rb +26 -0
  36. data/lib/action_web_service/support/signature.rb +100 -0
  37. data/setup.rb +1360 -0
  38. data/test/abstract_client.rb +131 -0
  39. data/test/abstract_soap.rb +58 -0
  40. data/test/abstract_unit.rb +9 -0
  41. data/test/api_test.rb +52 -0
  42. data/test/base_test.rb +42 -0
  43. data/test/client_soap_test.rb +93 -0
  44. data/test/client_xmlrpc_test.rb +92 -0
  45. data/test/container_test.rb +53 -0
  46. data/test/dispatcher_action_controller_test.rb +186 -0
  47. data/test/gencov +3 -0
  48. data/test/invocation_test.rb +149 -0
  49. data/test/protocol_registry_test.rb +53 -0
  50. data/test/protocol_soap_test.rb +252 -0
  51. data/test/protocol_xmlrpc_test.rb +147 -0
  52. data/test/run +5 -0
  53. data/test/struct_test.rb +40 -0
  54. metadata +131 -0
@@ -0,0 +1,16 @@
1
+ = metaWeblog example
2
+
3
+
4
+ This example shows how one might begin to go about adding metaWeblog
5
+ (http://www.xmlrpc.com/metaWeblogApi) API support to a Rails-based
6
+ blogging application.
7
+
8
+
9
+ = Running
10
+
11
+ 1. Copy blog_controller.rb to "app/controllers" in a Rails project.
12
+
13
+
14
+ 2. Fire up a desktop blogging application (such as BloGTK on Linux),
15
+ point it at http://localhost:3000/blog/api, and try creating or
16
+ editing blog posts.
@@ -0,0 +1,127 @@
1
+ # point your client at http://project_url/blog/api to test
2
+ # this
3
+
4
+ # structures as defined by the metaWeblog/blogger
5
+ # specifications.
6
+ module Blog
7
+ class Enclosure < ActionWebService::Struct
8
+ member :url, :string
9
+ member :length, :int
10
+ member :type, :string
11
+ end
12
+
13
+ class Source < ActionWebService::Struct
14
+ member :url, :string
15
+ member :name, :string
16
+ end
17
+
18
+ class Post < ActionWebService::Struct
19
+ member :title, :string
20
+ member :link, :string
21
+ member :description, :string
22
+ member :author, :string
23
+ member :category, :string
24
+ member :comments, :string
25
+ member :enclosure, Enclosure
26
+ member :guid, :string
27
+ member :pubDate, :string
28
+ member :source, Source
29
+ end
30
+
31
+ class Blog < ActionWebService::Struct
32
+ member :url, :string
33
+ member :blogid, :string
34
+ member :blogName, :string
35
+ end
36
+ end
37
+
38
+ # skeleton metaWeblog API
39
+ class MetaWeblogAPI < ActionWebService::API::Base
40
+ inflect_names false
41
+
42
+ api_method :newPost, :returns => [:string], :expects => [
43
+ {:blogid=>:string},
44
+ {:username=>:string},
45
+ {:password=>:string},
46
+ {:struct=>Blog::Post},
47
+ {:publish=>:bool},
48
+ ]
49
+
50
+ api_method :editPost, :returns => [:bool], :expects => [
51
+ {:postid=>:string},
52
+ {:username=>:string},
53
+ {:password=>:string},
54
+ {:struct=>Blog::Post},
55
+ {:publish=>:bool},
56
+ ]
57
+
58
+ api_method :getPost, :returns => [Blog::Post], :expects => [
59
+ {:postid=>:string},
60
+ {:username=>:string},
61
+ {:password=>:string},
62
+ ]
63
+
64
+ api_method :getUsersBlogs, :returns => [[Blog::Blog]], :expects => [
65
+ {:appkey=>:string},
66
+ {:username=>:string},
67
+ {:password=>:string},
68
+ ]
69
+
70
+ api_method :getRecentPosts, :returns => [[Blog::Post]], :expects => [
71
+ {:blogid=>:string},
72
+ {:username=>:string},
73
+ {:password=>:string},
74
+ {:numberOfPosts=>:int},
75
+ ]
76
+ end
77
+
78
+ class BlogController < ApplicationController
79
+ web_service_api MetaWeblogAPI
80
+
81
+ def initialize
82
+ @postid = 0
83
+ end
84
+
85
+ def newPost
86
+ $stderr.puts 'Creating post: username=%s password=%s struct=%s' % [
87
+ @params['username'],
88
+ @params['password'],
89
+ @params['struct'].inspect
90
+ ]
91
+ (@postid += 1).to_s
92
+ end
93
+
94
+ def editPost
95
+ $stderr.puts 'Editing post: username=%s password=%s struct=%s' % [
96
+ @params['username'],
97
+ @params['password'],
98
+ @params['struct'].inspect
99
+ ]
100
+ true
101
+ end
102
+
103
+ def getUsersBlogs
104
+ $stderr.puts "Returning user %s's blogs" % @params['username']
105
+ blog = Blog::Blog.new(
106
+ :url =>'http://blog.xeraph.org',
107
+ :blogid => 'sttm',
108
+ :blogName => 'slave to the machine'
109
+ )
110
+ [blog]
111
+ end
112
+
113
+ def getRecentPosts
114
+ $stderr.puts "Returning recent posts (%d requested)" % @params['numberOfPosts']
115
+ post1 = Blog::Post.new(
116
+ :title => 'first post!',
117
+ :link => 'http://blog.xeraph.org/testOne.html',
118
+ :description => 'this is the first post'
119
+ )
120
+ post2 = Blog::Post.new(
121
+ :title => 'second post!',
122
+ :link => 'http://blog.xeraph.org/testTwo.html',
123
+ :description => 'this is the second post'
124
+ )
125
+ [post1, post2]
126
+ end
127
+ end
@@ -0,0 +1,60 @@
1
+ #--
2
+ # Copyright (C) 2005 Leon Breedt
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ begin
25
+ require 'active_support'
26
+ require 'action_controller'
27
+ require 'active_record'
28
+ rescue LoadError
29
+ require 'rubygems'
30
+ require_gem 'activesupport', '>= 0.9.0'
31
+ require_gem 'actionpack', '>= 1.4.0'
32
+ require_gem 'activerecord', '>= 1.6.0'
33
+ end
34
+
35
+ $:.unshift(File.dirname(__FILE__))
36
+
37
+ require 'action_web_service/base'
38
+ require 'action_web_service/client'
39
+ require 'action_web_service/invocation'
40
+ require 'action_web_service/api'
41
+ require 'action_web_service/struct'
42
+ require 'action_web_service/container'
43
+ require 'action_web_service/protocol'
44
+ require 'action_web_service/dispatcher'
45
+
46
+ ActionWebService::Base.class_eval do
47
+ include ActionWebService::API
48
+ include ActionWebService::Invocation
49
+ end
50
+
51
+ ActionController::Base.class_eval do
52
+ include ActionWebService::Container
53
+ include ActionWebService::Protocol::Registry
54
+ include ActionWebService::Protocol::Soap
55
+ include ActionWebService::Protocol::XmlRpc
56
+ include ActionWebService::API
57
+ include ActionWebService::API::ActionController
58
+ include ActionWebService::Dispatcher
59
+ include ActionWebService::Dispatcher::ActionController
60
+ end
@@ -0,0 +1,2 @@
1
+ require 'action_web_service/api/abstract'
2
+ require 'action_web_service/api/action_controller'
@@ -0,0 +1,192 @@
1
+ module ActionWebService # :nodoc:
2
+ module API # :nodoc:
3
+ class APIError < ActionWebService::ActionWebServiceError # :nodoc:
4
+ end
5
+
6
+ def self.append_features(base) # :nodoc:
7
+ super
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ # Attaches ActionWebService API +definition+ to the calling class.
13
+ #
14
+ # Action Controllers can have a default associated API, removing the need
15
+ # to call this method if you follow the Action Web Service naming conventions.
16
+ #
17
+ # A controller with a class name of GoogleSearchController will
18
+ # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
19
+ # API definition class to be named <tt>GoogleSearchAPI</tt> or
20
+ # <tt>GoogleSearchApi</tt>.
21
+ #
22
+ # ==== Service class example
23
+ #
24
+ # class MyService < ActionWebService::Base
25
+ # web_service_api MyAPI
26
+ # end
27
+ #
28
+ # class MyAPI < ActionWebService::API::Base
29
+ # ...
30
+ # end
31
+ #
32
+ # ==== Controller class example
33
+ #
34
+ # class MyController < ActionController::Base
35
+ # web_service_api MyAPI
36
+ # end
37
+ #
38
+ # class MyAPI < ActionWebService::API::Base
39
+ # ...
40
+ # end
41
+ def web_service_api(definition=nil)
42
+ if definition.nil?
43
+ read_inheritable_attribute("web_service_api")
44
+ else
45
+ if definition.is_a?(Symbol)
46
+ raise(APIError, "symbols can only be used for #web_service_api inside of a controller")
47
+ end
48
+ unless definition.respond_to?(:ancestors) && definition.ancestors.include?(Base)
49
+ raise(APIError, "#{definition.to_s} is not a valid API definition")
50
+ end
51
+ write_inheritable_attribute("web_service_api", definition)
52
+ call_web_service_api_callbacks(self, definition)
53
+ end
54
+ end
55
+
56
+ def add_web_service_api_callback(&block) # :nodoc:
57
+ write_inheritable_array("web_service_api_callbacks", [block])
58
+ end
59
+
60
+ private
61
+ def call_web_service_api_callbacks(container_class, definition)
62
+ (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
63
+ block.call(container_class, definition)
64
+ end
65
+ end
66
+ end
67
+
68
+ # A web service API class specifies the methods that will be available for
69
+ # invocation for an API. It also contains metadata such as the method type
70
+ # signature hints.
71
+ #
72
+ # It is not intended to be instantiated.
73
+ #
74
+ # It is attached to web service implementation classes like
75
+ # ActionWebService::Base and ActionController::Base derivatives by using
76
+ # ClassMethods#web_service_api.
77
+ class Base
78
+ # Whether to transform the public API method names into camel-cased names
79
+ class_inheritable_option :inflect_names, true
80
+
81
+ # If present, the name of a method to call when the remote caller
82
+ # tried to call a nonexistent method. Semantically equivalent to
83
+ # +method_missing+.
84
+ class_inheritable_option :default_api_method
85
+
86
+ # Disallow instantiation
87
+ private_class_method :new, :allocate
88
+
89
+ class << self
90
+ include ActionWebService::Signature
91
+
92
+ # API methods have a +name+, which must be the Ruby method name to use when
93
+ # performing the invocation on the web service object.
94
+ #
95
+ # The signatures for the method input parameters and return value can
96
+ # by specified in +options+.
97
+ #
98
+ # A signature is an array of one or more parameter specifiers.
99
+ # A parameter specifier can be one of the following:
100
+ #
101
+ # * A symbol or string of representing one of the Action Web Service base types.
102
+ # See ActionWebService::Signature for a canonical list of the base types.
103
+ # * The Class object of the parameter type
104
+ # * A single-element Array containing one of the two preceding items. This
105
+ # will cause Action Web Service to treat the parameter at that position
106
+ # as an array containing only values of the given type.
107
+ # * A Hash containing as key the name of the parameter, and as value
108
+ # one of the three preceding items
109
+ #
110
+ # If no method input parameter or method return value signatures are given,
111
+ # the method is assumed to take no parameters and/or return no values of
112
+ # interest, and any values that are received by the server will be
113
+ # discarded and ignored.
114
+ #
115
+ # Valid options:
116
+ # [<tt>:expects</tt>] Signature for the method input parameters
117
+ # [<tt>:returns</tt>] Signature for the method return value
118
+ # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
119
+ def api_method(name, options={})
120
+ validate_options([:expects, :returns, :expects_and_returns], options.keys)
121
+ if options[:expects_and_returns]
122
+ expects = options[:expects_and_returns]
123
+ returns = options[:expects_and_returns]
124
+ else
125
+ expects = options[:expects]
126
+ returns = options[:returns]
127
+ end
128
+ expects = canonical_signature(expects) if expects
129
+ returns = canonical_signature(returns) if returns
130
+ if expects
131
+ expects.each do |param|
132
+ klass = signature_parameter_class(param)
133
+ klass = klass[0] if klass.is_a?(Array)
134
+ if klass.ancestors.include?(ActiveRecord::Base)
135
+ raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
136
+ end
137
+ end
138
+ end
139
+ name = name.to_sym
140
+ public_name = public_api_method_name(name)
141
+ info = { :expects => expects, :returns => returns }
142
+ write_inheritable_hash("api_methods", name => info)
143
+ write_inheritable_hash("api_public_method_names", public_name => name)
144
+ end
145
+
146
+ # Whether the given method name is a service method on this API
147
+ def has_api_method?(name)
148
+ api_methods.has_key?(name)
149
+ end
150
+
151
+ # Whether the given public method name has a corresponding service method
152
+ # on this API
153
+ def has_public_api_method?(public_name)
154
+ api_public_method_names.has_key?(public_name)
155
+ end
156
+
157
+ # The corresponding public method name for the given service method name
158
+ def public_api_method_name(name)
159
+ if inflect_names
160
+ name.to_s.camelize
161
+ else
162
+ name.to_s
163
+ end
164
+ end
165
+
166
+ # The corresponding service method name for the given public method name
167
+ def api_method_name(public_name)
168
+ api_public_method_names[public_name]
169
+ end
170
+
171
+ # A Hash containing all service methods on this API, and their
172
+ # associated metadata.
173
+ def api_methods
174
+ read_inheritable_attribute("api_methods") || {}
175
+ end
176
+
177
+ private
178
+ def api_public_method_names
179
+ read_inheritable_attribute("api_public_method_names") || {}
180
+ end
181
+
182
+ def validate_options(valid_option_keys, supplied_option_keys)
183
+ unknown_option_keys = supplied_option_keys - valid_option_keys
184
+ unless unknown_option_keys.empty?
185
+ raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
186
+ end
187
+ end
188
+
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,92 @@
1
+ module ActionWebService # :nodoc:
2
+ module API # :nodoc:
3
+ module ActionController # :nodoc:
4
+ def self.append_features(base) # :nodoc:
5
+ base.class_eval do
6
+ class << self
7
+ alias_method :inherited_without_api, :inherited
8
+ alias_method :web_service_api_without_require, :web_service_api
9
+ end
10
+ end
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+ # Creates a client for accessing remote web services, using the
16
+ # given +protocol+ to communicate with the +endpoint_uri+.
17
+ #
18
+ # ==== Example
19
+ #
20
+ # class MyController < ActionController::Base
21
+ # web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger'
22
+ # end
23
+ #
24
+ # In this example, a protected method named <tt>blogger</tt> will
25
+ # now exist on the controller, and calling it will return the
26
+ # XML-RPC client object for working with that remote service.
27
+ #
28
+ # +options+ is the set of protocol client specific options,
29
+ # see a protocol client class for details.
30
+ #
31
+ # If your API definition does not exist on the load path with the
32
+ # correct rules for it to be found using +name+, you can pass through
33
+ # the API definition class in +options+, using a key of <tt>:api</tt>
34
+ def web_client_api(name, protocol, endpoint_uri, options={})
35
+ unless method_defined?(name)
36
+ api_klass = options.delete(:api) || require_web_service_api(name)
37
+ class_eval do
38
+ define_method(name) do
39
+ probe_protocol_client(api_klass, protocol, endpoint_uri, options)
40
+ end
41
+ protected name
42
+ end
43
+ end
44
+ end
45
+
46
+ def web_service_api(definition=nil) # :nodoc:
47
+ return web_service_api_without_require if definition.nil?
48
+ case definition
49
+ when String, Symbol
50
+ klass = require_web_service_api(definition)
51
+ else
52
+ klass = definition
53
+ end
54
+ web_service_api_without_require(klass)
55
+ end
56
+
57
+ def require_web_service_api(name) # :nodoc:
58
+ case name
59
+ when String, Symbol
60
+ file_name = name.to_s.underscore + "_api"
61
+ class_name = file_name.camelize
62
+ class_names = [class_name, class_name.sub(/Api$/, 'API')]
63
+ begin
64
+ require_dependency(file_name)
65
+ rescue LoadError => load_error
66
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
67
+ raise LoadError, requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}"
68
+ end
69
+ klass = nil
70
+ class_names.each do |name|
71
+ klass = name.constantize rescue nil
72
+ break unless klass.nil?
73
+ end
74
+ unless klass
75
+ raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found")
76
+ end
77
+ klass
78
+ else
79
+ raise(ArgumentError, "expected String or Symbol argument")
80
+ end
81
+ end
82
+
83
+ private
84
+ def inherited(child)
85
+ inherited_without_api(child)
86
+ child.web_service_api(child.controller_path)
87
+ rescue Exception => e
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end