File Coverage

lib/App/Context.pm
Criterion Covered Total %
statement 312 1253 24.9
branch 149 828 18.0
condition 80 326 24.5
subroutine 29 93 31.1
pod 37 77 48.0
total 607 2577 23.5


line stmt bran cond sub pod time code
1              
2             #############################################################################
3             ## $Id: Context.pm 14127 2010-06-09 21:12:59Z spadkins $
4             #############################################################################
5              
6             package App::Context;
7             $VERSION = (q$Revision: 14127 $ =~ /(\d[\d\.]*)/)[0]; # VERSION numbers generated by svn
8              
9 6     6   32 use strict;
  6         10  
  6         327  
10              
11 6     6   29 use App;
  6         9  
  6         123  
12              
13 6     6   30 use Carp qw(confess shortmess);
  6         10  
  6         560  
14 6     6   11335 use Date::Format;
  6         59954  
  6         463  
15 6     6   6499 use Time::HiRes;
  6         11731  
  6         30  
16 6     6   991 use IO::Handle; # for the STDOUT->autoflush() method
  6         12  
  6         220  
17 6     6   5567 use IO::Socket;
  6         103603  
  6         40  
18 6     6   3738 use IO::Socket::INET;
  6         14  
  6         51  
19              
20             =head1 NAME
21              
22             App::Context - An application framework for web applications, command-line programs, server programs, and web services
23              
24             =head1 SYNOPSIS
25              
26             # ... official way to get a Context object ...
27             use App;
28             $context = App->context();
29             $context->dispatch_events(); # dispatch events
30             $conf = $context->conf(); # get the configuration
31              
32             # any of the following named parameters may be specified
33             $context = App->context(
34             context_class => "App::Context::CGI",
35             conf_class => "App::Conf::File", # or any Conf args
36             );
37              
38             # ... alternative way (used internally) ...
39             use App::Context;
40             $context = App::Context->new();
41              
42             =cut
43              
44             #############################################################################
45             # CONSTANTS
46             #############################################################################
47              
48             =head1 DOCUMENT STATUS
49              
50             This documentation is out of date and needs review and revision.
51              
52             Please start with the L document.
53              
54             =head1 DESCRIPTION
55              
56             A Context class models the environment (aka "context")
57             in which the current process is running.
58              
59             The role of the Context class is to abstract the details of the
60             various runtime environments (or Platforms) (including their event loops)
61             so that the basic programming model for the developer is uniform.
62              
63             Since the Context objects are the objects that initiate events in the
64             App-Context universe, they must be sure to wrap those event handlers with
65             try/catch blocks (i.e. "eval{};if($@){}" blocks).
66              
67             The main functions of the Context class are to
68              
69             * load the Conf data,
70             * dispatch events from the Context event loop, and
71             * manage Session data.
72              
73             The Context object is always a singleton per process (except in rare cases
74             like debugging during development).
75              
76             Conceptually, the Context may be associated with many
77             Conf's (one per authenticated user) and
78             Sessions (one per unique session_id)
79             in a single process (ModPerl).
80             However, in practice, it is often
81             associated with only one Conf or Session throughout the lifetime of
82             the process (CGI, Cmd).
83              
84             =cut
85              
86             #############################################################################
87             # CLASS GROUP
88             #############################################################################
89              
90             =head1 Class Group: Context
91              
92             The following classes might be a part of the Context Class Group.
93              
94             =over
95              
96             =item * Class: App::Context
97              
98             =item * Class: App::Context::CGI
99              
100             =item * Class: App::Context::FCGI
101              
102             =item * Class: App::Context::ModPerl
103              
104             =item * Class: App::Context::ModPerlRegistry
105              
106             =item * Class: App::Context::PPerl
107              
108             =item * Class: App::Context::Cmd
109              
110             =item * Class: App::Context::Daemon
111              
112             =item * Class: App::Context::POE
113              
114             =item * Class: App::Context::SOAP (when acting as a SOAP server)
115              
116             =item * Class: App::Context::Gtk
117              
118             =item * Class: App::Context::WxPerl
119              
120             =back
121              
122             =cut
123              
124             #############################################################################
125             # ATTRIBUTES/CONSTANTS/CLASS VARIABLES/GLOBAL VARIABLES
126             #############################################################################
127              
128             =head1 Attributes, Constants, Global Variables, Class Variables
129              
130             =head2 Master Data Structure Map
131              
132             $context
133             $context->{debug_scope}{$class} Debugging all methods in class
134             $context->{debug_scope}{$class.$method} Debugging a single method
135             $context->{options} Args that Context was created with
136             $context->{used}{$class} Similar to %INC, keeps track of what classes used
137             $context->{Conf}{$user} Info from conf file
138             [$context->{conf}]
139             $conf->{$type}{$name} Read-only service conf
140             $context->{sessions}{$session_id}
141             [$context->{session}]
142             $session->{store}{$type}{$name} Runtime state which is stored
143             $session->{cache}{$type}{$name} Instances of services
144              
145             =cut
146              
147             #############################################################################
148             # CONSTRUCTOR METHODS
149             #############################################################################
150              
151             =head1 Constructor Methods:
152              
153             =cut
154              
155             #############################################################################
156             # new()
157             #############################################################################
158              
159             =head2 new()
160              
161             The App::Context->new() method is rarely called directly.
162             That is because a $context should always be instantiated by calling
163             App->context(). This allows for caching of the $context
164             as a singleton and the autodetection of what type of Context subclass
165             should in fact be instantiated.
166              
167             * Signature: $context = App->new($named);
168             * Signature: $context = App->new(%named);
169             * Param: context_class class [in]
170             * Param: conf_class class [in]
171             * Param: conf_file string [in]
172             * Return: $context App::Context
173             * Throws: Exception::Class::Context
174             * Since: 0.01
175              
176             Sample Usage:
177              
178             $context = App::Context->new();
179             $context = App::Context->new( {
180             conf_class => 'App::Conf::File',
181             conf_file => 'app.xml',
182             } );
183             $context = App::Context->new(
184             conf_class => 'App::Conf::File',
185             conf_file => 'app.xml',
186             );
187              
188             =cut
189              
190             sub new {
191 6 50   6 1 73 &App::sub_entry if ($App::trace);
192 6         18 my $this = shift;
193 6   33     51 my $class = ref($this) || $this;
194 6         17 my $self = {};
195 6         63 bless $self, $class;
196              
197 6         12 my ($options, %options, $i);
198 6 50       31 if ($#_ > -1) {
199 6 50       31 if (ref($_[0]) eq "HASH") {
200 6         22 $options = shift;
201 6 50       29 die "Odd number of named args in App::Context->new()"
202             if ($#_ % 2 == 0);
203 6         30 for ($i = 0; $i < $#_; $i++) {
204 0         0 $options->{$_[$i]} = $_[$i+1];
205             }
206             }
207             else {
208 0 0       0 $options = ($#_ > -1) ? { @_ } : {};
209             }
210             }
211 6         59 %options = %$options;
212              
213             #################################################################
214             # DEBUGGING
215             #################################################################
216              
217             # Supports the following command-line usage:
218             # -debug=1 (global debug)
219             # -debug=1,App::Context (debug class only)
220             # -debug=3,App::Context,App::Session (multiple classes)
221             # -debug=6,App::Repository::DBI.select_rows (indiv. methods)
222 6         18 my ($debug, $pkg);
223 6         16 $debug = $options{debug};
224 6 50 33     34 if (defined $debug && $debug ne "") {
225 0 0       0 if ($debug =~ s/^([0-9]+),?//) {
226 0         0 $App::DEBUG = $1;
227             }
228 0 0       0 if ($debug) {
229 0         0 foreach $pkg (split(/,/,$debug)) {
230 0         0 $self->{debug_scope}{$pkg} = 1;
231             }
232             }
233             }
234              
235 6         12 my ($conf_class, $session_class);
236 6         54 $self->{options} = \%options;
237 6         16 $options{context} = $self;
238              
239 6         18 $self->{log_level} = $options{log_level};
240 6 50       36 $self->{log_level} = 2 if (!defined $self->{log_level});
241 6         55 $self->log_file_open();
242              
243 6         14 $conf_class = $options{conf_class};
244 6 50       26 $conf_class = "App::Conf::File" if (! $conf_class);
245              
246 6 50       25 if ($App::DEBUG >= 2) {
247 0         0 my (@str, $key);
248 0         0 push(@str,"Context->new(): conf=$conf_class\n");
249 0         0 foreach $key (sort keys %options) {
250 0         0 push(@str, " $key => $options{$key}\n");
251             }
252 0         0 $self->dbgprint(join("",@str));
253             }
254              
255             ##############################################################
256             # initialize conf
257             ##############################################################
258 6         15 my $conf = {};
259              
260 6         23 eval {
261              
262             # Initialize from "app.pl" or other file/source specified by the class
263 6         65 $conf = App->new($conf_class, "new", \%options);
264              
265             # Override any values which are supplied in "app.conf" (the "deployment descriptor")
266 6         38 foreach my $var (keys %options) {
267 20 50       89 if ($var =~ /^app\.(.+)/) {
268 0         0 $conf->set($1, $options{$var});
269             }
270             }
271             };
272 6 50       25 $self->add_message($@) if ($@);
273              
274 6         23 $self->{conf} = $conf;
275              
276             ##############################################################
277             # Include and Overlay $conf with additional files
278             ##############################################################
279 6         10 my ($includes);
280 6 50       62 $includes = $conf->{global}{include} if ($conf->{global});
281 6 50 33     28 if ($includes && ref($includes) eq "ARRAY") {
282 0         0 my $options = $self->{options};
283 0         0 my $prefix = $options->{prefix};
284 0         0 my (@include_files, $cond, $include_files, $matches);
285 0         0 for (my $i = 0; $i <= $#$includes; $i += 2) {
286 0         0 $cond = $includes->[$i];
287 0         0 $include_files = $includes->[$i+1];
288 0         0 $matches = $self->cond_matches_options($cond, $options);
289 0 0       0 if ($matches) {
290 0 0       0 if (ref($include_files) eq "ARRAY") {
    0          
291 0         0 @include_files = @$include_files;
292             }
293             elsif (ref($include_files) eq "") {
294 0         0 @include_files = ( $include_files );
295             }
296 0         0 foreach my $conf_file (@include_files) {
297 0 0       0 $conf_file = "$prefix/etc/app/$conf_file" if ($conf_file !~ m!^/!);
298 0 0       0 if ($self->{conf_included}{$conf_file}) {
299 0 0       0 print STDERR "Conf global include: [$cond][$conf_file] already included\n" if ($options{debug_conf});
300 0         0 next;
301             }
302 0 0       0 if (-r $conf_file) {
303 0         0 $options{conf_file} = $conf_file;
304 0         0 my $aux_conf = $conf_class->create({ %options });
305 0         0 $conf->overlay($aux_conf);
306 0 0       0 print STDERR "Conf global include: [$cond][$conf_file] included (overlayed)\n" if ($options{debug_conf});
307             }
308             else {
309 0 0       0 print STDERR "Conf global include: [$cond][$conf_file] not readable\n" if ($options{debug_conf});
310             }
311 0         0 $self->{conf_included}{$conf_file} = 1;
312             }
313             }
314 0 0 0     0 print STDERR "Conf global include: [$cond] did not match options\n" if (!$matches && $options{debug_conf});
315             }
316             }
317              
318             ##############################################################
319             # misc
320             ##############################################################
321 6 50 33     35 if (defined $options{debug_conf} && $options{debug_conf} >= 2) {
322 0         0 $self->dbgprint($self->{conf}->dump());
323             }
324              
325 6         17 $self->{events} = []; # the event queue starts empty
326 6         15 $self->{returntype} = "default"; # assume default return type
327              
328 6         15 $self->{scheduled_events} = [];
329 6         19 $self->{scheduled_event} = {};
330              
331 6         18 $self->{event_loop_extensions} = [];
332              
333 6         56 $self->_init(\%options); # allows the subclass to do initialization
334              
335 6         184 $self->set_current_session("default");
336              
337 6 50       26 if ($options{authentication_class}) {
338 0         0 $self->authentication("default", class => $options{authentication_class});
339             }
340              
341 6 50       19 &App::sub_exit($self) if ($App::trace);
342 6         31 return $self;
343             }
344              
345             sub _default_session_class {
346 5 50   5   33 &App::sub_entry if ($App::trace);
347 5 50       15 &App::sub_exit("App::Session") if ($App::trace);
348 5         18 return("App::Session");
349             }
350              
351             # NOTE: This is very similar logic to some logic in App::Options to see if sections
352             # of app.conf are active.
353             sub cond_matches_options {
354 0 0   0 0 0 &App::sub_entry if ($App::trace);
355 0         0 my ($self, $cond_str, $options) = @_;
356 0         0 my ($var, $value, $regexp, $cond, $cond_value);
357 0         0 my $matches = 1; # assume the condition matches
358 0         0 my @cond = split(/;/,$cond_str); # separate the conditions that must be satisfied
359 0         0 foreach $cond (@cond) { # check each condition
360 0 0       0 if ($cond =~ /^([^=]+)=(.*)$/) { # i.e. city=ATL or name=/[Ss]tephen/
361 0         0 $var = $1;
362 0         0 $cond_value = $2;
363             }
364             else { # i.e. [go] matches the program (app) named "go"
365 0         0 $var = "app";
366 0         0 $cond_value = $cond;
367             }
368 0 0 0     0 if ($cond_value =~ m!^/(.*)/$!) { # variable's value must match the regexp
    0 0        
369 0         0 $regexp = $1;
370 0         0 $value = $options->{$var};
371 0 0       0 $value = "" if (!defined $value);
372 0 0       0 $matches = ($value =~ /$regexp/) ? 1 : 0;
373             }
374             elsif ($var eq "app" && ($cond_value eq "" || $cond_value eq "ALL")) {
375 0         0 $matches = 1; # "" and "ALL" are special wildcards for the "app" variable
376             }
377             else { # a variable's value must match exactly
378 0         0 $value = $options->{$var};
379 0 0       0 $value = "" if (!defined $value);
380 0 0       0 $matches = ($value eq $cond_value) ? 1 : 0;
381             }
382 0 0       0 last if (!$matches);
383             }
384 0 0       0 &App::sub_exit($matches) if ($App::trace);
385 0         0 return($matches);
386             }
387              
388             #############################################################################
389             # PROTECTED METHODS
390             #############################################################################
391              
392             =head1 Protected Methods:
393              
394             The following methods are intended to be called by subclasses of the
395             current class (or environmental, "main" code).
396              
397             =cut
398              
399             #############################################################################
400             # _init()
401             #############################################################################
402              
403             =head2 _init()
404              
405             The _init() method is called from within the standard Context constructor.
406             The _init() method in this class does nothing.
407             It allows subclasses of the Context to customize the behavior of the
408             constructor by overriding the _init() method.
409              
410             * Signature: $context->_init($options)
411             * Param: $options {} [in]
412             * Return: void
413             * Throws: App::Exception
414             * Since: 0.01
415              
416             Sample Usage:
417              
418             $context->_init($options);
419              
420             =cut
421              
422             sub _init {
423 5 50   5   20 &App::sub_entry if ($App::trace);
424 5         10 my ($self, $options) = @_;
425              
426 5         37 $self->init_profiler_log();
427              
428 5 50       15 &App::sub_exit() if ($App::trace);
429             }
430              
431             #############################################################################
432             # PUBLIC METHODS
433             #############################################################################
434              
435             =head1 Public Methods: Services
436              
437             =cut
438              
439             #############################################################################
440             # service()
441             #############################################################################
442              
443             =head2 service()
444              
445             The service() method returns a named object of a certain service type.
446              
447             * Signature: $service = $context->service($type);
448             * Signature: $service = $context->service($type,$name);
449             * Signature: $service = $context->service($type,$name,%named);
450             * Param: $type string [in]
451             * Param: $name string [in]
452             * Return: $service App::Service
453             * Throws: App::Exception
454             * Since: 0.01
455              
456             Sample Usage:
457              
458             $user = $context->service("SessionObject","db.user.spadkins");
459             $gobutton = $context->service("SessionObject","gobutton");
460              
461             There are many services available within an App-Context application.
462             Each service is identified by two pieces of information:
463             it's type and its name.
464              
465             The following service types are standard in App-Context.
466             Others can be developed by deriving a class from the
467             App::Service class.
468             All service types must start with a capital letter.
469              
470             * Serializer
471             * CallDispatcher
472             * MessageDispatcher
473             * ResourceLocker
474             * SharedDatastore
475             * Authentication
476             * Authorization
477             * SessionObject
478              
479             Within each service type, each individual service is
480             identified by its name.
481             The name of a service, if not
482             specified, is assumed to be "default".
483              
484             Whenever a service is requested from the Context via this
485             service() method, the service cache in the Session is checked
486             first. If it exists, it is generally returned immediately
487             without modification by the named parameters.
488             (Parameters *are* taken into account if the "override"
489             parameter is supplied.)
490              
491             If it does not exist, it must be created and stored in the
492             cache.
493              
494             The name of a service, if not specified, is assumed to be "default".
495              
496             The named parameters (%named or $named),
497             if supplied, are considered defaults.
498             They are ignored if the values already exist in the service conf.
499             However, the additional named parameter, "override", may be supplied.
500             In that case, all of the values in the named parameters will accepted
501             into the service conf.
502              
503             Every service (i.e. $conf->{Repository}{default}) starts as
504             a simple hash which is populated with attributes from several
505             complementary sources. If we imagine that a service is requested
506             with type $type and name $name, we can envision the following
507             additional derived variables.
508              
509             $type = "Repository";
510             $name = "sysdb";
511             $conf = $context->conf();
512             $repository_type = $conf->{Repository}{sysdb}{repository_type};
513              
514             The following sources are consulted to populate the service
515             attributes.
516              
517             1. conf of the service (in Conf)
518             i.e. $conf->{Repository}{sysdb}
519              
520             2. optional conf of the service's service_type (in Conf)
521             i.e. $conf->{RepositoryType}{$repository_type}
522              
523             3. named parameters to the service() call
524              
525             All service configuration happens before instantiation
526             this allows you to override the "service_class" in the configuration
527             in time for instantiation
528              
529             =cut
530              
531             sub service {
532 26 50   26 1 1271 &App::sub_entry if ($App::trace);
533 26         76 my ($self, $type, $name, %named) = @_;
534 26 50 33     85 $self->dbgprint("Context->service(" . join(", ",@_) . ")")
535             if ($App::DEBUG && $self->dbg(3));
536 26         66 my $options = $self->{options};
537              
538 26         42 my ($args, $new_service, $override, $lightweight, $attrib);
539 0         0 my ($service, $conf, $class, $session);
540 0         0 my ($service_store, $service_conf, $service_type, $service_type_conf);
541 0         0 my ($default);
542              
543             # $type (i.e. SessionObject, Session, etc.) must be supplied
544 26 50       81 if (!defined $type) {
545 0         0 App::Exception->throw(
546             error => "cannot create a service of unknown type\n",
547             );
548             }
549              
550 26 100       68 if (%named) {
551 4         9 $args = \%named;
552             }
553             else {
554 22         42 $args = {};
555             }
556              
557 26 100 66     169 if (! defined $name || $name eq "") { # we need a name!
558 1         2 $name = "default";
559             }
560              
561 26         56 $session = $self->{session};
562 26         90 $service = $session->{cache}{$type}{$name}; # check the cache
563 26         57 $conf = $self->{conf};
564 26         65 $service_conf = $conf->{$type}{$name};
565 26   100     134 my $temporary = ($name eq "temporary") || $args->{temporary};
566 26   100     109 my $service_initialized = ($service && ref($service) ne "HASH");
567             #print "$type($name): SERVICE=$service INIT=$service_initialized\n";
568              
569             ##############################################################
570             # Load extra conf on demand
571             ##############################################################
572 26 50 66     124 if (!$service_initialized && !$service_conf && $name !~ /-/) { # if it's not a contained widget, try the file system
      66        
573 9         20 my $prefix = $options->{prefix};
574 9   50     44 my $conf_type = $options->{conf_type} || "pl";
575 9         129 my $conf_file = "$prefix/etc/app/$type.$name.$conf_type";
576 9 50 66     475 if (!$self->{conf_included}{$conf_file} && -r $conf_file) {
577 0         0 $options->{conf_file} = $conf_file;
578 0         0 my $aux_conf = App::Conf::File->create({ %$options });
579 0         0 $conf->overlay($aux_conf);
580 0         0 $service_conf = $conf->{$type}{$name};
581             }
582 9         189 $self->{conf_included}{$conf_file} = 1;
583             }
584              
585             ##############################################################
586             # conf includes
587             ##############################################################
588 26 50 100     162 if (!$service_initialized && $service_conf && $service_conf->{include}) {
      66        
589 0         0 my $prefix = $options->{prefix};
590 0         0 my (@include_files);
591 0         0 my $include_files = $service_conf->{include};
592 0 0       0 if (ref($include_files) eq "ARRAY") {
    0          
593 0         0 @include_files = @$include_files;
594             }
595             elsif (ref($include_files) eq "") {
596 0         0 @include_files = ( $include_files );
597             }
598 0         0 foreach my $conf_file (@include_files) {
599 0 0       0 $conf_file = "$prefix/etc/app/$conf_file" if ($conf_file !~ m!^/!);
600 0 0       0 next if ($self->{conf_included}{$conf_file});
601 0 0       0 if (-r $conf_file) {
602 0         0 $options->{conf_file} = $conf_file;
603 0         0 my $aux_conf = App::Conf::File->create({ %$options });
604 0         0 $conf->overlay($aux_conf);
605             }
606 0         0 $self->{conf_included}{$conf_file} = 1;
607             }
608             }
609              
610             ##############################################################
611             # Detect Deprecated Services
612             ##############################################################
613 26 100 100     107 if (!$service_initialized && $service_conf) {
614 8 50       38 if ($service_conf->{deprecated}) {
615 0         0 my $message_suffix = $service_conf->{deprecated};
616 0         0 my $message = "WARNING: $type($name) deprecated";
617 0 0       0 $message .= ": $message_suffix" if ($message_suffix ne "1");
618 0         0 my $deprecated_action = $options->{"app.Context.deprecated_action"};
619 0 0 0     0 if (!$deprecated_action || $deprecated_action eq "none") {
    0          
620             # do nothing
621             }
622             elsif ($deprecated_action eq "die") {
623 0         0 confess $message;
624             }
625             else {
626 0         0 $self->log(shortmess($message));
627             }
628             }
629             }
630              
631             ##############################################################
632             # aliases
633             ##############################################################
634 26 100 100     147 if (!$service_initialized && $service_conf) {
635 8         19 my $alias = $service_conf->{alias};
636 8 50 33     109 if ($alias && $alias ne $name) {
    50 33        
      33        
637 0         0 $service = $session->{cache}{$type}{$alias};
638 0 0       0 $service = $self->service($type, $alias) if (!$service);
639 0         0 $service_conf = $conf->{$type}{$alias};
640 0         0 $name = $alias;
641             }
642             elsif ($type ne "Authorization" && ($service_conf->{clone} || $service_conf->{auth_clone})) {
643 0         0 my $clone = $self->get_auth_attrib_value($service_conf, $type, $name, "clone");
644 0 0       0 if ($clone) {
645 0         0 $service_conf = $conf->{$type}{$clone};
646             }
647             }
648             }
649              
650 26         41 $new_service = 0;
651              
652             # NEVER DEFINED OR NON-BLESSED HASH (fully defined services are blessed into classes)
653 26 100 100     206 if ($temporary || !defined $service || ref($service) eq "HASH") {
      100        
654 17 100       52 $service = {} if (!defined $service); # start with new hash ref
655 17         70 $service->{name} = $name;
656 17         89 $service->{context} = $self;
657              
658 17         51 $service_store = $session->{store}{$type}{$name};
659 17 100       66 if ($temporary) {
660 4         5 $service_store = undef;
661 4         9 $service->{temporary} = 1;
662             }
663              
664 17 50 33     59 if ($App::DEBUG && $self->dbg(6)) {
665 0         0 $self->dbgprint("Context->service(): new service. conf=$conf svc=$service sconf=$service_conf sstore=$service_store");
666 0 0       0 $self->dbgprint("Context->service(): sconf={",join(",",%$service_conf),"}") if ($service_conf);
667 0 0       0 $self->dbgprint("Context->service(): sstore={",join(",",%$service_store),"}") if ($service_store);
668             }
669            
670 17         25 $new_service = 1;
671              
672             ################################################################
673             # start with runtime store for the service from the session
674             ################################################################
675 17 100       56 if ($service_store) {
676 6         23 foreach $attrib (keys %$service_store) {
677 2 100       19 if (!defined $service->{$attrib}) {
678 1         4 $service->{$attrib} = $service_store->{$attrib};
679             }
680             }
681             }
682              
683             ################################################################
684             # overlay with attributes from the conf file
685             ################################################################
686 17 100       68 if ($service_conf) {
687 8         27 foreach $attrib (keys %$service_conf) {
688             # include conf attributes only if not set already
689 10 50       28 if (!defined $service->{$attrib}) {
690 10         37 $service->{$attrib} = $service_conf->{$attrib};
691             }
692             }
693             }
694              
695             ################################################################
696             # overlay with attributes from the "service_type"
697             ################################################################
698 17         42 $service_type = $service->{type}; # i.e. "session_object_type"
699 17 50       58 if ($service_type) {
700 0         0 $service_type_conf = $conf->{"${type}Type"}{$service_type};
701 0 0       0 if ($service_type_conf) {
702 0         0 foreach $attrib (keys %$service_type_conf) {
703             # include service_type confs only if not set already
704 0 0       0 if (!defined $service->{$attrib}) {
705 0         0 $service->{$attrib} = $service_type_conf->{$attrib};
706             }
707             }
708             }
709             }
710             }
711              
712             ################################################################
713             # take care of all %$args attributes next
714             ################################################################
715              
716             # A "lightweight" service is one which never stores its attributes in
717             # the session store. It assumes that all necessary attributes will
718             # be supplied by the conf or by the code. As a result, a "lightweight"
719             # service can usually never handle events.
720             # 1. its attributes are only ever required when they are all supplied
721             # 2. its attributes will be OK by combining the %$args with the %$conf
722             # This all saves space in the Session store, as the attribute values can
723             # be relied upon to be supplied by the conf file and the code (and
724             # minimal reliance on the Session store).
725             # This is really handy when you have something like a huge spreadsheet
726             # of text entry cells (usually an indexed variable).
727              
728 26 100       97 if ($temporary) { # may be specified implicitly
    50          
729 4         7 $lightweight = 1;
730             }
731             elsif (defined $args->{lightweight}) { # may be specified explicitly
732 0         0 $lightweight = $args->{lightweight};
733             }
734             else {
735 22         147 $lightweight = ($name =~ /[\{\}\[\]]/); # or implicitly for indexed variables
736             }
737 26         46 $override = $args->{override};
738              
739 26 100 66     95 if ($new_service || $override) {
740 17         66 foreach $attrib (keys %$args) {
741             # don't include the entry which says whether we are overriding or not
742 4 50       16 next if ($attrib eq "override");
743              
744             # include attrib if overriding OR attrib not provided in the session_object confs already
745 4 100 33     24 if (!defined $service->{$attrib} ||
      66        
746             ($override && $service->{$attrib} ne $args->{$attrib})) {
747 2         7 $service->{$attrib} = $args->{$attrib};
748 2 50       20 $session->{store}{$type}{$name}{$attrib} = $args->{$attrib} if (!$lightweight);
749             }
750 4 50 33     22 $self->dbgprint("Context->service() [arg=$attrib] name=$name lw=$lightweight ovr=$override",
751             " service=", $service->{$attrib},
752             " service_store=", $service_store->{$attrib},
753             " args=", $args->{$attrib})
754             if ($App::DEBUG && $self->dbg(6));
755             }
756             }
757            
758 26 100       69 if ($new_service) {
759 17 50 33     139 $self->dbgprint("Context->service() new service [$name]")
760             if ($App::DEBUG && $self->dbg(3));
761              
762 17 50 66     112 if (!$temporary && defined $service->{default}) {
763 0         0 $default = $service->{default};
764 0 0       0 if ($default =~ /^\{today\}\+?(-?[0-9]+)?$/) {
765 0 0       0 $default = time2str("%Y-%m-%d",time + 2*3600 + ($1 ? ($1*3600*24) : 0));
766             }
767 0 0       0 if (defined $default) {
768 0         0 $self->so_get($name, "", $default, 1);
769 0         0 $self->so_delete($name, "default");
770             }
771             }
772              
773 17         35 $class = $service->{class}; # find class of service
774              
775 17 100 66     103 if (!defined $class || $class eq "") {
776 7         15 $class = "App::$type"; # assume the "generic" class
777 7         17 $service->{class} = $class;
778             }
779              
780 17 100       65 if (! $self->{used}{$class}) { # load the code
781 10         66 App->use($class);
782 9         43 $self->{used}{$class} = 1;
783             }
784 16 50 33     72 $self->dbgprint("Context->service() service class [$class]")
785             if ($App::DEBUG && $self->dbg(3));
786              
787 16         49 bless $service, $class; # bless the service into the class
788 16 100       81 if (!$temporary) {
789 12         53 $session->{cache}{$type}{$name} = $service; # save in the cache
790             }
791 16         93 $service->_init(); # perform additional initializations
792             }
793              
794 25 50 33     86 $self->dbgprint("Context->service() = $service")
795             if ($App::DEBUG && $self->dbg(3));
796              
797 25 50       131 &App::sub_exit($service) if ($App::trace);
798 25         139 return $service;
799             }
800              
801             #############################################################################
802             # service convenience methods
803             #############################################################################
804              
805             =head2 serializer()
806              
807             =head2 call_dispatcher()
808              
809             =head2 message_dispatcher()
810              
811             =head2 resource_locker()
812              
813             =head2 shared_datastore()
814              
815             =head2 authentication()
816              
817             =head2 authorization()
818              
819             =head2 session_object()
820              
821             These are all convenience methods, which simply turn around
822             and call the service() method with the service type as the
823             first argument.
824              
825             * Signature: $session = $context->session();
826             * Signature: $session = $context->session($name);
827             * Signature: $session = $context->session($name,%named);
828             * Param: $name string [in]
829             * Return: $service App::Service
830             * Throws: App::Exception
831             * Since: 0.01
832              
833             Sample Usage:
834              
835             $serializer = $context->serializer();
836             $call_dispatcher = $context->call_dispatcher();
837             $message_dispatcher = $context->message_dispatcher();
838             $resource_locker = $context->resource_locker();
839             $shared_datastore = $context->shared_datastore();
840             $authentication = $context->authentication();
841             $authorization = $context->authorization();
842             $session_object = $context->session_object();
843             $value_domain = $context->value_domain();
844              
845             =cut
846              
847             # Standard Services: provided in the App-Context distribution
848 0     0 1 0 sub call_dispatcher { my $self = shift; return $self->service("CallDispatcher",@_); }
  0         0  
849 0     0 1 0 sub message_dispatcher { my $self = shift; return $self->service("MessageDispatcher",@_); }
  0         0  
850 0     0 1 0 sub resource_locker { my $self = shift; return $self->service("ResourceLocker",@_); }
  0         0  
851 0     0 1 0 sub shared_datastore { my $self = shift; return $self->service("SharedDatastore",@_); }
  0         0  
852 0     0 1 0 sub authentication { my $self = shift; return $self->service("Authentication",@_); }
  0         0  
853 0     0 1 0 sub authorization { my $self = shift; return $self->service("Authorization",@_); }
  0         0  
854 12     12 1 3997 sub session_object { my $self = shift; return $self->service("SessionObject",@_); }
  12         63  
855 0     0 0 0 sub value_domain { my $self = shift; return $self->service("ValueDomain",@_); }
  0         0  
856              
857             sub serializer {
858 4     4 1 11729 my $self = shift;
859 4         19 my $name = shift;
860 4         8 my (@args);
861 4 50 33     73 if ($#_ > -1 || !$name || $self->service_exists("Serializer", $name)) {
      33        
862 4         11 @args = @_;
863             }
864             else {
865 0         0 my $class_base = ucfirst(lc($name));
866 0         0 $class_base =~ s/_([a-z])/"_" . uc($1)/eg;
  0         0  
867 0         0 my $class = "App::Serializer::" . $class_base;
868 0         0 @args = (class => $class);
869             }
870 4         27 return $self->service("Serializer", $name, @args);
871             }
872              
873             # Extended Services: provided in the App-Widget and App-Repository distributions
874             # this is kind of cheating for the core to know about the extensions, but OK
875 0     0 0 0 sub template_engine { my $self = shift; return $self->service("TemplateEngine",@_); }
  0         0  
876             sub repository {
877 0     0 0 0 my ($self, $name, @opts) = @_;
878 0         0 my $options = $self->{options};
879 0         0 my $key = "$name.dbclass";
880 0 0       0 if ($options->{$key}) {
881 0         0 $self->{conf}{Repository}{$name}{class} = $options->{$key};
882             }
883 0         0 return $self->service("Repository", $name, @opts);
884             }
885             sub widget {
886 0     0 0 0 my $self = shift;
887 0         0 my @args = @_;
888 0 0       0 if ($#args <= 0) {
889 0         0 push(@args, ("class", "App::Widget"));
890             }
891 0         0 return $self->service("SessionObject",@args);
892             }
893              
894             #############################################################################
895             # session_object_exists()
896             #############################################################################
897              
898             =head2 session_object_exists()
899              
900             * Signature: $exists = $context->session_object_exists($session_object_name);
901             * Param: $session_object_name string
902             * Return: $exists boolean
903             * Throws:
904             * Since: 0.01
905              
906             Sample Usage:
907              
908             if ($context->session_object_exists($session_object_name)) {
909             # do something
910             }
911              
912             The session_object_exists() returns whether or not a session_object is already known to the
913             Context. This is true if
914              
915             * it exists in the Session's session_object cache, or
916             (i.e. it has already been referenced and instantiated in the cache),
917             * it exists in the Session's store, or
918             (i.e. it was referenced in an earlier request in this session)
919             * it exists in the Conf
920              
921             If this method returns FALSE (undef), then any call to the session_object() method
922             must specify the session_object_class (at a minimum) and may not simply call it
923             with the $session_object_name.
924              
925             This is useful particularly for lightweight session_objects which generate events
926             (such as image buttons). The $context->dispatch_events() method can check
927             that the session_object has not yet been defined and automatically passes the
928             event to the session_object's container (implied by the name) for handling.
929              
930             =cut
931              
932             sub session_object_exists {
933 0 0   0 1 0 &App::sub_entry if ($App::trace);
934 0         0 my ($self, $session_object_name) = @_;
935 0         0 my ($exists, $session_object_type, $session_object_class);
936              
937 0   0     0 $session_object_class =
938             $self->{session}{cache}{SessionObject}{$session_object_name}{class} ||
939             $self->{session}{store}{SessionObject}{$session_object_name}{class} ||
940             $self->{conf}{SessionObject}{$session_object_name}{class};
941              
942 0 0       0 if (!$session_object_class) {
943              
944 0   0     0 $session_object_type =
945             $self->{session}{cache}{SessionObject}{$session_object_name}{type} ||
946             $self->{session}{store}{SessionObject}{$session_object_name}{type} ||
947             $self->{conf}{SessionObject}{$session_object_name}{type};
948              
949 0 0       0 if ($session_object_type) {
950 0         0 $session_object_class = $self->{conf}{SessionObjectType}{$session_object_type}{class};
951             }
952             }
953              
954 0 0       0 $exists = $session_object_class ? 1 : 0;
955              
956 0 0 0     0 $self->dbgprint("Context->session_object_exists($session_object_name) = $exists")
957             if ($App::DEBUG && $self->dbg(2));
958              
959 0 0       0 &App::sub_exit($exists) if ($App::trace);
960 0         0 return $exists;
961             }
962              
963             sub service_exists {
964 4 50   4 0 17 &App::sub_entry if ($App::trace);
965 4         13 my ($self, $service_type, $service_name) = @_;
966 4         9 my ($exists, $service_template, $service_class);
967              
968 4   33     87 $service_class =
969             $self->{session}{cache}{$service_type}{$service_name}{class} ||
970             $self->{session}{store}{$service_type}{$service_name}{class} ||
971             $self->{conf}{$service_type}{$service_name}{class};
972              
973 4 50       12 if (!$service_class) {
974              
975 0   0     0 $service_template =
976             $self->{session}{cache}{$service_type}{$service_name}{type} ||
977             $self->{session}{store}{$service_type}{$service_name}{type} ||
978             $self->{conf}{$service_type}{$service_name}{type};
979              
980 0 0       0 if ($service_template) {
981 0         0 $service_class = $self->{conf}{"${service_type}Type"}{$service_template}{class};
982             }
983             }
984              
985 4 50       17 $exists = $service_class ? 1 : 0;
986              
987 4 50 33     22 $self->dbgprint("Context->service_exists($service_name) = $exists")
988             if ($App::DEBUG && $self->dbg(2));
989              
990 4 50       10 &App::sub_exit($exists) if ($App::trace);
991 4         22 return $exists;
992             }
993              
994             #############################################################################
995             # PUBLIC METHODS
996             #############################################################################
997              
998             =head1 Public Methods: Accessors
999              
1000             =cut
1001              
1002             #############################################################################
1003             # get_option()
1004             #############################################################################
1005              
1006             =head2 get_option()
1007              
1008             * Signature: $value = $context->get_option($var, $default);
1009             * Param: $var string
1010             * Param: $attribute string
1011             * Return: $value string
1012             * Throws:
1013             * Since: 0.01
1014              
1015             Sample Usage:
1016              
1017             $script_url_dir = $context->get_option("scriptUrlDir", "/cgi-bin");
1018              
1019             The get_option() returns the value of an Option variable
1020             (or the "default" value if not set).
1021              
1022             This is an alternative to
1023             getting the reference of the entire hash of Option
1024             variables with $self->options().
1025              
1026             =cut
1027              
1028             sub get_option {
1029 1 50   1 1 6 &App::sub_entry if ($App::trace);
1030 1         4 my ($self, $var, $default) = @_;
1031 1         4 my $value = $self->{options}{$var};
1032 1 50       5 $value = $default if (!defined $value);
1033 1 50       4 &App::sub_exit($value) if ($App::trace);
1034 1         4 return($value);
1035             }
1036              
1037             #############################################################################
1038             # get_user_option()
1039             #############################################################################
1040              
1041             =head2 get_user_option()
1042              
1043             * Signature: $value = $context->get_user_option($var);
1044             * Param: $var string
1045             * Return: $value string
1046             * Throws:
1047             * Since: 0.01
1048              
1049             Sample Usage:
1050              
1051             $theme = $context->get_user_option("theme");
1052             $lang = $context->get_user_option("lang");
1053              
1054             The get_user_option() returns the value of a user option variable.
1055             This is simply the $var attribute of the "default" session object
1056             (if it exists) or the $var attribute from the global options file.
1057              
1058             =cut
1059              
1060             sub get_user_option {
1061 0 0   0 1 0 &App::sub_entry if ($App::trace);
1062 0         0 my ($self, $var) = @_;
1063 0         0 my $value = $self->so_get($var);
1064 0 0       0 $value = $self->{options}{$var} if (!defined $value);
1065 0 0       0 &App::sub_exit($value) if ($App::trace);
1066 0         0 return($value);
1067             }
1068              
1069             #############################################################################
1070             # get_auth_attrib_value()
1071             #############################################################################
1072              
1073             =head2 get_auth_attrib_value()
1074              
1075             The get_auth_attrib_value() consults the "default" Authorization service to determine
1076             the "authorized" value of a service configuration's attribute.
1077              
1078             * Signature: $attrib_value = $self->get_auth_attrib_value($service_conf, $service_type, $service_name, $attrib);
1079             * Param: $service_conf HASH
1080             * Param: $service_type string
1081             * Param: $service_name string
1082             * Param: $attrib string
1083             * Return: $attrib_value ANY
1084             * Throws:
1085             * Since: 0.01
1086              
1087             Sample Usage:
1088              
1089             $service_type = "SessionObject";
1090             $service_name = "foo";
1091             $service_conf = $self->{conf}{$service_type}{$service_name};
1092             $clone_name = $self->get_auth_attrib_value($service_conf, $service_type, $service_name, "clone");
1093              
1094             =cut
1095              
1096             sub get_auth_attrib_value {
1097 0     0 1 0 my ($self, $service_conf, $service_type, $service_name, $attrib) = @_;
1098 0         0 my ($auth_value);
1099 0         0 my $auth_value_list = $service_conf->{"auth_$attrib"};
1100 0 0 0     0 if ($auth_value_list && ref($auth_value_list) eq "ARRAY") {
1101 0         0 my ($auth_key, $auth_name);
1102 0         0 my $auth = $self->authorization();
1103 0         0 for (my $i = 0; $i <= $#$auth_value_list; $i += 2) {
1104 0         0 $auth_name = $auth_value_list->[$i];
1105 0 0       0 if ($auth_name =~ m!^/!) {
1106 0         0 $auth_key = $auth_name;
1107             }
1108             else {
1109 0         0 $auth_key = "/App/$service_type/$service_name/$auth_name";
1110             }
1111 0 0       0 if ($auth->is_authorized($auth_key)) {
1112 0         0 $auth_value = $auth_value_list->[$i+1];
1113 0         0 last;
1114             }
1115             }
1116             }
1117 0 0       0 if (!$auth_value) {
1118 0         0 $auth_value = $service_conf->{$attrib};
1119             }
1120 0         0 return($auth_value);
1121             }
1122              
1123             #############################################################################
1124             # so_get()
1125             #############################################################################
1126              
1127             =head2 so_get()
1128              
1129             The so_get() returns the attribute of a session_object.
1130              
1131             * Signature: $value = $context->so_get($session_objectname, $attribute);
1132             * Signature: $value = $context->so_get($session_objectname, $attribute, $default);
1133             * Signature: $value = $context->so_get($session_objectname, $attribute, $default, $setdefault);
1134             * Param: $session_objectname string
1135             * Param: $attribute string
1136             * Param: $default any
1137             * Param: $setdefault boolean
1138             * Return: $value string,ref
1139             * Throws:
1140             * Since: 0.01
1141              
1142             Sample Usage:
1143              
1144             $cname = $context->so_get("default", "cname");
1145             $width = $context->so_get("main.app.toolbar.calc", "width");
1146              
1147             =cut
1148              
1149             sub so_get {
1150 5 50   5 1 954 &App::sub_entry if ($App::trace);
1151 5         17 my ($self, $name, $var, $default, $setdefault) = @_;
1152 5         8 my ($perl, $value);
1153              
1154 5 100 66     26 if (!defined $var || $var eq "") {
1155 4 50       43 if ($name =~ /^([a-zA-Z0-9_\.-]+)([\{\}\[\]].*)$/) {
    100          
1156 0         0 $name = $1;
1157 0         0 $var = $2;
1158             }
1159             elsif ($name =~ /^([a-zA-Z0-9_\.-]+)-([a-zA-Z0-9_]+)$/) {
1160 1         6 $name = $1;
1161 1         4 $var = $2;
1162             }
1163             else {
1164 3         4 $var = $name;
1165 3         8 $name = "default";
1166             }
1167             }
1168              
1169 5 50       16 if ($var !~ /[\[\]\{\}]/) { # no special chars, "foo-bar"
    0          
    0          
1170 5         18 my $cached_service = $self->{session}{cache}{SessionObject}{$name};
1171 5 100 66     33 if (!defined $cached_service || ref($cached_service) eq "HASH") {
1172 1         8 $cached_service = $self->session_object($name);
1173             }
1174 5         12 $value = $cached_service->{$var};
1175 5 50 33     59 if ((!defined $value || $value eq "") && defined $default) {
      33        
1176 0         0 $value = $default;
1177 0 0       0 if ($setdefault) {
1178 0         0 $self->{session}{store}{SessionObject}{$name}{$var} = $value;
1179 0         0 $self->{session}{cache}{SessionObject}{$name}{$var} = $value;
1180             }
1181             }
1182 5 50 33     17 $self->dbgprint("Context->so_get($name,$var) (value) = [$value]")
1183             if ($App::DEBUG && $self->dbg(3));
1184             }
1185             elsif ($var =~ /^\{([^\{\}]+)\}$/) { # a simple "{foo-bar}"
1186 0         0 $var = $1;
1187 0         0 $value = $self->{session}{cache}{SessionObject}{$name}{$var};
1188 0 0 0     0 if (!defined $value && defined $default) {
1189 0         0 $value = $default;
1190 0 0       0 if ($setdefault) {
1191 0         0 $self->{session}{store}{SessionObject}{$name}{$var} = $value;
1192 0         0 my $cached_service = $self->{session}{cache}{SessionObject}{$name};
1193 0 0 0     0 if (!defined $cached_service || ref($cached_service) eq "HASH") {
1194 0         0 $self->session_object($name);
1195             }
1196 0         0 $self->{session}{cache}{SessionObject}{$name}{$var} = $value;
1197             }
1198             }
1199 0 0 0     0 $self->dbgprint("Context->so_get($name,$var) (attrib) = [$value]")
1200             if ($App::DEBUG && $self->dbg(3));
1201             }
1202             elsif ($var =~ /^[\{\}\[\]].*$/) {
1203              
1204 0 0       0 $self->session_object($name) if (!defined $self->{session}{cache}{SessionObject}{$name});
1205              
1206 0         0 $var =~ s/\{([^\{\}]+)\}/\{"$1"\}/g;
1207 0         0 $perl = "\$value = \$self->{session}{cache}{SessionObject}{\$name}$var;";
1208 0         0 eval $perl;
1209 0 0       0 $self->add_message("eval [$perl]: $@") if ($@);
1210             #print STDERR "ERROR: Context->get($var): eval ($perl): $@\n" if ($@);
1211              
1212 0 0 0     0 $self->dbgprint("Context->so_get($name,$var) (indexed) = [$value]")
1213             if ($App::DEBUG && $self->dbg(3));
1214             }
1215 5 50       11 &App::sub_exit($value) if ($App::trace);
1216 5         29 return $value;
1217             }
1218              
1219             # This is a very low-level _so_get() suitable for use within the App-Context
1220             # framework. It requires you to separate $name and $var yourself.
1221              
1222             sub _so_get {
1223 0 0   0   0 &App::sub_entry if ($App::trace);
1224 0         0 my ($self, $name, $var, $default) = @_;
1225              
1226 0         0 my $value = $self->{session}{cache}{SessionObject}{$name}{$var};
1227 0 0       0 if (! defined $value) {
1228 0         0 $value = $self->{session}{store}{SessionObject}{$name}{$var};
1229 0 0       0 if (! defined $value) {
1230 0         0 $value = $self->{conf}{SessionObject}{$name}{$var};
1231             }
1232             }
1233              
1234 0 0       0 &App::sub_exit($value) if ($App::trace);
1235 0         0 return $value;
1236             }
1237              
1238             #############################################################################
1239             # so_set()
1240             #############################################################################
1241              
1242             =head2 so_set()
1243              
1244             The so_set() sets an attribute of a session_object in the Session.
1245              
1246             * Signature: $context->so_set($session_objectname, $attribute, $value);
1247             * Param: $session_objectname string
1248             * Param: $attribute string
1249             * Param: $value string,ref
1250             * Return: void
1251             * Throws:
1252             * Since: 0.01
1253              
1254             Sample Usage:
1255              
1256             $context->so_set("default", "cname", "main_screen");
1257             $context->so_set("main.app.toolbar.calc", "width", 50);
1258             $context->so_set("xyz", "{arr}[1][2]", 14);
1259             $context->so_set("xyz", "{arr.totals}", 14);
1260              
1261             =cut
1262              
1263             sub so_set {
1264 3 50   3 1 881 &App::sub_entry if ($App::trace);
1265 3         9 my ($self, $name, $var, $value) = @_;
1266              
1267 3         5 my ($perl, $retval);
1268              
1269 3 50       33 if ($value eq "{:delete:}") {
1270 0         0 $retval = $self->so_delete($name,$var);
1271             }
1272             else {
1273 3 50 33     12 $self->dbgprint("Context->so_set($name,$var,$value)")
1274             if ($App::DEBUG && $self->dbg(3));
1275              
1276 3 50 33     14 if (!defined $var || $var eq "") {
1277 3 50       57 if ($name =~ /^([a-zA-Z0-9_\.-]+)([\{\}\[\]].*)$/) {
    50          
1278 0         0 $name = $1;
1279 0         0 $var = $2;
1280             }
1281             elsif ($name =~ /^([a-zA-Z0-9_\.-]+)-([a-zA-Z0-9_]+)$/) {
1282 0         0 $name = $1;
1283 0         0 $var = $2;
1284             }
1285             else {
1286 3         7 $var = $name;
1287 3         8 $name = "default";
1288             }
1289             }
1290              
1291 3 50       11 if ($var !~ /[\[\]\{\}]/) { # no special chars, "foo-bar"
    0          
    0          
1292 3         21 $self->{session}{store}{SessionObject}{$name}{$var} = $value;
1293 3         13 $self->{session}{cache}{SessionObject}{$name}{$var} = $value;
1294             # ... we used to only set the cache attribute when the
1295             # object was already in the cache.
1296             # if (defined $self->{session}{cache}{SessionObject}{$name});
1297 3         6 $retval = 1;
1298             } # match {
1299             elsif ($var =~ /^\{([^\}]+)\}$/) { # a simple "{foo-bar}"
1300 0         0 $var = $1;
1301 0         0 $self->{session}{store}{SessionObject}{$name}{$var} = $value;
1302 0         0 $self->{session}{cache}{SessionObject}{$name}{$var} = $value;
1303             # ... we used to only set the cache attribute when the
1304             # object was already in the cache.
1305             # if (defined $self->{session}{cache}{SessionObject}{$name});
1306 0         0 $retval = 1;
1307             }
1308             elsif ($var =~ /^\{/) { # { i.e. "{columnSelected}{first_name}"
1309            
1310 0         0 $var =~ s/\{([^\}]+)\}/\{"$1"\}/g; # put quotes around hash keys
1311            
1312 0         0 $perl = "\$self->{session}{store}{SessionObject}{\$name}$var = \$value;";
1313 0 0       0 $perl .= "\$self->{session}{cache}{SessionObject}{\$name}$var = \$value;"
1314             if (defined $self->{session}{cache}{SessionObject}{$name});
1315            
1316 0         0 eval $perl;
1317 0 0       0 if ($@) {
1318 0         0 $self->add_message("eval [$perl]: $@");
1319 0         0 $retval = 0;
1320             }
1321             else {
1322 0         0 $retval = 1;
1323             }
1324             #die "ERROR: Context->so_set($name,$var,$value): eval ($perl): $@" if ($@);
1325             }
1326             # } else we do nothing with it!
1327             }
1328              
1329 3 50       8 &App::sub_exit($retval) if ($App::trace);
1330 3         9 return $retval;
1331             }
1332              
1333             #############################################################################
1334             # so_default()
1335             #############################################################################
1336              
1337             =head2 so_default()
1338              
1339             The so_default() sets the value of a SessionObject's attribute
1340             only if it is currently undefined.
1341              
1342             * Signature: $value = $context->so_default($session_objectname, $attribute);
1343             * Param: $session_objectname string
1344             * Param: $attribute string
1345             * Return: $value string,ref
1346             * Throws:
1347             * Since: 0.01
1348              
1349             Sample Usage:
1350              
1351             $cname = $context->so_default("default", "cname");
1352             $width = $context->so_default("main.app.toolbar.calc", "width");
1353              
1354             =cut
1355              
1356             sub so_default {
1357 0 0   0 1 0 &App::sub_entry if ($App::trace);
1358 0         0 my ($self, $name, $var, $default) = @_;
1359 0         0 $self->so_get($name, $var, $default, 1);
1360 0 0       0 &App::sub_exit() if ($App::trace);
1361             }
1362              
1363             #############################################################################
1364             # so_delete()
1365             #############################################################################
1366              
1367             =head2 so_delete()
1368              
1369             The so_delete() deletes an attribute of a session_object in the Session.
1370              
1371             * Signature: $context->so_delete($session_objectname, $attribute);
1372             * Param: $session_objectname string
1373             * Param: $attribute string
1374             * Return: void
1375             * Throws:
1376             * Since: 0.01
1377              
1378             Sample Usage:
1379              
1380             $context->so_delete("default", "cname");
1381             $context->so_delete("main-app-toolbar-calc", "width");
1382             $context->so_delete("xyz", "{arr}[1][2]");
1383             $context->so_delete("xyz", "{arr.totals}");
1384              
1385             =cut
1386              
1387             sub so_delete {
1388 0 0   0 1 0 &App::sub_entry if ($App::trace);
1389 0         0 my ($self, $name, $var) = @_;
1390 0         0 my ($perl);
1391              
1392 0 0 0     0 $self->dbgprint("Context->so_delete($name,$var)")
1393             if ($App::DEBUG && $self->dbg(3));
1394              
1395 0 0 0     0 if (!defined $var || $var eq "") {
1396 0 0       0 if ($name =~ /^([a-zA-Z0-9_\.-]+)([\{\}\[\]].*)$/) {
    0          
1397 0         0 $name = $1;
1398 0         0 $var = $2;
1399             }
1400             elsif ($name =~ /^([a-zA-Z0-9_\.-]+)-([a-zA-Z0-9_]+)$/) {
1401 0         0 $name = $1;
1402 0         0 $var = $2;
1403             }
1404             else {
1405 0         0 $var = $name;
1406 0         0 $name = "default";
1407             }
1408             }
1409              
1410 0 0       0 if ($var !~ /[\[\]\{\}]/) { # no special chars, "foo-bar"
    0          
    0          
1411 0         0 delete $self->{session}{store}{SessionObject}{$name}{$var};
1412 0 0       0 delete $self->{session}{cache}{SessionObject}{$name}{$var}
1413             if (defined $self->{session}{cache}{SessionObject}{$name});
1414             } # match {
1415             elsif ($var =~ /^\{([^\}]+)\}$/) { # a simple "{foo-bar}"
1416 0         0 $var = $1;
1417 0         0 delete $self->{session}{store}{SessionObject}{$name}{$var};
1418 0 0       0 delete $self->{session}{cache}{SessionObject}{$name}{$var}
1419             if (defined $self->{session}{cache}{SessionObject}{$name});
1420             }
1421             elsif ($var =~ /^\{/) { # { i.e. "{columnSelected}{first_name}"
1422              
1423 0         0 $var =~ s/\{([^\}]+)\}/\{"$1"\}/g; # put quotes around hash keys
1424              
1425             #$self->session_object($name) if (!defined $self->{session}{cache}{SessionObject}{$name});
1426              
1427 0         0 $perl = "delete \$self->{session}{store}{SessionObject}{\$name}$var;";
1428 0 0       0 $perl .= "delete \$self->{session}{cache}{SessionObject}{\$name}$var;"
1429             if (defined $self->{session}{cache}{SessionObject}{$name});
1430              
1431 0         0 eval $perl;
1432 0 0       0 $self->add_message("eval [$perl]: $@") if ($@);
1433             #die "ERROR: Context->so_delete($name,$var): eval ($perl): $@" if ($@);
1434             }
1435             # } else we do nothing with it!
1436 0 0       0 &App::sub_exit() if ($App::trace);
1437             }
1438              
1439             #############################################################################
1440             # substitute()
1441             #############################################################################
1442              
1443             =head2 substitute()
1444              
1445             The substitute() method substitutes values of SessionObjects into target strings.
1446              
1447             * Signature: $context->substitute($session_objectname, $attribute);
1448             * Param: $session_objectname string
1449             * Param: $attribute string
1450             * Return: void
1451             * Throws:
1452             * Since: 0.01
1453              
1454             Sample Usage:
1455              
1456             $context->substitute("default", "cname");
1457             $context->substitute("main.app.toolbar.calc", "width");
1458             $context->substitute("xyz", "{arr}[1][2]");
1459             $context->substitute("xyz", "{arr.totals}");
1460              
1461             =cut
1462              
1463             sub substitute {
1464 0 0   0 1 0 &App::sub_entry if ($App::trace);
1465 0         0 my ($self, $text, $values) = @_;
1466 0 0 0     0 $self->dbgprint("Context->substitute()")
1467             if ($App::DEBUG && $self->dbg(1));
1468 0         0 my ($phrase, $var, $value);
1469 0 0       0 $values = {} if (! defined $values);
1470              
1471 0 0       0 if (ref($text) eq "HASH") {
1472 0         0 my ($hash, $newhash);
1473 0         0 $hash = $text; # oops, not text, but a hash of text values
1474 0         0 $newhash = {}; # prepare a new hash for the substituted values
1475 0         0 foreach $var (keys %$hash) {
1476 0         0 $newhash->{$var} = $self->substitute($hash->{$var}, $values);
1477             }
1478 0         0 return($newhash); # short-circuit this whole process
1479             }
1480              
1481 0         0 while ( $text =~ /\[([^\[\]]+)\]/ ) {
1482 0         0 $phrase = $1;
1483 0         0 while ( $phrase =~ /\{([^\{\}]+)\}/ ) {
1484 0         0 $var = $1;
1485 0 0       0 if (defined $values->{$var}) {
1486 0         0 $value = $values->{$var};
1487 0         0 $phrase =~ s/\{$var\}/$value/g;
1488             }
1489             else {
1490 0 0       0 if ($var =~ /^(.+)\.([^.]+)$/) {
1491 0         0 $value = $self->so_get($1, $2);
1492 0 0       0 if (defined $value) {
1493 0         0 $phrase =~ s/\{$var\}/$value/g;
1494             }
1495             else {
1496 0         0 $phrase = "";
1497             }
1498             }
1499             else {
1500 0         0 $phrase = "";
1501             }
1502             }
1503             }
1504 0 0       0 if ($phrase eq "") {
1505 0         0 $text =~ s/\[[^\[\]]+\]\n?//; # zap it including (optional) ending newline
1506             }
1507             else {
1508 0         0 $text =~ s/\[[^\[\]]+\]/$phrase/;
1509             }
1510             }
1511 0         0 while ( $text =~ /\{([^\{\}]+)\}/ ) { # vars of the form {var}
1512 0         0 $var = $1;
1513 0 0       0 if (defined $values->{$var}) {
1514 0         0 $value = $values->{$var};
1515 0         0 $text =~ s/\{$var\}/$value/g;
1516             }
1517             else {
1518 0         0 $value = "";
1519 0 0       0 if ($var =~ /^(.+)\.([^.]+)$/) {
1520 0         0 $value = $self->so_get($1, $2);
1521             }
1522             }
1523 0 0       0 $value = "" if (!defined $value);
1524 0         0 $text =~ s/\{$var\}/$value/g;
1525             }
1526              
1527 0 0       0 &App::sub_exit($text) if ($App::trace);
1528 0         0 $text;
1529             }
1530              
1531             #############################################################################
1532             # PUBLIC METHODS
1533             #############################################################################
1534              
1535             =head1 Public Methods: Miscellaneous
1536              
1537             =cut
1538              
1539             #############################################################################
1540             # add_message()
1541             #############################################################################
1542              
1543             =head2 add_message()
1544              
1545             The add_message() method stores a string (the concatenated list of @args) in
1546             the Context until it can be viewed by and acted upon by the user.
1547              
1548             * Signature: $context->add_message($msg);
1549             * Param: $msg string [in]
1550             * Return: void
1551             * Throws:
1552             * Since: 0.01
1553              
1554             Sample Usage:
1555              
1556             $context->add_message("Data was not saved. Try again.");
1557              
1558             =cut
1559              
1560             sub add_message {
1561 0 0   0 1 0 &App::sub_entry if ($App::trace);
1562 0         0 my ($self, $msg) = @_;
1563              
1564 0 0       0 if (defined $self->{messages}) {
1565 0         0 $self->{messages} .= "\n" . $msg;
1566             }
1567             else {
1568 0         0 $self->{messages} = $msg;
1569             }
1570 0 0       0 &App::sub_exit() if ($App::trace);
1571             }
1572              
1573             sub get_messages {
1574 0 0   0 0 0 &App::sub_entry if ($App::trace);
1575 0         0 my ($self) = @_;
1576 0         0 my $msgs = $self->{messages};
1577 0 0       0 delete $self->{messages} if ($msgs);
1578 0 0       0 &App::sub_exit($msgs) if ($App::trace);
1579 0         0 return($msgs);
1580             }
1581              
1582             #############################################################################
1583             # log()
1584             #############################################################################
1585              
1586             =head2 log()
1587              
1588             The log() method writes a string (the concatenated list of @args) to
1589             the default log channel.
1590              
1591             * Signature: $context->log(@args);
1592             * Signature: $context->log($options, @args);
1593             * Param: $options HASH [in] (named)
1594             * Param: level integer
1595             * Param: @args string [in]
1596             * Return: void
1597             * Throws:
1598             * Since: 0.01
1599              
1600             Sample Usage:
1601              
1602             $context->log("oops, a bug happened");
1603              
1604             These are the standardized log levels.
1605              
1606             0 - Context logs nothing (absolutely silent) [???]
1607             1 - only application events [???]
1608             2 - [default] major system-level events [standard level for operations]
1609             3 - internal system-level events [standard level for development]
1610             4 - internal activities [standard level for debugging internals]
1611             5 - internal activities (inside loops) [extreme level for debugging internals]
1612              
1613             $self->log("ERROR: send_async_event_now(): node not assigned\n");
1614             $self->log($@);
1615              
1616             $self->log({level=>2},"Starting Cluster Node on $self->{host}:$self->{port}\n");
1617             $self->log({level=>2},"Stopping Cluster Node\n");
1618             $self->log({level=>2},"Starting Server on $self->{host}:$self->{port}\n");
1619             $self->log({level=>2},"Stopping Server.\n");
1620             $self->log({level=>2},"Starting Cluster Controller on $self->{host}:$self->{port}\n");
1621             $self->log({level=>2},"Stopping Cluster Controller\n");
1622              
1623             $self->log({level=>3},"Send Event: $service_type($name).$method(@args)\n");
1624             $self->log({level=>3},"Send Event: $method(@args)\n");
1625             $self->log({level=>3},"$service_type $name instantiated [$service]\n");
1626             $self->log({level=>3},"Schedule Event (" . join(",",%event) . ")\n";
1627             $self->log({level=>3},"Caught Signal: @_\n"); };
1628             $self->log({level=>3},"Caught Signal: @_\n"); };
1629             $self->log({level=>3},"Caught Signal: @_\n"); };
1630             $self->log({level=>3},"Caught Signal: @_ (quitting)\n"); $quit = 1; };
1631             $self->log({level=>3},"Caught Signal: @_ (quitting)\n"); $quit = 1; };
1632             $self->log({level=>3},"Caught Signal: @_ (quitting)\n"); $quit = 1; };
1633             $self->log({level=>3},"send_message($host, $port, $message)\n");
1634             $self->log({level=>3},"send_message($host, $port, ...) => [$response]\n");
1635             $self->log({level=>3},"process_msg($msg)\n");
1636             $self->log({level=>3},"process_msg: [$msg]\n");
1637             $self->log({level=>3},"process_msg($msg)\n");
1638              
1639             $self->log({level=>4},"Checking for scheduled events.\n");
1640             $self->log({level=>4},"Listening on socket: timeout($sleep_interval)\n");
1641             $self->log({level=>4},"Caught Signal: @_\n"); };
1642             $self->log({level=>4},"Listening on socket: timeout($sleep_interval)\n");
1643             $self->log({level=>4},"Child $pid finished [exitval=$exitval,sig=$sig]\n");
1644              
1645             $self->log({level=>5},"Checking event: time=$time [$event->{time}, every $event->{interval}] $event->{method}().\n");
1646             $self->log({level=>5},"Event Rescheduled: time=$time [$event->{time}, every $event->{interval}] $event->{method}().\n");
1647             $self->log({level=>5},"Event Removed: time=$time [$event->{time}, every $event->{interval}] $event->{method}().\n");
1648              
1649             =cut
1650              
1651             sub log {
1652 4 50   4 1 12 &App::sub_entry if ($App::trace);
1653 4         5 my $self = shift;
1654 4         4 my ($msg_options);
1655 4 50 33     41 $msg_options = shift if ($#_ > -1 && ref($_[0]) eq "HASH");
1656 4   50     13 my $msg_level = $msg_options->{level} || 1;
1657 4         8 my $log_level = $self->{options}{log_level};
1658 4 50       12 $log_level = 2 if (!defined $log_level);
1659 4 50 33     19 if (!defined $log_level || $msg_level <= $log_level) {
1660 0         0 $self->_log(@_);
1661             }
1662 4 50       11 &App::sub_exit() if ($App::trace);
1663             }
1664              
1665             sub _log {
1666 0 0   0   0 &App::sub_entry if ($App::trace);
1667 0         0 my $self = shift;
1668 0         0 my $hi_res = $self->{options}{log_hi_res};
1669 0         0 my $elapsed = $self->{options}{log_elapsed};
1670 0         0 my $timestamp;
1671 0 0       0 if ($hi_res) {
1672 0         0 App->use("Time::HiRes");
1673 0         0 my @timestuff = Time::HiRes::gettimeofday();
1674 0         0 $timestamp = time2str("%Y-%m-%d %H:%M:%S.", $timestuff[0]) . sprintf("%06d", $timestuff[1]);
1675 0 0       0 if ($elapsed) {
1676 0 0       0 if (!defined($self->{_last_log_elapsed_time})) {
1677 0         0 $self->{_last_log_elapsed_time} = \@timestuff;
1678             }
1679 0         0 my $elapsed = Time::HiRes::tv_interval($self->{_last_log_elapsed_time}, \@timestuff);
1680 0         0 $timestamp .= " " . sprintf("%.6f", $elapsed);
1681 0         0 $self->{_last_log_elapsed_time} = \@timestuff;
1682             }
1683             }
1684             else {
1685 0         0 my $time = time();
1686 0         0 $timestamp = time2str("%Y-%m-%d %H:%M:%S", $time);
1687 0 0       0 if ($elapsed) {
1688 0         0 my $elapsed = $time - $self->{_last_log_elapsed_time};
1689 0         0 $timestamp .= " " . $elapsed;
1690 0         0 $self->{_last_log_elapsed_time} = $time;
1691             }
1692             }
1693 0 0       0 if ($#_ > 0) {
    0          
1694 0         0 my $fmt = "[$$] $timestamp " . shift;
1695 0         0 printf STDERR $fmt, @_;
1696             }
1697             elsif ($#_ == 0) {
1698 0         0 print STDERR "[$$] $timestamp ", @_;
1699             }
1700 0 0       0 &App::sub_exit() if ($App::trace);
1701             }
1702              
1703             # NOTE: log rotation always passes an $overwrite = 0, thus implementing the rule
1704             # that log rotation should never overwrite a log file, but only append to it.
1705             sub log_file_open {
1706 6 50   6 0 25 &App::sub_entry if ($App::trace);
1707 6         15 my ($self, $overwrite) = @_;
1708 6         20 my $log_file = $self->{options}{log_file};
1709 6 50       29 if ($log_file) {
1710 0 0       0 if ($self->{log_fh}) {
1711 0         0 close($self->{log_fh});
1712 0         0 delete $self->{log_fh};
1713             }
1714 0 0       0 if ($log_file =~ /%/) {
1715 0         0 $log_file = time2str($log_file, time());
1716             }
1717 0 0 0     0 if ((defined $overwrite && $overwrite) || (!defined $overwrite && $self->{options}{log_overwrite})) {
      0        
      0        
1718 0 0       0 open(LOG, "> $log_file") || die "Unable to open $log_file log file: $!";
1719             }
1720             else {
1721 0 0       0 open(LOG, ">> $log_file") || die "Unable to open $log_file log file: $!";
1722             }
1723 0         0 open(STDOUT, ">&LOG");
1724 0         0 open(STDERR, ">&LOG");
1725 0         0 LOG->autoflush(1);
1726 0         0 STDOUT->autoflush(1);
1727 0         0 STDERR->autoflush(1);
1728 0         0 $self->{log_fh} = \*App::Context::LOG;
1729             }
1730 6 50       29 &App::sub_exit() if ($App::trace);
1731             }
1732              
1733             #############################################################################
1734             # user()
1735             #############################################################################
1736              
1737             =head2 user()
1738              
1739             The user() method returns the username of the authenticated user.
1740             The special name, "guest", refers to the unauthenticated (anonymous) user.
1741              
1742             * Signature: $username = $context->user();
1743             * Param: void
1744             * Return: string
1745             * Throws:
1746             * Since: 0.01
1747              
1748             Sample Usage:
1749              
1750             $username = $context->user();
1751              
1752             =cut
1753              
1754             sub user {
1755 0 0   0 1 0 &App::sub_entry if ($App::trace);
1756 0         0 my $self = shift;
1757 0   0     0 my $user = $self->{user} || "guest";
1758 0 0       0 &App::sub_exit($user) if ($App::trace);
1759 0         0 $user;
1760             }
1761              
1762             sub set_user {
1763 0 0   0 0 0 &App::sub_entry if ($App::trace);
1764 0         0 my ($self, $user) = @_;
1765 0         0 $self->{user} = $user;
1766 0 0       0 &App::sub_exit() if ($App::trace);
1767             }
1768              
1769             #############################################################################
1770             # options()
1771             #############################################################################
1772              
1773             =head2 options()
1774              
1775             * Signature: $options = $context->options();
1776             * Param: void
1777             * Return: $options {}
1778             * Throws:
1779             * Since: 0.01
1780              
1781             Sample Usage:
1782              
1783             $options = $context->options();
1784              
1785             The options() method returns a hashreference to all of the variable/value
1786             pairs used in the initialization of the Context.
1787              
1788             =cut
1789              
1790             sub options {
1791 0 0   0 1 0 &App::sub_entry if ($App::trace);
1792 0         0 my $self = shift;
1793 0   0     0 my $options = ($self->{options} || {});
1794 0 0       0 &App::sub_exit($options) if ($App::trace);
1795 0         0 return($options);
1796             }
1797              
1798             #############################################################################
1799             # conf()
1800             #############################################################################
1801              
1802             =head2 conf()
1803              
1804             * Signature: $conf = $context->conf();
1805             * Param: void
1806             * Return: $conf App::Conf
1807             * Throws:
1808             * Since: 0.01
1809              
1810             Sample Usage:
1811              
1812             $conf = $context->conf();
1813              
1814             The conf() method returns the user's conf data structure.
1815              
1816             =cut
1817              
1818             sub conf {
1819 2 50   2 1 7 &App::sub_entry if ($App::trace);
1820 2         5 my $self = shift;
1821 2 50       6 &App::sub_exit($self->{conf}) if ($App::trace);
1822 2         13 $self->{conf};
1823             }
1824              
1825             #############################################################################
1826             # session()
1827             #############################################################################
1828              
1829             =head2 session()
1830              
1831             * Signature: $session = $context->session();
1832             * Signature: $session = $context->session($session_id);
1833             * Param: $session_id string
1834             * Return: $session App::Session
1835             * Throws:
1836             * Since: 0.01
1837              
1838             Sample Usage:
1839              
1840             $session = $context->session();
1841             $session = $context->session("some_session_id");
1842              
1843             The session() method returns the current session (if no session_id is
1844             supplied). If a session_id is supplied, the requested session is
1845             instantiated if necessary and is returned.
1846              
1847             =cut
1848              
1849             sub session {
1850 7 50   7 1 29 &App::sub_entry if ($App::trace);
1851 7         23 my ($self, $session_id, $args) = @_;
1852 7         11 my ($session_class, $session, $options);
1853 7 100 66     57 $session_id = "default" if (! defined $session_id || $session_id eq "");
1854 7         20 $session = $self->{sessions}{$session_id};
1855 7 100       47 if (!$session) {
1856 6         15 $options = $self->{options};
1857 6   66     249 $session_class = $options->{session_class} || $self->_default_session_class();
1858              
1859 6         14 eval {
1860 6 50 33     29 $self->dbgprint("Context->new(): session_class=$session_class (", join(",",%$options), ")")
1861             if ($App::DEBUG && $self->dbg(1));
1862 6 50       26 if (defined $args) {
1863 0         0 $args = { %$args };
1864             }
1865             else {
1866 6         12 $args = {};
1867             }
1868 6         15 $args->{context} = $self;
1869 6         15 $args->{name} = $session_id;
1870 6         35 $session = App->new($session_class, "new", $args);
1871 6         34 $self->{sessions}{$session_id} = $session;
1872             };
1873 6 50       21 $self->add_message($@) if ($@);
1874             }
1875 7 50       24 &App::sub_exit($session) if ($App::trace);
1876 7         34 return($session);
1877             }
1878              
1879             #sub new_session_id {
1880             # &App::sub_entry if ($App::trace);
1881             # my ($self) = @_;
1882             # my $session_id = "user";
1883             # &App::sub_exit($session_id) if ($App::trace);
1884             # return($session_id);
1885             #}
1886              
1887             sub set_current_session {
1888 6 50   6 0 24 &App::sub_entry if ($App::trace);
1889 6         13 my ($self, $session_id) = @_;
1890 6 50 33     66 $session_id = "default" if (!defined $session_id || $session_id ne "");
1891 6         43 $self->{session} = $self->session($session_id);
1892 6 50       28 &App::sub_exit() if ($App::trace);
1893             }
1894              
1895             sub restore_default_session {
1896 0 0   0 0 0 &App::sub_entry if ($App::trace);
1897 0         0 my ($self) = @_;
1898 0         0 $self->{session} = $self->{sessions}{default};
1899 0 0       0 &App::sub_exit() if ($App::trace);
1900             }
1901              
1902             sub clear_session {
1903 0 0   0 0 0 &App::sub_entry if ($App::trace);
1904 0         0 my ($self, $session_id, @service_types) = @_;
1905 0 0 0     0 $session_id = "default" if (!defined $session_id || $session_id ne "");
1906 0         0 my $session = $self->{sessions}{$session_id};
1907 0 0       0 if ($#service_types == -1) {
1908              
1909 0         0 my %service_type_seen;
1910 0         0 foreach my $service_type (keys %{$session->{store}}) {
  0         0  
1911 0         0 $service_type_seen{$service_type} = 1;
1912 0         0 push (@service_types, $service_type);
1913             }
1914              
1915 0         0 foreach my $service_type (keys %{$session->{cache}}) {
  0         0  
1916 0 0       0 if (!$service_type_seen{$service_type}) {
1917 0         0 push (@service_types, $service_type);
1918             }
1919             }
1920             }
1921              
1922 0         0 foreach my $service_type (@service_types) {
1923 0 0       0 if ($service_type ne "SessionObject") {
1924 0         0 delete $session->{store}{$service_type};
1925 0         0 delete $session->{cache}{$service_type};
1926             }
1927             else {
1928 0         0 my $special_attrib = "ctype|cname|u|p|eu|theme";
1929 0         0 my ($services, $default_session_object);
1930 0         0 $services = $session->{store}{SessionObject};
1931 0 0       0 if ($services) {
1932 0         0 foreach my $so_name (keys %$services) {
1933 0 0       0 delete $services->{$so_name} if ($so_name ne "default");
1934             }
1935             }
1936 0         0 $services = $session->{cache}{SessionObject};
1937 0 0       0 if ($services) {
1938 0         0 foreach my $so_name (keys %$services) {
1939 0 0       0 delete $services->{$so_name} if ($so_name ne "default");
1940             }
1941             }
1942 0         0 $default_session_object = $session->{store}{SessionObject}{default};
1943 0 0       0 if ($default_session_object) {
1944 0         0 foreach my $attrib (keys %$default_session_object) {
1945 0 0       0 delete $default_session_object->{$attrib} if ($attrib !~ /^$special_attrib$/);
1946             }
1947             }
1948 0         0 $default_session_object = $session->{cache}{SessionObject}{default};
1949 0 0       0 if ($default_session_object) {
1950 0         0 foreach my $attrib (keys %$default_session_object) {
1951 0 0       0 delete $default_session_object->{$attrib} if ($attrib !~ /^$special_attrib$/);
1952             }
1953             }
1954             }
1955             }
1956              
1957             #else {
1958             # delete $self->{sessions}{$session_id};
1959             # if ($session eq $self->{session}) {
1960             # delete $self->{session};
1961             # $self->{session} = $self->session($session_id);
1962             # }
1963             #}
1964 0 0       0 &App::sub_exit() if ($App::trace);
1965             }
1966              
1967             #############################################################################
1968             # PUBLIC METHODS
1969             #############################################################################
1970              
1971             =head1 Public Methods: Debugging
1972              
1973             =cut
1974              
1975             sub state {
1976 0 0   0 0 0 &App::sub_entry if ($App::trace);
1977 0         0 my ($self) = @_;
1978              
1979 0         0 my $datetime = time2str("%Y-%m-%d %H:%M:%S", time());
1980 0         0 my $class = ref($self);
1981 0         0 my $state = "Context: [$class]\n[$datetime]\n";
1982 0         0 $state .= "\n";
1983 0         0 $state .= $self->_state();
1984              
1985 0 0       0 &App::sub_exit($state) if ($App::trace);
1986 0         0 return($state);
1987             }
1988              
1989             sub _state {
1990 0 0   0   0 &App::sub_entry if ($App::trace);
1991 0         0 my ($self) = @_;
1992              
1993 0         0 my $state = "";
1994              
1995 0         0 my ($event, @args, $args_str);
1996 0         0 $state .= "Scheduled Events:\n";
1997 0         0 foreach $event (@{$self->{scheduled_events}}) {
  0         0  
1998 0         0 @args = ();
1999 0 0       0 @args = @{$event->{args}} if ($event->{args});
  0         0  
2000 0         0 $args_str = join(",",@args);
2001 0         0 $state .= sprintf(" %19s %5s %-32s %s\n",
2002             time2str("%Y-%m-%d %H:%M:%S",$event->{time}),
2003             $event->{interval},
2004             $event->{tag},
2005             "$event->{name}.$event->{method}($args_str)");
2006             }
2007              
2008 0         0 $state .= "\n";
2009 0         0 $state .= "Event Loop Extensions:\n";
2010 0         0 my ($obj, $method, $args);
2011 0         0 foreach my $event_loop_extension (@{$self->{event_loop_extensions}}) {
  0         0  
2012 0         0 ($obj, $method, $args) = @$event_loop_extension;
2013 0         0 @args = ();
2014 0 0       0 @args = @$args if ($args);
2015 0         0 $args_str = join(",",@args);
2016 0         0 $state .= sprintf(" %s\n", "$obj->{name}.$method($args_str)");
2017             }
2018              
2019 0 0       0 &App::sub_exit($state) if ($App::trace);
2020 0         0 return($state);
2021             }
2022              
2023             #############################################################################
2024             # dbg()
2025             #############################################################################
2026              
2027             =head2 dbg()
2028              
2029             The dbg() method is used to check whether a given line of debug output
2030             should be generated.
2031             It returns true or false (1 or 0).
2032              
2033             If all three parameters are specified, this function
2034             returns true only when the global debug level ($App::Context::DEBUG)
2035             is at least equal to $level and when the debug scope
2036             is set to debug this class and method.
2037              
2038             * Signature: $flag = $context->dbg($class,$method,$level);
2039             * Param: $class class [in]
2040             * Param: $method string [in]
2041             * Param: $level integer [in]
2042             * Return: void
2043             * Throws: App::Exception::Context
2044             * Since: 0.01
2045              
2046             Sample Usage:
2047              
2048             $context->dbgprint("this is debug output")
2049             if ($App::DEBUG && $context->dbg(3));
2050              
2051             $context->dbgprint("this is debug output")
2052             if ($context->dbg(3));
2053              
2054             The first usage is functionally identical to the second, but the check
2055             of the global debug level explicitly reduces the runtime overhead to
2056             eliminate any method calls when debugging is not turned on.
2057              
2058             =cut
2059              
2060             my %debug_scope;
2061              
2062             sub dbg {
2063 0     0 1 0 my ($self, $level) = @_;
2064 0 0       0 return 0 if (! $App::DEBUG);
2065 0 0       0 $level = 1 if (!defined $level);
2066 0 0 0     0 return 0 if (defined $level && $App::DEBUG < $level);
2067 0         0 my ($debug_scope, $stacklevel);
2068 0         0 my ($package, $file, $line, $subroutine, $hasargs, $wantarray);
2069 0 0       0 $debug_scope = (ref($self) eq "") ? \%debug_scope : $self->{debug_scope};
2070 0         0 $stacklevel = 1;
2071 0         0 ($package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
2072 0   0     0 while (defined $subroutine && $subroutine eq "(eval)") {
2073 0         0 $stacklevel++;
2074 0         0 ($package, $file, $line, $subroutine, $hasargs, $wantarray) = caller($stacklevel);
2075             }
2076 0 0       0 return 1 if (! defined $debug_scope);
2077 0 0       0 return 1 if (! %$debug_scope);
2078 0 0       0 return 1 if (defined $debug_scope->{$package});
2079 0 0       0 return 1 if (defined $debug_scope->{$subroutine});
2080 0         0 return 0;
2081             }
2082              
2083             #############################################################################
2084             # dbgprint()
2085             #############################################################################
2086              
2087             =head2 dbgprint()
2088              
2089             The dbgprint() method is used to produce debug output.
2090             The output goes to an output stream which is appropriate for
2091             the runtime context in which it is called.
2092              
2093             * Signature: $flag = $context->dbgprint(@args);
2094             * Param: @args string [in]
2095             * Return: void
2096             * Throws: App::Exception::Context
2097             * Since: 0.01
2098              
2099             Sample Usage:
2100              
2101             $context->dbgprint("this is debug output")
2102             if ($App::DEBUG && $context->dbg(3));
2103              
2104             =cut
2105              
2106             sub dbgprint {
2107 0     0 1 0 my $self = shift;
2108 0 0       0 if (defined $App::options{debug_file}) {
2109 0         0 print $App::DEBUG_FILE $$, ": ", @_, "\n";
2110             }
2111             else {
2112 0         0 print STDERR "Debug: ", @_, "\n";
2113             }
2114             }
2115              
2116             #############################################################################
2117             # dbglevel()
2118             #############################################################################
2119              
2120             =head2 dbglevel()
2121              
2122             The dbglevel() method is used to set the debug level.
2123             Setting the dbglevel to 0 turns off debugging output and is suitable
2124             for production use. Setting the dbglevel to 1 or higher turns on
2125             increasingly verbose debug output.
2126              
2127             * Signature: $context->dbglevel($dbglevel);
2128             * Signature: $dbglevel = $context->dbglevel();
2129             * Param: $dbglevel integer
2130             * Return: $dbglevel integer
2131             * Throws: App::Exception::Context
2132             * Since: 0.01
2133              
2134             Sample Usage:
2135              
2136             $context->dbglevel(1); # turn it on
2137             $context->dbglevel(0); # turn it off
2138             $dbglevel = $context->dbglevel(); # get the debug level
2139              
2140             =cut
2141              
2142             sub dbglevel {
2143 0     0 1 0 my ($self, $dbglevel) = @_;
2144 0 0       0 $App::DEBUG = $dbglevel if (defined $dbglevel);
2145 0         0 return $App::DEBUG;
2146             }
2147              
2148             #############################################################################
2149             # debug_scope()
2150             #############################################################################
2151              
2152             =head2 debug_scope()
2153              
2154             The debug_scope() method is used to get the hash which determines which
2155             debug statements are to be printed out when the debug level is set to a
2156             positive number. It returns a hash reference. If class names or
2157             "class.method" names are defined in the hash, it will cause the
2158             debug statements from those classes or methods to be printed.
2159              
2160             * Signature: $debug_scope = $context->debug_scope();
2161             * Param: void
2162             * Return: $debug_scope {}
2163             * Throws: App::Exception::Context
2164             * Since: 0.01
2165              
2166             Sample Usage:
2167              
2168             $debug_scope = $context->debug_scope();
2169             $debug_scope->{"App::Context::CGI"} = 1;
2170             $debug_scope->{"App::Context::CGI.process_request"} = 1;
2171              
2172             =cut
2173              
2174             sub debug_scope {
2175 0     0 1 0 my $self = shift;
2176 0         0 my $debug_scope = $self->{debug_scope};
2177 0 0       0 if (!defined $debug_scope) {
2178 0         0 $debug_scope = {};
2179 0         0 $self->{debug_scope} = $debug_scope;
2180             }
2181 0         0 $debug_scope;
2182             }
2183              
2184             #############################################################################
2185             # dump()
2186             #############################################################################
2187              
2188             =head2 dump()
2189              
2190             * Signature: $perl = $context->dump();
2191             * Param: void
2192             * Return: $perl text
2193             * Throws: App::Exception
2194             * Since: 0.01
2195              
2196             Sample Usage:
2197              
2198             print $self->dump(), "\n";
2199              
2200             =cut
2201              
2202 6     6   70707 use Data::Dumper;
  6         33202  
  6         47022  
2203              
2204             sub dump {
2205 0     0 1 0 my ($self) = @_;
2206 0         0 my $d = Data::Dumper->new([ $self ], [ "context" ]);
2207 0         0 $d->Indent(1);
2208 0         0 return $d->Dump();
2209             }
2210              
2211             #############################################################################
2212             # PROTECTED METHODS
2213             #############################################################################
2214              
2215             =head1 Protected Methods
2216              
2217             These methods are considered protected because no class is ever supposed
2218             to call them. They may however be called by the context-specific drivers.
2219              
2220             =cut
2221              
2222             #############################################################################
2223             # dispatch_events()
2224             #############################################################################
2225              
2226             =head2 dispatch_events()
2227              
2228             * Signature: $context->dispatch_events()
2229             * Param: void
2230             * Return: void
2231             * Throws: App::Exception
2232             * Since: 0.01
2233              
2234             Sample Usage:
2235              
2236             $context->dispatch_events();
2237              
2238             The dispatch_events() method is called by the bootstrap environmental code
2239             in order to get the Context object rolling. It causes the program to block
2240             (wait on I/O), loop, or poll, in order to find events from the environment
2241             and dispatch them to the appropriate places within the App-Context framework.
2242              
2243             It is considered "protected" because no classes should be calling it.
2244              
2245             =cut
2246              
2247             sub dispatch_events {
2248 0 0   0 1 0 &App::sub_entry if ($App::trace);
2249 0         0 my ($self, $max_events_occurred) = @_;
2250              
2251 0         0 $self->dispatch_events_begin();
2252              
2253 0         0 my $events = $self->{events};
2254 0         0 my ($event, $service, $name, $method, $args);
2255 0         0 my $results = "";
2256 0         0 my $show_current_session_object = 1;
2257              
2258 0         0 eval {
2259 0         0 while ($#$events > -1) {
2260 0         0 $event = shift(@$events);
2261 0         0 ($service, $name, $method, $args) = @$event;
2262 0         0 $results = $self->call($service, $name, $method, $args);
2263 0         0 $show_current_session_object = 0;
2264             }
2265 0         0 my ($type, $name);
2266 0 0       0 if ($show_current_session_object) {
2267 0         0 $type = $self->so_get("default","ctype","SessionObject");
2268 0         0 $name = $self->so_get("default","cname","default");
2269             }
2270 0 0 0     0 if ($show_current_session_object && $type && $name) {
      0        
2271 0         0 $results = $self->service($type, $name);
2272             }
2273              
2274 0         0 $self->send_results($results);
2275             };
2276 0 0       0 if ($@) {
2277 0         0 $self->send_error($@);
2278             }
2279              
2280 0 0       0 if ($self->{options}{debug_context}) {
2281 0         0 print STDERR $self->dump();
2282             }
2283              
2284 0         0 $self->dispatch_events_finish();
2285 0 0       0 &App::sub_exit() if ($App::trace);
2286             }
2287              
2288             sub dispatch_events_begin {
2289 0 0   0 0 0 &App::sub_entry if ($App::trace);
2290 0         0 my ($self) = @_;
2291 0 0       0 &App::sub_exit() if ($App::trace);
2292             }
2293              
2294             sub dispatch_events_finish {
2295 0 0   0 0 0 &App::sub_entry if ($App::trace);
2296 0         0 my ($self) = @_;
2297 0         0 $self->shutdown(); # assume we won't be doing anything else (this can be overridden)
2298 0 0       0 &App::sub_exit() if ($App::trace);
2299             }
2300              
2301             sub extend_event_loop {
2302 0 0   0 0 0 &App::sub_entry if ($App::trace);
2303 0         0 my ($self, $obj, $method, $args) = @_;
2304 0 0       0 $args = [] if (!$args);
2305 0         0 push(@{$self->{event_loop_extensions}}, [ $obj, $method, $args ]);
  0         0  
2306 0 0       0 &App::sub_exit() if ($App::trace);
2307             }
2308              
2309             sub call {
2310 0 0   0 0 0 &App::sub_entry if ($App::trace);
2311 0         0 my ($self, $service_type, $name, $method, $args) = @_;
2312 0         0 my ($contents, $result, $service);
2313              
2314 0 0       0 if ($service_type eq "Context") {
2315 0         0 $service = $self;
2316             }
2317             else {
2318 0         0 $service = $self->service($service_type, $name);
2319             }
2320              
2321 0 0 0     0 if (!$service) {
    0 0        
    0          
2322 0         0 $result = "Service not defined: $service_type($name)\n";
2323             }
2324             elsif (!$service->isa("App::Widget") && $method && $service->can($method)) {
2325 0 0       0 my @args = (ref($args) eq "ARRAY") ? (@$args) : $args;
2326 0         0 my @results = $service->$method(@args);
2327 0 0       0 if ($#results == -1) {
    0          
2328 0         0 $result = $service->internals();
2329             }
2330             elsif ($#results == 0) {
2331 0         0 $result = $results[0];
2332             }
2333             else {
2334 0         0 $result = \@results;
2335             }
2336             }
2337             elsif ($service->can("handle_event")) {
2338 0 0       0 my @args = (ref($args) eq "ARRAY") ? (@$args) : $args;
2339 0         0 $result = $service->handle_event($name, $method, @args);
2340             }
2341             else {
2342 0 0       0 if ($method eq "contents") {
2343 0         0 $result = $service;
2344             }
2345             else {
2346 0         0 $result = "Method not defined on Service: $service($name).$method($args)\n";
2347             }
2348             }
2349 0 0       0 &App::sub_exit($result) if ($App::trace);
2350 0         0 return($result);
2351             }
2352              
2353             #############################################################################
2354             # send_results()
2355             #############################################################################
2356              
2357             =head2 send_results()
2358              
2359             * Signature: $context->send_results()
2360             * Param: void
2361             * Return: void
2362             * Throws: App::Exception
2363             * Since: 0.01
2364              
2365             Sample Usage:
2366              
2367             $context->send_results();
2368              
2369             =cut
2370              
2371             sub send_results {
2372 0 0   0 1 0 &App::sub_entry if ($App::trace);
2373 0         0 my ($self, $results) = @_;
2374              
2375 0         0 my ($serializer, $returntype);
2376              
2377 0 0       0 if (ref($results)) {
2378 0         0 $returntype = $self->{returntype};
2379 0         0 $serializer = $self->serializer($returntype);
2380 0         0 $results = $serializer->serialize($results);
2381             }
2382              
2383 0 0       0 if ($self->{messages}) {
2384 0         0 my $msg = $self->{messages};
2385 0         0 $self->{messages} = "";
2386 0         0 $msg =~ s/
/\n/g;
2387 0         0 print $msg, "\n";
2388             }
2389             else {
2390 0         0 print $results, "\n";
2391             }
2392 0 0       0 &App::sub_exit() if ($App::trace);
2393             }
2394              
2395             sub send_error {
2396 0 0   0 0 0 &App::sub_entry if ($App::trace);
2397 0         0 my ($self, $errmsg) = @_;
2398 0         0 print <
2399             -----------------------------------------------------------------------------
2400             AN ERROR OCCURRED in App::Context->dispatch_events()
2401             -----------------------------------------------------------------------------
2402             $errmsg
2403              
2404             -----------------------------------------------------------------------------
2405             Additional messages from earlier stages may be relevant if they exist below.
2406             -----------------------------------------------------------------------------
2407             $self->{messages}
2408             EOF
2409 0 0       0 &App::sub_exit() if ($App::trace);
2410             }
2411              
2412             #############################################################################
2413             # SCHEDULED EVENTS
2414             #############################################################################
2415              
2416             # valid attributes:
2417             # REQD: method => "do_it",
2418             # OPT: tag => "tag01", (identifies an event.)
2419             # OPT: service_type => "SessionObject", (method is on a SessionObject rather than on the Context)
2420             # OPT: name => "prog_controller",
2421             # OPT: time => time() + 600,
2422             # OPT: interval => 600,
2423             # OPT: args => [ 1, 2, 3 ],
2424             # OPT: scheduled => 0,
2425              
2426             sub schedule_event {
2427 0 0   0 0 0 &App::sub_entry if ($App::trace);
2428 0         0 my $self = shift;
2429 0         0 my %event = @_;
2430              
2431 0         0 my $scheduled_event = $self->{scheduled_event};
2432 0         0 my $scheduled_events = $self->{scheduled_events};
2433              
2434 0 0       0 if (! defined $event{time}) {
2435 0         0 $event{time} = time();
2436 0 0       0 $event{time} += $event{interval} if ($event{interval});
2437             }
2438              
2439 0         0 my $unschedule = 0;
2440 0 0       0 if (defined $event{scheduled}) {
2441 0         0 $unschedule = ! $event{scheduled};
2442 0         0 delete $event{scheduled};
2443             }
2444              
2445 0 0 0     0 die "schedule_event(): (tag or method) is a required attribute of an event" if (!$event{tag} && !$event{method});
2446 0         0 $self->log({level=>3},"Schedule Event (" . join(",",%event) . ")\n");
2447              
2448 0         0 my $event;
2449 0 0       0 if ($event{tag}) {
2450 0         0 $event = $scheduled_event->{$event{tag}};
2451             }
2452 0 0       0 if ($event) {
2453 0         0 foreach my $key (keys %event) {
2454 0         0 $event->{$key} = $event{$key};
2455             }
2456             }
2457             else {
2458 0 0       0 $scheduled_event->{$event{tag}} = \%event if ($event{tag});
2459 0         0 $event = \%event;
2460             }
2461              
2462 0 0       0 if ($event->{scheduled}) {
2463 0 0 0     0 if ($unschedule && $event->{tag}) {
2464             # remove from list of scheduled events
2465 0         0 for (my $i = $#$scheduled_events; $i >= 0; $i--) {
2466 0 0       0 if ($scheduled_events->[$i]{tag} eq $event->{tag}) {
2467 0         0 splice(@$scheduled_events, $i, 1); # remove the event
2468 0         0 $event->{scheduled} = 0;
2469 0         0 last;
2470             }
2471             }
2472             }
2473             }
2474             else {
2475 0 0       0 if (!$unschedule) {
2476 0         0 push(@$scheduled_events, $event);
2477 0         0 $event->{scheduled} = 1;
2478             }
2479             }
2480              
2481 0 0       0 &App::sub_exit() if ($App::trace);
2482             }
2483              
2484             sub get_current_events {
2485 0 0   0 0 0 &App::sub_entry if ($App::trace);
2486 0         0 my ($self, $events, $time) = @_;
2487 0 0       0 $time = time() if (!$time);
2488 0         0 my $time_of_next_event = 0;
2489 0         0 @$events = ();
2490 0         0 my $scheduled_event = $self->{scheduled_event};
2491 0         0 my $scheduled_events = $self->{scheduled_events};
2492 0         0 my $verbose = $self->{verbose};
2493 0         0 my ($event);
2494             # note: go in reverse order so that the splice() doesn't throw our indexes off
2495             # we do unshift() to keep events executing in FIFO order for a particular time
2496 0         0 for (my $i = $#$scheduled_events; $i >= 0; $i--) {
2497 0         0 $event = $scheduled_events->[$i];
2498 0         0 $self->log({level=>5},"Checking event: time=$time [$event->{time}, every $event->{interval}] $event->{method}().\n");
2499 0 0       0 if ($event->{time} <= $time) {
2500 0         0 unshift(@$events, $event);
2501 0 0 0     0 if ($event->{time} && $event->{interval}) {
2502 0         0 $event->{time} += $event->{interval}; # reschedule the event
2503 0         0 $self->log({level=>5},"Event Rescheduled: time=$time [$event->{time}, every $event->{interval}] $event->{method}().\n");
2504 0 0 0     0 if ($time_of_next_event == 0 || $event->{time} < $time_of_next_event) {
2505 0         0 $time_of_next_event = $event->{time};
2506             }
2507             }
2508             else {
2509 0         0 $self->log({level=>5},"Event Removed: time=$time [$event->{time}, every $event->{interval}] $event->{method}().\n");
2510 0         0 splice(@$scheduled_events, $i, 1); # remove the (one-time) event
2511 0         0 $event->{scheduled} = 0;
2512             }
2513             }
2514             else {
2515 0 0 0     0 if ($time_of_next_event == 0 || $event->{time} < $time_of_next_event) {
2516 0         0 $time_of_next_event = $event->{time};
2517             }
2518             }
2519             }
2520 0 0       0 &App::sub_exit($time_of_next_event) if ($App::trace);
2521 0         0 return($time_of_next_event);
2522             }
2523              
2524             # NOTE: send_event() is similar to call(). I ought to resolve this.
2525             sub send_event {
2526 4 50   4 0 1473 &App::sub_entry if ($App::trace);
2527 4         7 my ($self, $event) = @_;
2528 4         8 my $method = $event->{method};
2529 4 100       19 my @args = $event->{args} ? @{$event->{args}} : ();
  2         6  
2530 4         8 my $name = $event->{name};
2531 4         7 my $service_type = $event->{service_type};
2532 4 50 66     17 $service_type = "SessionObject" if (!$service_type && $name);
2533 4         5 my (@results);
2534 4 100       8 if ($name) {
2535 3         10 my $service = $self->service($service_type, $name);
2536 3         23 $self->log({level=>3},"Send Event: $service_type($name).$method(@args)\n");
2537 3         16 @results = $service->$method(@args);
2538             }
2539             else {
2540 1         34 $self->log({level=>3},"Send Event: $method(@args)\n");
2541 1         7 @results = $self->$method(@args);
2542             }
2543 4 50       17 &App::sub_exit(@results) if ($App::trace);
2544 4 50       10 if (wantarray()) {
2545 0         0 return(@results);
2546             }
2547             else {
2548 4 50       13 if ($#results == -1) {
    50          
2549 0         0 return(undef);
2550             }
2551             elsif ($#results == 0) {
2552 4         16 return($results[0]);
2553             }
2554             else {
2555 0         0 return(\@results);
2556             }
2557             }
2558             }
2559              
2560             # NOTE: The baseline context implements the API for asynchronous events
2561             # in a simplistic, sequential way.
2562             # It merely sends the event, then sends the callback event.
2563             # See App::Context::Server for a context that spawns processes which
2564             # execute the event. When the process exits, the callback_event is fired.
2565             # See App::Context::Cluster for a context that sends a message to an
2566             # available cluster node for executing. When the node reports back that
2567             # it has completed the task, the callback_event is fired.
2568              
2569             sub send_async_event {
2570 1 50   1 0 571 &App::sub_entry if ($App::trace);
2571 1         3 my ($self, $event, $callback_event) = @_;
2572 1         8 my $event_token = $self->send_async_event_in_process($event, $callback_event);
2573 1 50       5 &App::sub_exit($event_token) if ($App::trace);
2574 1         4 return($event_token);
2575             }
2576              
2577             sub send_async_event_in_process {
2578 1 50   1 0 6 &App::sub_entry if ($App::trace);
2579 1         3 my ($self, $event, $callback_event) = @_;
2580 1         1 my $errnum = 0;
2581 1         3 my $errmsg = "";
2582 1         4 my $event_token = "local-$$";
2583 1         2 my ($returnval);
2584 1         2 eval {
2585 1         3 $returnval = $self->send_event($event);
2586             };
2587 1 50       5 if ($@) {
2588 0         0 $errmsg = $@;
2589 0         0 $errnum = 1;
2590 0         0 $self->log("ERROR: send_async_event_now() $event->{name}.$event->{method} : $errmsg\n");
2591             }
2592 1 50       4 if ($callback_event) {
2593 1 50       6 $callback_event->{args} = [] if (! $callback_event->{args});
2594 1         3 push(@{$callback_event->{args}}, {event_token => $event_token, returnval => $returnval, errnum => $errnum, errmsg => $errmsg});
  1         8  
2595 1         5 $self->send_event($callback_event);
2596             }
2597 1 50       5 &App::sub_exit($event_token) if ($App::trace);
2598 1         4 return($event_token);
2599             }
2600              
2601             =head2 wait_for_event()
2602              
2603             * Signature: $self->wait_for_event($event_token)
2604             * Param: $event_token string
2605             * Return: void
2606             * Throws: App::Exception
2607             * Since: 0.01
2608              
2609             Sample Usage:
2610              
2611             $self->wait_for_event($event_token);
2612              
2613             The wait_for_event() method is called when an asynchronous event has been
2614             sent and no more processing can be completed before it is done.
2615              
2616             =cut
2617              
2618             sub wait_for_event {
2619 0 0   0 1 0 &App::sub_entry if ($App::trace);
2620 0         0 my ($self, $event_token) = @_;
2621 0 0       0 &App::sub_exit() if ($App::trace);
2622             }
2623              
2624             # NOTE: This send_message() and send_async_message() can be on the App::Context
2625             # class to allow a program in any context to send this kind of message.
2626             # (The only downside is a dependency on IO::Socket::INET.)
2627             sub send_async_message {
2628 0 0   0 0 0 &App::sub_entry if ($App::trace);
2629 0         0 my ($self, $host, $port, $message, $await_return_value, $timeout, $server_close) = @_;
2630 0         0 my $pid = $self->fork();
2631 0 0       0 if (!$pid) { # running in child
2632 0         0 $self->send_message($host, $port, $message, $await_return_value, $timeout, $server_close);
2633 0         0 $self->exit(0);
2634             }
2635 0 0       0 &App::sub_exit() if ($App::trace);
2636             }
2637              
2638             # NOTE: $messages that start with "RV-" wait for a return value.
2639             # $messages that start with "SC-" force the server to close the socket first
2640             # This is to help manage which system has the sockets lingering in TIME_WAIT state.
2641             # Here is the truth table for $await_return_value, $server_close
2642             # $await_return_value $server_close = client + server
2643             # ------------------- ------------- ---------------------- ---------------------
2644             # 0 0 write/close read/close
2645             # 0 1 write/read/close read/close
2646             # 1 0 write/read/write/close read/write/read/close
2647             # 1 1 write/read/close read/write/close
2648             sub send_message {
2649 0 0   0 0 0 &App::sub_entry if ($App::trace);
2650 0         0 my ($self, $host, $port, $message, $await_return_value, $timeout, $server_close) = @_;
2651 0         0 my $verbose = $self->{verbose};
2652              
2653 0 0 0     0 if (!$port && $host =~ /^([^:]+):([0-9]+)$/) {
2654 0         0 $host = $1;
2655 0         0 $port = $2;
2656             }
2657              
2658 0         0 my $send_socket = IO::Socket::INET->new(
2659             PeerAddr => $host,
2660             PeerPort => $port,
2661             Proto => "tcp",
2662             Type => SOCK_STREAM,
2663             ReuseAddr => 1,
2664             );
2665 0         0 my ($send_fd);
2666 0 0       0 $send_fd = fileno($send_socket) if ($send_socket);
2667 0         0 $self->log({level=>3},"($send_fd) send_message($host, $port, $message)\n");
2668              
2669 0         0 my $response = "";
2670 0 0       0 my $rv = $await_return_value ? "RV-" : "";
2671 0 0       0 my $sc = $server_close ? "SC-" : "";
2672 0 0       0 if ($send_socket) {
2673 0         0 eval {
2674 0 0 0     0 $send_socket->autoflush(1) if ($await_return_value || $server_close);
2675 0         0 $send_socket->print("$rv$sc$message\n");
2676 0 0 0     0 if ($await_return_value || $server_close) {
2677             # $send_socket->timeout($timeout) if ($timeout); # doesn't seem to work
2678 0         0 $response = $send_socket->getline();
2679 0         0 $response =~ s/[\r\n]+$//;
2680 0 0 0     0 $send_socket->print("EOF\n") if ($await_return_value && !$server_close);
2681             }
2682 0         0 close($send_socket);
2683             };
2684 0 0       0 if ($@) {
2685 0         0 $response = "SEND ERROR: $@";
2686             }
2687             }
2688             else {
2689 0         0 $response = "CONNECT ERROR: $!";
2690             }
2691              
2692 0         0 $self->log({level=>3},"send_message($host, $port, ...) => [$response]\n");
2693 0 0       0 &App::sub_exit($response) if ($App::trace);
2694 0         0 return($response);
2695             }
2696              
2697             =head2 fork()
2698              
2699             * Signature: $pid = $self->fork()
2700             * Param: void
2701             * Return: $pid integer
2702             * Throws: App::Exception
2703             * Since: 0.01
2704              
2705             Sample Usage:
2706              
2707             $self->fork();
2708              
2709             The fork() method is called in a child process just after
2710             it has been fork()ed.
2711             This causes connections to databases, etc. to be closed gracefully and new
2712             connections to be created if necessary.
2713              
2714             Call this after a fork() in the child process.
2715             It will shut down the resources which cannot be shared between a parent and
2716             a child process.
2717              
2718             Currently, this is primarily for database connections.
2719             For most databases, the child needs its own connection.
2720              
2721             =cut
2722              
2723             sub fork {
2724 0 0   0 1 0 &App::sub_entry if ($App::trace);
2725 0         0 my ($self) = @_;
2726 0         0 my $pid = fork();
2727 0 0       0 if (!$pid) { # in the child process
2728             # $self->{is_child} = 1; # I might need to add this sometime, but not now
2729 0         0 $self->shutdown_unshareable_resources();
2730             }
2731             else {
2732 0         0 $self->log({level=>4},"Child $pid started.\n");
2733             }
2734 0 0       0 &App::sub_exit($pid) if ($App::trace);
2735 0         0 return($pid);
2736             }
2737              
2738             sub exit {
2739 0     0 0 0 my ($self, $exitval) = @_;
2740 0         0 $self->shutdown();
2741 0         0 exit($exitval);
2742             }
2743              
2744             #############################################################################
2745             # shutdown_unshareable_resources()
2746             #############################################################################
2747              
2748             =head2 shutdown_unshareable_resources()
2749              
2750             * Signature: $self->shutdown_unshareable_resources()
2751             * Param: void
2752             * Return: void
2753             * Throws: App::Exception
2754             * Since: 0.01
2755              
2756             Sample Usage:
2757              
2758             $self->shutdown_unshareable_resources();
2759              
2760             The shutdown_unshareable_resources() method is called in a child process just after
2761             it has been fork()ed.
2762             This causes connections to databases, etc. to be closed gracefully and new
2763             connections to be created if necessary.
2764              
2765             Call this after a fork() in the child process.
2766             It will shutdown_unshareable which cannot be shared between a parent and
2767             a child process.
2768              
2769             Currently, this is primarily for database connections.
2770             For most databases, the child needs its own connection.
2771              
2772             =cut
2773              
2774             sub shutdown_unshareable_resources {
2775 0     0 1 0 my $self = shift;
2776 0         0 my ($conf, $repdef, $repname, $instance);
2777 0         0 my ($class, $method, $args, $argidx, $repcache);
2778              
2779 0 0 0     0 $self->dbgprint("Context->shutdown_unshareable_resources()")
2780             if ($App::DEBUG && $self->dbg(1));
2781              
2782 0         0 $repcache = $self->{session}{cache}{Repository};
2783 0 0 0     0 if (defined $repcache && ref($repcache) eq "HASH") {
2784 0         0 foreach $repname (keys %$repcache) {
2785 0         0 $instance = $repcache->{$repname};
2786 0         0 $instance->_shutdown_unshareable_resources();
2787 0         0 delete $repcache->{$repname};
2788             }
2789             }
2790             }
2791              
2792             #############################################################################
2793             # shutdown()
2794             #############################################################################
2795              
2796             =head2 shutdown()
2797              
2798             The shutdown() method is called when the Context is preparing to exit.
2799             This allows for connections to databases, etc. to be closed gracefully.
2800              
2801             * Signature: $self->shutdown()
2802             * Param: void
2803             * Return: void
2804             * Throws: App::Exception
2805             * Since: 0.01
2806              
2807             Sample Usage:
2808              
2809             $self->shutdown();
2810              
2811             =cut
2812              
2813             sub shutdown {
2814 1     1 1 2 my ($self, $end_cd) = @_;
2815 1         2 my ($conf, $repdef, $repname, $instance);
2816 0         0 my ($class, $method, $args, $argidx, $repcache);
2817              
2818 1 50       5 if (!$self->{shutdown_complete}) {
2819 1         2 my $options = $self->{options};
2820 1         2 my $profiler = $options->{"app.Context.profiler"};
2821 1 50       4 if ($profiler) {
2822 0         0 $self->profile_stop("main");
2823 0         0 $self->finish_profiler_log($end_cd);
2824             }
2825              
2826 1 50 33     4 $self->dbgprint("Context->shutdown()")
2827             if ($App::DEBUG && $self->dbg(1));
2828              
2829 1         5 $repcache = $self->{session}{cache}{Repository};
2830 1 50 33     4 if (defined $repcache && ref($repcache) eq "HASH") {
2831 0         0 foreach $repname (keys %$repcache) {
2832 0         0 $instance = $repcache->{$repname};
2833            
2834 0 0 0     0 $self->dbgprint("Context->shutdown(): $instance->_disconnect()")
2835             if ($App::DEBUG && $self->dbg(1));
2836            
2837 0         0 $instance->_disconnect();
2838 0         0 delete $repcache->{$repname};
2839             }
2840             }
2841 1         4 $self->{shutdown_complete} = 1;
2842             }
2843             }
2844              
2845             sub DESTROY {
2846 0     0   0 my ($self) = @_;
2847 0         0 $self->shutdown("D");
2848             }
2849              
2850             #############################################################################
2851             # response()
2852             #############################################################################
2853              
2854             =head2 response()
2855              
2856             * Signature: $context->response()
2857             * Param: void
2858             * Return: void
2859             * Throws: App::Exception
2860             * Since: 0.01
2861              
2862             Sample Usage:
2863              
2864             $context->response();
2865              
2866             The response() method gets the current Response being handled in the Context.
2867              
2868             =cut
2869              
2870             sub response {
2871 0 0   0 1 0 &App::sub_entry if ($App::trace);
2872 0         0 my $self = shift;
2873              
2874 0         0 my $response = $self->{response};
2875 0 0       0 if (!defined $response) {
2876              
2877             #################################################################
2878             # RESPONSE
2879             #################################################################
2880              
2881 0         0 my $response_class = $self->get_option("response_class", "App::Response");
2882              
2883 0         0 eval {
2884 0         0 $response = App->new($response_class, "new", $self, $self->{options});
2885             };
2886 0         0 $self->{response} = $response;
2887 0 0       0 $self->add_message("Context::response(): $@") if ($@);
2888             }
2889              
2890 0 0       0 &App::sub_exit($response) if ($App::trace);
2891 0         0 return($response);
2892             }
2893              
2894             #############################################################################
2895             # CONTROLLING THE profiler_log
2896             #############################################################################
2897              
2898             sub init_profiler_log {
2899 5 50   5 0 16 &App::sub_entry if ($App::trace);
2900 5         10 my ($self) = @_;
2901 5         12 my $options = $self->{options};
2902 5         11 my $profiler = $options->{"app.Context.profiler"};
2903 5 50       17 if ($profiler) {
2904 0         0 $self->profile_start("main");
2905 0         0 $self->start_profiler_log();
2906             }
2907 5 50       23 &App::sub_exit() if ($App::trace);
2908             }
2909              
2910             sub start_profiler_log {
2911 0 0   0 0   &App::sub_entry if ($App::trace);
2912 0           my ($self) = @_;
2913              
2914 0           my $profile_state = $self->{profile_state};
2915 0 0         if (!$profile_state) {
2916 0           $self->profile_start("main");
2917 0           $profile_state = $self->{profile_state};
2918             }
2919 0           my $options = $self->{options};
2920 0   0       my $app = $options->{app} || "app";
2921 0           my $context_abbr = ref($self);
2922 0           $context_abbr =~ s/^App::Context:://;
2923 0   0       my $host = $options->{host} || "localhost";
2924 0           my $username = $self->user();
2925 0           my $events = $self->{events};
2926 0           my $events_str = "";
2927 0 0 0       if ($events && $#$events > -1) {
2928 0           $events_str .= ($#$events + 1);
2929 0           foreach my $event (@$events) {
2930 0           $events_str .= ":$event->[1].$event->[2]";
2931 0 0 0       if ($event->[3] && $#{$event->[3]} > -1) {
  0            
2932 0           $events_str .= "(" . join(",",@{$event->[3]}) . ")";
  0            
2933             }
2934             }
2935             }
2936 0   0       my $time = $profile_state->{last_timeofday}[0] || time();
2937 0           my $start_dttm = time2str("%Y-%m-%d %H:%M:%S", $time);
2938 0           my $info = $self->get_proc_info2();
2939 0           my $pinfo = $info->{$$};
2940 0           my $start_mem_mb = $pinfo->{vsize}/1048576;
2941              
2942 0           my $repname = $options->{"app.Context.profiler_repository"};
2943 0 0         my $rep = $repname ? $self->repository($repname) : undef;
2944              
2945 0 0         if ($rep) {
2946 0           eval {
2947 0 0         if (!$profile_state->{profiler_log_id}) {
2948 0           $rep->insert("app_profiler_log",
2949             ["context", "host", "username", "app", "start_dttm", "start_mem_mb", "events"],
2950             [$context_abbr, $host, $username, $app, $start_dttm, $start_mem_mb, $events_str],
2951             { last_inserted_id => 1 });
2952 0           $profile_state->{profiler_log_id} = $rep->last_inserted_id();
2953             }
2954             };
2955             }
2956             else {
2957 0           $self->log("Start : (Mem %.1f MB) %s [%s\@%s:%s]\n", $start_mem_mb, $context_abbr, $username, $host, $app);
2958             }
2959              
2960 0 0         &App::sub_exit() if ($App::trace);
2961             }
2962              
2963             sub update_profiler_log {
2964 0 0   0 0   &App::sub_entry if ($App::trace);
2965 0           my ($self, $app_scope, $content_name, $app_scope_id_type, $app_scope_id) = @_;
2966              
2967 0           my $options = $self->{options};
2968 0           my $repname = $options->{"app.Context.profiler_repository"};
2969 0 0         my $rep = $repname ? $self->repository($repname) : undef;
2970              
2971 0           my $profile_state = $self->{profile_state};
2972 0           my $profiler_log_id = $profile_state->{profiler_log_id};
2973              
2974 0 0         if (defined $app_scope) {
    0          
2975 0           $profile_state->{app_scope} = $app_scope;
2976             }
2977             elsif (defined $profile_state->{app_scope}) {
2978 0           $app_scope = $profile_state->{app_scope};
2979             }
2980              
2981 0 0         if (defined $app_scope_id) {
    0          
2982 0           $profile_state->{app_scope_id} = $app_scope_id;
2983 0           $profile_state->{app_scope_id_type} = $app_scope_id_type;
2984             }
2985             elsif (defined $profile_state->{app_scope_id}) {
2986 0           $app_scope_id = $profile_state->{app_scope_id};
2987 0           $app_scope_id_type = $profile_state->{app_scope_id_type};
2988             }
2989              
2990 0 0         if (defined $content_name) {
    0          
2991 0           $profile_state->{content_name} = $content_name;
2992             }
2993             elsif (defined $profile_state->{content_name}) {
2994 0           $content_name = $profile_state->{content_name};
2995             }
2996              
2997 0 0         if ($rep) {
2998 0 0         if ($profiler_log_id) {
2999 0           eval {
3000 0           $rep->update("app_profiler_log", { "profiler_log_id.eq" => $profiler_log_id },
3001             ["app_scope", "content_name", "app_scope_id_type", "app_scope_id"],
3002             [$app_scope, $content_name, $app_scope_id_type, $app_scope_id]);
3003             };
3004             }
3005             }
3006             else {
3007 0           $self->log("Update: %s [%s] (%s:%s)\n", $app_scope, $content_name, $app_scope_id_type, $app_scope_id);
3008             }
3009              
3010 0 0         &App::sub_exit() if ($App::trace);
3011             }
3012              
3013             sub finish_profiler_log {
3014 0 0   0 0   &App::sub_entry if ($App::trace);
3015 0           my ($self, $end_cd) = @_;
3016              
3017 0   0       $end_cd ||= "F"; # assume we finish using normal processing
3018 0           my $profile_state = $self->{profile_state};
3019 0           my $profile_stats = $self->profile_stats();
3020 0           my $profiler_log_id = $profile_state->{profiler_log_id};
3021 0           my $app_scope = $profile_state->{app_scope};
3022 0           my $content_name = $profile_state->{content_name};
3023 0           my $app_scope_id_type = $profile_state->{app_scope_id_type};
3024 0           my $app_scope_id = $profile_state->{app_scope_id};
3025 0   0       my $content_length = $profile_state->{content_length} || 0;
3026              
3027 0   0       my $time = $profile_state->{last_timeofday}[0] || time();
3028 0           my $end_dttm = time2str("%Y-%m-%d %H:%M:%S", $time);
3029 0   0       my $run_main_time = $profile_stats->{main}{cumul_time} || 0; # DONE
3030 0   0       my $run_event_time = $profile_stats->{event}{cumul_time} || 0; # DONE:HTTP, TBD:Context
3031 0   0       my $run_db_time = $profile_stats->{db}{cumul_time} || 0; # DONE
3032 0   0       my $run_file_time = $profile_stats->{file}{cumul_time} || 0; # TBD (application)
3033 0   0       my $run_net_time = $profile_stats->{net}{cumul_time} || 0; # TBD (application)
3034              
3035 0           my $run_aux1_label = $profile_state->{aux1_label};
3036 0           my $run_aux2_label = $profile_state->{aux2_label};
3037 0           my ($run_aux1_time, $run_aux2_time);
3038 0 0 0       $run_aux1_time = $run_aux1_label ? ($profile_stats->{$run_aux1_label}{cumul_time} || 0) : 0;
3039 0 0 0       $run_aux2_time = $run_aux2_label ? ($profile_stats->{$run_aux2_label}{cumul_time} || 0) : 0;
3040 0   0       my $run_xfer_time = $profile_stats->{xfer}{cumul_time} || 0; # DONE
3041 0   0       my $num_net_calls = $profile_stats->{net}{count} || 0; # DONE
3042 0   0       my $num_db_calls = $profile_stats->{db}{count} || 0; # DONE
3043 0   0       my $num_db_rows_read = $profile_stats->{db}{nrows_read} || 0; # DONE
3044 0   0       my $num_db_rows_write = $profile_stats->{db}{nrows_write} || 0; # DONE
3045 0           my $info = $self->get_proc_info2();
3046 0           my $pinfo = $info->{$$};
3047 0           my $end_mem_mb = $pinfo->{vsize}/1048576;
3048 0   0       my $cpu_time = ($pinfo->{cutime} + $pinfo->{cstime}) || 0;
3049 0           my $run_time = $self->profile_run_time();
3050 0           my $run_other_time = $run_time - ($run_event_time + $run_main_time + $run_db_time + $run_file_time + $run_net_time + $run_xfer_time + $run_aux1_time + $run_aux2_time);
3051 0 0         $run_other_time = 0 if ($run_other_time < 1e-6);
3052              
3053 0           my $options = $self->{options};
3054 0           my $repname = $options->{"app.Context.profiler_repository"};
3055 0 0         my $rep = $repname ? $self->repository($repname) : undef;
3056              
3057 0 0         if ($rep) {
3058 0 0         if ($profiler_log_id) {
3059 0           eval {
3060 0           $rep->update("app_profiler_log", { "profiler_log_id.eq" => $profiler_log_id },
3061             ["app_scope", "content_name", "app_scope_id_type", "app_scope_id",
3062             "end_cd", "end_dttm", "end_mem_mb", "cpu_time",
3063             "run_time", "run_main_time", "run_event_time",
3064             "run_db_time", "run_file_time", "run_net_time",
3065             "run_aux1_label", "run_aux2_label",
3066             "run_aux1_time", "run_aux2_time", "run_other_time",
3067             "run_xfer_time", "content_length", "num_net_calls",
3068             "num_db_calls", "num_db_rows_read", "num_db_rows_write"],
3069             [$app_scope, $content_name, $app_scope_id_type, $app_scope_id,
3070             $end_cd, $end_dttm, $end_mem_mb, $cpu_time,
3071             $run_time, $run_main_time, $run_event_time,
3072             $run_db_time, $run_file_time, $run_net_time,
3073             $run_aux1_label, $run_aux2_label,
3074             $run_aux1_time, $run_aux2_time, $run_other_time,
3075             $run_xfer_time, $content_length, $num_net_calls,
3076             $num_db_calls, $num_db_rows_read, $num_db_rows_write]);
3077             };
3078 0           delete $profile_state->{profiler_log_id};
3079             }
3080             }
3081             else {
3082 0           my $aux_fmt = "";
3083 0           my (@aux_values);
3084 0 0         if ($run_aux1_label) {
3085 0           $aux_fmt .= " $run_aux1_label=%.2f";
3086 0           push(@aux_values, $run_aux1_time);
3087             }
3088 0 0         if ($run_aux2_label) {
3089 0           $aux_fmt .= " $run_aux2_label=%.2f";
3090 0           push(@aux_values, $run_aux2_time);
3091             }
3092 0           $self->log("Finish: (Mem %.1f MB) cpu=%.2f run=%.2f run[main=%.2f event=%.2f db=%.2f/%d(r%d:w%d) file=%.2f net=%.2f/%d${aux_fmt} other=%.2f xfer=%.2f] (Content %s bytes)\n",
3093             $end_mem_mb, $cpu_time, $run_time, $run_main_time, $run_event_time,
3094             $run_db_time, $num_db_calls, $num_db_rows_read, $num_db_rows_write, $run_file_time, $run_net_time, $num_net_calls,
3095             @aux_values, $run_other_time, $run_xfer_time, $content_length);
3096             }
3097              
3098 0 0         &App::sub_exit() if ($App::trace);
3099             }
3100              
3101             #############################################################################
3102             # PROFILING
3103             # $context->profile_start($key, $replace);
3104             # $context->profile_stop($key);
3105             # $context->profile_run_time();
3106             # $context->profile_stats();
3107             # $context->profile_clear();
3108             # $context->profile_log();
3109             # $context->set_profile_state_value($state_var, $state_value);
3110             # $context->_profile_accumulate($profile_stats, $key, $time_elapsed, $key_started);
3111             #############################################################################
3112              
3113             sub profile_start {
3114 0     0 0   my ($self, $key, $replace) = @_;
3115              
3116 0           my $timeofday = [ Time::HiRes::gettimeofday() ];
3117              
3118 0           my $profile_state = $self->{profile_state};
3119 0 0         if (!$profile_state) {
3120 0           $profile_state = {
3121             first_timeofday => $timeofday,
3122             last_timeofday => $timeofday,
3123             key_stack => [],
3124             key_started => 1,
3125             };
3126 0           $self->{profile_state} = $profile_state;
3127             }
3128              
3129 0           my $profile_stats = $self->{profile_stats};
3130 0 0         if (!$profile_stats) {
3131 0           $profile_stats = { db => { nrows_read => 0, nrows_write => 0 }, };
3132 0           $self->{profile_stats} = $profile_stats;
3133             }
3134              
3135 0           my $last_timeofday = $profile_state->{last_timeofday};
3136 0           my $key_stack = $profile_state->{key_stack};
3137 0           my $key_started = $profile_state->{key_started};
3138 0 0         my $last_key = ($#$key_stack > -1) ? $key_stack->[$#$key_stack] : "";
3139 0 0         if ($last_key) {
3140 0           my $time_elapsed = Time::HiRes::tv_interval($last_timeofday, $timeofday);
3141 0           $self->_profile_accumulate($profile_stats, $last_key, $time_elapsed, $key_started);
3142             }
3143 0 0         if ($#$key_stack > 100) {
3144 0           splice(@$key_stack, 0, 50);
3145             }
3146 0 0 0       if (!$replace || $#$key_stack == -1) {
3147 0           push(@$key_stack, $key);
3148             }
3149             else {
3150 0           $key_stack->[$#$key_stack] = $key;
3151             }
3152 0           $profile_state->{key_started} = 1;
3153 0           $profile_state->{last_timeofday} = $timeofday;
3154             }
3155              
3156             sub profile_stop {
3157 0     0 0   my ($self, $key) = @_;
3158 0           my $profile_state = $self->{profile_state};
3159 0           my $profile_stats = $self->{profile_stats};
3160 0 0 0       if ($profile_state && $profile_stats) {
3161 0           my $last_timeofday = $profile_state->{last_timeofday};
3162 0           my $key_stack = $profile_state->{key_stack};
3163 0           my $key_started = $profile_state->{key_started};
3164 0 0         my $last_key = ($#$key_stack > -1) ? $key_stack->[$#$key_stack] : "";
3165 0           my $timeofday = [ Time::HiRes::gettimeofday() ];
3166 0           my $time_elapsed = Time::HiRes::tv_interval($last_timeofday, $timeofday);
3167 0           $profile_state->{last_timeofday} = $timeofday;
3168 0           $self->_profile_accumulate($profile_stats, $last_key, $time_elapsed, $key_started);
3169 0           while ($#$key_stack > -1) {
3170 0           my $last_key = pop(@$key_stack);
3171 0 0         last if ($last_key eq $key);
3172             }
3173 0           $profile_state->{key_started} = 0;
3174             }
3175             }
3176              
3177             sub _profile_accumulate {
3178 0     0     my ($self, $profile_stats, $key, $time_elapsed, $key_started) = @_;
3179 0           my $stats = $profile_stats->{$key};
3180 0 0         if (!defined $stats) {
3181 0           $stats = {
3182             count => 1,
3183             cumul_time => $time_elapsed,
3184             min_time => $time_elapsed,
3185             max_time => $time_elapsed,
3186             sample_time => $time_elapsed,
3187             };
3188 0           $profile_stats->{$key} = $stats;
3189             }
3190             else {
3191 0           $stats->{cumul_time} += $time_elapsed;
3192 0 0         if ($key_started) {
3193 0           $stats->{count}++;
3194 0           my $sample_time = $stats->{sample_time};
3195 0 0         if ($sample_time > 0) {
3196 0 0         $stats->{min_time} = $sample_time if ($sample_time < $stats->{min_time});
3197 0 0         $stats->{max_time} = $sample_time if ($sample_time > $stats->{max_time});
3198             }
3199 0           $stats->{sample_time} = $time_elapsed;
3200             }
3201             else {
3202 0           $stats->{sample_time} += $time_elapsed;
3203             }
3204             }
3205             }
3206              
3207             sub profile_run_time {
3208 0     0 0   my ($self) = @_;
3209 0           my $profile_state = $self->{profile_state};
3210 0           my $time_elapsed = 0;
3211 0 0         if ($profile_state) {
3212 0           my $first_timeofday = $profile_state->{first_timeofday};
3213 0           my $last_timeofday = $profile_state->{last_timeofday};
3214 0           $time_elapsed = Time::HiRes::tv_interval($first_timeofday, $last_timeofday);
3215             }
3216 0           return($time_elapsed);
3217             }
3218              
3219             sub profile_stats {
3220 0     0 0   my ($self) = @_;
3221 0   0       return($self->{profile_stats} || {});
3222             }
3223              
3224             sub profile_clear {
3225 0     0 0   my ($self) = @_;
3226 0           delete $self->{profile_stats};
3227 0           delete $self->{profile_state};
3228             }
3229              
3230             sub set_profile_state_value {
3231 0     0 0   my ($self, $state_var, $state_value) = @_;
3232 0           $self->{profile_state}{$state_var} = $state_value;
3233             }
3234              
3235             sub profile_log {
3236 0     0 0   my ($self) = @_;
3237 0           my $profile_stats = $self->profile_stats();
3238 0           $self->log("PROFILE: cumultime count avgtime mintime maxtime key\n");
3239 0           my ($stats);
3240 0           foreach my $key (sort { $profile_stats->{$b}{cumul_time} <=> $profile_stats->{$a}{cumul_time} } keys %$profile_stats) {
  0            
3241 0           $stats = $profile_stats->{$key};
3242 0 0         if ($stats->{count}) {
3243 0           $self->log("PROFILE: %10.4f %10d %8.4f %8.4f %8.4f %s\n",
3244             $stats->{cumul_time},
3245             $stats->{count},
3246             $stats->{cumul_time}/$stats->{count},
3247             $stats->{min_time},
3248             $stats->{max_time},
3249             $key);
3250             }
3251             }
3252             }
3253              
3254             #############################################################################
3255             # SYSTEM AND PROCESS INFORMATION
3256             #############################################################################
3257              
3258             # /proc/meminfo
3259             # total: used: free: shared: buffers: cached:
3260             # Mem: 525942784 468914176 57028608 0 69124096 51593216
3261             # Swap: 1069268992 56954880 1012314112
3262             # MemTotal: 513616 kB
3263             # MemFree: 55692 kB
3264             # MemShared: 0 kB
3265             # Buffers: 67504 kB
3266             # Cached: 42328 kB
3267             # SwapCached: 8056 kB
3268             # Active: 171720 kB
3269             # ActiveAnon: 88224 kB
3270             # ActiveCache: 83496 kB
3271             # Inact_dirty: 22032 kB
3272             # Inact_laundry: 3120 kB
3273             # Inact_clean: 5572 kB
3274             # Inact_target: 40488 kB
3275             # HighTotal: 0 kB
3276             # HighFree: 0 kB
3277             # LowTotal: 513616 kB
3278             # LowFree: 55692 kB
3279             # SwapTotal: 1044208 kB
3280             # SwapFree: 988588 kB
3281              
3282             # /proc/loadavg
3283             # 0.02 0.12 0.15 1/138 30412
3284              
3285             # This only works on Linux (as far as I know)
3286             sub get_sys_info {
3287 0     0 0   my ($self) = @_;
3288 0           my $info = {};
3289             # print "FILE: /proc/meminfo\n";
3290 0 0         if (open(App::Context::FILE, "/proc/meminfo")) {
3291 0           while () {
3292 0 0         if (/^([A-Za-z]+):\s*([0-9]+)/) {
3293 0           $info->{lc($1)} = $2;
3294             # print ">>> $1 = $2\n";
3295             }
3296             }
3297 0           close(App::Context::FILE);
3298             }
3299             # print "FILE: /proc/loadavg\n";
3300 0 0         if (open(App::Context::FILE, "/proc/loadavg")) {
3301 0           while () {
3302 0 0         if (/^([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9]+)\/([0-9]+)\s+([0-9]+)/) {
3303 0           $info->{load} = $1;
3304 0           $info->{load5} = $2;
3305 0           $info->{load15} = $3;
3306 0           $info->{runprocs} = $4;
3307 0           $info->{nprocs} = $5;
3308 0           $info->{unknown} = $6;
3309             # print ">>> [$1][$2][$3][$4][$5][$6]\n";
3310             }
3311             }
3312 0           close(App::Context::FILE);
3313             }
3314 0           return($info);
3315             }
3316              
3317             # /proc/$$/status
3318             # Name: ksh
3319             # State: S (sleeping)
3320             # Tgid: 29147
3321             # Pid: 29147
3322             # PPid: 29146
3323             # TracerPid: 0
3324             # Uid: 102 102 102 102
3325             # Gid: 205 205 205 205
3326             # FDSize: 32
3327             # Groups: 205 201 202 214 3000 203 217
3328             # VmSize: 1624 kB
3329             # VmLck: 0 kB
3330             # VmRSS: 608 kB
3331             # VmData: 124 kB
3332             # VmStk: 12 kB
3333             # VmExe: 176 kB
3334             # VmLib: 1292 kB
3335             # SigPnd: 0000000000000000
3336             # SigBlk: 0000000000000000
3337             # SigIgn: 8000000000380000
3338             # SigCgt: 0000000000016007
3339             # CapInh: 0000000000000000
3340             # CapPrm: 0000000000000000
3341             # CapEff: 0000000000000000
3342              
3343             sub get_proc_info {
3344 0     0 0   my ($self, @pids) = @_;
3345 0 0         @pids = ($$) if ($#pids == -1);
3346 0           my ($pid, $proc);
3347 0           my $procs = {};
3348 0           foreach $pid (@pids) {
3349 0           $proc = {};
3350 0           $procs->{$pid} = $proc;
3351             # print "FILE: /proc/$$/status\n";
3352 0 0         if (open(App::Context::FILE, "/proc/$$/status")) {
3353 0           while () {
3354 0 0         if (/^Vm([A-Za-z]+):\s*([0-9]+)/) {
3355 0           $proc->{lc($1)} = $2;
3356             }
3357             }
3358 0           close(App::Context::FILE);
3359 0           $proc->{text} = $proc->{exe} + $proc->{lib};
3360             }
3361             else {
3362 0           $self->log("ERROR: Can't open /proc/$$/status: $!");
3363             }
3364             }
3365 0           return($procs);
3366             }
3367              
3368             # http://www.comptechdoc.org/os/linux/howlinuxworks/linux_hlproc.html
3369             #stat - Status information about the process used by the ps(1) command. Fields are:
3370             # 31137 (bash) S 19885 31137 31137 34841 651 0 1450
3371             # 185030 316 14024 1 3 687 715 14 0 0
3372             # 0 1792102651 4403200 361 4294967295 134512640 135217536 3221217344 3221216648 1074425592
3373             # 0 65536 3686404 1266761467 3222400107 0 0 17 2
3374             # 1. pid - Process id
3375             # 2. comm - The executable filename
3376             # 3. state - R (running), S(sleeping interruptable), D(sleeping), Z(zombie), or T(stopped on a signal).
3377             # 4. ppid - Parent process ID
3378             # 5. pgrp - Process group ID
3379             # 6. session - The process session ID.
3380             # 7. tty - The tty the process is using
3381             # 8. tpgid - The process group ID of the owning process of the tty the current process is connected to.
3382             # 9. flags - Process flags, currently with bugs
3383             # 10. minflt - Minor faults the process has made
3384             # 11. cminflt - Minor faults the process and its children have made.
3385             # 12. majflt
3386             # 13. cmajflt
3387             # 14. utime - The number of jiffies (processor time) that this process has been scheduled in user mode
3388             # 15. stime - in kernel mode
3389             # 16. cutime - This process and its children in user mode
3390             # 17. cstime - in kernel mode
3391             # 18. counter - The maximum time of this processes next time slice.
3392             # 19. priority - The priority of the nice(1) (process priority) value plus fifteen.
3393             # 20. timeout - The time in jiffies of the process's next timeout.
3394             # 21. itrealvalue - The time in jiffies before the next SIGALRM is sent to the process because of an internal timer.
3395             # 22. starttime - Time the process started after system boot
3396             # 23. vsize - Virtual memory size
3397             # 24. rlim - Current limit in bytes of the rss of the process.
3398             # 25. startcode - The address above which program text can run.
3399             # 26. endcode - The address below which program text can run.
3400             # 27. startstack - The address of the start of the stack
3401             # 28. kstkesp - The current value of esp for the process as found in the kernel stack page.
3402             # 29. kstkeip - The current 32 bit instruction pointer, EIP.
3403             # 30. signal - The bitmap of pending signals
3404             # 31. blocked - The bitmap of blocked signals
3405             # 32. sigignore - The bitmap of ignored signals
3406             # 33. sigcatch - The bitmap of catched signals
3407             # 34. wchan - The channel in which the process is waiting. The "ps -l" command gives somewhat of a list.
3408              
3409             sub get_proc_info2 {
3410 0     0 0   my ($self, @pids) = @_;
3411 0 0         @pids = ($$) if ($#pids == -1);
3412 0           my ($pid, $proc);
3413 0           my $procs = {};
3414 0           foreach $pid (@pids) {
3415 0           $proc = {};
3416 0           $procs->{$pid} = $proc;
3417             # print "FILE: /proc/$$/status\n";
3418 0 0         if (open(App::Context::FILE, "/proc/$$/stat")) {
3419 0           my $line = ;
3420 0           my @f = split(/ +/, $line);
3421 0           close(App::Context::FILE);
3422 0           $proc->{cutime} = $f[15];
3423 0           $proc->{cstime} = $f[16];
3424 0           $proc->{vsize} = $f[22];
3425             }
3426             else {
3427 0           $self->log("ERROR: Can't open /proc/$$/stat: $!");
3428             }
3429             }
3430 0           return($procs);
3431             }
3432              
3433             1;
3434