fdv-actionwebservice 2.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/CHANGELOG +320 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +381 -0
  4. data/Rakefile +180 -0
  5. data/TODO +32 -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 +17 -0
  14. data/examples/metaWeblog/apis/blogger_api.rb +60 -0
  15. data/examples/metaWeblog/apis/blogger_service.rb +34 -0
  16. data/examples/metaWeblog/apis/meta_weblog_api.rb +67 -0
  17. data/examples/metaWeblog/apis/meta_weblog_service.rb +48 -0
  18. data/examples/metaWeblog/controllers/xmlrpc_controller.rb +16 -0
  19. data/generators/web_service/USAGE +28 -0
  20. data/generators/web_service/templates/api_definition.rb +5 -0
  21. data/generators/web_service/templates/controller.rb +8 -0
  22. data/generators/web_service/templates/functional_test.rb +19 -0
  23. data/generators/web_service/web_service_generator.rb +29 -0
  24. data/lib/action_web_service.rb +66 -0
  25. data/lib/action_web_service/api.rb +297 -0
  26. data/lib/action_web_service/base.rb +38 -0
  27. data/lib/action_web_service/casting.rb +149 -0
  28. data/lib/action_web_service/client.rb +3 -0
  29. data/lib/action_web_service/client/base.rb +28 -0
  30. data/lib/action_web_service/client/soap_client.rb +113 -0
  31. data/lib/action_web_service/client/xmlrpc_client.rb +58 -0
  32. data/lib/action_web_service/container.rb +3 -0
  33. data/lib/action_web_service/container/action_controller_container.rb +93 -0
  34. data/lib/action_web_service/container/delegated_container.rb +86 -0
  35. data/lib/action_web_service/container/direct_container.rb +69 -0
  36. data/lib/action_web_service/dispatcher.rb +2 -0
  37. data/lib/action_web_service/dispatcher/abstract.rb +207 -0
  38. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +379 -0
  39. data/lib/action_web_service/invocation.rb +202 -0
  40. data/lib/action_web_service/protocol.rb +4 -0
  41. data/lib/action_web_service/protocol/abstract.rb +112 -0
  42. data/lib/action_web_service/protocol/discovery.rb +37 -0
  43. data/lib/action_web_service/protocol/soap_protocol.rb +176 -0
  44. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +242 -0
  45. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +122 -0
  46. data/lib/action_web_service/scaffolding.rb +281 -0
  47. data/lib/action_web_service/struct.rb +64 -0
  48. data/lib/action_web_service/support/class_inheritable_options.rb +26 -0
  49. data/lib/action_web_service/support/signature_types.rb +226 -0
  50. data/lib/action_web_service/templates/scaffolds/layout.html.erb +65 -0
  51. data/lib/action_web_service/templates/scaffolds/methods.html.erb +6 -0
  52. data/lib/action_web_service/templates/scaffolds/parameters.html.erb +29 -0
  53. data/lib/action_web_service/templates/scaffolds/result.html.erb +30 -0
  54. data/lib/action_web_service/test_invoke.rb +110 -0
  55. data/lib/action_web_service/version.rb +9 -0
  56. data/lib/actionwebservice.rb +1 -0
  57. data/setup.rb +1379 -0
  58. data/test/abstract_client.rb +183 -0
  59. data/test/abstract_dispatcher.rb +548 -0
  60. data/test/abstract_unit.rb +43 -0
  61. data/test/api_test.rb +102 -0
  62. data/test/apis/auto_load_api.rb +3 -0
  63. data/test/apis/broken_auto_load_api.rb +2 -0
  64. data/test/base_test.rb +42 -0
  65. data/test/casting_test.rb +95 -0
  66. data/test/client_soap_test.rb +155 -0
  67. data/test/client_xmlrpc_test.rb +153 -0
  68. data/test/container_test.rb +73 -0
  69. data/test/dispatcher_action_controller_soap_test.rb +139 -0
  70. data/test/dispatcher_action_controller_xmlrpc_test.rb +59 -0
  71. data/test/fixtures/db_definitions/mysql.sql +8 -0
  72. data/test/fixtures/users.yml +12 -0
  73. data/test/gencov +3 -0
  74. data/test/invocation_test.rb +185 -0
  75. data/test/run +6 -0
  76. data/test/scaffolded_controller_test.rb +146 -0
  77. data/test/struct_test.rb +52 -0
  78. data/test/test_invoke_test.rb +112 -0
  79. metadata +166 -0
