omf_sfa 0.2.6 → 0.2.7
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/bin/brite2rspec.rb +0 -0
- data/lib/omf-sfa/am.rb +4 -1
- data/lib/omf-sfa/am/am-rest/promise_handler.rb +119 -0
- data/lib/omf-sfa/am/am-rest/rest_handler.rb +301 -45
- data/lib/omf-sfa/resource.rb +3 -1
- data/lib/omf-sfa/resource/gurn.rb +5 -3
- data/lib/omf-sfa/resource/node.rb +1 -1
- data/lib/omf-sfa/resource/oproperty.rb +13 -14
- data/lib/omf-sfa/resource/oresource.rb +14 -5
- data/lib/omf-sfa/resource/sfa_base.rb +3 -1
- data/lib/omf-sfa/util/graph_json.rb +34 -14
- data/lib/omf-sfa/util/promise.rb +325 -0
- data/lib/omf-sfa/version.rb +1 -2
- data/omf_sfa.gemspec +1 -1
- metadata +64 -117
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c8e057cf74d9cfbe634681eb5a86a56d1b06f48e
|
4
|
+
data.tar.gz: df5ac1030bab27d3db06ff6bd67e72c899ae75ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e561cdb795206908e2dc0f32cc58be10af3e5cb959bfafdf89f6b3be285ee470e92c46ce012b11dad2749ae998356aab1576fc71cffcc80272104eeb3100c59
|
7
|
+
data.tar.gz: dfbc2f4d3e40a9c4895c7ea6479d3e5dc7cd2a7df030522ea257aac78a8649e856745130048455501c067b031a70359800a1380df97d6bf79bd79a720082d1c9
|
data/.gitignore
CHANGED
data/bin/brite2rspec.rb
CHANGED
File without changes
|
data/lib/omf-sfa/am.rb
CHANGED
@@ -0,0 +1,119 @@
|
|
1
|
+
|
2
|
+
module OMF::SFA::AM::Rest
|
3
|
+
|
4
|
+
class PromiseHandler < RestHandler
|
5
|
+
|
6
|
+
@@contexts = {}
|
7
|
+
|
8
|
+
def self.register_promise(promise, uuid = nil, html_reply = false, collection_set = nil)
|
9
|
+
debug "Registering promise '#{uuid}' - #{promise}"
|
10
|
+
uuid ||= UUIDTools::UUID.random_create
|
11
|
+
#uuid = 1 # TODO: REMOVE
|
12
|
+
@@contexts[uuid.to_s] = {
|
13
|
+
promise: promise,
|
14
|
+
html_reply: html_reply,
|
15
|
+
collection_set: collection_set || Set.new,
|
16
|
+
timestamp: Time.now
|
17
|
+
}
|
18
|
+
path = "/promises/#{uuid}"
|
19
|
+
debug "Redirecting to #{path}"
|
20
|
+
return [302, {'Location' => path}, ['Promised, but not ready yet.']]
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
begin
|
25
|
+
req = ::Rack::Request.new(env)
|
26
|
+
headers = {
|
27
|
+
'Access-Control-Allow-Origin' => '*',
|
28
|
+
'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
|
29
|
+
'Access-Control-Allow-Headers' => 'origin, x-csrftoken, content-type, accept'
|
30
|
+
}
|
31
|
+
if req.request_method == 'OPTIONS'
|
32
|
+
return [200 , headers, ""]
|
33
|
+
end
|
34
|
+
|
35
|
+
uuid = req.path_info.split('/')[-1]
|
36
|
+
debug "Checking for promise '#{uuid}'"
|
37
|
+
|
38
|
+
unless context = @@contexts[uuid]
|
39
|
+
return [404, {}, "Can't find requested promise"]
|
40
|
+
end
|
41
|
+
context[:timestamp] = Time.now # keep reaper away
|
42
|
+
promise = context[:promise]
|
43
|
+
case promise.status
|
44
|
+
when :pending
|
45
|
+
return retry_later(req, context)
|
46
|
+
when :rejected
|
47
|
+
body = {
|
48
|
+
type: 'error',
|
49
|
+
error: {
|
50
|
+
reason: promise.error_msg,
|
51
|
+
code: promise.error_code
|
52
|
+
}
|
53
|
+
}
|
54
|
+
headers['Content-Type'] = 'application/json'
|
55
|
+
return [500, headers, JSON.pretty_generate(body)]
|
56
|
+
end
|
57
|
+
|
58
|
+
# OK, the promise seems to have been resolved
|
59
|
+
content_type, body = promise.value
|
60
|
+
#puts ">>>>VALUE #{body.class} - #{content_type}>>>>> #{body}"
|
61
|
+
if body.is_a? Thin::AsyncResponse
|
62
|
+
return body.finish
|
63
|
+
end
|
64
|
+
|
65
|
+
begin
|
66
|
+
if content_type == 'text/html'
|
67
|
+
#body = self.class.convert_to_html(body, env, Set.new((@coll_handlers || {}).keys))
|
68
|
+
body = convert_to_html(body, env, {}, context[:collection_set])
|
69
|
+
else
|
70
|
+
content_type = 'application/json'
|
71
|
+
body = JSON.pretty_generate(body)
|
72
|
+
end
|
73
|
+
rescue OMF::SFA::Util::PromiseUnresolvedException => pex
|
74
|
+
proxy = OMF::SFA::Util::Promise.new
|
75
|
+
pex.promise.on_success do |d|
|
76
|
+
proxy.resolve [content_type, body]
|
77
|
+
end.on_error(proxy)
|
78
|
+
uuid = pex.uuid
|
79
|
+
path = "/promises/#{uuid}"
|
80
|
+
require 'omf-sfa/am/am-rest/promise_handler' # delay loading as PromiseHandler sub classes this class
|
81
|
+
self.class.register_promise(proxy, uuid, context[:html_reply], context[:collection_set])
|
82
|
+
debug "Redirecting to #{path}"
|
83
|
+
return [302, {'Location' => path}, ['Promised, but not ready yet.']]
|
84
|
+
end
|
85
|
+
headers['Content-Type'] = content_type
|
86
|
+
return [200 , headers, (body || '') + "\n"]
|
87
|
+
|
88
|
+
rescue RackException => rex
|
89
|
+
return rex.reply
|
90
|
+
rescue Exception => ex
|
91
|
+
body = {
|
92
|
+
type: 'error',
|
93
|
+
error: {
|
94
|
+
reason: ex.to_s,
|
95
|
+
bt: ex.backtrace #.select {|l| !l.start_with?('/') }
|
96
|
+
}
|
97
|
+
}
|
98
|
+
warn "ERROR: #{ex}"
|
99
|
+
debug ex.backtrace.join("\n")
|
100
|
+
headers['Content-Type'] = 'application/json'
|
101
|
+
return [500, headers, JSON.pretty_generate(body)]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def retry_later(req, context)
|
106
|
+
body = {
|
107
|
+
type: 'retry',
|
108
|
+
delay: 10, # rex.delay,
|
109
|
+
url: absolute_path(req.env['REQUEST_PATH']),
|
110
|
+
progress: context[:promise].progress.map do |ts, msg|
|
111
|
+
"#{ts.utc.iso8601}: #{msg}"
|
112
|
+
end
|
113
|
+
}
|
114
|
+
debug "Retry later request - #{req.url}"
|
115
|
+
headers = {'Content-Type' => 'application/json'}
|
116
|
+
return [504, headers, JSON.pretty_generate(body)]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -8,6 +8,7 @@ require 'thin/async'
|
|
8
8
|
require 'cgi'
|
9
9
|
|
10
10
|
require 'omf_base/lobject'
|
11
|
+
require 'omf-sfa/util/promise'
|
11
12
|
|
12
13
|
|
13
14
|
module OMF::SFA::AM::Rest
|
@@ -75,6 +76,12 @@ module OMF::SFA::AM::Rest
|
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
79
|
+
class TemporaryUnavailableException < RackException
|
80
|
+
def initialize()
|
81
|
+
super 504, "Upstream servers haven't responded yet, please try again a bit later"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
78
85
|
class RedirectException < Exception
|
79
86
|
attr_reader :path
|
80
87
|
|
@@ -83,17 +90,68 @@ module OMF::SFA::AM::Rest
|
|
83
90
|
end
|
84
91
|
end
|
85
92
|
|
93
|
+
class ContentFoundException < Exception
|
94
|
+
def initialize(content, type = 'text', &block)
|
95
|
+
@type = case type.to_sym
|
96
|
+
when :json
|
97
|
+
'application/json'
|
98
|
+
when :xml
|
99
|
+
'text/xml'
|
100
|
+
when :text
|
101
|
+
'text'
|
102
|
+
else
|
103
|
+
(type || 'text').to_s
|
104
|
+
end
|
105
|
+
if content.is_a? OMF::SFA::Util::Promise
|
106
|
+
@content = OMF::SFA::Util::Promise.new
|
107
|
+
content.on_success do |pv|
|
108
|
+
v = block ? block.call(pv) : pv
|
109
|
+
@content.resolve [@type, v]
|
110
|
+
end.on_error(@content).on_progress(@content)
|
111
|
+
else
|
112
|
+
@content = content
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def reply
|
117
|
+
if @content.is_a? OMF::SFA::Util::Promise
|
118
|
+
require 'omf-sfa/am/am-rest/promise_handler' # delay loading as PromiseHandler sub classes this class
|
119
|
+
OMF::SFA::AM::Rest::PromiseHandler.register_promise(@content)
|
120
|
+
else
|
121
|
+
headers = {'Content-Type' => @type}
|
122
|
+
[200 , headers, @content]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# class PromiseUnresolvedException < Exception
|
128
|
+
# attr_reader :uuid, :promise
|
129
|
+
#
|
130
|
+
# def initialize(promise)
|
131
|
+
# @uuid = UUIDTools::UUID.random_create
|
132
|
+
# @promise = promise
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
|
86
136
|
# Raised when a request triggers an async call whose
|
87
137
|
# result we need before answering the request
|
88
138
|
#
|
89
139
|
class RetryLaterException < Exception
|
90
140
|
attr_reader :delay
|
91
141
|
|
92
|
-
def initialize(delay =
|
142
|
+
def initialize(delay = 10)
|
93
143
|
@delay = delay
|
94
144
|
end
|
95
145
|
end
|
96
146
|
|
147
|
+
# class FoundUnresolvedPromiseException < Exception
|
148
|
+
# attr_reader :promise
|
149
|
+
#
|
150
|
+
# def initialize(promise)
|
151
|
+
# @promise = promise
|
152
|
+
# end
|
153
|
+
# end
|
154
|
+
|
97
155
|
|
98
156
|
|
99
157
|
class RestHandler < OMF::Base::LObject
|
@@ -147,40 +205,63 @@ module OMF::SFA::AM::Rest
|
|
147
205
|
return [200 , headers, ""]
|
148
206
|
end
|
149
207
|
content_type, body = dispatch(req)
|
208
|
+
#puts "BODY(#{body.class}) - #{content_type}) >>>>> #{body}"
|
150
209
|
if body.is_a? Thin::AsyncResponse
|
151
210
|
return body.finish
|
152
211
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
212
|
+
content_type, body = _format_body(body, content_type, req, env)
|
213
|
+
# if req['_format'] == 'html'
|
214
|
+
# #body = self.class.convert_to_html(body, env, Set.new((@coll_handlers || {}).keys))
|
215
|
+
# body = convert_to_html(body, env, {}, Set.new((@coll_handlers || {}).keys))
|
216
|
+
# content_type = 'text/html'
|
217
|
+
# elsif content_type == 'application/json'
|
218
|
+
# body = JSON.pretty_generate(body)
|
219
|
+
# end
|
160
220
|
#return [200 ,{'Content-Type' => content_type}, body + "\n"]
|
161
221
|
headers['Content-Type'] = content_type
|
162
|
-
return [200 , headers, body + "\n"]
|
222
|
+
return [200 , headers, (body || '') + "\n"]
|
223
|
+
rescue ContentFoundException => cex
|
224
|
+
return cex.reply
|
163
225
|
rescue RackException => rex
|
226
|
+
unless rex.is_a? TemporaryUnavailableException
|
227
|
+
warn "Caught #{rex} - #{env['REQUEST_METHOD']} - #{env['REQUEST_PATH']}"
|
228
|
+
debug "#{rex.class} - #{req.inspect}"
|
229
|
+
debug rex.backtrace.join("\n")
|
230
|
+
end
|
164
231
|
return rex.reply
|
165
|
-
rescue
|
232
|
+
rescue OMF::SFA::Util::PromiseUnresolvedException => pex
|
233
|
+
require 'omf-sfa/am/am-rest/promise_handler' # delay loading as PromiseHandler sub classes this class
|
234
|
+
OMF::SFA::AM::Rest::PromiseHandler.register_promise(pex.promise,
|
235
|
+
pex.uuid,
|
236
|
+
req['_format'] == 'html',
|
237
|
+
Set.new((@coll_handlers || {}).keys))
|
238
|
+
#return [302, {'Location' => path}, ['Promised, but not ready yet.']]
|
239
|
+
rescue OMF::SFA::AM::Rest::RedirectException => rex
|
166
240
|
debug "Redirecting to #{rex.path}"
|
167
|
-
return [
|
241
|
+
return [302, {'Location' => rex.path}, ['Next window, please.']]
|
242
|
+
|
168
243
|
# rescue OMF::SFA::AM::AMManagerException => aex
|
169
244
|
# return RackException.new(400, aex.to_s).reply
|
170
|
-
rescue RetryLaterException => rex
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
245
|
+
# rescue RetryLaterException => rex
|
246
|
+
# body = {
|
247
|
+
# type: 'retry',
|
248
|
+
# delay: rex.delay,
|
249
|
+
# request_id: Thread.current[:request_context_id] || 'unknown'
|
250
|
+
# }
|
251
|
+
# debug "Retry later request - #{req.url}"
|
252
|
+
# if req['_format'] == 'html'
|
253
|
+
# refresh = rex.delay.to_s
|
254
|
+
# if (req_id = Thread.current[:request_context_id])
|
255
|
+
# refresh += "; url=#{req.url}&_request_id=#{req_id}"
|
256
|
+
# end
|
257
|
+
# headers['Refresh'] = refresh # 10; url=
|
258
|
+
# headers['X-Request-ID'] = req_id
|
259
|
+
# opts = {} #{html_header: "<META HTTP-EQUIV='refresh' CONTENT='#{rex.delay}'>"}
|
260
|
+
# body = convert_to_html(body, env, opts)
|
261
|
+
# return [200 , headers, body + "\n"]
|
262
|
+
# end
|
263
|
+
# headers['Content-Type'] = 'application/json'
|
264
|
+
# return [504, headers, JSON.pretty_generate(body)]
|
184
265
|
|
185
266
|
rescue Exception => ex
|
186
267
|
body = {
|
@@ -197,6 +278,37 @@ module OMF::SFA::AM::Rest
|
|
197
278
|
end
|
198
279
|
end
|
199
280
|
|
281
|
+
def _format_body(body, content_type, req, env, proxy_promise = nil)
|
282
|
+
begin
|
283
|
+
if req['_format'] == 'html'
|
284
|
+
#body = self.class.convert_to_html(body, env, Set.new((@coll_handlers || {}).keys))
|
285
|
+
content_type = 'text/html'
|
286
|
+
body = convert_to_html(body, env, {}, Set.new((@coll_handlers || {}).keys))
|
287
|
+
elsif content_type == 'application/json'
|
288
|
+
body = JSON.pretty_generate(body)
|
289
|
+
end
|
290
|
+
[content_type, body]
|
291
|
+
rescue OMF::SFA::Util::PromiseUnresolvedException => pex
|
292
|
+
proxy = OMF::SFA::Util::Promise.new
|
293
|
+
pex.promise.on_success do |d|
|
294
|
+
proxy.resolve [content_type, body]
|
295
|
+
end.on_error(proxy).on_progress(proxy)
|
296
|
+
raise OMF::SFA::Util::PromiseUnresolvedException.new proxy
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# def _x(promise, req)
|
301
|
+
# uuid = 1 #pex.uuid
|
302
|
+
# path = "/promises/#{uuid}"
|
303
|
+
# require 'omf-sfa/am/am-rest/promise_handler' # delay loading as PromiseHandler sub classes this class
|
304
|
+
# OMF::SFA::AM::Rest::PromiseHandler.register_promise(promise,
|
305
|
+
# uuid,
|
306
|
+
# req['_format'] == 'html',
|
307
|
+
# Set.new((@coll_handlers || {}).keys))
|
308
|
+
# debug "Redirecting to #{path}"
|
309
|
+
# return [302, {'Location' => path}, ['Promised, but not ready yet.']]
|
310
|
+
# end
|
311
|
+
|
200
312
|
def on_get(resource_uri, opts)
|
201
313
|
debug 'get: resource_uri: "', resource_uri, '"'
|
202
314
|
if resource_uri
|
@@ -207,6 +319,11 @@ module OMF::SFA::AM::Rest
|
|
207
319
|
end
|
208
320
|
end
|
209
321
|
|
322
|
+
def on_put(resource_uri, opts)
|
323
|
+
debug '>>> PUT NOT IMPLEMENTED'
|
324
|
+
raise UnsupportedMethodException.new('on_put', @resource_class)
|
325
|
+
end
|
326
|
+
|
210
327
|
def on_post(resource_uri, opts)
|
211
328
|
#debug 'POST: resource_uri "', resource_uri, '" - ', opts.inspect
|
212
329
|
description, format = parse_body(opts, [:json, :form])
|
@@ -214,7 +331,7 @@ module OMF::SFA::AM::Rest
|
|
214
331
|
|
215
332
|
if resource = opts[:resource]
|
216
333
|
debug 'POST: Modify ', resource, ' --- ', resource.class
|
217
|
-
modify_resource(resource, description, opts)
|
334
|
+
resource = modify_resource(resource, description, opts)
|
218
335
|
else
|
219
336
|
if description.is_a? Array
|
220
337
|
resources = description.map do |d|
|
@@ -268,7 +385,7 @@ module OMF::SFA::AM::Rest
|
|
268
385
|
|
269
386
|
def find_handler(path, opts)
|
270
387
|
#debug "find_handler: path; '#{path}' opts: #{opts}"
|
271
|
-
debug "find_handler: path
|
388
|
+
debug "find_handler: path: '#{path}'"
|
272
389
|
rid = path.shift
|
273
390
|
resource_id = opts[:resource_uri] = (rid ? URI.decode(rid) : nil) # make sure we get rid of any URI encoding
|
274
391
|
opts[:resource] = nil
|
@@ -279,6 +396,7 @@ module OMF::SFA::AM::Rest
|
|
279
396
|
|
280
397
|
raise OMF::SFA::AM::Rest::UnknownResourceException.new "Unknown resource '#{resource_id}'." unless resource
|
281
398
|
opts[:context] = resource
|
399
|
+
opts[:contexts][opts[:context_name].to_sym] = resource
|
282
400
|
comp = path.shift
|
283
401
|
if (handler = @coll_handlers[comp.to_sym])
|
284
402
|
opts[:context_name] = comp
|
@@ -292,7 +410,6 @@ module OMF::SFA::AM::Rest
|
|
292
410
|
end
|
293
411
|
|
294
412
|
|
295
|
-
|
296
413
|
protected
|
297
414
|
|
298
415
|
def modify_resource(resource, description, opts)
|
@@ -301,6 +418,7 @@ module OMF::SFA::AM::Rest
|
|
301
418
|
end
|
302
419
|
description.delete(:href)
|
303
420
|
resource.update(description) ? resource : nil
|
421
|
+
resource
|
304
422
|
end
|
305
423
|
|
306
424
|
|
@@ -384,15 +502,34 @@ module OMF::SFA::AM::Rest
|
|
384
502
|
#
|
385
503
|
def populate_opts(req, opts)
|
386
504
|
opts[:req] = req
|
505
|
+
opts[:context_name] = (req.env['REQUEST_PATH'].split('/') - req.path_info.split('/'))[-1]
|
506
|
+
opts[:contexts] ||= {}
|
387
507
|
path = req.path_info.split('/').select { |p| !p.empty? }
|
388
508
|
opts[:target] = find_handler(path, opts)
|
389
509
|
rl = req.params.delete('_level')
|
390
|
-
opts[:max_level] = rl ? rl.to_i :
|
510
|
+
opts[:max_level] = rl ? rl.to_i : 1
|
391
511
|
#opts[:target].inspect
|
392
512
|
opts
|
393
513
|
end
|
394
514
|
|
515
|
+
# Return a named context resource. If it is a promise and not yet
|
516
|
+
# available, a TemporaryUnavailableException is being thrown.
|
517
|
+
#
|
518
|
+
# For instance if we have a path /users/xxx/friends/yyy/favorites
|
519
|
+
# by the time we get to the 'favorites' handler, we can access
|
520
|
+
# the respective 'users', 'friends' object through this method.
|
521
|
+
# PLEASE note the plurals 'users', 'friends'.
|
522
|
+
#
|
523
|
+
def get_context_resource(name, opts)
|
524
|
+
resource = opts[:contexts][name]
|
525
|
+
if resource.is_a? OMF::SFA::Util::Promise
|
526
|
+
resource = resource.value(OMF::SFA::AM::Rest::TemporaryUnavailableException)
|
527
|
+
end
|
528
|
+
resource
|
529
|
+
end
|
530
|
+
|
395
531
|
def parse_body(opts, allowed_formats = [:json, :xml])
|
532
|
+
puts "ALLOWD>>>> #{allowed_formats}"
|
396
533
|
req = opts[:req]
|
397
534
|
body = req.body #req.POST
|
398
535
|
raise EmptyBodyException.new unless body
|
@@ -424,12 +561,16 @@ module OMF::SFA::AM::Rest
|
|
424
561
|
raise UnsupportedBodyFormatException.new(:json) unless allowed_formats.include?(:json)
|
425
562
|
jb = JSON.parse(body)
|
426
563
|
return [_rec_sym_keys(jb), :json]
|
564
|
+
when 'application/gjson'
|
565
|
+
raise UnsupportedBodyFormatException.new(:gjson) unless allowed_formats.include?(:gjson)
|
566
|
+
jb = JSON.parse(body)
|
567
|
+
return [_rec_sym_keys(jb), :gjson]
|
427
568
|
when 'text/xml'
|
428
569
|
xb = Nokogiri::XML(body)
|
429
570
|
raise UnsupportedBodyFormatException.new(:xml) unless allowed_formats.include?(:xml)
|
430
571
|
return [xb, :xml]
|
431
572
|
when 'application/x-www-form-urlencoded'
|
432
|
-
raise UnsupportedBodyFormatException.new(:
|
573
|
+
raise UnsupportedBodyFormatException.new(:form) unless allowed_formats.include?(:form)
|
433
574
|
fb = req.POST
|
434
575
|
#puts "FORM: #{fb.inspect}"
|
435
576
|
return [fb, :form]
|
@@ -447,11 +588,18 @@ module OMF::SFA::AM::Rest
|
|
447
588
|
def dispatch(req)
|
448
589
|
opts = {}
|
449
590
|
populate_opts(req, opts)
|
450
|
-
opts[:req] = req
|
591
|
+
#opts[:req] = req
|
451
592
|
#puts "OPTS>>>> #{opts.inspect}"
|
452
593
|
method = req.request_method
|
453
594
|
target = opts[:target] #|| self
|
595
|
+
if target.is_a? OMF::SFA::AM::Rest::ContentFoundException
|
596
|
+
raise target
|
597
|
+
end
|
454
598
|
resource_uri = opts[:resource_uri]
|
599
|
+
_dispatch(method, target, resource_uri, opts)
|
600
|
+
end
|
601
|
+
|
602
|
+
def _dispatch(method, target, resource_uri, opts)
|
455
603
|
case method
|
456
604
|
when 'GET'
|
457
605
|
res = target.on_get(resource_uri, opts)
|
@@ -468,22 +616,30 @@ module OMF::SFA::AM::Rest
|
|
468
616
|
|
469
617
|
def show_resource_status(resource, opts)
|
470
618
|
if resource
|
619
|
+
resource = resolve_promise(resource) do |r|
|
620
|
+
show_resource_status(r, opts)
|
621
|
+
end
|
471
622
|
about = opts[:req].path
|
472
|
-
|
623
|
+
refresh = ['', '1', 't', 'T', 'true', 'TRUE'].include?(opts[:req].params['_refresh'])
|
624
|
+
props = resource.to_hash({}, max_level: opts[:max_level], refresh: refresh)
|
473
625
|
props.delete(:type)
|
474
|
-
res = {
|
626
|
+
res = after_resource_to_hash_hook({
|
475
627
|
#:about => about,
|
476
628
|
:type => resource.resource_type,
|
477
|
-
}.merge!(props)
|
629
|
+
}.merge!(props), resource)
|
478
630
|
else
|
479
631
|
res = {:error => 'Unknown resource'}
|
480
632
|
end
|
481
|
-
|
482
|
-
['application/json', res]
|
633
|
+
check_for_promises 'application/json', res
|
483
634
|
end
|
484
635
|
|
636
|
+
def after_resource_to_hash_hook(res_hash, resource)
|
637
|
+
res_hash
|
638
|
+
end
|
485
639
|
|
486
|
-
|
640
|
+
def absolute_path(rel_path)
|
641
|
+
"http://#{Thread.current[:http_host]}#{rel_path.start_with?('/') ? '' : '/'}#{rel_path}"
|
642
|
+
end
|
487
643
|
|
488
644
|
def find_resource(resource_uri, description = {}, opts = {})
|
489
645
|
descr = description.dup
|
@@ -497,10 +653,21 @@ module OMF::SFA::AM::Rest
|
|
497
653
|
end
|
498
654
|
|
499
655
|
#authenticator = Thread.current["authenticator"]
|
656
|
+
descr = _find_resource_before_hook(descr, opts)
|
657
|
+
_find_resource(descr)
|
658
|
+
end
|
659
|
+
|
660
|
+
# Allow sub class to override actual finding of resource - may create it on the fly
|
661
|
+
def _find_resource(descr)
|
500
662
|
debug "Finding #{@resource_class}.first(#{descr})"
|
501
663
|
@resource_class.first(descr)
|
502
664
|
end
|
503
665
|
|
666
|
+
# Allow sub class to override search criteria for resource
|
667
|
+
def _find_resource_before_hook(descr, opts)
|
668
|
+
descr
|
669
|
+
end
|
670
|
+
|
504
671
|
def show_resource_list(opts)
|
505
672
|
# authenticator = Thread.current["authenticator"]
|
506
673
|
if (context = opts[:context])
|
@@ -512,12 +679,21 @@ module OMF::SFA::AM::Rest
|
|
512
679
|
end
|
513
680
|
|
514
681
|
def show_resources(resources, resource_name, opts)
|
682
|
+
resources = resolve_promise(resources) do |r|
|
683
|
+
show_resources(r, resource_name, opts)
|
684
|
+
end
|
515
685
|
#hopts = {max_level: opts[:max_level], level: 1}
|
516
|
-
hopts = {max_level: opts[:max_level], level: 0}
|
686
|
+
hopts = {max_level: opts[:max_level] - 1, level: 0}
|
517
687
|
objs = {}
|
518
688
|
res_hash = resources.map do |a|
|
689
|
+
a = resolve_promise(a) do |r|
|
690
|
+
# The resolved promise was embeded, so we need to start afresh from the top
|
691
|
+
show_resources(resources, resource_name, opts)
|
692
|
+
end
|
519
693
|
next unless a # TODO: This seems to be a bug in OProperty (removing objects)
|
520
|
-
a.to_hash(
|
694
|
+
#puts ">>>>>>>>> #{a.to_hash({}, hopts.dup)}"
|
695
|
+
#a.to_hash(objs, hopts.dup)
|
696
|
+
after_resource_to_hash_hook(a.to_hash(objs, hopts), a)
|
521
697
|
#a.to_hash_brief(:href_use_class_prefix => true)
|
522
698
|
end.compact
|
523
699
|
if resource_name
|
@@ -529,9 +705,84 @@ module OMF::SFA::AM::Rest
|
|
529
705
|
else
|
530
706
|
res = res_hash
|
531
707
|
end
|
532
|
-
|
708
|
+
check_for_promises 'application/json', res
|
533
709
|
end
|
534
710
|
|
711
|
+
# Check if 'value' is a promise. If not, return it immediately. If it is
|
712
|
+
# a promise, return it's value if it has been already resolved. Otherwise
|
713
|
+
# throw a PromiseDeferException. If the promise gets resolved at some later
|
714
|
+
# stage the 'block' is called and is expected to return the same result as
|
715
|
+
# the caller of this function would have returned if this function would have
|
716
|
+
# returned immediately.
|
717
|
+
#
|
718
|
+
def resolve_promise(value, &block)
|
719
|
+
if (promise = value).is_a? OMF::SFA::Util::Promise
|
720
|
+
case promise.status
|
721
|
+
when :pending
|
722
|
+
proxy = OMF::SFA::Util::Promise.new
|
723
|
+
promise.on_success do |d|
|
724
|
+
proxy.resolve block.call(d)
|
725
|
+
end.on_error(proxy).on_progress(proxy)
|
726
|
+
raise OMF::SFA::Util::PromiseUnresolvedException.new proxy
|
727
|
+
when :rejected
|
728
|
+
raise promise.error_msg
|
729
|
+
else
|
730
|
+
value = promise.value
|
731
|
+
end
|
732
|
+
end
|
733
|
+
#puts "RESOLVE PROMISE(#{value.class}>>> #{value}"
|
734
|
+
value
|
735
|
+
end
|
736
|
+
|
737
|
+
# Check if any elements in res, which is either an array
|
738
|
+
# or hash, is a promise. If one is found, and can't be resolved,
|
739
|
+
# a PromiseDeferException is thrown. The proxy promise in the exception
|
740
|
+
# will monitor the unresolved promises and after all of them are resolved
|
741
|
+
# will resolve the associated promise with a 'clean' res.
|
742
|
+
#
|
743
|
+
def check_for_promises(mime_type, res, proxy = nil)
|
744
|
+
begin
|
745
|
+
res = _scan_for_promises(res)
|
746
|
+
rescue OMF::SFA::Util::PromiseUnresolvedException => pex
|
747
|
+
unless proxy
|
748
|
+
proxy = OMF::SFA::Util::Promise.new
|
749
|
+
pex.promise.on_success do |x|
|
750
|
+
check_for_promises(mime_type, res, proxy)
|
751
|
+
end.on_error do |ec, em|
|
752
|
+
proxy.reject(ec, em)
|
753
|
+
end.on_progress(proxy)
|
754
|
+
raise OMF::SFA::Util::PromiseUnresolvedException.new proxy
|
755
|
+
end
|
756
|
+
end
|
757
|
+
if proxy
|
758
|
+
proxy.resolve [mime_type, res]
|
759
|
+
end
|
760
|
+
[mime_type, res]
|
761
|
+
end
|
762
|
+
|
763
|
+
def _scan_for_promises(res)
|
764
|
+
if res.is_a? Array
|
765
|
+
res = res.map do |el|
|
766
|
+
_scan_for_promises(el)
|
767
|
+
end
|
768
|
+
elsif res.is_a? Hash
|
769
|
+
res.each do |key, val|
|
770
|
+
res[key] = _scan_for_promises(val)
|
771
|
+
end
|
772
|
+
elsif res.is_a? OMF::SFA::Util::Promise
|
773
|
+
case res.status
|
774
|
+
when :resolved
|
775
|
+
return res.value
|
776
|
+
when :rejected
|
777
|
+
raise res.err_message
|
778
|
+
else
|
779
|
+
raise OMF::SFA::Util::PromiseUnresolvedException.new(res)
|
780
|
+
end
|
781
|
+
end
|
782
|
+
res # seems to be 'normal' value
|
783
|
+
end
|
784
|
+
|
785
|
+
|
535
786
|
def show_deleted_resource(uuid)
|
536
787
|
res = {
|
537
788
|
uuid: uuid,
|
@@ -602,12 +853,13 @@ module OMF::SFA::AM::Rest
|
|
602
853
|
tmpl
|
603
854
|
end
|
604
855
|
|
605
|
-
def convert_to_html(
|
856
|
+
def convert_to_html(obj, env, opts, collections = Set.new)
|
606
857
|
req = ::Rack::Request.new(env)
|
607
858
|
opts = {
|
608
859
|
collections: collections,
|
609
860
|
level: 0,
|
610
|
-
href_prefix: "#{req.path}/"
|
861
|
+
href_prefix: "#{req.path}/",
|
862
|
+
env: env
|
611
863
|
}.merge(opts)
|
612
864
|
|
613
865
|
path = req.path.split('/').select { |p| !p.empty? }
|
@@ -617,11 +869,11 @@ module OMF::SFA::AM::Rest
|
|
617
869
|
end
|
618
870
|
|
619
871
|
res = []
|
620
|
-
_convert_obj_to_html(
|
872
|
+
_convert_obj_to_html(obj, nil, res, opts)
|
621
873
|
|
622
874
|
render_html(
|
623
875
|
header: opts[:html_header] || '',
|
624
|
-
result:
|
876
|
+
result: obj,
|
625
877
|
title: @@service_name || env["HTTP_HOST"],
|
626
878
|
service: h2.join('/'),
|
627
879
|
content: res.join("\n")
|
@@ -636,6 +888,9 @@ module OMF::SFA::AM::Rest
|
|
636
888
|
def _convert_obj_to_html(obj, ref_name, res, opts)
|
637
889
|
klass = obj.class
|
638
890
|
#puts "CONVERT>>>> #{ref_name} ... #{obj.class}::#{obj.to_s[0 .. 80]}"
|
891
|
+
if obj.is_a? OMF::SFA::Util::Promise
|
892
|
+
obj = obj.to_html()
|
893
|
+
end
|
639
894
|
if (obj.is_a? OMF::SFA::Resource::OPropertyArray) || obj.is_a?(Array)
|
640
895
|
if obj.empty?
|
641
896
|
res << '<span class="empty">empty</span>'
|
@@ -661,7 +916,7 @@ module OMF::SFA::AM::Rest
|
|
661
916
|
end
|
662
917
|
|
663
918
|
def _convert_array_to_html(array, ref_name, res, opts)
|
664
|
-
opts = opts.merge(level: opts[:level] + 1)
|
919
|
+
opts = opts.merge(level: opts[:level] + 1, context: array)
|
665
920
|
array.each do |obj|
|
666
921
|
#puts "AAA>>>> #{obj}::#{opts}"
|
667
922
|
name = nil
|
@@ -684,6 +939,7 @@ module OMF::SFA::AM::Rest
|
|
684
939
|
|
685
940
|
def _convert_hash_to_html(hash, ref_name, res, opts)
|
686
941
|
#puts ">>>> #{hash}::#{opts}"
|
942
|
+
opts = opts.merge(context: hash)
|
687
943
|
hash.each do |key, obj|
|
688
944
|
#key = "#{key}-#{opts[:level]}-#{opts[:collections].to_a.inspect}"
|
689
945
|
if opts[:level] == 0 && opts[:collections].include?(key.to_sym)
|