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
data/LICENSE
CHANGED
data/README.rdoc
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
== Simrpc - A Simple RPC library using AMQP as the transport mechanism
|
2
|
+
|
3
|
+
Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
|
5
|
+
Simrpc is made available under the MIT License
|
6
|
+
|
7
|
+
=== Intro
|
8
|
+
Simrpc is a simple Ruby module for rpc communication
|
9
|
+
that uses Apache QPID as the transport mechanism.
|
10
|
+
|
11
|
+
Developers define class / function schemas in xml and register handlers
|
12
|
+
to be invoked when schema methods are called. Schema classes and members
|
13
|
+
are mapped to Ruby classes via Ruby's builtin introspection mechanisms.
|
14
|
+
|
15
|
+
To install simrpc simply run:
|
16
|
+
gem install simrpc
|
17
|
+
|
18
|
+
Source code is available via:
|
19
|
+
git clone http://github.com/movitto/simrpc
|
20
|
+
|
21
|
+
=== Using
|
22
|
+
Generate documentation via
|
23
|
+
rake rdoc
|
24
|
+
|
25
|
+
Also see specs for detailed usage.
|
26
|
+
|
27
|
+
The primary interface to simrpc is provided by the 'node' module which
|
28
|
+
defines Simrpc::Node. This class should be instantiated using a custom identifier
|
29
|
+
for every simrpc endpoint you want to establish (nodes that share
|
30
|
+
an identifier will share a message queue) after which it can be used to
|
31
|
+
register handlers to schema methods and invoke remote methods.
|
32
|
+
|
33
|
+
|
34
|
+
== Example
|
35
|
+
|
36
|
+
TEST_SCHEMA =
|
37
|
+
"<schema>"+
|
38
|
+
" <method name='foo_method'>" +
|
39
|
+
" <param type='int' name='some_int'/>"+
|
40
|
+
" <param type='float' name='floating_point_number'/>"+
|
41
|
+
" <return_value type='str' name='a_string' />" +
|
42
|
+
" <return_value type='obj' name='my_class_instance' associated='MyClass' />" +
|
43
|
+
" </method>"+
|
44
|
+
" <method name='bar_method'>" +
|
45
|
+
" <param type='array' name='byte_array' associated='int'/>"+
|
46
|
+
" <return_value type='int' name='bool_success' />"+
|
47
|
+
" </method>"+
|
48
|
+
" <class name='MyClass'>"+
|
49
|
+
" <member type='str' name='str_member' />" +
|
50
|
+
" <member type='float' name='float_member' />" +
|
51
|
+
" <member type='obj' name='associated_obj' ignore_null='true' />" +
|
52
|
+
" </class>"+
|
53
|
+
"</schema>"
|
54
|
+
|
55
|
+
class MyClass
|
56
|
+
attr_accessor :str_member
|
57
|
+
attr_accessor :float_member
|
58
|
+
attr_accessor :associated_obj
|
59
|
+
end
|
60
|
+
|
61
|
+
server = Node.new(:id => "server3", :schema => TEST_SCHEMA)
|
62
|
+
client = Node.new(:id => "client3", :schema => TEST_SCHEMA, :destination => "server3")
|
63
|
+
|
64
|
+
server.handle_method("foo_method") { |some_int, floating_point_number|
|
65
|
+
some_int # => 10
|
66
|
+
floating_point_number # => 15.4
|
67
|
+
|
68
|
+
["stuff", MyClass.new("foobar", 4.2)]
|
69
|
+
}
|
70
|
+
|
71
|
+
a_str, my_class_instance = client.foo_method(10, 15.4)
|
72
|
+
|
73
|
+
a_str # => "stuff"
|
74
|
+
my_class_instance.str_member # => "foobar"
|
75
|
+
my_class_instance.float_member # => 4.2
|
76
|
+
|
77
|
+
|
78
|
+
=== Authors
|
79
|
+
Mohammed Morsi <movitto@yahoo.com>
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# simrpc project Rakefile
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# See LICENSE for the License of this software
|
5
|
+
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'spec/rake/spectask'
|
8
|
+
require 'rake/gempackagetask'
|
9
|
+
|
10
|
+
GEM_NAME="simrpc"
|
11
|
+
PKG_VERSION=0.2
|
12
|
+
|
13
|
+
desc "Run all specs"
|
14
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
15
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
16
|
+
end
|
17
|
+
|
18
|
+
Rake::RDocTask.new do |rd|
|
19
|
+
rd.main = "README.rdoc"
|
20
|
+
rd.rdoc_dir = "doc/site/api"
|
21
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
22
|
+
end
|
23
|
+
|
24
|
+
PKG_FILES = FileList['lib/**/*.rb', 'LICENSE', 'Rakefile', 'README.rdoc', 'spec/**/*.rb' ]
|
25
|
+
|
26
|
+
SPEC = Gem::Specification.new do |s|
|
27
|
+
s.name = GEM_NAME
|
28
|
+
s.version = PKG_VERSION
|
29
|
+
s.files = PKG_FILES
|
30
|
+
|
31
|
+
s.required_ruby_version = '>= 1.8.1'
|
32
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.3.3")
|
33
|
+
# FIXME require qpid
|
34
|
+
|
35
|
+
s.author = "Mohammed Morsi"
|
36
|
+
s.email = "movitto@yahoo.com"
|
37
|
+
s.date = %q{2010-03-11}
|
38
|
+
s.description = %q{simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.}
|
39
|
+
s.summary = %q{simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.}
|
40
|
+
s.homepage = %q{http://projects.morsi.org/Simrpc}
|
41
|
+
end
|
42
|
+
|
43
|
+
Rake::GemPackageTask.new(SPEC) do |pkg|
|
44
|
+
pkg.need_tar = true
|
45
|
+
pkg.need_zip = true
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# simrpc common module, methods that don't fit elsewhere
|
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 'logger'
|
27
|
+
|
28
|
+
module Simrpc
|
29
|
+
|
30
|
+
# Logger helper class
|
31
|
+
class Logger
|
32
|
+
private
|
33
|
+
def self._instantiate_logger
|
34
|
+
unless defined? @@logger
|
35
|
+
@@logger = ::Logger.new(STDOUT)
|
36
|
+
@@logger.level = ::Logger::FATAL # FATAL ERROR WARN INFO DEBUG
|
37
|
+
end
|
38
|
+
end
|
39
|
+
public
|
40
|
+
def self.method_missing(method_id, *args)
|
41
|
+
_instantiate_logger
|
42
|
+
@@logger.send(method_id, args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# simrpc exceptions, may be thrown in simrpc operations
|
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
|
+
class InvalidSchemaClass < RuntimeError
|
27
|
+
def self(msg = '')
|
28
|
+
super(msg)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,156 @@
|
|
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
|
+
module Simrpc
|
27
|
+
|
28
|
+
# the message module provides the Message definition,
|
29
|
+
# including a header with routing info and a body
|
30
|
+
# with any number of data fields
|
31
|
+
module Message
|
32
|
+
|
33
|
+
# Simrpc::Message formatter helper module
|
34
|
+
class Formatter
|
35
|
+
|
36
|
+
# helper method to format a data field,
|
37
|
+
# prepending a fixed size to it
|
38
|
+
def self.format_with_size(data)
|
39
|
+
# currently size is set to a 8 digit int
|
40
|
+
len = "%08d" % data.to_s.size
|
41
|
+
len + data.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# helper method to parse a data field
|
45
|
+
# off the front of a data sequence, using the
|
46
|
+
# formatted size. Returns parsed data field
|
47
|
+
# and remaining data sequence. If optional
|
48
|
+
# class is given, the from_s method will
|
49
|
+
# be invoked w/ the parsed data field and
|
50
|
+
# returned with the remaining data sequence
|
51
|
+
# instead
|
52
|
+
def self.parse_from_formatted(data, data_class = nil)
|
53
|
+
len = data[0...8].to_i
|
54
|
+
parsed = data[8...8+len]
|
55
|
+
remaining = data[8+len...data.size]
|
56
|
+
return parsed, remaining if data_class.nil?
|
57
|
+
return data_class.from_s(parsed), remaining
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# a single field trasnmitted via a message,
|
62
|
+
# containing a key / value pair
|
63
|
+
class Field
|
64
|
+
attr_accessor :name, :value
|
65
|
+
|
66
|
+
def initialize(args = {})
|
67
|
+
@name = args[:name].nil? ? "" : args[:name]
|
68
|
+
@value = args[:value].nil? ? "" : args[:value]
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
Formatter::format_with_size(@name) + Formatter::format_with_size(@value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.from_s(data)
|
76
|
+
field = Field.new
|
77
|
+
field.name, data = Formatter::parse_from_formatted(data)
|
78
|
+
field.value, data = Formatter::parse_from_formatted(data)
|
79
|
+
return field
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# header contains various descriptive properies
|
84
|
+
# about a message
|
85
|
+
class Header
|
86
|
+
attr_accessor :type, :target
|
87
|
+
|
88
|
+
def initialize(args = {})
|
89
|
+
@type = args[:type].nil? ? "" : args[:type]
|
90
|
+
@target = args[:target].nil? ? "" : args[:target]
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_s
|
94
|
+
Formatter::format_with_size(@type) + Formatter::format_with_size(@target)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.from_s(data)
|
98
|
+
header = Header.new
|
99
|
+
header.type, data = Formatter::parse_from_formatted(data)
|
100
|
+
header.target, data = Formatter::parse_from_formatted(data)
|
101
|
+
return header
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# body consists of a list of data fields
|
106
|
+
class Body
|
107
|
+
attr_accessor :fields
|
108
|
+
|
109
|
+
def initialize
|
110
|
+
@fields = []
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
s = ''
|
115
|
+
@fields.each { |field|
|
116
|
+
fs = field.to_s
|
117
|
+
s += Formatter::format_with_size(fs)
|
118
|
+
}
|
119
|
+
return s
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.from_s(data)
|
123
|
+
body = Body.new
|
124
|
+
while(data != "")
|
125
|
+
field, data = Formatter::parse_from_formatted(data)
|
126
|
+
field = Field.from_s field
|
127
|
+
body.fields.push field
|
128
|
+
end
|
129
|
+
return body
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# message contains a header / body
|
134
|
+
class Message
|
135
|
+
attr_accessor :header, :body
|
136
|
+
|
137
|
+
def initialize
|
138
|
+
@header = Header.new
|
139
|
+
@body = Body.new
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_s
|
143
|
+
Formatter::format_with_size(@header) + Formatter::format_with_size(@body)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.from_s(data)
|
147
|
+
message = Message.new
|
148
|
+
message.header, data = Formatter::parse_from_formatted(data, Header)
|
149
|
+
message.body, data = Formatter::parse_from_formatted(data, Body)
|
150
|
+
return message
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end # module Message
|
155
|
+
|
156
|
+
end # module Simrpc
|
data/lib/simrpc/node.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# simrpc node 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
|
+
module Simrpc
|
27
|
+
|
28
|
+
# Simrpc Method Message Controller, generates and handles method messages
|
29
|
+
class MethodMessageController
|
30
|
+
public
|
31
|
+
# initialize with a specified schema definition
|
32
|
+
def initialize(schema_def)
|
33
|
+
@schema_def = schema_def
|
34
|
+
end
|
35
|
+
|
36
|
+
# generate new new method message, setting the message
|
37
|
+
# target to the specified method name, and setting the fields
|
38
|
+
# on the message to the method arguments
|
39
|
+
def generate(method_name, args)
|
40
|
+
@schema_def.methods.each { |method|
|
41
|
+
if method.name == method_name
|
42
|
+
msg = Message::Message.new
|
43
|
+
msg.header.type = 'request'
|
44
|
+
msg.header.target = method.name
|
45
|
+
|
46
|
+
# loop through each param, convering corresponding
|
47
|
+
# argument to message field and adding it to msg
|
48
|
+
i = 0
|
49
|
+
method.parameters.each { |param|
|
50
|
+
field = Message::Field.new
|
51
|
+
field.name = param.name
|
52
|
+
field.value = param.to_s(args[i], @schema_def)
|
53
|
+
msg.body.fields.push field
|
54
|
+
i += 1
|
55
|
+
}
|
56
|
+
|
57
|
+
return msg
|
58
|
+
end
|
59
|
+
}
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# should be invoked when a message is received,
|
64
|
+
# takes a message, converts it into a method call, and calls the corresponding
|
65
|
+
# handler in the provided schema. Takes return arguments and sends back to caller
|
66
|
+
def message_received(node, message, reply_to)
|
67
|
+
message = Message::Message::from_s(message)
|
68
|
+
@schema_def.methods.each { |method|
|
69
|
+
|
70
|
+
if method.name == message.header.target
|
71
|
+
Logger.info "received method #{method.name} message "
|
72
|
+
|
73
|
+
# for request messages, dispatch to method handler
|
74
|
+
if message.header.type != 'response' && method.handler != nil
|
75
|
+
# order the params
|
76
|
+
params = []
|
77
|
+
method.parameters.each { |data_field|
|
78
|
+
value_field = message.body.fields.find { |f| f.name == data_field.name }
|
79
|
+
params.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
|
80
|
+
}
|
81
|
+
|
82
|
+
Logger.info "invoking #{method.name} handler "
|
83
|
+
|
84
|
+
# invoke method handler
|
85
|
+
return_values = method.handler.call(*params) # FIXME handlers can't use 'return' as this will fall through here
|
86
|
+
# FIXME throw a catch block around this call to catch all handler exceptions
|
87
|
+
return_values = [return_values] unless return_values.is_a? Array
|
88
|
+
|
89
|
+
# if method returns no values, do not return response
|
90
|
+
unless method.return_values.size == 0
|
91
|
+
|
92
|
+
# consruct and send response message using return values
|
93
|
+
response = Message::Message.new
|
94
|
+
response.header.type = 'response'
|
95
|
+
response.header.target = method.name
|
96
|
+
(0...method.return_values.size).each { |rvi|
|
97
|
+
field = Message::Field.new
|
98
|
+
field.name = method.return_values[rvi].name
|
99
|
+
field_def = method.return_values.find { |rv| rv.name == field.name }
|
100
|
+
field.value = field_def.to_s(return_values[rvi], @schema_def) unless field_def.nil? # TODO what if field_def is nil
|
101
|
+
response.body.fields.push field
|
102
|
+
}
|
103
|
+
Logger.info "responding to #{reply_to}"
|
104
|
+
node.send_message(reply_to, response)
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
# for response values just return converted return values
|
109
|
+
else
|
110
|
+
results = []
|
111
|
+
method.return_values.each { |data_field|
|
112
|
+
value_field = message.body.fields.find { |f| f.name == data_field.name }
|
113
|
+
results.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
|
114
|
+
}
|
115
|
+
return results
|
116
|
+
end
|
117
|
+
end
|
118
|
+
}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Simrpc Node represents ths main api which to communicate and send/listen for data.
|
123
|
+
class Node
|
124
|
+
|
125
|
+
# Instantiate it w/ a specified id
|
126
|
+
# or one will be autogenerated. Specify schema (or location) containing
|
127
|
+
# data and methods which to invoke and/or handle. Optionally specify
|
128
|
+
# a remote destination which to send new messages to. Automatically listens
|
129
|
+
# for incoming messages.
|
130
|
+
def initialize(args = {})
|
131
|
+
@id = args[:id] if args.has_key? :id
|
132
|
+
@schema = args[:schema]
|
133
|
+
@schema_file = args[:schema_file]
|
134
|
+
@destination = args[:destination]
|
135
|
+
|
136
|
+
if !@schema.nil?
|
137
|
+
@schema_def = Schema::Parser.parse(:schema => @schema)
|
138
|
+
elsif !@schema_file.nil?
|
139
|
+
@schema_def = Schema::Parser.parse(:file => @schema_file)
|
140
|
+
end
|
141
|
+
raise ArgumentError, "schema_def cannot be nil" if @schema_def.nil?
|
142
|
+
@mmc = MethodMessageController.new(@schema_def)
|
143
|
+
@message_lock = Semaphore.new(1)
|
144
|
+
@message_lock.wait
|
145
|
+
|
146
|
+
# FIXME currently not allowing for any other params to be passed into
|
147
|
+
# QpidAdapter::Node such as broker ip or port, NEED TO FIX THIS
|
148
|
+
@qpid_node = QpidAdapter::Node.new(:id => @id)
|
149
|
+
@qpid_node.async_accept { |node, msg, reply_to|
|
150
|
+
results = @mmc.message_received(node, msg, reply_to)
|
151
|
+
message_received(results)
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def id
|
156
|
+
return @id unless @id.nil?
|
157
|
+
return @qpid_node.node_id
|
158
|
+
end
|
159
|
+
|
160
|
+
# implements, message_received callback to be notified when qpid receives a message
|
161
|
+
def message_received(results)
|
162
|
+
@message_results = results
|
163
|
+
@message_lock.signal
|
164
|
+
end
|
165
|
+
|
166
|
+
# wait until the node is no longer accepting messages
|
167
|
+
def join
|
168
|
+
@qpid_node.join
|
169
|
+
end
|
170
|
+
|
171
|
+
# add a handler which to invoke when an schema method is invoked
|
172
|
+
def handle_method(method, &handler)
|
173
|
+
@schema_def.methods.each { |smethod|
|
174
|
+
if smethod.name == method.to_s
|
175
|
+
smethod.handler = handler
|
176
|
+
break
|
177
|
+
end
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
# send method request to remote destination w/ the specified args
|
182
|
+
def send_method(method_name, destination, *args)
|
183
|
+
# generate and send new method message
|
184
|
+
msg = @mmc.generate(method_name, args)
|
185
|
+
@qpid_node.send_message(destination + "-queue", msg)
|
186
|
+
|
187
|
+
# FIXME race condition if response is received b4 wait is invoked
|
188
|
+
|
189
|
+
# block if we are expecting return values
|
190
|
+
if @schema_def.methods.find{|m| m.name == method_name}.return_values.size != 0
|
191
|
+
@message_lock.wait # block until response received
|
192
|
+
|
193
|
+
# return return values
|
194
|
+
#@message_received.body.fields.collect { |f| f.value }
|
195
|
+
return *@message_results
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# can invoke schema methods directly on Node instances, this will catch
|
200
|
+
# them and send them onto the destination
|
201
|
+
def method_missing(method_id, *args)
|
202
|
+
send_method(method_id.to_s, @destination, *args)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end # module Simrpc
|