@@ -0,0 +1,86 @@
1
+ module ActionWebService # :nodoc:
2
+ module Container # :nodoc:
3
+ module Delegated # :nodoc:
4
+ class ContainerError < ActionWebServiceError # :nodoc:
5
+ end
6
+
7
+ def self.included(base) # :nodoc:
8
+ base.extend(ClassMethods)
9
+ base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ # Declares a web service that will provide access to the API of the given
14
+ # +object+. +object+ must be an ActionWebService::Base derivative.
15
+ #
16
+ # Web service object creation can either be _immediate_, where the object
17
+ # instance is given at class definition time, or _deferred_, where
18
+ # object instantiation is delayed until request time.
19
+ #
20
+ # ==== Immediate web service object example
21
+ #
22
+ # class ApiController < ApplicationController
23
+ # web_service_dispatching_mode :delegated
24
+ #
25
+ # web_service :person, PersonService.new
26
+ # end
27
+ #
28
+ # For deferred instantiation, a block should be given instead of an
29
+ # object instance. This block will be executed in controller instance
30
+ # context, so it can rely on controller instance variables being present.
31
+ #
32
+ # ==== Deferred web service object example
33
+ #
34
+ # class ApiController < ApplicationController
35
+ # web_service_dispatching_mode :delegated
36
+ #
37
+ # web_service(:person) { PersonService.new(request.env) }
38
+ # end
39
+ def web_service(name, object=nil, &block)
40
+ if (object && block_given?) || (object.nil? && block.nil?)
41
+ raise(ContainerError, "either service, or a block must be given")
42
+ end
43
+ name = name.to_sym
44
+ if block_given?
45
+ info = { name => { :block => block } }
46
+ else
47
+ info = { name => { :object => object } }
48
+ end
49
+ write_inheritable_hash("web_services", info)
50
+ call_web_service_definition_callbacks(self, name, info)
51
+ end
52
+
53
+ # Whether this service contains a service with the given +name+
54
+ def has_web_service?(name)
55
+ web_services.has_key?(name.to_sym)
56
+ end
57
+
58
+ def web_services # :nodoc:
59
+ read_inheritable_attribute("web_services") || {}
60
+ end
61
+
62
+ def add_web_service_definition_callback(&block) # :nodoc:
63
+ write_inheritable_array("web_service_definition_callbacks", [block])
64
+ end
65
+
66
+ private
67
+ def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
68
+ (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
69
+ block.call(container_class, web_service_name, service_info)
70
+ end
71
+ end
72
+ end
73
+
74
+ module InstanceMethods # :nodoc:
75
+ def web_service_object(web_service_name)
76
+ info = self.class.web_services[web_service_name.to_sym]
77
+ unless info
78
+ raise(ContainerError, "no such web service '#{web_service_name}'")
79
+ end
80
+ service = info[:block]
81
+ service ? self.instance_eval(&service) : info[:object]
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,69 @@
1
+ module ActionWebService # :nodoc:
2
+ module Container # :nodoc:
3
+ module Direct # :nodoc:
4
+ class ContainerError < ActionWebServiceError # :nodoc:
5
+ end
6
+
7
+ def self.included(base) # :nodoc:
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(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
47
+ end
48
+ unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
49
+ raise(ContainerError, "#{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
+ end
68
+ end
69
+ end
@@ -0,0 +1,2 @@
1
+ require 'action_web_service/dispatcher/abstract'
2
+ require 'action_web_service/dispatcher/action_controller_dispatcher'
@@ -0,0 +1,207 @@
1
+ require 'benchmark'
2
+
3
+ module ActionWebService # :nodoc:
4
+ module Dispatcher # :nodoc:
5
+ class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
6
+ def initialize(*args)
7
+ super
8
+ set_backtrace(caller)
9
+ end
10
+ end
11
+
12
+ def self.included(base) # :nodoc:
13
+ base.class_inheritable_option(:web_service_dispatching_mode, :direct)
14
+ base.class_inheritable_option(:web_service_exception_reporting, true)
15
+ base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
16
+ end
17
+
18
+ module InstanceMethods # :nodoc:
19
+ private
20
+ def invoke_web_service_request(protocol_request)
21
+ invocation = web_service_invocation(protocol_request)
22
+ if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
23
+ xmlrpc_multicall_invoke(invocation)
24
+ else
25
+ web_service_invoke(invocation)
26
+ end
27
+ end
28
+
29
+ def web_service_direct_invoke(invocation)
30
+ @method_params = invocation.method_ordered_params
31
+ arity = method(invocation.api_method.name).arity rescue 0
32
+ if arity < 0 || arity > 0
33
+ params = @method_params
34
+ else
35
+ params = []
36
+ end
37
+ web_service_filtered_invoke(invocation, params)
38
+ end
39
+
40
+ def web_service_delegated_invoke(invocation)
41
+ web_service_filtered_invoke(invocation, invocation.method_ordered_params)
42
+ end
43
+
44
+ def web_service_filtered_invoke(invocation, params)
45
+ cancellation_reason = nil
46
+ return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x|
47
+ cancellation_reason = x
48
+ end
49
+ if cancellation_reason
50
+ raise(DispatcherError, "request canceled: #{cancellation_reason}")
51
+ end
52
+ return_value
53
+ end
54
+
55
+ def web_service_invoke(invocation)
56
+ case web_service_dispatching_mode
57
+ when :direct
58
+ return_value = web_service_direct_invoke(invocation)
59
+ when :delegated, :layered
60
+ return_value = web_service_delegated_invoke(invocation)
61
+ end
62
+ web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value)
63
+ end
64
+
65
+ def xmlrpc_multicall_invoke(invocations)
66
+ responses = []
67
+ invocations.each do |invocation|
68
+ if invocation.is_a?(Hash)
69
+ responses << [invocation, nil]
70
+ next
71
+ end
72
+ begin
73
+ case web_service_dispatching_mode
74
+ when :direct
75
+ return_value = web_service_direct_invoke(invocation)
76
+ when :delegated, :layered
77
+ return_value = web_service_delegated_invoke(invocation)
78
+ end
79
+ api_method = invocation.api_method
80
+ if invocation.api.has_api_method?(api_method.name)
81
+ response_type = (api_method.returns ? api_method.returns[0] : nil)
82
+ return_value = api_method.cast_returns(return_value)
83
+ else
84
+ response_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
85
+ end
86
+ responses << [return_value, response_type]
87
+ rescue Exception => e
88
+ responses << [{ 'faultCode' => 3, 'faultString' => e.message }, nil]
89
+ end
90
+ end
91
+ invocation = invocations[0]
92
+ invocation.protocol.encode_multicall_response(responses, invocation.protocol_options)
93
+ end
94
+
95
+ def web_service_invocation(request, level = 0)
96
+ public_method_name = request.method_name
97
+ invocation = Invocation.new
98
+ invocation.protocol = request.protocol
99
+ invocation.protocol_options = request.protocol_options
100
+ invocation.service_name = request.service_name
101
+ if web_service_dispatching_mode == :layered
102
+ case invocation.protocol
103
+ when Protocol::Soap::SoapProtocol
104
+ soap_action = request.protocol_options[:soap_action]
105
+ if soap_action && soap_action =~ /^\/\w+\/(\w+)\//
106
+ invocation.service_name = $1
107
+ end
108
+ when Protocol::XmlRpc::XmlRpcProtocol
109
+ if request.method_name =~ /^([^\.]+)\.(.*)$/
110
+ public_method_name = $2
111
+ invocation.service_name = $1
112
+ end
113
+ end
114
+ end
115
+ if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol
116
+ if public_method_name == 'multicall' && invocation.service_name == 'system'
117
+ if level > 0
118
+ raise(DispatcherError, "Recursive system.multicall invocations not allowed")
119
+ end
120
+ multicall = request.method_params.dup
121
+ unless multicall.is_a?(Array) && multicall[0].is_a?(Array)
122
+ raise(DispatcherError, "Malformed multicall (expected array of Hash elements)")
123
+ end
124
+ multicall = multicall[0]
125
+ return multicall.map do |item|
126
+ raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash)
127
+ raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName')
128
+ method_name = item['methodName']
129
+ params = item.has_key?('params') ? item['params'] : []
130
+ multicall_request = request.dup
131
+ multicall_request.method_name = method_name
132
+ multicall_request.method_params = params
133
+ begin
134
+ web_service_invocation(multicall_request, level + 1)
135
+ rescue Exception => e
136
+ {'faultCode' => 4, 'faultMessage' => e.message}
137
+ end
138
+ end
139
+ end
140
+ end
141
+ case web_service_dispatching_mode
142
+ when :direct
143
+ invocation.api = self.class.web_service_api
144
+ invocation.service = self
145
+ when :delegated, :layered
146
+ invocation.service = web_service_object(invocation.service_name)
147
+ invocation.api = invocation.service.class.web_service_api
148
+ end
149
+ if invocation.api.nil?
150
+ raise(DispatcherError, "no API attached to #{invocation.service.class}")
151
+ end
152
+ invocation.protocol.register_api(invocation.api)
153
+ request.api = invocation.api
154
+ if invocation.api.has_public_api_method?(public_method_name)
155
+ invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
156
+ else
157
+ if invocation.api.default_api_method.nil?
158
+ raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
159
+ else
160
+ invocation.api_method = invocation.api.default_api_method_instance
161
+ end
162
+ end
163
+ if invocation.service.nil?
164
+ raise(DispatcherError, "no service available for service name #{invocation.service_name}")
165
+ end
166
+ unless invocation.service.respond_to?(invocation.api_method.name)
167
+ raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
168
+ end
169
+ request.api_method = invocation.api_method
170
+ begin
171
+ invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
172
+ rescue
173
+ logger.warn "Casting of method parameters failed" unless logger.nil?
174
+ invocation.method_ordered_params = request.method_params
175
+ end
176
+ request.method_params = invocation.method_ordered_params
177
+ invocation.method_named_params = {}
178
+ invocation.api_method.param_names.inject(0) do |m, n|
179
+ invocation.method_named_params[n] = invocation.method_ordered_params[m]
180
+ m + 1
181
+ end
182
+ invocation
183
+ end
184
+
185
+ def web_service_create_response(protocol, protocol_options, api, api_method, return_value)
186
+ if api.has_api_method?(api_method.name)
187
+ return_type = api_method.returns ? api_method.returns[0] : nil
188
+ return_value = api_method.cast_returns(return_value)
189
+ else
190
+ return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
191
+ end
192
+ protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options)
193
+ end
194
+
195
+ class Invocation # :nodoc:
196
+ attr_accessor :protocol
197
+ attr_accessor :protocol_options
198
+ attr_accessor :service_name
199
+ attr_accessor :api
200
+ attr_accessor :api_method
201
+ attr_accessor :method_ordered_params
202
+ attr_accessor :method_named_params
203
+ attr_accessor :service
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,379 @@
1
+ require 'benchmark'
2
+ require 'builder/xmlmarkup'
3
+
4
+ module ActionWebService # :nodoc:
5
+ module Dispatcher # :nodoc:
6
+ module ActionController # :nodoc:
7
+ def self.included(base) # :nodoc:
8
+ class << base
9
+ include ClassMethods
10
+ alias_method_chain :inherited, :action_controller
11
+ end
12
+ base.class_eval do
13
+ alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
14
+ end
15
+ base.add_web_service_api_callback do |klass, api|
16
+ if klass.web_service_dispatching_mode == :direct
17
+ klass.class_eval 'def api; dispatch_web_service_request; end'
18
+ end
19
+ end
20
+ base.add_web_service_definition_callback do |klass, name, info|
21
+ if klass.web_service_dispatching_mode == :delegated
22
+ klass.class_eval "def #{name}; dispatch_web_service_request; end"
23
+ elsif klass.web_service_dispatching_mode == :layered
24
+ klass.class_eval 'def api; dispatch_web_service_request; end'
25
+ end
26
+ end
27
+ base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
28
+ end
29
+
30
+ module ClassMethods # :nodoc:
31
+ def inherited_with_action_controller(child)
32
+ inherited_without_action_controller(child)
33
+ child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
34
+ end
35
+ end
36
+
37
+ module InstanceMethods # :nodoc:
38
+ private
39
+ def dispatch_web_service_request
40
+ method = request.method.to_s.upcase
41
+ allowed_methods = self.class.web_service_api ? (self.class.web_service_api.allowed_http_methods || []) : [ :post ]
42
+ allowed_methods = allowed_methods.map{|m| m.to_s.upcase }
43
+ if !allowed_methods.include?(method)
44
+ render :text => "#{method} not supported", :status=>500
45
+ return
46
+ end
47
+ exception = nil
48
+ begin
49
+ ws_request = discover_web_service_request(request)
50
+ rescue Exception => e
51
+ exception = e
52
+ end
53
+ if ws_request
54
+ ws_response = nil
55
+ exception = nil
56
+ bm = Benchmark.measure do
57
+ begin
58
+ ws_response = invoke_web_service_request(ws_request)
59
+ rescue Exception => e
60
+ exception = e
61
+ end
62
+ end
63
+ log_request(ws_request, request.raw_post)
64
+ if exception
65
+ log_error(exception) unless logger.nil?
66
+ send_web_service_error_response(ws_request, exception)
67
+ else
68
+ send_web_service_response(ws_response, bm.real)
69
+ end
70
+ else
71
+ exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
72
+ log_error(exception) unless logger.nil?
73
+ send_web_service_error_response(ws_request, exception)
74
+ end
75
+ rescue Exception => e
76
+ log_error(e) unless logger.nil?
77
+ send_web_service_error_response(ws_request, e)
78
+ end
79
+
80
+ def send_web_service_response(ws_response, elapsed=nil)
81
+ log_response(ws_response, elapsed)
82
+ options = { :type => ws_response.content_type, :disposition => 'inline' }
83
+ send_data(ws_response.body, options)
84
+ end
85
+
86
+ def send_web_service_error_response(ws_request, exception)
87
+ if ws_request
88
+ unless self.class.web_service_exception_reporting
89
+ exception = DispatcherError.new("Internal server error (exception raised)")
90
+ end
91
+ api_method = ws_request.api_method
92
+ public_method_name = api_method ? api_method.public_name : ws_request.method_name
93
+ return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
94
+ ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
95
+ send_web_service_response(ws_response)
96
+ else
97
+ if self.class.web_service_exception_reporting
98
+ message = exception.message
99
+ backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
100
+ else
101
+ message = "Exception raised"
102
+ backtrace = ""
103
+ end
104
+ render :status => 500, :text => "Internal protocol error: #{message}#{backtrace}"
105
+ end
106
+ end
107
+
108
+ def web_service_direct_invoke(invocation)
109
+ invocation.method_named_params.each do |name, value|
110
+ params[name] = value
111
+ end
112
+ web_service_direct_invoke_without_controller(invocation)
113
+ end
114
+
115
+ def log_request(ws_request, body)
116
+ unless logger.nil?
117
+ name = ws_request.method_name
118
+ api_method = ws_request.api_method
119
+ params = ws_request.method_params
120
+ if api_method && api_method.expects
121
+ params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
122
+ else
123
+ params = params.map{ |param| param.inspect }
124
+ end
125
+ service = ws_request.service_name
126
+ logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
127
+ logger.debug(indent(body))
128
+ end
129
+ end
130
+
131
+ def log_response(ws_response, elapsed=nil)
132
+ unless logger.nil?
133
+ elapsed = (elapsed ? " (%f):" % elapsed : ":")
134
+ logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
135
+ logger.debug(indent(ws_response.body))
136
+ end
137
+ end
138
+
139
+ def indent(body)
140
+ body.split(/\n/).map{|x| " #{x}"}.join("\n")
141
+ end
142
+ end
143
+
144
+ module WsdlAction # :nodoc:
145
+ XsdNs = 'http://www.w3.org/2001/XMLSchema'
146
+ WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
147
+ SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
148
+ SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
149
+ SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
150
+
151
+ def wsdl
152
+ case request.method
153
+ when :get
154
+ begin
155
+ options = { :type => 'text/xml', :disposition => 'inline' }
156
+ send_data(to_wsdl, options)
157
+ rescue Exception => e
158
+ log_error(e) unless logger.nil?
159
+ end
160
+ when :post
161
+ render :status => 500, :text => 'POST not supported'
162
+ end
163
+ end
164
+
165
+ private
166
+ def base_uri
167
+ host = request.host_with_port
168
+ relative_url_root = ::ActionController::Base.relative_url_root
169
+ scheme = request.ssl? ? 'https' : 'http'
170
+ '%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
171
+ end
172
+
173
+ def to_wsdl
174
+ xml = ''
175
+ dispatching_mode = web_service_dispatching_mode
176
+ global_service_name = wsdl_service_name
177
+ namespace = wsdl_namespace || 'urn:ActionWebService'
178
+ soap_action_base = "/#{controller_name}"
179
+
180
+ marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
181
+ apis = {}
182
+ case dispatching_mode
183
+ when :direct
184
+ api = self.class.web_service_api
185
+ web_service_name = controller_class_name.sub(/Controller$/, '').underscore
186
+ apis[web_service_name] = [api, register_api(api, marshaler)]
187
+ when :delegated, :layered
188
+ self.class.web_services.each do |web_service_name, info|
189
+ service = web_service_object(web_service_name)
190
+ api = service.class.web_service_api
191
+ apis[web_service_name] = [api, register_api(api, marshaler)]
192
+ end
193
+ end
194
+ custom_types = []
195
+ apis.values.each do |api, bindings|
196
+ bindings.each do |b|
197
+ custom_types << b unless custom_types.include?(b)
198
+ end
199
+ end
200
+
201
+ xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
202
+ xm.instruct!
203
+ xm.definitions('name' => wsdl_service_name,
204
+ 'targetNamespace' => namespace,
205
+ 'xmlns:typens' => namespace,
206
+ 'xmlns:xsd' => XsdNs,
207
+ 'xmlns:soap' => SoapNs,
208
+ 'xmlns:soapenc' => SoapEncodingNs,
209
+ 'xmlns:wsdl' => WsdlNs,
210
+ 'xmlns' => WsdlNs) do
211
+ # Generate XSD
212
+ if custom_types.size > 0
213
+ xm.types do
214
+ xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
215
+ custom_types.each do |binding|
216
+ case
217
+ when binding.type.array?
218
+ xm.xsd(:complexType, 'name' => binding.type_name) do
219
+ xm.xsd(:complexContent) do
220
+ xm.xsd(:restriction, 'base' => 'soapenc:Array') do
221
+ xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
222
+ 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
223
+ end
224
+ end
225
+ end
226
+ when binding.type.structured?
227
+ xm.xsd(:complexType, 'name' => binding.type_name) do
228
+ xm.xsd(:all) do
229
+ binding.type.each_member do |name, type|
230
+ b = marshaler.register_type(type)
231
+ xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ # APIs
242
+ apis.each do |api_name, values|
243
+ api = values[0]
244
+ api.api_methods.each do |name, method|
245
+ gen = lambda do |msg_name, direction|
246
+ xm.message('name' => message_name_for(api_name, msg_name)) do
247
+ sym = nil
248
+ if direction == :out
249
+ returns = method.returns
250
+ if returns
251
+ binding = marshaler.register_type(returns[0])
252
+ xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
253
+ end
254
+ else
255
+ expects = method.expects
256
+ expects.each do |type|
257
+ binding = marshaler.register_type(type)
258
+ xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
259
+ end if expects
260
+ end
261
+ end
262
+ end
263
+ public_name = method.public_name
264
+ gen.call(public_name, :in)
265
+ gen.call("#{public_name}Response", :out)
266
+ end
267
+
268
+ # Port
269
+ port_name = port_name_for(global_service_name, api_name)
270
+ xm.portType('name' => port_name) do
271
+ api.api_methods.each do |name, method|
272
+ xm.operation('name' => method.public_name) do
273
+ xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
274
+ xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
275
+ end
276
+ end
277
+ end
278
+
279
+ # Bind it
280
+ binding_name = binding_name_for(global_service_name, api_name)
281
+ xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
282
+ xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
283
+ api.api_methods.each do |name, method|
284
+ xm.operation('name' => method.public_name) do
285
+ case web_service_dispatching_mode
286
+ when :direct
287
+ soap_action = soap_action_base + "/api/" + method.public_name
288
+ when :delegated, :layered
289
+ soap_action = soap_action_base \
290
+ + "/" + api_name.to_s \
291
+ + "/" + method.public_name
292
+ end
293
+ xm.soap(:operation, 'soapAction' => soap_action)
294
+ xm.input do
295
+ xm.soap(:body,
296
+ 'use' => 'encoded',
297
+ 'namespace' => namespace,
298
+ 'encodingStyle' => SoapEncodingNs)
299
+ end
300
+ xm.output do
301
+ xm.soap(:body,
302
+ 'use' => 'encoded',
303
+ 'namespace' => namespace,
304
+ 'encodingStyle' => SoapEncodingNs)
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
310
+
311
+ # Define it
312
+ xm.service('name' => "#{global_service_name}Service") do
313
+ apis.each do |api_name, values|
314
+ port_name = port_name_for(global_service_name, api_name)
315
+ binding_name = binding_name_for(global_service_name, api_name)
316
+ case web_service_dispatching_mode
317
+ when :direct, :layered
318
+ binding_target = 'api'
319
+ when :delegated
320
+ binding_target = api_name.to_s
321
+ end
322
+ xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
323
+ xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
329
+
330
+ def port_name_for(global_service, service)
331
+ "#{global_service}#{service.to_s.camelize}Port"
332
+ end
333
+
334
+ def binding_name_for(global_service, service)
335
+ "#{global_service}#{service.to_s.camelize}Binding"
336
+ end
337
+
338
+ def message_name_for(api_name, message_name)
339
+ mode = web_service_dispatching_mode
340
+ if mode == :layered || mode == :delegated
341
+ api_name.to_s + '-' + message_name
342
+ else
343
+ message_name
344
+ end
345
+ end
346
+
347
+ def register_api(api, marshaler)
348
+ bindings = {}
349
+ traverse_custom_types(api, marshaler, bindings) do |binding|
350
+ bindings[binding] = nil unless bindings.has_key?(binding)
351
+ element_binding = binding.element_binding
352
+ bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
353
+ end
354
+ bindings.keys
355
+ end
356
+
357
+ def traverse_custom_types(api, marshaler, bindings, &block)
358
+ api.api_methods.each do |name, method|
359
+ expects, returns = method.expects, method.returns
360
+ expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects
361
+ returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns
362
+ end
363
+ end
364
+
365
+ def traverse_type(marshaler, type, bindings, &block)
366
+ binding = marshaler.register_type(type)
367
+ return if bindings.has_key?(binding)
368
+ bindings[binding] = nil
369
+ yield binding
370
+ if type.array?
371
+ yield marshaler.register_type(type.element_type)
372
+ type = type.element_type
373
+ end
374
+ type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured?
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end