Class: Arrow::AppletRegistry

Inherits:
Object
  • Object
show all
Extended by:
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

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

Defined Under Namespace

Classes: AppletFile, ChainLink

Constant Summary collapse

IDENTIFIER =

Pattern for matching valid components of the uri

/^\w[-\w]*/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Object

deprecate_class_method, deprecate_method, inherited

Constructor Details

#initialize(config) ⇒ AppletRegistry

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

#configObject (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

#filemapObject (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

#load_timeObject

The Time when the registry was last loaded



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

def load_time
  @load_time
end

#pathObject (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

#template_factoryObject (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

#urispaceObject (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

.get_safe_gemhomeObject

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

#build_classmapObject

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

#check_for_updatesObject

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

#find_applet_chain(uri) ⇒ Object

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

#find_appletfiles(excludeList = []) ⇒ Object

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

#initialize_copy(other) ⇒ Object

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

#load_appletsObject 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

#load_applets_from_file(path) ⇒ Object

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

#load_gemsObject 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

#purge_deleted_applets(*missing_files) ⇒ Object

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

#register_applet_class(klass) ⇒ Object

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