package PXP::Plugin; =pod =head1 NAME PXP::Plugin - Plugin class definition (used only in the internal registry) =head1 SYNOPSIS =head1 DESCRIPTION A plugin groups together a set of extensions and/or extension-points. A C represents such a container as it is read and its content loaded into the system. A common interface is provided to access the configuration descriptors and the actual implementation that has been loaded into the system. B: a C object is B a plugin. A I plugin, is just a set of Perl modules loaded according to the 'plugin.xml' descriptor file. Plugin dependencies are supported with the tag. =head2 Limitations We support only a subset of the Eclipse plugin model. =cut use strict; use warnings; use PXP::ExtensionPoint; use PXP::Extension; use XML::XPath; use File::Spec; use Cwd; use Log::Log4perl qw(get_logger); my $logger = get_logger(__PACKAGE__); sub new { my $class = shift; my $self = { name => '', id => '', version => 0, provider_name => '', extensions => {}, extension_points => {}, dependencies => [] }; bless $self, $class; return $self->init(@_); } sub init { my $self = shift; my %opts = @_; # TODO : warn if no directory $self->directory($opts{'directory'}); my $filename = $opts{'filename'} || ''; if ($filename) { my $config = $self->loadLocalizedResource($filename) || die "cannot find config for plugin"; my $xp = XML::XPath->new(xml => $config); $self->loadFromXML($xp); } return $self; } sub loadFromXML { my $self = shift; my $xp = shift; # FIXME: Check all values # Set plugin params; my @nodes = $xp->findnodes('/plugin'); unless (@nodes) { $logger->error("No plugin defined in xml config file. Loading aborted."); return undef; } my $node = shift @nodes; # Define parameters $self->id($node->getAttribute('id')); $self->class($node->getAttribute('class')); $self->name($node->getAttribute('name')); $self->version($node->getAttribute('version')); $self->providerName($node->getAttribute('provider-name')); # Handle dependencies my @deps = $xp->findnodes('/plugin/require'); $self->addDependencies(map ($_->getAttribute('plugin'), @deps)); # Handle libraries my @libs = $xp->findnodes('/plugin/runtime/library'); $self->addLibraries(map ($_->getAttribute('name'), @libs)); # Save XML node $self->{'xml'} = $xp; # Add the plugin directory to Perl search path unshift @INC, $self->directory(); # Extensions and ExtensionPoints are no longer instantiated here. # This must be done by _instantiateExtensions(), after dependencies # have been loaded. } sub instantiateExtensions { my $self = shift; my $xp = $self->{'xml'}; my $node; # Create extension points foreach $node ($xp->findnodes('/plugin/extension-point')) { my $id = $node->getAttribute('id') || die "bummer"; my $class = $node->getAttribute('class') || $id; # FIXME: if nothing was specified, do not force a class load my $name = $node->getAttribute('name'); my $version = $node->getAttribute('version'); my $obj; # Instantiate it # FIXME: Add test on values my $extpt = PXP::ExtensionPoint->new($self); if (_loadClass($class)) { $obj = $class->new($self); # support for Aurelien's approach (an extension point 'IS-A' PXP::ExtensionPoint) if ($obj->isa('PXP::ExtensionPoint')) { $logger->warn("$id should not be an PXP::ExtensionPoint !"); undef $extpt; $extpt = $obj; } $extpt->object($obj); } $extpt->id($id); $extpt->class($class); $extpt->name($name); $extpt->version($version); $self->_storeExtensionPoint($extpt); } # Add extensions foreach $node ($xp->findnodes('/plugin/extension')) { # Get informations my $id = $node->getAttribute('id') || die "bummer"; my $point = $node->getAttribute('point') || die "bummer"; my $class = $node->getAttribute('class') || $id; my $name = $node->getAttribute('name'); my $version = $node->getAttribute('version'); my $obj; my $ext = PXP::Extension->new($self); $ext->id($id); $ext->class($class); $ext->name($name); $ext->version($version); $ext->point($point); $ext->node($node); $self->_storeExtension($ext); } } =pod =over 4 =item instantiateExtension($class, @args) Load a perl class ($class) and instantiate a new object of this class, with optional arguments for the object initializer (@args) Return the new instance or undef if the class could not be loaded =back =cut sub instantiateExtension { my $self = shift; my $class = shift; my @args = shift; # Instantiate if (PXP::Plugin::_loadClass($class)) { return $class->new(@args); } else { die "Could not instanciate class $class"; return undef; } } =pod " =over 4 =item I, I, I, I Basic accessors for plugin properties. =cut " sub name { my $self = shift; if (@_) { $self->{name} = shift; return $self; } else { return $self->{name}; } } sub id { my $self = shift; if (@_) { $self->{id} = shift; return $self; } else { return $self->{id}; } } sub class { my $self = shift; if (@_) { $self->{class} = shift; return $self; } else { return $self->{class}; } } sub version { my $self = shift; if (@_) { $self->{version} = shift; return $self; } else { return $self->{version}; } } sub providerName { my $self = shift; if (@_) { $self->{provider_name} = shift; return $self; } else { return $self->{provider_name}; } } =pod =item I Accessor for the 'resourceDir' attribute. 'resourceDir' points to the directory containing the resources bundled with the plugin, such as config files, templates, etc. This method is called by the I as it loads the plugin. =cut " sub resourceDir { my $self = shift; return $self->directory(@_); } sub xml { my $self = shift; return $self->{xml}; } sub directory { my $self = shift; if (@_) { $self->{directory} = shift; return $self; } else { return $self->{directory}; } } sub fullDirectory { my $self = shift; return File::Spec->catdir(getcwd(), $self->{directory}); } sub resource { my $self = shift; return File::Spec->catfile($self->directory(), @_); } sub fullResource { my $self = shift; return File::Spec->catfile($self->fullDirectory(), @_); } sub getDependencies { my $self = shift; return $self->{'dependencies'}; } sub addDependencies { my $self = shift; push @{$self->{'dependencies'}}, @_; return $self; } sub libraries { my $self = shift; return $self->{'libraries'}; } sub addLibraries { my $self = shift; push @{$self->{'libraries'}}, @_; return $self; } sub _loadClass { my $class = shift; eval "require $class"; if ($@) { warn "Could not load $class: " .$@; return undef; } return 1; } sub _storeExtensionPoint { my $self = shift; my $ext = shift; $self->{'extension_points'}->{$ext->id()} = $ext; return $ext; } sub getExtensionPoint { my $self = shift; my $id = shift; return $self->{'extension_points'}->{$id}; } sub getAllExtensionPoints { my $self = shift; if ($self->{'extension_points'}) { return (values %{$self->{'extension_points'}}); } } sub _storeExtension { my $self = shift; my $ext = shift; $self->{'extensions'}->{$ext->id()} = $ext; # remember extension declaration order in the plugin.xml file push @{$self->{'extensions_array'}}, $ext; return $ext; } sub getExtension { my($self) = shift; my($id) = shift; return($self->{'extensions'}->{$id}); } # Return an _ordered_ list of extensions for the plugin, so we can control extension registration order # (this is important for constructing the MainPipeline, and make sure the RequestManager gets called first) sub getAllExtensions { my $self = shift; if ($self->{'extensions_array'}) { return @{$self->{'extensions_array'}}; } } use FileHandle; sub getResourceHandle { my $self = shift; my $resourceId = shift; if (! $self->isa('PXP::Plugin')) { $self = PXP::PluginRegistry::getPlugin($self) || die "cannot find which plugin I am !"; } my $filename = File::Spec->catfile($self->resourceDir(), $resourceId); $logger->debug("resource for " . $self->id() . " should be in " . $filename); return new FileHandle $filename; } # almost the same code as above, except we try to find a localized property file # property files are named with the locale and after the original filename, # but with a .properties extension (instead of the filename original extension, like .xml) sub getPropertyFileHandle { my $self = shift; my $resourceId = shift; my $dir; if (! $self->isa('PXP::Plugin')) { $self = PXP::PluginRegistry::getPlugin($self) || die "cannot find which plugin I am !"; } $dir = $self->resourceDir(); use File::Basename; my ($filenamePrefix, $dummy, $dummy2) = fileparse($resourceId, qr/\..*/); my $filename = File::Spec->catfile($dir, $filenamePrefix); $logger->debug('looking for property file ' . $filenamePrefix . '.properties'); my $pfname = PXP::I18N::getPropertyFile($filename); return new FileHandle $pfname; } sub loadResource { my $self = shift; my $resourceId = shift; $logger->debug("loading resource $resourceId"); my $handle = $self->getResourceHandle($resourceId); local $/; return <$handle>; } # same as above, but we look for a property file # and substitute with the '%property' pattern (same as in Eclipse) sub loadLocalizedResource { my $self = shift; my $resourceId = shift; $logger->debug("loading localized resource $resourceId"); my $pfh = $self->getPropertyFileHandle($resourceId); unless ($pfh) { my $rfh = $self->getResourceHandle($resourceId); local $/; return <$rfh>; } my $rfh = $self->getResourceHandle($resourceId); return PXP::I18N::loadNLocalize($rfh, $pfh); } =pod " =head1 SEE ALSO C, C, C See the article on eclipse.org describing the plugin architecture : http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html =cut 1;