File Coverage

blib/lib/EntityModel/Web/Context.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package EntityModel::Web::Context;
2             {
3             $EntityModel::Web::Context::VERSION = '0.004';
4             }
5             use EntityModel::Class {
6 1         18 request => { type => 'EntityModel::Web::Request' },
7             response => { type => 'EntityModel::Web::Response' },
8             site => { type => 'EntityModel::Web::Site' },
9             page => { type => 'EntityModel::Web::Page' },
10             user => { type => 'EntityModel::Web::User' },
11             session => { type => 'EntityModel::Web::Session' },
12             data => { type => 'hash', subclass => 'data' },
13             template => { type => 'EntityModel::Template' },
14 1     1   1097 };
  1         2  
15              
16             =head1 NAME
17              
18             EntityModel::Web::Context - handle context for a web request
19              
20             =head1 VERSION
21              
22             version 0.004
23              
24             =head1 SYNOPSIS
25              
26             my $web = EntityModel::Web->new;
27             my $req = EntityModel::Web::Request->new;
28             my $ctx = EntityModel::Web::Context->new(
29             request => $req
30             );
31             $ctx->find_page_and_data($web);
32             $ctx->resolve_data;
33             $ctx->process;
34             $ctx->save_session;
35             return $ctx->response;
36              
37             =head1 DESCRIPTION
38              
39             =cut
40              
41 1     1   2241 use EntityModel::Template;
  0            
  0            
