Skip to content

Commit

Permalink
Item14237: Added documentation to DBConfigExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
vrurg committed May 9, 2017
1 parent 6d4c92b commit 7d13baf
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 21 deletions.
56 changes: 40 additions & 16 deletions DBConfigExtension/data/System/DBConfigExtension.txt
@@ -1,32 +1,56 @@
%META:TOPICINFO{author="ProjectContributor" date="1494032249" format="1.1" version="1"}%
%META:TOPICPARENT{name="Plugins"}%
---+!! Database Config Extension
%FORMFIELD{"Description"}%

This extension purpose is to make possible use of a database to store
%WIKITOOLNAME% configuration.

%TOC%

Learn how to create your own plugin in %SYSTEMWEB%.DevelopingPlugins.
Documentation for Plugin API is in [[%SYSTEMWEB%.PerlDoc?module=Foswiki::Plugins::EmptyPlugin][PerlDoc for EmptyPlugin.pm]].
Database Config Extension extends =Foswiki::Config= class low-level
read/write routines to store all configuration keys in a user-defined
database. The extension doesn't override the class' own read/write methods
but only extends them to add database backed storage to the file-based one.

One possible purpose for the extension is to be used in a distributed
environment where single overloaded wiki is been backed by a load-balanced
set of servers. In this case each server would only have a minimal set of
settings in its local site configuration file enough to perform basic
startup and extension loading. After that this extension would step in,
fetch common site configuration from a DB and – voilà! – the server is
configured exactly as others in its farm.

Sure, this is not enough to get a fully distributed system because in
addition to the configuration the topics must be backed by a DB storage
too. But this is a job for another extension.

<div class="foswikiHelp">%T% Note that EmptyPlugin is documentation only. Its
settings are ignored by =configure= and it cannot be enabled.</div>
Another purpose of this extension is to serve as a demo of capabilities for
the new extensions framework.

---++ Preferences

Plugin preferences should be set using =configure=, as described in
%SYSTEMWEB%.DevelopingPlugins.

You can also use [[%SYSTEMWEB%.PreferenceSettings][preference settings]]
to define any user-controllable configuration. You are strongly advised
to use the name of the plugin as a prefix, to avoid the risk of namespace
clashes with other extensions that may try to use the same name.
The extension receives its initial configuration from a locally stored file
by means of =Foswiki::Config::readLSC= method. The following keys are
defining necessary parameters to connect to a database and are stored under
_Extensions.DBConfigExtension.Connection_ branch.

| *Key* | *Description* | *Default value* |
| =Host= | Host name of the DB server | _localhost_ |
| =Port= | Port number | =undef= |
| =Drvier= | DBI driver to use | _mysql_ |
| =Database= | Database name | _fw_config_ |
| =Table= | Table name | _LSC_ |
| =User= | DB server account name | _dbuser_ |
| =Password= | DB server account password | _dbpassword_ |
| =DSN= | DBI Data Source Name string | an empty string |
| =EnableSSL= | Allow connection over SSL (currently not used) | =TRUE= |

---++ Demo Code

*BAD*
* Set EXAMPLE = Example setting
* Set FORMAT = %d-%m-%y
*Good*
* Set EMPTYPLUGIN_EXAMPLE = Example setting
* Set EMPTYPLUGIN_FORMAT = %d-%m-%y
See [[%SYSTEMWEB%.PerlDoc?module=Foswiki::Extension::DBConfig][Foswiki::Extension::DBConfig]].

---++ Installation
%$INSTALL_INSTRUCTIONS%
Expand All @@ -39,7 +63,7 @@ clashes with other extensions that may try to use the same name.
| | |

