File Coverage

blib/lib/ClearPress/controller.pm
Criterion Covered Total %
statement 322 349 92.2
branch 105 136 77.2
condition 72 104 69.2
subroutine 26 26 100.0
pod 15 15 100.0
total 540 630 85.7


line stmt bran cond sub pod time code
1             # -*- mode: cperl; tab-width: 8; indent-tabs-mode: nil; basic-offset: 2 -*-
2             # vim:ts=8:sw=2:et:sta:sts=2
3             #########
4             # Author: rmp
5             # Created: 2007-03-28
6             #
7             # method id action aspect result CRUD
8             # =====================================
9             # POST n create - create *
10             # POST y create update update *
11             # POST y create delete delete *
12             # GET n read - list
13             # GET n read add add/new
14             # GET y read - read *
15             # GET y read edit edit
16              
17             package ClearPress::controller;
18 4     4   45583 use strict;
  4         16  
  4         146  
19 4     4   82 use warnings;
  4         12  
  4         169  
20 4     4   28 use English qw(-no_match_vars);
  4         10  
  4         37  
21 4     4   2022 use Carp;
  4         10  
  4         261  
22 4     4   1777 use ClearPress::decorator;
  4         14  
  4         35  
23 4     4   1674 use ClearPress::view::error;
  4         17  
  4         36  
24 4     4   187 use CGI;
  4         11  
  4         29  
25 4     4   270 use HTTP::Status qw(:constants :is);
  4         11  
  4         1840  
26 4     4   35 use HTTP::Headers;
  4         11  
  4         18510  
