####################################################
# Sample Usage (under mod_perl)
####################################################
use OpenPlugin();
my $r = shift;
my $OP = OpenPlugin->new( config => { src => '/etc/myconf.conf' },
request => { apache => $r } );
my $hair_color = $OP->param->get_incoming('hair');
my $eye_color = $OP->param->get_incoming('eyes');
my $credit_card = $OP->param->get_incoming('credit');
$OP->session->save({ credit => $credit_card });
$OP->httpheader->set_outgoing({ content-type => text/html });
$OP->httpheader->send_outgoing();
print "We got your number!
";
####################################################
# Sample Config
####################################################
# Portions of OpenPlugin.conf
load = Startup
driver = DBI
datasource = rwcsql
load = Startup
driver = Apache
load = Auto
driver = File
expires = +3h
# Portions of OpenPlugin-drivermap.conf
DBI = OpenPlugin::Authenticate::DBI
Htpasswd = OpenPlugin::Authenticate::Htpasswd
LDAP = OpenPlugin::Authenticate::LDAP
SMB = OpenPlugin::Authenticate::SMB
PAM = OpenPlugin::Authenticate::PAM
Apache = OpenPlugin::Param::Apache
CGI = OpenPlugin::Param::CGI
File = OpenPlugin::Cache::File
####################################################
# OpenPlugin Class
####################################################
package OpenPlugin;
use strict;
use vars qw( $AUTOLOAD );
use base qw( Class::Factory );
use OpenPlugin::Plugin qw();
use OpenPlugin::Utility qw();
use Log::Log4perl qw( get_logger );
use constant STATE => '_state';
use constant TOGGLE => '_toggle';
use constant PLUGIN => '_plugin';
use constant PLUGINCONF => '_pluginconf';
use constant INSTANCE => '_instance';
$OpenPlugin::VERSION = '0.06.6';
##########################
# Bootstrap related code
# TODO: We should be able to override this by passing in arguments
my $log_conf = q(
log4perl.rootLogger = WARN, stderr
log4perl.appender.stderr = Log::Dispatch::Screen
log4perl.appender.stderr.layout = org.apache.log4j.PatternLayout
log4perl.appender.stderr.layout.ConversionPattern = %C (%L) %m%n
);
Log::Log4perl::init( \$log_conf );
my $logger = get_logger();
# End bootstrap code
#########################
sub new {
my $pkg = shift;
my $params = { @_ };
my $class = ref $pkg || $pkg;
my $self = bless( {}, $class );
$self->state("command_line", $params);
# Read configuration from file if given
$params->{config}{src} ||= $OpenPlugin::Config::Src;
if ( $params->{config}{src} ) {
$logger->info( "Given config source of [$params->{config}{src}]" );
$self->load_config( $params );
}
# Quit if we haven't been given some sort of config to use
unless (( $params->{config}{src} ) || ( $params->{config}{config} )) {
die "No configuration given! You need to pass in the location ",
"to your configuration file, or pass in a hashref containing ",
"your configuration data.";
}
$self->register_plugins;
return $self;
}
########################################
# Public methods
########################################
# This gets and sets state information for user requests. For instance, we can
# maintain the current user and group, whether the user is an administrator,
# etc.
sub state {
my ( $self, $key, $value ) = @_;
if(( defined $key ) && ( not defined $value )) {
$logger->info("Calling state() with key [$key].");
# Just a key passed in, return a single value
return $self->{ STATE() }{ $key };
}
elsif(( defined $key ) && ( defined $value )) {
$logger->info("Calling state() with key [$key] and value [$value].");
# We have a key and value, so assign the value to the key
return $self->{ STATE() }{ $key } = $value;
}
else {
$logger->info("Calling state() with no parameters.");
# No key or value, return the entire state hash
return $self->{ STATE() };
}
}
# Cleans up the current state in this object and sends a message to
# all plugins to cleanup their state as well.
sub cleanup {
my ( $self ) = @_;
$logger->info( "Running cleanup()" );
# Allow plugins to clean up their own state
foreach my $plugin ( $self->loaded_plugins ) {
$self->$plugin()->cleanup;
}
# Completely erase all state related information
$self->{ STATE() } = {};
# Recreate a hash key for each plugin
foreach my $plugin ( $self->loaded_plugins ) {
$self->$plugin()->state("Init", 1);
}
}
# This should be called before the object is taken out of scope and
# should probably incorporated into a DESTROY() method.
sub shutdown {
my ( $self ) = @_;
$logger->info( "Calling shutdown() from OP" );
$self->cleanup();
# ... do any additional cleanup so we don't have dangling/circular
# references, etc....
}
########################################
# Accessor methods
########################################
# Get a list of all plugins which the config plugin knows about
sub get_plugins {
my $self = shift;
return sort keys %{ $self->config->{plugin} };
}
# Save any info that we have relating the a plugins configuration
sub set_plugin_info {
my ( $self, $plugin ) = @_;
# $plugin_info contains all the information about a given plugin that was
# found in the configuration file
my $plugin_info = $self->config->{plugin}{ $plugin };
# We definitely cannot load a plugin without a driver. Warn and skip if
# that is the case.
unless ( ref $plugin_info eq 'HASH' and $plugin_info->{driver} ) {
$logger->warn("Invalid driver listed for [$plugin]: ",
"[$plugin_info->{driver}]. Skipping." );
}
$logger->info( "Driver type found for [$plugin]: ",
"[$plugin_info->{driver}]" );
# Store this configuration for whenever we need it
$self->{ PLUGINCONF() }{ $plugin } = $plugin_info;
}
# Get the name of the class name to use for a given driver
sub get_plugin_class {
my ( $self, $plugin ) = @_;
# Get the driver for this plugin, as defined in the config file
my $driver = $self->{ PLUGINCONF() }{ $plugin }{driver};
# Get the class name for the driver, as defined in the drivermap file
my $plugin_class = $self->config->{drivermap}{ $plugin }{ $driver };
$logger->info( "Plugin class found for [$plugin]: [$plugin_class]" );
return $plugin_class;
}
# Retrieve a list of plugins which are currently loaded, return the value we
# received when we called it's load() function earlier
sub loaded_plugins {
my $self = shift;
unless ( ref $self->{ PLUGIN() } eq 'HASH' ) {
return ();
}
return sort keys %{ $self->{ PLUGIN() } };
}
# Save the plugin instance (object) that we received by calling its new()
# function
sub set_plugin_instance {
my ( $self, $plugin_type, $instance ) = @_;
$self->{ INSTANCE() }{ $plugin_type } = $instance;
}
########################################
# Plugin Instanciation
########################################
# Decide how and when to load each plugin
sub register_plugins {
my $self = shift;
foreach my $plugin ( $self->get_plugins ) {
$self->set_plugin_info( $plugin );
# These plugins have a "load" time of "Startup", meaning they are
# loaded when the main OpenPlugin module is
if( $self->{ PLUGINCONF() }{ $plugin }{ load } eq "Startup" ) {
unless( OpenPlugin::Plugin->get_factory_map->{$plugin} ) {
# Tell OpenPlugin::Plugin that we have a new class that we wish
# to load now
OpenPlugin::Plugin->add_factory_type(
$plugin => $self->get_plugin_class( $plugin ));
}
$self->init_plugin( $plugin );
}
# These plugins have a "load" time of "Auto", meaning they are
# loaded on demand. If they aren't ever used, they'll never be loaded
elsif ( $self->{ PLUGINCONF() }{ $plugin }{ load } eq "Auto" ) {
unless( OpenPlugin::Plugin->get_register_map->{$plugin} ) {
# Tell OpenPlugin::Plugin about a class, so it can load it if
# and when we finally decide to use it
OpenPlugin::Plugin->register_factory_type(
$plugin => $self->get_plugin_class( $plugin ));
}
}
# We need to know how to load a plugin, it doesn't seem appropriate to
# guess. If the configuration isn't correct, give a warning message,
# but skip loading it.
else {
$logger->warn("Invalid load time listed for [$plugin]: [",
$self->{ PLUGINCONF() }{ $plugin }{ load },
"]. Skipping." );
}
}
}
# Make a plugin available to programs using us
sub init_plugin {
my ( $self, $plugin_type ) = @_;
# TODO: Eventually, instead of passing the entire command line to each
# plugin, we should just pass items directly related to that plugin
my $instance = OpenPlugin::Plugin->new( $plugin_type, $self,
$self->state->{command_line} );
$self->{ INSTANCE() }{ $plugin_type } = $instance;
$self->generate_plugin_method_call( $plugin_type );
$self->{ PLUGIN() }{ $plugin_type } = $self->$plugin_type()->load();
}
# Build a method call for a given plugin
sub generate_plugin_method_call {
my ( $self, $plugin_type ) = @_;
my $class = ref $self;
my $method = $class . '::' . $plugin_type;
no strict 'refs';
unless ( defined &{ $method } ) {
$logger->info("Generating method [$method]");
*{ $method } =
sub {
my $self = shift;
return $self->{ INSTANCE() }{ $plugin_type };
}
}
}
########################################
# AUTOLOAD
# (so great it gets its own section!)
########################################
sub AUTOLOAD {
my ( $self, $params ) = @_;
my $request = $AUTOLOAD;
$request =~ s/.*://;
$logger->info( "Autoload request: [$request]\n" );
$self->init_plugin( $request );
$self->$request( $params );
}
# Lets not go looking for DESTROY via AUTOLOAD
sub DESTROY { }
########################################
# CONFIGURATION
########################################
# Configuration is different from other plugins because of the bootstrapping
# issue.
sub load_config {
my ( $self, $params ) = @_;
unless( OpenPlugin::Plugin->get_factory_map->{config} ) {
OpenPlugin::Plugin->add_factory_type(
config => 'OpenPlugin::Config' );
}
my $config = OpenPlugin::Plugin->new( 'config', $self, $params->{config} );
$self->set_plugin_instance( "config", $config->read );
$self->generate_plugin_method_call( "config" );
}
1;
__END__
=head1 NAME
OpenPlugin - Plugin manager for web applications
=head1 SYNOPSIS
use OpenPlugin();
my $r = shift;
my $OP = OpenPlugin->new( config => { src => '/etc/myconf.conf' },
request => { apache => $r } );
my $is_authenticated = $OP->authenticate->authenticate({ username => 'badguy',
password => 'scylla' });
unless ( $is_authenticated ) {
$OP->exception->throw( "Login incorrect!" );
}
$session = $OP->session->fetch( $session_id );
$session->{ 'hair' } = $OP->param->get_incoming( 'hair' );
$session->{ 'eyes' } = $OP->param->get_incoming( 'eyes' );
$OP->session->save( $session );
$OP->httpheader->send_outgoing();
print "You have $session->{'hair'} hair and $session->{'eyes'} eyes
";
=head1 DESCRIPTION
OpenPlugin is an architecture which manages plugins for web applications. It
allows you to incorporate any number of plugins and drivers into your web
application, offering a powerful user environment.
OpenPlugin comes with numerous plugins, including Session Support, User
Authentication, Datasource Management, and Logging. With OpenPlugin's driver
support, you can do things like change your logging facility from STDERR to
Syslog by changing one line in a config file.
OpenPlugin also offers plugins which abstract Apache::Request and CGI, allowing
you to switch between the two by only changing a setting in a config file.
Each plugin is written in a way to allow any number of drivers which can
manipulate how the plugin functions, or where it can find it's data.
=head1 BACKGROUND
Currently, there are a number of web application frameworks available.
And while each one is unique, there is a certain amount of functionality that
each shares. Often, that functionality is built in to the particular
framework, instead of being a seperate component.ÜÜ
OpenPlugin offers this functionality that is common between frameworks, but it
is designed to be a reusable component, which can be used within any framework
or standalone web application. This allows OpenPlugin to grow beyond the
abilities of any one developer, and beyond the scope of any one framework.
OpenPlugin has developed into a powerful architecture allowing for extensible
applications.
=head1 FUNCTIONS
While the main OpenPlugin class does provide some publicaly available
functions, you'll find the majority of OpenPlugin's funcionality in it's
plugins.
=over 4
=item B<$OP = OpenPlugin->new( %params )>
You can pass a number of parameters into the B method. Each of those
parameters can effect how a given plugin, or OpenPlugin as a whole, functions.
The parameters in %params will be available to each plugin as they are
loaded.
The syntax for the %params hash is:
%params = qw(
plugin_name => { plugin_param => plugin_value },
other_plugin => { plugin_param => plugin_value },
For example:
%params = qw(
config => { src => /path/to/config.conf },
request => { apache => $r },
);
This returns an OpenPlugin object.
=item B
This function is for storing and retrieving state information. This
information is destroyed when the script exits (see the
L and L plugins
for storing information across requests).
This returns the full state hash if passed no parameters, a value if passed one
parameter (a key), or sets a key equal to a given value if sent two parameters.
=item B
This function tells the main OpenPlugin module, and all of it's plugins, to
perform a "cleanup". This is typically called by you when your application is
exiting, perhaps even from a DESTROY() method. This is important when running
under mod_perl, as it clears out all the state information created during the
current request. If not cleared out, mod_perl will happily keep this
information for the next request too -- this is not what you want! Be sure to
run cleanup() at the end of your application. If you are using L,
OpenThought does call this method for you.
=back
=head1 PLUGINS
The API for individual plugins is available by looking at that particular
plugin's documentation. The following plugins are available:
=over 4
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=back
Many of these plugins accept parameters passed into OpenPlugin's B
constructor, and a few even require it. You can obtain a list of what
parameters a plugin recognizes by reading the documentation for the plugin and
driver which you are using. Generally speaking, the documentation for a plugin
shows how to program it's interface, and the documentation for the driver shows
how to configure it, along with what parameters you can pass into it.
=head1 TO DO
All kinds of stuff! This module has plenty of room for improvement. The API
is not complete. See the TO DO list for the individual plugins to see a few of
the ideas that I've written down.
There's also plenty of room for more documentation.
=head1 COPYRIGHT
Copyright (c) 2001-2002 Eric Andreychek. All rights reserved.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 AUTHORS
Eric Andreychek
=head1 CONTRIBUTORS
Chris Winters initially helped get things rolling. OpenPlugin also makes use
of his Class::Factory module, and I occasionally borrow code from
OpenInteract/SPOPS.
=head1 SEE ALSO
Web applications which make use of OpenPlugin:
=over 4
=item * L
=back
=cut