File Coverage

blib/lib/RapidApp/Module.pm
Criterion Covered Total %
statement 222 393 56.4
branch 73 192 38.0
condition 23 84 27.3
subroutine 46 69 66.6
pod 5 48 10.4
total 369 786 46.9


line stmt bran cond sub pod time code
1             package RapidApp::Module;
2              
3 6     6   3483 use strict;
  6         14  
  6         158  
4 6     6   29 use warnings;
  6         10  
  6         136  
5              
6             # ABSTRACT: Base class for RapidApp Modules
7              
8 6     6   27 use Moose;
  6         12  
  6         95  
9              
10 6     6   32921 use Clone;
  6         12  
  6         205  
11 6     6   29 use Try::Tiny;
  6         12  
  6         349  
12 6     6   2909 use String::Random;
  6         18286  
  6         267  
13 6     6   40 use Module::Runtime;
  6         12  
  6         38  
14 6     6   173 use Clone qw(clone);
  6         12  
  6         209  
15 6     6   33 use Time::HiRes qw(gettimeofday tv_interval);
  6         11  
  6         47  
16 6     6   2231 use Catalyst::Utils;
  6         171893  
  6         177  
17 6     6   47 use Scalar::Util qw(blessed weaken);
  6         13  
  6         264  
18 6     6   33 use RapidApp::JSONFunc;
  6         10  
  6         123  
19 6     6   27 use RapidApp::JSON::MixedEncoder;
  6         12  
  6         339  
20              
21 6     6   35 use RapidApp::Util qw(:all);
  6         8  
  6         32633  
