Home | Wiki | OI 1.x Docs | OI 2.x Docs |
OpenInteract2::Manual::Management - Creating tasks to manage OpenInteract2
This part of the manual describes how create your own tasks using the OpenInteract2::Manage framework.
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.
In OI 1.x most of the logic to run the management tasks was in the
oi_manage
script which had multiple problems:
You could not call it from anywhere but the command line. For most
tasks this was fine, but it prevented any sort of web-based
management without simply cobbling together strings to run using
system
.
Its size also meant that it was difficult to refactor. And it was fairly difficult to add new tasks without doing lots of copy-and-paste and knowing where certain tasks (parameter parsing, etc.) were performed.
Documentation always got out of sync. Sometimes the docs didn't list the task at all, sometimes it had the wrong or too few parameters for the task or it, and at other times the description didn't really say what the task was doing.
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:
Naming itself. The get_name()
method returns the task name
associated with the class -- so
OpenInteract2::Manage::Website::Create
has a name of 'create_website', and this is what you'd lookup the task
by.
Describing itself. A call to get_brief_description()
returns a one-
or two-sentence description of what the task accomplishes. Additional
documentation is in the POD, and you can view that POD separate from
that of all other tasks. (It's a normal class after all!)
Listing its parameters. The get_parameters()
method returns a
hashref with parameter names and metadata that describe how to change
the task's behavior.
Validating its parameters. Any parameter value passed in can be validated. If it is invalid execution is stopped, all transparently to the individual task.
Tracking its own status. As the task runs it can generate one or
more status messages. These are in a standard format (hashref with
specified keys) and the task caller has a single way to retrieve them
(call to get_status()
).
Cleaning up after itself. Tasks should ensure they don't leave any stray database connections open or files scattered around the filesystem.
And finally, running the task!
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.
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.)
What this parameter is used for. Strongly advised.
Indicates this is a required parameter. Note that all required parameters are automatically submitted for validation. (That doesn't mean you have to validate them, just that they're available for it.)
Tells the managment dispatcher to validate this parameter. You don't need this if 'is_required' is set.
Indicates this parameter is boolean, or a toggled value.
Indicates this parameter may hold multiple values.
A default value to use for the parameter, used if the task caller doesn't provide one.
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.
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.)
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.
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.
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.
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!
_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 (c) 2003 Chris Winters. All rights reserved.
Chris Winters <chris@cwinters.com>
Generated from the OpenInteract 1.99_03 source.