actionwebservice 0.6.2 → 0.7.0

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.
Files changed (58) hide show
  1. data/CHANGELOG +21 -0
  2. data/README +50 -6
  3. data/Rakefile +9 -9
  4. data/TODO +6 -6
  5. data/lib/action_web_service.rb +4 -3
  6. data/lib/action_web_service/api.rb +248 -1
  7. data/lib/action_web_service/casting.rb +111 -0
  8. data/lib/action_web_service/client/soap_client.rb +17 -33
  9. data/lib/action_web_service/client/xmlrpc_client.rb +10 -34
  10. data/lib/action_web_service/container/delegated_container.rb +1 -1
  11. data/lib/action_web_service/dispatcher/abstract.rb +52 -72
  12. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +71 -55
  13. data/lib/action_web_service/protocol/abstract.rb +82 -3
  14. data/lib/action_web_service/protocol/discovery.rb +2 -2
  15. data/lib/action_web_service/protocol/soap_protocol.rb +95 -22
  16. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +197 -0
  17. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +56 -24
  18. data/lib/action_web_service/scaffolding.rb +237 -0
  19. data/lib/action_web_service/struct.rb +17 -4
  20. data/lib/action_web_service/support/signature_types.rb +194 -0
  21. data/lib/action_web_service/templates/scaffolds/layout.rhtml +65 -0
  22. data/lib/action_web_service/templates/scaffolds/methods.rhtml +6 -0
  23. data/lib/action_web_service/templates/scaffolds/parameters.rhtml +28 -0
  24. data/lib/action_web_service/templates/scaffolds/result.rhtml +30 -0
  25. data/lib/action_web_service/test_invoke.rb +23 -42
  26. data/test/abstract_dispatcher.rb +102 -48
  27. data/test/abstract_unit.rb +1 -1
  28. data/test/api_test.rb +40 -7
  29. data/test/casting_test.rb +82 -0
  30. data/test/client_soap_test.rb +3 -0
  31. data/test/client_xmlrpc_test.rb +5 -1
  32. data/test/dispatcher_action_controller_soap_test.rb +9 -12
  33. data/test/dispatcher_action_controller_xmlrpc_test.rb +1 -11
  34. data/test/run +1 -0
  35. data/test/scaffolded_controller_test.rb +67 -0
  36. data/test/struct_test.rb +33 -21
  37. data/test/test_invoke_test.rb +1 -1
  38. metadata +18 -31
  39. data/lib/action_web_service/api/base.rb +0 -135
  40. data/lib/action_web_service/vendor/ws.rb +0 -4
  41. data/lib/action_web_service/vendor/ws/common.rb +0 -8
  42. data/lib/action_web_service/vendor/ws/encoding.rb +0 -3
  43. data/lib/action_web_service/vendor/ws/encoding/abstract.rb +0 -26
  44. data/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +0 -90
  45. data/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +0 -53
  46. data/lib/action_web_service/vendor/ws/marshaling.rb +0 -3
  47. data/lib/action_web_service/vendor/ws/marshaling/abstract.rb +0 -17
  48. data/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +0 -277
  49. data/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +0 -116
  50. data/lib/action_web_service/vendor/ws/types.rb +0 -165
  51. data/test/ws/abstract_encoding.rb +0 -68
  52. data/test/ws/abstract_unit.rb +0 -13
  53. data/test/ws/gencov +0 -3
  54. data/test/ws/run +0 -5
  55. data/test/ws/soap_marshaling_test.rb +0 -91
  56. data/test/ws/soap_rpc_encoding_test.rb +0 -47
  57. data/test/ws/types_test.rb +0 -43
  58. data/test/ws/xmlrpc_encoding_test.rb +0 -34
@@ -3,27 +3,106 @@ module ActionWebService # :nodoc:
3
3
  class ProtocolError < ActionWebServiceError # :nodoc:
4
4
  end
5
5
 
