File Coverage

blib/lib/OpenInteract2/Manage.pm
Criterion Covered Total %
statement 6 6 100.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 8 8 100.0


line stmt bran cond sub pod time code
1             package OpenInteract2::Manage;
2              
3             # $Id: Manage.pm,v 1.49 2005/03/17 14:57:58 sjn Exp $
4              
5 1     1   16315 use strict;
  1         2  
  1         213  
6 1     1   7 use base qw( Exporter OpenInteract2::ParamContainer Class::Factory Class::Observable );
  1         1  
  1         1196  
7             use Cwd qw( cwd );
8             use File::Spec::Functions qw( :ALL );
9             use Log::Log4perl qw( get_logger :levels );
10             use OpenInteract2::Constants qw( :log );
11             use OpenInteract2::Context qw( CTX );
12             use OpenInteract2::Exception qw( oi_error oi_param_error );
13             use OpenInteract2::Setup;
14              
15             $OpenInteract2::Manage::VERSION = sprintf("%d.%02d", q$Revision: 1.49 $ =~ /(\d+)\.(\d+)/);
16              
17             my $SYSTEM_PACKAGES = [
18             qw/ base base_box base_error base_group
19             base_page base_security base_template base_theme
20             base_user comments full_text news
21             lookup object_activity system_doc whats_new /
22             ];
23              
24             sub SYSTEM_PACKAGES { return $SYSTEM_PACKAGES }
25              
26             my %PACKAGE_GROUPS = (
27             SYSTEM => $SYSTEM_PACKAGES,
28             );
29              
30             use constant DEV_LIST => 'openinteract-dev@lists.sourceforge.net';
31             use constant HELP_LIST => 'openinteract-help@lists.sourceforge.net';
32              
33             @OpenInteract2::Manage::EXPORT_OK = qw(
34             SYSTEM_PACKAGES DEV_LIST HELP_LIST
35             );
36              
37             ########################################
38             # MAIN EXTERNAL INTERFACE
39              
40             sub new {
41             my ( $pkg, $task_name, $params, @extra ) = @_;
42             my $class = $pkg->get_factory_class( $task_name );
43             my $self = bless( { _status => [] }, $class );
44              
45             if ( ref $params eq 'HASH' ) {
46             while ( my ( $name, $value ) = each %{ $params } ) {
47             $self->param( $name => $value );
48             }
49             }
50              
51             # Check for defaults
52              
53             my $param_metadata = $self->get_parameters;
54             foreach my $name ( keys %{ $param_metadata } ) {
55             next if ( $self->param( $name ) );
56             next unless ( $param_metadata->{ $name }{default} );
57             $self->param( $name => $param_metadata->{ $name }{default} );
58             }
59              
60             $self->init( @extra );
61             return $self;
62             }
63              
64             sub execute {
65             my ( $self ) = @_;
66             $self->check_parameters;
67             $self->setup_task;
68              
69             # Track our current directory so the task can feel free to do what
70             # it wants
71              
72             my $pwd = rel2abs( curdir );
73              
74             $self->notify_observers( progress => 'Starting task' );
75              
76             eval { $self->run_task };
77             my $error = $@;
78             if ( $error ) {
79             $self->notify_observers( progress => 'Failed task' );
80             $self->param( task_failed => 'yes' );
81             $self->param( task_error => "$error" );
82             }
83             $self->tear_down_task;
84             chdir( $pwd );
85             if ( $error ) {
86             oi_error $error;
87             }
88             $self->notify_observers( progress => 'Task complete' );
89             return $self->get_status;
90             }
91              
92              
93             ########################################
94             # STANDARD PARAMETER DESCRIPTIONS AND DESCRIPTORS
95              
96             sub get_param_description {
97             my ( $self, $param_name ) = @_;
98             if ( $param_name eq 'source_dir' ) {
99             return "OpenInteract2 source directory, or at least a directory with " .
100             "the 'pkg/' and 'sample/' directories from the distribution.";
101             }
102             elsif ( $param_name eq 'website_dir' ) {
103             return "Functional OpenInteract2 website directory";
104             }
105             elsif ( $param_name eq 'package_list_file' ) {
106             return "Filename with packages to process, one per line";
107             }
108             elsif ( $param_name eq 'package' ) {
109             return "One or more packages to process";
110             }
111             return 'no description available';
112             }
113              
114             sub _get_source_dir_param {
115             my ( $self ) = @_;
116             return {
117             description => $self->get_param_description( 'source_dir' ),
118             is_required => 'yes',
119             default => cwd(),
120             }
121             }
122              
123             ########################################
124             # PARAMETER CHECKING
125              
126             # Wrapper for all check methods
127              
128             sub check_parameters {
129             my ( $self ) = @_;
130             $self->_init_setup_packages;
131             $self->param_initialize();
132             my $params = $self->get_parameters();
133              
134             # First check parameters that are required
135              
136             my @field_notfound = ();
137             while ( my ( $name, $info ) = each %{ $params } ) {
138             next unless ( $info->{is_required} eq 'yes' );
139             unless ( $self->param( $name ) ) {
140             push @field_notfound, $name;
141             }
142             }
143             if ( scalar @field_notfound ) {
144             my %err = map { $_ => 'Required parameter not defined' }
145             @field_notfound;
146             oi_param_error "A value for one or more required parameters ",
147             "was not found.",
148             { parameter_fail => \%err };
149             }
150              
151             # Now do validatable parameters
152              
153             my %field_invalid = ();
154             while ( my ( $name, $info ) = each %{ $params } ) {
155             my $do_validate = ( ( defined $info->{is_required} and $info->{is_required} eq 'yes' ) ||
156             ( defined $info->{do_validate} and $info->{do_validate} eq 'yes' ) );
157             next unless ( $do_validate );
158             my $value = $self->param( $name );
159             my @errors = grep { defined $_ } $self->validate_param( $name, $value );
160             if ( scalar @errors ) {
161             $field_invalid{ $name } = \@errors
162             }
163             }
164              
165             if ( scalar keys %field_invalid ) {
166             oi_param_error "One or more parameters failed a validity check",
167             { parameter_fail => \%field_invalid };
168             }
169             }
170              
171              
172             # Validate the given parameter -- these are built-in for everyone to
173             # use
174              
175             sub validate_param {
176             my ( $self, $param_name, $value ) = @_;
177             if ( $param_name eq 'source_dir' ) {
178             return $self->_check_source_dir( $value );
179             }
180             elsif ( $param_name eq 'website_dir' ) {
181             unless ( -d $value ) {
182             return "Value for 'website_dir' ($value) must be a " .
183             "valid directory";
184             }
185             }
186             elsif ( $param_name eq 'package_file' ) {
187             unless ( -f $value ) {
188             return "Value for 'package_file' ($value) must specify " .
189             "a valid file";
190             }
191             }
192             return undef;
193             }
194              
195             sub _check_source_dir {
196             my ( $self, $source_dir ) = @_;
197             unless ( -d $source_dir ) {
198             return "Value for 'source_dir' ($source_dir) is not a valid directory";
199             }
200             foreach my $distrib_dir ( qw( pkg sample ) ) {
201             my $full_distrib_dir = catdir( $source_dir, $distrib_dir );
202             unless ( -d $full_distrib_dir ) {
203             return "The 'source_dir' must contain valid subdirectory " .
204             "[$distrib_dir]";
205             }
206             }
207             return;
208             }
209              
210              
211             ########################################
212             # PARAMETER INITIALIZATION
213              
214             # If package exist, reads in the SYSTEM value, the
215             # package_list_file, etc.
216              
217             sub _init_setup_packages {
218             my ( $self ) = @_;
219             my $initial_packages = $self->param( 'package' );
220             return unless ( $initial_packages );
221             if ( ref $initial_packages ne 'ARRAY' ) {
222             $self->param( package => [ $initial_packages ] );
223             }
224             $self->_init_setup_comma_packages;
225             $self->_init_setup_package_groups;
226             $self->_init_read_packages_from_file;
227              
228             # Remove dupes
229              
230             my $packages = $self->param( 'package' );
231             if ( ref $packages eq 'ARRAY' ) {
232             my %names = map { $_ => 1 } @{ $packages };
233             $self->param( package => [ sort keys %names ] );
234             }
235             }
236              
237             # allows --package=x,y --package=z to be combined; assumes 'package'
238             # param is already an arrayref
239              
240             sub _init_setup_comma_packages {
241             my ( $self ) = @_;
242             my $packages = $self->param( 'package' );
243             $self->param( package => [ split( /\s*,\s*/, join( ',', @{ $packages } ) ) ] );
244             }
245              
246              
247             # Allow a special keyword for users to specify all the initial (base)
248             # packages. This allows something like:
249             #
250             # oi_manage --package=SYSTEM ...
251             # oi_manage --package=SYSTEM,mypkg,theirpkg ...
252             #
253             # and the keyword 'SYSTEM' will be replaced by all the system
254             # packages, which can be found by doing 'oi2_manage system_packages';
255             # assumes 'package' param is already an arrayref
256              
257             sub _init_setup_package_groups {
258             my ( $self ) = @_;
259             my $packages = $self->param( 'package' );
260             return unless ( ref $packages eq 'ARRAY' );
261             my %pkg_names = map { $_ => 1 } @{ $packages };
262             foreach my $group_key ( keys %PACKAGE_GROUPS ) {
263             if ( exists $pkg_names{ $group_key } ) {
264             $pkg_names{ $_ }++ for ( @{ $PACKAGE_GROUPS{ $group_key } } );
265             delete $pkg_names{ $group_key };
266             }
267             }
268             $self->param( package => [ sort keys %pkg_names ] )
269             }
270              
271             # assumes 'package' param is already an arrayref
272              
273             sub _init_read_packages_from_file {
274             my ( $self ) = @_;
275             my $filename = $self->param( 'package_list_file' );
276             return unless ( $filename );
277             unless ( -f $filename ) {
278             oi_error "Failure reading package list file [$filename]: ",
279             "file does not exist";
280             }
281             eval { open( PKG, '<', $filename ) || die $! };
282             if ( $@ ) {
283             oi_error "Failure reading package list file [$filename]: $@";
284             }
285             my @read_packages = ();
286             while ( ) {
287             chomp;
288             next if /^\s*\#/;
289             next if /^\s*$/;
290             s/^\s+//;
291             s/\s+$//;
292             push @read_packages, $_;
293             }
294             close( PKG );
295              
296             # They can also specify --package, so add those too -- don't worry
297             # about dupes, they get weeded out later
298              
299             $self->param( package => [ @read_packages,
300             @{ $self->param( 'package' ) } ] );
301             }
302              
303              
304             ########################################
305             # TASK LIST/CHECK
306              
307             sub is_valid_task {
308             my ( $class, $task_name ) = @_;
309             my %tasks = map { $_ => 1 } $class->valid_tasks;
310             return ( defined $tasks{ $task_name } );
311             }
312              
313             sub valid_tasks {
314             return __PACKAGE__->get_registered_types;
315             }
316              
317             sub valid_tasks_description {
318             my ( $self ) = @_;
319             my %tasks = map { $_ => 1 } $self->valid_tasks;
320             foreach my $task ( keys %tasks ) {
321             my $task_class = $self->get_factory_class( $task );
322             my $desc = $task_class->get_brief_description;
323             $tasks{ $task } = $desc;
324             }
325             return \%tasks;
326             }
327              
328             # Retrieves the parameter hashref for a particular task -- this can be
329             # a class method (needs $task_name filled in) or an object method
330              
331             sub task_parameters {
332             my ( $item, $task_name ) = @_;
333             my ( $manage );
334             if ( ref $item ) {
335             $manage = $item;
336             }
337             else {
338             unless ( $task_name ) {
339             oi_error "If you call 'task_parameters' as a class method ",
340             "you must pass the task name as the only argument.";
341             }
342             $manage = $item->new( $task_name );
343             }
344             my $params = $manage->get_parameters;
345             my %basic_params = ();
346             while ( my ( $name, $info ) = each %{ $params } ) {
347             $info->{name} = $name;
348             $basic_params{ $name } = $info;
349             }
350             return \%basic_params;
351             }
352              
353             # Retrieves all parameter names plus whether they're
354             # boolean/multivalued
355              
356             sub all_parameters {
357             my ( $class ) = @_;
358             my %all = ();
359             foreach my $task_name ( $class->valid_tasks ) {
360             my $param_data = $class->task_parameters( $task_name );
361             next unless ( ref $param_data eq 'HASH' );
362             while ( my ( $name, $info ) = each %{ $param_data } ) {
363             next if ( $all{ $name } );
364             $all{ $name } = $info;
365             }
366             }
367             return \%all;
368             }
369              
370              
371              
372             sub all_parameters_long_options {
373             my ( $class ) = @_;
374             my $all_params = $class->all_parameters;
375             my @opt = ();
376             while ( my ( $name, $info ) = each %{ $all_params } ) {
377             if ( defined $info->{is_boolean} and
378             $info->{is_boolean} eq 'yes' ) {
379             push @opt, $name;
380             }
381             elsif ( defined $info->{is_multivalued} and
382             $info->{is_multivalued} eq 'yes' ) {
383             push @opt, "$name=s@";
384             }
385             else {
386             push @opt, "$name=s";
387             }
388             }
389             return @opt;
390             }
391              
392             ########################################
393             # PARAMETERS
394              
395             sub param_copy_from {
396             my ( $self, $other_task ) = @_;
397             $self->param_assign( $other_task->param );
398             return $self->param;
399             }
400              
401              
402             ########################################
403             # STATUS
404              
405             sub _add_status {
406             my ( $self, @status ) = @_;
407             push @{ $self->{_status} }, @status;
408             foreach my $hr ( @status ) {
409             $self->notify_observers( status => $hr );
410             }
411             return $self->{_status};
412             }
413              
414             sub _add_status_head {
415             my ( $self, @status ) = @_;
416             unshift @{ $self->{_status} }, @status;
417             foreach my $hr ( @status ) {
418             $self->notify_observers( status => $hr );
419             }
420             return $self->{_status};
421             }
422              
423             sub get_status {
424             my ( $self ) = @_;
425             return @{ $self->{_status} };
426             }
427              
428             sub merge_status_by_action {
429             my ( $item, @status ) = @_;
430             if ( scalar @status == 0
431             and UNIVERSAL::isa( $item, 'OpenInteract2::Manage' ) ) {
432             @status = $item->get_status;
433             }
434             my $current_action = '';
435             my @tmp_status = ();
436             my @new_status = ();
437             foreach my $s ( @status ) {
438             unless ( $current_action ) {
439             $current_action = $s->{action};
440             }
441             if ( defined $s->{action} and $s->{action} ne $current_action ) {
442             push @new_status, { action => $current_action,
443             status => [ @tmp_status ] };
444             @tmp_status = ();
445             $current_action = $s->{action};
446             }
447             push @tmp_status, $s;
448             }
449             if ( scalar @tmp_status > 0 ) {
450             push @new_status, { action => $current_action,
451             status => \@tmp_status };
452             }
453             return @new_status;
454             }
455              
456             # shortcut for adding bad/good status
457              
458             sub _fail {
459             my ( $self, $action, $msg, %additional ) = @_;
460             $self->_add_status({
461             is_ok => 'no',
462             action => $action,
463             message => $msg,
464             %additional,
465             });
466             return;
467             }
468              
469             sub _ok {
470             my ( $self, $action, $msg, %additional ) = @_;
471             $self->_add_status({
472             is_ok => 'yes',
473             action => $action,
474             message => $msg,
475             %additional,
476             });
477             return;
478             }
479              
480              
481              
482             ########################################
483             # INFRASTRUCTURE (SUBCLASSES)
484              
485             sub _setup_context {
486             my ( $self, $params ) = @_;
487              
488             # don't recreate the context every time
489             eval { OpenInteract2::Context->instance };
490             return unless ( $@ );
491              
492             my $log = get_logger();
493             if ( $self->param( 'debug' ) ) {
494             $log->level( $DEBUG );
495             }
496             my $website_dir = $self->param( 'website_dir' );
497             unless ( -d $website_dir ) {
498             oi_error "Cannot open context with invalid website ",
499             "directory '$website_dir'";
500             }
501             $log->info( "Website directory '$website_dir' exists, setting up context..." );
502             my $bootstrap = OpenInteract2::Config::Bootstrap->new({
503             website_dir => $website_dir
504             });
505             $log->info( "Created bootstrap config ok, creating context..." );
506             OpenInteract2::Context->create( $bootstrap, $params );
507             $log->info( "Context setup for management task(s) ok" );
508             }
509              
510             # Creates status entry with all files removed/skipped/updated
511              
512             sub _set_copy_file_status {
513             my ( $self, $status ) = @_;
514             $status->{copied} ||= [];
515             $status->{skipped} ||= [];
516             $status->{same} ||= [];
517             foreach my $file ( @{ $status->{copied} } ) {
518             $self->_ok(
519             'copy updated template files',
520             "File $file copied",
521             filename => $file
522             );
523             }
524             foreach my $file ( @{ $status->{skipped} } ) {
525             $self->_ok(
526             'copy updated template files',
527             "File $file skipped, marked as read-only",
528             filename => $file
529             );
530             }
531             foreach my $file ( @{ $status->{same} } ) {
532             $self->_ok(
533             'copy updated template files',
534             "File $file skipped, source and destination same",
535             filename => $file
536             );
537             }
538             }
539              
540             ########################################
541             # FACTORY
542              
543             sub factory_log {
544             # no-op so we get around the l4p 'no init' msg
545             # my ( $self, @msg ) = @_;
546             # get_logger()->info( @msg );
547             }
548              
549             sub factory_error {
550             my ( $self, @msg ) = @_;
551             get_logger()->error( @msg );
552             die @msg, "\n";
553             }
554              
555              
556             ##############################
557             # FIND ALL MANAGEMENT TASKS
558              
559             OpenInteract2::Util->find_factory_subclasses(
560             'OpenInteract2::Manage', @INC
561             );
562              
563              
564             ########################################
565             # INTERFACE
566             # All are optional except run_task()
567              
568             # Run at new()
569             sub init {}
570              
571             # Identify the name by which your task is known
572             sub get_name { return undef }
573              
574             # Help out tools using your task and describe what it does
575             sub get_brief_description { return 'No description available' }
576              
577             # Return parameter information
578             sub get_parameters { return {} }
579              
580             # Do the work!
581             sub run_task { die "Define run_task() in subclass" }
582              
583             # Do work before run_task()
584             sub setup_task { return undef }
585              
586             # Do cleanup after run_task()
587             sub tear_down_task { return undef }
588              
589             # Do pre-validation transformations of parameters
590             sub param_initialize { return undef }
591              
592             1;
593              
594             __END__