Home | Wiki | OI 1.x Docs | OI 2.x Docs OI logo

NAME

OpenInteract2::Manual::Management - Creating tasks to manage OpenInteract2

SYNOPSIS

This part of the manual describes how create your own tasks using the OpenInteract2::Manage framework.

PREREQUISITES

Before you read this you should have at least a passing familiarity with how OpenInteract2::Manage works. You can see its use in oi2_manage, but since that script uses tasks in a generic fashion it may be a little tougher to follow.

MOTIVATION

Previous environment

In OI 1.x most of the logic to run the management tasks was in the oi_manage script which had multiple problems:

Turnabout is fair play

The architecture of the management framework in OI2 is exactly the opposite of this. The command-line tool (oi2_manage) is just a shell taking advantage of individual tasks with a facade interface to them all.

Every task is a OpenInteract2::Manage subclass and is responsible for:

SUBCLASSING

Now we'll discuss how to create a subclass. When we task about method names below we're always talking about OpenInteract2::Manage unless otherwise indicated.

Mandatory methods

Management tasks must implement:

run_task()

This is where you actually perform the work of your task. You can indicate the status of your task with status hashrefs passed to _add_status() or _add_status_head(). (See STATUS MESSAGES in OpenInteract2::Manage.)

Errors are indicated by throwing an exception -- generally an OpenInteract2::Exception object, but if you want to create your own there is nothing stopping you.

