simrpc 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/simrpc.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # Implements a simple to use method based RPC for ruby
4
4
  # built upon Apache Qpid
5
5
  #
6
- # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
6
+ # Copyright (c) 2010 Mohammed Morsi <movitto@yahoo.com>
7
7
  #
8
8
  # Permission is hereby granted, free of charge, to any person
9
9
  # obtaining a copy of this software and associated documentation
@@ -26,806 +26,13 @@
26
26
  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
27
  # OTHER DEALINGS IN THE SOFTWARE.
28
28
 
29
- require 'rexml/document'
30
- require 'logger'
29
+ lib = File.dirname(__FILE__)
31
30
 
32
- require 'qpid'
33
- require 'socket'
34
- require 'semaphore'
31
+ require lib + '/simrpc/common'
32
+ require lib + '/simrpc/exceptions'
33
+ require lib + '/simrpc/schema'
34
+ require lib + '/simrpc/message'
35
+ require lib + '/simrpc/qpid_adapter'
36
+ require lib + '/simrpc/node'
35
37
 
36
- require 'activesupport' # for inflector demodulize
37
-
38
- module Simrpc
39
-
40
- # Logger helper class
41
- class Logger
42
- private
43
- def self._instantiate_logger
44
- unless defined? @@logger
45
- @@logger = ::Logger.new(STDOUT)
46
- @@logger.level = ::Logger::FATAL # FATAL ERROR WARN INFO DEBUG
47
- end
48
- end
49
- public
50
- def self.method_missing(method_id, *args)
51
- _instantiate_logger
52
- @@logger.send(method_id, args)
53
- end
54
- end
55
-
56
- # schema module defines classes / methods to define
57
- # a simrpc schema / load it from an xml file
58
- module Schema
59
-
60
- Types = [ :str, :int, :float, :bool, :obj, :array ]
61
-
62
- # return true if type is a primitive, else false
63
- def is_primitive?(type)
64
- return [:str, :int, :float, :bool].include?(type)
65
- end
66
- module_function :is_primitive?
67
-
68
- # convert primitive type from string
69
- def primitive_from_str(type, str)
70
- if type == :str
71
- return str
72
-
73
- elsif type == :int
74
- return str.to_i
75
-
76
- elsif type == :float
77
- return str.to_f
78
-
79
- elsif type == :bool
80
- return str == "true"
81
-
82
- end
83
- end
84
- module_function :primitive_from_str
85
-
86
- # FIXME store lengths in binary instead of ascii string
87
-
88
- # Data field defintion containing a type, name, and value.
89
- # Optinally an associated class or type may be given.
90
- # Associated only valid for :obj and :array types, and
91
- # must be set to associated ClassDef or Type (:array only)
92
- class DataFieldDef
93
- attr_accessor :type, :name, :associated
94
-
95
- # indicates this data field should be ignored if it has a null value
96
- attr_accessor :ignore_null
97
-
98
- def initialize(args = {})
99
- @type = args[:type] unless args[:type].nil?
100
- @name = args[:name] unless args[:name].nil?
101
- @associated = args[:associated] unless args[:associated].nil?
102
- @ignore_null = !args[:ignore_null].nil? && args[:ignore_null]
103
- end
104
-
105
- # helper method to lookup and return the specified class name in the
106
- # specified schema
107
- def find_class_def(cl_name, schema_def)
108
- return schema_def.classes.find { |cl| cl.name == cl_name.to_s } unless cl_name.nil? || schema_def.nil?
109
- nil
110
- end
111
-
112
- # helper method to lookup and return the class definition corresponding to the associated
113
- # attribte in the specified schema def
114
- def associated_class_def(schema_def)
115
- find_class_def(@associated, schema_def) unless @associated.nil?
116
- end
117
-
118
- # convert given value of this data field into a string. Provide schema_def
119
- # for :obj or :array data fields associated w/ a non-primitive type. converted_classes
120
- # is a recursive helper array used/maintained internally
121
- def to_s(value, schema_def = nil, converted_classes = [])
122
- if value.nil?
123
- return ""
124
- elsif Schema::is_primitive?(@type)
125
- return value.to_s
126
-
127
- elsif type == :array
128
- str = "%04d" % value.size
129
- value.each { |val|
130
- if Schema::is_primitive?(@associated)
131
- str += "%04d" % val.to_s.size
132
- str += val.to_s
133
- else
134
- cl_def = associated_class_def(schema_def)
135
- unless cl_def.nil?
136
- cl_s = cl_def.to_s(val, schema_def, converted_classes)
137
- str += "%04d" % cl_s.size
138
- str += cl_s
139
- end
140
- end
141
- }
142
- return str
143
-
144
- # elsif @type == :map # TODO
145
-
146
- elsif type == :obj
147
- cl_name = ""
148
- cl_def = associated_class_def(schema_def)
149
-
150
- # if associated class isn't specified, store the class name,
151
- # providing for generic object support
152
- if cl_def.nil?
153
- cl_name = value.class.to_s.demodulize
154
- cl_def = find_class_def(cl_name, schema_def)
155
- cl_name = "%04d" % cl_name.size + cl_name
156
- end
157
-
158
- return cl_name + cl_def.to_s(value, schema_def, converted_classes) unless cl_def.nil?
159
-
160
- end
161
- end
162
-
163
- # convert given string representation of this data field into its original value.
164
- # Provide schema_def for :obj or :array data fields associated w/ non-primitive types
165
- # # coverted_classes is a recursive helper array used/maintained internally
166
- def from_s(str, schema_def = nil, converted_classes = [])
167
- if str == ""
168
- return nil
169
-
170
- elsif Schema::is_primitive?(@type)
171
- return Schema::primitive_from_str(@type, str)
172
-
173
- elsif @type == :array
174
- res = []
175
- cl_def = associated_class_def(schema_def) unless Schema::is_primitive?(@associated)
176
- alen = str[0...4].to_i
177
- apos = 4
178
- (0...alen).each { |i|
179
- elen = str[apos...apos+4].to_i
180
- parsed = str[apos+4...apos+4+elen]
181
- if Schema::is_primitive?(@associated)
182
- p = Schema::primitive_from_str(@associated, parsed)
183
- res.push p
184
- else
185
- res.push cl_def.from_s(parsed, schema_def, converted_classes)
186
- end
187
- apos = apos+4+elen
188
- }
189
- #parsed = str[4...4+len]
190
- return res
191
-
192
- # elsif @type == :map # TODO
193
-
194
- elsif @type == :obj
195
- cl_def = associated_class_def(schema_def)
196
-
197
- # if associated class isn't specified, parse the class name,
198
- # providing for generic object support
199
- if cl_def.nil?
200
- cnlen = str[0...4].to_i
201
- cname = str[4...cnlen+4]
202
- str = str[cnlen+4...str.size]
203
- cl_def = find_class_def(cname, schema_def)
204
- end
205
-
206
- return cl_def.from_s(str, schema_def, converted_classes) unless cl_def.nil?
207
-
208
- end
209
- end
210
-
211
- end
212
-
213
- # A class definition, containing data members.
214
- # Right now we build into this the assumption that
215
- # the 'name' attribute will share the same name as
216
- # the actual class name which data will be mapped to / from
217
- # and accessors exist on it corresponding to the names of
218
- # each of the members
219
- class ClassDef
220
- # class name
221
- # array of DataFieldDef
222
- attr_accessor :name, :members
223
-
224
- def initialize
225
- @members = []
226
- end
227
-
228
- # convert value instance of class represented by this ClassDef
229
- # into a string. schema_def must be provided if this ClassDef
230
- # contains any associated class members. converted_classes is
231
- # a recursive helper array used internally
232
- def to_s(value, schema_def = nil, converted_classes = [])
233
- return "O" + ("%04d" % converted_classes.index(value)) if converted_classes.include? value # if we already converted the class, store 'O' + its index
234
- converted_classes.push value
235
-
236
- # just encode each member w/ length
237
- str = ""
238
- unless value.nil?
239
- @members.each { |member|
240
- mval = value.send(member.name.intern)
241
- #mval = value.method(member.name.intern).call # invoke member getter
242
- mstr = member.to_s(mval, schema_def, converted_classes)
243
- mlen = "%04d" % mstr.size
244
- #unless mstr == "" && member.ignore_null
245
- str += mlen + mstr
246
- #end
247
- }
248
- end
249
- return str
250
- end
251
-
252
- # convert string instance of class represented by this ClassDef
253
- # into actual class instance. schema_def must be provided if this
254
- # ClassDef contains any associated class members. converted_classes
255
- # is a recurvice helper array used internally.
256
- def from_s(str, schema_def = nil, converted_classes = [])
257
- return nil if str == ""
258
-
259
- mpos = 0
260
- if str[mpos,1] == "O" # if we already converted the class, simply return that
261
- return converted_classes[str[1...5].to_i]
262
- end
263
-
264
- # construct an instance of the class
265
- obj = Object.module_eval("::#{@name}", __FILE__, __LINE__).new
266
- converted_classes.push obj
267
-
268
- # decode each member
269
- @members.each { |member|
270
- mlen = str[mpos...mpos+4].to_i
271
- parsed = str[mpos+4...mpos+4+mlen]
272
- parsed_o = member.from_s(parsed, schema_def, converted_classes)
273
- unless parsed_o.nil? && member.ignore_null
274
- obj.send(member.name + "=", parsed_o) # invoke member setter
275
- end
276
- mpos = mpos+4+mlen
277
- }
278
-
279
- return obj
280
- end
281
- end
282
-
283
- # method definition, containing parameters
284
- # and return values. May optionally have
285
- # a handler to be invoked when a remote
286
- # entity invokes this method
287
- class MethodDef
288
- attr_accessor :name
289
-
290
- # both are arrays of DataFieldDef
291
- attr_accessor :parameters, :return_values
292
-
293
- # should be a callable entity that takes
294
- # the specified parameters, and returns
295
- # the specified return values
296
- attr_accessor :handler
297
-
298
- def initialize
299
- @parameters = []
300
- @return_values = []
301
- end
302
- end
303
-
304
- # schema defintion including all defined classes and methods
305
- class SchemaDef
306
- # array of ClassDef
307
- attr_accessor :classes
308
-
309
- # array of MethodDef
310
- attr_accessor :methods
311
-
312
- def initialize
313
- @classes = []
314
- @methods = []
315
- end
316
- end
317
-
318
- # parse classes, methods, and data out of a xml definition
319
- class Parser
320
-
321
- # Parse and return a SchemaDef.
322
- # Specify :schema argument containing xml schema to parse
323
- # or :file containing location of file containing xml schema
324
- def self.parse(args = {})
325
- if(!args[:schema].nil?)
326
- schema = args[:schema]
327
- elsif(!args[:file].nil?)
328
- schema = File.new(args[:file], "r")
329
- end
330
-
331
- schema_def = SchemaDef.new
332
-
333
- unless schema.nil? || schema == ""
334
- doc = REXML::Document.new(schema)
335
- # grab each method definition
336
- doc.elements.each('schema/method') do |ele|
337
- method = MethodDef.new
338
- method.name = ele.attributes["name"]
339
- Logger.debug "parsed schema method #{method.name}"
340
-
341
- ele.elements.each("param") do |param|
342
- param = _parse_data_def(param)
343
- method.parameters.push param
344
- Logger.debug " parameter #{param.name}"
345
- end
346
-
347
- ele.elements.each("return_value") do |rv|
348
- rv = _parse_data_def(rv)
349
- method.return_values.push rv
350
- Logger.debug " return_value #{rv.name}"
351
- end
352
-
353
- schema_def.methods.push method
354
- end
355
-
356
- # grab each class definition
357
- doc.elements.each('schema/class') do |ele|
358
- cl = ClassDef.new
359
- cl.name = ele.attributes["name"]
360
- Logger.debug "parsed schema class #{cl.name}"
361
-
362
- ele.elements.each("member") do |mem|
363
- mem = _parse_data_def(mem)
364
- cl.members.push mem
365
- Logger.debug " member #{mem.name}"
366
- end
367
-
368
- schema_def.classes.push cl
369
- end
370
- end
371
- return schema_def
372
- end
373
-
374
- private
375
- # helper method to parse a DataFieldDef out of an Element
376
- def self._parse_data_def(element)
377
- data_field = DataFieldDef.new
378
- data_field.type = element.attributes["type"].intern
379
- data_field.name = element.attributes["name"]
380
- data_field.associated = element.attributes["associated"].intern unless element.attributes["associated"].nil?
381
- data_field.ignore_null = true if element.attributes.include? "ignore_null"
382
- return data_field
383
- end
384
- end
385
-
386
- end
387
-
388
- # the message module provides the Message definition,
389
- # including a header with routing info and a body
390
- # with any number of data fields
391
- module Message
392
-
393
- # Simrpc::Message formatter helper module
394
- class Formatter
395
-
396
- # helper method to format a data field,
397
- # prepending a fixed size to it
398
- def self.format_with_size(data)
399
- # currently size is set to a 8 digit int
400
- len = "%08d" % data.to_s.size
401
- len + data.to_s
402
- end
403
-
404
- # helper method to parse a data field
405
- # off the front of a data sequence, using the
406
- # formatted size. Returns parsed data field
407
- # and remaining data sequence. If optional
408
- # class is given, the from_s method will
409
- # be invoked w/ the parsed data field and
410
- # returned with the remaining data sequence
411
- # instead
412
- def self.parse_from_formatted(data, data_class = nil)
413
- len = data[0...8].to_i
414
- parsed = data[8...8+len]
415
- remaining = data[8+len...data.size]
416
- return parsed, remaining if data_class.nil?
417
- return data_class.from_s(parsed), remaining
418
- end
419
- end
420
-
421
- # a single field trasnmitted via a message,
422
- # containing a key / value pair
423
- class Field
424
- attr_accessor :name, :value
425
-
426
- def initialize(args = {})
427
- @name = args[:name].nil? ? "" : args[:name]
428
- @value = args[:value].nil? ? "" : args[:value]
429
- end
430
-
431
- def to_s
432
- Formatter::format_with_size(@name) + Formatter::format_with_size(@value)
433
- end
434
-
435
- def self.from_s(data)
436
- field = Field.new
437
- field.name, data = Formatter::parse_from_formatted(data)
438
- field.value, data = Formatter::parse_from_formatted(data)
439
- return field
440
- end
441
- end
442
-
443
- # header contains various descriptive properies
444
- # about a message
445
- class Header
446
- attr_accessor :type, :target
447
-
448
- def initialize(args = {})
449
- @type = args[:type].nil? ? "" : args[:type]
450
- @target = args[:target].nil? ? "" : args[:target]
451
- end
452
-
453
- def to_s
454
- Formatter::format_with_size(@type) + Formatter::format_with_size(@target)
455
- end
456
-
457
- def self.from_s(data)
458
- header = Header.new
459
- header.type, data = Formatter::parse_from_formatted(data)
460
- header.target, data = Formatter::parse_from_formatted(data)
461
- return header
462
- end
463
- end
464
-
465
- # body consists of a list of data fields
466
- class Body
467
- attr_accessor :fields
468
-
469
- def initialize
470
- @fields = []
471
- end
472
-
473
- def to_s
474
- s = ''
475
- @fields.each { |field|
476
- fs = field.to_s
477
- s += Formatter::format_with_size(fs)
478
- }
479
- return s
480
- end
481
-
482
- def self.from_s(data)
483
- body = Body.new
484
- while(data != "")
485
- field, data = Formatter::parse_from_formatted(data)
486
- field = Field.from_s field
487
- body.fields.push field
488
- end
489
- return body
490
- end
491
- end
492
-
493
- # message contains a header / body
494
- class Message
495
- attr_accessor :header, :body
496
-
497
- def initialize
498
- @header = Header.new
499
- @body = Body.new
500
- end
501
-
502
- def to_s
503
- Formatter::format_with_size(@header) + Formatter::format_with_size(@body)
504
- end
505
-
506
- def self.from_s(data)
507
- message = Message.new
508
- message.header, data = Formatter::parse_from_formatted(data, Header)
509
- message.body, data = Formatter::parse_from_formatted(data, Body)
510
- return message
511
- end
512
- end
513
-
514
- end
515
-
516
- # The QpidAdapter module implements the simrpc qpid subsystem, providing
517
- # a convenient way to access qpid constructs
518
- module QpidAdapter
519
-
520
- # Simrpc::Qpid::Node class, represents an enpoint on a qpid
521
- # network which has its own exchange and queue which it listens on
522
- class Node
523
- private
524
- # helper method to generate a random id
525
- def gen_uuid
526
- ["%02x"*4, "%02x"*2, "%02x"*2, "%02x"*2, "%02x"*6].join("-") %
527
- Array.new(16) {|x| rand(0xff) }
528
- end
529
-
530
- public
531
- # a node can have children nodes mapped to by keys
532
- attr_accessor :children
533
-
534
- # node always has a node id
535
- attr_reader :node_id
536
-
537
- # create the qpid base connection with the specified broker / port
538
- # or config file. Then establish exchange and queue and start listening
539
- # for requests.
540
- #
541
- # specify :broker and :port arguments to directly connect to those
542
- # specify :config argument to use that yml file
543
- # specify MOTEL_AMQP_CONF environment variable to use that yml file
544
- # specify :id parameter to set id, else it will be set to a uuid just created
545
- def initialize(args = {})
546
- # if no id specified generate a new uuid
547
- @node_id = args[:id].nil? ? gen_uuid : args[:id]
548
-
549
- # we generate a random session id
550
- @session_id = gen_uuid
551
-
552
- # get the broker/port
553
- broker = args[:broker].nil? ? "localhost" : args[:broker]
554
- port = args[:port].nil? ? 5672 : args[:port]
555
-
556
- if (broker.nil? || port.nil?) && args.has_key?(:config)
557
- config =
558
- amqpconfig = YAML::load(File.open(args[:config]))
559
- broker = amqpconfig["broker"] if broker.nil?
560
- port = amqpconfig["port"] if port.nil?
561
- end
562
-
563
- ### create underlying tcp connection
564
- @conn = Qpid::Connection.new(TCPSocket.new(broker,port))
565
- @conn.start
566
-
567
- ### connect to qpid broker
568
- @ssn = @conn.session(@session_id)
569
-
570
- @children = {}
571
-
572
- @accept_lock = Semaphore.new(1)
573
-
574
- # qpid constructs that will be created for node
575
- @exchange = args[:exchange].nil? ? @node_id.to_s + "-exchange" : args[:exchange]
576
- @queue = args[:queue].nil? ? @node_id.to_s + "-queue" : args[:queue]
577
- @local_queue = args[:local_queue].nil? ? @node_id.to_s + "-local-queue" : args[:local_queue]
578
- @routing_key = @queue
579
-
580
- Logger.warn "creating qpid exchange #{@exchange} queue #{@queue} binding_key #{@routing_key}"
581
-
582
- if @ssn.exchange_query(@exchange).not_found
583
- @ssn.exchange_declare(@exchange, :type => "direct")
584
- end
585
-
586
- if @ssn.queue_query(@queue).queue.nil?
587
- @ssn.queue_declare(@queue)
588
- end
589
-
590
- @ssn.exchange_bind(:exchange => @exchange,
591
- :queue => @queue,
592
- :binding_key => @routing_key)
593
- end
594
-
595
- # Instruct Node to start accepting requests asynchronously and immediately return.
596
- # handler must be callable and take node, msg, respond_to arguments, corresponding to
597
- # 'self', the message received', and the routing_key which to send any response.
598
- def async_accept(&handler)
599
- # TODO permit a QpidNode to accept messages from multiple exchanges/queues
600
- @accept_lock.wait
601
-
602
- # subscribe to the queue
603
- @ssn.message_subscribe(:destination => @local_queue,
604
- :queue => @queue,
605
- :accept_mode => @ssn.message_accept_mode.none)
606
- @incoming = @ssn.incoming(@local_queue)
607
- @incoming.start
608
-
609
- Logger.warn "listening for messages on #{@queue}"
610
-
611
- # start receiving messages
612
- @incoming.listen{ |msg|
613
- Logger.info "queue #{@queue} received message #{msg.body.to_s.size} #{msg.body}"
614
- reply_to = msg.get(:message_properties).reply_to.routing_key
615
- handler.call(self, msg.body, reply_to)
616
- }
617
- end
618
-
619
- # block until accept operation is complete
620
- def join
621
- @accept_lock.wait
622
- end
623
-
624
- # instructs QpidServer to stop accepting, blocking
625
- # untill all accepting operations have terminated
626
- def terminate
627
- Logger.warn "terminating qpid session"
628
- unless @incoming.nil?
629
- @incoming.stop
630
- @incoming.close
631
- @accept_lock.signal
632
- end
633
- @ssn.close
634
- # TODO undefine the @queue/@exchange
635
- end
636
-
637
- # send a message to the specified routing_key
638
- def send_message(routing_key, message)
639
- dp = @ssn.delivery_properties(:routing_key => routing_key)
640
- mp = @ssn.message_properties( :content_type => "text/plain")
641
- rp = @ssn.message_properties( :reply_to =>
642
- @ssn.reply_to(@exchange, @routing_key))
643
- msg = Qpid::Message.new(dp, mp, rp, message.to_s)
644
-
645
- Logger.warn "sending qpid message #{msg.body} to #{routing_key}"
646
-
647
- # send it
648
- @ssn.message_transfer(:message => msg)
649
- end
650
-
651
- end
652
-
653
- end
654
-
655
- # Simrpc Method Message Controller, generates and handles method messages
656
- class MethodMessageController
657
- public
658
- # initialize with a specified schema definition
659
- def initialize(schema_def)
660
- @schema_def = schema_def
661
- end
662
-
663
- # generate new new method message, setting the message
664
- # target to the specified method name, and setting the fields
665
- # on the message to the method arguments
666
- def generate(method_name, args)
667
- @schema_def.methods.each { |method|
668
- if method.name == method_name
669
- msg = Message::Message.new
670
- msg.header.type = 'request'
671
- msg.header.target = method.name
672
-
673
- # loop through each param, convering corresponding
674
- # argument to message field and adding it to msg
675
- i = 0
676
- method.parameters.each { |param|
677
- field = Message::Field.new
678
- field.name = param.name
679
- field.value = param.to_s(args[i], @schema_def)
680
- msg.body.fields.push field
681
- i += 1
682
- }
683
-
684
- return msg
685
- end
686
- }
687
- return nil
688
- end
689
-
690
- # should be invoked when a message is received,
691
- # takes a message, converts it into a method call, and calls the corresponding
692
- # handler in the provided schema. Takes return arguments and sends back to caller
693
- def message_received(node, message, reply_to)
694
- message = Message::Message::from_s(message)
695
- @schema_def.methods.each { |method|
696
-
697
- if method.name == message.header.target
698
- Logger.info "received method #{method.name} message "
699
-
700
- # for request messages, dispatch to method handler
701
- if message.header.type != 'response' && method.handler != nil
702
- # order the params
703
- params = []
704
- method.parameters.each { |data_field|
705
- value_field = message.body.fields.find { |f| f.name == data_field.name }
706
- params.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
707
- }
708
-
709
- Logger.info "invoking #{method.name} handler "
710
-
711
- # invoke method handler
712
- return_values = method.handler.call(*params) # FIXME handlers can't use 'return' as this will fall through here
713
- # FIXME throw a catch block around this call to catch all handler exceptions
714
- return_values = [return_values] unless return_values.is_a? Array
715
-
716
- # if method returns no values, do not return response
717
- unless method.return_values.size == 0
718
-
719
- # consruct and send response message using return values
720
- response = Message::Message.new
721
- response.header.type = 'response'
722
- response.header.target = method.name
723
- (0...method.return_values.size).each { |rvi|
724
- field = Message::Field.new
725
- field.name = method.return_values[rvi].name
726
- field_def = method.return_values.find { |rv| rv.name == field.name }
727
- field.value = field_def.to_s(return_values[rvi], @schema_def) unless field_def.nil? # TODO what if field_def is nil
728
- response.body.fields.push field
729
- }
730
- Logger.info "responding to #{reply_to}"
731
- node.send_message(reply_to, response)
732
-
733
- end
734
-
735
- # for response values just return converted return values
736
- else
737
- results = []
738
- method.return_values.each { |data_field|
739
- value_field = message.body.fields.find { |f| f.name == data_field.name }
740
- results.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
741
- }
742
- return results
743
- end
744
- end
745
- }
746
- end
747
- end
748
-
749
- # Simrpc Node represents ths main api which to communicate and send/listen for data.
750
- class Node
751
-
752
- # Instantiate it w/ a specified id
753
- # or one will be autogenerated. Specify schema (or location) containing
754
- # data and methods which to invoke and/or handle. Optionally specify
755
- # a remote destination which to send new messages to. Automatically listens
756
- # for incoming messages.
757
- def initialize(args = {})
758
- @id = args[:id] if args.has_key? :id
759
- @schema = args[:schema]
760
- @schema_file = args[:schema_file]
761
- @destination = args[:destination]
762
-
763
- if !@schema.nil?
764
- @schema_def = Schema::Parser.parse(:schema => @schema)
765
- elsif !@schema_file.nil?
766
- @schema_def = Schema::Parser.parse(:file => @schema_file)
767
- end
768
- raise ArgumentError, "schema_def cannot be nil" if @schema_def.nil?
769
- @mmc = MethodMessageController.new(@schema_def)
770
- @message_lock = Semaphore.new(1)
771
- @message_lock.wait
772
-
773
- @qpid_node = QpidAdapter::Node.new(:id => @id)
774
- @qpid_node.async_accept { |node, msg, reply_to|
775
- results = @mmc.message_received(node, msg, reply_to)
776
- message_received(results)
777
- }
778
- end
779
-
780
- def id
781
- return @id unless @id.nil?
782
- return @qpid_node.node_id
783
- end
784
-
785
- # implements, message_received callback to be notified when qpid receives a message
786
- def message_received(results)
787
- @message_results = results
788
- @message_lock.signal
789
- end
790
-
791
- # wait until the node is no longer accepting messages
792
- def join
793
- @qpid_node.join
794
- end
795
-
796
- # add a handler which to invoke when an schema method is invoked
797
- def handle_method(method, &handler)
798
- @schema_def.methods.each { |smethod|
799
- if smethod.name == method.to_s
800
- smethod.handler = handler
801
- break
802
- end
803
- }
804
- end
805
-
806
- # send method request to remote destination w/ the specified args
807
- def send_method(method_name, destination, *args)
808
- # generate and send new method message
809
- msg = @mmc.generate(method_name, args)
810
- @qpid_node.send_message(destination + "-queue", msg)
811
-
812
- # FIXME race condition if response is received b4 wait is invoked
813
-
814
- # block if we are expecting return values
815
- if @schema_def.methods.find{|m| m.name == method_name}.return_values.size != 0
816
- @message_lock.wait # block until response received
817
-
818
- # return return values
819
- #@message_received.body.fields.collect { |f| f.value }
820
- return *@message_results
821
- end
822
- end
823
-
824
- # can invoke schema methods directly on Node instances, this will catch
825
- # them and send them onto the destination
826
- def method_missing(method_id, *args)
827
- send_method(method_id.to_s, @destination, *args)
828
- end
829
- end
830
-
831
- end # module Simrpc
38
+ require 'activesupport' # for inflector