cvsuser 02/02/05 14:26:44
Added: P5EEx/Blue/P5EEx/Blue devguide.pod
Log:
began the devguide to describe the flow of control through the framework
Revision Changes Path
1.1 p5ee/P5EEx/Blue/P5EEx/Blue/devguide.pod
Index: devguide.pod
===================================================================
#!perl -w
# run this document through perl to check its syntax
use Pod::Checker;
podchecker(\*DATA);
__END__
=head1 NAME
P5EEx::Blue::devguide - P5EE Developer's Guide
=head1 INTRODUCTION
This is the Developer's Guide to the
P5EE is the Perl 5 Enterprise Environment.
You can find out more background to the project on the web.
http://p5ee.perl.org
http://www.officevision.com/pub/p5ee
=head1 P5EE DESIGN PHILOSOPHY
When the P5EE project was begun, there were already
* many outstanding Perl packages on CPAN
* an excellent systems architecture for Perl webapps (mod_perl)
* several excellent web application development frameworks
So why was P5EE needed?
For a variety of reasons, there was never sufficient unity of
purpose or direction within the Perl community to provide
a coherent blueprint of what Enterprise Programming in Perl is
or how to do it.
* What are the pieces?
* What pieces are default? standard? mandatory? optional?
* How do they fit together?
* How can I override or customize them?
* What techniques do I need to use to assemble them effectively?
After "Enterprise Systems" were defined (see website), the field
of existing frameworks, solutions, and components was surveyed.
The goal was to examine everything that people were already doing,
divide it into pieces that seemed interchangeable, and come up
with a unified blueprint. Pieces that people often like to do
differently (template systems, persistence frameworks, configuration
files) were allowed to vary, while their essential contribution
to the working system was standardized in the framework.
It should be noted that most of the work on what people might
term "Enterprise Systems" in Perl was actually focused on
"Web Systems" in Perl with a relational database.
The goal of the P5EE design is to unify the Perl community
on a framework for cooperation, while providing flexibility in
the areas that might otherwise divide it.
Essentially, everything in P5EE is overridable and customizable,
but good defaults are provided for everything as well.
On the practical side, P5EE was also designed to allow for gradual
adoption and incorporation into existing projects.
=head1 P5EE Execution Flow: CGI
The first step to understanding the flow of execution through
the P5EE framework is to understand the flow in the CGI
Context.
This is one of the most challenging contexts
to develop for because of the stateless nature of HTTP,
the need to initialize all resources before accessing them,
and the need to properly shut down all resources after using
them or in case of user abort.
=head2 cgi-bin/p5ee and the Initialization Config File
All usage of P5EE from the web can be driven through the CGI program,
"cgi-bin/p5ee". (Actually, depending on the settings in the Initialization
Config File, the "p5ee" program might not be a CGI program at all.)
The p5ee program should be installed at a location which is executable
as a CGI program such as the following.
http://www.officevision.com/cgi-bin/pub/p5ee/p5ee
The "p5ee" progam runs with the "-wT" switches turned on for maximum
safety, security, and enforcement of programming rigor.
Then a BEGIN block is executed to read the Initialization Config File
to get low level config settings and perhaps modify the @INC variable.
Modification of the @INC variable in the BEGIN block through configuration
is critical so that you can have multiple versions of modules installed
(at various stages of development through testing, production, and support).
and access the correct ones.
The Initialization Config File (.conf) is located in the following way.
First, the PATH_INFO is checked and a corresponding .conf file is opened.
Thus, the following URL
http://www.officevision.com/cgi-bin/pub/p5ee/p5ee/ecommerce/shop
would open "ecommerce_shop.conf" in the directory of the "p5ee" program.
If it is not found, "$0.conf" is opened. That would be "p5ee.conf"
in this case. However, you can see that this allows for the "p5ee"
program to be renamed, and (according to its configuration) it will
behave like a completely different application.
If it is still not found, "p5ee.conf" is opened.
If this is not found, no Initialization Config information will be
used and all defaults will be used.
Whichever .conf file was first opened, it is read for simple configuration
variables of the form "variable = value". Anything following a "#" is considered
a comment. Leading and trailing spaces are removed, and blank lines are
ignored. Spaces may precede or follow the "=" sign without affecting the
"variable" or the "value". The "variable" must be a sequence of alphabetic
characters
and ".", "_", or "-" (i.e. matches /[a-zA-Z_.-]+/). The value may be
any string of characters (including none), but leading and trailing spaces
are stripped. The variable/value pairs are saved in a global hash in %main::conf.
If the "perlinc" variable is set, it is understood to be a comma-separated
list of directories to search for Perl modules. This list is placed
at the beginning of the special Perl @INC variable.
Some sample lines in the .conf file are:
perlinc = /usr/ov/acoc/dev/src/P5EEx-Blue, /usr/ov/acoc/dev/lib/perl5/5.6.0
debugmode = record
showsession = 1
gzip = 1
configFile = p5ee.pl
The meanings of these variables are:
perlinc - directories to search for perl modules
debugmode - (off|record|replay) useful for recording a failed CGI request and
replaying it for debugging
debug -
(0|1,P5EEx::Blue::Context::CGI|6,P5EEx::Blue::Repository::DBI.select_rows)
showsession - (0|1) show the contents of the session in an HTML comment
gzip - (0|1) allows compression of HTML output if the client browser
supports it
Additional variables may be provided. However, suitable defaults
are usually detected if they are not supplied.
contextClass - (i.e. P5EEx::Blue::Context::CGI) class for the context
configClass - (i.e. P5EEx::Blue::Config::File) class for the config
configFile - (i.e. "config.pl") name of the main configuration file
configSerializerClass - (i.e. P5EEx::Blue::Serializer::Dumper) class for the
config deserialization
sessionClass - (i.e. P5EEx::Blue::Session::HTMLHidden) class for the session
defaultWname - widget name to be the first current_widget to be displayed
After the Initialization Config File is read into %main::conf, the
command line arguments are scanned for options of the form "-variable" or
"-variable=value". (Options may also start with double-dashes, "--".)
Such options are removed from the command line, and the variable/value
pair is added to the %main::conf hash, thus overriding any values from
the Initialization Config File. The CGI environment never passes options
to the program in this way (with a preceding dash), so this is mainly
useful for debugging at the command line
(i.e. "p5ee -debugmode=replay -debug=1 -gzip=0").
=head2 use P5EEx::Blue::P5EE
Next, the P5EEx::Blue::P5EE module is included, which does the following:
* disable "Use of uninitialized value" warnings
* use Fatal; # cause internal perl functions to throw exceptions
* use Error; # enable try/catch syntax
* use Exception::Class; # enable Exception inheritance
* use P5EEx::Blue::Exceptions; # define P5EE exceptions
The base class of all P5EE exceptions is "P5EEx::Blue::Exception".
Derived from this base exception, each component service of P5EE
has its own base class as follows.
P5EEx::Blue::Exception::Context
P5EEx::Blue::Exception::Config
P5EEx::Blue::Exception::Serializer
P5EEx::Blue::Exception::Repository
P5EEx::Blue::Exception::Security
P5EEx::Blue::Exception::Session
P5EEx::Blue::Exception::Widget
P5EEx::Blue::Exception::TemplateEngine
P5EEx::Blue::Exception::Procedure
P5EEx::Blue::Exception::Messaging
P5EEx::Blue::Exception::LogChannel
=head2 cgi-bin/p5ee and bootstrapping the environment
The "p5ee" program then executes the following line.
my $context = P5EEx::Blue::P5EE->context(\%main::conf);
This instantiates a $context object, passing the
global %main::conf hash as an argument.
Note that in other Context implementations other than
CGI, the $context may survive to serve more than a single
request or to dispatch many events coming from the network
or from a user. In that case, the
P5EEx::Blue::P5EE->context() method may return an
already-instantiated $context object. The $context
is a singleton per process.
=head2 P5EEx::Blue::P5EE->context()
If the "contextClass" variable is in the argument hash, it is used.
Otherwise, if the "p5ee" program is running in the CGI context
(HTTP_USER_AGENT environment variable is set) or at the
command line, the P5EEx::Blue::Context::CGI class
will be assumed.
The code for the selected class is loaded and a $context
of the appropriate class is instantiated.
For the CGI context, the P5EEx::Blue::Context::CGI->new()
method is called, and the arguments of the context() method
call (%main::conf, in this case) are passed on to it.
=head2 P5EEx::Blue::Context::CGI->new()
The constructor (new()) for P5EEx::Blue::Context::CGI is actually provided
by its parent class, P5EEx::Blue::Context.
If the "configClass" was not specified in the arguments
(%main::conf, in this case), P5EEx::Blue::Config::File is assumed.
It is instantiated, once again passing on the hash of initialization args,
and the result is stored in $self->{config};
Then the P5EEx::Blue::Context::CGI->init() method is called to
complete the $context constructor. A CGI object is created
and added to the %args to be passed on to Session instantiation.
If the "sessionClass" was not specified in the arguments,
P5EEx::Blue::Session::HTMLHidden is assumed.
It is instantiated, once again passing on the hash of initialization args,
and the result is stored in $self->{session};
=head2 P5EEx::Blue::Config::File->new()
The constructor (new()) for P5EEx::Blue::Config::File is actually provided
by its grand-parent class, P5EEx::Blue::Reference.
It creates a reference by calling P5EEx::Blue::Config::File->create(),
blesses it into the class, calls init()
(P5EEx::Blue::Reference->init()) which does nothing,
and then returns the constructed Config::File object.
The Config::File->create() method loads data from a configuration
file and returns the reference to a hash. But first it has to find
the file and deserialize it.
If the "configFile" was not specified in the arguments
(%main::conf, in this case), the following files are searched for in
order. (If the script were renamed to "foo", it would look for "foo"
variants of the files instead of "p5ee" variants of the files.)
1. p5ee.pl 2. config.pl
3. p5ee.xml 4. config.xml
5. p5ee.ini 6. config.ini
7. p5ee.properties 8. config.properties
9. p5ee.perl 10. config.perl
11. p5ee.conf 12. config.conf
By convention, "config.pl" is the config file for P5EE CGI scripts.
However, the first file that is found is assumed to be the relevant config file.
If no config file is found or if it cannot be opened, an exception is thrown.
Otherwise, the text is read in from the file and deserialized into
a hash reference.
If the "configSerializerClass" was not specified in the arguments
(%main::conf, in this case), the file suffix for the config file
is used to determine the Serializer class to use for deserialization.
pl # use "eval" instead of a serializer
perl # P5EEx::Blue::Serializer::Dumper (like "pl" but use a serializer)
xml # P5EEx::Blue::Serializer::XMLSimple
ini # P5EEx::Blue::Serializer::Ini
properties # P5EEx::Blue::Serializer::Properties
conf # P5EEx::Blue::Serializer::Properties
stor # P5EEx::Blue::Serializer::Storable
If the file is a .pl file, no serializer is implied. It is just eval'ed.
(It must have "$var =" as the first non-whitespace text in the file,
where "var" is any variable name.)
If a Serializer is specified or implied, a serializer is instantiated
and the config file data is deserialized into a hash reference.
The resulting hash reference is returned and stored in $context->{config}.
=head2 P5EEx::Blue::Context::CGI->init()
The init() method is where the CGI object is created, parsing the
environment variables and STDIN which are
part of the CGI runtime environment.
For debugging purposes, a "debugmode" variable is checked in the
%args (i.e. %main::conf) to see if
special processing with the CGI object should be performed.
If "debugmode" is "record", the CGI objects and the %ENV hash will
be saved to files ("debug.vars" and "debug.env", respectively).
If "debugmode" is "replay", the current %ENV and CGI will be
cleared and loaded from the files from a previously recorded request.
Another sort of debugging is initialized if the "debug"
variable is supplied in the %args. This turns on a global debug
flag ($P5EEx::Blue::Context::DEBUG) and sets the debug scope
(which classes or methods should produce debug output).
To support migration of code, the CGI object can also be passed into
the P5EE->context() method as an argument, and it will be used rather
than trying to create a new one. However, this is not the execution
path being described here.
=head2 P5EEx::Blue::Session::HTMLHidden->new()
The constructor (new()) for P5EEx::Blue::Session::HTMLHidden is actually provided
by its grand-parent class, P5EEx::Blue::Reference.
It creates a hash reference by calling P5EEx::Blue::Reference->create(),
blesses it into the class, calls init()
(P5EEx::Blue::Session::HTMLHidden->init()),
and then returns the constructed Session::HTMLHidden object.
The init() method looks at the CGI variables in the request
and restores the session state information from the variable
named "p5ee.sessiondata" (and "p5ee.sessiondata[2..n]").
When the values of these variables are concatenated, they
form a Base64-encoded, gzipped, frozen multi-level hash of
session state data. To retrieve the state data, the text
is therefore decoded, gunzipped, and thawed (a la Storable).
This state is stored in $session->{state} and the session
cache is initialized to an empty hashref.
=head2 cgi-bin/p5ee and dispatching events
The "p5ee" program finally executes the following line.
$context->dispatch_events();
This does everything necessary to dispatch events which are
implied in the HTTP request, loading whatever data is needed,
modifying it, and saving it again to await the next request.
Please note that other Context implementations (i.e. Context::Modperl)
use the same API, but they may already have database connections
initialized, data already loaded, etc. However, the basic
CGI Context (Context::CGI) described here must initialize
everything at the outset and shut it all down at the completion
of each request.
=head2 P5EEx::Blue::Context::CGI->dispatch_events()
The CGI variables are examined. Variables which start with "p5ee.event"
are identified as events to be handled, and they are saved for later.
These are called "event variables".
All other variables are understood to be widget attributes and they
are saved to their respective widgets.
Variables with any of the
"{}[]" "indexing" characters (such as "table_editor{data}[1][5]") are
called "indexed variables". The value is saved to the "table_editor"
widget, which evidently has an attribute called "data" which is a
two dimensional array.
Variables without the indexing characters but
with at least one dot (".") in them are "dotted variables"
of the form "widgetname.attributename". (This is a synonym for
"widgetname{attributename}", but it is handled more efficiently.)
Widget names may include dots, but attribute names may not.
Thus, the last dot separates the widget name from the attribute name.
So "app.toolbar.savebutton.width" is the "width" attribute on the
"app.toolbar.savebutton" widget.
Variables without indexing characters or dots are "plain variables".
If the special variable "wname" was also supplied, all plain variables
are understood to be attributes of the $wname widget. Otherwise,
all plain variables are stored in the "global" widget.
After all variables are stored in the Session, events are handled.
There are two kinds of events, "user events" (such as come from
<input type=submit> and <input type=image> tags) and "callback events"
(such as come from <input type=hidden> tags).
User events have a name that looks like one of the following.
p5ee.event.widgetname.eventname
p5ee.event.widgetname.eventname(arg1)
p5ee.event.widgetname.eventname(arg1,arg2,...)
These events are parsed, the appropriate widget is summoned, and the
widget is instructed to handle the event (i.e. $w->handle_event()).
Callback events have a name that looks simply like "p5ee.event".
The "widgetname.eventname(args)" is in the value of the variable.
After all events have been handled, the context calls
$self->display_current_widget().
=head2 P5EEx::Blue::Context::CGI->display_current_widget()
The display_current_widget() method is implemented in the parent
class, P5EEx::Blue::Context::HTML.
A attribute "global.current_widget" contains the name of the current
widget to display.
If this is not set, check the CGI variable "wname"
(and set global.current_widget if found).
If this is not set, check the Initialization Config (i.e. the copy
of %main::conf, stored in the $context) for a variable named
"defaultWname".
If this is not set, use the $PATH_INFO, with internal "/'s"
converted to dots (i.e. "/app/selector" => "app.selector").
Otherwise, use "default" as the current widget
(and set global.current_widget).
Then the current widget is summoned and handed to the display_items()
method.
=head2 P5EEx::Blue::Context::CGI->display_items()
The job of display items is to take the list of args, convert them
to HTML, and print them to STDOUT with the appropriate HTTP headers.
Also, depending on the %main::conf and the browser capabilities,
the content may be compressed (gzipped).
That's it. All of the processing for a single CGI request is complete.
All application code is wrapped up in user interface widgets
(P5EEx::Blue::Widget::HTML) and entity widgets (P5EEx::Blue::Widget::Entity).
The user interface widgets are mostly prepackaged, but are configured
through the configuration file, allowing for a data-driven programming
style on the user interface. The entity widgets store most of their state
in Repositories. They also get configured
through the config file, but they are frequently subclassed to add
additional functionality.
=cut