actionservice 0.2.100 → 0.2.102

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.
@@ -1,22 +1,23 @@
1
1
  module ActionService
2
2
  module Protocol
3
+ CheckedMessage = :checked
4
+ UncheckedMessage = :unchecked
5
+
3
6
  class ProtocolError < ActionService::ActionServiceError
4
7
  end
5
- class SystemExportNotHandledError < ProtocolError
6
- end
7
8
 
8
9
  class AbstractProtocol
9
- attr :container_klass
10
+ attr :container_class
10
11
 
11
- def initialize(container_klass)
12
- @container_klass = container_klass
12
+ def initialize(container_class)
13
+ @container_class = container_class
13
14
  end
14
15
 
15
- def unmarshal_request(request_info, export_info, strict=true)
16
+ def unmarshal_request(protocol_request)
16
17
  raise NotImplementedError
17
18
  end
18
19
 
19
- def marshal_response(request_info, export_info, return_value, strict=true)
20
+ def marshal_response(protocol_request, return_value)
20
21
  raise NotImplementedError
21
22
  end
22
23
 
@@ -24,71 +25,100 @@ module ActionService
24
25
  raise NotImplementedError
25
26
  end
26
27
 
27
- def request_info
28
- raise NotImplementedError
28
+ def self.create_protocol_request(container_class, action_pack_request)
29
+ nil
29
30
  end
31
+ end
30
32
 
31
- def request_supported?(request)
32
- raise NotImplementedError
33
+ class AbstractProtocolMessage
34
+ attr_accessor :signature
35
+ attr_accessor :return_signature
36
+ attr_accessor :type
37
+
38
+ def initialize(options={})
39
+ @signature = @return_signature = nil
40
+ @type = options[:type] || CheckedMessage
33
41
  end
34
-
35
- private
36
- def validate_types(method_name, signature, params, kind)
37
- unless signature.length == params.length
38
- raise(ProtocolError, "signature/parameter mismatch")
39
- end
40
- case kind
41
- when :in
42
- extra_msg = ' (Input parameter %d of method "%s")'
43
- when :out
44
- extra_msg = ' (Return value %d of method "%s")'
45
- end
46
- i = 0
47
- signature.each do |klass|
48
- check_compatibility(params[i].class, klass, extra_msg % [i+1, method_name])
49
- i += 1
42
+
43
+ def signature=(value)
44
+ return if value.nil?
45
+ @signature = []
46
+ value.each do |klass|
47
+ if klass.is_a?(Hash)
48
+ @signature << klass.values.shift
49
+ else
50
+ @signature << klass
50
51
  end
51
52
  end
53
+ @signature
54
+ end
52
55
 
53
- def check_compatibility(given_klass, signature_klass, extra_msg=nil)
54
- if (given_klass == TrueClass or given_klass == FalseClass) and \
55
- (signature_klass == TrueClass or signature_klass == FalseClass)
56
- return true
57
- end
58
- unless given_klass.ancestors.include?(signature_klass) || \
59
- signature_klass.ancestors.include?(given_klass)
60
- raise(ProtocolError, "value of type #{given_klass.name} is not compatible " +
61
- "with expected type #{signature_klass.name}" +
62
- (extra_msg ? extra_msg : ''))
63
- end
56
+ def checked?
57
+ @type == CheckedMessage
58
+ end
59
+
60
+ def check_parameter_types(values, signature)
61
+ return unless checked? && signature
62
+ unless signature.length == values.length
63
+ raise(ProtocolError, "Signature and parameter lengths mismatch")
64
64
  end
65
+ (1..signature.length).each do |i|
66
+ check_compatibility(signature[i-1], values[i-1].class)
67
+ end
68
+ end
69
+
70
+ def check_compatibility(expected_class, received_class)
71
+ return if \
72
+ (expected_class == TrueClass or expected_class == FalseClass) and \
73
+ (received_class == TrueClass or received_class == FalseClass)
74
+ unless received_class.ancestors.include?(expected_class) or \
75
+ expected_class.ancestors.include?(received_class)
76
+ raise(ProtocolError, "value of type #{received_class.name} is not " +
77
+ "compatible with expected type #{expected_class.name}")
78
+ end
79
+ end
65
80
  end
66
81
 
