rhcp 0.1.3 → 0.1.4
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/History.txt +3 -3
- data/Manifest.txt +35 -0
- data/README.txt +4 -4
- data/Rakefile +4 -0
- data/build_gem.sh +21 -0
- data/docroot/builder.js +136 -0
- data/docroot/controls.js +965 -0
- data/docroot/dragdrop.js +975 -0
- data/docroot/effects.js +1130 -0
- data/docroot/index.html +176 -0
- data/docroot/prototype.js +4320 -0
- data/docroot/scriptaculous.js +60 -0
- data/docroot/slider.js +275 -0
- data/docroot/sound.js +55 -0
- data/docroot/unittest.js +568 -0
- data/lib/rhcp.rb +47 -0
- data/lib/rhcp/broker.rb +40 -0
- data/lib/rhcp/client/command_param_stub.rb +51 -0
- data/lib/rhcp/client/command_stub.rb +64 -0
- data/lib/rhcp/client/http_broker.rb +86 -0
- data/lib/rhcp/command.rb +143 -0
- data/lib/rhcp/command_param.rb +93 -0
- data/lib/rhcp/dispatching_broker.rb +25 -0
- data/lib/rhcp/http_exporter.rb +159 -0
- data/lib/rhcp/request.rb +93 -0
- data/lib/rhcp/response.rb +62 -0
- data/lib/rhcp/rhcp_exception.rb +6 -0
- data/start_test_server.sh +3 -0
- data/test/rhcp/broker_test.rb +29 -0
- data/test/rhcp/client/command_param_stub_test.rb +73 -0
- data/test/rhcp/client/command_stub_test.rb +56 -0
- data/test/rhcp/command_param_test.rb +72 -0
- data/test/rhcp/command_test.rb +136 -0
- data/test/rhcp/dispatching_broker_test.rb +41 -0
- data/test/rhcp/http_exporter_test.rb +113 -0
- data/test/rhcp/http_registry_test.rb +73 -0
- data/test/rhcp/http_test_server.rb +70 -0
- data/test/rhcp/request_test.rb +124 -0
- data/test/rhcp/response_test.rb +45 -0
- metadata +41 -6
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rhcp/rhcp_exception'
|
2
|
+
require 'rhcp/broker'
|
3
|
+
|
4
|
+
module RHCP
|
5
|
+
|
6
|
+
class DispatchingBroker < RHCP::Broker
|
7
|
+
def initialize
|
8
|
+
@known_commands = Hash.new()
|
9
|
+
end
|
10
|
+
|
11
|
+
# returns a list of all known commands
|
12
|
+
def get_command_list()
|
13
|
+
@known_commands
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_broker(new_broker)
|
17
|
+
new_broker.get_command_list.each do |name, command|
|
18
|
+
raise RHCP::RhcpException.new("duplicate command: '#{name}' has already been defined.") if @known_commands.has_key?(name)
|
19
|
+
@known_commands[name] = command
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
require 'rhcp/broker'
|
6
|
+
require 'rhcp/request'
|
7
|
+
require 'rhcp/rhcp_exception'
|
8
|
+
|
9
|
+
include WEBrick
|
10
|
+
|
11
|
+
module RHCP
|
12
|
+
|
13
|
+
# TODO add some logging
|
14
|
+
|
15
|
+
# The +HttpExporter+ connects to the RHCP::Broker and exports all commands
|
16
|
+
# registered there via http, i.e. it servers requests to the URLs
|
17
|
+
#
|
18
|
+
# /rhcp/get_commands
|
19
|
+
# /rhcp/get_lookup_values
|
20
|
+
# /rhcp/execute
|
21
|
+
#
|
22
|
+
# All data is transferred in JSON format.
|
23
|
+
class HttpExporter
|
24
|
+
|
25
|
+
DEFAULT_PORT = 42000
|
26
|
+
DEFAULT_PREFIX = "rhcp"
|
27
|
+
|
28
|
+
# by default, a new HttpExporter will create it's own +HTTPServer+
|
29
|
+
# on port 42000 - if you want to change this port, you can pass the port
|
30
|
+
# to use as +port+ option. If you want to use an existing +HTTPServer+
|
31
|
+
# instance, you can pass it as +server+ option instead.
|
32
|
+
#
|
33
|
+
# Also, you can specify the prefix for the rhcp URLs using the +prefix+
|
34
|
+
# option - otherwise, all
|
35
|
+
# rhcp actions will be exported at
|
36
|
+
# /rhcp/get_commands
|
37
|
+
# /rhcp/get_lookup_values
|
38
|
+
# /rhcp/execute
|
39
|
+
# If you change this, please be aware that all clients that want to connect
|
40
|
+
# to this server need to be configured accordingly.
|
41
|
+
def initialize(broker, options = Hash.new())
|
42
|
+
|
43
|
+
@logger = RHCP::ModuleHelper::instance.logger
|
44
|
+
@broker = broker
|
45
|
+
|
46
|
+
# build your own server or use the one passed in as param
|
47
|
+
port = options.has_key?(:port) ? options[:port] : DEFAULT_PORT
|
48
|
+
|
49
|
+
if options.has_key?(:server)
|
50
|
+
@server = options[:server]
|
51
|
+
@logger.debug("using existing server #{@server}")
|
52
|
+
else
|
53
|
+
@logger.debug("opening own server on port #{port}")
|
54
|
+
@server = HTTPServer.new( :Port => port )
|
55
|
+
end
|
56
|
+
|
57
|
+
@url_prefix = options.has_key?(:prefix) ? options[:prefix] : DEFAULT_PREFIX
|
58
|
+
@server.mount_proc("/#{@url_prefix}/") {|req, res|
|
59
|
+
res.body = "<HTML>hello (again)</HTML>"
|
60
|
+
res['Content-Type'] = "text/html"
|
61
|
+
}
|
62
|
+
# TODO this path should probably be quite relative
|
63
|
+
@server.mount "/#{@url_prefix}/info", HTTPServlet::FileHandler, "docroot"
|
64
|
+
@server.mount "/#{@url_prefix}/info2", HTTPServlet::FileHandler, "qooxdoo_rhcp/build"
|
65
|
+
@server.mount "/#{@url_prefix}/get_commands", GetCommandsServlet, @broker
|
66
|
+
@server.mount "/#{@url_prefix}/get_lookup_values", GetLookupValuesServlet, @broker
|
67
|
+
@server.mount "/#{@url_prefix}/execute", ExecuteServlet, @broker
|
68
|
+
@logger.info("http exporter has been initialized - once started, it will listen on port '#{port}' for URLs starting with prefix '#{@url_prefix}'")
|
69
|
+
end
|
70
|
+
|
71
|
+
class BaseServlet < HTTPServlet::AbstractServlet
|
72
|
+
|
73
|
+
def initialize(server, broker)
|
74
|
+
super(server)
|
75
|
+
@broker = broker
|
76
|
+
end
|
77
|
+
|
78
|
+
def do_GET(req, res)
|
79
|
+
res['Content-Type'] = "application/json"
|
80
|
+
begin
|
81
|
+
#@logger.info("executing #{req}")
|
82
|
+
do_it(req, res)
|
83
|
+
#@logger.info("finished executing #{req}")
|
84
|
+
rescue RHCP::RhcpException => ex
|
85
|
+
# TODO define error format (should we send back a JSON-ified response object here?)
|
86
|
+
@logger.error("got an exception while executing request . #{ex}")
|
87
|
+
res.status = 500
|
88
|
+
res.body = [ ex.to_s ].to_json()
|
89
|
+
#puts ex
|
90
|
+
#puts ex.backtrace.join("\n")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# TODO is this a good idea?
|
95
|
+
def do_POST(req, res)
|
96
|
+
do_GET(req, res)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
class GetCommandsServlet < BaseServlet
|
102
|
+
|
103
|
+
def do_it(req, res)
|
104
|
+
commands = @broker.get_command_list
|
105
|
+
puts "about to send back commands : #{commands.values.to_json()}"
|
106
|
+
res.body = commands.values.to_json()
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class GetLookupValuesServlet < BaseServlet
|
111
|
+
|
112
|
+
def do_it(req, res)
|
113
|
+
partial_value = req.query.has_key?('partial') ? req.query['partial'] : ''
|
114
|
+
command = @broker.get_command(req.query['command'])
|
115
|
+
param = command.get_param(req.query['param'])
|
116
|
+
lookup_values = param.get_lookup_values(partial_value)
|
117
|
+
res.body = lookup_values.to_json()
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class ExecuteServlet < BaseServlet
|
123
|
+
|
124
|
+
def do_it(req, res)
|
125
|
+
@logger.info("got an execute request : #{req}")
|
126
|
+
# TODO filter "nocache" parameters from query string
|
127
|
+
request = RHCP::Request.reconstruct_from_json(@broker, req.body)
|
128
|
+
|
129
|
+
response = request.execute()
|
130
|
+
|
131
|
+
@logger.debug("sending back response : #{response.to_json()}")
|
132
|
+
res.body = response.to_json()
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
def run
|
138
|
+
@server.start
|
139
|
+
end
|
140
|
+
|
141
|
+
def start
|
142
|
+
puts "about to start server..."
|
143
|
+
@thread = Thread.new {
|
144
|
+
puts "in thread; starting server..."
|
145
|
+
@server.start
|
146
|
+
puts "in thread: continuing..."
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def stop
|
151
|
+
puts "about to stop server"
|
152
|
+
@server.stop
|
153
|
+
puts "thread stopped"
|
154
|
+
# TODO wait until after the server has ended - when this method exits, the server is still shutting down
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
data/lib/rhcp/request.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'rhcp/rhcp_exception'
|
2
|
+
require 'rhcp/broker'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module RHCP
|
8
|
+
|
9
|
+
# This class represents a request made by a RHCP client to a RHCP server.
|
10
|
+
# It is passed as an argument to the method that should execute the requested
|
11
|
+
# command
|
12
|
+
class Request
|
13
|
+
|
14
|
+
attr_reader :command
|
15
|
+
attr_reader :param_values
|
16
|
+
|
17
|
+
# default constructor; will throw exceptions on invalid values
|
18
|
+
def initialize(command, param_values = {})
|
19
|
+
raise RHCP::RhcpException.new("command may not be null") if command == nil
|
20
|
+
|
21
|
+
# autobox the parameters if necessary
|
22
|
+
param_values.each do |k,v|
|
23
|
+
if ! v.instance_of?(Array)
|
24
|
+
param_values[k] = [ v ]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# check all param values for plausibility
|
29
|
+
param_values.each do |key,value|
|
30
|
+
command.get_param(key).check_param_is_valid(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
# check that we've got all mandatory params
|
34
|
+
command.params.each do |name, param|
|
35
|
+
raise RHCP::RhcpException.new("missing mandatory parameter '#{name}'") if param.mandatory && ! param_values.has_key?(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
@command = command
|
39
|
+
@param_values = param_values
|
40
|
+
end
|
41
|
+
|
42
|
+
# used to retrieve the value for the specified parameter
|
43
|
+
# returns either the value or an array of values if the parameter allows
|
44
|
+
# multiple values
|
45
|
+
def get_param_value(param_name)
|
46
|
+
raise "no such parameter : #{param_name}" unless @param_values.has_key?(param_name)
|
47
|
+
param = @command.get_param(param_name)
|
48
|
+
if (param.allows_multiple_values)
|
49
|
+
@param_values[param_name]
|
50
|
+
else
|
51
|
+
@param_values[param_name][0]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_param_value(param_name)
|
56
|
+
@param_values.has_key?(param_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# convenience method that executes the command that actually delegates to the
|
60
|
+
# command that's inside this request
|
61
|
+
def execute
|
62
|
+
@command.execute_request(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
# reconstructs the request from it's JSON representation
|
66
|
+
# Since the JSON version of a request does hold the command name instead
|
67
|
+
# of the full command only, a broker is needed to lookup the command by
|
68
|
+
# it's name
|
69
|
+
#
|
70
|
+
# Params:
|
71
|
+
# +broker+ is the broker to use for command lookup
|
72
|
+
# +json_data+ is the JSON data that represents the request
|
73
|
+
def self.reconstruct_from_json(broker, json_data)
|
74
|
+
object = JSON.parse(json_data)
|
75
|
+
command = broker.get_command(object['command_name'])
|
76
|
+
self.new(command, object['param_values'])
|
77
|
+
end
|
78
|
+
|
79
|
+
# returns a JSON representation of this request.
|
80
|
+
def to_json(*args)
|
81
|
+
{
|
82
|
+
'command_name' => @command.name,
|
83
|
+
'param_values' => @param_values
|
84
|
+
}.to_json(*args)
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
"#{@command.name} (#{@param_values})"
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module RHCP
|
5
|
+
|
6
|
+
class Response
|
7
|
+
|
8
|
+
class Status
|
9
|
+
OK="ok"
|
10
|
+
ERROR="error"
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO these should be attr_reader's, but then we've got to solve json_create() differently
|
14
|
+
attr_accessor :status
|
15
|
+
attr_accessor :error_text
|
16
|
+
attr_accessor :error_detail
|
17
|
+
attr_accessor :data
|
18
|
+
|
19
|
+
# textual description of the result (optional)
|
20
|
+
attr_accessor :result_text
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@status = Status::OK
|
24
|
+
@error_text = ""
|
25
|
+
@error_detail = ""
|
26
|
+
@result_text = "";
|
27
|
+
end
|
28
|
+
|
29
|
+
def mark_as_error(text, detail="")
|
30
|
+
@status = Status::ERROR
|
31
|
+
@error_text = text
|
32
|
+
@error_detail = detail
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_payload(data)
|
36
|
+
@data = data
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.reconstruct_from_json(json_data)
|
40
|
+
object = JSON.parse(json_data)
|
41
|
+
instance = self.new()
|
42
|
+
instance.status = object["status"]
|
43
|
+
instance.error_text = object["error_text"]
|
44
|
+
instance.error_detail = object["error_detail"]
|
45
|
+
instance.set_payload(object["data"])
|
46
|
+
instance.result_text = object['result_text']
|
47
|
+
instance
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_json(*args)
|
51
|
+
{
|
52
|
+
'status' => @status,
|
53
|
+
'error_text' => @error_text,
|
54
|
+
'error_detail' => @error_detail,
|
55
|
+
'data' => @data, # TODO what about JSONinification of data? (probably data should be JSON-ish data only, i.e. no special objects)
|
56
|
+
'result_text' => @result_text
|
57
|
+
}.to_json(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'rhcp'
|
6
|
+
|
7
|
+
class BrokerTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_register_commands
|
10
|
+
broker = RHCP::Broker.new()
|
11
|
+
assert_not_nil broker
|
12
|
+
commands = broker.get_command_list
|
13
|
+
assert_not_nil commands
|
14
|
+
assert_equal 0, commands.size
|
15
|
+
|
16
|
+
command = RHCP::Command.new("test", "a test command", lambda {})
|
17
|
+
broker.register_command(command)
|
18
|
+
commands = broker.get_command_list
|
19
|
+
assert_equal 1, commands.size
|
20
|
+
assert_equal command, commands["test"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_register_duplicate
|
24
|
+
broker = RHCP::Broker.new()
|
25
|
+
broker.register_command RHCP::Command.new("test", "a test command", lambda {})
|
26
|
+
assert_raise(RHCP::RhcpException) { broker.register_command RHCP::Command.new("test", "a command with the same name", lambda {}) }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rhcp/client/command_param_stub'
|
5
|
+
require 'rhcp/command_param'
|
6
|
+
|
7
|
+
class CommandParamStubTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def param_lookup
|
10
|
+
["foo", "bar", "baz"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@p = RHCP::CommandParam.new("test", "this param is used for testing purposes only",
|
15
|
+
:mandatory => true,
|
16
|
+
:allows_multiple_values => true,
|
17
|
+
:lookup_method => self.method(:param_lookup),
|
18
|
+
:is_default_param => true
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_json
|
23
|
+
json = @p.to_json
|
24
|
+
puts "json : >>#{json}<<"
|
25
|
+
p2 = RHCP::Client::CommandParamStub.reconstruct_from_json(json)
|
26
|
+
assert_not_nil p2
|
27
|
+
assert_instance_of RHCP::Client::CommandParamStub, p2
|
28
|
+
assert_equal @p.name, p2.name
|
29
|
+
assert_equal @p.description, p2.description
|
30
|
+
assert_equal @p.allows_multiple_values, p2.allows_multiple_values
|
31
|
+
assert_equal @p.has_lookup_values, p2.has_lookup_values
|
32
|
+
assert_equal @p.is_default_param, p2.is_default_param
|
33
|
+
assert_equal @p.mandatory, p2.mandatory
|
34
|
+
|
35
|
+
json_hash = JSON.parse(json)
|
36
|
+
p3 = RHCP::Client::CommandParamStub.reconstruct_from_json(json_hash)
|
37
|
+
assert_instance_of RHCP::Client::CommandParamStub, p3
|
38
|
+
assert_equal @p.name, p3.name
|
39
|
+
assert_equal @p.description, p3.description
|
40
|
+
assert_equal @p.allows_multiple_values, p3.allows_multiple_values
|
41
|
+
end
|
42
|
+
|
43
|
+
# when a param is "stubbed", it should be possible to inject a method
|
44
|
+
# for retrieving the lookup values
|
45
|
+
def test_stubbing
|
46
|
+
json = @p.to_json
|
47
|
+
stub = RHCP::Client::CommandParamStub.reconstruct_from_json(json)
|
48
|
+
stub.get_lookup_values_block = lambda {
|
49
|
+
|partial_value|
|
50
|
+
[ "mascarpone", "limoncello" ]
|
51
|
+
}
|
52
|
+
lookup_values = stub.get_lookup_values()
|
53
|
+
assert_equal [ "mascarpone", "limoncello" ], lookup_values
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_stubbing_without_lookup_values
|
57
|
+
p = RHCP::CommandParam.new("test", "without lookup values",
|
58
|
+
:mandatory => true,
|
59
|
+
:allows_multiple_values => true,
|
60
|
+
:is_default_param => true
|
61
|
+
)
|
62
|
+
stub = RHCP::Client::CommandParamStub.reconstruct_from_json(p.to_json())
|
63
|
+
has_been_invoked = false
|
64
|
+
stub.get_lookup_values_block = lambda {
|
65
|
+
|partial_value|
|
66
|
+
has_been_invoked = true
|
67
|
+
}
|
68
|
+
stub.get_lookup_values()
|
69
|
+
assert_equal false, has_been_invoked
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end
|