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   38231 use strict;
  4         10  
  4         128  
19 4     4   26 use warnings;
  4         8  
  4         136  
20 4     4   26 use English qw(-no_match_vars);
  4         9  
  4         34  
21 4     4   1719 use Carp;
  4         11  
  4         280  
22 4     4   1417 use ClearPress::decorator;
  4         12  
  4         28  
23 4     4   1403 use ClearPress::view::error;
  4         24  
  4         39  
24 4     4   157 use CGI;
  4         11  
  4         34  
25 4     4   276 use HTTP::Status qw(:constants :is);
  4         10  
  4         1728  
26 4     4   35 use HTTP::Headers;
  4         11  
  4         16290  
27              
28             our $VERSION = q[477.1.4];
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 238     238 1 4980 {'.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 119     119 1 773 {'application/json' => q[_json]},
75             {'text/xml' => q[_xml]},
76             ];
77             }
78              
79             sub new {
80 119     119 1 91538 my ($class, $self) = @_;
81 119   50     612 $self ||= {};
82 119         345 bless $self, $class;
83 119         443 $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 119         466 $self->util->dbh->rollback();
93 119         685 1;
94              
95 119 50       260 } 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 119         426 return $self;
103             }
104              
105             sub init {
106 119     119 1 239 return 1;
107             }
108              
109             sub util {
110 254     254 1 571 my ($self, $util) = @_;
111 254 50       692 if(defined $util) {
112 0         0 $self->{util} = $util;
113             }
114 254         1108 return $self->{util};
115             }
116              
117             sub packagespace {
118 149     149 1 492 my ($self, $type, $entity, $util) = @_;
119              
120 149 50 66     562 if($type ne 'view' &&
121             $type ne 'model') {
122 0         0 return;
123             }
124              
125 149   33     414 $util ||= $self->util();
126 149         295 my $entity_name = $entity;
127              
128 149 50       607 if($util->config->SectionExists('packagemap')) {
129             #########
130             # if there are uri-to-package maps, process here
131             #
132 149         4081 my $map = $util->config->val('packagemap', $entity);
133 149 100       4916 if($map) {
134 3         8 $entity = $map;
135             }
136             }
137              
138 149         512 my $namespace = $self->namespace($util);
139 149         642 return "${namespace}::${type}::$entity";
140             }
141              
142             sub process_request { ## no critic (Subroutines::ProhibitExcessComplexity)
143 119     119 1 2087 my ($self, $headers) = @_;
144 119         308 my $util = $self->util;
145 119   50     504 my $method = $ENV{REQUEST_METHOD} || 'GET';
146 119         492 my $action = $CRUD->{uc $method};
147 119   50     435 my $pi = $ENV{PATH_INFO} || q[];
148 119   100     580 my $accept = $ENV{HTTP_ACCEPT} || q[];
149 119   100     502 my $qs = $ENV{QUERY_STRING} || q[];
150 119   100     475 my $hxrw = $ENV{HTTP_X_REQUESTED_WITH} || q[];
151 119         377 my $xhr = ($hxrw =~ /XMLHttpRequest/smix);
152              
153             my $accept_extensions = join q[|],
154 1785         4252 grep { defined }
155 1785 50       5714 map { m{[.](\S+)$}smx; $1 || undef; } ## no critic (ProhibitCaptureWithoutTest, ProhibitComplexMappings)
  1785         7050  
156 1785         4179 map { join q[,], keys %{$_} }
  1785         7228  
157 119         255 @{$self->accept_extensions()};
  119         365  
158              
159 119 100 100     1706 if($xhr && $pi !~ m{(?:$accept_extensions)(?:/[^/]*?)?$}smx) {
160 6 100       43 if($pi =~ /[;]/smx) {
161 1         3 $pi .= q[_ajax];
162             } else {
163 5         25 $pi .= q[.ajax];
164             }
165             }
166              
167 119         761 my ($entity) = $pi =~ m{^/([^/;.]+)}smx;
168 119   100     458 $entity ||= q[];
169 119         4051 my ($dummy, $aspect_extra, $id) = $pi =~ m{^/$entity(/(.*))?/([[:lower:][:digit:]:,\-_%@.+\s]+)}smix;
170              
171 119         786 my ($aspect) = $pi =~ m{;(\S+)}smx;
172              
173 119 100 100     679 if($action eq 'read' && !$id && !$aspect) {
      100        
174 12         36 $aspect = 'list';
175             }
176              
177 119 100 100     657 if($action eq 'create' && $id) {
178 28 100 100     166 if(!$aspect || $aspect =~ /^update/smx) {
    100          
179 25         68 $action = 'update';
180              
181             } elsif($aspect =~ /^delete/smx) {
182 1         3 $action = 'delete';
183             }
184             }
185              
186 119   100     505 $aspect ||= q[];
187 119   100     555 $aspect_extra ||= q[];
188              
189             #########
190             # process request extensions
191             #
192 119   100     546 my $uriaspect = $self->_process_request_extensions(\$pi, $aspect, $action) || q[];
193 119 100       409 if($uriaspect ne $aspect) {
194 53         130 $aspect = $uriaspect;
195 53         3162 ($id) = $pi =~ m{^/$entity/?$aspect_extra/([[:lower:][:digit:]:,\-_%@.+\s]+)}smix;
196             }
197              
198             #########
199             # process HTTP 'Accept' header
200             #
201 119         564 $aspect = $self->_process_request_headers(\$accept, $aspect, $action);
202 119   100     444 $entity ||= $util->config->val('application', 'default_view');
203 119   100     572 $aspect ||= q[];
204 119   100     992 $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 119 100       3220 if(!$entity) {
211 2   100     10 my $views = $util->config->val('application', 'views') || q[];
212 2         58 $entity = (split /[\s,]+/smx, $views)[0];
213             }
214              
215             #########
216             # no view determined, no default_view and none in the list
217             #
218 119 100       341 if(!$entity) {
219 1         159 croak q[No available views];
220             }
221              
222 118         417 my $viewclass = $self->packagespace('view', $entity, $util);
223              
224 118 100       385 if($aspect_extra) {
225 39         141 $aspect_extra =~ s{/}{_}smxg;
226             }
227              
228 118 100       815 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     119 my $tmp = $aspect || $action;
236 34 50       108 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 35         75 my $tmp = $aspect;
254              
255 35 100       111 if($tmp =~ /_/smx) {
256 24         169 $tmp =~ s/_/_${id}_/smx;
257              
258             } else {
259 11         42 $tmp = "${action}_$id";
260              
261             }
262              
263 35         172 $tmp =~ s/^read/list/smx;
264 35         119 $tmp =~ s/^update/create/smx;
265              
266 35 100       111 if($aspect_extra) {
267 18         93 $tmp =~ s/_/_${aspect_extra}_/smx;
268             }
269              
270 35 100       532 if($viewclass->can($tmp)) {
271 15         53 $id = 0;
272 15         34 $aspect = $tmp;
273              
274             #########
275             # id has been modified, so reset action
276             #
277 15 100       74 if($aspect =~ /^create/smx) {
278 5         11 $action = 'create';
279             }
280              
281             } else {
282 20 100       58 if($aspect_extra) {
283 16 100       47 if($aspect =~ /_/smx) {
284 11         55 $aspect =~ s/_/_${aspect_extra}_/smx;
285             } else {
286 5         22 $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       114 if($aspect =~ /_/smx) {
296 11         94 $aspect =~ s/_/_${aspect_extra}_/smx;
297             } else {
298 10         43 $aspect .= "${action}_$aspect_extra";
299             }
300             }
301              
302             #########
303             # fix up aspect
304             #
305 118         1515 my ($firstpart) = $aspect =~ /^${action}_([^_]+)_?/smx;
306 118 100       384 if($firstpart) {
307 57         189 my $restpart = $REST->{$firstpart};
308 57 100       173 if($restpart) {
309 8         45 ($restpart) = $restpart =~ /^([^|]+)/smx;
310 8 50       32 if($restpart) {
311 8         31 my ($crudpart) = $CRUD->{$restpart};
312 8 50       30 if($crudpart) {
313 8         91 $aspect =~ s/^${crudpart}_//smx;
314             }
315             }
316             }
317             }
318              
319 118 100       653 if($aspect !~ /^(?:create|read|update|delete|add|list|edit|options)/smx) {
320 23         58 my $action_extended = $action;
321 23 100       86 if(!$id) {
322             $action_extended = {
323             read => 'list',
324 8   66     75 }->{$action} || $action_extended;
325             }
326              
327 23 100       107 $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 118         578 my ($type) = $aspect =~ /^([^_]+)/smx; # read|list|add|edit|create|update|delete
339 118 100       1269 if($method !~ /^$REST->{$type}$/smx) {
340 3         15 $headers->header('Status', HTTP_BAD_REQUEST);
341 3         412 croak qq[Bad request. $aspect ($type) is not a $CRUD->{$method} method];
342             }
343              
344 115 100 100     591 if(!$id &&
345             $aspect =~ /^(?:delete|update|edit|read)/smx) {
346 4         37 $headers->header('Status', HTTP_BAD_REQUEST);
347 4         622 croak qq[Bad request. Cannot $aspect without an id];
348             }
349              
350 111 100 100     541 if($id &&
351             $aspect =~ /^(?:create|add|list)/smx) {
352 3         13 $headers->header('Status', HTTP_BAD_REQUEST);
353 3         354 croak qq[Bad request. Cannot $aspect with an id];
354             }
355              
356 108         370 $aspect =~ s/__/_/smxg;
357 108         963 return ($action, $entity, $aspect, $id);
358             }
359              
360             sub _process_request_extensions {
361 119     119   445 my ($self, $pi, $aspect, $action) = @_;
362              
363 119         241 my $extensions = join q[], reverse ${$pi} =~ m{([.][^;.]+)}smxg;
  119         679  
364              
365 119         297 for my $pair (@{$self->accept_extensions}) {
  119         318  
366 1785         3733 my ($ext, $meth) = %{$pair};
  1785         6216  
367 1785         6618 $ext =~ s/[.]/\\./smxg;
368              
369 1785 100       19455 if($extensions =~ s{$ext$}{}smx) {
370 53         141 ${$pi} =~ s{$ext}{}smx;
  53         598  
371 53   66     399 $aspect ||= $action;
372 53         366 $aspect =~ s/$meth$//smx;
373 53         240 $aspect .= $meth;
374             }
375             }
376              
377 119         1438 return $aspect;
378             }
379              
380             sub _process_request_headers {
381 119     119   392 my ($self, $accept, $aspect, $action) = @_;
382              
383 119         223 for my $pair (@{$self->accept_headers()}) {
  119         459  
384 238         521 my ($header, $meth) = %{$pair};
  238         817  
385 238 100       501 if(${$accept} =~ /$header$/smx) {
  238         3446  
386 1   33     4 $aspect ||= $action;
387 1         8 $aspect =~ s/$meth$//smx;
388 1         3 $aspect .= $meth;
389 1         3 last;
390             }
391             }
392              
393 119         632 return $aspect;
394             }
395              
396             sub decorator {
397 46     46 1 149 my ($self, $util, $headers) = @_;
398              
399 46 100       178 if(!$self->{decorator}) {
400 15   50     91 my $appname = $util->config->val('application', 'name') || 'Application';
401 15         778 my $namespace = $self->namespace;
402 15         61 my $decorpkg = "${namespace}::decorator";
403 15         64 my $config = $util->config;
404 15         38 my $decor;
405              
406 15         73 my $ref = {
407             headers => $headers,
408             };
409             eval {
410 15         431 $decor = $decorpkg->new($ref);
411 0         0 1;
412 15 50       49 } or do {
413 15         154 $decor = ClearPress::decorator->new($ref);
414             };
415              
416 15         119 for my $field ($decor->fields) {
417 330         936 $decor->$field($config->val('application', $field));
418             }
419              
420 15 50       109 if(!$decor->title) {
421 15   50     81 $decor->title($config->val('application', 'name') || 'ClearPress Application');
422             }
423              
424             #########
425             # only cache decorator when $headers is passed
426             #
427 15 50       827 if($headers) {
428 15         70 $self->{decorator} = $decor;
429             }
430             }
431              
432 46         372 return $self->{decorator};
433             }
434              
435             sub session {
436 15     15 1 54 my ($self, $util) = @_;
437 15   50     92 return $self->decorator($util || $self->util())->session() || {};
438             }
439              
440             sub handler {
441 15     15 1 4518 my ($self, $util) = @_;
442 15 100       82 if(!ref $self) {
443 13         96 $self = $self->new({util => $util});
444             }
445              
446 15         158 my $headers = HTTP::Headers->new();
447 15         238 my $cgi = $util->cgi();
448 15         89 my $decorator = $self->decorator($util, $headers);
449 15         74 my $namespace = $self->namespace($util);
450              
451 15         115 $headers->header('Status', HTTP_OK);
452 15         1625 $headers->header('X-Generator', 'ClearPress');
453              
454             #########
455             # no obvious right place for this
456             #
457 15         1104 my $lang = $decorator->lang;
458 15 50 50     98 if($lang && scalar @{$lang}) {
  15         81  
459 15         46 $headers->header('Content-Language', join q[,], @{$lang});
  15         91  
460             }
461              
462 15         943 my ($action, $entity, $aspect, $id, $process_request_error);
463             eval {
464 15         96 ($action, $entity, $aspect, $id) = $self->process_request($headers);
465 15         78 1;
466 15 50       46 } or do {
467 0         0 carp qq[CAUGHT $EVAL_ERROR];
468 0         0 $process_request_error = $EVAL_ERROR;
469             };
470              
471 15         141 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     249 $headers->header('Content-Type', ClearPress::view->new($params)->content_type || 'text/html'); # don't forget to add charset
483              
484 15         1318 for my $cookie ($decorator->cookie) {
485 0         0 $self->{headers}->push_header('Set-Cookie', $_);
486             }
487              
488 15 50       68 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         77 $util->username($decorator->username());
496 15         300 $util->session($self->session($util));
497              
498 15         203 my $viewobject;
499             eval {
500 15         84 $viewobject = $self->dispatch($params);
501 15         68 1;
502 15 50       41 } or do {
503 0         0 return $self->handle_error($EVAL_ERROR, $headers);
504             };
505              
506 15         108 my $decor = $viewobject->decor(); # boolean
507              
508             #########
509             # let the view have the decorator in case it wants to modify headers
510             #
511 15         116 $viewobject->decorator($decorator);
512              
513 15         286 my $charset = $viewobject->charset();
514 15 50 33     301 $charset = ($charset && !exists $ENV{REDIRECT_STATUS}) ? qq[;charset=$charset] : q[];
515 15         107 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         283 $headers->header('Content-Type', $content_type);
521              
522 15 100       943 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         39 $viewobject->output_buffer($decorator->header());
531             }
532              
533 15         45 my $errstr;
534             eval {
535             #########
536             # view->render() may be streamed
537             #
538 15 100       97 if($viewobject->streamed) {
539             #########
540             # ->render is responsible for all (decorated/undecorated) output
541             #
542 1         7 $viewobject->render();
543              
544             } else {
545             #########
546             # output returned content
547             #
548 14         90 $viewobject->output_buffer($viewobject->render());
549             }
550              
551 14         69 1;
552 15 100       45 } 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         173 carp qq[controller::handler: view->render failed: $EVAL_ERROR];
560 1         165 $viewobject->output_reset(); # reset headers on the original view
561 1         9 $self->errstr($EVAL_ERROR);
562              
563 1         9 my $code = $headers->header('Status');
564              
565 1 50 33     91 if(!$code || $code == HTTP_OK) {
566 1         6 $headers->header('Status', HTTP_INTERNAL_SERVER_ERROR);
567             }
568              
569             # my $content_type = $headers->header('Content-Type');
570 1         76 $content_type =~ s{;.*$}{}smx;
571 1         9 $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         67 return $self->handle_error(undef, $headers); # hand off
574             };
575              
576             #########
577             # prepend all response headers (and header block termination)
578             #
579 14         122 $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         105 $decorator->save_session();
597              
598             #########
599             # clean up any shared state so it's not carried over (e.g. incomplete transactions)
600             #
601 14         113 $util->cleanup();
602              
603 14         880 return 1;
604             }
605              
606             sub handle_error {
607 1     1 1 6 my ($self, $errstr, $headers) = @_;
608 1         6 my $util = $self->util;
609 1         7 my $decorator = $self->decorator();
610 1         6 my $namespace = $self->namespace();
611 1         7 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         7 delete $util->{cgi};
620              
621             #########
622             # but pass-through the errstr
623             #
624 1   33     8 $util->cgi->param('errstr', CGI::escape($errstr || $self->errstr));
625              
626             #########
627             # non-mod-perl errordocument handled by application internals
628             #
629 1         88 my $error_ns = sprintf q[%s::view::error], $namespace;
630 1         9 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         26 $viewobject = $error_ns->new($params);
641 0         0 1;
642 1 50       4 } or do {
643 1         27 $viewobject = ClearPress::view::error->new($params);
644             };
645              
646 1         12 my $decor = $viewobject->decor();
647 1         3 my $header = q[];
648 1         4 my $footer = q[];
649              
650 1         7 $viewobject->output_reset();
651              
652 1         5 my $body = $viewobject->render;
653              
654 1 50       4 if($viewobject->decor) {
655 1         7 $header = $decorator->header;
656 1         7 $footer = $decorator->footer;
657             }
658              
659 1         6 my $str = $header . $body . $footer;
660              
661 1         8 $viewobject->output_prepend($headers->as_string, "\n");
662 1         6 $viewobject->output_buffer($str);
663 1         6 $viewobject->output_end();
664 1         10 $decorator->save_session();
665 1         11 $util->cleanup();
666              
667 1         69 return;
668             }
669              
670             sub namespace {
671 180     180 1 434 my ($self, $util) = @_;
672 180         376 my $ns = q[];
673              
674 180 100 66     1219 if((ref $self && !$self->{namespace}) || !ref $self) {
      66        
675 118   66     444 $util ||= $self->util();
676 118   50     350 $ns = $util->config->val('application', 'namespace') ||
677             $util->config->val('application', 'name') ||
678             'ClearPress';
679 118 50       4268 if(ref $self) {
680 118         415 $self->{namespace} = $ns;
681             }
682             } else {
683 62         162 $ns = $self->{namespace};
684             }
685              
686 180         571 return $ns;
687             }
688              
689             sub is_valid_view {
690 15     15 1 48 my ($self, $ref, $viewname) = @_;
691 15         51 my $util = $ref->{util};
692 15         69 my @entities = split /[,\s]+/smx, $util->config->val('application','views');
693              
694 15         716 for my $ent (@entities) {
695 57 100       177 if($ent eq $viewname) {
696 15         69 return 1;
697             }
698             }
699              
700 0         0 return;
701             }
702              
703             sub errstr {
704 2     2 1 9 my ($self, $str) = @_;
705              
706 2 50       10 if($str) {
707 0         0 $self->{errstr} = $str;
708             }
709              
710 2         14 return $self->{errstr};
711             }
712              
713             sub dispatch {
714 15     15 1 50 my ($self, $ref) = @_;
715 15         46 my $util = $ref->{util};
716 15         45 my $entity = $ref->{entity};
717 15         45 my $aspect = $ref->{aspect};
718 15         45 my $action = $ref->{action};
719 15         47 my $id = $ref->{id};
720 15         39 my $headers = $ref->{headers};
721 15         29 my $viewobject;
722              
723 15         75 my $state = $self->is_valid_view($ref, $entity);
724 15 50       60 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         50 my $entity_name = $entity;
730 15         61 my $viewclass = $self->packagespace('view', $entity, $util);
731              
732 15         42 my $modelobject;
733 15 50       69 if($entity ne 'error') {
734 15         59 my $modelclass = $self->packagespace('model', $entity, $util);
735             eval {
736 15         219 my $modelpk = $modelclass->primary_key();
737 15 100       238 $modelobject = $modelclass->new({
738             util => $util,
739             $modelpk?($modelpk => $id):(),
740             });
741 15         76 1;
742 15 50       41 } 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         79 $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         77 1;
767 15 50       41 } 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         62 return $viewobject;
779             }
780              
781             1;
782             __END__