The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Scaffold::Handler;

our $VERSION = '0.02';

use 5.8.8;
use Switch;
use Try::Tiny;
use Scaffold::Stash;

use Scaffold::Class
  version   => $VERSION,
  base      => 'Scaffold::Base',
  accessors => 'stash scaffold page_title',
  mutators  => 'is_declined',
  constants => 'TRUE FALSE :state :plugins',
  messages => {
      'declined'          => '%s',
      'redirect'          => "%s",
      'moved_permanently' => "%s",
      'render'            => "%s",
      'not_found'         => "%s",
      'bad_url'           => "%s",
  },
  constant => {
      DECLINED   => 'scaffold.handler.declined',
      REDIRECT   => 'scaffold.handler.redirect',
      MOVED_PERM => 'scaffold.handler.moved_permanently',
      RENDER     => 'scaffold.handler.render',
      NOTFOUND   => 'scaffold.handler.notfound',
      BADURL     => 'scaffold.handler.bad_url',
  }
;

use Data::Dumper;

# ----------------------------------------------------------------------
# Public Methods
# ----------------------------------------------------------------------

sub handler {
    my ($class, $module, @params) = @_;

    my $action;
    my $location;
    my $state = STATE_PRE_ACTION;
    my $p1 = ( shift(@params) || 'main' );
    my $root = $class->scaffold->config('configs')->{'app_rootp'};

    $p1 = 'main' if ($p1 eq '1');
    $action = 'do_' . $p1;

    $class->{stash} = Scaffold::Stash->new(
        request => $class->scaffold->request
    );

    $class->{page_title} = $location = $class->scaffold->request->uri->path;

    $class->scaffold->response->status('200');
    $class->scaffold->response->header('Content-Type' => 'text/html');

    try {

        LOOP: 
        while ($state) {

            switch ($state) {
                case STATE_PRE_ACTION {
                    $state = $class->_pre_action();
                }
                case STATE_ACTION {
                    $state = $class->_perform_action($action, $p1, @params);
                }
                case STATE_POST_ACTION {
                    $state = $class->_post_action();
                }
                case STATE_PRE_RENDER {
                    $state = $class->_pre_render();
                }
                case STATE_RENDER {
                    $state = $class->_process_render();
                }
                case STATE_POST_RENDER {
                    $state = $class->_post_render();
                }
                case STATE_FINI {
                    last LOOP;
                }
            };

        }

    } catch {

        my $ex = $_;

        $class->exceptions($ex, $action, $location, $module);

    };

    $class->_pre_exit();

    return $class->scaffold->response;

}

sub redirect {
    my ($self, $url) = @_;

    my $uri = $self->scaffold->request->uri;
    $url = substr($url, 1);
    $uri->path($url);

    $self->throw_msg(REDIRECT, 'redirect', $uri->canonical);

}

sub moved_permanently {
    my ($self, $url) = @_;

    my $uri = $self->scaffold->request->uri;
    $url = substr($url, 1);
    $uri->path($url);

    $self->throw_msg(MOVED_PERM, 'moved_permanently', $uri->canonical);

}

sub declined {
    my ($self) = @_;

    $self->throw_msg(DECLINED, 'declined', "");

}

sub not_found {
    my ($self, $file) = @_;

    $self->throw_msg(NOTFOUND, 'not_found', $file);

}

sub bad_url {
    my ($self, $url) = @_;

    $self->throw_msg(BADURL, 'bad_url', $url);
    
}