6
+ class AbstractProtocol # :nodoc:
7
+ def decode_action_pack_request(action_pack_request)
8
+ end
9
+
10
+ def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
11
+ klass = options[:request_class] || SimpleActionPackRequest
12
+ request = klass.new
13
+ request.request_parameters['action'] = service_name.to_s
14
+ request.env['RAW_POST_DATA'] = raw_body
15
+ request.env['REQUEST_METHOD'] = 'POST'
16
+ request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
17
+ request
18
+ end
19
+
20
+ def decode_request(raw_request, service_name, protocol_options=nil)
21
+ end
22
+
23
+ def encode_request(method_name, params, param_types)
24
+ end
25
+
26
+ def decode_response(raw_response)
27
+ end
28
+
29
+ def encode_response(method_name, return_value, return_type)
30
+ end
31
+
32
+ def protocol_client(api, protocol_name, endpoint_uri, options)
33
+ end
34
+
35
+ def register_api(api)
36
+ end
37
+ end
38
+
6
39
  class Request # :nodoc:
7
40
  attr :protocol
8
41
  attr :method_name
9
- attr :method_params
42
+ attr_accessor :method_params
10
43
  attr :service_name
44
+ attr_accessor :api
45
+ attr_accessor :api_method
46
+ attr :protocol_options
11
47
 
12
- def initialize(protocol, method_name, method_params, service_name)
48
+ def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil, protocol_options=nil)
13
49
  @protocol = protocol
14
50
  @method_name = method_name
15
51
  @method_params = method_params
16
52
  @service_name = service_name
53
+ @api = api
54
+ @api_method = api_method
55
+ @protocol_options = protocol_options || {}
17
56
  end
18
57
  end
19
58
 
20
59
  class Response # :nodoc:
21
60
  attr :body
22
61
  attr :content_type
62
+ attr :return_value
23
63
 
24
- def initialize(body, content_type)
64
+ def initialize(body, content_type, return_value)
25
65
  @body = body
26
66
  @content_type = content_type
67
+ @return_value = return_value
68
+ end
69
+ end
70
+
71
+ class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc:
72
+ def initialize
73
+ @env = {}
74
+ @qparams = {}
75
+ @rparams = {}
76
+ @cookies = {}
77
+ reset_session
78
+ end
79
+
80
+ def query_parameters
81
+ @qparams
82
+ end
83
+
84
+ def request_parameters
85
+ @rparams
86
+ end
87
+
88
+ def env
89
+ @env
90
+ end
91
+
92
+ def host
93
+ ''
94
+ end
95
+
96
+ def cookies
97
+ @cookies
98
+ end
99
+
100
+ def session
101
+ @session
102
+ end
103
+
104
+ def reset_session
105
+ @session = {}
27
106
  end
28
107
  end
29
108
  end
@@ -14,10 +14,10 @@ module ActionWebService # :nodoc:
14
14
 
15
15
  module InstanceMethods # :nodoc:
16
16
  private
