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

NAME

OpenInteract::Handler::GenericDispatcher - Define task-dispatching, security-checking and other routines for Handlers to use

SYNOPSIS

 use OpenInteract::Handler::GenericDispatcher qw( DEFAULT_SECURITY_KEY );
 use SPOPS::Secure qw( :level );

 @OpenInteract::Handler::MyHandler::ISA = qw(
                             OpenInteract::Handler::GenericDispatcher SPOPS::Secure );
 %OpenInteract::Handler::MyHandler::default_security = (
     DEFAULT_SECURITY_KEY() => SEC_LEVEL_READ,
     'edit'                 => SEC_LEVEL_WRITE );

DESCRIPTION

The Generic Dispatcher provides the methods to discern what task is supposed to execute, ensure that the current user is allowed to execute it, and returns the results of the task.

It is meant to be subclassed so that your handlers do not have to keep parsing the URL for the action to take. Each action the Generic Dispatcher takes can be overridden with your own.

This module provides the routine 'handler' for you, which does all the routines (security checking and other) for you, then calls the proper method.

There are also a couple of utility methods you can use, although they will probably be punted off to a separate module at some point.

NOTE: This module will likely be scrapped for a more robust dispatching system. Please see NOTES for a discussion.

METHODS

Even though there is only one primary method for this class (handler()), you may override individual aspects of the checking routine:

_get_task

Return a task name by whatever means necessary. Default behavior is to return the next element (lowercased) from:

 $R->{path}{current}

If that element is undefined (or blank), the default behavior returns the the package variable default_method.

Return a string corresponding to a method.

_no_task_found

Called when no task is found from the _get_task method. Default behavior is to email the author of the handler (found in the package variable author) and tell him/her to at least define a default method.

Method should either return or die() with html necessary for displaying an error.

_task_allowed( $task )

Called to ensure the $task found earlier is not forbidden from being run. Tasks beginning with '_' are automatically denied, and we look into the @forbidden_methods package variable for further enlightenment. Return 1 if allowed, 0 if forbidden.

_check_task_security

Called to ensure this $task can be run by the currently logged-in user. Default behavior is to check the security for this user and module against the package hash security, which has tasks as keys and security levels as values.

Note: you can define a default security for your methods and then specify security for only the ones you need using the exported constant 'DEFAULT_SECURITY_KEY'. For instance:

  %My::Handler::Action = (
     DEFAULT_SECURITY_KEY() => SEC_LEVEL_READ,
     edit                   => SEC_LEVEL_WRITE,
  );

So all methods except 'edit' are protected by SEC_LEVEL_READ.

Returns: the level for this user and this task.

_local_task

This is an empty method in the GenericDispatcher, but you can create a subclass of the dispatcher for your application to do application-wide actions. For instance, if you had a tag in every handler that was to be set in $R->{page} and parsed by the main template to select a particular 'tab' on your web page, you could do so in this method.

Caching

Note that the \%params parameter for both of these methods should be the same as the one passed to your implementation method. For instance, in this snippet it would be the hashref $p:

 sub listing {
     my ( $class, $p ) = @_;
 }

Yes, this is awkward. But the current version of OpenInteract content handlers is stateless -- they use class methods rather than objects. The next version will take care of this, but in the meantime we need to pass around more parameters than normal.

Also note that users who are administrators -- as defined in OpenInteract::Auth or a relevant subclass -- do not view or save cached content. This is to prevent OpenInteract from caching a view that includes admin-only links, such as 'Edit' or 'Remove' that normal users do not see.

generate_content( \%params, \%key_params, \%template_params, \%template_variables, \%template_source )

or

generate_content( \%params, \%key_params, \%template_variables, \%template_source )

This optionally replaces the typical last call within a handler:

 return $R->template->handler( \%template_params, \%template_variables,
                               \%template_source );

The purpose is to catch the content before it is passed on and save it to the cache. You can then retrieve it from the cache using check_cache().

For example, if you currently do something like:

 sub listing {
     my ( $class, $p ) = @_;
     my $R = OpenInteract::Request->instance;
     my $thingy_id = $R->apache->param( 'thingy_id' );
     my $thingy = $R->thingy->fetch( $thingy_id );
     ...
     return $R->template->handler( {},
                                   { thingy => $thingy,
                                     error_msg => $error_msg },
                                   { name => 'mypkg::mytmpl' } );
 }

 sub listing {
     my ( $class, $p ) = @_;
     my $R = OpenInteract::Request->instance;
     my $thingy_id = $R->apache->param( 'thingy_id' );
     my $thingy = $R->thingy->fetch( $thingy_id );
     ...
     return $class->generate_content( $p,
                                      { thingy_id => $thingy_id },
                                      { thingy => $thingy,
                                        error_msg => $error_msg, ... },
                                      { name => 'mypkg::mytmpl' } );
 }