27              
28             our $VERSION = q[477.1.2];
29              
30             our $CRUD = { # these map HTTP verbs to $action
31             POST => 'create',
32             GET => 'read',
33             PUT => 'update',
34             DELETE => 'delete',
35             OPTIONS => 'options',
36             HEAD => 'null',
37             TRACE => 'null',
38             };
39             our $REST = { # these assist sanitising $aspect
40             create => 'POST',
41             read => 'GET',
42             update => 'PUT|POST',
43             delete => 'DELETE|POST',
44             add => 'GET',
45             edit => 'GET',
46             list => 'GET',
47             options => 'OPTIONS',
48             null => 'HEAD|TRACE'
49             };
50              
51             sub accept_extensions {
52             return [
53 214     214 1 4437 {'.html' => q[]},
54             {'.xml' => q[_xml]},
55             {'.png' => q[_png]},
56             {'.svg' => q[_svg]},
57             {'.svgz' => q[_svgz]},
58             {'.jpg' => q[_jpg]},
59             {'.rss' => q[_rss]},
60             {'.atom' => q[_atom]},
61             {'.js' => q[_json]},
62             {'.json' => q[_json]},
63             {'.ical' => q[_ical]},
64             {'.txt' => q[_txt]},
65             {'.xls' => q[_xls]},
66             {'.csv' => q[_csv]},
67             {'.ajax' => q[_ajax]},
68             ];
69             }
70              
71             sub accept_headers {
72             return [
73             # {'text/html' => q[]},
74 107     107 1 815 {'application/json' => q[_json]},
75             {'text/xml' => q[_xml]},
76             ];
77             }
78              
79             sub new {
80 107     107 1 132102 my ($class, $self) = @_;
81 107   50     426 $self ||= {};
82 107         378 bless $self, $class;
83 107         528 $self->init();
84              
85             eval {
86             #########
87             # We may be given a database handle from the cache with an open
88             # transaction (e.g. from running a few selects), so on controller
89             # construction (effectively per-page-view), we rollback any open
90             # transaction on the database handle we've been given.
91             #
92 107         356 $self->util->dbh->rollback();
93 107         454 1;
94              
95 107 50       383 } or do {
96             #########
97             # ignore any error
98             #
99 0         0 carp qq[Failed per-request rollback on fresh database handle: $EVAL_ERROR];
100             };
101              
102 107         362 return $self;
103             }
104              
105             sub init {
106 107     107 1 220 return 1;
107             }
108              
109             sub util {
110 230     230 1 575 my ($self, $util) = @_;
111 230 50       839 if(defined $util) {
112 0         0 $self->{util} = $util;
113             }
114 230         1005 return $self->{util};
115             }
116              
117             sub packagespace {
118 137     137 1 500 my ($self, $type, $entity, $util) = @_;
119              
120 137 50 66     566 if($type ne 'view' &&
121             $type ne 'model') {
122 0         0 return;
123             }
124              
125 137   33     698 $util ||= $self->util();
126 137         312 my $entity_name = $entity;
127              
128 137 50       595 if($util->config->SectionExists('packagemap')) {
129             #########
130             # if there are uri-to-package maps, process here
131             #
132 137         3866 my $map = $util->config->val('packagemap', $entity);
133 137 100       5246 if($map) {
134 3         12 $entity = $map;
135             }
136             }
137              
138 137         800 my $namespace = $self->namespace($util);
139 137         833 return "${namespace}::${type}::$entity";
140             }
141              
142             sub process_request { ## no critic (Subroutines::ProhibitExcessComplexity)
143 107     107 1 4775 my ($self, $headers) = @_;
144 107         333 my $util = $self->util;
145 107   50     576 my $method = $ENV{REQUEST_METHOD} || 'GET';
146 107         527 my $action = $CRUD->{uc $method};
147 107   50     4042 my $pi = $ENV{PATH_INFO} || q[];
148 107   100     786 my $accept = $ENV{HTTP_ACCEPT} || q[];
149 107   100     526 my $qs = $ENV{QUERY_STRING} || q[];
150 107   100     501 my $hxrw = $ENV{HTTP_X_REQUESTED_WITH} || q[];
151 107         429 my $xhr = ($hxrw =~ /XMLHttpRequest/smix);
152              
153             my $accept_extensions = join q[|],
154 1605         3942 grep { defined }
155 1605 50       5741 map { m{[.](\S+)$}smx; $1 || undef; } ## no critic (ProhibitCaptureWithoutTest, ProhibitComplexMappings)
  1605         6735  
156 1605         3272 map { join q[,], keys %{$_} }
  1605         6908  
157 107         344 @{$self->accept_extensions()};
  107         1127  
158              
159 107 100 100     1812 if($xhr && $pi !~ m{(?:$accept_extensions)(?:/[^/]*?)?$}smx) {
160 6 100       475 if($pi =~ /[;]/smx) {
161 1         6 $pi .= q[_ajax];
162             } else {
163 5         25 $pi .= q[.ajax];
164             }
165             }
166              
167 107         701 my ($entity) = $pi =~ m{^/([^/;.]+)}smx;
168 107   100     509 $entity ||= q[];
169 107         4123 my ($dummy, $aspect_extra, $id) = $pi =~ m{^/$entity(/(.*))?/([[:lower:][:digit:]:,\-_%@.+\s]+)}smix;
170              
171 107         563 my ($aspect) = $pi =~ m{;(\S+)}smx;
172              
173 107 100 100     718 if($action eq 'read' && !$id && !$aspect) {
      100        
174 12         43 $aspect = 'list';
175             }
176              
177 107 100 100     500 if($action eq 'create' && $id) {
178 22 100 100     141 if(!$aspect || $aspect =~ /^update/smx) {
    100          
179 19         49 $action = 'update';
180              
181             } elsif($aspect =~ /^delete/smx) {
182 1         5 $action = 'delete';
183             }
184             }
185              
186 107   100     493 $aspect ||= q[];
187 107   100     504 $aspect_extra ||= q[];
188              
189             #########
190             # process request extensions
191             #
192 107   100     502 my $uriaspect = $self->_process_request_extensions(\$pi, $aspect, $action) || q[];
193 107 100       643 if($uriaspect ne $aspect) {
194 45         181 $aspect = $uriaspect;
195 45         3019 ($id) = $pi =~ m{^/$entity/?$aspect_extra/([[:lower:][:digit:]:,\-_%@.+\s]+)}smix;
196             }
197              
198             #########
199             # process HTTP 'Accept' header
200             #
201 107         922 $aspect = $self->_process_request_headers(\$accept, $aspect, $action);
202 107   100     389 $entity ||= $util->config->val('application', 'default_view');
203 107   100     632 $aspect ||= q[];
204 107   100     915 $id = CGI->unescape($id||'0');
205              
206             #########
207             # no view determined and no configured default_view
208             # pull the first one off the list
209             #
210 107 100       3249 if(!$entity) {
211 2   100     21 my $views = $util->config->val('application', 'views') || q[];
212 2         202 $entity = (split /[\s,]+/smx, $views)[0];
213             }
214              
215             #########
216             # no view determined, no default_view and none in the list
217             #
218 107 100       343 if(!$entity) {
219 1         507 croak q[No available views];
220             }
221              
222 106         417 my $viewclass = $self->packagespace('view', $entity, $util);
223              
224 106 100       470 if($aspect_extra) {
225 27         96 $aspect_extra =~ s{/}{_}smxg;
226             }
227              
228 106 100       830 if($id eq '0') {
    100          
    100          
229             #########
230             # no primary key:
231             # /thing;method
232             # /thing;method_xml
233             # /thing.xml;method
234             #
235 34   66     137 my $tmp = $aspect || $action;
236 34 50       136 if($aspect_extra) {
237 0         0 $tmp =~ s/_/_${aspect_extra}_/smx;
238              
239 0 0       0 if($viewclass->can($tmp)) {
240 0         0 $aspect = $tmp;
241             }
242             }
243              
244             } elsif($id !~ /^\d+$/smx) {
245             #########
246             # mangled primary key - attempt to match method in view object
247             # /thing/method => list_thing_method (if exists), or read(pk=method)
248             # /thing/part1/part2 => list_thing_part1_part2 if exists, or read_thing_part1(pk=part2)
249             # /thing/method.xml => list_thing_method_xml (if exists), or read_thing_xml (pk=method)
250             # /thing/part1/part2.xml => list_thing_part1_part2_xml (if exists), or read_thing_part1_xml (pk=part2)
251             #
252              
253 23         72 my $tmp = $aspect;
254              
255 23 100       103 if($tmp =~ /_/smx) {
256 16         152 $tmp =~ s/_/_${id}_/smx;
257              
258             } else {
259 7         26 $tmp = "${action}_$id";
260              
261             }
262              
263 23         137 $tmp =~ s/^read/list/smx;
264 23         211 $tmp =~ s/^update/create/smx;
265              
266 23 100       191 if($aspect_extra) {
267 6         46 $tmp =~ s/_/_${aspect_extra}_/smx;
268             }
269              
270 23 100       668 if($viewclass->can($tmp)) {
271 15         58 $id = 0;
272 15         47 $aspect = $tmp;
273              
274             #########
275             # id has been modified, so reset action
276             #
277 15 100       94 if($aspect =~ /^create/smx) {
278 5         26 $action = 'create';
279             }
280              
281             } else {
282 8 100       35 if($aspect_extra) {
283 4 100       21 if($aspect =~ /_/smx) {
284 3         24 $aspect =~ s/_/_${aspect_extra}_/smx;
285             } else {
286 1         6 $aspect .= "_$aspect_extra";
287             }
288             }
289             }
290              
291             } elsif($aspect_extra) {
292             #########
293             # /thing/method/50 => read_thing_method(pk=50)
294             #
295 21 100       126 if($aspect =~ /_/smx) {
296 11         98 $aspect =~ s/_/_${aspect_extra}_/smx;
297             } else {
298 10         35 $aspect .= "${action}_$aspect_extra";
299             }
300             }
301              
302             #########
303             # fix up aspect
304             #
305 106         1720 my ($firstpart) = $aspect =~ /^${action}_([^_]+)_?/smx;
306 106 100       640 if($firstpart) {
307 49         182 my $restpart = $REST->{$firstpart};
308 49 100       169 if($restpart) {
309 8         28 ($restpart) = $restpart =~ /^([^|]+)/smx;
310 8 50       24 if($restpart) {
311 8         17 my ($crudpart) = $CRUD->{$restpart};
312 8 50       18 if($crudpart) {
313 8         60 $aspect =~ s/^${crudpart}_//smx;
314             }
315             }
316             }
317             }
318              
319 106 100       596 if($aspect !~ /^(?:create|read|update|delete|add|list|edit|options)/smx) {
320 19         67 my $action_extended = $action;
321 19 100       78 if(!$id) {
322             $action_extended = {
323             read => 'list',
324 8   66     132 }->{$action} || $action_extended;
325             }
326              
327 19 100       219 $aspect = $action_extended . ($aspect?"_$aspect":q[]);
328             }
329              
330             # if($method eq 'OPTIONS') {
331             # $action = 'options';
332             # $aspect = 'options';
333             # }
334              
335             #########
336             # sanity checks
337             #
338 106         669 my ($type) = $aspect =~ /^([^_]+)/smx; # read|list|add|edit|create|update|delete
339 106 100       1278 if($method !~ /^$REST->{$type}$/smx) {
340 3         23 $headers->header('Status', HTTP_BAD_REQUEST);
341 3         566 croak qq[Bad request. $aspect ($type) is not a $CRUD->{$method} method];
342             }
343              
344 103 100 100     675 if(!$id &&
345             $aspect =~ /^(?:delete|update|edit|read)/smx) {
346 4         22 $headers->header('Status', HTTP_BAD_REQUEST);
347 4         592 croak qq[Bad request. Cannot $aspect without an id];
348             }
349              
350 99 100 100     977 if($id &&
351             $aspect =~ /^(?:create|add|list)/smx) {
352 3         15 $headers->header('Status', HTTP_BAD_REQUEST);
353 3         398 croak qq[Bad request. Cannot $aspect with an id];
354             }
355              
356 96         306 $aspect =~ s/__/_/smxg;
357 96         818 return ($action, $entity, $aspect, $id);
358             }
359              
360             sub _process_request_extensions {
361 107     107   522 my ($self, $pi, $aspect, $action) = @_;
362              
363 107         230 my $extensions = join q[], reverse ${$pi} =~ m{([.][^;.]+)}smxg;
  107         702  
364              
365 107         257 for my $pair (@{$self->accept_extensions}) {
  107         330  
366 1605         3676 my ($ext, $meth) = %{$pair};
  1605         6285  
367 1605         6208 $ext =~ s/[.]/\\./smxg;
368              
369 1605 100       21752 if($extensions =~ s{$ext$}{}smx) {
370 45         118 ${$pi} =~ s{$ext}{}smx;
  45         2266  
371 45   66     281 $aspect ||= $action;
372 45         396 $aspect =~ s/$meth$//smx;
373 45         171 $aspect .= $meth;
374             }
375             }
376              
377 107         1510 return $aspect;
378             }
379              
380             sub _process_request_headers {
381 107     107   379 my ($self, $accept, $aspect, $action) = @_;
382              
383 107         214 for my $pair (@{$self->accept_headers()}) {
  107         374  
384 214         486 my ($header, $meth) = %{$pair};
  214         904  
385 214 100       448 if(${$accept} =~ /$header$/smx) {
  214         3211  
386 1   33     7 $aspect ||= $action;
387 1         17 $aspect =~ s/$meth$//smx;
388 1         5 $aspect .= $meth;
389 1         5 last;
390             }
391             }
392              
393 107         586 return $aspect;
394             }
395              
396             sub decorator {
397 46     46 1 152 my ($self, $util, $headers) = @_;
398              
399 46 100       233 if(!$self->{decorator}) {
400 15   50     92 my $appname = $util->config->val('application', 'name') || 'Application';
401 15         843 my $namespace = $self->namespace;
402 15         111 my $decorpkg = "${namespace}::decorator";
403 15         76 my $config = $util->config;
404 15         42 my $decor;
405              
406 15         75 my $ref = {
407             headers => $headers,
408             };
409             eval {
410 15         328 $decor = $decorpkg->new($ref);
411 0         0 1;
412 15 50       45 } or do {
413 15         166 $decor = ClearPress::decorator->new($ref);
414             };
415              
416 15         104 for my $field ($decor->fields) {
417 330         996 $decor->$field($config->val('application', $field));
418             }
419              
420 15 50       102 if(!$decor->title) {
421 15   50     82 $decor->title($config->val('application', 'name') || 'ClearPress Application');
422             }
423              
424             #########
425             # only cache decorator when $headers is passed
426             #
427 15 50       968 if($headers) {
428 15         70 $self->{decorator} = $decor;
429             }
430             }
431              
432 46         399 return $self->{decorator};
433             }
434              
435             sub session {
436 15     15 1 62 my ($self, $util) = @_;
437 15   50     96 return $self->decorator($util || $self->util())->session() || {};
438             }
439              
440             sub handler {
441 15     15 1 4702 my ($self, $util) = @_;
442 15 100       89 if(!ref $self) {
443 13         97 $self = $self->new({util => $util});
444             }
445              
446 15         157 my $headers = HTTP::Headers->new();
447 15         243 my $cgi = $util->cgi();
448 15         94 my $decorator = $self->decorator($util, $headers);
449 15         65 my $namespace = $self->namespace($util);
450              
451 15         126 $headers->header('Status', HTTP_OK);
452 15         1585 $headers->header('X-Generator', 'ClearPress');
453              
454             #########
455             # no obvious right place for this
456             #
457 15         1042 my $lang = $decorator->lang;
458 15 50 50     85 if($lang && scalar @{$lang}) {
  15         79  
459 15         47 $headers->header('Content-Language', join q[,], @{$lang});
  15         83  
460             }
461              
462 15         845 my ($action, $entity, $aspect, $id, $process_request_error);
463             eval {
464 15         96 ($action, $entity, $aspect, $id) = $self->process_request($headers);
465 15         75 1;
466 15 50       51 } or do {
467 0         0 carp qq[CAUGHT $EVAL_ERROR];
468 0         0 $process_request_error = $EVAL_ERROR;
469             };
470              
471 15         145 my $params = {
472             util => $util,
473             entity => $entity,
474             aspect => $aspect,
475             action => $action,
476             id => $id,
477             headers => $headers,
478             };
479             #########
480             # initial header block
481             #
482 15   50     228 $headers->header('Content-Type', ClearPress::view->new($params)->content_type || 'text/html'); # don't forget to add charset
483              
484 15         1285 for my $cookie ($decorator->cookie) {
485 0         0 $self->{headers}->push_header('Set-Cookie', $_);
486             }
487              
488 15 50       69 if($process_request_error) {
489             #########
490             # deferred error handling
491             #
492 0         0 return $self->handle_error($process_request_error, $headers);
493             }
494              
495 15         84 $util->username($decorator->username());
496 15         322 $util->session($self->session($util));
497              
498 15         215 my $viewobject;
499             eval {
500 15         86 $viewobject = $self->dispatch($params);
501 15         76 1;
502 15 50       42 } or do {
503 0         0 return $self->handle_error($EVAL_ERROR, $headers);
504             };
505              
506 15         131 my $decor = $viewobject->decor(); # boolean
507              
508             #########
509             # let the view have the decorator in case it wants to modify headers
510             #
511 15         142 $viewobject->decorator($decorator);
512              
513 15         343 my $charset = $viewobject->charset();
514 15 50 33     336 $charset = ($charset && !exists $ENV{REDIRECT_STATUS}) ? qq[;charset=$charset] : q[];
515 15         96 my $content_type = sprintf q[%s%s], $viewobject->content_type(), $charset;
516              
517             #########
518             # update the content-type/charset with whatever the view determined was right for the response
519             #
520 15         329 $headers->header('Content-Type', $content_type);
521              
522 15 100       926 if($decor) {
523             # if($content_type =~ /text/smx && $charset =~ /utf-?8/smix) {
524             # binmode STDOUT, q[:encoding(UTF-8)]; # is this useful? If so, should it be less conditional?
525             # }
526              
527             #########
528             # decorated header
529             #
530 6         40 $viewobject->output_buffer($decorator->header());
531             }
532              
533 15         47 my $errstr;
534             eval {
535             #########
536             # view->render() may be streamed
537             #
538 15 100       117 if($viewobject->streamed) {
539             #########
540             # ->render is responsible for all (decorated/undecorated) output
541             #
542 1         14 $viewobject->render();
543              
544             } else {
545             #########
546             # output returned content
547             #
548 14         93 $viewobject->output_buffer($viewobject->render());
549             }
550              
551 14         74 1;
552 15 100       40 } or do {
553             #########
554             # 1. reset pending output_buffer (different view object)
555             # 2. set up error response w/headers
556             # 3. emit headers
557             # 4. hand off to error response handler
558             #
559 1         103 carp qq[controller::handler: view->render failed: $EVAL_ERROR];
560 1         105 $viewobject->output_reset(); # reset headers on the original view
561 1         6 $self->errstr($EVAL_ERROR);
562              
563 1         9 my $code = $headers->header('Status');
564              
565 1 50 33     59 if(!$code || $code == HTTP_OK) {
566 1         4 $headers->header('Status', HTTP_INTERNAL_SERVER_ERROR);
567             }
568              
569             # my $content_type = $headers->header('Content-Type');
570 1         44 $content_type =~ s{;.*$}{}smx;
571 1         5 $headers->header('Content-Type', $content_type); # ErrorDocuments seem to have a bit of trouble with content-encoding errors so strip the charset
572              
573 1         38 return $self->handle_error(undef, $headers); # hand off
574             };
575              
576             #########
577             # prepend all response headers (and header block termination)
578             #
579 14         121 $viewobject->output_prepend($headers->as_string, "\n");
580              
581             #########
582             # re-test decor in case it's changed by render()
583             #
584 14 100       87 if($viewobject->decor()) {
585 5         40 $viewobject->output_buffer($decorator->footer());
586             }
587              
588             #########
589             # flush everything left to client socket (via stdout)
590             #
591 14         116 $viewobject->output_end();
592              
593             #########
594             # save the session after the request has processed
595             #
596 14         113 $decorator->save_session();
597              
598             #########
599             # clean up any shared state so it's not carried over (e.g. incomplete transactions)
600             #
601 14         121 $util->cleanup();
602              
603 14         798 return 1;
604             }
605              
606             sub handle_error {
607 1     1 1 4 my ($self, $errstr, $headers) = @_;
608 1         6 my $util = $self->util;
609 1         5 my $decorator = $self->decorator();
610 1         5 my $namespace = $self->namespace();
611 1         6 my ($action, $entity, $aspect, $id) = $self->process_request($headers);
612              
613             # if running in mod_perl, main request serves a bad status header and errordocument is handled by a subrequest
614             # if running in CGI, main request serves a bad status header and follows with errordocument content
615              
616             #########
617             # force reconstruction of CGI object from subrequest QUERY_STRING
618             #
619 1         4 delete $util->{cgi};
620              
621             #########
622             # but pass-through the errstr
623             #
624 1   33     6 $util->cgi->param('errstr', CGI::escape($errstr || $self->errstr));
625              
626             #########
627             # non-mod-perl errordocument handled by application internals
628             #
629 1         63 my $error_ns = sprintf q[%s::view::error], $namespace;
630 1         8 my $params = {
631             util => $util,
632             action => $action,
633             aspect => $aspect,
634             headers => $headers, # same header block as original response? hmm.
635             decorator => $decorator,
636             };
637              
638 1         3 my $viewobject;
639             eval {
640 1         53 $viewobject = $error_ns->new($params);
641 0         0 1;
642 1 50       2 } or do {
643 1         24 $viewobject = ClearPress::view::error->new($params);
644             };
645              
646 1         11 my $decor = $viewobject->decor();
647 1         3 my $header = q[];
648 1         3 my $footer = q[];
649              
650 1         6 $viewobject->output_reset();
651              
652 1         5 my $body = $viewobject->render;
653              
654 1 50       5 if($viewobject->decor) {
655 1         9 $header = $decorator->header;
656 1         6 $footer = $decorator->footer;
657             }
658              
659 1         4 my $str = $header . $body . $footer;
660              
661 1         8 $viewobject->output_prepend($headers->as_string, "\n");
662 1         8 $viewobject->output_buffer($str);
663 1         6 $viewobject->output_end();
664 1         5 $decorator->save_session();
665 1         9 $util->cleanup();
666              
667 1         60 return;
668             }
669              
670             sub namespace {
671 168     168 1 471 my ($self, $util) = @_;
672 168         529 my $ns = q[];
673              
674 168 100 66     1352 if((ref $self && !$self->{namespace}) || !ref $self) {
      66        
675 106   66     401 $util ||= $self->util();
676 106   50     383 $ns = $util->config->val('application', 'namespace') ||
677             $util->config->val('application', 'name') ||
678             'ClearPress';
679 106 50       4002 if(ref $self) {
680 106         343 $self->{namespace} = $ns;
681             }
682             } else {
683 62         161 $ns = $self->{namespace};
684             }
685              
686 168         550 return $ns;
687             }
688              
689             sub is_valid_view {
690 15     15 1 62 my ($self, $ref, $viewname) = @_;
691 15         53 my $util = $ref->{util};
692 15         84 my @entities = split /[,\s]+/smx, $util->config->val('application','views');
693              
694 15         672 for my $ent (@entities) {
695 57 100       177 if($ent eq $viewname) {
696 15         72 return 1;
697             }
698             }
699              
700 0         0 return;
701             }
702              
703             sub errstr {
704 2     2 1 8 my ($self, $str) = @_;
705              
706 2 50       7 if($str) {
707 0         0 $self->{errstr} = $str;
708             }
709              
710 2         11 return $self->{errstr};
711             }
712              
713             sub dispatch {
714 15     15 1 59 my ($self, $ref) = @_;
715 15         52 my $util = $ref->{util};
716 15         46 my $entity = $ref->{entity};
717 15         49 my $aspect = $ref->{aspect};
718 15         44 my $action = $ref->{action};
719 15         50 my $id = $ref->{id};
720 15         42 my $headers = $ref->{headers};
721 15         35 my $viewobject;
722              
723 15         84 my $state = $self->is_valid_view($ref, $entity);
724 15 50       68 if(!$state) {
725 0         0 $headers->header('Status', HTTP_NOT_FOUND);
726 0         0 croak qq[No such view ($entity). Is it in your config.ini?];
727             }
728              
729 15         43 my $entity_name = $entity;
730 15         72 my $viewclass = $self->packagespace('view', $entity, $util);
731              
732 15         108 my $modelobject;
733 15 50       77 if($entity ne 'error') {
734 15         66 my $modelclass = $self->packagespace('model', $entity, $util);
735             eval {
736 15         220 my $modelpk = $modelclass->primary_key();
737 15 100       235 $modelobject = $modelclass->new({
738             util => $util,
739             $modelpk?($modelpk => $id):(),
740             });
741 15         78 1;
742 15 50       46 } or do {
743             # bail out
744              
745 0         0 my $code = $headers->header('Status');
746              
747 0 0 0     0 if(!$code || $code == HTTP_OK) {
748 0         0 $headers->header('Status', HTTP_INTERNAL_SERVER_ERROR);
749 0         0 croak qq[Failed to instantiate $entity model: $EVAL_ERROR];
750             }
751              
752 0         0 croak $EVAL_ERROR;
753             };
754             }
755              
756             eval {
757 15         75 $viewobject = $viewclass->new({
758             util => $util,
759             model => $modelobject,
760             action => $action,
761             aspect => $aspect,
762             entity_name => $entity_name,
763             decorator => $self->decorator,
764             headers => $headers,
765             });
766 15         82 1;
767 15 50       40 } or do {
768 0         0 my $code = $headers->header('Status');
769              
770 0 0 0     0 if(!$code || $code == HTTP_OK) {
771 0         0 $headers->header('Status', HTTP_INTERNAL_SERVER_ERROR);
772 0         0 croak qq[Failed to instantiate $entity view: $EVAL_ERROR];
773             }
774              
775 0         0 croak $EVAL_ERROR;
776             };
777              
778 15         65 return $viewobject;
779             }
780              
781             1;
782             __END__