Class: Arrow::AppletRegistry

Inherits:
Object
  • Object
show all
Extends:
Forwardable
Includes:
Loggable, Enumerable
Defined in:
lib/arrow/appletregistry.rb

Overview

The Arrow::AppletRegistry class, a derivative of Arrow::Object. Instances of this class are responsible for loading and maintaining the collection of Arrow::Applets registered with an Arrow::Broker.

VCS Id

$Id$

Authors

  • Michael Granger

Please see the file LICENSE in the top-level directory for licensing details.

Defined Under Namespace

Classes: AppletFile, ChainLink

Constant Summary

IDENTIFIER =

Pattern for matching valid components of the uri

/^\w[-\w]*/

Instance Attribute Summary

Class Method Summary

Instance Method Summary

Methods included from Loggable

#log

Methods inherited from Object

deprecate_class_method, deprecate_method, inherited

Methods included from Loggable

#log

Constructor Details

- (AppletRegistry) initialize(config)

Create a new Arrow::AppletRegistry object.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/arrow/appletregistry.rb', line 226

def initialize( config )
  @config = config

  @path = @config.applets.path
  @classmap = nil
  @filemap = {}
  @urispace = {}
  @template_factory = Arrow::TemplateFactory.new( config )
  @load_time = nil

  self.load_gems
  self.load_applets

  super()
end

Instance Attribute Details

- (Object) config (readonly)

The Arrow::Config object which specified the registry’s behavior.



273
274
275
# File 'lib/arrow/appletregistry.rb', line 273

def config
  @config
end

- (Object) filemap (readonly)

The internal hash of Entry objects keyed by the file they were loaded from



270
271
272
# File 'lib/arrow/appletregistry.rb', line 270

def filemap
  @filemap
end

- (Object) load_time

The Time when the registry was last loaded



279
280
281
# File 'lib/arrow/appletregistry.rb', line 279

def load_time
  @load_time
end

- (Object) path (readonly)

The path the registry will search when looking for new/updated/deleted applets



282
283
284
# File 'lib/arrow/appletregistry.rb', line 282

def path
  @path
end

- (Object) template_factory (readonly)

The Arrow::TemplateFactory which will be given to any loaded applet



276
277
278
# File 'lib/arrow/appletregistry.rb', line 276

def template_factory
  @template_factory
end

- (Object) urispace (readonly)

The internal hash of Entry objects, keyed by URI



266
267
268
# File 'lib/arrow/appletregistry.rb', line 266

def urispace
  @urispace
end

Class Method Details

+ (Object) get_safe_gemhome

Get the ‘gem home’ from RubyGems and check it for sanity. Returns nil if it is not an extant, non-world-writable directory.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/arrow/appletregistry.rb', line 197

def self::get_safe_gemhome
  gemhome = Pathname.new( Gem.user_home ) + 'gems'
  gemhome.untaint

  if ! gemhome.directory?
    Arrow::Logger[ self ].notice "Gem home '%s' is not a directory; ignoring it" % [ gemhome ]
    return nil
  elsif (gemhome.stat.mode & 0002).nonzero?
    Arrow::Logger[ self ].notice "Gem home '%s' is world-writable; ignoring it" % [ gemhome ]
    return nil
  end

  Arrow::Logger[ self ].info "Got safe gem home: %p" % [ gemhome ]
  return gemhome
end

Instance Method Details

- (Object) build_classmap

Make and return a Hash which inverts the registry’s applet layout into a map of class name to the URIs onto which instances of them should be installed.



527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/arrow/appletregistry.rb', line 527

def build_classmap
  classmap = Hash.new {|ary,k| ary[k] = []}

  # Invert the applet layout into Class => [ uris ] so as classes
  # load, we know where to put 'em.
  @config.applets.layout.each do |uri, klassname|
    uri = uri.to_s.sub( %r{^/}, '' )
    self.log.debug "Mapping %p to %p" % [ klassname, uri ]
    classmap[ klassname ] << uri
  end

  return classmap
end

- (Object) check_for_updates

Check the applets path for new/updated/deleted applets if the poll interval has passed.



411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/arrow/appletregistry.rb', line 411

def check_for_updates
  interval = @config.applets.pollInterval
  if interval.nonzero?
    if Time.now - self.load_time > interval
      self.log.debug "Checking for applet updates: poll interval at %ds" % [ interval ]
      self.reload_applets
      self.reload_gems
    end
  else
    self.log.debug "Dynamic applet reloading turned off, continuing"
  end
