File Coverage

blib/lib/OpenInteract/Startup.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package OpenInteract::Startup;
2              
3             # $Id: Startup.pm,v 1.37 2003/03/13 03:26:34 lachoy Exp $
4              
5 1     1   877 use strict;
  1         2  
  1         46  
6 1     1   5 use Cwd qw( cwd );
  1         2  
  1         83  
7 1     1   1093 use Data::Dumper qw( Dumper );
  1         7078  
  1         87  
8 1     1   11 use File::Basename qw( dirname );
  1         2  
  1         73  
9 1     1   6 use File::Path qw();
  1         2  
  1         18  
10 1     1   1304 use Getopt::Long qw( GetOptions );
  1         13756  
  1         9  
11 1     1   762 use OpenInteract::Config;
  1         2  
  1         44  
12 1     1   515 use OpenInteract::Config::GlobalOverride;
  1         4  
  1         35  
13 1     1   8 use OpenInteract::Error;
  1         21  
  1         21  
14 1     1   884 use OpenInteract::Package;
  0            
  0            
15             use OpenInteract::PackageRepository;
16             use SPOPS::ClassFactory;
17              
18             $OpenInteract::Startup::VERSION = sprintf("%d.%02d", q$Revision: 1.37 $ =~ /(\d+)\.(\d+)/);
19              
20             use constant DEBUG => 0;
21              
22             my $TEMP_LIB_DIR = 'tmplib';
23             my $REPOS_CLASS = 'OpenInteract::PackageRepository';
24             my $PKG_CLASS = 'OpenInteract::Package';
25              
26             sub main_initialize {
27             my ( $class, $p ) = @_;
28              
29             # Ensure we can find the base configuration, and use it or read it in
30              
31             return undef unless ( $p->{base_config} or $p->{base_config_file} );
32             my $bc = $p->{base_config} ||
33             $class->read_base_config({ filename => $p->{base_config_file} });
34              
35             # Create our main config object
36              
37             my $C = $class->create_config({ base_config => $bc });
38              
39             # Initialize the package repository class -- it's a SPOPS class,
40             # but a really simple one
41              
42             $REPOS_CLASS->class_initialize( $C );
43              
44             # Read in our fundamental modules -- these should be in our @INC
45             # already, since the 'request_class' is in
46             # 'OpenInteract/OpenInteract' and the 'stash_class' is in
47             # 'MyApp/MyApp'
48              
49             $class->require_module({ class => [ $bc->{request_class}, $bc->{stash_class} ] });
50              
51             # Either use a package list provided or read in all the packages from
52             # the website package database
53              
54             my $packages = [];
55             my $repository = $REPOS_CLASS->fetch( undef, { directory => $bc->{website_dir} } );
56             if ( my $package_list = $p->{package_list} ) {
57             foreach my $pkg_name ( @{ $p->{package_list} } ) {
58             my $pkg_info = $repository->fetch_pacakge_by_name({ name => $pkg_name });
59             push @{ $packages }, $pkg_info if ( $pkg_info );
60             }
61             }
62             else {
63             $packages = $repository->fetch_all_packages();
64             }
65              
66             # We keep track of the package names currently installed and use them
67             # elsewhere in the system
68              
69             $C->{package_list} = [ map { $_->{name} } @{ $packages } ];
70             foreach my $pkg_info ( @{ $packages } ) {
71             $class->process_package( $pkg_info, $C );
72             }
73              
74             $class->_process_global_overrides( $C );
75             $class->_require_extra_classes( $C );
76              
77             # Store the configuration for later use
78              
79             my $stash_class = $bc->{stash_class};
80             $stash_class->set_stash( 'config', $C );
81              
82             # Create an instance of $R since later steps might need it --
83             # particularly SPOPS initialization which may want a connection to
84             # the datasource during setup. (Crossing fingers this doesn't mess
85             # something up, particularly w/ parent/child sharing issues...)
86              
87             my $request_class = $bc->{request_class};
88             my $R = $request_class->instance;
89             $R->{stash_class} = $stash_class;
90              
91             # The config object should now have all actions and SPOPS definitions
92             # read in, so run any necessary configuration options
93              
94             my $init_class = $class->finalize_configuration({ config => $C });
95              
96             # Tell OpenInteract::Request to setup aliases if they haven't already
97              
98             if ( $p->{alias_init} ) {
99             $request_class->setup_aliases;
100             }
101              
102             # Initialize all the SPOPS object classes
103              
104             if ( $p->{spops_init} ) {
105             $class->initialize_spops({ config => $C, class => $init_class });
106             }
107              
108             # Read in all the classes for all configured conductors
109              
110             my @conductor_classes = ();
111             foreach my $conductor ( keys %{ $C->{conductor} } ) {
112             push @conductor_classes, $C->{conductor}{ $conductor }{class};
113             }
114             $class->require_module({ class => \@conductor_classes });
115              
116             # Read in the modules referred to in the 'system_alias' key from
117             # the configuration -- EXCEPT for anything beginning with the
118             # website name since that's an SPOPS object and has already been
119             # created
120              
121             my @system_alias_classes = grep ! /^$bc->{website_name}/, values %{ $C->{system_alias} };
122             $class->require_module({ class => \@system_alias_classes });
123              
124             DEBUG && _w( 2, "Contents of INC: @INC" );
125              
126             # All done! Return the configuration object so the user can
127             # do whatever else is necessary
128              
129             return ( $init_class, $C );
130             }
131              
132              
133             sub setup_static_environment_options {
134             my ( $class, $usage, $options, $params ) = @_;
135             $options ||= {};
136             my ( $OPT_website_dir );
137             $options->{'website_dir=s'} = \$OPT_website_dir;
138              
139             # Get the options
140              
141             GetOptions( %{ $options } );
142              
143             if ( ! $OPT_website_dir and $ENV{OIWEBSITE} ) {
144             warn "Using ($ENV{OIWEBSITE}) for 'website_dir'.\n";
145             $OPT_website_dir = $ENV{OIWEBSITE};
146             }
147              
148             unless ( -d $OPT_website_dir ) {
149             die "$usage\n Parameter 'website_dir' must refer to an OpenInteract website directory!\n";
150             }
151             return $class->setup_static_environment( $OPT_website_dir, undef, $params );
152             }
153              
154              
155             # Use this if you want to setup the OpenInteract environment outside
156             # of the web application server -- just pass in the website directory!
157              
158             sub setup_static_environment {
159             my ( $class, $website_dir, $su_passwd, $params ) = @_;
160             die "Directory ($website_dir) is not a valid directory!\n" unless ( -d $website_dir );
161             $params ||= {};
162              
163             my $bc = $class->read_base_config({ dir => $website_dir });
164             unless ( $bc and ref $bc eq 'HASH' ) {
165             die "No base configuration file found in website directory ($website_dir)" ;
166             }
167              
168             $class->create_temp_lib( $bc, $params->{temp_lib} );
169              
170             unshift @INC, $website_dir;
171              
172             my ( $init, $C ) = $class->main_initialize({ base_config => $bc,
173             alias_init => 1,
174             spops_init => 1 });
175             my $REQUEST_CLASS = $C->{server_info}{request_class};
176             my $R = $REQUEST_CLASS->instance;
177              
178             $R->{stash_class} = $C->{server_info}{stash_class};
179             $R->stash( 'config', $C );
180              
181             # If we were given the superuser password, retrieve the user and
182             # check the password
183              
184             if ( $su_passwd ) {
185             my $user = $R->user->fetch( 1, { skip_security => 1 });
186             die "Cannot create superuser!" unless ( $user );
187             unless ( $user->check_password( $su_passwd ) ) {
188             die "Password for superuser does not match!\n";
189             }
190             $R->{auth}{user} = $user;
191             }
192              
193             return $R;
194             }
195              
196              
197              
198             # Slimmed down initialization procedure -- just do everything
199             # necessary to read the config and set various values there
200              
201             sub create_config {
202             my ( $class, $p ) = @_;
203             my $bc = $p->{base_config} ||
204             $class->read_base_config({ filename => $p->{base_config_file},
205             website_dir => $p->{website_dir} });
206             return undef unless ( $bc );
207              
208             # Create the configuration file and set the base directory as configured;
209             # also set other important classes from the config
210              
211             my $config_file = join( '/', $bc->{website_dir},
212             $bc->{config_dir}, $bc->{config_file} );
213             my $C = eval { OpenInteract::Config->instance( $bc->{config_type}, $config_file ) };
214             if ( $@ ) {
215             die "Cannot read configuration file! Error: $@\n";
216             }
217              
218             # This information will be set for the life of the config object,
219             # which should be as long as the apache child is alive if we're using
220             # mod_perl, and will be set in the returned config object in any case
221              
222             $C->{dir}{base} = $bc->{website_dir};
223             $C->{dir}{interact} = $bc->{base_dir};
224             $C->{server_info}{request_class} = $bc->{request_class};
225             $C->{server_info}{stash_class} = $bc->{stash_class};
226             $C->{server_info}{website_name} = $bc->{website_name};
227             return $C;
228             }
229              
230              
231             # Method to copy all .pm files from all packages in a website to a
232             # separate directory -- if it currently exists we clear it out first.
233              
234             sub create_temp_lib {
235             my ( $class, $base_config, $opt ) = @_;
236             $opt ||= '';
237             my $site_dir = $base_config->{website_dir};
238              
239             my $lib_dir = $base_config->{templib_dir}
240             || "$site_dir/$TEMP_LIB_DIR";
241             unshift @INC, $lib_dir;
242              
243             if ( -d $lib_dir and $opt eq 'lazy' ) {
244             DEBUG && _w( 1, "Temp lib dir [$lib_dir] already exists and we're lazy;",
245             "not copying modules to temp lib dir" );
246             return [];
247             }
248              
249             File::Path::rmtree( $lib_dir ) if ( -d $lib_dir );
250             mkdir( $lib_dir, 0777 );
251              
252             my $site_repos = $REPOS_CLASS->fetch( undef,
253             { directory => $base_config->{website_dir} } );
254             my $packages = $site_repos->fetch_all_packages();
255             my ( @all_files );
256             foreach my $package ( @{ $packages } ) {
257             DEBUG && _w( 2, "Trying to copy files for package $package->{name}" );
258             my $files_copied = $PKG_CLASS->copy_modules( $package, $lib_dir );
259             push @all_files, @{ $files_copied };
260             }
261             DEBUG && _w( 3, "Copied ", scalar @all_files, " module files to [$lib_dir]" );
262              
263             # Now change permissions so all the files and directories are
264             # world-everything, letting the process's umask kick in
265              
266             chmod( 0666, @all_files );
267              
268             my %tmp_dirs = map { $_ => 1 } map { dirname( $_ ) } @all_files;
269             chmod( 0777, keys %tmp_dirs );
270              
271             return \@all_files;
272             }
273              
274              
275             sub read_package_list {
276             my ( $class, $p ) = @_;
277             return [] unless ( $p->{filename} or $p->{config} );
278             my $filename = $p->{filename} ||
279             join( '/', $p->{config}->get_dir( 'config' ), $p->{config}{package_list} );
280             open( PKG, $filename ) || die "Cannot open package list ($filename): $!";
281             my @packages = ();
282             while ( ) {
283             chomp;
284             next if /^\s*\#/;
285             next if /^\s*$/;
286             s/^\s*//;
287             s/\s*$//;
288             push @packages, $_;
289             }
290             close( PKG );
291             return \@packages;
292             }
293              
294              
295              
296             # simple key-value config file
297              
298             sub read_base_config {
299             my ( $class, $p ) = @_;
300             unless ( $p->{filename} ) {
301             my $dir = $p->{dir} || $p->{website_dir};
302             if ( $dir ) {
303             $p->{filename} = $class->create_base_config_filename( $dir );
304             }
305             }
306             return undef unless ( -f $p->{filename} );
307             open( CONF, $p->{filename} ) || die "$!\n";
308             my $vars = {};
309             while ( ) {
310             chomp;
311             DEBUG && _w( 1, "Config line read: $_" );
312             next if ( /^\s*\#/ );
313             next if ( /^\s*$/ );
314             s/^\s*//;
315             s/\s*$//;
316             my ( $var, $value ) = split /\s+/, $_, 2;
317             $vars->{ $var } = $value;
318             }
319             return $vars;
320             }
321              
322             sub create_base_config_filename {
323             my ( $class, $dir ) = @_;
324             return join( '/', $dir, 'conf', 'base.conf' );
325             }
326              
327             # Params:
328             # filename - file with modules to read, one per line (skip blanks, commented lines)
329             # class - arrayref of classes to require
330             # (pick one)
331              
332             sub require_module {
333             my ( $class, $p ) = @_;
334             my @success = ();
335             if ( $p->{filename} ) {
336             DEBUG && _w( 1, "Trying to open file $p->{filename}" );
337             return [] unless ( -f $p->{filename} );
338             open( MOD, $p->{filename} ) || die "Cannot open $p->{filename}: $!";
339             while ( ) {
340             next if ( /^\s*$/ );
341             next if ( /^\s*\#/ );
342             chomp;
343             DEBUG && _w( 1, "Trying to require $_" );
344             eval "require $_";
345             if ( $@ ) { _w( 0, sprintf( " --require error: %-40s: %s", $_, $@ ) ) }
346             else { push @success, $_ }
347             }
348             close( MOD );
349             }
350             elsif ( $p->{class} ) {
351             $p->{class} = [ $p->{class} ] unless ( ref $p->{class} eq 'ARRAY' );
352             foreach ( @{ $p->{class} } ) {
353             DEBUG && _w( 1, "Trying to require class ($_)" );
354             eval "require $_";
355             if ( $@ ) { _w( 0, sprintf( " --require error: %-40s (from %s): %s", $_, $p->{pkg_link}{$_}, $@ ) ) }
356             else { push @success, $_ }
357             }
358             }
359             return \@success;
360             }
361              
362              
363              
364             # Params:
365             # config = config object
366             # package = name of package
367             # package_dir = arrayref of base package directories (optional, read from config if not passed)
368              
369             sub process_package {
370             my ( $class, $pkg_info, $CONF ) = @_;
371             return undef unless ( $pkg_info );
372             return undef unless ( $CONF );
373              
374             my $pkg_name = join( '-', $pkg_info->{name}, $pkg_info->{version} );
375             DEBUG && _w( 1, "Trying to process package ($pkg_name)" );
376              
377             my $site_pkg_dir = join( '/', $pkg_info->{website_dir}, $pkg_info->{package_dir} );
378             my $base_pkg_dir = join( '/', $pkg_info->{base_dir}, $pkg_info->{package_dir} );
379             DEBUG && _w( 1, "Pkg dirs: ($base_pkg_dir, $site_pkg_dir) for $pkg_name" );
380              
381             # Plow through the directories and find the module listings (to
382             # include), action config (to parse and set) and the SPOPS config (to
383             # parse and set). Base package first so its info can be overridden.
384              
385             foreach my $package_dir ( $base_pkg_dir, $site_pkg_dir ) {
386             my $conf_pkg_dir = "$package_dir/conf";
387              
388             # If the package does not have a 'list_module.dat', that's ok and the
389             # 'require_module' class method will simply return an empty list.
390              
391             $class->require_module({ filename => "$conf_pkg_dir/list_module.dat" });
392              
393             # Read in the 'action' information and set in the config object
394              
395             $class->read_action_definition({ filename => "$conf_pkg_dir/action.perl",
396             config => $CONF,
397             package => $pkg_info });
398              
399             # Read in the SPOPS information and set in the config object; note
400             # that we cannot *process* the SPOPS config yet because we must be
401             # able to relate SPOPS objects, which cannot be done until all the
402             # definitions are read in. (Yes, we could use 'map' here and above,
403             # but it's confusing to people first reading the code)
404              
405             $class->read_spops_definition({ filename => "$conf_pkg_dir/spops.perl",
406             config => $CONF,
407             package => $pkg_info });
408             }
409             }
410              
411              
412              
413             # Read in the action config info and set the information in the CONFIG
414             # object. note that we overwrite whatever information is in the CONFIG
415             # object -- this is a feature, not a bug, since it allows the base
416             # installation to define lots of information and the website to only
417             # override what it needs.
418              
419             # Also save the key under which this was retrieved under 'key'
420              
421             sub read_action_definition {
422             my ( $class, $p ) = @_;
423             DEBUG && _w( 1, "Reading action definitions from ($p->{filename})" );
424              
425             # $CONF is easier to read and more consistent
426              
427             my $CONF = $p->{config};
428             my $action_info = eval { $class->read_perl_file({ filename => $p->{filename} }) };
429             return undef unless ( $action_info );
430             my @class_list = ();
431             foreach my $action_key ( keys %{ $action_info } ) {
432             $CONF->{action}{ $action_key }{key} = $action_key;
433             foreach my $action_conf ( keys %{ $action_info->{ $action_key } } ) {
434             $CONF->{action}{ $action_key }{ $action_conf } =
435             $action_info->{ $action_key }{ $action_conf };
436             }
437             if ( ref $p->{package} ) {
438             $CONF->{action}{ $action_key }{package_name} = $p->{package}{name};
439             $CONF->{action}{ $action_key }{package_version} = $p->{package}{version};
440             }
441             }
442             }
443              
444              
445              
446             # See comments in read_action_definition
447              
448             sub read_spops_definition {
449             my ( $class, $p ) = @_;
450             DEBUG && _w( 1, "Reading SPOPS definitions from ($p->{filename})" );
451              
452             # $CONF is easier to read and more consistent
453             my $CONF = $p->{config};
454             my $spops_info = eval { $class->read_perl_file({ filename => $p->{filename} }) };
455             return undef unless ( $spops_info );
456             my @class_list = ();
457             foreach my $spops_key ( keys %{ $spops_info } ) {
458             $CONF->{SPOPS}{ $spops_key }{key} = $spops_key;
459             foreach my $spops_conf ( keys %{ $spops_info->{ $spops_key } } ) {
460             $CONF->{SPOPS}{ $spops_key }{ $spops_conf } =
461             $spops_info->{ $spops_key }{ $spops_conf };
462             }
463             if ( ref $p->{package} ) {
464             $CONF->{SPOPS}{ $spops_key }{package_name} = $p->{package}{name};
465             $CONF->{SPOPS}{ $spops_key }{package_version} = $p->{package}{version};
466             }
467             }
468             }
469              
470              
471             # Read in a perl structure (probably generated by Data::Dumper) from a
472             # file and return the actual structure. We should probably use
473             # SPOPS::HashFile for this for consistency...
474              
475             sub read_perl_file {
476             my ( $class, $p ) = @_;
477             return undef unless ( -f $p->{filename} );
478             eval { open( INFO, $p->{filename} ) || die $! };
479             if ( $@ ) {
480             warn "Cannot open config file for evaluation ($p->{filename}): $@ ";
481             return undef;
482             }
483             local $/ = undef;
484             no strict;
485             my $info = ;
486             close( INFO );
487             my $data = eval $info;
488             if ( $@ ) {
489             die "Cannot read data structure! from $p->{filename}\nError: $@";
490             }
491             return $data;
492             }
493              
494              
495             # Everything has been read in, now just finalize aliases and so on
496              
497             sub finalize_configuration {
498             my ( $class, $p ) = @_;
499             my $CONF = $p->{config};
500             my $REQUEST_CLASS = $CONF->{server_info}{request_class};
501             my $STASH_CLASS = $CONF->{server_info}{stash_class};
502              
503             # Create all the packages and subroutines on the fly as necessary
504              
505             DEBUG && _w( 1, "Trying to configure SPOPS classes with SPOPS::ClassFactory" );
506             my $init_class = SPOPS::ClassFactory->create( $CONF->{SPOPS} );
507              
508             # Setup the default responses, template classes, etc. for all the
509             # actions read in.
510              
511             $CONF->flatten_action_config;
512             DEBUG && _w( 2, "Config: \n", Dumper( $CONF ) );
513             DEBUG && _w( 1, "Configuration read into Request ok." );
514              
515             # We also want to go through each alias in the 'SPOPS' config key
516             # and setup aliases to the proper class within our Request class; so
517             # $request_alias is just a reference to where we'll actually be storing
518             # this stuff
519              
520             my $request_alias = $REQUEST_CLASS->ALIAS;
521             DEBUG && _w( 1, "Setting up SPOPS aliases" );
522             foreach my $init_alias ( keys %{ $CONF->{SPOPS} } ) {
523             next if ( $init_alias =~ /^_/ );
524             my $info = $CONF->{SPOPS}{ $init_alias };
525             my $class_alias = $info->{class};
526             my @alias_list = ( $init_alias );
527             push @alias_list, @{ $info->{alias} } if ( $info->{alias} );
528             foreach my $alias ( @alias_list ) {
529             DEBUG && _w( 1, "Tag $alias in $STASH_CLASS to be $class_alias" );
530             $request_alias->{ $alias }{ $STASH_CLASS } = $class_alias;
531             }
532             }
533              
534             DEBUG && _w( 1, "Setting up System aliases" );
535             foreach my $alias ( keys %{ $CONF->{system_alias} } ) {
536             $request_alias->{ $alias }{ $STASH_CLASS } = $CONF->{system_alias}{ $alias };
537             }
538             DEBUG && _w( 1, "Setup object and system aliases ok" );
539             return $init_class;
540             }
541              
542              
543             # Plow through a list of classes and call the class_initialize
544             # method on each; ok to call OpenInteract::Startup->initialize_spops( ... )
545             # from the mod_perl child init handler
546              
547             sub initialize_spops {
548             my ( $class, $p ) = @_;
549             return undef unless ( ref $p->{class} );
550             return undef unless ( ref $p->{config} );
551             my @success = ();
552              
553             # Just cycle through and initialize each
554              
555             foreach my $spops_class ( @{ $p->{class} } ) {
556             eval { $spops_class->class_initialize( $p->{config} ); };
557             push @success, $spops_class unless ( $@ );
558             DEBUG && _w( 1, sprintf( "%-40s: %-30s","init: $spops_class", ( $@ ) ? $@ : 'ok' ) );
559             }
560             return \@success;
561             }
562              
563              
564             # Do any global overrides for both SPOPS and the action table entries.
565              
566             sub _process_global_overrides {
567             my ( $class, $config ) = @_;
568             my $override_spops_file = join( '/', $config->{dir}{base},
569             $config->{override}{spops_file} );
570             my $override_action_file = join( '/', $config->{dir}{base},
571             $config->{override}{action_file} );
572              
573             if ( -f $override_spops_file ) {
574             my $override_spops = OpenInteract::Config::GlobalOverride->new(
575             { filename => $override_spops_file } );
576             $override_spops->apply_rules( $config->{SPOPS} );
577             }
578             if ( -f $override_action_file ) {
579             my $override_action = OpenInteract::Config::GlobalOverride->new(
580             { filename => $override_action_file } );
581             $override_action->apply_rules( $config->{action} );
582             }
583             }
584              
585              
586             sub _require_extra_classes {
587             my ( $class, $config ) = @_;
588             my ( %require_class );
589              
590             my $action_require = $class->_find_extra_action_classes( $config );
591             my $spops_require = $class->_find_extra_spops_classes( $config );
592              
593             # Read in all the classes specified by the packages
594              
595             my $successful_action = $class->require_module({
596             class => [ keys %{ $action_require } ],
597             pkg_link => $action_require });
598             if ( scalar @{ $successful_action } != scalar keys %{ $action_require } ) {
599             my %all_tried = map { $_ => 1 } keys %{ $action_require };
600             delete $all_tried{ $_ } for ( @{ $successful_action } );
601             _w( 0, "Some action classes were not required: ",
602             join( ', ', keys %all_tried ) );
603             }
604              
605             my $successful_spops = $class->require_module({
606             class => [ keys %{ $spops_require } ],
607             pkg_link => $spops_require });
608             if ( scalar @{ $successful_spops } != scalar keys %{ $spops_require } ) {
609             my %all_tried = map { $_ => 1 } keys %{ $spops_require };
610             delete $all_tried{ $_ } for ( @{ $successful_spops } );
611             _w( 0, "Some SPOPS classes were not required: ",
612             join( ', ', keys %all_tried ) );
613             }
614             }
615              
616              
617             sub _find_extra_action_classes {
618             my ( $class, $config ) = @_;
619             my %map = ();
620             my $action = $config->{action};
621             foreach my $key ( keys %{ $action } ) {
622             next unless ( $key and $action->{ $key });
623             my $package = $action->{ $key }{package_name};
624             if ( $action->{ $key }{class} ) {
625             $map{ $action->{ $key }{class} } = $package
626             }
627             if ( $action->{ $key }{filter} ) {
628             if ( ref $action->{ $key }{filter} eq 'ARRAY' ) {
629             $map{ $_ } = $package for ( @{ $action->{ $key }{filter} } );
630             }
631             else {
632             $map{ $action->{ $key }{filter} } = $package
633             }
634             }
635             if ( $action->{ $key }{error} ) {
636             if ( ref $action->{ $key }{error} eq 'ARRAY' ) {
637             $map{ $_ } = $package for ( @{ $action->{ $key }{error} } );
638             }
639             else {
640             $map{ $action->{ $key }{error} } = $package;
641             }
642             }
643             }
644             return \%map;
645             }
646              
647              
648             sub _find_extra_spops_classes {
649             my ( $class, $config ) = @_;
650             my %map = ();
651             my $spops = $config->{SPOPS};
652             foreach my $key ( keys %{ $spops } ) {
653             next unless ( $key and $spops->{ $key });
654             my $package = $spops->{ $key }{package_name};
655             if ( ref $spops->{ $key }{isa} eq 'ARRAY' ) {
656             map { $map{ $_ } = $package } @{ $spops->{ $key }{isa} };
657             }
658             }
659             return \%map;
660             }
661              
662              
663              
664             sub _w {
665             return unless ( DEBUG >= shift );
666             my ( $pkg, $file, $line ) = caller;
667             my @ci = caller(1);
668             warn "$ci[3] ($line) >> ", join( ' ', @_ ), "\n";
669             }
670              
671             1;
672              
673             __END__