File Coverage

blib/lib/ClearPress/controller.pm
Criterion Covered Total %
statement 347 354 98.0
branch 107 140 76.4
condition 73 107 68.2
subroutine 26 26 100.0
pod 15 15 100.0
total 568 642 88.4


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