simrpc 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.rdoc +79 -0
- data/Rakefile +46 -0
- data/lib/simrpc/common.rb +46 -0
- data/lib/simrpc/exceptions.rb +30 -0
- data/lib/simrpc/message.rb +156 -0
- data/lib/simrpc/node.rb +206 -0
- data/lib/simrpc/qpid_adapter.rb +171 -0
- data/lib/simrpc/schema.rb +400 -0
- data/lib/simrpc.rb +9 -802
- data/spec/common_spec.rb +26 -0
- data/spec/message_spec.rb +93 -0
- data/spec/node_spec.rb +99 -0
- data/spec/qpid_spec.rb +92 -0
- data/spec/schema_spec.rb +348 -0
- data/spec/spec_helper.rb +56 -0
- metadata +25 -23
- data/README +0 -25
- /data/lib/{semaphore.rb → simrpc/semaphore.rb} +0 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
# simrpc message module
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
6
|
+
# obtaining a copy of this software and associated documentation
|
7
|
+
# files (the "Software"), to deal in the Software without
|
8
|
+
# restriction, including without limitation the rights to use,
|
9
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the
|
11
|
+
# Software is furnished to do so, subject to the following
|
12
|
+
# conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
require 'qpid'
|
27
|
+
require 'socket'
|
28
|
+
require 'semaphore'
|
29
|
+
|
30
|
+
module Simrpc
|
31
|
+
|
32
|
+
# The QpidAdapter module implements the simrpc qpid subsystem, providing
|
33
|
+
# a convenient way to access qpid constructs
|
34
|
+
module QpidAdapter
|
35
|
+
|
36
|
+
# Simrpc::Qpid::Node class, represents an enpoint on a qpid
|
37
|
+
# network which has its own exchange and queue which it listens on
|
38
|
+
class Node
|
39
|
+
private
|
40
|
+
# helper method to generate a random id
|
41
|
+
def gen_uuid
|
42
|
+
["%02x"*4, "%02x"*2, "%02x"*2, "%02x"*2, "%02x"*6].join("-") %
|
43
|
+
Array.new(16) {|x| rand(0xff) }
|
44
|
+
end
|
45
|
+
|
46
|
+
public
|
47
|
+
# a node can have children nodes mapped to by keys
|
48
|
+
attr_accessor :children
|
49
|
+
|
50
|
+
# node always has a node id
|
51
|
+
attr_reader :node_id
|
52
|
+
|
53
|
+
# create the qpid base connection with the specified broker / port
|
54
|
+
# or config file. Then establish exchange and queue and start listening
|
55
|
+
# for requests.
|
56
|
+
#
|
57
|
+
# specify :broker and :port arguments to directly connect to those
|
58
|
+
# specify :config argument to use that yml file
|
59
|
+
# specify MOTEL_AMQP_CONF environment variable to use that yml file
|
60
|
+
# specify :id parameter to set id, else it will be set to a uuid just created
|
61
|
+
def initialize(args = {})
|
62
|
+
# if no id specified generate a new uuid
|
63
|
+
@node_id = args[:id].nil? ? gen_uuid : args[:id]
|
64
|
+
|
65
|
+
# we generate a random session id
|
66
|
+
@session_id = gen_uuid
|
67
|
+
|
68
|
+
# get the broker/port
|
69
|
+
broker = args[:broker].nil? ? "localhost" : args[:broker]
|
70
|
+
port = args[:port].nil? ? 5672 : args[:port]
|
71
|
+
|
72
|
+
if (broker.nil? || port.nil?) && args.has_key?(:config)
|
73
|
+
config =
|
74
|
+
amqpconfig = YAML::load(File.open(args[:config]))
|
75
|
+
broker = amqpconfig["broker"] if broker.nil?
|
76
|
+
port = amqpconfig["port"] if port.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
### create underlying tcp connection
|
80
|
+
@conn = Qpid::Connection.new(TCPSocket.new(broker,port))
|
81
|
+
@conn.start
|
82
|
+
|
83
|
+
### connect to qpid broker
|
84
|
+
@ssn = @conn.session(@session_id)
|
85
|
+
|
86
|
+
@children = {}
|
87
|
+
|
88
|
+
@accept_lock = Semaphore.new(1)
|
89
|
+
|
90
|
+
# qpid constructs that will be created for node
|
91
|
+
@exchange = args[:exchange].nil? ? @node_id.to_s + "-exchange" : args[:exchange]
|
92
|
+
@queue = args[:queue].nil? ? @node_id.to_s + "-queue" : args[:queue]
|
93
|
+
@local_queue = args[:local_queue].nil? ? @node_id.to_s + "-local-queue" : args[:local_queue]
|
94
|
+
@routing_key = @queue
|
95
|
+
|
96
|
+
Logger.warn "creating qpid exchange #{@exchange} queue #{@queue} binding_key #{@routing_key}"
|
97
|
+
|
98
|
+
if @ssn.exchange_query(@exchange).not_found
|
99
|
+
@ssn.exchange_declare(@exchange, :type => "direct")
|
100
|
+
end
|
101
|
+
|
102
|
+
if @ssn.queue_query(@queue).queue.nil?
|
103
|
+
@ssn.queue_declare(@queue)
|
104
|
+
end
|
105
|
+
|
106
|
+
@ssn.exchange_bind(:exchange => @exchange,
|
107
|
+
:queue => @queue,
|
108
|
+
:binding_key => @routing_key)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Instruct Node to start accepting requests asynchronously and immediately return.
|
112
|
+
# handler must be callable and take node, msg, respond_to arguments, corresponding to
|
113
|
+
# 'self', the message received', and the routing_key which to send any response.
|
114
|
+
def async_accept(&handler)
|
115
|
+
# TODO permit a QpidNode to accept messages from multiple exchanges/queues
|
116
|
+
@accept_lock.wait
|
117
|
+
|
118
|
+
# subscribe to the queue
|
119
|
+
@ssn.message_subscribe(:destination => @local_queue,
|
120
|
+
:queue => @queue,
|
121
|
+
:accept_mode => @ssn.message_accept_mode.none)
|
122
|
+
@incoming = @ssn.incoming(@local_queue)
|
123
|
+
@incoming.start
|
124
|
+
|
125
|
+
Logger.warn "listening for messages on #{@queue}"
|
126
|
+
|
127
|
+
# start receiving messages
|
128
|
+
@incoming.listen{ |msg|
|
129
|
+
Logger.info "queue #{@queue} received message #{msg.body.to_s.size} #{msg.body}"
|
130
|
+
reply_to = msg.get(:message_properties).reply_to.routing_key
|
131
|
+
handler.call(self, msg.body, reply_to)
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
# block until accept operation is complete
|
136
|
+
def join
|
137
|
+
@accept_lock.wait
|
138
|
+
end
|
139
|
+
|
140
|
+
# instructs QpidServer to stop accepting, blocking
|
141
|
+
# untill all accepting operations have terminated
|
142
|
+
def terminate
|
143
|
+
Logger.warn "terminating qpid session"
|
144
|
+
unless @incoming.nil?
|
145
|
+
@incoming.stop
|
146
|
+
@incoming.close
|
147
|
+
@accept_lock.signal
|
148
|
+
end
|
149
|
+
@ssn.close
|
150
|
+
# TODO undefine the @queue/@exchange
|
151
|
+
end
|
152
|
+
|
153
|
+
# send a message to the specified routing_key
|
154
|
+
def send_message(routing_key, message)
|
155
|
+
dp = @ssn.delivery_properties(:routing_key => routing_key)
|
156
|
+
mp = @ssn.message_properties( :content_type => "text/plain")
|
157
|
+
rp = @ssn.message_properties( :reply_to =>
|
158
|
+
@ssn.reply_to(@exchange, @routing_key))
|
159
|
+
msg = Qpid::Message.new(dp, mp, rp, message.to_s)
|
160
|
+
|
161
|
+
Logger.warn "sending qpid message #{msg.body} to #{routing_key}"
|
162
|
+
|
163
|
+
# send it
|
164
|
+
@ssn.message_transfer(:message => msg)
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end # module QpidAdapter
|
170
|
+
|
171
|
+
end # module Simrpc
|
@@ -0,0 +1,400 @@
|
|
1
|
+
# simrpc schema module
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
6
|
+
# obtaining a copy of this software and associated documentation
|
7
|
+
# files (the "Software"), to deal in the Software without
|
8
|
+
# restriction, including without limitation the rights to use,
|
9
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the
|
11
|
+
# Software is furnished to do so, subject to the following
|
12
|
+
# conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
require 'rexml/document'
|
27
|
+
|
28
|
+
# FIXME store lengths in binary instead of ascii string
|
29
|
+
|
30
|
+
module Simrpc
|
31
|
+
|
32
|
+
# schema module defines classes / methods to define
|
33
|
+
# a simrpc schema / load it from an xml file
|
34
|
+
module Schema
|
35
|
+
|
36
|
+
Types = [ :str, :int, :float, :bool, :obj, :array ]
|
37
|
+
|
38
|
+
# return true if type is a primitive, else false
|
39
|
+
def is_primitive?(type)
|
40
|
+
return [:str, :int, :float, :bool].include?(type)
|
41
|
+
end
|
42
|
+
module_function :is_primitive?
|
43
|
+
|
44
|
+
# convert primitive type from string
|
45
|
+
def primitive_from_str(type, str)
|
46
|
+
if type == :str
|
47
|
+
return str
|
48
|
+
|
49
|
+
elsif type == :int
|
50
|
+
return str.to_i
|
51
|
+
|
52
|
+
elsif type == :float
|
53
|
+
return str.to_f
|
54
|
+
|
55
|
+
elsif type == :bool
|
56
|
+
return str == "true"
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
module_function :primitive_from_str
|
61
|
+
|
62
|
+
# Data field defintion containing a type, name, and value.
|
63
|
+
# Optinally an associated class or type may be given.
|
64
|
+
# Associated only valid for :obj and :array types, and
|
65
|
+
# must be set to associated ClassDef or Type (:array only)
|
66
|
+
class DataFieldDef
|
67
|
+
attr_accessor :type, :name, :associated
|
68
|
+
|
69
|
+
# indicates this data field should be ignored if it has a null value
|
70
|
+
attr_accessor :ignore_null
|
71
|
+
|
72
|
+
def initialize(args = {})
|
73
|
+
@type = args[:type] unless args[:type].nil?
|
74
|
+
@name = args[:name] unless args[:name].nil?
|
75
|
+
@associated = args[:associated] unless args[:associated].nil?
|
76
|
+
@ignore_null = !args[:ignore_null].nil? && args[:ignore_null]
|
77
|
+
end
|
78
|
+
|
79
|
+
# helper method to lookup and return the specified class name in the
|
80
|
+
# specified schema
|
81
|
+
def find_class_def(cl_name, schema_def)
|
82
|
+
return schema_def.classes.find { |cl| cl.name == cl_name.to_s } unless cl_name.nil? || schema_def.nil?
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# helper method to lookup and return the class definition corresponding to the associated
|
87
|
+
# attribte in the specified schema def
|
88
|
+
def associated_class_def(schema_def)
|
89
|
+
find_class_def(@associated, schema_def) unless @associated.nil?
|
90
|
+
end
|
91
|
+
|
92
|
+
# convert given value of this data field into a string. Provide schema_def
|
93
|
+
# for :obj or :array data fields associated w/ a non-primitive type. converted_classes
|
94
|
+
# is a recursive helper array used/maintained internally
|
95
|
+
def to_s(value, schema_def = nil, converted_classes = [])
|
96
|
+
if value.nil?
|
97
|
+
return ""
|
98
|
+
elsif Schema::is_primitive?(@type)
|
99
|
+
return value.to_s
|
100
|
+
|
101
|
+
elsif type == :array
|
102
|
+
str = "%04d" % value.size
|
103
|
+
value.each { |val|
|
104
|
+
if Schema::is_primitive?(@associated)
|
105
|
+
str += "%04d" % val.to_s.size
|
106
|
+
str += val.to_s
|
107
|
+
else
|
108
|
+
cl_def = associated_class_def(schema_def)
|
109
|
+
unless cl_def.nil?
|
110
|
+
cl_s = cl_def.to_s(val, schema_def, converted_classes)
|
111
|
+
str += "%04d" % cl_s.size
|
112
|
+
str += cl_s
|
113
|
+
end
|
114
|
+
end
|
115
|
+
}
|
116
|
+
return str
|
117
|
+
|
118
|
+
# elsif @type == :map # TODO
|
119
|
+
|
120
|
+
elsif type == :obj
|
121
|
+
cl_name = ""
|
122
|
+
cl_def = associated_class_def(schema_def)
|
123
|
+
|
124
|
+
# if associated class isn't specified, store the class name,
|
125
|
+
# providing for generic object support
|
126
|
+
if cl_def.nil?
|
127
|
+
cl_name = value.class.to_s.demodulize
|
128
|
+
cl_def = find_class_def(cl_name, schema_def)
|
129
|
+
raise InvalidSchemaClass.new("cannot find #{cl_name} in schema") if cl_def.nil?
|
130
|
+
cl_name = "%04d" % cl_name.size + cl_name
|
131
|
+
end
|
132
|
+
|
133
|
+
return cl_name + cl_def.to_s(value, schema_def, converted_classes)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# convert given string representation of this data field into its original value.
|
138
|
+
# Provide schema_def for :obj or :array data fields associated w/ non-primitive types
|
139
|
+
# # coverted_classes is a recursive helper array used/maintained internally
|
140
|
+
def from_s(str, schema_def = nil, converted_classes = [])
|
141
|
+
if str == ""
|
142
|
+
return nil
|
143
|
+
|
144
|
+
elsif Schema::is_primitive?(@type)
|
145
|
+
return Schema::primitive_from_str(@type, str)
|
146
|
+
|
147
|
+
elsif @type == :array
|
148
|
+
res = []
|
149
|
+
cl_def = associated_class_def(schema_def) unless Schema::is_primitive?(@associated)
|
150
|
+
alen = str[0...4].to_i
|
151
|
+
apos = 4
|
152
|
+
(0...alen).each { |i|
|
153
|
+
elen = str[apos...apos+4].to_i
|
154
|
+
parsed = str[apos+4...apos+4+elen]
|
155
|
+
if Schema::is_primitive?(@associated)
|
156
|
+
p = Schema::primitive_from_str(@associated, parsed)
|
157
|
+
res.push p
|
158
|
+
else
|
159
|
+
res.push cl_def.from_s(parsed, schema_def, converted_classes)
|
160
|
+
end
|
161
|
+
apos = apos+4+elen
|
162
|
+
}
|
163
|
+
return res
|
164
|
+
|
165
|
+
# elsif @type == :map # TODO
|
166
|
+
|
167
|
+
elsif @type == :obj
|
168
|
+
cl_def = associated_class_def(schema_def)
|
169
|
+
|
170
|
+
# if associated class isn't specified, parse the class name,
|
171
|
+
# providing for generic object support
|
172
|
+
if cl_def.nil?
|
173
|
+
cnlen = str[0...4].to_i
|
174
|
+
cname = str[4...cnlen+4]
|
175
|
+
str = str[cnlen+4...str.size]
|
176
|
+
cl_def = find_class_def(cname, schema_def)
|
177
|
+
raise InvalidSchemaClass.new("cannot find #{cname} in schema") if cl_def.nil?
|
178
|
+
end
|
179
|
+
|
180
|
+
return cl_def.from_s(str, schema_def, converted_classes)
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
# A class definition, containing data members.
|
188
|
+
# Right now we build into this the assumption that
|
189
|
+
# the 'name' attribute will share the same name as
|
190
|
+
# the actual class name which data will be mapped to / from
|
191
|
+
# and accessors exist on it corresponding to the names of
|
192
|
+
# each of the members
|
193
|
+
class ClassDef
|
194
|
+
# class name
|
195
|
+
# array of DataFieldDef
|
196
|
+
# base class name
|
197
|
+
attr_accessor :name, :members, :inherits
|
198
|
+
|
199
|
+
def initialize
|
200
|
+
@members = []
|
201
|
+
end
|
202
|
+
|
203
|
+
def base_class_def(schema_def)
|
204
|
+
return schema_def.classes.find { |cl| cl.name == inherits.to_s } unless inherits.nil? || schema_def.nil?
|
205
|
+
end
|
206
|
+
|
207
|
+
# convert value instance of class represented by this ClassDef
|
208
|
+
# into a string. schema_def must be provided if this ClassDef
|
209
|
+
# contains any associated class members. converted_classes is
|
210
|
+
# a recursive helper array used internally
|
211
|
+
def to_s(value, schema_def = nil, converted_classes = [])
|
212
|
+
return "O" + ("%04d" % converted_classes.index(value)) if converted_classes.include? value # if we already converted the class, store 'O' + its index
|
213
|
+
converted_classes.push value
|
214
|
+
|
215
|
+
# just encode each member w/ length
|
216
|
+
str = "I" # NEED to have something here incase the length of the first member is the same as the ascii character for 'O'
|
217
|
+
unless value.nil?
|
218
|
+
@members.each { |member|
|
219
|
+
mval = value.send(member.name.intern) if value.respond_to? member.name.intern
|
220
|
+
#mval = value.method(member.name.intern).call # invoke member getter
|
221
|
+
mstr = member.to_s(mval, schema_def, converted_classes)
|
222
|
+
mlen = "%04d" % mstr.size
|
223
|
+
#unless mstr == "" && member.ignore_null
|
224
|
+
str += mlen + mstr
|
225
|
+
#end
|
226
|
+
}
|
227
|
+
|
228
|
+
# encode and append base class
|
229
|
+
base_class = base_class_def(schema_def)
|
230
|
+
until base_class.nil?
|
231
|
+
base_class.members.each { |member|
|
232
|
+
mval = value.send(member.name.intern) if value.respond_to? member.name.intern
|
233
|
+
mstr = member.to_s(mval, schema_def, converted_classes)
|
234
|
+
mlen = "%04d" % mstr.size
|
235
|
+
str += mlen + mstr
|
236
|
+
}
|
237
|
+
base_class = base_class.base_class_def(schema_def)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
return str
|
242
|
+
end
|
243
|
+
|
244
|
+
# convert string instance of class represented by this ClassDef
|
245
|
+
# into actual class instance. schema_def must be provided if this
|
246
|
+
# ClassDef contains any associated class members.
|
247
|
+
# The converted_classes recursive helper array is used internally.
|
248
|
+
def from_s(str, schema_def = nil, converted_classes = [])
|
249
|
+
return nil if str == "I"
|
250
|
+
|
251
|
+
if str[0] == "O" # if we already converted the class, simply return that
|
252
|
+
return converted_classes[str[1...5].to_i]
|
253
|
+
end
|
254
|
+
|
255
|
+
# construct an instance of the class
|
256
|
+
cl = Object.module_eval("::#{@name}", __FILE__, __LINE__)
|
257
|
+
obj = cl.new if cl.respond_to? :new
|
258
|
+
obj = cl.instance if obj.nil? && cl.respond_to?(:instance)
|
259
|
+
raise InvalidSchemaClass.new("cannot create schema class #{@name}") if obj.nil?
|
260
|
+
|
261
|
+
# decode each member
|
262
|
+
mpos = 1 # start at 1 to skip the 'I'
|
263
|
+
@members.each { |member|
|
264
|
+
mlen = str[mpos...mpos+4].to_i
|
265
|
+
parsed = str[mpos+4...mpos+4+mlen]
|
266
|
+
parsed_o = member.from_s(parsed, schema_def, converted_classes)
|
267
|
+
unless parsed_o.nil? && member.ignore_null
|
268
|
+
member_method = (member.name + "=").intern
|
269
|
+
obj.send(member_method, parsed_o) if obj.respond_to? member_method # invoke member setter
|
270
|
+
end
|
271
|
+
mpos = mpos+4+mlen
|
272
|
+
}
|
273
|
+
|
274
|
+
# decode base object from string
|
275
|
+
base_class = base_class_def(schema_def)
|
276
|
+
until base_class.nil?
|
277
|
+
base_class.members.each { |member|
|
278
|
+
mlen = str[mpos...mpos+4].to_i
|
279
|
+
parsed = str[mpos+4...mpos+4+mlen]
|
280
|
+
parsed_o = member.from_s(parsed, schema_def, converted_classes)
|
281
|
+
unless parsed_o.nil? && member.ignore_null
|
282
|
+
member_method = (member.name + "=").intern
|
283
|
+
obj.send(member_method, parsed_o) if obj.respond_to? member_method # invoke member setter
|
284
|
+
end
|
285
|
+
mpos = mpos+4+mlen
|
286
|
+
}
|
287
|
+
|
288
|
+
base_class = base_class.base_class_def(schema_def)
|
289
|
+
end
|
290
|
+
|
291
|
+
return obj
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# method definition, containing parameters
|
296
|
+
# and return values. May optionally have
|
297
|
+
# a handler to be invoked when a remote
|
298
|
+
# entity invokes this method
|
299
|
+
class MethodDef
|
300
|
+
attr_accessor :name
|
301
|
+
|
302
|
+
# both are arrays of DataFieldDef
|
303
|
+
attr_accessor :parameters, :return_values
|
304
|
+
|
305
|
+
# should be a callable entity that takes
|
306
|
+
# the specified parameters, and returns
|
307
|
+
# the specified return values
|
308
|
+
attr_accessor :handler
|
309
|
+
|
310
|
+
def initialize
|
311
|
+
@parameters = []
|
312
|
+
@return_values = []
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# schema defintion including all defined classes and methods
|
317
|
+
class SchemaDef
|
318
|
+
# array of ClassDef
|
319
|
+
attr_accessor :classes
|
320
|
+
|
321
|
+
# array of MethodDef
|
322
|
+
attr_accessor :methods
|
323
|
+
|
324
|
+
def initialize
|
325
|
+
@classes = []
|
326
|
+
@methods = []
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# parse classes, methods, and data out of a xml definition
|
331
|
+
class Parser
|
332
|
+
|
333
|
+
# Parse and return a SchemaDef.
|
334
|
+
# Specify :schema argument containing xml schema to parse
|
335
|
+
# or :file containing location of file containing xml schema
|
336
|
+
def self.parse(args = {})
|
337
|
+
if(!args[:schema].nil?)
|
338
|
+
schema = args[:schema]
|
339
|
+
elsif(!args[:file].nil?)
|
340
|
+
schema = File.new(args[:file], "r")
|
341
|
+
end
|
342
|
+
|
343
|
+
schema_def = SchemaDef.new
|
344
|
+
|
345
|
+
unless schema.nil? || schema == ""
|
346
|
+
doc = REXML::Document.new(schema)
|
347
|
+
# grab each method definition
|
348
|
+
doc.elements.each('schema/method') do |ele|
|
349
|
+
method = MethodDef.new
|
350
|
+
method.name = ele.attributes["name"]
|
351
|
+
Logger.debug "parsed schema method #{method.name}"
|
352
|
+
|
353
|
+
ele.elements.each("param") do |param|
|
354
|
+
param = _parse_data_def(param)
|
355
|
+
method.parameters.push param
|
356
|
+
Logger.debug " parameter #{param.name}"
|
357
|
+
end
|
358
|
+
|
359
|
+
ele.elements.each("return_value") do |rv|
|
360
|
+
rv = _parse_data_def(rv)
|
361
|
+
method.return_values.push rv
|
362
|
+
Logger.debug " return_value #{rv.name}"
|
363
|
+
end
|
364
|
+
|
365
|
+
schema_def.methods.push method
|
366
|
+
end
|
367
|
+
|
368
|
+
# grab each class definition
|
369
|
+
doc.elements.each('schema/class') do |ele|
|
370
|
+
cl = ClassDef.new
|
371
|
+
cl.name = ele.attributes["name"]
|
372
|
+
cl.inherits = ele.attributes["inherits"]
|
373
|
+
Logger.debug "parsed schema class #{cl.name}"
|
374
|
+
|
375
|
+
ele.elements.each("member") do |mem|
|
376
|
+
mem = _parse_data_def(mem)
|
377
|
+
cl.members.push mem
|
378
|
+
Logger.debug " member #{mem.name}"
|
379
|
+
end
|
380
|
+
|
381
|
+
schema_def.classes.push cl
|
382
|
+
end
|
383
|
+
end
|
384
|
+
return schema_def
|
385
|
+
end
|
386
|
+
|
387
|
+
private
|
388
|
+
# helper method to parse a DataFieldDef out of an Element
|
389
|
+
def self._parse_data_def(element)
|
390
|
+
data_field = DataFieldDef.new
|
391
|
+
data_field.type = element.attributes["type"].intern
|
392
|
+
data_field.name = element.attributes["name"]
|
393
|
+
data_field.associated = element.attributes["associated"].intern unless element.attributes["associated"].nil?
|
394
|
+
data_field.ignore_null = true if element.attributes.include? "ignore_null"
|
395
|
+
return data_field
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
end # module Schema
|
400
|
+
end # module Simrpc
|