There are three things happening here:

  • We have jettisoned the first argument to template->handler, since it was rarely used.

  • We have passed the hashref $p as the first argument.

  • We have passed a hashref of parameters OI will use to cache the content.

check_cache( \%params, \%cache_params )

If cached content exists that matches the cache key for your action and the parameters you pass in, then it is returned.

 package My::Handler;

 use base qw( OpenInteract::Handler::GenericDispatcher );

 sub listing {
     my ( $class, $p ) = @_;
     my $R = OpenInteract::Request->instance;
     my $thingy_id = $R->apache->param( 'thingy_id' );
     my $cached = $class->check_cache( $p, { thingy_id => $thingy_id } );
     return $cached if ( $cached );
     ...
 }

clear_cache()

Whenever you modify, add or remove an object, it is normally best to clear the cache of all items your handler class has produced. All you need to do is call:

  $class->clear_cache();

And everything that handler has created will be cleared out. When the next call is made to one of the methods, it will first check the cache for its content and, not finding it, generate it and set it in the cache again.

Utility

_create_object( \%params )

Create an object from the information passed in via GET/POST and \%params.

Parameters:

 _id_field: \@ or $ with field name(s) used to find ID value
 _class:    $ with class of object to create

Returns: object created with information, undef if object ID not found, die thrown if object class or ID field not given, or if the retrieval fails.

date_process( 'yyyy-mm-dd' )

WARNING: This method might be removed altogether.

Return a hashref formatted:

 { year  => 'yyyy',
   month => 'mm',
   day   => 'dd' }

date_read( $prefix, [ \%defaults ] )

Read in date information from GET/POST information. The fields are:

 day    => ${prefix}_day
 month  => ${prefix}_month
 year   => ${prefix}_year

If you want a default set for the day, month or year, pass the information in a hashref as the second argument.

NOTES

Discussion about Creating a 'Real' Dispatcher

Think about making available to a handler its configuration information from the action.perl file, so you can set information there and have it available in your environment without having to know how your handler was called.

For instance, in your action.perl you might have:

 {
    'news' => {
        language => 'en',
        class    => 'OpenInteract::Handler::News',
        security => 'no',
        title    => 'Weekly News',
        posted_on_format => "Posted: DATE_FORMAT( posted_on, '%M %e, %Y' )",
    },

    'nouvelles' => {
        language => 'fr',
        title    => 'Les Nouvelles',
        redir    => 'news',
        posted_on_format => "Les Post: DATE_FORMAT( posted_on, '%M %e, %Y' )",
    },

 }

A call to the URL '/nouvelles/' would make the information:

 {
   language => 'fr',
   title    => 'Les Nouvelles',
   security => 'no',
   class    => 'OpenInteract::Handler::News', 
   posted_on_format => "Les Post: DATE_FORMAT( posted_on, '%M %e, %Y' )",
 }

available to the handler via the $p variable passed in:

 my $info = $p->{handler_info};
 # $info->{language} is now 'fr'

Use this as the basis for a new class: 'OpenInteract::ActionDispatcher' which you use to call all actions. The ActionDispatcher can lookup the action (and remember all its properties, even through 'redir' calls like outlined above), can check the executor of an action for whether the task can be executed or not (whether the task exists, whether the task is allowed) and can check the security of the task as well. At each step the ActionDispatcher has the option of running its automated checks (which it might cache by website...) or checking callbacks defined in the content handler.

So each content handler would get two arguments: its own class and a hashref of metadata, which would include:

 - task called ('task')

 - action info compiled ('action_info', a hashref with basic things
 like 'class', 'security', 'title' as well as any custom modifications
 by the developer

 - the security level for this user and this task ('security_level'
 and 'security' to be backward compatible)

We will use 'can' to see whether the callback exists in the handler class so the callback could also be defined in a superclass of the handler. So I could define a hierarchy of content handlers and have things just work. (You can do this now, but it is a little more difficult.)

One sticky thing: every request for an action would have to be rewritten to use the dispatcher, although we could create a wrapper in OpenInteract::Request to try for backward compatibility ('lookup_request' and all).

TO DO

Move utility methods to separate class

BUGS

None known.

COPYRIGHT

Copyright (c) 2001-2002 intes.net, inc.. All rights reserved.

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

AUTHORS

Chris Winters <chris@cwinters.com>