67
- class ServiceRequestInfo
68
- attr :service_name
69
- attr :public_method_name
82
+ class ProtocolRequest < AbstractProtocolMessage
83
+ attr :protocol
70
84
  attr :raw_body
71
- attr :content_type
72
- attr :protocol_info
73
85
 
74
- def initialize(service_name, public_method_name, raw_body, content_type, protocol_info=nil)
86
+ attr_accessor :service_name
87
+ attr_accessor :public_method_name
88
+ attr_accessor :content_type
89
+ attr_accessor :values
90
+
91
+ def initialize(protocol, raw_body, service_name, public_method_name, content_type, options={})
92
+ super(options)
93
+ @protocol = protocol
94
+ @raw_body = raw_body
75
95
  @service_name = service_name
76
96
  @public_method_name = public_method_name
77
- @raw_body = raw_body
78
97
  @content_type = content_type
79
- @protocol_info = protocol_info
98
+ @values = nil
99
+ end
100
+
101
+ def unmarshal
102
+ @values ||= @protocol.unmarshal_request(self)
103
+ end
104
+
105
+ def marshal(return_value)
106
+ @protocol.marshal_response(self, return_value)
80
107
  end
81
108
  end
82
109
 
83
- class ServiceResponseInfo
110
+ class ProtocolResponse < AbstractProtocolMessage
111
+ attr :protocol
84
112
  attr :raw_body
85
- attr :content_type
86
113
 
87
- def initialize(raw_body, content_type)
114
+ attr_accessor :content_type
115
+
116
+ def initialize(protocol, raw_body, content_type, options={})
117
+ super(options)
118
+ @protocol = protocol
88
119
  @raw_body = raw_body
89
120
  @content_type = content_type
90
121
  end
91
122
  end
92
-
93
123
  end
94
124
  end
@@ -26,11 +26,9 @@ module ActionService
26
26
  module InstanceMethods
27
27
  private
28
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
29
+ (header_and_body_protocols + body_only_protocols).each do |protocol|
30
+ protocol_request = protocol.create_protocol_request(self.class, action_pack_request)
31
+ return protocol_request if protocol_request
34
32
  end
35
33
  raise(ProtocolError, "unsupported request message format")
36
34
  end
@@ -3,6 +3,7 @@ require 'soap/mapping'
3
3
  require 'soap/rpc/element'
4
4
  require 'xsd/datatypes'
5
5
  require 'xsd/ns'
6
+ require 'singleton'
6
7
 
7
8
  module ActionService
8
9
  module Protocol
@@ -28,78 +29,91 @@ module ActionService
28
29
  end
29
30
 
30
31
  class SoapProtocol < AbstractProtocol
31
- def unmarshal_request(request_info, export_info, strict=true)
32
- expects = export_info[:expects]
33
- unmarshal_soap_message = lambda do
34
- envelope = SOAP::Processor.unmarshal(request_info.raw_body)
32
+ def self.create_protocol_request(container_class, action_pack_request)
33
+ soap_action = extract_soap_action(action_pack_request)
34
+ return nil unless soap_action
35
+ service_name = action_pack_request.parameters['action']
36
+ public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1]
37
+ content_type = action_pack_request.env['HTTP_CONTENT_TYPE']
38
+ content_type ||= 'text/xml'
39
+ protocol = SoapProtocol.new(container_class)
40
+ ProtocolRequest.new(protocol,
41
+ action_pack_request.raw_post,
42
+ service_name.to_sym,
43
+ public_method_name,
44
+ content_type)
45
+ end
46
+
47
+ def unmarshal_request(protocol_request)
48
+ unmarshal = lambda do
49
+ envelope = SOAP::Processor.unmarshal(protocol_request.raw_body)
35
50
  request = envelope.body.request
36
- params = request.collect{|k, v| request[k]}
37
- soap_to_ruby_array(params)
38
- end if expects || !strict
39
- if expects
40
- map_types(expects)
41
- params = unmarshal_soap_message.call
42
- validate_types(request_info.public_method_name, expects, params, :in) if expects
43
- params
51
+ values = request.collect{|k, v| request[k]}
52
+ soap_to_ruby_array(values)
53
+ end
54
+ signature = protocol_request.signature
55
+ if signature
56
+ map_signature_types(signature)
57
+ values = unmarshal.call
58
+ signature = signature.map{|x|mapper.lookup(x).ruby_klass}
59
+ protocol_request.check_parameter_types(values, signature)
60
+ values
44
61
  else