sub exceptions {
    my ($self, $ex, $action, $location, $module) = @_;

    my $page;
    my $ref = ref($ex);

    if ($ref && $ex->isa('Badger::Exception')) {

        my $type = $ex->type;
        my $info = $ex->info;

        switch ($type) {
            case MOVED_PERM {
                $self->scaffold->response->redirect($info, '301');
            }
            case REDIRECT {
                $self->scaffold->response->redirect($info, '302');
            }
            case RENDER {
                $page = $self->custom_error(
                    $self->scaffold,
                    $self->page_title,
                    $info,
                );
                $self->scaffold->response->status('500');
                $self->scaffold->response->body($page);
            }
            case DECLINED {
                my $text = qq(
                    Declined - undefined method<br />
                    <span style='font-size: .8em'>
                    Method: $action <br />
                    Location: $location <br />
                    Module: $module <br />
                    </span>
                );
                $page = $self->custom_error(
                    $self->scaffold,
                    $self->page_title,
                    $text,
                );
                $self->scaffold->response->status('404');
                $self->scaffold->response->body($page);
            }
            case NOTFOUND {
                my $text = qq(
                    File not found<br />
                    <span style='font-size: .8em'>
                    File: $info<br />
                    </span>
                );
                $page = $self->custom_error(
                    $self->scaffold,
                    $self->page_title,
                    $text,
                );
                $self->scaffold->response->status('404');
                $self->scaffold->response->body($page);
            }
            case BADURL {
                my $text = qq(
                    URL not handled<br />
                    <span style='font-size: .8em'>
                    URL: $info<br />
                    </span>
                );
                $page = $self->custom_error(
                    $self->scaffold,
                    $self->page_title,
                    $text,
                );
                $self->scaffold->response->status('404');
                $self->scaffold->response->body($page);
            }

        }

    }

}

# ----------------------------------------------------------------------
# Private Methods
# ----------------------------------------------------------------------

sub init {
    my ($self, $config) = @_;

    $self->{config} = $config;
    $self->{scaffold} = $config->{scaffold};

    return $self;

}

sub _pre_action {
    my ($self) = @_;

    my $pstatus;
    my $status = STATE_ACTION;

    if (my $plugins = $self->scaffold->plugins) {

        foreach my $plugin (@$plugins) {

            $pstatus = $plugin->pre_action($self);
            last if ($pstatus != PLUGIN_NEXT);

        }

    }

    return $status;

}

sub _perform_action {
    my ($self, $action , $p1, @p) = @_;

    my $method = lc($action);

    if ($self->can($method)) {

        $self->$method(@p);

    } elsif ($self->can('do_default')) {

        $self->do_default($p1, @p);

    } else {

        $self->declined();

    }

    $self->declined() if ($self->is_declined);

    return STATE_POST_ACTION;

}

sub _post_action {
    my ($self) = @_;

    my $pstatus;
    my $status = STATE_PRE_RENDER;

    if (my $plugins = $self->scaffold->plugins) {

        foreach my $plugin (@$plugins) {

            $pstatus = $plugin->post_action($self);
            last if ($pstatus != PLUGIN_NEXT);

        }

    }

    return $status;

}

sub _pre_render {
    my ($self) = @_;

    my $pstatus;
    my $status = STATE_RENDER;

    if (my $plugins = $self->scaffold->plugins) {

        foreach my $plugin (@$plugins) {

            $pstatus = $plugin->pre_render($self);
            last if ($pstatus != PLUGIN_NEXT);

        }

    }

    return $status;

}

sub _process_render {
    my ($self) = @_;

    my $status = STATE_POST_RENDER;
    my $view   = $self->stash->view;
    my $cache  = $self->scaffold->cache;
    my $page   = $self->stash->view->data;

    # set the content type

    if (my $type = $view->content_type) {

        $self->scaffold->response->header('Content-Type' => $type);

    }

    # render the output

    if (! $view->template_disabled) {

        if ($view->cache) {

            if ($page = $cache->get($view->cache_key)) {

                $self->scaffold->response->body($page);

            } else {

                $self->_process_page($page);

            }

        } else {

            $self->_process_page($page);

        }

    } else {

        $self->scaffold->response->body($page);

    }

    return $status;

}

sub _process_page {
    my $self = shift;

    my $view  = $self->stash->view;
    my $cache = $self->scaffold->cache;
    my $page  = $self->stash->view->data;

    if (my $render = $self->scaffold->render) {

        if (! $view->template_disabled) {

            $page = $render->process($self);

        }

        # cache the output

        if ($view->cache) {

            $cache->set($view->cache_key, $page);

        }

    }

    $self->scaffold->response->body($page);

}

