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,299 @@
1
+ module ActionWebService # :nodoc:
2
+ module Dispatcher # :nodoc:
3
+ module ActionController # :nodoc:
4
+ def self.append_features(base) # :nodoc:
5
+ super
6
+ base.class_eval do
7
+ class << self
8
+ alias_method :inherited_without_action_controller, :inherited
9
+ end
10
+ alias_method :before_direct_invoke_without_action_controller, :before_direct_invoke
11
+ alias_method :after_direct_invoke_without_action_controller, :after_direct_invoke
12
+ end
13
+ base.add_web_service_api_callback do |klass, api|
14
+ if klass.web_service_dispatching_mode == :direct
15
+ klass.class_eval <<-EOS
16
+ def api
17
+ controller_dispatch_web_service_request
18
+ end
19
+ EOS
20
+ end
21
+ end
22
+ base.add_web_service_definition_callback do |klass, name, info|
23
+ if klass.web_service_dispatching_mode == :delegated
24
+ klass.class_eval <<-EOS
25
+ def #{name}
26
+ controller_dispatch_web_service_request
27
+ end
28
+ EOS
29
+ end
30
+ end
31
+ base.extend(ClassMethods)
32
+ base.send(:include, ActionWebService::Dispatcher::ActionController::Invocation)
33
+ end
34
+
35
+ module ClassMethods # :nodoc:
36
+ def inherited(child)
37
+ inherited_without_action_controller(child)
38
+ child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlGeneration)
39
+ end
40
+ end
41
+
42
+ module Invocation # :nodoc:
43
+ private
44
+ def controller_dispatch_web_service_request
45
+ request, response, elapsed, exception = dispatch_web_service_request(@request)
46
+ if response
47
+ begin
48
+ log_request(request)
49
+ log_error(exception) if exception && logger
50
+ log_response(response, elapsed)
51
+ response_options = { :type => response.content_type, :disposition => 'inline' }
52
+ send_data(response.raw_body, response_options)
53
+ rescue Exception => e
54
+ log_error(e) unless logger.nil?
55
+ render_text("Internal protocol error", "500 Internal Server Error")
56
+ end
57
+ else
58
+ logger.error("No response available") unless logger.nil?
59
+ render_text("Internal protocol error", "500 Internal Server Error")
60
+ end
61
+ end
62
+
63
+ def before_direct_invoke(request)
64
+ before_direct_invoke_without_action_controller(request)
65
+ @params ||= {}
66
+ signature = request.signature
67
+ if signature && (expects = request.signature[:expects])
68
+ (0..(@method_params.size-1)).each do |i|
69
+ if expects[i].is_a?(Hash)
70
+ @params[expects[i].keys[0].to_s] = @method_params[i]
71
+ else
72
+ @params['param%d' % i] = @method_params[i]
73
+ end
74
+ end
75
+ end
76
+ @params['action'] = request.method_name.to_s
77
+ @session ||= {}
78
+ @assigns ||= {}
79
+ return nil if before_action == false
80
+ true
81
+ end
82
+
83
+ def after_direct_invoke(request)
84
+ after_direct_invoke_without_action_controller(request)
85
+ after_action
86
+ end
87
+
88
+ def log_request(request)
89
+ unless logger.nil? || request.nil?
90
+ logger.debug("\nWeb Service Request:")
91
+ indented = request.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
92
+ logger.debug(indented)
93
+ end
94
+ end
95
+
96
+ def log_response(response, elapsed)
97
+ unless logger.nil? || response.nil?
98
+ logger.debug("\nWeb Service Response (%f):" % elapsed)
99
+ indented = response.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
100
+ logger.debug(indented)
101
+ end
102
+ end
103
+
104
+ unless method_defined?(:logger)
105
+ def logger; @logger; end
106
+ end
107
+ end
108
+
109
+ module WsdlGeneration # :nodoc:
110
+ XsdNs = 'http://www.w3.org/2001/XMLSchema'
111
+ WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
112
+ SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
113
+ SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
114
+ SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
115
+
116
+ def wsdl
117
+ case @request.method
118
+ when :get
119
+ begin
120
+ host_name = @request.env['HTTP_HOST'] || @request.env['SERVER_NAME']
121
+ uri = "http://#{host_name}/#{controller_name}/"
122
+ soap_action_base = "/#{controller_name}"
123
+ xml = to_wsdl(self, uri, soap_action_base)
124
+ send_data(xml, :type => 'text/xml', :disposition => 'inline')
125
+ rescue Exception => e
126
+ log_error e unless logger.nil?
127
+ render_text('', "500 #{e.message}")
128
+ end
129
+ when :post
130
+ render_text('', "500 POST not supported")
131
+ end
132
+ end
133
+
134
+ private
135
+ def to_wsdl(container, uri, soap_action_base)
136
+ wsdl = ""
137
+
138
+ web_service_dispatching_mode = container.web_service_dispatching_mode
139
+ mapper = container.class.soap_mapper
140
+ namespace = mapper.custom_namespace
141
+ wsdl_service_name = namespace.split(/:/)[1]
142
+
143
+ services = {}
144
+ mapper.map_container_services(container) do |name, api, api_methods|
145
+ services[name] = [api, api_methods]
146
+ end
147
+ custom_types = mapper.custom_types
148
+
149
+
150
+ xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2)
151
+ xm.instruct!
152
+
153
+ xm.definitions('name' => wsdl_service_name,
154
+ 'targetNamespace' => namespace,
155
+ 'xmlns:typens' => namespace,
156
+ 'xmlns:xsd' => XsdNs,
157
+ 'xmlns:soap' => SoapNs,
158
+ 'xmlns:soapenc' => SoapEncodingNs,
159
+ 'xmlns:wsdl' => WsdlNs,
160
+ 'xmlns' => WsdlNs) do
161
+
162
+ # Custom type XSD generation
163
+ if custom_types.size > 0
164
+ xm.types do
165
+ xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
166
+ custom_types.each do |klass, mapping|
167
+ case
168
+ when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping)
169
+ xm.xsd(:complexType, 'name' => mapping.type_name) do
170
+ xm.xsd(:complexContent) do
171
+ xm.xsd(:restriction, 'base' => 'soapenc:Array') do
172
+ xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
173
+ 'wsdl:arrayType' => mapping.element_mapping.qualified_type_name + '[]')
174
+ end
175
+ end
176
+ end
177
+ when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping)
178
+ xm.xsd(:complexType, 'name' => mapping.type_name) do
179
+ xm.xsd(:all) do
180
+ mapping.each_attribute do |name, type_name|
181
+ xm.xsd(:element, 'name' => name, 'type' => type_name)
182
+ end
183
+ end
184
+ end
185
+ else
186
+ raise(WsdlError, "unsupported mapping type #{mapping.class.name}")
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ services.each do |service_name, service_values|
194
+ service_api, api_methods = service_values
195
+ # Parameter list message definitions
196
+ api_methods.each do |method_name, method_signature|
197
+ gen = lambda do |msg_name, direction|
198
+ xm.message('name' => msg_name) do
199
+ sym = nil
200
+ if direction == :out
201
+ if method_signature[:returns]
202
+ xm.part('name' => 'return', 'type' => method_signature[:returns][0].qualified_type_name)
203
+ end
204
+ else
205
+ mapping_list = method_signature[:expects]
206
+ i = 1
207
+ mapping_list.each do |mapping|
208
+ if mapping.is_a?(Hash)
209
+ param_name = mapping.keys.shift
210
+ mapping = mapping.values.shift
211
+ else
212
+ param_name = "param#{i}"
213
+ end
214
+ xm.part('name' => param_name, 'type' => mapping.qualified_type_name)
215
+ i += 1
216
+ end if mapping_list
217
+ end
218
+ end
219
+ end
220
+ public_name = service_api.public_api_method_name(method_name)
221
+ gen.call(public_name, :in)
222
+ gen.call("#{public_name}Response", :out)
223
+ end
224
+
225
+ # Declare the port
226
+ port_name = port_name_for(wsdl_service_name, service_name)
227
+ xm.portType('name' => port_name) do
228
+ api_methods.each do |method_name, method_signature|
229
+ public_name = service_api.public_api_method_name(method_name)
230
+ xm.operation('name' => public_name) do
231
+ xm.input('message' => "typens:#{public_name}")
232
+ xm.output('message' => "typens:#{public_name}Response")
233
+ end
234
+ end
235
+ end
236
+
237
+ # Bind the port to SOAP
238
+ binding_name = binding_name_for(wsdl_service_name, service_name)
239
+ xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
240
+ xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
241
+ api_methods.each do |method_name, method_signature|
242
+ public_name = service_api.public_api_method_name(method_name)
243
+ xm.operation('name' => public_name) do
244
+ case web_service_dispatching_mode
245
+ when :direct
246
+ soap_action = soap_action_base + "/api/" + public_name
247
+ when :delegated
248
+ soap_action = soap_action_base \
249
+ + "/" + service_name.to_s \
250
+ + "/" + public_name
251
+ end
252
+ xm.soap(:operation, 'soapAction' => soap_action)
253
+ xm.input do
254
+ xm.soap(:body,
255
+ 'use' => 'encoded',
256
+ 'namespace' => namespace,
257
+ 'encodingStyle' => SoapEncodingNs)
258
+ end
259
+ xm.output do
260
+ xm.soap(:body,
261
+ 'use' => 'encoded',
262
+ 'namespace' => namespace,
263
+ 'encodingStyle' => SoapEncodingNs)
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ # Define the service
271
+ xm.service('name' => "#{wsdl_service_name}Service") do
272
+ services.each do |service_name, service_values|
273
+ port_name = port_name_for(wsdl_service_name, service_name)
274
+ binding_name = binding_name_for(wsdl_service_name, service_name)
275
+ case web_service_dispatching_mode
276
+ when :direct
277
+ binding_target = 'api'
278
+ when :delegated
279
+ binding_target = service_name.to_s
280
+ end
281
+ xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
282
+ xm.soap(:address, 'location' => "#{uri}#{binding_target}")
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ def port_name_for(wsdl_service_name, service_name)
290
+ "#{wsdl_service_name}#{service_name.to_s.camelize}Port"
291
+ end
292
+
293
+ def binding_name_for(wsdl_service_name, service_name)
294
+ "#{wsdl_service_name}#{service_name.to_s.camelize}Binding"
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,205 @@
1
+ module ActionWebService # :nodoc:
2
+ module Invocation # :nodoc:
3
+ class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
4
+ end
5
+
6
+ def self.append_features(base) # :nodoc:
7
+ super
8
+ base.extend(ClassMethods)
9
+ base.send(:include, ActionWebService::Invocation::InstanceMethods)
10
+ end
11
+
12
+ # Invocation interceptors provide a means to execute custom code before
13
+ # and after method invocations on ActionWebService::Base objects.
14
+ #
15
+ # When running in _Direct_ dispatching mode, ActionController filters
16
+ # should be used for this functionality instead.
17
+ #
18
+ # The semantics of invocation interceptors are the same as ActionController
19
+ # filters, and accept the same parameters and options.
20
+ #
21
+ # A _before_ interceptor can also cancel execution by returning +false+,
22
+ # or returning a <tt>[false, "cancel reason"]</tt> array if it wishes to supply
23
+ # a reason for canceling the request.
24
+ #
25
+ # === Example
26
+ #
27
+ # class CustomService < ActionWebService::Base
28
+ # before_invocation :intercept_add, :only => [:add]
29
+ #
30
+ # def add(a, b)
31
+ # a + b
32
+ # end
33
+ #
34
+ # private
35
+ # def intercept_add
36
+ # return [false, "permission denied"] # cancel it
37
+ # end
38
+ # end
39
+ #
40
+ # Options:
41
+ # [<tt>:except</tt>] A list of methods for which the interceptor will NOT be called
42
+ # [<tt>:only</tt>] A list of methods for which the interceptor WILL be called
43
+ module ClassMethods
44
+ # Appends the given +interceptors+ to be called
45
+ # _before_ method invocation.
46
+ def append_before_invocation(*interceptors, &block)
47
+ conditions = extract_conditions!(interceptors)
48
+ interceptors << block if block_given?
49
+ add_interception_conditions(interceptors, conditions)
50
+ append_interceptors_to_chain("before", interceptors)
51
+ end
52
+
53
+ # Prepends the given +interceptors+ to be called
54
+ # _before_ method invocation.
55
+ def prepend_before_invocation(*interceptors, &block)
56
+ conditions = extract_conditions!(interceptors)
57
+ interceptors << block if block_given?
58
+ add_interception_conditions(interceptors, conditions)
59
+ prepend_interceptors_to_chain("before", interceptors)
60
+ end
61
+
62
+ alias :before_invocation :append_before_invocation
63
+
64
+ # Appends the given +interceptors+ to be called
65
+ # _after_ method invocation.
66
+ def append_after_invocation(*interceptors, &block)
67
+ conditions = extract_conditions!(interceptors)
68
+ interceptors << block if block_given?
69
+ add_interception_conditions(interceptors, conditions)
70
+ append_interceptors_to_chain("after", interceptors)
71
+ end
72
+
73
+ # Prepends the given +interceptors+ to be called
74
+ # _after_ method invocation.
75
+ def prepend_after_invocation(*interceptors, &block)
76
+ conditions = extract_conditions!(interceptors)
77
+ interceptors << block if block_given?
78
+ add_interception_conditions(interceptors, conditions)
79
+ prepend_interceptors_to_chain("after", interceptors)
80
+ end
81
+
82
+ alias :after_invocation :append_after_invocation
83
+
84
+ def before_invocation_interceptors # :nodoc:
85
+ read_inheritable_attribute("before_invocation_interceptors")
86
+ end
87
+
88
+ def after_invocation_interceptors # :nodoc:
89
+ read_inheritable_attribute("after_invocation_interceptors")
90
+ end
91
+
92
+ def included_intercepted_methods # :nodoc:
93
+ read_inheritable_attribute("included_intercepted_methods") || {}
94
+ end
95
+
96
+ def excluded_intercepted_methods # :nodoc:
97
+ read_inheritable_attribute("excluded_intercepted_methods") || {}
98
+ end
99
+
100
+ private
101
+ def append_interceptors_to_chain(condition, interceptors)
102
+ write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
103
+ end
104
+
105
+ def prepend_interceptors_to_chain(condition, interceptors)
106
+ interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors")
107
+ write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors)
108
+ end
109
+
110
+ def extract_conditions!(interceptors)
111
+ return nil unless interceptors.last.is_a? Hash
112
+ interceptors.pop
113
+ end
114
+
115
+ def add_interception_conditions(interceptors, conditions)
116
+ return unless conditions
117
+ included, excluded = conditions[:only], conditions[:except]
118
+ write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included
119
+ write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded
120
+ end
121
+
122
+ def condition_hash(interceptors, *methods)
123
+ interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
124
+ end
125
+ end
126
+
127
+ module InstanceMethods # :nodoc:
128
+ def self.append_features(base)
129
+ super
130
+ base.class_eval do
131
+ alias_method :perform_invocation_without_interception, :perform_invocation
132
+ alias_method :perform_invocation, :perform_invocation_with_interception
133
+ end
134
+ end
135
+
136
+ def perform_invocation_with_interception(method_name, params, &block)
137
+ return if before_invocation(method_name, params, &block) == false
138
+ return_value = perform_invocation_without_interception(method_name, params)
139
+ after_invocation(method_name, params, return_value)
140
+ return_value
141
+ end
142
+
143
+ def perform_invocation(method_name, params)
144
+ send(method_name, *params)
145
+ end
146
+
147
+ def before_invocation(name, args, &block)
148
+ call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
149
+ end
150
+
151
+ def after_invocation(name, args, result)
152
+ call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
153
+ end
154
+
155
+ private
156
+
157
+ def call_interceptors(interceptors, interceptor_args, &block)
158
+ if interceptors and not interceptors.empty?
159
+ interceptors.each do |interceptor|
160
+ next if method_exempted?(interceptor, interceptor_args[0].to_s)
161
+ result = case
162
+ when interceptor.is_a?(Symbol)
163
+ self.send(interceptor, *interceptor_args)
164
+ when interceptor_block?(interceptor)
165
+ interceptor.call(self, *interceptor_args)
166
+ when interceptor_class?(interceptor)
167
+ interceptor.intercept(self, *interceptor_args)
168
+ else
169
+ raise(
170
+ InvocationError,
171
+ "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
172
+ )
173
+ end
174
+ reason = nil
175
+ if result.is_a?(Array)
176
+ reason = result[1] if result[1]
177
+ result = result[0]
178
+ end
179
+ if result == false
180
+ block.call(reason) if block && reason
181
+ return false
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ def interceptor_block?(interceptor)
188
+ interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
189
+ end
190
+
191
+ def interceptor_class?(interceptor)
192
+ interceptor.respond_to?("intercept")
193
+ end
194
+
195
+ def method_exempted?(interceptor, method_name)
196
+ case
197
+ when self.class.included_intercepted_methods[interceptor]
198
+ !self.class.included_intercepted_methods[interceptor].include?(method_name)
199
+ when self.class.excluded_intercepted_methods[interceptor]
200
+ self.class.excluded_intercepted_methods[interceptor].include?(method_name)
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end