end

- (Object) find_applet_chain(uri)

Find the chain of applets indicated by the given uri and return an Array of ChainLink structs.



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/arrow/appletregistry.rb', line 378

def find_applet_chain( uri )
  self.log.debug "Searching urispace for appletchain for %p" % [ uri ]

  uri_parts = uri.sub(%r{^/(?=.)}, '').split(%r{/}).grep( IDENTIFIER )
  appletchain = []
  args = []

  # If there's an applet installed at the base, prepend it to the
  # appletchain
  if @urispace.key?( "" )
    appletchain << ChainLink.new( @urispace[""], "", uri_parts )
    self.log.debug "Added base applet to chain."
  end

  # Only allow reference to internal handlers (handlers mapped to 
  # directories that start with '_') if allow_internal is set.
  self.log.debug "Split URI into parts: %p" % [uri_parts]

  # Map uri fragments onto registry entries, stopping at any element 
  # which isn't a valid Ruby identifier.
  uri_parts.each_index do |i|
    newuri = uri_parts[0,i+1].join("/")
    # self.log.debug "Testing %s against %p" % [ newuri, @urispace.keys.sort ]
    appletchain << ChainLink.new( @urispace[newuri], newuri, uri_parts[(i+1)..-1] ) if
      @urispace.key?( newuri )
  end

  return appletchain
end

- (Object) find_appletfiles(excludeList = [])

Find applet files by looking in the applets path of the registry’s configuration for files matching the configured pattern. Return an Array of fully-qualified applet files. If the optional excludeList is given, exclude any files specified from the return value.



546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/arrow/appletregistry.rb', line 546

def find_appletfiles( excludeList=[] )
  files = []
  dirCount = 0

  # The Arrow::Path object will only give us extant directories...
  @path.each do |path|

    # Look for files under a directory
    dirCount += 1
    pat = File.join( path, @config.applets.pattern )
    pat.untaint

    self.log.debug "Looking for applets: %p" % [ pat ]
    files.push( *Dir[ pat ] )
  end

  self.log.info "Fetched %d applet file paths from %d directories (out of %d)" %
    [ files.nitems, dirCount, @path.dirs.nitems ]

  files.each {|file| file.untaint }
  return files - excludeList
end

- (Object) initialize_copy(other)

Copy initializer — reload applets for cloned registries.



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/arrow/appletregistry.rb', line 244

def initialize_copy( other ) # :nodoc:
  @config = other.config.dup

  @path = @config.applets.path.dup
  @classmap = nil
  @filemap = {}
  @urispace = {}
  @template_factory = Arrow::TemplateFactory.new( config )
  @load_time = nil

  self.load_gems
  self.load_applets

  super
end

- (Object) load_applets Also known as: reload_applets

Load any new applets in the registry’s path, reload any previously- loaded applets whose files have changed, and discard any applets whose files have disappeared.



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/arrow/appletregistry.rb', line 352

def load_applets
  self.log.debug "Loading applet registry"

  @classmap = self.build_classmap
  filelist = self.find_appletfiles

  # Remove applet files which correspond to files that are no longer
  # in the list
  self.purge_deleted_applets( @filemap.keys - filelist ) unless 
    @filemap.empty?

  # Now search the applet path for applet files
  filelist.each do |appletfile|
    self.log.debug "Found applet file %p" % appletfile
    self.load_applets_from_file( appletfile )
    self.log.debug "After %s, registry has %d entries" %
      [ appletfile, @urispace.length ]
  end

  self.load_time = Time.now
end

- (Object) load_applets_from_file(path)

Load the applet classes from the given path and return them in an Array. If a block is given, then each loaded class is yielded to the block in turn, and the return values are used in the Array instead.



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/arrow/appletregistry.rb', line 449