sub _post_render {
    my ($self) = @_;

    my $pstatus;
    my $status = STATE_FINI;

    if (my $plugins = $self->scaffold->plugins) {

        foreach my $plugin (@$plugins) {

            $pstatus = $plugin->post_render($self);
            last if ($pstatus != PLUGIN_NEXT);

        }

    }

    return $status;

}

sub _pre_exit {
    my ($self) = @_;

    my $pstatus;

    if (my $plugins = $self->scaffold->plugins) {

        foreach my $plugin (@$plugins) {

            $pstatus = $plugin->pre_exit($self);
            last if ($pstatus != PLUGIN_NEXT);

        }

    }

}

1;

__END__

=head1 NAME

Scaffold::Handler - The base class for Scaffold Handlers

=head1 SYNOPSIS

 use Scaffold::Server;

 my $server = Scaffold::Server->new(
    locations => [
        {
            route   => qr{^/$},
            handler => 'App::Main'
        },{
            route   => qr{^/something/(\w+)/(\d+)$},
            handler => 'App::Something'
        }
    ]
 );

 ...

 package App::Main;

 use Scaffold::Class
   version => '0.01',
   base    => 'Scaffold::Handler',
   filesystem => 'File',
 ;

 sub do_main
     my ($self) = @_;

    $self->view->template_disable(1);
    $self->view->data('<p>Hello World</p>');

 }

 1;

 package App::Something;

 use Scaffold::Class
   version => '0.01',
   base    => 'Scaffold::Handler',
   filesystem => 'File',
 ;

 sub do_main
     my ($self, $action, $id) = @_;

    my $text = sprintf("action = %s, id = %s\n", $action, $id);

    $self->view->template_disable(1);
    $self->view->data($text);

 }

 1;

=head1 DESCRIPTION

This is the base class for all handlers within Scaffold. Your application will
inherit and extend this class. Handlers are the basis of your application.  

=head2 The Request Lifecycle

When a request comes into Scaffold it is first processed by L<Scaffold::Server|Scaffold::Server>
which takes the incomming url and passes it to L<Scaffold::Routes|Scaffold::Routes>.
Scaffold::Routes parses the url depending on the regex from the "route" verbs 
in the "locations" stanzia of the configuration. If a match is found it returns
the "handler" associated with the route and any parameters extracted from
the url. The server then calls the handler's handler() method passing the
parameters in @_;

=head2 Plugins

Scaffold handlers have an internal state machine. At certain steps, plugins are
called. Scaffold loads three plugins at startup. They are 
L<Scaffold::Cache::Manager|Scaffold::Cache::Manager>,
L<Scaffold::Session::Manager|Scaffold::Session::Manager> and L<Scaffold::Stash::Manager|Scaffold::Stash::Manager>.
These plugins help maintain the Scaffold environment. Plugins are guranteed to
run in the order they are defined.

=head2 The State Machine

The following are the steps that the state machine performs. 

=over 4

=item pre_action

Plugins are called during this phase. For example Scaffold::Cache::Manager
and Scaffold::Session::Manager run in this phase.

=item action

Your main line code is called during this phase. Please see below to 
understand how it is called.

=item post_action

Plugins are called during this phase.

=item pre_render

Plugins are called during this phase.

=item render

Your defined render is called to process items in the view stash.

=item post_render

Plugins are called during this phase.

=item pre_exit

Plugins are called during this phase. For example Scaffold::Stash::Manager and
Scaffold::Session::Manager run during this phase. This is also the last phase 
before the response is returned back to Scaffold::Server.

=back

=head2 The Action Phase

The action phase is where your mainline application code is called. During 
this phase one of three options can happen. They are the following.

=over 4

=item Option 1

If any parameters where extracted from the url, the first one is assumed
to be the method that will be called in the handler. This paramenter is
then prepended with "do_" and is checked with can() to see if it is defined.
If the method is defined it is called with the remaining parameters passed 
in @_;

