Class: OMF::SFA::AM::Rest::RestHandler

Inherits:
Base::LObject
  • Object
show all
Defined in:
lib/omf-sfa/am/am-rest/rest_handler.rb

Direct Known Subclasses

AccountHandler, PromiseHandler, ResourceHandler

Constant Summary collapse

@@service_name =
nil
@@html_template =
File::read(File.dirname(__FILE__) + '/api_template.html')

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ RestHandler

Returns a new instance of RestHandler.



191
192
193
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 191

def initialize(opts = {})
  @opts = opts
end

Class Method Details

.load_api_template(fname) ⇒ Object



169
170
171
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 169

def self.load_api_template(fname)
  @@html_template =  File::read(fname)
end

.parse_resource_uri(resource_uri, description = {}) ⇒ Object

Parse format of ‘resource_uri’ and (re)turn into a description hash (optionally provided).



180
181
182
183
184
185
186
187
188
189
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 180

def self.parse_resource_uri(resource_uri, description = {})
  if UUID.validate(resource_uri)
    description[:uuid] = resource_uri
  elsif resource_uri.start_with? 'urn:'
    description[:urn] = resource_uri
  else
    description[:name] = resource_uri
  end
  description
end

.render_html(parts) ⇒ Object



173
174
175
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 173

def self.render_html(parts)
  self.new().render_html(parts)
end

.service_nameObject



165
166
167
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 165

def self.service_name()
  @@service_name || "Unknown Service"
end

.set_service_name(name) ⇒ Object



161
162
163
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 161

def self.set_service_name(name)
  @@service_name = name
end

Instance Method Details

#_format_body(body, content_type, req, env, proxy_promise = nil) ⇒ Object



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 281

def _format_body(body, content_type, req, env, proxy_promise = nil)
  begin
    if req['_format'] == 'html'
      #body = self.class.convert_to_html(body, env, Set.new((@coll_handlers || {}).keys))
      content_type = 'text/html'
      body = convert_to_html(body, env, {}, Set.new((@coll_handlers || {}).keys))
    elsif content_type == 'application/json'
      body = JSON.pretty_generate(body)
    end
    [content_type, body]
  rescue OMF::SFA::Util::PromiseUnresolvedException => pex
    proxy = OMF::SFA::Util::Promise.new
    pex.promise.on_success do |d|
      proxy.resolve [content_type, body]
    end.on_error(proxy).on_progress(proxy)
    raise OMF::SFA::Util::PromiseUnresolvedException.new proxy
  end
end

#call(env) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 195