17
- def discover_web_service_request(ap_request)
17
+ def discover_web_service_request(action_pack_request)
18
18
  (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
19
19
  protocol = protocol.new
20
- request = protocol.unmarshal_request(ap_request)
20
+ request = protocol.decode_action_pack_request(action_pack_request)
21
21
  return request unless request.nil?
22
22
  end
23
23
  nil
@@ -1,3 +1,5 @@
1
+ require 'action_web_service/protocol/soap_protocol/marshaler'
2
+
1
3
  module ActionWebService # :nodoc:
2
4
  module Protocol # :nodoc:
3
5
  module Soap # :nodoc:
@@ -6,41 +8,106 @@ module ActionWebService # :nodoc:
6
8
  base.class_inheritable_option(:wsdl_service_name)
7
9
  end
8
10
 
9
- class SoapProtocol # :nodoc:
10
- def initialize
11
- @encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
12
- @marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
11
+ class SoapProtocol < AbstractProtocol # :nodoc:
12
+ def marshaler
13
+ @marshaler ||= SoapMarshaler.new
14
+ end
15
+
16
+ def decode_action_pack_request(action_pack_request)
17
+ return nil unless soap_action = has_valid_soap_action?(action_pack_request)
18
+ service_name = action_pack_request.parameters['action']
19
+ protocol_options = { :soap_action => soap_action }
20
+ decode_request(action_pack_request.raw_post, service_name, protocol_options)
21
+ end
22
+
23
+ def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
24
+ request = super
25
+ request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
26
+ request
13
27
  end
14
28
 
15
- def unmarshal_request(ap_request)
16
- return nil unless has_valid_soap_action?(ap_request)
17
- method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
18
- params = params.map{|x| @marshaler.unmarshal(x)}
19
- service_name = ap_request.parameters['action']
20
- Request.new(self, method_name, params, service_name)
29
+ def decode_request(raw_request, service_name, protocol_options=nil)
30
+ envelope = SOAP::Processor.unmarshal(raw_request)
31
+ unless envelope
32
+ raise ProtocolError, "Failed to parse SOAP request message"
33
+ end
34
+ request = envelope.body.request
35
+ method_name = request.elename.name
36
+ params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) }
37
+ Request.new(self, method_name, params, service_name, nil, nil, protocol_options)
21
38
  end
22
39
 
23
- def marshal_response(method_name, return_value, signature_type)
24
- if !return_value.nil? && signature_type
25
- type_binding = @marshaler.register_type(signature_type)
26
- info = WS::ParamInfo.create(signature_type, type_binding, 0)
27
- return_value = @marshaler.marshal(WS::Param.new(return_value, info))
40
+ def encode_request(method_name, params, param_types)
41
+ param_types.each{ |type| marshaler.register_type(type) } if param_types
42
+ qname = XSD::QName.new(marshaler.type_namespace, method_name)
43
+ param_def = []
44
+ if param_types
45
+ params = param_types.zip(params).map do |type, param|
46
+ param_def << ['in', type.name, marshaler.lookup_type(type).mapping]
47
+ [type.name, marshaler.ruby_to_soap(param)]
48
+ end
28
49
  else
29
- return_value = nil
50
+ params = []
30
51
  end
31
- body = @encoder.encode_rpc_response(method_name + 'Response', return_value)
32
- Response.new(body, 'text/xml')
52
+ request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
53
+ request.set_param(params)
54
+ envelope = create_soap_envelope(request)
55
+ SOAP::Processor.marshal(envelope)
33
56
  end
34
57
 
35
- def register_signature_type(spec)
36
- @marshaler.register_type(spec)
58
+ def decode_response(raw_response)
59
+ envelope = SOAP::Processor.unmarshal(raw_response)
60
+ unless envelope
61
+ raise ProtocolError, "Failed to parse SOAP request message"
62
+ end
63
+ method_name = envelope.body.request.elename.name
64
+ return_value = envelope.body.response
65
+ return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil?
66
+ [method_name, return_value]
37
67
  end
38
68
 
39
- def protocol_client(api, protocol_name, endpoint_uri, options)
69
+ def encode_response(method_name, return_value, return_type)
70
+ if return_type
71
+ return_binding = marshaler.register_type(return_type)
72
+ marshaler.annotate_arrays(return_binding, return_value)
73
+ end
74
+ qname = XSD::QName.new(marshaler.type_namespace, method_name)
75
+ if return_value.nil?
76
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
77
+ else
78
+ if return_value.is_a?(Exception)
79
+ detail = SOAP::Mapping::SOAPException.new(return_value)
80
+ response = SOAP::SOAPFault.new(
81
+ SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']),
82
+ SOAP::SOAPString.new(return_value.to_s),
83
+ SOAP::SOAPString.new(self.class.name),
84
+ marshaler.ruby_to_soap(detail))
85
+ else
86
+ if return_type
87
+ param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]]
88
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
89
+ response.retval = marshaler.ruby_to_soap(return_value)
90
+ else
91
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
92
+ end
93
+ end
94
+ end
95
+ envelope = create_soap_envelope(response)
96
+ Response.new(SOAP::Processor.marshal(envelope), 'text/xml', return_value)
97
+ end
98
+
99
+ def protocol_client(api, protocol_name, endpoint_uri, options={})
40
100
  return nil unless protocol_name == :soap