45
- if strict
62
+ if protocol_request.checked?
46
63
  []
47
64
  else
48
- unmarshal_soap_message.call
65
+ unmarshal.call
49
66
  end
50
67
  end
51
68
  end
52
69
 
53
- def marshal_response(request_info, export_info, return_value, strict=true)
54
- returns = export_info[:returns]
55
- create_param_def = lambda do |ret|
56
- map_types(ret)
57
- mapping = mapper.lookup(ret[0])
58
- ret = [mapping.ruby_klass]
59
- retval = fixup_array_types(mapping, return_value)
60
- validate_types(request_info.public_method_name, ret, [retval], :out)
61
- retval = ruby_to_soap(retval)
70
+ def marshal_response(protocol_request, return_value)
71
+ marshal = lambda do |signature|
72
+ mapping = mapper.lookup(signature[0])
73
+ return_value = fixup_array_types(mapping, return_value)
74
+ signature = signature.map{|x|mapper.lookup(x).ruby_klass}
75
+ protocol_request.check_parameter_types([return_value], signature)
62
76
  param_def = [['retval', 'return', mapping.registry_mapping]]
63
- [param_def, ret, retval]
64
- end if returns || !strict
65
- if returns
66
- param_def, returns, return_value = create_param_def.call(returns)
77
+ [param_def, ruby_to_soap(return_value)]
78
+ end
79
+ signature = protocol_request.return_signature
80
+ param_def = nil
81
+ if signature
82
+ param_def, return_value = marshal.call(signature)
67
83
  else
68
- if strict
69
- param_def = []
84
+ if protocol_request.checked?
85
+ param_def, return_value = nil, nil
70
86
  else
71
- param_def, returns, return_value = create_param_def.call([return_value.class])
87
+ param_def, return_value = marshal.call([return_value.class])
72
88
  end
73
89
  end
74
- qname = XSD::QName.new(mapper.custom_namespace, request_info.public_method_name)
90
+ qname = XSD::QName.new(mapper.custom_namespace,
91
+ protocol_request.public_method_name)
75
92
  response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
76
- response.retval = return_value if returns
77
- ServiceResponseInfo.new(create_response(response), 'text/xml')
78
- end
79
-
80
- def marshal_exception(exception)
81
- ServiceResponseInfo.new(create_exception_response(exception), 'text/xml')
93
+ response.retval = return_value unless return_value.nil?
94
+ ProtocolResponse.new(self, create_response(response), 'text/xml')
82
95
  end
83
96
 
84
- def request_info
85
- @request_info
86
- end
87
-
88
- def request_supported?(request)
89
- soap_action = extract_soap_action(request)
90
- return false unless soap_action
91
- service_name = request.parameters['action']
92
- public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1]
93
- @request_info = ServiceRequestInfo.new(service_name,
94
- public_method_name,
95
- request.raw_post,
96
- request.env['HTTP_CONTENT_TYPE'] || 'text/xml')
97
- true
97
+ def marshal_exception(exc)
98
+ ProtocolResponse.new(self, create_exception_response(exc), 'text/xml')
98
99
  end
99
100
 
100
101
  private