%META:FORM{name="PackageForm"}%
%META:FIELD{name="Author" title="Author" value="%25$CREATED_AUTHOR%25"}%
%META:FIELD{name="Author" title="Author" value="ProjectContributor"}%
%META:FIELD{name="Version" title="Version" value="%25$VERSION%25"}%
%META:FIELD{name="Release" title="Release" value="%25$RELEASE%25"}%
%META:FIELD{name="Description" title="Description" value="%25$SHORTDESCRIPTION%25"}%
Expand Down
195 changes: 190 additions & 5 deletions DBConfigExtension/lib/Foswiki/Extension/DBConfig.pm
@@ -1,5 +1,42 @@
# See bottom of file for license and copyright information

=begin TML
---+!! Class Foswiki::Extension::DBConfig
The main class providing extension functionality.
---++ Code description
First of all, this extension is not production ready. Though to get it ready
only a few minor touches are needed.
This extension utilizes two basic features of the new extensions framework:
pluggable methods and tag handlers.
The tag handler declares a new tag named =DBCONFIG_INFO= which does a very
simplistic thing: it outputs a table with connection parameters used to
initialize the extension.
All job is done by extending pluggable methods =readLSC=, =writeLSCStart=,
=writeLSCRecord=, and =writeLSCFinalize= as provided by =Foswiki::Config=.
---+++ Database Format
Configuration is stored in the database as a key/value pair accompanied with
comment field (*key*, *value*, *comment* table columns respectively). Keys are
stored with their full path notation; for example:
<verbatim>
Extensions.DBConfigExtension.Connection.Table
</verbatim>
Values and comments are stored one-to-one as passed over to
=Foswiki::Config::writeLSCRecord=. _undef_ values are stored as undefined in the
DB.
=cut

package Foswiki::Extension::DBConfig;

use DBI;
Expand All @@ -12,8 +49,26 @@ extends qw(Foswiki::Extension);

use version 0.77; our $VERSION = version->declare(0.1.1);
our $API_VERSION = version->declare("2.99.0");

# Features this extension require to run.
our @FS_REQUIRED = qw( MOO OOSPECS );

=begin TML
---++ Object Attributes
=cut

=begin TML
---+++ ObjectAttribute dbh
Initialized instance database handle object returned by [[CPAN:DBI]] =connect()= method.
See =prepareDbh= method.
=cut