def load_applets_from_file( path )

  # Reload mode -- don't do anything unless the file's been updated
  if @filemap.key?( path )
    file = @filemap[ path ]

    if file.has_changed?
      self.log.info "File %p has changed since loaded. Reloading." % [path]
      self.purge_deleted_applets( path )
    elsif !file.loaded_okay?
      self.log.warning "File %s could not be loaded: %s" % 
        [path, file.exception.message]
      file.exception.backtrace.each do |frame|
        self.log.debug "  " + frame
      end
    else
      self.log.debug "File %p has not changed." % [path]
      return nil
    end
  end

  self.log.debug "Attempting to load applet objects from %p" % path
  @filemap[ path ] = AppletFile.new( path )

  @filemap[ path ].appletclasses.each do |appletclass|
    self.log.debug "Registering applet class %s from %p" % [appletclass.name, path]
    begin
      uris = self.register_applet_class( appletclass )
      @filemap[ path ].uris << uris
    rescue ::Exception => err
      frames = filter_backtrace( err.backtrace )
      self.log.error "%s loaded, but failed to initialize: %s" % [
        appletclass.normalized_name,
        err.message,
      ]
      self.log.debug "  " + frames.collect {|frame| "[%s]" % frame }.join("  ")
      @filemap[ path ].exception = err
    end
  end

end

- (Object) load_gems Also known as: reload_gems

Check the config for any gems to load, load them, and add their template and applet directories to the appropriate parts of the config.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/arrow/appletregistry.rb', line 291

def load_gems
  self.log.info "Loading gems."

  unless @config.respond_to?( :gems )
    self.log.debug "No gems section in the config; skipping gemified applets"
    return
  end

  self.log.debug "  using gem config: %p" % [ config.gems ]

  # Make sure the 'gem home' is a directory and not world-writable; don't use it
  # otherwise
  gemhome = self.class.get_safe_gemhome
  paths = @config.gems.path.collect {|path| path.untaint }
  self.log.debug "  safe gem paths: %p" % [ paths ]
  Gem.use_paths( Apache.server_root, paths )

  @config.gems.applets.to_h.each do |gemname, reqstring|
    self.log.debug "    trying to load %s %s" % [ gemname, reqstring ]
    reqstring = '>= 0' if reqstring.nil? or reqstring.empty?

    begin
      self.log.info "Activating gem %s (%s)" % [ gemname, reqstring ]
      Gem.activate( gemname.to_s, reqstring )
      self.log.info "  gem %s activated." % [ gemname ]
    rescue LoadError => err
      self.log.crit "%s while activating '%s': %s" %
        [ err.class.name, gemname, err.message ]
      err.backtrace.each do |frame|
        self.log.debug "  " + frame
      end
    else
      datadir = Pathname.new( Gem.datadir(gemname.to_s) )
      appletdir = datadir + 'applets'
      templatedir = datadir + 'templates'
      self.log.debug "Adding appletdir %p and templatedir %p" %
        [ appletdir, templatedir ]
      @path << appletdir.to_s
      @template_factory.path << templatedir.to_s
    end
  end

  self.log.info "  done loading gems (path is now: %p)." % [ @path ]
end

- (Object) purge_deleted_applets(*missing_files)

Remove the applets that were loaded from the given missing_files from the registry.



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/arrow/appletregistry.rb', line 427

def purge_deleted_applets( *missing_files )

  # For each filename, find the applets which were loaded from it, 
  # map the name of each applet to a uri via the classmap, and delete
  # the entries by uri
  missing_files.flatten.each do |filename|
    self.log.info "Unregistering old applets from %p" % [ filename ]

    @filemap[ filename ].uris.each do |uri|
      self.log.debug "  Removing %p, registered at %p" % [ @urispace[uri], uri ]
      @urispace.delete( uri )
    end

    @filemap.delete( filename )
  end
end

- (Object) register_applet_class(klass)

Register an instance of the given klass with the broker if the classmap includes it, returning the URIs which were mapped to instances of the klass.



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/arrow/appletregistry.rb', line 495

def register_applet_class( klass )
  uris = []

  # Trim the Module serving as private namespace from the
  # class name
  appletname = klass.normalized_name
  self.log.debug "Registering %p applet as %p" % [ klass.name, appletname ]

  # Look for a uri corresponding to the loaded class, and instantiate it
  # if there is one.
  if @classmap.key?( appletname )
    self.log.debug "  Found one or more uris for '%s'" % appletname


    # Create a new instance of the applet for each uri it's
    # registered under, then wrap that in a RegistryEntry
    # and put it in the entries hash we'll return later.
    @classmap[ appletname ].each do |uri|
      @urispace[ uri ] = klass.new( @config, @template_factory, uri )
      uris << uri
    end
  else
    self.log.debug "No uri for '%s': Not instantiated" % appletname
  end

  return uris
end