42              
43             =head1 METHODS
44              
45             =cut
46              
47             sub new {
48             my $class = shift;
49             my $self = $class->SUPER::new;
50             my %args = @_;
51             if(defined(my $req = delete $args{request})) {
52             $self->request($req);
53             }
54             if(defined(my $tmpl = delete $args{template})) {
55             $self->template($tmpl);
56             } else {
57             $self->{template} = EntityModel::Template->new;
58             }
59             return $self;
60             }
61              
62             =head2 find_page_and_data
63              
64             Locate the page and populate any initial data from the path information.
65              
66             =cut
67              
68             sub find_page_and_data {
69             my $self = shift;
70             my $web = shift;
71             my $host = $self->request->uri->host;
72             # my ($site) = grep { warn "have " . $_->host; $_->host eq $host } $web->site->list;
73             my ($site) = $web->site->list;
74             # grep(sub { $_[0] eq $host })->first;
75             logDebug("Check for site [%s]", $host);
76             return EntityModel::Error->new($self, "No site") unless $site;
77             $self->site($site);
78              
79             # If we have a regex match, return the remaining entries
80             my ($page, @data) = $site->page_from_uri($self->request->uri);
81             unless($page) {
82             logWarning("Failed to find page for URI [%s]", $self->request->uri);
83             logInfo("Page [%s]", $_->path) for $site->page->list;
84             }
85             return EntityModel::Error->new($self, "No page") unless $page;
86              
87             # Pick up the page entry first
88             $self->page($page);
89             logDebug("Page is [%s]", $page->name);
90              
91             # Get all page entries in order from first to last
92             my @pages = $page;
93             while($page->parent) {
94             $page = $page->parent;
95             unshift @pages, $page;
96             }
97              
98             # Apply data for any entries that have regex captures
99             my %page_data;
100             foreach my $p (@pages) {
101             my %page_data = $p->extract_data(\@data);
102             $self->data->set($_, $page_data{$_}) for keys %page_data;
103             }
104              
105             return $self;
106             }
107              
108             =head2 data_missing
109              
110             Returns number of missing dependencies for the given L entry.
111              
112             =cut
113              
114             sub data_missing {
115             my ($self, $entry) = @_;
116             return 0 unless $entry;
117              
118             my @missing = grep {
119             defined($_->data) && !$self->data->exists($_->data)
120             } $entry->parameter->list;
121             push @missing, $entry if $entry->data && !$self->data->exists($entry->data);
122              
123             logDebug("Data $entry requires " . join(', ', map { $_->value } @missing) . " items which are not ready yet") if @missing;
124             return scalar(@missing);
125             }
126              
127             =head2 resolve_data
128              
129             Process all data for this page, handling initial population and then going through each item in
130             turn, adding it back to the queue if the dependencies aren't ready.
131              
132             Chained method.
133              
134             =cut
135              
136             sub resolve_data {
137             my $self = shift;
138             return EntityModel::Error->new($self, 'No page') unless $self->page;
139              
140             # Get list of required items for this page
141             my @dataList = $self->page->data->list;
142              
143             # Iterate through them until we no longer have any entries to resolve (or all existing entries are
144             # failing).
145             my $failed = 0;
146             DATA:
147             while(@dataList) {
148             my $entry = shift(@dataList) or next DATA;
149              
150             logDebug("Resolve data for " . ($entry->key // 'undef'));
151             if($self->resolve_data_item($entry)) {
152             # Successful resolution means we should reset the failure counter
153             $failed = 0;
154             } else {
155             ++$failed;
156             push @dataList, $entry;
157             }
158              
159             # If all entries in the queue are failing, raise an error here
160             return EntityModel::Error->new($self, sub { "Could not resolve items [%s], population failed", join ',', map { $_->key // 'undef' } @dataList }) if $failed && $failed >= @dataList;
161             }
162              
163             return $self;
164             }
165              
166             =head2 find_data_value
167              
168             Retrieve data value for given L entry.
169              
170             =cut
171              
172             sub find_data_value {
173             my $self = shift;
174             my $entry = shift;
175              
176             my $v;
177              
178             if(defined $entry->value) {
179             # Simple value, used for constants
180             $v = $entry->value;
181             } elsif($entry->class) {
182             # Class method
183             $v = $self->data_from_class_method($entry);
184             } elsif ($entry->instance) {
185             # Instance method
186             $v = $self->data_from_instance_method($entry);
187             } elsif ($entry->data) {
188             # Data value from somewhere else
189             $v = $self->data->get($entry->data);
190             } else {
191             # Default case - probably an error
192             logDebug(" * $_ => " . $entry->{$_}) foreach keys %$entry;
193             logError({ %$entry });
194             $v = EntityModel::Error->new($self, 'Unknown data type');
195             }
196             return $v;
197             }
198              
199             =head2 resolve_data_item
200              
201             Resolve a single data item if we can.
202              
203             Returns undef on failure, original entry on success.
204              
205             =cut
206              
207             sub resolve_data_item {
208             my $self = shift;
209             my $entry = shift;
210             my $k = $entry->key;
211              
212             if($self->data_missing($entry)) {
213             logDebug("Deferring " . $k);
214             return undef;
215             }
216              
217             my $v = $self->find_data_value($entry);
218             $self->data->{$k} = $v unless eval { $v->isa('EntityModel::Error') };
219              
220             logDebug("Data [$k] is now " . ($self->data->{$k} // 'undef'));
221             return $entry;
222             }
223              
224             =head2 args_for_data
225              
226             Generate list of arguments for a method call.
227              
228             =cut
229              
230             sub args_for_data {
231             my $self = shift;
232             my $entry = shift;
233              
234             my @param;
235             $entry->parameter->each($self->sap(sub {
236             my ($self, $item) = @_;
237             push @param, $self->find_data_value($item);
238             }));
239             return @param;
240             }
241              
242             =head2 data_from_class_method
243              
244             Call class method to obtain new data value.
245              
246             =cut
247              
248             sub data_from_class_method {
249             my $self = shift;
250             my $entry = shift;
251             my $class = $entry->class;
252             my $method = $entry->method;
253              
254             return EntityModel::Error->new($self, 'No class provided') unless $class;
255             return EntityModel::Error->new($self, 'Invalid method %s for %s', $method, $class) unless $class->can($method);
256              
257             return try {
258             $class->$method($self->args_for_data($entry));
259             } catch {
260             EntityModel::Error->new($self, "Failed in %s->%s for %s: %s", $class, $method, $entry->key, $_);
261             };
262             }
263              
264             =head2 data_from_instance_method
265              
266             Instance method, in which case hopefully we already set this one up
267              
268             =cut
269              
270             sub data_from_instance_method {
271             my $self = shift;
272             my $entry = shift;
273             logDebug("Look up [%s]", $entry->instance);
274              
275             my $obj = $self->data->get($entry->instance);
276             $obj ||= $self->site if $entry->instance eq 'site';
277             $obj ||= $self->page if $entry->instance eq 'page';
278             $obj ||= $self if $entry->instance eq 'context';
279             logDebug("Got [%s]", $obj);
280             my $method = $entry->method;
281             my $v = try {
282             logDebug("Call $method");
283             my @args = $self->args_for_data($entry);
284             $obj->$method(@args);
285             } catch {
286             logError("Method [%s] not valid for class %s on key %s, error %s", $method, $obj, $entry->key, $_);
287             };
288             logDebug("Had [%s]", $v);
289             return $v;
290             }
291              
292             =head2 process
293              
294             =cut
295              
296             sub process {
297             my $self = shift;
298             logDebug("Try to handle request for this page");
299              
300             $self->page->handle_request(request => $self->request, data => $self->data);
301              
302             my %section = map {
303             $_->section => $self->section_content($_->section)
304             } $self->site->layout->list;
305             my $tmpl = $self->page->template // $self->site->template;
306             return '' unless $tmpl;
307              
308             return $self->template->as_text($tmpl, {
309             context => $self,
310             page => $self->page,
311             data => $self->data,
312             section => \%section
313             });
314             }
315              
316              
317             =head2 section_content
318              
319             =cut
320              
321             sub section_content {
322             my $self = shift;
323             my $section = shift or return '';
324             return EntityModel::Error->new($self, 'No page defined') unless $self->page;
325              
326             logDebug("Try section [%s]", $section);
327             my $content = $self->page->content_by_section->get($section);
328             logDebug("Had content [%s]", $content);
329             return EntityModel::Error->new($self, "Section [$section] not found") unless $content;
330             logDebug("Template [%s]", $content->template);
331             return $self->template->as_text($content->template, {
332             context => $self,
333             page => $self->page,
334             data => $self->data,
335             });
336             }
337              
338             =head2 load_session
339              
340             Loads session data into the current context.
341              
342             =cut
343              
344             sub load_session {
345             my $self = shift;
346             }
347              
348             =head2 save_session
349              
350             =cut
351              
352             sub save_session {
353             my $self = shift;
354             }
355              
356             1;
357              
358             __END__