actionservice 0.2.99
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.
- data/CHANGELOG +3 -0
- data/HACKING +39 -0
- data/MIT-LICENSE +21 -0
- data/README +36 -0
- data/Rakefile +121 -0
- data/TODO +25 -0
- data/examples/soap/README +121 -0
- data/examples/soap/app/controllers/search_controller.rb +8 -0
- data/examples/soap/lib/google_search_service.rb +90 -0
- data/lib/action_service/base.rb +74 -0
- data/lib/action_service/container.rb +69 -0
- data/lib/action_service/invocation.rb +172 -0
- data/lib/action_service/protocol/abstract.rb +61 -0
- data/lib/action_service/protocol/registry.rb +49 -0
- data/lib/action_service/protocol/soap.rb +386 -0
- data/lib/action_service/protocol.rb +3 -0
- data/lib/action_service/router/action_controller.rb +71 -0
- data/lib/action_service/router/wsdl.rb +180 -0
- data/lib/action_service/router.rb +2 -0
- data/lib/action_service/struct.rb +15 -0
- data/lib/action_service/support/class_inheritable_options.rb +26 -0
- data/lib/action_service.rb +50 -0
- data/setup.rb +1360 -0
- data/test/abstract_unit.rb +9 -0
- data/test/base_test.rb +47 -0
- data/test/container_test.rb +29 -0
- data/test/invocation_test.rb +144 -0
- data/test/protocol_registry_test.rb +54 -0
- data/test/protocol_soap_test.rb +94 -0
- data/test/router_action_controller_test.rb +98 -0
- data/test/router_wsdl_test.rb +43 -0
- data/test/struct_test.rb +16 -0
- metadata +87 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'action_service/support/class_inheritable_options'
|
2
|
+
|
3
|
+
module ActionService
|
4
|
+
class ActionServiceError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class Base
|
8
|
+
class_inheritable_option :export_name_mangling, true
|
9
|
+
class_inheritable_option :report_exceptions, true
|
10
|
+
class_inheritable_option :logger
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def export(name, options={})
|
14
|
+
validate_options([:expects, :returns, :expects_and_returns], options.keys)
|
15
|
+
if options[:expects_and_returns]
|
16
|
+
expects = options[:expects_and_returns]
|
17
|
+
returns = options[:expects_and_returns]
|
18
|
+
else
|
19
|
+
expects = options[:expects]
|
20
|
+
returns = options[:returns]
|
21
|
+
end
|
22
|
+
if expects.nil?
|
23
|
+
arity = instance_method(name).arity
|
24
|
+
expects = [Object] * arity if arity > 0
|
25
|
+
end
|
26
|
+
if returns.nil?
|
27
|
+
returns = [NilClass]
|
28
|
+
end
|
29
|
+
|
30
|
+
name = name.to_sym
|
31
|
+
public_name = public_export_name(name)
|
32
|
+
info = { :expects => expects, :returns => returns }
|
33
|
+
write_inheritable_hash("action_service_exports", name => info)
|
34
|
+
write_inheritable_hash("action_service_public_exports", public_name => name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_export?(name)
|
38
|
+
exports.has_key?(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_public_export?(name)
|
42
|
+
public_exports.has_key?(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def public_export_name(export_name)
|
46
|
+
if export_name_mangling
|
47
|
+
Inflector.camelize(export_name.to_s)
|
48
|
+
else
|
49
|
+
export_name.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def internal_export_name(public_name)
|
54
|
+
public_exports[public_name]
|
55
|
+
end
|
56
|
+
|
57
|
+
def exports
|
58
|
+
read_inheritable_attribute("action_service_exports") || {}
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def public_exports
|
63
|
+
read_inheritable_attribute("action_service_public_exports") || {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_options(valid_option_keys, supplied_option_keys)
|
67
|
+
unknown_option_keys = supplied_option_keys - valid_option_keys
|
68
|
+
unless unknown_option_keys.empty?
|
69
|
+
raise(ActionServiceError, "Unknown options: #{unknown_option_keys}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActionService
|
2
|
+
module Container
|
3
|
+
class ContainerError < ActionService::ActionServiceError
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.append_features(base)
|
7
|
+
super
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
base.send(:include, ActionService::Container::InstanceMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def service(name, object=nil, options={}, &block)
|
14
|
+
if (object && block_given?) || (object.nil? && block.nil?)
|
15
|
+
raise(ContainerError, "either service, or a block must be given")
|
16
|
+
end
|
17
|
+
name = name.to_sym
|
18
|
+
if block_given?
|
19
|
+
info = { name => { :block => block } }
|
20
|
+
else
|
21
|
+
info = { name => { :object => object } }
|
22
|
+
end
|
23
|
+
write_inheritable_hash("action_services", info)
|
24
|
+
call_service_definition_callbacks(self, name, info)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_service_definition_callback(&block)
|
28
|
+
write_inheritable_array("action_service_definition_callbacks", [block])
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_service?(name)
|
32
|
+
services.has_key?(name.to_sym)
|
33
|
+
end
|
34
|
+
|
35
|
+
def services
|
36
|
+
read_inheritable_attribute("action_services") || {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def service_object(service_name)
|
40
|
+
info = services[service_name.to_sym]
|
41
|
+
unless info
|
42
|
+
raise(ContainerError, "no such service '#{service_name}'")
|
43
|
+
end
|
44
|
+
service = info[:block]
|
45
|
+
service ? instance_eval(&service) : info[:object]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def call_service_definition_callbacks(container_klass, service_name, service_info)
|
50
|
+
(read_inheritable_attribute("action_service_definition_callbacks") || []).each do |block|
|
51
|
+
block.call(container_klass, service_name, service_info)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module InstanceMethods
|
57
|
+
private
|
58
|
+
def dispatch_service_invocation_request(protocol, info)
|
59
|
+
service = self.class.service_object(info.service_name)
|
60
|
+
method_name = service.class.internal_export_name(info.public_method_name)
|
61
|
+
export_info = service.class.exports[method_name]
|
62
|
+
params = protocol.unmarshal_request(info, export_info)
|
63
|
+
result = service.perform_invocation(method_name, params)
|
64
|
+
protocol.marshal_response(info, export_info, result)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module ActionService
|
2
|
+
module Invocation
|
3
|
+
class InvocationError < ActionService::ActionServiceError
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.append_features(base) # :nodoc:
|
7
|
+
super
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
base.send(:include, ActionService::Invocation::InstanceMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def append_before_invocation(*interceptors, &block)
|
14
|
+
conditions = extract_conditions!(interceptors)
|
15
|
+
interceptors << block if block_given?
|
16
|
+
add_interception_conditions(interceptors, conditions)
|
17
|
+
append_interceptors_to_chain("before", interceptors)
|
18
|
+
end
|
19
|
+
|
20
|
+
def prepend_before_invocation(*interceptors, &block)
|
21
|
+
conditions = extract_conditions!(interceptors)
|
22
|
+
interceptors << block if block_given?
|
23
|
+
add_interception_conditions(interceptors, conditions)
|
24
|
+
prepend_interceptors_to_chain("before", interceptors)
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :before_invocation :append_before_invocation
|
28
|
+
|
29
|
+
def append_after_invocation(*interceptors, &block)
|
30
|
+
conditions = extract_conditions!(interceptors)
|
31
|
+
interceptors << block if block_given?
|
32
|
+
add_interception_conditions(interceptors, conditions)
|
33
|
+
append_interceptors_to_chain("after", interceptors)
|
34
|
+
end
|
35
|
+
|
36
|
+
def prepend_after_invocation(*interceptors, &block)
|
37
|
+
conditions = extract_conditions!(interceptors)
|
38
|
+
interceptors << block if block_given?
|
39
|
+
add_interception_conditions(interceptors, conditions)
|
40
|
+
prepend_interceptors_to_chain("after", interceptors)
|
41
|
+
end
|
42
|
+
|
43
|
+
alias :after_invocation :append_after_invocation
|
44
|
+
|
45
|
+
def before_invocation_interceptors
|
46
|
+
read_inheritable_attribute("before_invocation_interceptors")
|
47
|
+
end
|
48
|
+
|
49
|
+
def after_invocation_interceptors
|
50
|
+
read_inheritable_attribute("after_invocation_interceptors")
|
51
|
+
end
|
52
|
+
|
53
|
+
def included_intercepted_methods
|
54
|
+
read_inheritable_attribute("included_intercepted_methods") || {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def excluded_intercepted_methods
|
58
|
+
read_inheritable_attribute("excluded_intercepted_methods") || {}
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def append_interceptors_to_chain(condition, interceptors)
|
64
|
+
write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
|
65
|
+
end
|
66
|
+
|
67
|
+
def prepend_interceptors_to_chain(condition, interceptors)
|
68
|
+
interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors")
|
69
|
+
write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors)
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_conditions!(interceptors)
|
73
|
+
return nil unless interceptors.last.is_a? Hash
|
74
|
+
interceptors.pop
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_interception_conditions(interceptors, conditions)
|
78
|
+
return unless conditions
|
79
|
+
included, excluded = conditions[:only], conditions[:except]
|
80
|
+
write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included
|
81
|
+
write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded
|
82
|
+
end
|
83
|
+
|
84
|
+
def condition_hash(interceptors, *methods)
|
85
|
+
interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module InstanceMethods # :nodoc:
|
90
|
+
def self.append_features(base)
|
91
|
+
super
|
92
|
+
base.class_eval do
|
93
|
+
alias_method :perform_invocation_without_interception, :perform_invocation
|
94
|
+
alias_method :perform_invocation, :perform_invocation_with_interception
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def perform_invocation_with_interception(name, args=nil, &block)
|
99
|
+
return if before_invocation(name, args, &block) == false
|
100
|
+
result = perform_invocation_without_interception(name, args)
|
101
|
+
after_invocation(name, args, result)
|
102
|
+
result
|
103
|
+
end
|
104
|
+
|
105
|
+
def perform_invocation(name, args=nil)
|
106
|
+
unless self.respond_to?(name) and self.class.has_export?(name)
|
107
|
+
raise InvocationError, "no such exported method '#{name}'"
|
108
|
+
end
|
109
|
+
args ||= []
|
110
|
+
self.send(name, *args)
|
111
|
+
end
|
112
|
+
|
113
|
+
def before_invocation(name, args, &block)
|
114
|
+
call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
|
115
|
+
end
|
116
|
+
|
117
|
+
def after_invocation(name, args, result)
|
118
|
+
call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def call_interceptors(interceptors, interceptor_args, &block)
|
124
|
+
if interceptors and not interceptors.empty?
|
125
|
+
interceptors.each do |interceptor|
|
126
|
+
next if method_exempted?(interceptor, interceptor_args[0].to_s)
|
127
|
+
result = case
|
128
|
+
when interceptor.is_a?(Symbol)
|
129
|
+
self.send(interceptor, *interceptor_args)
|
130
|
+
when interceptor_block?(interceptor)
|
131
|
+
interceptor.call(self, *interceptor_args)
|
132
|
+
when interceptor_class?(interceptor)
|
133
|
+
interceptor.intercept(self, *interceptor_args)
|
134
|
+
else
|
135
|
+
raise(
|
136
|
+
InvocationError,
|
137
|
+
"Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
|
138
|
+
)
|
139
|
+
end
|
140
|
+
reason = nil
|
141
|
+
if result.is_a?(Array)
|
142
|
+
reason = result[1] if result[1]
|
143
|
+
result = result[0]
|
144
|
+
end
|
145
|
+
if result == false
|
146
|
+
block.call(reason) if block && reason
|
147
|
+
return false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def interceptor_block?(interceptor)
|
154
|
+
interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
|
155
|
+
end
|
156
|
+
|
157
|
+
def interceptor_class?(interceptor)
|
158
|
+
interceptor.respond_to?("intercept")
|
159
|
+
end
|
160
|
+
|
161
|
+
def method_exempted?(interceptor, method_name)
|
162
|
+
case
|
163
|
+
when self.class.included_intercepted_methods[interceptor]
|
164
|
+
!self.class.included_intercepted_methods[interceptor].include?(method_name)
|
165
|
+
when self.class.excluded_intercepted_methods[interceptor]
|
166
|
+
self.class.excluded_intercepted_methods[interceptor].include?(method_name)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ActionService
|
2
|
+
module Protocol
|
3
|
+
class ProtocolError < ActionService::ActionServiceError
|
4
|
+
end
|
5
|
+
|
6
|
+
class AbstractProtocol
|
7
|
+
attr :container_klass
|
8
|
+
|
9
|
+
def initialize(container_klass)
|
10
|
+
@container_klass = container_klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def unmarshal_request(request_info, export_info)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def marshal_response(request_info, export_info, return_value)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def marshal_exception(exception)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def request_info(request)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def request_supported?(request)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ServiceRequestInfo
|
35
|
+
attr :service_name
|
36
|
+
attr :public_method_name
|
37
|
+
attr :raw_body
|
38
|
+
attr :content_type
|
39
|
+
attr :protocol_info
|
40
|
+
|
41
|
+
def initialize(service_name, public_method_name, raw_body, content_type, protocol_info)
|
42
|
+
@service_name = service_name
|
43
|
+
@public_method_name = public_method_name
|
44
|
+
@raw_body = raw_body
|
45
|
+
@content_type = content_type
|
46
|
+
@protocol_info = protocol_info
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ServiceResponseInfo
|
51
|
+
attr :raw_body
|
52
|
+
attr :content_type
|
53
|
+
|
54
|
+
def initialize(raw_body, content_type)
|
55
|
+
@raw_body = raw_body
|
56
|
+
@content_type = content_type
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ActionService
|
2
|
+
module Protocol
|
3
|
+
HeaderAndBody = :header_and_body
|
4
|
+
BodyOnly = :body_only
|
5
|
+
|
6
|
+
module Registry
|
7
|
+
def self.append_features(base)
|
8
|
+
super
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
base.send(:include, ActionService::Protocol::Registry::InstanceMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def register_protocol(type, klass)
|
15
|
+
case type
|
16
|
+
when HeaderAndBody
|
17
|
+
write_inheritable_array("header_and_body_protocols", [klass])
|
18
|
+
when BodyOnly
|
19
|
+
write_inheritable_array("body_only_protocols", [klass])
|
20
|
+
else
|
21
|
+
raise(ProtocolError, "unknown protocol type #{type}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
private
|
28
|
+
def probe_request_protocol(action_pack_request)
|
29
|
+
(header_and_body_protocols + body_only_protocols).each do |klass|
|
30
|
+
protocol = klass.new(self.class)
|
31
|
+
if protocol.request_supported?(action_pack_request)
|
32
|
+
return protocol
|
33
|
+
end
|
34
|
+
end
|
35
|
+
raise(ProtocolError, "unsupported request message format")
|
36
|
+
end
|
37
|
+
|
38
|
+
def header_and_body_protocols
|
39
|
+
self.class.read_inheritable_attribute("header_and_body_protocols") || []
|
40
|
+
end
|
41
|
+
|
42
|
+
def body_only_protocols
|
43
|
+
self.class.read_inheritable_attribute("body_only_protocols") || []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,386 @@
|
|
1
|
+
require 'soap/processor'
|
2
|
+
require 'soap/mapping'
|
3
|
+
require 'soap/rpc/element'
|
4
|
+
require 'xsd/datatypes'
|
5
|
+
require 'xsd/ns'
|
6
|
+
|
7
|
+
module ActionService
|
8
|
+
module Protocol
|
9
|
+
module Soap
|
10
|
+
class ProtocolError < ActionService::ActionServiceError
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.append_features(base)
|
14
|
+
super
|
15
|
+
base.register_protocol(HeaderAndBody, SoapProtocol)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
base.wsdl_service_name('ActionService')
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def wsdl_service_name(name)
|
22
|
+
write_inheritable_attribute("soap_mapper", SoapMapper.new("urn:#{name}"))
|
23
|
+
end
|
24
|
+
|
25
|
+
def soap_mapper
|
26
|
+
read_inheritable_attribute("soap_mapper")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class SoapProtocol < AbstractProtocol
|
31
|
+
def unmarshal_request(request_info, export_info)
|
32
|
+
expects = export_info[:expects]
|
33
|
+
map_types(expects) if expects
|
34
|
+
envelope = SOAP::Processor.unmarshal(request_info.raw_body)
|
35
|
+
request = envelope.body.request
|
36
|
+
params = request.collect{|k, v| request[k]}
|
37
|
+
params = soap_to_ruby_array(params)
|
38
|
+
validate_types(request_info.public_method_name, expects, params, :in) if expects
|
39
|
+
params
|
40
|
+
end
|
41
|
+
|
42
|
+
def marshal_response(request_info, export_info, return_value)
|
43
|
+
returns = export_info[:returns]
|
44
|
+
if returns
|
45
|
+
map_types(returns)
|
46
|
+
mapping = mapper.lookup(returns[0])
|
47
|
+
return_value = fixup_array_types(mapping, return_value)
|
48
|
+
end
|
49
|
+
validate_types(request_info.public_method_name, returns, [return_value], :out)
|
50
|
+
return_value = ruby_to_soap(return_value)
|
51
|
+
param_def = returns ? [['retval', 'return', mapping.registry_mapping]] : []
|
52
|
+
qname = XSD::QName.new(mapper.custom_namespace, request_info.public_method_name)
|
53
|
+
response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
|
54
|
+
response.retval = return_value
|
55
|
+
ServiceResponseInfo.new(create_response(response), 'text/xml')
|
56
|
+
end
|
57
|
+
|
58
|
+
def marshal_exception(exception)
|
59
|
+
ServiceResponseInfo.new(create_exception_response(exception), 'text/xml')
|
60
|
+
end
|
61
|
+
|
62
|
+
def request_info(request)
|
63
|
+
soap_action = extract_soap_action(request)
|
64
|
+
service_name = request.parameters['action']
|
65
|
+
public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1]
|
66
|
+
ServiceRequestInfo.new(service_name,
|
67
|
+
public_method_name,
|
68
|
+
request.raw_post,
|
69
|
+
request.env['HTTP_CONTENT_TYPE'] || 'text/xml',
|
70
|
+
:soap => { :action => soap_action })
|
71
|
+
end
|
72
|
+
|
73
|
+
def request_supported?(request)
|
74
|
+
extract_soap_action(request) ? true : false
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def mapper
|
79
|
+
container_klass.soap_mapper
|
80
|
+
end
|
81
|
+
|
82
|
+
def fixup_array_types(mapping, obj)
|
83
|
+
mapping.each_attribute do |name, type, attr_mapping|
|
84
|
+
if attr_mapping.custom_type?
|
85
|
+
attr_obj = obj.send(name)
|
86
|
+
new_obj = fixup_array_types(attr_mapping, attr_obj)
|
87
|
+
obj.send("#{name}=", new_obj) unless new_obj.equal?(attr_obj)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
if mapping.is_a?(SoapArrayMapping)
|
91
|
+
obj = mapping.ruby_klass.new(obj)
|
92
|
+
else
|
93
|
+
obj
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def extract_soap_action(request)
|
98
|
+
return nil unless request.method == :post
|
99
|
+
content_type = request.env['HTTP_CONTENT_TYPE'] || 'text/xml'
|
100
|
+
return nil unless content_type
|
101
|
+
soap_action = request.env['HTTP_SOAPACTION']
|
102
|
+
return nil unless soap_action
|
103
|
+
soap_action.gsub!(/^"/, '')
|
104
|
+
soap_action.gsub!(/"$/, '')
|
105
|
+
soap_action.strip!
|
106
|
+
return nil if soap_action.empty?
|
107
|
+
soap_action
|
108
|
+
end
|
109
|
+
|
110
|
+
def map_types(types)
|
111
|
+
types.collect{|type| mapper.map(type)}
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_types(method_name, signature, params, kind)
|
115
|
+
unless signature.length == params.length
|
116
|
+
raise(ProtocolError, "signature/parameter mismatch")
|
117
|
+
end
|
118
|
+
case kind
|
119
|
+
when :in
|
120
|
+
extra_msg = ' (Input parameter %d of method "%s")'
|
121
|
+
when :out
|
122
|
+
extra_msg = ' (Return value %d of method "%s")'
|
123
|
+
end
|
124
|
+
i = 0
|
125
|
+
signature.each do |klass|
|
126
|
+
check_compatibility(params[i].class, klass, extra_msg % [i, method_name])
|
127
|
+
i += 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def check_compatibility(klass1, klass2, extra_msg=nil)
|
132
|
+
if (klass1 == TrueClass or klass1 == FalseClass) and \
|
133
|
+
(klass2 == TrueClass or klass2 == FalseClass)
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
unless klass1.ancestors.include?(klass2) || \
|
137
|
+
klass2.ancestors.include?(klass1)
|
138
|
+
raise(ProtocolError, "value of type #{klass1.name} is not compatible " +
|
139
|
+
"with expected type #{klass2.name}" +
|
140
|
+
(extra_msg ? extra_msg : ''))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def create_response(body, is_error=false)
|
145
|
+
header = SOAP::SOAPHeader.new
|
146
|
+
body = SOAP::SOAPBody.new(body)
|
147
|
+
envelope = SOAP::SOAPEnvelope.new(header, body)
|
148
|
+
message = SOAP::Processor.marshal(envelope)
|
149
|
+
end
|
150
|
+
|
151
|
+
def create_exception_response(exc)
|
152
|
+
detail = SOAP::Mapping::SOAPException.new(exc)
|
153
|
+
body = SOAP::SOAPFault.new(
|
154
|
+
SOAP::SOAPString.new('Server'),
|
155
|
+
SOAP::SOAPString.new(exc.to_s),
|
156
|
+
SOAP::SOAPString.new(self.class.name),
|
157
|
+
SOAP::Mapping.obj2soap(detail))
|
158
|
+
create_response(body, true)
|
159
|
+
end
|
160
|
+
|
161
|
+
def ruby_to_soap(obj)
|
162
|
+
SOAP::Mapping.obj2soap(obj, mapper.registry)
|
163
|
+
end
|
164
|
+
|
165
|
+
def soap_to_ruby(obj)
|
166
|
+
SOAP::Mapping.soap2obj(obj, mapper.registry)
|
167
|
+
end
|
168
|
+
|
169
|
+
def soap_to_ruby_array(array)
|
170
|
+
array.map{|x| soap_to_ruby(x)}
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class SoapMapper
|
175
|
+
attr :registry
|
176
|
+
attr :custom_namespace
|
177
|
+
attr :custom_types
|
178
|
+
|
179
|
+
def initialize(custom_namespace)
|
180
|
+
@custom_namespace = custom_namespace
|
181
|
+
@registry = SOAP::Mapping::Registry.new
|
182
|
+
@klass2map = {}
|
183
|
+
@custom_types = {}
|
184
|
+
end
|
185
|
+
|
186
|
+
def lookup(klass)
|
187
|
+
return @klass2map[klass] if @klass2map.has_key?(klass)
|
188
|
+
|
189
|
+
custom_type = false
|
190
|
+
|
191
|
+
ruby_klass = select_class(klass.is_a?(Array) ? klass[0] : klass)
|
192
|
+
type_name = Inflector.camelize(ruby_klass.name.split(/::/)[-1])
|
193
|
+
|
194
|
+
# Array signatures generate a double-mapping and require generation
|
195
|
+
# of an Array subclass to represent the mapping in the SOAP
|
196
|
+
# registry
|
197
|
+
array_klass = nil
|
198
|
+
if klass.is_a?(Array)
|
199
|
+
array_klass = Class.new(Array) do
|
200
|
+
module_eval <<-END
|
201
|
+
def self.name
|
202
|
+
"#{type_name}Array"
|
203
|
+
end
|
204
|
+
END
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
mapping = @registry.find_mapped_soap_class(ruby_klass) rescue nil
|
209
|
+
unless mapping
|
210
|
+
# Custom structured type, generate a mapping
|
211
|
+
info = { :type => XSD::QName.new(@custom_namespace, type_name) }
|
212
|
+
@registry.add(ruby_klass,
|
213
|
+
SOAP::SOAPStruct,
|
214
|
+
SOAP::Mapping::Registry::TypedStructFactory,
|
215
|
+
info)
|
216
|
+
mapping = ensure_mapped(ruby_klass)
|
217
|
+
custom_type = true
|
218
|
+
end
|
219
|
+
|
220
|
+
array_mapping = nil
|
221
|
+
if array_klass
|
222
|
+
# Typed array always requires a custom type. The info of the array
|
223
|
+
# is the info of its element type (in mapping[2]), falling back
|
224
|
+
# to SOAP base types.
|
225
|
+
info = mapping[2]
|
226
|
+
info ||= {}
|
227
|
+
info[:type] ||= soap_base_type_qname(mapping[0])
|
228
|
+
@registry.add(array_klass,
|
229
|
+
SOAP::SOAPArray,
|
230
|
+
SOAP::Mapping::Registry::TypedArrayFactory,
|
231
|
+
info)
|
232
|
+
array_mapping = ensure_mapped(array_klass)
|
233
|
+
end
|
234
|
+
|
235
|
+
if array_mapping
|
236
|
+
@klass2map[ruby_klass] = SoapMapping.new(self,
|
237
|
+
type_name,
|
238
|
+
ruby_klass,
|
239
|
+
mapping[0],
|
240
|
+
mapping,
|
241
|
+
custom_type)
|
242
|
+
@klass2map[klass] = SoapArrayMapping.new(self,
|
243
|
+
type_name,
|
244
|
+
array_klass,
|
245
|
+
array_mapping[0],
|
246
|
+
array_mapping,
|
247
|
+
@klass2map[ruby_klass])
|
248
|
+
@custom_types[klass] = @klass2map[klass]
|
249
|
+
@custom_types[ruby_klass] = @klass2map[ruby_klass] if custom_type
|
250
|
+
else
|
251
|
+
@klass2map[klass] = SoapMapping.new(self,
|
252
|
+
type_name,
|
253
|
+
ruby_klass,
|
254
|
+
mapping[0],
|
255
|
+
mapping,
|
256
|
+
custom_type)
|
257
|
+
@custom_types[klass] = @klass2map[klass] if custom_type
|
258
|
+
end
|
259
|
+
|
260
|
+
@klass2map[klass]
|
261
|
+
end
|
262
|
+
alias :map :lookup
|
263
|
+
|
264
|
+
def map_container_services(container_klass, &block)
|
265
|
+
container_klass.services.each do |service_name, service_info|
|
266
|
+
object = container_klass.service_object(service_name)
|
267
|
+
service_klass = object.class
|
268
|
+
service_exports = {}
|
269
|
+
object.class.exports.each do |export_name, export_info|
|
270
|
+
expects = export_info[:expects]
|
271
|
+
lookup_proc = lambda do |klass|
|
272
|
+
mapping = lookup(klass)
|
273
|
+
custom_mapping = nil
|
274
|
+
if mapping.respond_to?(:element_mapping)
|
275
|
+
custom_mapping = mapping.element_mapping
|
276
|
+
else
|
277
|
+
custom_mapping = mapping
|
278
|
+
end
|
279
|
+
if custom_mapping && custom_mapping.custom_type?
|
280
|
+
# What gives? This is required so that structure types
|
281
|
+
# referenced only by structures (and not signatures) still
|
282
|
+
# have a custom type mapping in the registry (needed for WSDL
|
283
|
+
# generation).
|
284
|
+
custom_mapping.each_attribute{}
|
285
|
+
end
|
286
|
+
mapping
|
287
|
+
end
|
288
|
+
expects_signature = expects ? expects.map{|klass| lookup_proc.call(klass)} : nil
|
289
|
+
returns_signature = export_info[:returns].map{|klass| lookup_proc.call(klass)}
|
290
|
+
service_exports[export_name] = {
|
291
|
+
:expects => expects_signature,
|
292
|
+
:returns => returns_signature
|
293
|
+
}
|
294
|
+
end
|
295
|
+
yield service_name, service_klass, service_exports if block_given?
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
private
|
300
|
+
def select_class(klass)
|
301
|
+
return Integer if klass == Fixnum
|
302
|
+
klass
|
303
|
+
end
|
304
|
+
|
305
|
+
def ensure_mapped(klass)
|
306
|
+
mapping = @registry.find_mapped_soap_class(klass) rescue nil
|
307
|
+
raise(ProtocolError, "failed to register #{klass.name}") unless mapping
|
308
|
+
mapping
|
309
|
+
end
|
310
|
+
|
311
|
+
def soap_base_type_qname(base_type)
|
312
|
+
xsd_type = base_type.ancestors.find{|c| c.const_defined? 'Type'}
|
313
|
+
xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class SoapMapping
|
318
|
+
attr :ruby_klass
|
319
|
+
attr :soap_klass
|
320
|
+
attr :registry_mapping
|
321
|
+
|
322
|
+
def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping,
|
323
|
+
custom_type=false)
|
324
|
+
@mapper = mapper
|
325
|
+
@type_name = type_name
|
326
|
+
@ruby_klass = ruby_klass
|
327
|
+
@soap_klass = soap_klass
|
328
|
+
@registry_mapping = registry_mapping
|
329
|
+
@custom_type = custom_type
|
330
|
+
end
|
331
|
+
|
332
|
+
def type_name
|
333
|
+
@type_name
|
334
|
+
end
|
335
|
+
|
336
|
+
def custom_type?
|
337
|
+
@custom_type
|
338
|
+
end
|
339
|
+
|
340
|
+
def qualified_type_name
|
341
|
+
name = type_name
|
342
|
+
if custom_type?
|
343
|
+
"typens:#{name}"
|
344
|
+
else
|
345
|
+
xsd_type_for(@soap_klass)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def each_attribute(&block)
|
350
|
+
if @ruby_klass.respond_to?(:members)
|
351
|
+
@ruby_klass.members.each do |name, klass|
|
352
|
+
name = name.to_s
|
353
|
+
mapping = @mapper.lookup(klass)
|
354
|
+
yield name, mapping.qualified_type_name, mapping
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def is_xsd_type?(klass)
|
360
|
+
klass.ancestors.include?(XSD::NSDBase)
|
361
|
+
end
|
362
|
+
|
363
|
+
def xsd_type_for(klass)
|
364
|
+
ns = XSD::NS.new
|
365
|
+
ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
|
366
|
+
xsd_klass = klass.ancestors.find{|c| c.const_defined?('Type')}
|
367
|
+
return ns.name(XSD::AnyTypeName) unless xsd_klass
|
368
|
+
ns.name(xsd_klass.const_get('Type'))
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
class SoapArrayMapping < SoapMapping
|
373
|
+
attr :element_mapping
|
374
|
+
|
375
|
+
def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping, element_mapping)
|
376
|
+
super(mapper, type_name, ruby_klass, soap_klass, registry_mapping, true)
|
377
|
+
@element_mapping = element_mapping
|
378
|
+
end
|
379
|
+
|
380
|
+
def type_name
|
381
|
+
super + "Array"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|