PXP::PluginRegistry - Registry of plugins for PXP


PXP documentation Contained in the PXP distribution.

Index


Code Index:

NAME

Top

PXP::PluginRegistry - Registry of plugins for PXP

SYNOPSIS

Top

  use PXP::PluginRegistry;
  PXP::init(dir => './plugins', default => 'all', load => [], noload => []);
  ...
  my $plugin   = PXP::PluginRegistry::getExtension(ref($self))->plugin();
  my $extpt = PXP::PluginRegistry::getExtensionPoint('PXP::StaticResourcesHandler');

ABSTRACT

Top

The PluginRegistry groups together a set of plugins, extension points and extensions for the running system.

DESCRIPTION

Top

The PluginRegistry manages plugins, extension points and extensions.

The PluginRegistry takes care of loading the plugins from file, loading classes and Perl modules and, optionnaly, running 'startup' sub-routines as each plugin is loaded in the system.

init

Initialize the plugin registry. Note: only one instance of PluginRegistry is supported (singleton pattern).

Return a hash containing all registered plugins.

Return a plugin referenced by its id, or undef if no such plugin exists.

Warning : a plugin id can be different from its actual object class, as declared in plugin.xml

Load all plugins present in the directory.

This is usually called once by by PXP::init. Plugin developpers can call this function to load plugins installed outside of the system default directory ('plugins' at the root of the PXP home directory).

See below for details about the loading process.

Search the registry for an ExtensionPoint and return it, based on its id.

Return the internal XML node describing the extension in the plugin.xml file.

This is mostly used by plugin developpers to get access to the XML config.

Search the registry for an Extension and return it, based on its id.

This returns the internal extension descriptor object, ie an PXP::Extension object, not the real extension See the code for details of the private API.

Loading process

First, the PluginRegistry calls loadPluginFromDirectory to load all the plugins installed in the system directory (usually called 'plugins' inside the PXP hierarchy). The loader calculates the dependencies so that plugin can resolve symbols.

For each plugin, extension points are instantiated and registered into the global registry.

Then, extension _definitions_ are handed to the extension points to be registered. Extension points choose wether to instantiate a particular objet for each of their extensions.

Last, the PluginRegistry calls the 'startup' routine of each plugin that declared a specific class (class="" attribute inside the plugin.xml header).

An error during plugin load stops the process for a whole plugin dependency branch, but plugin startup is still called for plugins that have already been loaded.

SEE ALSO

Top

See PXP::ExtensionPoint, PXP::Extension for details about the concepts

1;


PXP documentation Contained in the PXP distribution.
package PXP::PluginRegistry;

use strict;
use warnings;

use constant PLUGIN_INFO_FILE => 'plugin.xml';

use PXP::Plugin;
use File::Spec;

use Log::Log4perl qw(get_logger);
our $logger = get_logger(__PACKAGE__);

our $VERSION = '0.1';
our $plugins;

our $loader = {};

sub init {
  $loader = shift;

  loadPluginsFromDirectory($loader->{dir});
}

sub getPluginList {
  return %{$plugins};
}

sub getPlugin {
  my $id = shift;
  return $plugins->{$id};
}

