actionwebservice 0.5.0 → 0.6.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.
- data/{ChangeLog → CHANGELOG} +20 -0
- data/README +45 -1
- data/Rakefile +12 -10
- data/TODO +8 -9
- data/lib/action_web_service.rb +10 -6
- data/lib/action_web_service/api.rb +1 -2
- data/lib/action_web_service/api/{abstract.rb → base.rb} +14 -71
- data/lib/action_web_service/base.rb +0 -3
- data/lib/action_web_service/client/base.rb +1 -12
- data/lib/action_web_service/client/soap_client.rb +49 -17
- data/lib/action_web_service/client/xmlrpc_client.rb +20 -15
- data/lib/action_web_service/container.rb +3 -85
- data/lib/action_web_service/{api/action_controller.rb → container/action_controller_container.rb} +2 -2
- data/lib/action_web_service/container/delegated_container.rb +87 -0
- data/lib/action_web_service/container/direct_container.rb +70 -0
- data/lib/action_web_service/dispatcher/abstract.rb +100 -102
- data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +199 -137
- data/lib/action_web_service/protocol.rb +1 -1
- data/lib/action_web_service/protocol/abstract.rb +14 -112
- data/lib/action_web_service/protocol/discovery.rb +37 -0
- data/lib/action_web_service/protocol/soap_protocol.rb +32 -458
- data/lib/action_web_service/protocol/xmlrpc_protocol.rb +29 -149
- data/lib/action_web_service/struct.rb +2 -5
- data/lib/action_web_service/test_invoke.rb +130 -0
- data/lib/action_web_service/vendor/ws.rb +4 -0
- data/lib/action_web_service/vendor/ws/common.rb +8 -0
- data/lib/action_web_service/vendor/ws/encoding.rb +3 -0
- data/lib/action_web_service/vendor/ws/encoding/abstract.rb +26 -0
- data/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +90 -0
- data/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +53 -0
- data/lib/action_web_service/vendor/ws/marshaling.rb +3 -0
- data/lib/action_web_service/vendor/ws/marshaling/abstract.rb +17 -0
- data/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +277 -0
- data/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +116 -0
- data/lib/action_web_service/vendor/ws/types.rb +162 -0
- data/test/abstract_client.rb +8 -11
- data/test/abstract_dispatcher.rb +370 -0
- data/test/abstract_unit.rb +1 -0
- data/test/api_test.rb +18 -1
- data/test/apis/auto_load_api.rb +3 -0
- data/test/apis/broken_auto_load_api.rb +2 -0
- data/test/client_soap_test.rb +16 -3
- data/test/client_xmlrpc_test.rb +16 -4
- data/test/container_test.rb +28 -8
- data/test/dispatcher_action_controller_soap_test.rb +106 -0
- data/test/dispatcher_action_controller_xmlrpc_test.rb +44 -0
- data/test/gencov +1 -1
- data/test/invocation_test.rb +39 -3
- data/test/run +4 -4
- data/test/test_invoke_test.rb +77 -0
- data/test/ws/abstract_encoding.rb +68 -0
- data/test/ws/abstract_unit.rb +13 -0
- data/test/ws/gencov +3 -0
- data/test/ws/run +5 -0
- data/test/ws/soap_marshaling_test.rb +91 -0
- data/test/ws/soap_rpc_encoding_test.rb +47 -0
- data/test/ws/types_test.rb +41 -0
- data/test/ws/xmlrpc_encoding_test.rb +34 -0
- metadata +48 -19
- data/lib/action_web_service/protocol/registry.rb +0 -55
- data/lib/action_web_service/support/signature.rb +0 -100
- data/test/abstract_soap.rb +0 -58
- data/test/dispatcher_action_controller_test.rb +0 -186
- data/test/protocol_registry_test.rb +0 -53
- data/test/protocol_soap_test.rb +0 -252
- data/test/protocol_xmlrpc_test.rb +0 -147
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'xmlrpc/marshal'
|
2
|
+
|
3
|
+
module WS
|
4
|
+
module Encoding
|
5
|
+
class XmlRpcError < WSError
|
6
|
+
end
|
7
|
+
|
8
|
+
class XmlRpcEncoding < AbstractEncoding
|
9
|
+
def encode_rpc_call(method_name, params)
|
10
|
+
XMLRPC::Marshal.dump_call(method_name, *params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def decode_rpc_call(obj)
|
14
|
+
method_name, params = XMLRPC::Marshal.load_call(obj) rescue nil
|
15
|
+
unless method_name && params
|
16
|
+
raise(XmlRpcError, "Malformed XML-RPC request")
|
17
|
+
end
|
18
|
+
i = 0
|
19
|
+
params = params.map do |value|
|
20
|
+
param = XmlRpcDecodedParam.new("param#{i}", value)
|
21
|
+
i += 1
|
22
|
+
param
|
23
|
+
end
|
24
|
+
[method_name, params]
|
25
|
+
end
|
26
|
+
|
27
|
+
def encode_rpc_response(method_name, return_value)
|
28
|
+
if return_value.nil?
|
29
|
+
XMLRPC::Marshal.dump_response(true)
|
30
|
+
else
|
31
|
+
XMLRPC::Marshal.dump_response(return_value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def decode_rpc_response(obj)
|
36
|
+
return_value = XMLRPC::Marshal.load_response(obj) rescue nil
|
37
|
+
if return_value.nil?
|
38
|
+
raise(XmlRpcError, "Malformed XML-RPC response")
|
39
|
+
end
|
40
|
+
[nil, XmlRpcDecodedParam.new('return', return_value)]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class XmlRpcDecodedParam
|
45
|
+
attr :param
|
46
|
+
|
47
|
+
def initialize(name, value)
|
48
|
+
info = ParamInfo.new(name, value.class)
|
49
|
+
@param = Param.new(value, info)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
require 'soap/mapping'
|
2
|
+
require 'xsd/ns'
|
3
|
+
|
4
|
+
module WS
|
5
|
+
module Marshaling
|
6
|
+
SoapEncodingNS = 'http://schemas.xmlsoap.org/soap/encoding/'
|
7
|
+
|
8
|
+
class SoapError < WSError
|
9
|
+
end
|
10
|
+
|
11
|
+
class SoapMarshaler < AbstractMarshaler
|
12
|
+
attr :registry
|
13
|
+
attr_accessor :type_namespace
|
14
|
+
|
15
|
+
def initialize(type_namespace='')
|
16
|
+
@type_namespace = type_namespace
|
17
|
+
@registry = SOAP::Mapping::Registry.new
|
18
|
+
@spec2binding = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def marshal(param)
|
22
|
+
annotate_arrays(param.info.data, param.value)
|
23
|
+
if param.value.is_a?(Exception)
|
24
|
+
detail = SOAP::Mapping::SOAPException.new(param.value)
|
25
|
+
soap_obj = SOAP::SOAPFault.new(
|
26
|
+
SOAP::SOAPString.new('Server'),
|
27
|
+
SOAP::SOAPString.new(param.value.to_s),
|
28
|
+
SOAP::SOAPString.new(self.class.name),
|
29
|
+
SOAP::Mapping.obj2soap(detail))
|
30
|
+
else
|
31
|
+
soap_obj = SOAP::Mapping.obj2soap(param.value, @registry)
|
32
|
+
end
|
33
|
+
SoapForeignObject.new(param, soap_obj)
|
34
|
+
end
|
35
|
+
|
36
|
+
def unmarshal(obj)
|
37
|
+
param = obj.param
|
38
|
+
soap_object = obj.soap_object
|
39
|
+
soap_type = soap_object ? soap_object.type : nil
|
40
|
+
value = soap_object ? SOAP::Mapping.soap2obj(soap_object, @registry) : nil
|
41
|
+
param.value = value
|
42
|
+
param.info.type = value.class
|
43
|
+
mapping = @registry.find_mapped_soap_class(param.info.type) rescue nil
|
44
|
+
if soap_type && soap_type.name == 'Array' && soap_type.namespace == SoapEncodingNS
|
45
|
+
param.info.data = SoapBinding.new(self, soap_object.arytype, Array, mapping)
|
46
|
+
else
|
47
|
+
param.info.data = SoapBinding.new(self, soap_type, value.class, mapping)
|
48
|
+
end
|
49
|
+
param
|
50
|
+
end
|
51
|
+
|
52
|
+
def register_type(spec)
|
53
|
+
if @spec2binding.has_key?(spec)
|
54
|
+
return @spec2binding[spec]
|
55
|
+
end
|
56
|
+
|
57
|
+
klass = BaseTypes.canonical_param_type_class(spec)
|
58
|
+
if klass.is_a?(Array)
|
59
|
+
type_class = klass[0]
|
60
|
+
else
|
61
|
+
type_class = klass
|
62
|
+
end
|
63
|
+
|
64
|
+
type_binding = nil
|
65
|
+
if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil)
|
66
|
+
qname = mapping[2] ? mapping[2][:type] : nil
|
67
|
+
qname ||= soap_base_type_name(mapping[0])
|
68
|
+
type_binding = SoapBinding.new(self, qname, type_class, mapping)
|
69
|
+
else
|
70
|
+
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
|
71
|
+
@registry.add(type_class,
|
72
|
+
SOAP::SOAPStruct,
|
73
|
+
typed_struct_factory(type_class),
|
74
|
+
{ :type => qname })
|
75
|
+
mapping = @registry.find_mapped_soap_class(type_class)
|
76
|
+
type_binding = SoapBinding.new(self, qname, type_class, mapping)
|
77
|
+
end
|
78
|
+
|
79
|
+
array_binding = nil
|
80
|
+
if klass.is_a?(Array)
|
81
|
+
array_mapping = @registry.find_mapped_soap_class(Array) rescue nil
|
82
|
+
if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil?
|
83
|
+
@registry.set(Array,
|
84
|
+
SOAP::SOAPArray,
|
85
|
+
SoapTypedArrayFactory.new)
|
86
|
+
array_mapping = @registry.find_mapped_soap_class(Array)
|
87
|
+
end
|
88
|
+
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name) + 'Array')
|
89
|
+
array_binding = SoapBinding.new(self, qname, Array, array_mapping, type_binding)
|
90
|
+
end
|
91
|
+
|
92
|
+
@spec2binding[spec] = array_binding ? array_binding : type_binding
|
93
|
+
@spec2binding[spec]
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
def annotate_arrays(binding, value)
|
98
|
+
if binding.is_typed_array?
|
99
|
+
mark_typed_array(value, binding.element_binding.qname)
|
100
|
+
if binding.element_binding.is_custom_type?
|
101
|
+
value.each do |element|
|
102
|
+
annotate_arrays(register_type(element.class), element)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
elsif binding.is_typed_struct?
|
106
|
+
if binding.type_class.respond_to?(:members)
|
107
|
+
binding.type_class.members.each do |name, spec|
|
108
|
+
member_binding = register_type(spec)
|
109
|
+
member_value = value.send(name)
|
110
|
+
if member_binding.is_custom_type?
|
111
|
+
annotate_arrays(member_binding, member_value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def mark_typed_array(array, qname)
|
119
|
+
(class << array; self; end).class_eval do
|
120
|
+
define_method(:arytype) do
|
121
|
+
qname
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def typed_struct_factory(type_class)
|
127
|
+
if Object.const_defined?('ActiveRecord')
|
128
|
+
if WS.derived_from?(ActiveRecord::Base, type_class)
|
129
|
+
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
|
130
|
+
type_class.instance_variable_set('@qname', qname)
|
131
|
+
return SoapActiveRecordStructFactory.new
|
132
|
+
end
|
133
|
+
end
|
134
|
+
SOAP::Mapping::Registry::TypedStructFactory
|
135
|
+
end
|
136
|
+
|
137
|
+
def soap_type_name(type_name)
|
138
|
+
type_name.gsub(/::/, '..')
|
139
|
+
end
|
140
|
+
|
141
|
+
def soap_base_type_name(type)
|
142
|
+
xsd_type = type.ancestors.find{|c| c.const_defined? 'Type'}
|
143
|
+
xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class SoapForeignObject
|
148
|
+
attr_accessor :param
|
149
|
+
attr_accessor :soap_object
|
150
|
+
|
151
|
+
def initialize(param, soap_object)
|
152
|
+
@param = param
|
153
|
+
@soap_object = soap_object
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class SoapBinding
|
158
|
+
attr :qname
|
159
|
+
attr :type_class
|
160
|
+
attr :mapping
|
161
|
+
attr :element_binding
|
162
|
+
|
163
|
+
def initialize(marshaler, qname, type_class, mapping, element_binding=nil)
|
164
|
+
@marshaler = marshaler
|
165
|
+
@qname = qname
|
166
|
+
@type_class = type_class
|
167
|
+
@mapping = mapping
|
168
|
+
@element_binding = element_binding
|
169
|
+
end
|
170
|
+
|
171
|
+
def is_custom_type?
|
172
|
+
is_typed_array? || is_typed_struct?
|
173
|
+
end
|
174
|
+
|
175
|
+
def is_typed_array?
|
176
|
+
@mapping[1].is_a?(WS::Marshaling::SoapTypedArrayFactory)
|
177
|
+
end
|
178
|
+
|
179
|
+
def is_typed_struct?
|
180
|
+
@mapping[1] == SOAP::Mapping::Registry::TypedStructFactory || \
|
181
|
+
@mapping[1].is_a?(WS::Marshaling::SoapActiveRecordStructFactory)
|
182
|
+
end
|
183
|
+
|
184
|
+
def each_member(&block)
|
185
|
+
if is_typed_struct?
|
186
|
+
if @mapping[1] == SOAP::Mapping::Registry::TypedStructFactory
|
187
|
+
if @type_class.respond_to?(:members)
|
188
|
+
@type_class.members.each do |name, spec|
|
189
|
+
yield name, spec
|
190
|
+
end
|
191
|
+
end
|
192
|
+
elsif @mapping[1].is_a?(WS::Marshaling::SoapActiveRecordStructFactory)
|
193
|
+
@type_class.columns.each do |column|
|
194
|
+
yield column.name, column.klass
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def type_name
|
201
|
+
is_custom_type? ? @qname.name : nil
|
202
|
+
end
|
203
|
+
|
204
|
+
def qualified_type_name(ns=nil)
|
205
|
+
if is_custom_type?
|
206
|
+
"#{ns ? ns : @qname.namespace}:#{@qname.name}"
|
207
|
+
else
|
208
|
+
ns = XSD::NS.new
|
209
|
+
ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
|
210
|
+
xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
|
211
|
+
return ns.name(XSD::AnyTypeName) unless xsd_klass
|
212
|
+
ns.name(xsd_klass.const_get('Type'))
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
|
218
|
+
def obj2soap(soap_class, obj, info, map)
|
219
|
+
unless obj.is_a?(ActiveRecord::Base)
|
220
|
+
return nil
|
221
|
+
end
|
222
|
+
soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
|
223
|
+
obj.class.columns.each do |column|
|
224
|
+
key = column.name.to_s
|
225
|
+
value = obj.send(key)
|
226
|
+
soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
|
227
|
+
end
|
228
|
+
soap_obj
|
229
|
+
end
|
230
|
+
|
231
|
+
def soap2obj(obj_class, node, info, map)
|
232
|
+
unless node.type == obj_class.instance_variable_get('@qname')
|
233
|
+
return false
|
234
|
+
end
|
235
|
+
obj = obj_class.new
|
236
|
+
node.each do |key, value|
|
237
|
+
obj[key] = value.data
|
238
|
+
end
|
239
|
+
obj.instance_variable_set('@new_record', false)
|
240
|
+
return true, obj
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
class SoapTypedArrayFactory < SOAP::Mapping::Factory
|
245
|
+
def obj2soap(soap_class, obj, info, map)
|
246
|
+
unless obj.respond_to?(:arytype)
|
247
|
+
return nil
|
248
|
+
end
|
249
|
+
soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
|
250
|
+
mark_marshalled_obj(obj, soap_obj)
|
251
|
+
obj.each do |item|
|
252
|
+
child = SOAP::Mapping._obj2soap(item, map)
|
253
|
+
soap_obj.add(child)
|
254
|
+
end
|
255
|
+
soap_obj
|
256
|
+
end
|
257
|
+
|
258
|
+
def soap2obj(obj_class, node, info, map)
|
259
|
+
return false
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
module ActiveRecordSoapMarshallable
|
264
|
+
def allocate
|
265
|
+
obj = super
|
266
|
+
attrs = {}
|
267
|
+
self.columns.each{|c| attrs[c.name.to_s] = c.default}
|
268
|
+
obj.instance_variable_set('@attributes', attrs)
|
269
|
+
obj
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
if Object.const_defined?('ActiveRecord')
|
274
|
+
ActiveRecord::Base.extend(ActiveRecordSoapMarshallable)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module WS
|
2
|
+
module Marshaling
|
3
|
+
class XmlRpcError < WSError
|
4
|
+
end
|
5
|
+
|
6
|
+
class XmlRpcMarshaler < AbstractMarshaler
|
7
|
+
def initialize
|
8
|
+
@caster = BaseTypeCaster.new
|
9
|
+
@spec2binding = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def marshal(param)
|
13
|
+
transform_outbound(param)
|
14
|
+
end
|
15
|
+
|
16
|
+
def unmarshal(obj)
|
17
|
+
obj.param.value = transform_inbound(obj.param)
|
18
|
+
obj.param
|
19
|
+
end
|
20
|
+
|
21
|
+
def typed_unmarshal(obj, spec)
|
22
|
+
param = obj.param
|
23
|
+
param.info.data = register_type(spec)
|
24
|
+
param.value = transform_inbound(param)
|
25
|
+
param
|
26
|
+
end
|
27
|
+
|
28
|
+
def register_type(spec)
|
29
|
+
if @spec2binding.has_key?(spec)
|
30
|
+
return @spec2binding[spec]
|
31
|
+
end
|
32
|
+
|
33
|
+
klass = BaseTypes.canonical_param_type_class(spec)
|
34
|
+
type_binding = nil
|
35
|
+
if klass.is_a?(Array)
|
36
|
+
type_binding = XmlRpcArrayBinding.new(klass[0])
|
37
|
+
else
|
38
|
+
type_binding = XmlRpcBinding.new(klass)
|
39
|
+
end
|
40
|
+
|
41
|
+
@spec2binding[spec] = type_binding
|
42
|
+
end
|
43
|
+
|
44
|
+
def transform_outbound(param)
|
45
|
+
binding = param.info.data
|
46
|
+
case binding
|
47
|
+
when XmlRpcArrayBinding
|
48
|
+
param.value.map{|x| cast_outbound(x, binding.element_klass)}
|
49
|
+
when XmlRpcBinding
|
50
|
+
cast_outbound(param.value, param.info.type)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def transform_inbound(param)
|
55
|
+
return param.value if param.info.data.nil?
|
56
|
+
binding = param.info.data
|
57
|
+
param.info.type = binding.klass
|
58
|
+
case binding
|
59
|
+
when XmlRpcArrayBinding
|
60
|
+
param.value.map{|x| cast_inbound(x, binding.element_klass)}
|
61
|
+
when XmlRpcBinding
|
62
|
+
cast_inbound(param.value, param.info.type)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def cast_outbound(value, klass)
|
67
|
+
if BaseTypes.base_type?(klass)
|
68
|
+
@caster.cast(value, klass)
|
69
|
+
elsif value.is_a?(Exception)
|
70
|
+
XMLRPC::FaultException.new(2, value.message)
|
71
|
+
elsif Object.const_defined?('ActiveRecord') && value.is_a?(ActiveRecord::Base)
|
72
|
+
value.attributes
|
73
|
+
else
|
74
|
+
struct = {}
|
75
|
+
value.instance_variables.each do |name|
|
76
|
+
key = name.sub(/^@/, '')
|
77
|
+
struct[key] = value.instance_variable_get(name)
|
78
|
+
end
|
79
|
+
struct
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def cast_inbound(value, klass)
|
84
|
+
if BaseTypes.base_type?(klass)
|
85
|
+
value = value.to_time if value.is_a?(XMLRPC::DateTime)
|
86
|
+
@caster.cast(value, klass)
|
87
|
+
elsif value.is_a?(XMLRPC::FaultException)
|
88
|
+
value
|
89
|
+
else
|
90
|
+
obj = klass.new
|
91
|
+
value.each do |name, val|
|
92
|
+
obj.send('%s=' % name.to_s, val)
|
93
|
+
end
|
94
|
+
obj
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class XmlRpcBinding
|
100
|
+
attr :klass
|
101
|
+
|
102
|
+
def initialize(klass)
|
103
|
+
@klass = klass
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class XmlRpcArrayBinding < XmlRpcBinding
|
108
|
+
attr :element_klass
|
109
|
+
|
110
|
+
def initialize(element_klass)
|
111
|
+
super(Array)
|
112
|
+
@element_klass = element_klass
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|