actionservice 0.2.100 → 0.2.102

Sign up to get free protection for your applications and to get access to all the features.
@@ -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