def call(env)
  begin
    Thread.current[:http_host] = env["HTTP_HOST"]
    req = ::Rack::Request.new(env)
    headers = {
      'Access-Control-Allow-Origin' => '*',
      'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers' => 'origin, x-csrftoken, content-type, accept'
    }
    if req.request_method == 'OPTIONS'
      return [200 , headers, ""]
    end
    content_type, body = dispatch(req)
    #puts "BODY(#{body.class}) - #{content_type}) >>>>> #{body}"
    if body.is_a? Thin::AsyncResponse
      return body.finish
    end
    content_type, body  = _format_body(body, content_type, req, env)
    # if req['_format'] == 'html'
    #   #body = self.class.convert_to_html(body, env, Set.new((@coll_handlers || {}).keys))
    #   body = convert_to_html(body, env, {}, Set.new((@coll_handlers || {}).keys))
    #   content_type = 'text/html'
    # elsif content_type == 'application/json'
    #   body = JSON.pretty_generate(body)
    # end
    #return [200 ,{'Content-Type' => content_type}, body + "\n"]
    headers['Content-Type'] = content_type
    return [200 , headers, (body || '') + "\n"]
  rescue ContentFoundException => cex
    return cex.reply
  rescue RackException => rex
    unless rex.is_a? TemporaryUnavailableException
      warn "Caught #{rex} - #{env['REQUEST_METHOD']} - #{env['REQUEST_PATH']}"
      debug "#{rex.class} - #{req.inspect}"
      debug rex.backtrace.join("\n")
    end
    return rex.reply
  rescue OMF::SFA::Util::PromiseUnresolvedException => pex
    require 'omf-sfa/am/am-rest/promise_handler' # delay loading as PromiseHandler sub classes this class
    OMF::SFA::AM::Rest::PromiseHandler.register_promise(pex.promise,
                                                        pex.uuid,
                                                        req['_format'] == 'html',
                                                        Set.new((@coll_handlers || {}).keys))
    #return [302, {'Location' => path}, ['Promised, but not ready yet.']]
  rescue OMF::SFA::AM::Rest::RedirectException => rex
    debug "Redirecting to #{rex.path}"
    return [302, {'Location' => rex.path}, ['Next window, please.']]

  # rescue OMF::SFA::AM::AMManagerException => aex
    # return RackException.new(400, aex.to_s).reply
  # rescue RetryLaterException => rex
  #   body = {
  #     type: 'retry',
  #     delay: rex.delay,
  #     request_id: Thread.current[:request_context_id] || 'unknown'
  #   }
  #   debug "Retry later request - #{req.url}"
  #   if req['_format'] == 'html'
  #     refresh = rex.delay.to_s
  #     if (req_id = Thread.current[:request_context_id])
  #       refresh += "; url=#{req.url}&_request_id=#{req_id}"
  #     end
  #     headers['Refresh'] = refresh # 10; url=
  #     headers['X-Request-ID'] = req_id
  #     opts = {} #{html_header: "<META HTTP-EQUIV='refresh' CONTENT='#{rex.delay}'>"}
  #     body = convert_to_html(body, env, opts)
  #     return [200 , headers, body + "\n"]
  #   end
  #   headers['Content-Type'] = 'application/json'
  #   return [504, headers, JSON.pretty_generate(body)]

  rescue Exception => ex
    body = {
      type: 'error',
      error: {
        reason: ex.to_s,
        bt: ex.backtrace #.select {|l| !l.start_with?('/') }
      }
    }
    warn "ERROR: #{ex}"
    debug ex.backtrace.join("\n")
    headers['Content-Type'] = 'application/json'
    return [500, headers, JSON.pretty_generate(body)]
  end
end

#convert_to_html(obj, env, opts, collections = Set.new) ⇒ Object



856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 856

def convert_to_html(obj, env, opts, collections = Set.new)
  req = ::Rack::Request.new(env)
  opts = {
    collections: collections,
    level: 0,
    href_prefix: "#{req.path}/",
    env: env
  }.merge(opts)

  path = req.path.split('/').select { |p| !p.empty? }
  h2 = ["<a href='/?_format=html&_level=0'>ROOT</a>"]
  path.each_with_index do |s, i|
    h2 << "<a href='/#{path[0 .. i].join('/')}?_format=html&_level=#{i % 2 ? 0 : 1}'>#{s}</a>"
  end

  res = []
  _convert_obj_to_html(obj, nil, res, opts)

  render_html(
    header: opts[:html_header] || '',
    result: obj,
    title: @@service_name || env["HTTP_HOST"],
    service: h2.join('/'),
    content: res.join("\n")
  )
end

#find_handler(path, opts) ⇒ Object



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 386

def find_handler(path, opts)
  #debug "find_handler: path; '#{path}' opts: #{opts}"
  debug "find_handler: path: '#{path}'"
  rid = path.shift
  resource_id = opts[:resource_uri] = (rid ? URI.decode(rid) : nil) # make sure we get rid of any URI encoding
  opts[:resource] = nil
  if resource_id
    resource = opts[:resource] = find_resource(resource_id, {}, opts)
  end
  return self if path.empty?

  raise OMF::SFA::AM::Rest::UnknownResourceException.new "Unknown resource '#{resource_id}'." unless resource
  opts[:context] = resource
  opts[:contexts][opts[:context_name].to_sym] = resource
  comp = path.shift
  if (handler = @coll_handlers[comp.to_sym])
    opts[:context_name] = comp
    opts[:resource_uri] = URI.decode(path.join('/'))
    if handler.is_a? Proc
      return handler.call(path, opts)
    end
    return handler.find_handler(path, opts)
  end
  raise UnknownResourceException.new "Unknown sub collection '#{comp}' for '#{resource_id}:#{resource.class}'."
end

#html_templateObject



883
884
885
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 883

def html_template()
  @@html_template
end