41
101
  ActionWebService::Client::Soap.new(api, endpoint_uri, options)
42
102
  end
43
103
 
104
+ def register_api(api)
105
+ api.api_methods.each do |name, method|
106
+ method.expects.each{ |type| marshaler.register_type(type) } if method.expects
107
+ method.returns.each{ |type| marshaler.register_type(type) } if method.returns
108
+ end
109
+ end
110
+
44
111
  private
45
112
  def has_valid_soap_action?(request)
46
113
  return nil unless request.method == :post
@@ -53,7 +120,13 @@ module ActionWebService # :nodoc:
53
120
  return nil if soap_action.empty?
54
121
  soap_action
55
122
  end
56
- end
123
+
124
+ def create_soap_envelope(body)
125
+ header = SOAP::SOAPHeader.new
126
+ body = SOAP::SOAPBody.new(body)
127
+ SOAP::SOAPEnvelope.new(header, body)
128
+ end
129
+ end
57
130
  end
58
131
  end
59
132
  end
@@ -0,0 +1,197 @@
1
+ require 'soap/mapping'
2
+
3
+ module ActionWebService
4
+ module Protocol
5
+ module Soap
6
+ class SoapMarshaler
7
+ attr :type_namespace
8
+ attr :registry
9
+
10
+ def initialize(type_namespace=nil)
11
+ @type_namespace = type_namespace || 'urn:ActionWebService'
12
+ @registry = SOAP::Mapping::Registry.new
13
+ @type2binding = {}
14
+ end
15
+
16
+ def soap_to_ruby(obj)
17
+ SOAP::Mapping.soap2obj(obj, @registry)
18
+ end
19
+
20
+ def ruby_to_soap(obj)
21
+ SOAP::Mapping.obj2soap(obj, @registry)
22
+ end
23
+
24
+ def register_type(type)
25
+ return @type2binding[type] if @type2binding.has_key?(type)
26
+
27
+ type_class = type.array?? type.element_type.type_class : type.type_class
28
+ type_type = type.array?? type.element_type : type
29
+ type_binding = nil
30
+ if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil)
31
+ qname = mapping[2] ? mapping[2][:type] : nil
32
+ qname ||= soap_base_type_name(mapping[0])
33
+ type_binding = SoapBinding.new(self, qname, type_type, mapping)
34
+ else
35
+ qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
36
+ @registry.add(type_class,
37
+ SOAP::SOAPStruct,
38
+ typed_struct_factory(type_class),
39
+ { :type => qname })
40
+ mapping = @registry.find_mapped_soap_class(type_class)
41
+ type_binding = SoapBinding.new(self, qname, type_type, mapping)
42
+ end
43
+
44
+ array_binding = nil
45
+ if type.array?
46
+ array_mapping = @registry.find_mapped_soap_class(Array) rescue nil
47
+ if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil?
48
+ @registry.set(Array,
49
+ SOAP::SOAPArray,
50
+ SoapTypedArrayFactory.new)
51
+ array_mapping = @registry.find_mapped_soap_class(Array)
52
+ end
53
+ qname = XSD::QName.new(@type_namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
54
+ array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding)
55
+ end
56
+
57
+ @type2binding[type] = array_binding ? array_binding : type_binding
58
+ @type2binding[type]
59
+ end
60
+ alias :lookup_type :register_type
61
+
62
+ def annotate_arrays(binding, value)
63
+ if binding.type.array?
64
+ mark_typed_array(value, binding.element_binding.qname)
65
+ if binding.element_binding.type.custom?
66
+ value.each do |element|
67
+ annotate_arrays(binding.element_binding, element)
68
+ end
69
+ end
70
+ elsif binding.type.structured?
71
+ binding.type.each_member do |name, type|
72
+ member_binding = register_type(type)
73
+ member_value = value.respond_to?('[]') ? value[name] : value.send(name)
74
+ annotate_arrays(member_binding, member_value) if type.custom?
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+ def typed_struct_factory(type_class)
81
+ if Object.const_defined?('ActiveRecord')
82
+ if type_class.ancestors.include?(ActiveRecord::Base)
83
+ qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
84
+ type_class.instance_variable_set('@qname', qname)
85
+ return SoapActiveRecordStructFactory.new
86
+ end
87
+ end
88
+ SOAP::Mapping::Registry::TypedStructFactory
89
+ end
90
+
91
+ def mark_typed_array(array, qname)
92
+ (class << array; self; end).class_eval do
93
+ define_method(:arytype) do
94
+ qname
95
+ end
96
+ end
97
+ end
98
+
99
+ def soap_base_type_name(type)
100
+ xsd_type = type.ancestors.find{ |c| c.const_defined? 'Type' }
101
+ xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
102
+ end
103
+
104
+ def soap_type_name(type_name)
105
+ type_name.gsub(/::/, '..')
106
+ end
107
+ end
108
+
109
+ class SoapBinding
110
+ attr :qname
111
+ attr :type
112
+ attr :mapping
113
+ attr :element_binding
114
+
115
+ def initialize(marshaler, qname, type, mapping, element_binding=nil)
116
+ @marshaler = marshaler
117
+ @qname = qname
118
+ @type = type
119
+ @mapping = mapping
120
+ @element_binding = element_binding
121
+ end
122
+
123
+ def type_name
124
+ @type.custom? ? @qname.name : nil
125
+ end
126
+
127
+ def qualified_type_name(ns=nil)
128
+ if @type.custom?
129
+ "#{ns ? ns : @qname.namespace}:#{@qname.name}"
130
+ else
131
+ ns = XSD::NS.new
132
+ ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
133
+ xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
134
+ return ns.name(XSD::AnyTypeName) unless xsd_klass
135
+ ns.name(xsd_klass.const_get('Type'))
136
+ end
137
+ end
138
+
139
+ def eql?(other)
140
+ @qname == other.qname
141
+ end
142
+ alias :== :eql?
143
+
144
+ def hash
145
+ @qname.hash
146
+ end
147
+ end
148
+
149
+ class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
150
+ def obj2soap(soap_class, obj, info, map)
151
+ unless obj.is_a?(ActiveRecord::Base)
152
+ return nil
153
+ end
154
+ soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
155
+ obj.class.columns.each do |column|
156
+ key = column.name.to_s
157
+ value = obj.send(key)
158
+ soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
159
+ end
160
+ soap_obj
161
+ end
162
+
163
+ def soap2obj(obj_class, node, info, map)
164
+ unless node.type == obj_class.instance_variable_get('@qname')
165
+ return false
166
+ end
167
+ obj = obj_class.new
168
+ node.each do |key, value|
169
+ obj[key] = value.data
170
+ end
171
+ obj.instance_variable_set('@new_record', false)
172
+ return true, obj
173
+ end
174
+ end
175
+
176
+ class SoapTypedArrayFactory < SOAP::Mapping::Factory
177
+ def obj2soap(soap_class, obj, info, map)
178
+ unless obj.respond_to?(:arytype)
179
+ return nil
180
+ end
181
+ soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
182
+ mark_marshalled_obj(obj, soap_obj)
183
+ obj.each do |item|
184
+ child = SOAP::Mapping._obj2soap(item, map)
185
+ soap_obj.add(child)
186
+ end
187
+ soap_obj
188
+ end
189
+
190
+ def soap2obj(obj_class, node, info, map)
191
+ return false
192
+ end
193
+ end
194
+
195
+ end
196
+ end
197
+ end