omf_sfa 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|