=item Option 2

If the above method is not defined then "do_main" is checked for with can().
If it is defined, it is called with all the parameters passed in @_.

=item Option 3 

If "do_main" is not defined then "do_default" is checked for with can(). If it
is defined, it is called with all the parameters passed in @_. If it doesn't
exist an exception is thrown and a nice error page is displayed.

=back

=head2 Extending and Overriding

Since Scaffold::Handler is inherited, you can override any of the methods
that the default handler defines. For example you can override the 
exceptions() method to handle your applications exceptions.

=head2 Where's the MVC

Scaffold handlers don't enforce the MVC pattern. You can certainly write your
code in that fashion. There is nothing stopping you. The handler can be 
considered the controller, the render phase could be considered the view, 
all you would have to do is create the model. Remember, Scaffold is 
about flexiablity, not comformity to predefined methodolgies.

=head1 METHODS

The following are the methods that you inherit from Scaffold::Handler.

=over 4

=item handler(sobj, ref(handler) params) 

The main entry point. This method contains the state machine that handles the
life cycle of a request. It runs the plugins sends the output thru a renderer
for format and returns the response back to the dispatcher.

=item redirect(url)

The method performs a 302 redirect with the specified URL. A fully qualified 
URL is returned in the response header.

 $self->redirect('/login');

Redirects are considered exceptions. When one is generated normal processing
stops and the redirect happens. Since 3xx level http codes are handled directly
by the browser, this method is a prime candiate to override in a single page
JavaScript application. In that case it may return a data structure that has
meaning to the JavaScript application.

=item moved_permanently(url)

The method performs a 301 redirect with the specified URL. A fully qualified 
URL is returned in the response header.

 $self->moved_permanently('/login');

This is considered an exception and normal processing stops.

=item declined()

This method performs a 404 response, along with an error page. The error page
shows the location and the handler that was supposed to run along with a dump
of various objects within Scaffold.

 $self->declined();

This is considered an exception and normal processing stops.

=item not_found(file)

This method performs a 404 response, along with an error page. The error page
shows the name of the file that was not found along with a dump of various
objects within Scaffold.

 $self->not_found($file);

This is considered an exception and normal processing stops.

=item exceptions()

This method performs exception handling. The methods redirect(), 
moved_permanently(), declined() and not_found() throw exceptions. They are 
handled here. If other exception types need to be handled, this method 
can be overridden.

=back

=head1 SEE ALSO

 Scaffold
 Scaffold::Base
 Scaffold::Cache
 Scaffold::Cache::FastMmap
 Scaffold::Cache::Manager
 Scaffold::Cache::Memcached
 Scaffold::Class
 Scaffold::Constants
 Scaffold::Engine
 Scaffold::Handler
 Scaffold::Handler::Default
 Scaffold::Handler::Favicon
 Scaffold::Handler::Robots
 Scaffold::Handler::Static
 Scaffold::Lockmgr
 Scaffold::Lockmgr::KeyedMutex
 Scaffold::Lockmgr::UnixMutex
 Scaffold::Plugins
 Scaffold::Render
 Scaffold::Render::Default
 Scaffold::Render::TT
 Scaffold::Routes
 Scaffold::Server
 Scaffold::Session::Manager
 Scaffold::Stash
 Scaffold::Stash::Controller
 Scaffold::Stash::Cookie
 Scaffold::Stash::Manager
 Scaffold::Stash::View
 Scaffold::Uaf::Authenticate
 Scaffold::Uaf::AuthorizeFactory
 Scaffold::Uaf::Authorize
 Scaffold::Uaf::GrantAllRule
 Scaffold::Uaf::Login
 Scaffold::Uaf::Logout
 Scaffold::Uaf::Manager
 Scaffold::Uaf::Rule
 Scaffold::Uaf::User
 Scaffold::Utils

=head1 AUTHOR

Kevin L. Esteb, E<lt>kevin@kesteb.usE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2009 by Kevin L. Esteb

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.5 or,
at your option, any later version of Perl 5 you may have available.

=cut