22              
23             has 'base_params' => ( is => 'ro', lazy => 1, default => sub {{}} );
24             has 'params' => ( is => 'ro', required => 0, isa => 'ArrayRef' );
25             has 'base_query_string' => ( is => 'ro', default => '' );
26             has 'exception_style' => ( is => 'ro', required => 0, default => "color: red; font-weight: bolder;" );
27             has 'auto_viewport' => ( is => 'rw', default => 0 );
28              
29             has 'auto_init_modules', is => 'ro', isa => 'Maybe[HashRef]', default => sub{undef};
30             # ----------
31              
32              
33              
34             has 'instance_id' => (
35             is => 'ro', lazy => 1,
36             traits => ['RapidApp::Role::PerRequestBuildDefReset'],
37             default => sub {
38             my $self = shift;
39             return 'instance-' . String::Random->new->randregex('[a-z0-9A-Z]{5}');
40             });
41              
42              
43             ###########################################################################################
44              
45             sub BUILD {
46 220     220 0 825334 my $self= shift;
47            
48             # Init ONREQUEST_called to true to prevent ONREQUEST from running during BUILD:
49 220         6833 $self->ONREQUEST_called(1);
50            
51 220         6767 foreach my $mod ($self->module_class_list) {
52 0 0       0 my $class= ref($mod) eq ''? $mod : ref $mod eq 'HASH'? $mod->{class} : undef;
    0          
53 0 0       0 Catalyst::Utils::ensure_class_loaded($class) if defined $class;
54             };
55            
56             # Init:
57 220         1053 $self->cached_per_req_attr_list;
58            
59 220         7042 $self->apply_actions(viewport => 'viewport');
60 220         6471 $self->apply_actions(printview => 'printview');
61            
62 220 50       5372 $self->apply_init_modules(%{$self->auto_init_modules})
  0         0  
63             if ($self->auto_init_modules);
64             }
65              
66             sub suburl {
67 141     141 0 276 my $self = shift;
68 141         237 my $url = shift;
69            
70 141         3168 my $new_url = $self->base_url;
71 141         268 $new_url =~ s/\/$//;
72 141         457 $url =~ s/^\/?/\//;
73            
74 141         299 $new_url .= $url;
75            
76 141 50 33     3423 if (defined $self->base_query_string and $self->base_query_string ne '') {
77 0 0       0 $new_url .= '?' unless ($self->base_query_string =~ /^\?/);
78 0         0 $new_url .= $self->base_query_string;
79             }
80            
81 141         2439 return $new_url;
82             }
83              
84             # like suburl, but also prefixes mount_url
85             sub local_url {
86 42     42 0 104 my ($self,$url) = @_;
87 42 50       132 $url = $url ? $self->suburl($url) : $self->base_url;
88 42         102 join('',$self->c->mount_url,$url)
89             }
90              
91              
92             sub urlparams {
93 0     0 0 0 my $self = shift;
94 0         0 my $params = shift;
95            
96 0         0 my $new = Clone($self->base_params);
97            
98 0 0 0     0 if (defined $params and ref($params) eq 'HASH') {
99 0         0 foreach my $k (keys %{ $params }) {
  0         0  
100 0         0 $new->{$k} = $params->{$k};
101             }
102             }
103 0         0 return $new;
104             }
105              
106             sub content {
107 0     0 0 0 die "Unimplemented";
108             }
109              
110              
111             sub viewport {
112 1     1 0 1293 my $self= shift;
113 1   50     5 $self->c->stash->{current_view} ||= 'RapidApp::Viewport';
114 1   33     73 $self->c->stash->{title} ||= $self->module_name;
115 1   33     73 $self->c->stash->{config_url} ||= $self->base_url;
116 1 50       2 if (scalar keys %{$self->c->req->params}) {
  1         4  
117 0   0     0 $self->c->stash->{config_params} //= { %{$self->c->req->params} };
  0         0  
118             }
119             }
120              
121             sub printview {
122 0     0 0 0 my $self= shift;
123 0   0     0 $self->c->stash->{current_view} ||= 'RapidApp::Printview';
124 0         0 return $self->viewport;
125             }
126              
127             sub navable {
128 0     0 0 0 my $self = shift;
129              
130             # Apply common stash params:
131 0         0 $self->viewport;
132              
133 0         0 my $c = $self->c;
134              
135 0         0 my $url = delete $c->stash->{config_url};
136 0 0       0 my $params = exists $c->stash->{config_params} ? delete $c->stash->{config_params} : {};
137              
138             $c->stash->{panel_cfg} = {
139 0         0 xtype => 'apptabpanel',
140             id => 'main-load-target',
141             initLoadTabs => [{
142             closable => \0,
143             autoLoad => {
144             url => $url,
145             params => $params
146             }
147             }]
148             };
149              
150             }
151              
152              
153             ## --------------------------------------------------------------
154             ##
155             ## Code from legacy roles which have been DEPRECATED:
156             ##
157             ## * RapidApp::Role::Module
158             ## * RapidApp::Role::Controller
159             ##
160             ## Code below was moved from roles.
161             ##
162             ## The original rationales behind why these were separate
163             ## no longer apply, and have been combined here
164             ##
165             ## --------------------------------------------------------------
166              
167              
168             ##################################
169             #### Original Module Role ####
170             ##################################
171              
172              
173             # In catalyst terminology, "app" is the package name of the class that extends catalyst
174             # Many catalyst methods can be called from the package level
175             has 'app', is => 'ro', required => 1;
176              
177             has 'module_name' => ( is => 'ro', isa => 'Str', required => 1 );
178             has 'module_path' => ( is => 'ro', isa => 'Str', required => 1 );
179             has 'parent_module_ref' => ( is => 'ro', isa => 'Maybe[RapidApp::Module]', weak_ref => 1, required => 1);
180             has 'modules_obj' => ( is => 'ro', default => sub {{}} );
181             has 'default_module' => ( is => 'rw', default => 'default_module' );
182              
183             # This is defined in Controller role
184             #has 'create_module_params' => ( is => 'ro', default => sub { {} } );
185             has 'modules_params' => ( is => 'ro', default => sub { {} } );
186              
187             has 'print_rapidapp_handlers_call_debug' => ( is => 'rw', isa => 'Bool', default => 0 );
188              
189              
190             # All purpose options:
191             has 'module_options' => ( is => 'ro', lazy => 1, default => sub {{}}, traits => [ 'RapidApp::Role::PerRequestBuildDefReset' ] );
192              
193             has 'modules' => (
194             traits => ['Hash'],
195             is => 'ro',
196             isa => 'HashRef',
197             default => sub { {} },
198             handles => {
199             apply_modules => 'set',
200             get_module => 'get',
201             has_module => 'exists',
202             module_class_list => 'values'
203             }
204             );
205              
206              
207             has 'per_request_attr_build_defaults' => ( is => 'ro', default => sub {{}}, isa => 'HashRef' );
208             has 'per_request_attr_build_not_set' => ( is => 'ro', default => sub {{}}, isa => 'HashRef' );
209              
210             # TODO: add back in functionality to record the time to load the module.
211             # removed during the unfactor work in Github Issue #41
212 4     4 0 133 sub timed_new { (shift)->new(@_) }
213              
214             sub cached_per_req_attr_list {
215 386     386 0 656 my $self = shift;
216             # XXX TODO: I think there is some Moose way of applying roles to the meta object,
217             # but I'm not taking the time to look it up. This would also help with clearing the cache
218             # if new attributes were defined.
219 386         2470 my $attrs= (ref $self)->meta->{RapidApp_Module_PerRequestAttributeList};
220 386 100       7485 if (!defined $attrs) {
221 32         104 my $attrs= [ grep { $self->should_clear_per_req($_) } $self->meta->get_all_attributes ];
  2720         298659  
222             # we don't want this cache to make attributes live longer than needed, so weaken the references
223 32         3640 for (my $i=$#$attrs; $i>=0; $i--) {
224 380         1045 weaken $attrs->[$i];
225             }
226 32         166 (ref $self)->meta->{RapidApp_Module_PerRequestAttributeList}= $attrs;
227             }
228 386         1343 return $attrs;
229             };
230              
231              
232             sub should_clear_per_req {
233 2720     2720 0 3842 my ($self, $attr) = @_;
234 2720         5491 $attr->does('RapidApp::Role::PerRequestBuildDefReset')
235             }
236              
237              
238             # Does the same thing as apply_modules but also init/loads the modules
239             sub apply_init_modules {
240 113     113 0 898 my $self = shift;
241 113 50       652 my %mods = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
242            
243 113         3678 $self->apply_modules(%mods);
244 113         352 foreach my $module (keys %mods) {
245             # Initialize every module that we just added and set ONREQUEST_called back to false:
246 113         478 $self->Module($module)->ONREQUEST_called(0);
247             }
248             }
249              
250             # 'ONREQUEST' is called once per web request. Add before modifiers to any classes that
251             # need to run code at this time
252             #has 'ONREQUEST_called' => ( is => 'rw', lazy => 1, default => 0, traits => [ 'RapidApp::Role::PerRequestBuildDefReset' ] );
253              
254             has 'ONREQUEST_called' => ( is => 'rw', lazy => 1, default => 0 );
255              
256             has '_lastRequestApplied' => ( is => 'rw', default => 0 );
257              
258             sub reset_ONREQUEST {
259 0     0 0 0 my $self = shift;
260 0         0 $self->_lastRequestApplied(0);
261             }
262              
263              
264              
265             sub ONREQUEST {
266 83     83 0 234 my $self = shift;
267 83         273 my ($sec0, $msec0)= gettimeofday;
268            
269             #$self->c->log->debug(MAGENTA . '[' . $self->get_rapidapp_module_path . ']->ONREQUEST (' . $self->c->request_id . ')');
270            
271 83         208 $self->_lastRequestApplied($self->c->request_id);
272            
273 83         333 $self->init_per_req_attrs;
274 83         224 $self->c->rapidApp->markDirtyModule($self);
275            
276             #$self->process_customprompt;
277            
278             #$self->new_clear_per_req_attrs;
279            
280 83         415 $self->call_ONREQUEST_handlers;
281            
282 83         1990 $self->ONREQUEST_called(1);
283            
284 83         280 my ($sec1, $msec1)= gettimeofday;
285 83         280 my $elapsed= ($sec1-$sec0)+($msec1-$msec0)*.000001;
286 83         219 $self->c->stash->{onrequest_time_elapsed}+= $elapsed;
287            
288             #$self->log->debug(sprintf(GREEN."ONREQUEST for %s took %0.3f seconds".CLEAR, $self->module_path, $elapsed));
289 83         5373 return $self;
290             }
291              
292             sub call_ONREQUEST_handlers {
293 105     105 0 185 my $self = shift;
294 105         3336 $self->call_rapidapp_handlers($self->all_ONREQUEST_calls_early);
295 105         5099 $self->call_rapidapp_handlers($self->all_ONREQUEST_calls);
296 105         3491 $self->call_rapidapp_handlers($self->all_ONREQUEST_calls_late);
297             }
298              
299              
300              
301             sub init_per_req_attrs {
302 83     83 0 129 my $self = shift;
303            
304 83         124 foreach my $attr (@{$self->cached_per_req_attr_list}) {
  83         272  
305 1016 100       2697 if($attr->has_value($self)) {
306 661 100       33246 unless (defined $self->per_request_attr_build_defaults->{$attr->name}) {
307 286         739 my $val = $attr->get_value($self);
308 286 100       39466 $val = clone($val) if (ref($val));
309 286         7721 $self->per_request_attr_build_defaults->{$attr->name} = $val;
310             }
311             }
312             else {
313 355         17688 $self->per_request_attr_build_not_set->{$attr->name} = 1;
314             }
315             }
316             }
317              
318             sub reset_per_req_attrs {
319 83     83 0 131 my $self = shift;
320 83         123 my $c = shift;
321            
322 83         125 foreach my $attr (@{$self->cached_per_req_attr_list}) {
  83         253  
323              
324             # Reset to "not_set":
325 1016 100       196841 if (defined $self->per_request_attr_build_not_set->{$attr->name}) {
    50          
326             #$c->log->debug(GREEN . BOLD . ' =====> ' . $attr->name . ' (clear_value)' . CLEAR);
327 355         931 $attr->clear_value($self);
328             }
329             # Reset to default:
330             elsif(defined $self->per_request_attr_build_defaults->{$attr->name}) {
331 661         14425 my $val = $self->per_request_attr_build_defaults->{$attr->name};
332 661 100       25234 $val = clone($val) if (ref($val));
333             #$c->log->debug(YELLOW . BOLD . ' =====> ' . $attr->name . ' (set_value)' . CLEAR);
334 661         2319 $attr->set_value($self,$val);
335             }
336             }
337            
338             # Legacy:
339 83 50       17514 $self->clear_attributes if ($self->no_persist);
340             }
341              
342              
343              
344              
345             #sub new_clear_per_req_attrs {
346             # my $self = shift;
347             #
348             # #$self->ONREQUEST_called(0);
349             #
350             # foreach my $attr (@{$self->cached_per_req_attr_list}) {
351             # # Reset to default:
352             # if(defined $self->per_request_attr_build_defaults->{$attr->name}) {
353             # my $val = $self->per_request_attr_build_defaults->{$attr->name};
354             # $val = clone($val) if (ref($val));
355             # $attr->set_value($self,$val);
356             # }
357             # # Initialize default:
358             # else {
359             # my $val = $attr->get_value($self);
360             # $val = clone($val) if (ref($val));
361             # $self->per_request_attr_build_defaults->{$attr->name} = $val;
362             # }
363             # }
364             #
365             # # Legacy:
366             # $self->clear_attributes if ($self->no_persist);
367             #}
368              
369              
370              
371              
372             sub THIS_MODULE {
373 1395     1395 0 2068 my $self = shift;
374 1395 100       2660 return $self unless (defined $self->c);
375            
376 519 100 66     928 return $self->ONREQUEST if (defined $self->c && $self->c->request_id != $self->_lastRequestApplied);
377 436         2360 return $self;
378             }
379              
380              
381             # Gets a Module by / delim path
382             sub get_Module {
383 4     4 0 9 my $self = shift;
384 4 50       18 my $path = shift or return $self->THIS_MODULE;
385            
386 4         20 my @parts = split('/',$path);
387 4         16 my $first = shift @parts;
388             # If $first is undef then the path is absolute (starts with '/'):
389 4 50       15 unless ($first) {
390 0         0 my $topModule = $self->topmost_module;
391             # New: support returning modules when the module_root_namespace is supplied in the path:
392 0 0 0     0 shift @parts if (
393             !$topModule->has_module($parts[0]) &&
394             $parts[0] eq $self->app->module_root_namespace
395             );
396            
397 0         0 return $topModule->get_Module(join('/',@parts));
398             }
399            
400             # If there are no more parts in the path, then the name is a direct submodule:
401 4 50       45 return $self->Module($first) unless (scalar @parts > 0);
402            
403 0         0 return $self->Module($first)->get_Module(join('/',@parts));
404             }
405              
406              
407              
408             sub Module {
409 632     632 0 1121 my $self = shift;
410 632         1127 my $name = shift;
411 632         1014 my $no_onreq = shift;
412            
413 632 100       1755 $self->_load_module($name) or confess "Failed to load Module '$name'";
414            
415             #return $self->modules_obj->{$name} if ($no_onreq);
416 628         12478 return $self->modules_obj->{$name}->THIS_MODULE;
417             }
418              
419              
420             sub _load_module {
421 672     672   1016 my $self = shift;
422 672 50       1692 my $name = shift or return 0;
423 672 100       19006 return 0 unless ($self->has_module($name));
424            
425             #my $class_name = $self->modules->{$name} or return 0;
426 668         17780 my $class_name = $self->get_module($name);
427 668         1033 my $params;
428 668 100       1859 if (ref($class_name) eq 'HASH') {
429 655         1404 $params = $class_name->{params};
430 655 50       1757 $class_name = $class_name->{class} or die "Missing required parameter 'class'";
431             }
432              
433 668 100 66     14531 return 1 if (defined $self->modules_obj->{$name} and ref($self->modules_obj->{$name}) eq $class_name);
434            
435 216 50       945 my $Object = $self->create_module($name,$class_name,$params) or die "Failed to create new $class_name object";
436            
437 216         5052 $self->modules_obj->{$name} = $Object;
438            
439 216         800 return 1;
440             }
441              
442             sub create_module {
443 216     216 0 440 my $self = shift;
444 216         424 my $name = shift;
445 216         370 my $class_name = shift;
446 216         384 my $params = shift;
447            
448 216 50       751 die "Bad module name '$name' -- cannot contain '/'" if ($name =~ /\//);
449              
450 216         1136 Module::Runtime::require_module($class_name);
451            
452 216 100       7361 $params = $self->create_module_params unless (defined $params);
453            
454 216 50       5528 if (defined $self->modules_params->{$name}) {
455 0         0 foreach my $k (keys %{$self->modules_params->{$name}}) {
  0         0  
456 0         0 $params->{$k} = $self->modules_params->{$name}->{$k};
457             }
458             }
459            
460 216         4649 $params->{app} = $self->app;
461 216         685 $params->{module_name} = $name;
462 216         4842 $params->{module_path} = $self->module_path;
463 216 100       1235 $params->{module_path} .= '/' unless substr($params->{module_path}, -1) eq '/';
464 216         473 $params->{module_path} .= $name;
465 216         456 $params->{parent_module_ref} = $self;
466            
467              
468             # Colorful console messages, non-standard, replaced with normal logging below:
469             #print STDERR
470             # ' >> ' .
471             # CYAN . "Load: " . BOLD . $params->{module_path} . CLEAR .
472             # CYAN . " [$class_name]" . CLEAR . "\n"
473             #if ($self->app->debug);
474            
475 216         1091 my $start = [gettimeofday];
476            
477 216 50       6244 my $Object = $class_name->new($params) or die "Failed to create module instance ($class_name)";
478 216 50       3711 die "$class_name is not a valid RapidApp Module" unless ($Object->isa('RapidApp::Module'));
479            
480 216         4919 my $c = $self->app;
481             $c->log->debug( join('',
482 216 50       1509 " >> Loaded: ",$params->{module_path}," [$class_name] ",
483             sprintf("(%0.3fs)",tv_interval($start))
484             )) if ($c->debug);
485            
486 216         1470 return $Object;
487             }
488              
489             sub parent_module {
490 756     756 0 9157 my $self = shift;
491 756 100       16250 return $self->parent_module_ref ? $self->parent_module_ref->THIS_MODULE : undef;
492             }
493              
494              
495             sub topmost_module {
496 0     0 0 0 my $self = shift;
497 0 0       0 return $self unless (defined $self->parent_module);
498 0         0 return $self->parent_module->topmost_module;
499             }
500              
501              
502             sub parent_by_name {
503 0     0 0 0 my $self = shift;
504 0         0 my $name = shift;
505 0 0       0 return $self if (lc($self->module_name) eq lc($name));
506 0 0       0 return undef unless (defined $self->parent_module);
507 0         0 return $self->parent_module->parent_by_name($name);
508             }
509              
510              
511              
512             sub applyIf_module_options {
513 0     0 0 0 my $self = shift;
514 0 0       0 my %new = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
515            
516 0         0 my %unset = ();
517 0         0 foreach my $opt (keys %new) {
518 0 0       0 next if (defined $self->module_options->{$opt});
519 0         0 $unset{$opt} = $new{$opt};
520             }
521            
522 0         0 return $self->apply_module_options(%unset);
523             }
524              
525              
526             sub apply_module_options {
527 0     0 0 0 my $self = shift;
528 0 0       0 my %new = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
529            
530 0         0 %{ $self->module_options } = (
531 0         0 %{ $self->module_options },
  0         0  
532             %new
533             );
534             }
535              
536             sub get_module_option {
537 0     0 0 0 my $self = shift;
538 0         0 my $opt = shift;
539 0         0 return $self->module_options->{$opt};
540             }
541              
542              
543             has 'ONREQUEST_calls' => (
544             traits => [ 'Array' ],
545             is => 'ro',
546             isa => 'ArrayRef[RapidApp::Handler]',
547             default => sub { [] },
548             handles => {
549             all_ONREQUEST_calls => 'elements',
550             add_ONREQUEST_calls => 'push',
551             has_no_ONREQUEST_calls => 'is_empty',
552             }
553             );
554             around 'add_ONREQUEST_calls' => __PACKAGE__->add_ONREQUEST_calls_modifier;
555              
556             has 'ONREQUEST_calls_early' => (
557             traits => [ 'Array' ],
558             is => 'ro',
559             isa => 'ArrayRef[RapidApp::Handler]',
560             default => sub { [] },
561             handles => {
562             all_ONREQUEST_calls_early => 'elements',
563             add_ONREQUEST_calls_early => 'push',
564             has_no_ONREQUEST_calls_early => 'is_empty',
565             }
566             );
567             around 'add_ONREQUEST_calls_early' => __PACKAGE__->add_ONREQUEST_calls_modifier;
568              
569             has 'ONREQUEST_calls_late' => (
570             traits => [ 'Array' ],
571             is => 'ro',
572             isa => 'ArrayRef[RapidApp::Handler]',
573             default => sub { [] },
574             handles => {
575             all_ONREQUEST_calls_late => 'elements',
576             add_ONREQUEST_calls_late => 'push',
577             has_no_ONREQUEST_calls_late => 'is_empty',
578             }
579             );
580             around 'add_ONREQUEST_calls_late' => __PACKAGE__->add_ONREQUEST_calls_modifier;
581              
582             sub add_ONREQUEST_calls_modifier {
583             return sub {
584 509     509   7551 my $orig = shift;
585 509         719 my $self = shift;
586 509 50       1147 return $self->$orig(@_) if (ref($_[0]));
587            
588 509         917 my @new = ();
589 509         918 foreach my $item (@_) {
590 509         1870 push @new, RapidApp::Handler->new(
591             method => $item,
592             scope => $self
593             );
594             }
595 509         18863 return $self->$orig(@new);
596 23     23 0 222 };
597             }
598              
599             sub call_rapidapp_handlers {
600 361     361 0 489 my $self = shift;
601 361         679 foreach my $Handler (@_) {
602 175 50       817 die 'not a RapidApp::Handler' unless (ref($Handler) eq 'RapidApp::Handler');
603            
604 175 50       4278 if($self->print_rapidapp_handlers_call_debug) {
605 0         0 my $msg = YELLOW . '->call_rapidapp_handlers[' . $self->get_rapidapp_module_path . '] ' . CLEAR;
606 0         0 $msg .= GREEN;
607 0 0       0 if (defined $Handler->scope) {
608 0         0 $msg .= '(' . ref($Handler->scope);
609 0 0       0 if ($Handler->scope->isa('RapidApp::Module')) {
610 0         0 $msg .= CLEAR . BLUE . ' ' . $Handler->scope->get_rapidapp_module_path;
611             }
612 0         0 $msg .= CLEAR . GREEN . ')' . CLEAR;
613             }
614             else {
615 0         0 $msg .= '(no scope)';
616             }
617            
618 0 0       0 if (defined $Handler->method) {
619 0         0 $msg .= BOLD . '->' . $Handler->method . CLEAR;
620             }
621             else {
622 0         0 $msg .= BOLD . '==>CODEREF->()' . CLEAR;
623             }
624            
625 0         0 $self->app->log->debug($msg);
626             }
627            
628 175         532 $Handler->call;
629             }
630             }
631              
632             #before 'ONREQUEST' => sub {
633             # my $self = shift;
634             # $self->call_rapidapp_handlers($self->all_ONREQUEST_calls_early);
635             #};
636             #
637             #after 'ONREQUEST' => sub {
638             # my $self = shift;
639             # $self->call_rapidapp_handlers($self->all_ONREQUEST_calls_late);
640             #};
641              
642              
643             # All purpose flags (true/false) settings
644             has 'flags' => (
645             traits => [
646             'Hash',
647             'RapidApp::Role::PerRequestBuildDefReset'
648             ],
649             is => 'ro',
650             isa => 'HashRef[Bool]',
651             default => sub { {} },
652             handles => {
653             apply_flags => 'set',
654             has_flag => 'get',
655             delete_flag => 'delete',
656             flag_defined => 'exists',
657             all_flags => 'elements'
658             },
659             );
660              
661              
662             # function for debugging purposes - returns a string of the module path
663             sub get_rapidapp_module_path {
664 0     0 0 0 return (shift)->module_path;
665             }
666              
667              
668             has 'customprompt_button' => (
669             is => 'rw',
670             isa => 'Maybe[Str]',
671             traits => [ 'RapidApp::Role::PerRequestBuildDefReset' ],
672             lazy => 1,
673             default => sub {
674             my $self = shift;
675             return $self->c->req->header('X-RapidApp-CustomPrompt-Button') || $self->c->req->params->{'X-RapidApp-CustomPrompt-Button'};
676             }
677             );
678              
679              
680             has 'customprompt_data' => (
681             is => 'rw',
682             isa => 'HashRef',
683             traits => [ 'RapidApp::Role::PerRequestBuildDefReset' ],
684             lazy => 1,
685             default => sub {
686             my $self = shift;
687             my $rawdata = $self->c->req->header('X-RapidApp-CustomPrompt-Data') || $self->c->req->params->{'X-RapidApp-CustomPrompt-Data'};
688             return {} unless (defined $rawdata);
689             return $self->json->decode($rawdata);
690             }
691             );
692              
693              
694             ##################################
695             #### Original Controller Role ####
696             ##################################
697              
698              
699             has 'base_url' => (
700             is => 'rw', lazy => 1, default => sub {
701             my $self = shift;
702             my $ns = $self->app->module_root_namespace;
703             $ns = $ns eq '' ? $ns : '/' . $ns;
704             my $parentUrl= defined $self->parent_module? $self->parent_module->base_url.'/' : $ns;
705             return $parentUrl . $self->{module_name};
706             },
707             traits => [ 'RapidApp::Role::PerRequestBuildDefReset' ]
708             );
709              
710             #has 'extra_actions' => ( is => 'ro', default => sub {{}} );
711             has 'default_action' => ( is => 'ro', default => undef );
712             has 'render_as_json' => ( is => 'rw', default => 1, traits => [ 'RapidApp::Role::PerRequestBuildDefReset' ] );
713              
714             # NEW: if true, sub-args (of url path) are passed in even if the sub path does
715             # not exist as a defined action or sub-module. TODO: refactor and use built-in Catalyst
716             # functionality for controller actions. ALL of Module/Controller should be refactored
717             # into proper sub-classes of Catalyst controllers
718             has 'accept_subargs', is => 'rw', isa => 'Bool', default => 0;
719              
720             has 'actions' => (
721             traits => ['Hash'],
722             is => 'ro',
723             isa => 'HashRef',
724             default => sub { {} },
725             handles => {
726             apply_actions => 'set',
727             get_action => 'get',
728             has_action => 'exists'
729             }
730             );
731              
732             # In catalyst terminology, "c" is the catalyst instance, embodying a request.
733 3333     3333 0 9654 sub c { RapidApp->active_request_context }
734              
735             # The current logger object, probably the same as ->c->log, but maybe not.
736 0     0 0 0 sub log { (shift)->app->log }
737              
738              
739             has 'no_persist' => ( is => 'rw', lazy => 1, default => sub {
740             my $self = shift;
741             # inherit the parent's no_persist setting if its set:
742             return $self->parent_module->no_persist if (
743             defined $self->parent_module and
744             defined $self->parent_module->no_persist
745             );
746             return undef;
747             });
748              
749             has 'render_append' => ( is => 'rw', default => '', isa => 'Str' );
750              
751             sub add_render_append {
752 0     0 0 0 my $self = shift;
753 0 0       0 my $add or return;
754 0 0       0 die 'ref encountered, string expected' if ref($add);
755            
756 0         0 my $cur = $self->render_append;
757 0         0 return $self->render_append( $cur . $add );
758             }
759              
760              
761             has 'no_json_ref_types' => ( is => 'ro', default => sub {
762             return {
763             'IO::File' => 1
764             }
765             });
766              
767             has 'create_module_params' => ( is => 'ro', lazy => 1, default => sub {{}} );
768              
769             has 'json' => ( is => 'ro', lazy_build => 1 );
770             sub _build_json {
771 3     3   7 my $self = shift;
772             #$self->log->warn((ref $self)."->json still being used");
773 3         29 return RapidApp::JSON::MixedEncoder->new;
774             }
775              
776             sub JSON_encode {
777 0     0 0 0 my $self = shift;
778 0         0 return $self->json->encode(shift);
779             }
780              
781              
782             ## TODO: REMOVE 'simulateRequest' ---
783             # This method attempts to set up a catalyst request instance such that a new request can be executed
784             # to a different module and with different parameters and HTTP headers than were used for the main
785             # request.
786             sub simulateRequest {
787 0     0 0 0 my ($self, $req)= @_;
788            
789 0         0 my $c = RapidApp->active_request_context;
790            
791 0         0 my $tempResp= Catalyst::Response->new();
792            
793 0         0 my $origReq= $c->request;
794 0         0 my $origResp= $c->response;
795 0         0 my $origStash= $c->stash;
796            
797             try {
798 0     0   0 $c->request($req);
799 0         0 $c->response($tempResp);
800            
801             # This is dangerous both any way you do it. We could make an empty stash, but then might lose important
802             # settings (like those set by ModuleDispatcher)
803 0         0 $c->stash({ %$origStash });
804            
805 0         0 my $path= $req->uri->path;
806 0         0 $path =~ s|^/||;
807 0         0 my @args= split('/', $path);
808 0         0 $self->c->log->debug("Simulate Request: \"".join('", "', @args));
809 0         0 my $ctl_ret= $self->Controller($c, @args);
810            
811 0 0       0 $c->log->debug('controller return: '.(length($ctl_ret) > 20? (ref $ctl_ret).' length='.length($ctl_ret) : $ctl_ret));
812 0 0       0 $c->log->debug('body: '.(length($tempResp->body) > 20? (ref $tempResp->body).' length='.length($tempResp->body) : $tempResp->body));
813            
814             # execute the specified view, if needed
815 0 0       0 if (!defined $c->res->body) {
816 0   0     0 my $view= $self->c->stash->{current_view_instance} || $c->view($c->stash->{current_view});
817 0         0 $view->process($c);
818             }
819            
820 0         0 $c->request($origReq);
821 0         0 $c->response($origResp);
822 0         0 $c->stash($origStash);
823             }
824             catch {
825 0     0   0 $c->request($origReq);
826 0         0 $c->response($origResp);
827 0         0 $c->stash($origStash);
828 0         0 die $_;
829 0         0 };
830 0         0 return $tempResp;
831             }
832              
833             sub simulateRequestToSubUrl {
834 0     0 0 0 my ($self, $uri, @params)= @_;
835 0 0 0     0 blessed($uri) && $uri->isa('URI') or $uri= URI->new($uri);
836            
837             # if parameters were part of the URI, extract them first, then possibly override them with @params
838             # Note that "array-style" URI params will be returned as duplicate key entries, so we have to do some work to
839             # assemble the values into lists to match the way you'd expect it to work.
840 0         0 my @uriParams= $uri->query_form;
841 0         0 my %paramHash;
842 0         0 for (my $i=0; $i < $#uriParams; $i+= 2) {
843 0         0 my ($key, $val)= ($uriParams[$i], $uriParams[$i+1]);
844             $paramHash{$key}= (!defined $paramHash{$key})?
845             $val
846             : (ref $paramHash{$key} ne 'ARRAY')?
847             [ $paramHash{$key}, $val ]
848 0 0       0 : [ @{$paramHash{$key}}, $val ];
  0 0       0  
849             }
850            
851             # add in the supplied parameters
852 0         0 %paramHash= ( %paramHash, @params );
853            
854 0         0 my $req= Catalyst::Request->new( uri => $uri, parameters => \%paramHash );
855            
856 0         0 return $self->simulateRequest($req);
857             }
858              
859             sub simulateRequestToSubUrl_asString {
860 0     0 0 0 my $self= shift;
861 0         0 my $resp= $self->simulateRequestToSubUrl(@_);
862 0 0       0 $resp->status == 200
863             or die "Simulated request to ".$_[0]." returned status ".$resp->status;
864 0         0 my $ret= $resp->body;
865 0 0       0 if (ref $ret) {
866 0         0 my $fd= $ret;
867 0         0 local $/= undef;
868 0         0 $ret= <$fd>;
869 0         0 $fd->close;
870             }
871 0         0 return $ret;
872             }
873              
874             # Initializes variables of the controller based on the details of the current request being handled.
875             # This is a stub for 'after's and 'before's and overrides.
876       52 0   sub prepare_controller {
877             }
878              
879             =head2 Controller( $catalyst, @pathArguments )
880              
881             This method handles a request.
882              
883             =cut
884             sub Controller {
885 52     52 1 160 my ($self, $c, @args) = @_;
886              
887 52         303 $self->prepare_controller(@args);
888              
889             # dispatch the request to the appropriate handler
890              
891 52 50       169 $c->log->debug('--> ' .
892             GREEN.BOLD . ref($self) . CLEAR . ' ' .
893             GREEN . join('/',@args) . CLEAR
894             ) if ($c->debug);
895              
896 52         364 $self->controller_dispatch(@args);
897             }
898              
899             # module or action:
900             sub has_subarg {
901 23     23 0 66 my ($self, $opt) = @_;
902 23 50 33     744 return ($opt && (
903             $self->has_module($opt) ||
904             $self->has_action($opt)
905             )) ? 1 : 0;
906             }
907              
908              
909             has 'get_local_args', is => 'ro', isa => 'Maybe[CodeRef]', lazy => 1, default => undef;
910              
911             sub local_args {
912 0     0 0 0 my $self = shift;
913            
914 0 0       0 return $self->get_local_args->() if ($self->get_local_args);
915            
916 0         0 my $path = '/' . $self->c->req->path;
917 0         0 my $base = quotemeta($self->base_url . '/');
918 0         0 my ($match) = ($path =~ /^${base}(.+$)/);
919 0 0       0 my $argpath = defined $match ? $match : '';
920 0         0 return split('/',$argpath);
921             }
922              
923             # is this being used anyplace??
924             sub clear_attributes {
925 0     0 0 0 my $self = shift;
926 0         0 for my $attr ($self->meta->get_all_attributes) {
927 0 0       0 next if ($attr->name eq 'actions');
928 0 0 0     0 $attr->clear_value($self) if ($attr->is_lazy or $attr->has_clearer);
929             }
930             }
931              
932              
933             =head2 controller_dispatch( @args )
934              
935             controller_dispatch performs the standard RapidApp dispatch processing for a Module.
936              
937             =over
938              
939             =item *
940              
941             If the first argument names an action, the action is executed.
942              
943             =item *
944              
945             If the first argument names a sub-module, the processing is passed to the sub-module.
946              
947             =item *
948              
949             If the first argument does not match anything, then the default action is called, if specified,
950             otherwise a 404 is returned to the user.
951              
952             =item *
953              
954             If there are no arguments, and the client was not requesting JSON, the viewport is executed.
955              
956             =item *
957              
958             Else, content is called, and its return value is passed to render_data.
959              
960             =back
961              
962             =cut
963              
964             sub controller_dispatch {
965 52     52 1 137 my ($self, $opt, @subargs)= @_;
966 52         112 my $c = $self->c;
967            
968             # We're doing this because its the cleanest way to expose the currently dispatching module to
969             # other Catalyst Components, such as the view. We needed this specifically to add the literal
970             # sql default_value handling (i.e. default column values like \'current_timestamp').
971 52         132 $c->stash->{'RAPIDAPP_DISPATCH_MODULE'} = $self;
972            
973 52 100 100     4511 return $self->Module($opt)->Controller($self->c,@subargs)
      66        
974             if ($opt && !$self->has_action($opt) && $self->_load_module($opt));
975            
976 12 100 66     270 return $self->process_action($opt,@subargs)
977             if ($opt && $self->has_action($opt));
978            
979 3 50       85 return $self->process_action($self->default_action,@_)
980             if (defined $self->default_action);
981            
982 3         10 my $ct= $self->c->stash->{requestContentType};
983            
984             # if there were unprocessed arguments which were not an action, and there was no default action, generate a 404
985             # UPDATE: unless new 'accept_subargs' attr is true (see attribute declaration above)
986 3 50 33     250 if (defined $opt && !$self->accept_subargs) {
    100 66        
987             # Handle the special case of browser requests for 'favicon.ico' (#57)
988 0 0 0     0 return $c->redispatch_public_path(
989             $c->default_favicon_url
990             ) if ($opt eq 'favicon.ico' && !$c->is_ra_ajax_req);
991              
992 0 0       0 $self->c->log->debug(join('',"--> ",RED,BOLD,"unknown action: $opt",CLEAR)) if ($self->c->debug);
993 0         0 $c->stash->{template} = 'rapidapp/http-404.html';
994 0         0 $c->stash->{current_view} = 'RapidApp::Template';
995 0         0 $c->res->status(404);
996 0         0 return $c->detach;
997             }
998             # --
999             # TODO: this is the last remaining logic from the old "web1" stuff (see the v0.996xx branch for
1000             # the last state of that code before it was unfactored)
1001             #
1002             # this needs to be merged with the next, newer codeblock (render_viewport stuff...)
1003             elsif ($self->auto_viewport && !$self->c->is_ra_ajax_req) {
1004 1 50       77 $self->c->log->debug("--> " . GREEN . BOLD . "[auto_viewport_content]" . CLEAR . ". (no action)")
1005             if($self->c->debug);
1006 1         7 return $self->viewport;
1007             }
1008             # --
1009             else {
1010 2 50       15 if(my $ret = $self->_maybe_render_viewport) {
1011 0         0 return $ret;
1012             }
1013             else {
1014             ## ---
1015             ## detect direct browser GET requests (i.e. not from the ExtJS client)
1016             ## and redirect them back to the #! hashnav path
1017 2         149 $self->auto_hashnav_redirect_current;
1018             # ---
1019 2 50       9 $self->c->log->debug("--> " . GREEN . BOLD . "[content]" . CLEAR . ". (no action)")
1020             if($self->c->debug);
1021 2         25 return $self->render_data($self->content);
1022             }
1023             }
1024            
1025             }
1026              
1027              
1028             sub _maybe_render_viewport {
1029 11     11   25 my $self = shift;
1030              
1031 11 50       31 my $rdr_vp = $self->c->stash->{render_viewport} or return 0;
1032            
1033 0 0 0     0 if($rdr_vp && $rdr_vp eq 'printview' && $self->can('printview')) {
    0 0        
    0 0        
      0        
      0        
1034 0         0 return $self->printview;
1035             }
1036             elsif($rdr_vp && $rdr_vp eq 'navable' && $self->can('navable')) {
1037 0         0 return $self->navable;
1038             }
1039             elsif($rdr_vp && $self->can('viewport')) {
1040 0         0 return $self->viewport;
1041             }
1042             }
1043              
1044              
1045             # This call happens via local method so subclasses are able to override
1046             sub auto_hashnav_redirect_current {
1047 2     2 0 5 my $self = shift;
1048 2         9 $self->c->auto_hashnav_redirect_current
1049             }
1050              
1051              
1052              
1053             =head2 process_action( $actionName, [optional @args] )
1054              
1055             This routine handles the execution of a selected action. The action must exist.
1056             For actions that map to coderefs, the coderef is executed.
1057             For actions that map to strings, a method of that name is called on $self.
1058              
1059             =cut
1060             sub process_action {
1061 9     9 1 26 my $self = shift;
1062 9         27 my ( $opt, @args ) = @_;
1063            
1064 9 50       35 die "No action specified" unless ($opt);
1065            
1066 9 50       29 $self->c->log->debug('--> ' .
1067             GREEN.BOLD . ref($self) . CLEAR . ' ' .
1068             GREEN . "action{ " . $opt . " }" . CLEAR . ' ' .
1069             GREEN . join('/',@args) . CLEAR
1070             ) if ($self->c->debug);
1071            
1072 9 50       302 my $coderef = $self->get_action($opt) or die "No action named $opt";
1073            
1074 9 50       51 if(my $ret = $self->_maybe_render_viewport) {
1075 0         0 return $ret;
1076             }
1077            
1078             # If $coderef is not actually a coderef, we assume its a string representing an
1079             # object method and we call it directly:
1080 9 50       587 return $self->render_data(
1081             ref($coderef) eq 'CODE' ?
1082             $coderef->($self,@args) :
1083             $self->$coderef(@args)
1084             );
1085             }
1086              
1087             =head2 render_data( $data )
1088              
1089             This is a very DWIM sort of routine that takes its parameter (likely the return value of
1090             content or an action) and picks an appropriate view for it, possibly ignoring it altogether.
1091              
1092             =over
1093              
1094             =item *
1095              
1096             If the action generated a body, no view is needed, and the parameter is ignored.
1097              
1098             =item *
1099              
1100             If the action chose its own view, no further processing is done, and the parameter is returned.
1101              
1102             =item *
1103              
1104             If the controller is configured to render json (the default) and the parameter isn't blacklisted
1105             in no_json_ref_types, and the parameter isn't a plain string, the RapidApp::JSON view is chosen.
1106             The parameter is returned (as-is) to get passed back to TopController who passes it to the view.
1107              
1108             =item *
1109              
1110             Else, the data is treated as an explicit string for the body. The body is assigned, and returned.
1111              
1112             =back
1113              
1114             =cut
1115             sub render_data {
1116 11     11 1 38 my ($self, $data)= @_;
1117            
1118             #$self->c->log->debug(Dumper($data));
1119            
1120             # do nothing if the body has been set
1121 11 50 33     43 if (defined $self->c->response->body && length $self->c->response->body) {
1122 0         0 $self->c->log->debug("(body set by user)");
1123            
1124             # check for the condition that will cause a "Wide character in syswrite" and give a better error message
1125 0 0       0 if (utf8::is_utf8($self->c->response->body)) {
1126 0 0       0 $self->c->response->content_type =~ /^text|xml$|javascript$|JSON$/
1127             or $self->c->log->warn("Controller ".(ref $self)." returned unicode text but isn't using a \"text\" content type!");
1128             }
1129 0         0 return undef;
1130             }
1131            
1132             # do nothing if the view has been configured
1133 11 50 33     426 if (defined $self->c->stash->{current_view} || defined $self->c->stash->{current_view_instance}) {
1134 0         0 $self->c->log->debug("(view set by user)");
1135 0         0 return $data;
1136             }
1137            
1138             # if we want auto-json rendering, use the JSON view
1139 11 50 33     908 if ($self->render_as_json && ref($data) && !defined $self->no_json_ref_types->{ref($data)}) {
      33        
1140 11         31 $self->c->stash->{current_view} = 'RapidApp::JSON';
1141 11         1006 return $data;
1142             }
1143             # else set the body directly and use no view
1144             else {
1145 0           $self->c->response->header('Cache-Control' => 'no-cache');
1146 0           return $self->c->response->body( $data );
1147             }
1148             }
1149              
1150 0     0 0   sub set_response_warning { (shift)->c->set_response_warning(@_) }
1151              
1152              
1153             # if response_callback_scoped is true when set_response_callback is called, the
1154             # function will be called with the scope (this reference) of the Ext.data.Connection
1155             # object that initiated the Ajax request (Ext.Ajax.request) and this.response will
1156             # also contain the response object; This is false by default because setting the
1157             # scope breaks many functions, and this is usually not needed (the only reason to
1158             # turn this on would be if you need to examine the specific request/response)
1159             has 'response_callback_scoped' => (
1160             is => 'rw',
1161             traits => [ 'RapidApp::Role::PerRequestBuildDefReset' ],
1162             default => 0
1163             );
1164              
1165             =head2 set_response_callback
1166              
1167             examples
1168              
1169             $self->set_response_callback( 'Ext.ux.MyFunc' );
1170            
1171             $self->set_response_callback( alert => 'foo!' );
1172            
1173             $self->set_response_callback( 'Ext.Msg.alert' => ( 'A message!!', 'this is awesome!!' ) );
1174            
1175             my $func = RapidApp::JSONFunc->new(
1176             raw => 1,
1177             func => 'function(){ console.log("anon!!"); console.dir(this.response); }'
1178             );
1179             $self->response_callback_scoped(1);
1180             $self->set_response_callback(
1181             $func => ( "arg1",{ key_in_arg2 => 'blah!!!' },'arg3',\1 )
1182             );
1183              
1184             =cut
1185              
1186             # when calling set_response_callback the JS function specified will be
1187             # called after the request is completed successfully
1188             sub set_response_callback {
1189 0     0 1   my ($self, $func, @args) = @_;
1190              
1191 0           my $data = {};
1192 0 0         $data->{arguments} = [ @args ] if (scalar @args > 0);
1193            
1194 0 0         if(ref($func) eq 'RapidApp::JSONFunc') {
1195 0 0         die "only 'raw' RapidApp::JSONFunc objects are supported" unless ($func->raw);
1196 0           $data->{anonfunc} = $func;
1197             }
1198             else {
1199 0           $data->{func} = $func;
1200             }
1201            
1202 0 0         $data->{scoped} = \1 if ($self->response_callback_scoped);
1203            
1204 0           return $self->c->response->header( 'X-RapidApp-Callback' => $self->json->encode($data) );
1205             }
1206              
1207              
1208             has 'response_server_events' => (
1209             is => 'ro',
1210             isa => 'ArrayRef[Str]',
1211             traits => [ 'Array' ],
1212             default => sub {[]},
1213             handles => {
1214             add_response_server_events => 'push',
1215             all_response_server_events => 'uniq'
1216             }
1217             );
1218             after 'add_response_server_events' => sub {
1219             my $self = shift;
1220             $self->c->response->header(
1221             'X-RapidApp-ServerEvents' => $self->json->encode([ $self->all_response_server_events ])
1222             );
1223             };
1224              
1225              
1226 6     6   59 no Moose;
  6         15  
  6         62  
1227             __PACKAGE__->meta->make_immutable;
1228              
1229             1;
1230              
1231             __END__
1232              
1233             =head1 NAME
1234              
1235             RapidApp::Module - Base class for RapidApp Modules
1236              
1237             =head1 SYNOPSIS
1238              
1239             package MyApp::Module::MyModule;
1240             use Moose;
1241             extends 'RapidApp::Module';
1242              
1243             =head1 DESCRIPTION
1244              
1245             This is the base class for all RapidApp Modules. Documentation still TDB...
1246              
1247             =head1 SEE ALSO
1248              
1249             =over
1250              
1251             =item *
1252              
1253             L<RapidApp::Manual::Modules>
1254              
1255             =back
1256              
1257             =head1 AUTHOR
1258              
1259             Henry Van Styn <vanstyn@cpan.org>
1260              
1261             =head1 COPYRIGHT AND LICENSE
1262              
1263             This software is copyright (c) 2013 by IntelliTree Solutions llc.
1264              
1265             This is free software; you can redistribute it and/or modify it under
1266             the same terms as the Perl 5 programming language system itself.
1267              
1268             =cut
1269