actionwebservice 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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