The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Eidolon::Application;
# ==============================================================================
#
#   Eidolon
#   Copyright (c) 2009, Atma 7
#   ---
#   Eidolon/Application.pm - application class
#
# ==============================================================================

use Eidolon::Core::Exceptions;
use Eidolon::Core::Registry;
use Eidolon::Core::Loader;
use Eidolon::Core::CGI;
use warnings;
use strict;

our $VERSION = "0.02"; # 2009-05-12 05:12:54

# application interface types
use constant
{
    "TYPE_CGI"  => 0,
    "TYPE_FCGI" => 1
};

# ------------------------------------------------------------------------------
# \% new()
# constructor
# ------------------------------------------------------------------------------
sub new
{
    my ($class, $self);

    $class = shift;

    $self = {};
    bless $self, $class;

    return $self;
}

# ------------------------------------------------------------------------------
# start($type)
# start the application
# ------------------------------------------------------------------------------
sub start
{
    my ($self, $name, $type, $r, $config);

    ($self, $type) = @_;

    $name = ref $self;
    $type = TYPE_CGI unless defined $type;

    $r = Eidolon::Core::Registry->new;
    $config = "$name\::Config";

    # load config
    {
        local $SIG{"__DIE__"} = sub {};
        eval "require $config";
    }

    throw CoreError::Compile($@) if $@;
    $r->config( $config->new($name, $type) );
}

# ------------------------------------------------------------------------------
# handle_request()
# handle HTTP request
# ------------------------------------------------------------------------------
sub handle_request
{
    my ($self, $r, $e, $ctrl, $handler, $router);

    $self = shift;
    $r    = Eidolon::Core::Registry->get_instance;

    eval 
    { 
        local $SIG{"__DIE__"} = sub 
        {
            $_[0]->rethrow if ($_[0] eq "Eidolon::Core::Exception");
            throw CoreError($_[0]);
        };

        # some useful classes
        $r->cgi   ( Eidolon::Core::CGI->new    );
        $r->loader( Eidolon::Core::Loader->new );

        # load drivers specified in config
        foreach (@{ $r->config->{"drivers"} })
        {
            $r->loader->load
            ( 
                $_->{"class"}, 
                exists $_->{"params"} ? @{ $_->{"params"} } : () 
            );
        }

        # get a router
        $router = $r->loader->get_object("Eidolon::Driver::Router");
        throw CoreError::NoRouter unless $router;

        # find and start a request handler
        $router->find_handler;

        {
            no strict "refs";
            &{ $router->get_handler }( @{ $router->get_params || [] } );
        }
    };

    # if something went wrong
    if ($@) 
    {
        $e = $@;

        throw CoreError::NoErrorHandler($e) unless $r->config->{"app"}->{"error"};    
        $ctrl = $r->config->{"app"}->{"error"}->[0] || undef;

        eval "require $ctrl";
        throw CoreError::Compile($@) if $@;

        # find the error handler
        $handler = $r->config->{"app"}->{"error"}->[1];
        throw CoreError::NoErrorHandler($e) unless $handler;

        $handler = "$ctrl\::$handler";

        {
            no strict "refs";
            &{ $handler }( ( $e ) );
        }
    }

    # cleanup
    $r->free;
}

1;

__END__

=head1 NAME

Eidolon::Application - Eidolon application base class.

=head1 SYNOPSIS

Example application package (C<lib/ExampleApp.pm>):

    package ExampleApp;
    use base qw/Eidolon::Application/;

    our $VERSION = "0.01";

    1;

CGI application gateway (C<index.cgi>):

    use lib "./lib";
    use ExampleApp;

    my $app = ExampleApp->new;
    $app->start($app->TYPE_CGI);
    $app->handle_request;

=head1 DESCRIPTION

The I<Eidolon::Application> class is the base high-level class of a usual 
I<Eidolon> application. It creates various system objects, reads 
application configuration and handles user requests. 

Should never be used directly, subclass it from your application's main
package instead.

=head2 Request processing flow

These steps are performed while processing user request. This sequence can be 
redefined by the ancestor class, but in most cases you won't need to do this.

=over 4

=item 1. Create system registry

Registry is the system information structure. This step is done first, because
other parts of the system depend on the registry. For more information see 
L<Eidolon::Core::Registry/Mount points> and L<Eidolon::Core::Registry> module 
documentation.

=item 2. Load application configuration

In this step application loads and instantiates the application configuration 
class. It contains all basic system settings. Application configuration in
example application would be stored in C<lib/ExampleApp/Config.pm>. For more 
information see L<Eidolon::Core::Config>.

=item 3. Accept requests (I<FastCGI application only>)

FastCGI application works like a I<daemon> - it resides in memory, accepting
connections from the web server. Whence a new connection is established, 
the application goes to the next step.

=item 4. Handle request

Actually, all the work is done in this step. First of all, the application
creates the L<Eidolon::Core::CGI> object, that is used to process all incoming 
data. Next, application loads system drivers using the L<Eidolon::Core::Loader> 
object. The list of required drivers must be specified in the application 
configuration (note, that you I<must> specify a router driver, otherwise the 
application will not handle requests properly). Afterwards, the 
application transfers execution to a router driver, which tries to find the 
request handler and then calls it.

If an error occurs on this step, the application calls an error handler,
that is specified in application configuration (or uses the default one - 
Eidolon::Error).

After request (or error) handling, a CGI application shuts down, but FastCGI
application goes next.

=item 5. Go to step #3 (I<FastCGI application only>!)

When a user request is handled, our I<daemon>-like application is ready to
serve another clients I<without termination>, so we save a bit of system
resources.

=back

=head1 METHODS

=head2 new()

Class constructor. Creates an application object.

=head2 start($type)

Application initialization. Creates system registry and loads application
configuration. C<$type> - application type (see L</CONSTANTS> section below).

=head2 handle_request()

User request processing. This function parses and routes the request, then it
transfers execution to request handler. If any exception occurs during 
processing, it tries to start application's error handler. If no such handler 
was found, application dies.

=head1 CONSTANTS

=head2 TYPE_CGI

CGI application is the default type. It is suitable for low-loaded 
applications. When this type is used, the web server starts one interpreter 
instance per user request, so if you issue high CPU usage or high memory 
load - use the next application type.

=head2 TYPE_FCGI

FastCGI application type is suitable for high-loaded applications with a lot
of concurrent connections. It's faster than CGI I<up to 2000%>. This type 
suggests L<FCGI> module to be installed, otherwise application won't work.

=head1 SEE ALSO

L<Eidolon>, L<Eidolon::Debug>, 
L<Eidolon::Core::Registry>,
L<Eidolon::Core::Config>,
L<Eidolon::Core::CGI>,
L<Eidolon::Core::Loader>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHOR

Anton Belousov, E<lt>abel@cpan.orgE<gt>

=head1 COPYRIGHT

Copyright (c) 2009, Atma 7, L<http://www.atma7.com>

=cut