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