sub loadPluginsFromDirectory {
  my $directory = shift;

  # List the plugins inside the directory
  $directory = File::Spec->canonpath($directory);
  $logger->debug("looking for plugins in $directory...");
  opendir DIR, $directory;
  my @dirs = readdir DIR;
  closedir DIR;

  # Check each sub-directory.
  my ($pluginFile, @plugins);
  my $default = $loader->{default};
  my $load = $loader->{load};
  if ($load && ref $load eq "ARRAY") {
	  $load = join ' ',@$load;
  }
  my $noload = $loader->{noload};
  if ($noload && ref $noload eq "ARRAY") {
    $noload = join ' ',@$noload;
  }

  my $debugList = '';

  foreach my $plugdir (@dirs) {
    if ($default eq 'none') {
      next unless $load and $load =~ /$plugdir/i;
    } else { # load all by default
      next if $noload and $noload =~ /$plugdir/i;
    }

    my $dir = File::Spec->catdir(($directory, $plugdir));

    next unless (-d $dir);

    # Load the plugin xml description file
    $pluginFile = File::Spec->catfile($dir, PLUGIN_INFO_FILE);

    # Create plugin files list
    if (-e $pluginFile) {
      my $plugin = PXP::Plugin->new('filename' => PLUGIN_INFO_FILE, 'directory' => $dir, 'moduleName' => $plugdir);
      foreach my $p (@plugins) {
	if ($p->id eq $plugin->id) {
	  $logger->fatal("Plugin " . $p->id . " loaded twice in " .
			 $p->directory . " and " . $plugin->directory);
	  die "problem detected in the plugin directory";
	}
      }
      push @plugins, $plugin;
      $debugList .= "$dir, ";
    }
  }

  $logger->info("preparing to load the following plugins : [$debugList]");

  # Load all plugins, considering dependencies
  loadPlugins(@plugins) if (@plugins);
}

sub loadPlugins {
  my @plugins = @_;

  # Check dependencies to load the plugins in right order.
  my $tree = {};
  my $instances = {};

  # Get plugin dependencies & Fill the tree
  foreach my $plugin (@plugins) {
    $tree->{$plugin->id()} = $plugin->getDependencies();
    $instances->{$plugin->id()} = $plugin;
  }

  # Load the dependencies in the right order
  foreach (@plugins) {
    _loadAndStartPlugin($_ , $tree, $instances);
  }

  return 1;
}

sub _loadAndStartPlugin {
  my $plugin = shift;
  my $tree = shift;
  my $instances = shift;
  my @path = @_;

  # Maybe the plugin is already loaded ?
  return 1 if (_getRegisteredPlugin($plugin->id()));

  $logger->debug("loading plugin " . $plugin->id);
  # Check to find circular dependencies
  if (grep {$plugin->id() eq $_} @path) {
    die "Circular dependencies detected with ".$plugin->id().". Correct this first !";
  }

  # Does the plugin have dependencies ?
  if ($tree->{$plugin->id()} && ref($tree->{$plugin->id()}) eq 'ARRAY') {
    # Load them !
    foreach my $dep (@{$tree->{$plugin->id()}}) {

      # Check if dependency is right
      my $insdep = $instances->{$dep} || _getRegisteredPlugin($dep);
      unless ($insdep) {
	$logger->warn("Dependency $dep does not exist for plugin ".$plugin->id().'. Loading aborted.');
	die "check dependency problem";
      }

      unless (_loadAndStartPlugin($insdep, $tree, $instances, @path, $plugin->id())) {
	$logger->warn("Error loading dependency $dep for ".$plugin->id().'. Loading aborted.');
	die "check dependency problem";
      }
    }
  }

  # Load libraries
  _loadLibraries($plugin->directory(), $plugin->libraries());

  # Instantiate the extensions
  $plugin->instantiateExtensions();

  # register everybody
  registerPlugin($plugin);

  # and call startup
  startupPlugin($plugin);
}


sub _loadLibraries {
  my $dir = shift;
  my $libs = shift;

  foreach my $lib (@{$libs}) {
    $logger->debug("loading library $lib (in $dir)");
    my $filename = File::Spec->catfile($dir, $lib);
    my $loader = "use PAR \'$filename\';";
    eval $loader;
  }
  return 1;
}

sub registerPlugin {
  my $plugin = shift;

  my $error = 1;

  if ($plugin && ref($plugin) && $plugin->isa('PXP::Plugin')) {
    $error = 0;

    # Register extensions with their extension point
    foreach my $ext ($plugin->getAllExtensions()) {
      # in case the plugin does not declare extensions
      last if not $ext;

      # Search the extension point in the global registry or in the own registry of the plugin we're currently registering
      my $extpt = _getExtensionPoint($ext->point()) || $plugin->getExtensionPoint($ext->point());
      if ($extpt) {
	$logger->debug("registering " . $ext->id . " (" . ref($ext) . ") with " . $extpt->id . " (" . ref($extpt) . ")");
	$extpt->register($ext);
      } else {
	$logger->warn("No matching extension point " . $ext->point() . ' while registering '.$plugin->id());
	# $error = 1;
	last;
      }
    }

  } else {

    $logger->error("Could not register the specified plugin. Wrong plugin type.");
    return 0;

  }

  # FIXME : can there really be _detected_ errors now !?
  unless ($error) {

    # Store the plugin in registry
    $plugins->{$plugin->id()} = $plugin;
    $logger->debug('Plugin '.$plugin->id().'-'.$plugin->version()." successfully registered.");
    return 1;
  }
}