102
+ def self.extract_soap_action(request)
103
+ return nil unless request.method == :post
104
+ content_type = request.env['HTTP_CONTENT_TYPE'] || 'text/xml'
105
+ return nil unless content_type
106
+ soap_action = request.env['HTTP_SOAPACTION']
107
+ return nil unless soap_action
108
+ soap_action.gsub!(/^"/, '')
109
+ soap_action.gsub!(/"$/, '')
110
+ soap_action.strip!
111
+ return nil if soap_action.empty?
112
+ soap_action
113
+ end
114
+
101
115
  def mapper
102
- container_klass.soap_mapper
116
+ container_class.soap_mapper
103
117
  end
104
118
 
105
119
  def fixup_array_types(mapping, obj)
@@ -113,33 +127,32 @@ module ActionService
113
127
  if mapping.is_a?(SoapArrayMapping)
114
128
  obj = mapping.ruby_klass.new(obj)
115
129
  # man, this is going to be slow for big arrays :(
116
- obj.each{|el| fixup_array_types(mapping.element_mapping, el)}
130
+ (1..obj.size).each do |i|
131
+ i -= 1
132
+ obj[i] = fixup_array_types(mapping.element_mapping, obj[i])
133
+ end
134
+ else
135
+ if !mapping.generated_klass.nil? && mapping.generated_klass.respond_to?(:members)
136
+ # have to map the publically visible structure of the class
137
+ new_obj = mapping.generated_klass.new
138
+ mapping.generated_klass.members.each do |name, klass|
139
+ new_obj.send("#{name}=", obj.send(name))
140
+ end
141
+ obj = new_obj
142
+ end
117
143
  end
118
144
  obj
119
145
  end
120
146
 
121
- def extract_soap_action(request)
122
- return nil unless request.method == :post
123
- content_type = request.env['HTTP_CONTENT_TYPE'] || 'text/xml'
124
- return nil unless content_type
125
- soap_action = request.env['HTTP_SOAPACTION']
126
- return nil unless soap_action
127
- soap_action.gsub!(/^"/, '')
128
- soap_action.gsub!(/"$/, '')
129
- soap_action.strip!
130
- return nil if soap_action.empty?
131
- soap_action
132
- end
133
-
134
- def map_types(types)
147
+ def map_signature_types(types)
135
148
  types.collect{|type| mapper.map(type)}
136
149
  end
137
150
 
138
- def create_response(body, is_error=false)
151
+ def create_response(body)
139
152
  header = SOAP::SOAPHeader.new
140
153
  body = SOAP::SOAPBody.new(body)
141
154
  envelope = SOAP::SOAPEnvelope.new(header, body)
142
- message = SOAP::Processor.marshal(envelope)
155
+ SOAP::Processor.marshal(envelope)
143
156
  end
144
157
 
145
158
  def create_exception_response(exc)
@@ -149,7 +162,7 @@ module ActionService
149
162
  SOAP::SOAPString.new(exc.to_s),
150
163
  SOAP::SOAPString.new(self.class.name),
151
164
  SOAP::Mapping.obj2soap(detail))
152
- create_response(body, true)
165
+ create_response(body)
153
166
  end
154
167
 
155
168
  def ruby_to_soap(obj)
@@ -175,15 +188,23 @@ module ActionService
175
188
  @registry = SOAP::Mapping::Registry.new
176
189
  @klass2map = {}
177
190
  @custom_types = {}
191
+ @ar2klass = {}
178
192
  end
179
193
 
180
194
  def lookup(klass)
195
+ lookup_klass = klass.is_a?(Array) ? klass[0] : klass
196
+ generated_klass = nil
197
+ if lookup_klass.ancestors.include?(ActiveRecord::Base)
198
+ generated_klass = @ar2klass.has_key?(klass) ? @ar2klass[klass] : nil
199
+ klass = generated_klass if generated_klass
200
+ end
181
201
  return @klass2map[klass] if @klass2map.has_key?(klass)
182
202
 
183
203
  custom_type = false
184
204
 
185
- ruby_klass = select_class(klass.is_a?(Array) ? klass[0] : klass)
186
- type_name = Inflector.camelize(ruby_klass.name.split(/::/)[-1])
205
+ ruby_klass = select_class(lookup_klass)
206
+ generated_klass = @ar2klass[lookup_klass] if @ar2klass.has_key?(lookup_klass)
207
+ type_name = ruby_klass.name
187
208
 
188
209
  # Array signatures generate a double-mapping and require generation
189
210
  # of an Array subclass to represent the mapping in the SOAP
@@ -230,6 +251,7 @@ module ActionService
230
251
  @klass2map[ruby_klass] = SoapMapping.new(self,
231
252
  type_name,
232
253
  ruby_klass,
254
+ generated_klass,
233
255
  mapping[0],
234
256
  mapping,
235
257
  custom_type)
@@ -245,6 +267,7 @@ module ActionService
245
267
  @klass2map[klass] = SoapMapping.new(self,
246
268
  type_name,
247
269
  ruby_klass,
270
+ generated_klass,
248
271
  mapping[0],
249
272
  mapping,
250
273
  custom_type)
@@ -256,11 +279,38 @@ module ActionService
256
279
  alias :map :lookup
257
280
 
258
281
  def map_container_services(container, &block)
259
- container.class.services.each do |service_name, service_info|
260
- object = container.service_object(service_name)
261
- service_klass = object.class
282
+ dispatching_mode = container.service_dispatching_mode
283
+
284
+ services = nil
285
+ case dispatching_mode
286
+ when :direct
287
+ service_name = Inflector.underscore(container.class.name.gsub(/Controller$/, ''))
288
+ services = {
289
+ service_name => {
290
+ :info => nil,
291
+ :class => container.class,
292
+ :object => container,
293
+ }
294
+ }
295
+ when :delegated
296
+ services = {}
297
+ container.class.services.each do |service_name, service_info|
298
+ begin
299
+ object = container.service_object(service_name)
300
+ rescue Exception => e
301
+ raise(ProtocolError, "failed to retrieve service object for mapping: #{e.message}")
302
+ end
303
+ services[service_name] = {
304
+ :info => service_info,
305
+ :class => object.class,
306
+ :object => object,
307
+ }
308
+ end
309
+ end
310
+
311
+ services.each do |service_name, service|
262
312
  service_exports = {}
263
- object.class.exports.each do |export_name, export_info|
313
+ service[:class].exports.each do |export_name, export_info|
264
314
  expects = export_info[:expects]
265
315
  lookup_proc = lambda do |klass|
266
316
  mapping = lookup(klass)
@@ -279,7 +329,17 @@ module ActionService
279
329
  end
280
330
  mapping
281
331
  end
282
- expects_signature = expects ? expects.map{|klass| lookup_proc.call(klass)} : nil
332
+ expects_signature = nil
333
+ if expects
334
+ expects_signature = []
335
+ expects.each do |klass|
336
+ if klass.is_a?(Hash)
337
+ expects_signature << {klass.keys.shift => lookup_proc.call(klass.values.shift)}
338
+ else
339
+ expects_signature << lookup_proc.call(klass)
340
+ end
341
+ end
342
+ end
283
343
  returns = export_info[:returns]
284
344
  returns_signature = returns ? returns.map{|klass| lookup_proc.call(klass)} : nil
285
345
  service_exports[export_name] = {
@@ -287,13 +347,27 @@ module ActionService
287
347
  :returns => returns_signature
288
348
  }
289
349
  end
290
- yield service_name, service_klass, service_exports if block_given?
350
+ yield service_name, service[:class], service_exports if block_given?
291
351
  end
292
352
  end
293
353
 
294
354
  private
295
355
  def select_class(klass)
296
356
  return Integer if klass == Fixnum
357
+ if klass.ancestors.include?(ActiveRecord::Base)
358
+ new_klass = Class.new(ActionService::Struct)
359
+ new_klass.class_eval <<-EOS
360
+ def self.name
361
+ "#{klass.name}"
362
+ end
363
+ EOS
364
+ klass.columns.each do |column|
365
+ next if column.klass.nil?
366
+ new_klass.send(:member, column.name.to_sym, column.klass)
367
+ end
368
+ @ar2klass[klass] = new_klass
369
+ return new_klass
370
+ end
297
371
  klass
298
372
  end
299
373
 
@@ -311,14 +385,16 @@ module ActionService
311
385
 
312
386
  class SoapMapping
313
387
  attr :ruby_klass
388
+ attr :generated_klass
314
389
  attr :soap_klass
315
390
  attr :registry_mapping
316
391
 
317
- def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping,
392
+ def initialize(mapper, type_name, ruby_klass, generated_klass, soap_klass, registry_mapping,
318
393
  custom_type=false)
319
394
  @mapper = mapper
320
395
  @type_name = type_name
321
396
  @ruby_klass = ruby_klass
397
+ @generated_klass = generated_klass
322
398
  @soap_klass = soap_klass
323
399
  @registry_mapping = registry_mapping
324
400
  @custom_type = custom_type
@@ -368,7 +444,7 @@ module ActionService
368
444
  attr :element_mapping
369
445
 
370
446
  def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping, element_mapping)
371
- super(mapper, type_name, ruby_klass, soap_klass, registry_mapping, true)
447
+ super(mapper, type_name, ruby_klass, nil, soap_klass, registry_mapping, true)
372
448
  @element_mapping = element_mapping
373
449
  end
374
450