#on_delete(resource_uri, opts) ⇒ Object



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 364

def on_delete(resource_uri, opts)
  res = ['application/json', {}]
  if resource = opts[:resource]
    if (context = opts[:context])
      remove_resource_from_context(resource, context)
      res = show_resource_status(resource, opts)
    else
      debug "Delete resource #{resource}"
      res = show_deleted_resource(resource.uuid)
      resource.destroy
    end
  else
    res = on_delete_all(opts) || res
  end
  res
end

#on_delete_all(opts) ⇒ Object



381
382
383
384
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 381

def on_delete_all(opts)
  # Delete ALL resources of this type
  raise OMF::SFA::AM::Rest::BadRequestException.new "I'm sorry, Dave. I'm afraid I can't do that."
end

#on_get(resource_uri, opts) ⇒ Object

def _x(promise, req)

uuid = 1 #pex.uuid
path = "/promises/#{uuid}"
require 'omf-sfa/am/am-rest/promise_handler' # delay loading as PromiseHandler sub classes this class
OMF::SFA::AM::Rest::PromiseHandler.register_promise(promise,
                                                    uuid,
                                                    req['_format'] == 'html',
                                                    Set.new((@coll_handlers || {}).keys))
debug "Redirecting to #{path}"
return [302, {'Location' => path}, ['Promised, but not ready yet.']]

end



312
313
314
315
316
317
318
319
320
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 312

def on_get(resource_uri, opts)
  debug 'get: resource_uri: "', resource_uri, '"'
  if resource_uri
    resource = opts[:resource]
    show_resource_status(resource, opts)
  else
    show_resource_list(opts)
  end
end

#on_post(resource_uri, opts) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 327

def on_post(resource_uri, opts)
  #debug 'POST: resource_uri "', resource_uri, '" - ', opts.inspect
  description, format = parse_body(opts, [:json, :form])
  #debug 'POST(', resource_uri, '): body(', format, '): "', description, '"'

  if resource = opts[:resource]
    debug 'POST: Modify ', resource, ' --- ', resource.class
    resource = modify_resource(resource, description, opts)
  else
    if description.is_a? Array
      resources = description.map do |d|
        debug 'POST: Create? ', d
        create_resource(d, opts)
      end
      return show_resources(resources, nil, opts)
    else
      debug 'POST: Create ', resource_uri
      # if resource_uri
        # if UUID.validate(resource_uri)
          # description[:uuid] = resource_uri
        # else
          # description[:name] = resource_uri
        # end
      # end
      resource = create_resource(description, opts, resource_uri)
    end
  end

  if resource
    show_resource_status(resource, opts)
  elsif context = opts[:context]
    show_resource_status(context, opts)
  else
    raise "Report me. Should never get here"
  end
end

#on_put(resource_uri, opts) ⇒ Object



322
323
324
325
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 322

def on_put(resource_uri, opts)
  debug '>>> PUT NOT IMPLEMENTED'
  raise UnsupportedMethodException.new('on_put', @resource_class)
end

#render_html(parts = {}) ⇒ Object

Render an HTML page using the resource’s template. The template is populated with information provided in ‘parts’

  • :header - HTML header additions

  • :title - HTML title

  • :service - Service path (usually a set of <a>)

  • :content - Main content

  • :footer - Optional footer

  • :result - hash or array describing the result (may used by JS to further format)



833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
# File 'lib/omf-sfa/am/am-rest/rest_handler.rb', line 833

def render_html(parts = {})
  #puts "PP>> #{parts}"
  tmpl = html_template()
  if (header = parts[:header])
    tmpl = tmpl.gsub('##HEADER##', header)
  end
  if (result = parts[:result])
    tmpl = tmpl.gsub('##JS##', JSON.pretty_generate(result))
  end
  title = parts[:title] || @@service_name || "Unknown Service"
  tmpl = tmpl.gsub('##TITLE##', title)
  if (service = parts[:service])
    tmpl = tmpl.gsub('##SERVICE##', service)
  end
  if (content = parts[:content])
    tmpl = tmpl.gsub('##CONTENT##', content)
  end
  if (footer = parts[:footer])
    tmpl = tmpl.gsub('##FOOTER##', footer)
  end
  tmpl
end