has dbh => (
is => 'rw',
lazy => 1,
Expand All @@ -22,19 +77,84 @@ has dbh => (
builder => 'prepareDbh',
);

=begin TML
---+++ ObjectAttribute sth
Initialized instance of a statement handle, as returned by [[CPAN:DBI]]
=prepare= method.
=cut

has sth => (
is => 'rw',
clearer => 1,
);

=begin TML
---+++ ObjectAttribute data
Cached reference to =$app->cfg->data= hash. Initialized by
DBConfig's =readLSC= method extension.
=cut

has data => ( is => 'rw', );

# Text to be indicate if we're reading or writing LSC.
=begin TML
---+++ ObjectAttribute mode
Text to indicate if we're reading or writing LSC. Used for error reporting.
=cut

has mode => ( is => 'rw', );

# We cannot use readLSC{Start|Record|Finalize} because we depend of database
# connection info stored in the local LSC file. This is also the reason for
# using plugAfter.
=begin TML
---++ Methods and method extenders
=cut

=begin TML
---+++ ObjectMethod readLSC
This is an extender for corresponding =Foswiki::Config= pluggable method.
Contrary to how the extension deals with writing of LSC,
readLSC{Start|Record|Finalize} cannot be used because there is dependcy on
database connection info stored in the local LSC file. This is also the reason
for using plugAfter: at this stage it's guaranteed that the local file has been
read by =Foswiki::Config=.
Basically, =readLSC= does nothing more than fetching key/value pairs from the database,
converts value from Perl code to pure data by =eval='ing it stores the result into
the configuration data hash by calling =Foswiki::Config::set= method.
---++++ Error processing
If any exception is thrown at this stage it would basically be ignored
and only a warning will be issued with text generated by =Foswiki::Exception='s
=stringify()= method. The exception could be cause by either a [[cpan:DBI]]
error, or syntax eror within eval as the most common causes.
This approach to error handling has been chosen with the reasoning in mind that
this extension is not mandatory for site funtioning and that admin must be given a chance
to proceed further with possibly fixing the problem by running configure or fetching
more information by using any other means provided by %WIKITOOLNAME%.
For now the error is only reported by issuing a call to Perl's =warn= function
and enforcing =readLSC= return value to a false value (=0=). Though use of
=warn= is undesirable because it causes fatal exception when DEBUG is on. Yet,
even if not causing application death it sends output to a log file only. As
soon as =Foswiki::App= will have bufferized broadcast messaging support it must
come in place of the =warn= call.
=cut

plugAfter 'Foswiki::Config::readLSC' => sub {
my $this = shift; # This is extension object, not the Foswiki::Config
my ($params) = @_;
Expand All @@ -53,7 +173,7 @@ plugAfter 'Foswiki::Config::readLSC' => sub {
my $connData = $cfgData->{Extensions}{DBConfigExtension}{Connection};

# SMELL We expect all connection data to be in place if the Connection key
# is defined. Fair enough for testing but better be fully checked for the
# is defined. Fair enough for testing but better be fully validated for the
# production use.
if ( defined $connData ) {
try {
Expand Down Expand Up @@ -89,6 +209,19 @@ plugAfter 'Foswiki::Config::readLSC' => sub {
}
};

=begin TML
---+++ ObjectMethod writeLSCStart
Extends corresponding =Foswiki::Config= method. Prepares the database for
writing by clearing up the configuration table of the old records and preparing
a new statement handle.
The table would be locked until writing of the new config is done for a good or
a bad reason.
=cut

plugAround 'Foswiki::Config::writeLSCStart' => sub {
my $this = shift;
my ($params) = @_;
Expand Down Expand Up @@ -120,6 +253,14 @@ plugAround 'Foswiki::Config::writeLSCStart' => sub {
$this->sth($sth);
};

=begin TML
---+++ ObjectMethod writeLSCRecord
Extends corresponding =Foswiki::Config= method. Writes a record to the databse.
=cut

plugAround 'Foswiki::Config::writeLSCRecord' => sub {
my $this = shift;
my ($params) = @_;
Expand All @@ -140,6 +281,15 @@ plugAround 'Foswiki::Config::writeLSCRecord' => sub {
$this->sth->execute( $keyPath, $keyVal, $comment );
};

=begin TML
---+++ ObjectMethod writeLSCFinalize
Extends corresponding =Foswiki::Config= method. Commits all changes and
disconnects from the database.
=cut

plugAround 'Foswiki::Config::writeLSCFinalize' => sub {
my $this = shift;

Expand All @@ -154,6 +304,14 @@ plugAround 'Foswiki::Config::writeLSCFinalize' => sub {
$this->clear_dbh;
};

=begin TML
---+++ tagHandler DBCONFIG_INFO
Outputs a table with this extension's version and connection information.
=cut

tagHandler DBCONFIG_INFO => sub {
my $this = shift;

Expand All @@ -170,6 +328,14 @@ tagHandler DBCONFIG_INFO => sub {
return $text;
};

=begin TML
---+++ ObjectMethod lockTable
Locks configuration table.
=cut

sub lockTable {
my $this = shift;

Expand All @@ -182,6 +348,16 @@ sub lockTable {
$this->dbh->do("LOCK TABLE $table $lockMode");
}

=begin TML
---+++ ObjectMethod prepareDbh
Builder for the =dbh= attribute of the extension's object. Connects to a
database using information from =Extensions.DBConfigExtension.Connection=
configuration key.
=cut

sub prepareDbh {
my $this = shift;

Expand Down Expand Up @@ -224,6 +400,15 @@ sub prepareDbh {
return $dbh;
}

=begin TML
---+++ ObjectMethod _dbiError
Called by =HandleError= callback of =[[cpan:DBI]]. Rollbacks current transaction,
disconnects and throws a fatal exception.
=cut

sub _dbiError {
my $this = shift;

Expand Down

0 comments on commit 7d13baf

Please sign in to comment.