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.
@@ -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