The task manager will set the parameter task_failed to 'yes' if it catches an error from run_task. This allows you to do conditional cleanup in tear_down_task(), discussed below. (For example, if the main task died you probably don't need that directory tree you just created...)

Note that the caller ensures that the current working directory remains the same for the caller, so you can chdir to your heart's content.

get_parameters()

Well, technically you don't have to implement this yourself, you could inherit it. But you really, really should do it yourself, if only to help to poor sap who has to maintain your task.

This method should return a hashref with the keys as parameter names and hashrefs of parameter metadata as the values. The parameter metadata may consist of the following. (Note: 'bool' means that 'yes' equals true while everything else is false.)

This management task provides _get_source_dir_param() which can be used as necessary:

  sub get_parameters {
      my ( $self ) = @_;
      return {
          source_dir => $self->_get_source_dir_param(),
          ...
      };
  }

It specifies that the parameter is required and gives a generic description, plus a default value of the current directory.

Optional methods

init( @extra )

This is called within the new() method. All extra parameters sent to new() are passed to this method, since the main parameters have already been set in the object.

get_name()

Return the task name with which your class is associated. This is how people lookup your task: they don't use the class name, they use the normal name for us humans.. For instance, OpenInteract2::Manage::Website::InstallPackage is associated with 'install_package'.

get_brief_description()

Return a string a sentence or two long describing what the task does.

setup_task()

Sets up the environment required for this task. This might require creating an OpenInteract2::Context, a database connection, or some other action. (Some of these have shortcuts -- see below.)

Note that there may be an abstract subclass of OpenInteract2::Manage that implements common functionality for you here. For instance, OpenInteract2::Manage::Website automatically creates a context here so you don't have to.

If you cannot setup your required environment you should throw an exception with an appropriate message.

tear_down_task()

If your task needs to do any cleanup actions -- closing a database connection, etc. -- it should perform them here.

The task manager will set the parameter task_failed to 'yes' if the main task threw an error. This allows you to do conditional cleanup -- for instance, OpenInteract2::Manage::Website::Create checks this field and if it is set will remove the directories created and all the files copied in the halted process of creating a new website.

validate_param( $param_name, $param_value )

Implement if you'd like to validate one or more paramter values. Note that you should call SUPER as the last command, just in case it has its own validation routines. (More below.)

Parameter Validation

Here's an example where we depend on the validation routine for website_dir from OpenInteract2::Manage:

   2: sub get_parameters {
   3:     my ( $self ) = @_;
   4:     return {
   5:         website_dir => {
   6:             description => 'a directory',
   7:             is_required => 'yes',
   8:         },
   9:     };
  10: }
  11: 
  12: # we're not validating anything ourselves -- no 'validate_param'
  13: # subroutine defined

Easy enough. Now, say we want to validate a different parameter ourselves:

   2: sub get_parameters {
   3:     my ( $self ) = @_;
   4:     return {
   5:         game_choice => {
   6:             description => 'Your choice in the game',
   7:             is_required => 'yes',
   8:         },
   9:         ...
  10:     };
  11: }
  12:   
  13: sub validate_param {
  14:     my ( $self, $param_name, $param_value ) = @_;
  15:     if ( $param_name eq 'game_choice' ) {
  16:         unless ( $param_value =~ /^(rock|scissors|paper)$/i ) {
  17:             return "Value must be 'rock', 'scissors' or 'paper'";
  18:         }
  19:     }
  20:     return $self->SUPER::validate_param( $param_name, $param_value );
  21: }

This ensures that the parameter value for 'game_choice' (a) exists and (b) is either 'rock', 'scissors' or 'paper' (case-insensitive). Your run_task() method will never be run unless all the parameter requirements and validation checks are successful.

Status helper methods

These methods should only be used by management tasks themselves, not by the users of those tasks.

Note: All status messages are sent to the observers as a 'status' observation. These are sent in the order received, so the user may be a little confused if you use _add_status_head().

_add_status( ( \%status, \%status, ...) )

Adds status message \%status to those tracked by the object.

_add_status_head( ( \%status, \%status, ... ) )

Adds status messages to the head of the list of status messages. This is useful for when your management task comprises several others. You can collect their status messages as your own, then insert an overall status as the initial one seen by the user.

Notifying Observers

All management tasks are observable. This means anyone can add any number of classes, objects or subroutines that receive observations you post. Notifying observers is simple:

  $self->notify_observers( $type, @extra_info )
  

What goes into @extra_info depends on the $type. The two types of observations supported right now are 'status' and 'progress'. The 'status' observations are generated automatically when you use _add_status() or _add_status_head() (see above).

Generally 'progress' notifications are accompanied by a simple text message. You may also pass as a third argument a hashref. This hashref gives us room to grow and the observers the ability to differentiate among progress messages. For now, the hashref only supports one key: long. If you're posting a progress notification of a process that will take a long time, set this to 'yes' so the observer can differentiate -- let the user know it will take a while, etc.

  sub run_task {
      my ( $self ) = @_;
      $self->_do_some_simple( 'thing' );
      $self->notify_observers( progress => 'Simple thing complete' );
      $self->_do_some_other( @stuff );
      $self->notify_observers( progress => 'Other stuff complete' );
      $self->notify_observers( progress => 'Preparing complex task',
                               { long => 'yes' } );
      $self->_do_complex_task;
      $self->notify_observers( progress => 'Complex task complete' );
  
      # This fires an implicit observation of type 'status'
      $self->_add_status( { is_ok   => 'yes',
                            message => 'Foobar task ok' } );
  }
  

This is a contrived example -- if your task is very simple (like this) you probably don't need to bother with observations. The notifications generated by the status messages will be more than adequate.

However, if you're looping through a set of packages, or performing a complicated set of operations, it can be very helpful for your users to let them know things are actually happening.

Example

Here is an example of a direct subclass that just creates a file 'hello_world' in the website directory:

   1: package Openinteract2::Manage::MyTask
   2: 
   3: use strict;
   4: use base qw( OpenInteract2::Manage::Website );
   5: 
   6: sub get_name {
   7:     return 'hello_world';
   8: }
   9: 
  10: sub get_brief_description {
  11:     return "Creates a 'hello_world' file in your website directory.";
  12: }
  13: 
  14: sub get_parameters {
  15:     my ( $self ) = @_;
  16:     return { website_dir => $self->_get_website_dir_param,
  17:              hello_message => {
  18:                  description => 'Message to write to file',
  19:                  is_required => 'yes',
  20:              },
  21:     };
  22: }
  23: 
  24: sub run_task {
  25:     my ( $self ) = @_;
  26:     my $website_dir = $self->param( 'website_dir' );
  27:     $website_dir =~ s|/$||;
  28:     my $filename = File::Spec->catfile( $website_dir, 'hello_world' );
  29:     my %status = ();
  30:     if ( -f $filename ) {
  31:         $status{message} = "Could not create [$filename]: already exists";
  32:         $status{is_ok}   = 'no';
  33:         $self->_add_status( \%status );
  34:         return;
  35:     }
  36:     eval { open( HW, '>', $filename ) || die $! };
  37:     if ( $@ ) {
  38:         $status{message} = "Cannot write to [$filename]: $@";
  39:         $status{is_ok}   = 'no';
  40:     }
  41:     else {
  42:         print HW $self->param( 'hello_message' );
  43:         close( HW );
  44:         $status{is_ok}   = 'yes';
  45:         $status{message} = "File [$filename] created ok";
  46:     }
  47:     $self->_add_status( \%status );
  48: }
  49: 
  50: OpenInteract2::Manage->register_factory_type( get_name() => __PACKAGE__ );
  51: 
  52: 1;

And here is how you would run your task:

  #!/usr/bin/perl
  
  use strict;
  use OpenInteract2::Manage;
  
  my $task = OpenInteract2::Manage->new( 'hello_world',
                                        { website_dir => $ENV{OPENINTERACT2} } );
  my @status = eval { $task->execute };
  if ( $@ ) {
      print "Task failed to run: $@";
  }
  else {
      foreach my $s ( @status ) {
          print "Task OK? $s->{is_ok}\n",
                "$s->{message}\n";
      }
  }

Since all management tasks are auto-discovered by OpenInteract2::Manage at startup, you can also run:

  $ oi2_manage hello_world

And it'll work!

Other subclass helper methods

_setup_context( @params )

Sets up a context given the website directory named in the parameter website_dir. If the 'debug' parameter is true it sets the level of the root log4perl logger to be 'debug'.

COPYRIGHT

Copyright (c) 2003 Chris Winters. All rights reserved.

AUTHORS

Chris Winters <chris@cwinters.com>

Generated from the OpenInteract 1.99_03 source.


Home | Wiki | OI 1.x Docs | OI 2.x Docs
SourceForge Logo