# remember which plugin we started to avoid double starts
our $startupRegistry = {};

sub startupPluginWithDependencies {
  my $plugin = shift;
  my $tree = shift;
  my $instances = shift;
  my @path = @_;

  # if class is not declared, do not attempt to startup the plugin
  return 1 if (not $plugin->class);

  # Maybe the plugin has already been started ?
  return 1 if ($startupRegistry->{$plugin->id()});

  # Does the plugin have dependencies ?
  if ($tree->{$plugin->id()} && ref($tree->{$plugin->id()}) eq 'ARRAY') {
    # Load them !
    foreach my $dep (@{$tree->{$plugin->id()}}) {
      # Check if dependency is right
      my $insdep = $instances->{$dep} || _getRegisteredPlugin($dep);
      unless ($insdep) {
	$logger->warn("Dependency $dep does not exist for plugin ".$plugin->id().'. Startup aborted.');
	return undef;
      }

      unless (startupPluginWithDependencies($insdep, $tree, $instances, @path, $plugin->id())) {
	$logger->warn("Error starting up dependency $dep for ".$plugin->id().'. Startup aborted.');
	return undef;
      }
    }
  }

  $startupRegistry->{$plugin->id} = 1 if startupPlugin($plugin);
}

sub startupPlugin {
  my $plugin = shift;

  my $module = $plugin->class() || $plugin->id();

  my $module_file = $module .".pm";
  $module_file =~ s|::|/|g;
  $module_file = File::Spec->catfile($plugin->directory(), $module_file);

  # try to load the module
  if(-e $module_file) {
    $logger->debug("loading $module");
    eval "require $module";
    # $logger->warn("$@\n") if $@;
    die $@ if $@;
  }

  # startup
  {
    no strict;
    if (defined &{"${module}::startup"}) {
      $logger->debug("calling 'startup' for $module");
      &{"${module}::startup"}($plugin);
    }
  }

  return 1;
}

sub getResourceHandle {
  my $plugin = shift;
  my $resourceId = shift;

  return $plugin->getResourceHandle($resourceId);
}

sub loadResource {
  my $plugin = shift;
  my $resourceId = shift;

  return $plugin->loadResource($resourceId);
}

# public interface : this one returns the _object_ associated with an
# extension point, ie the _object_ that actually handles the
# extensions, _not_ the internal descriptor.
sub getExtensionPoint {
  my $id = shift;

  foreach (values %{$plugins}) {
    return $_->getExtensionPoint($id)->object() if ($_->getExtensionPoint($id));
  }
  return undef;
}

# private function : returns the internal ExtensionPoint descriptor,
# _not_ the object that actually handles the extensions.
sub _getExtensionPoint {
  my $id = shift;

  #FIXME: Add checks;
  foreach (values %{$plugins}) {
    return $_->getExtensionPoint($id) if ($_->getExtensionPoint($id));
  }
  return undef;
}

sub getExtensionNode {
  my $id = shift;

  #FIXME: Add checks;
  foreach (values %{$plugins}) {
    return $_->getExtension($id)->node() if ($_->getExtension($id));
  }
  return undef;
}

# find the extension object in the registry
sub getExtension {
  my $id = shift;

  #FIXME: Add checks;
  foreach (values %{$plugins}) {
    return $_->getExtension($id) if ($_->getExtension($id));
  }
  return undef;
}

sub _getRegisteredPlugin {
  my $id = shift || '';
  return $plugins->{$id};
}