simrpc 0.1 → 0.2
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/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
|