File Coverage

blib/lib/Web/App/Config.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Web::App::Config;
2             # $Id: Config.pm,v 1.31 2009/06/07 20:57:49 apla Exp $
3              
4 1     1   9 use Class::Easy;
  1         2  
  1         11  
5              
6 1     1   1427 use Storable qw(retrieve nstore);
  1         4384  
  1         119  
7              
8 1     1   555 use XML::LibXML;
  0            
  0            
9              
10             use Web::App::Config::Screen;
11              
12             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13              
14             =pod
15              
16             =head1 NAME
17              
18             Web::App::Config - parsing Web::App configuration
19              
20             =head1 DESCRIPTION
21              
22             Web::App
23              
24             =cut
25              
26             sub path_to_val {
27             my ($data, $path) = @_;
28            
29             my @path = split '/', $path;
30             foreach (@path) {
31             $data = $data->[$_], next
32             if ref $data eq 'ARRAY';
33             $data = $data->{$_};
34             }
35             return $data;
36             }
37              
38             sub assign_path {
39             my ($data, $path, $value) = @_;
40              
41             my @path = split '/', $path;
42             my $last = pop @path;
43             foreach (@path) {
44             unless (exists $data->{$_}) {
45             # debug "$path => $_";
46             $data->{$_} = {};
47             }
48              
49             $data = $data->{$_};
50             }
51             $data->{$last} = $value;
52             }
53              
54              
55             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
56             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
57             sub last_modified_since {
58             my $self = shift;
59             my $last_modified = shift;
60            
61             my $config_file = $self->file;
62             my $config_dir = ($config_file =~ /^(.*\/)[^\/]+$/)[0];
63            
64             my $config_files = [$config_file, map {"$config_dir$_"} @{$self->int->{'files'}}];
65            
66             debug "compare timestamps of the config file '$config_file' and module Web::App::Config";
67              
68             my $configuration_package_file = $INC {'Web/App/Config.pm'};
69            
70             my $package_change = (stat ($configuration_package_file))[9];
71             my $config_change = 0;
72              
73             foreach $config_file (@$config_files) {
74             my $mtime = (stat ($config_file))[9];
75             $config_change = $mtime
76             if $mtime > $config_change;
77             }
78            
79             my $outdated = 0;
80              
81             # workaround for configuration init
82             if (not defined $last_modified or $last_modified == 0) {
83             debug "initial configuration";
84             $last_modified = $config_change;
85             }
86            
87             if ($config_change > $last_modified) {
88             debug "config changed since last modified";
89             # debug "config (" . scalar localtime ($config_change) . ") changed since last modified (".scalar localtime ($last_modified).")";
90             $last_modified = $config_change;
91             $outdated = $last_modified;
92             }
93              
94             if ($package_change > $last_modified) {
95             debug "module are newer than configuration";
96             # debug "module (" . scalar localtime ($package_change) . ") are newer than configuration (".scalar localtime ($last_modified).")";
97             $last_modified = $package_change;
98             $outdated = $last_modified;
99             }
100            
101             return $outdated;
102            
103             }
104             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
105             has 'app', is => 'ro';
106             has 'file', is => 'ro';
107             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
108             sub int {
109             return shift->{'internals'};
110             }
111             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
112             sub screens {
113             return shift->{'internals'}->{'screens'};
114             }
115             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
116             sub modules {
117             return shift->{'internals'}->{'modules'};
118             }
119             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
120             sub presenters {
121             return shift->{'internals'}->{'presenters'};
122             }
123             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
124             sub get {
125             my $class = shift;
126             my $app = shift;
127             my $config_file = shift;
128            
129             # first we need to check existing config in web::app
130             # then we check
131            
132             my $bin_config_file = $config_file . '.binary';
133              
134             my $self = {
135             'file' => $config_file,
136             'app' => $app,
137             'mtime' => 0,
138             'internals' => {
139             'screens' => {},
140             'modules' => {},
141             'presenters' => {},
142             'files' => {},
143             }
144             };
145            
146             my $mtime_config_binary = 0;
147             my $loaded = 0;
148            
149             if (-f $bin_config_file) {
150             $self->{'mtime'} = $mtime_config_binary = (stat ($bin_config_file))[9];
151            
152             debug "loading configuration binary";
153             eval {
154             $self->{'internals'} = retrieve ($bin_config_file);
155             $loaded = 1;
156             };
157            
158             };
159            
160             unless (-f $config_file) {
161             critical "Can't read config from file $config_file";
162             }
163            
164             bless $self, $class;
165            
166             if (not $loaded or $self->last_modified_since ($mtime_config_binary)) {
167             debug 'parsing configuration';
168            
169             $self->parse_file ($app);
170            
171             nstore ($self->{'internals'}, $bin_config_file)
172             or debug ("cannot write binary config to '$bin_config_file'");
173            
174             }
175            
176             # reload mtime after store or retrieve
177             $self->{'mtime'} = (stat ($bin_config_file))[9];
178            
179             return $self;
180             }
181             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
182             sub init_modules {
183             my $self = shift;
184            
185             my $app = $self->app;
186            
187             my $dump_separators = ',;:\/ ';
188            
189             foreach my $module (keys %{$self->modules}) {
190             next unless $module;
191            
192             my $t = timer ("$module require");
193            
194             debug "$@"
195             unless try_to_use ($module);
196            
197             my $params = $self->modules->{$module};
198            
199             my $dump = 'all';
200            
201             if (ref $params eq 'HASH') {
202            
203             if ($module->can ('init') and $params->{type} ne 'use') {
204             $t->lap ("$module init");
205             $module->init ($params);
206             } else {
207             # debug "module $module doesn't have 'init' method";
208             }
209             }
210            
211             $t->end;
212             }
213            
214             }
215             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
216             sub screen {
217             my $self = shift;
218             my $screen_name = shift;
219            
220             return $self->screens->{$screen_name};
221             }
222             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
223             sub parse_file {
224             my $self = shift;
225             my $app = shift;
226            
227             my $screen_config_path = $self->{'file'};
228            
229             debug 'loading screens configuration';
230            
231             my $parser = new XML::LibXML;
232             my $xp = $parser->parse_file ($screen_config_path);
233            
234             my @files = $xp->findnodes ('/config/xi:include');
235            
236             $self->int->{'files'} = [map {$_->getAttribute ('href')} @files];
237             $parser->processXIncludes ($xp);
238            
239             my @plugin_list = $xp->findnodes ('/config/*[local-name() = "extension" or local-name() = "presenter" or local-name() = "use" or local-name() = "request" or local-name() = "session"]');
240            
241             my $modules = $self->int->{'modules'} = {};
242            
243             foreach my $plugin (@plugin_list) {
244             my @arguments = $plugin->findnodes ('@*');
245            
246             my $module_params = {'type' => ''};
247            
248             foreach (@arguments) {
249             #$module = $module->string_value;
250             #debug "found '" . $plugin->nodeName . "'in module: '$module'";
251            
252             $module_params->{$_->localname} = $_->nodeValue;
253             }
254            
255             my $defined_type = $module_params->{'type'};
256             my $computed_type = $plugin->nodeName;
257             $computed_type .= ':' . $defined_type
258             if defined $defined_type and $defined_type ne '';
259            
260             $module_params->{'type'} = $computed_type;
261            
262             my $module_name = delete $module_params->{'pack'};
263            
264             # after parsing, store module config
265             $modules->{$module_name} = $module_params;
266            
267             debug "found $module_name in ", $plugin->localname;
268             }
269            
270             my $screens_node = $xp->findnodes ('/config/screens')->get_node(1);
271            
272             my $screens = $self->int->{'screens'} = {};
273            
274             $screens->{'#base-uri'} = $screens_node->findvalue ('base-url/text()');
275             $screens->{'#separators'} = $screens_node->findvalue ('request-queue/@separators');
276            
277             $screens->{'#user-name-separator'} =
278             $screens_node->findvalue ('request-queue/user-name/@separator-symbol');
279            
280             $screens->{'#user-name-position'} =
281             $screens_node->findvalue ('request-queue/user-name/@name-position');
282            
283             my @screen_nodes_list = $screens_node->findnodes ('screen');
284             # find all paragraphs
285            
286             #my $presenters_dir = $self->{'home'} . '/share/presentation/' .
287             # $self->{'template-set'} . '/';
288            
289             #debug 'presenters in \'' . $presenters_dir . '\'';
290            
291             foreach my $screen (@screen_nodes_list) {
292              
293             my $id = $screen->getAttribute ('id');
294             next
295             unless defined $id;
296            
297             my $screen_object = Web::App::Config::Screen->create ($id);
298            
299             $screen_object->{auth} = $screen->getAttribute ('auth');
300            
301             if ($id ne '') {
302             assign_path ($screens, "$id/?", $screen_object);
303             $screens->{$id}->{'?'} = $screen_object
304             unless defined $screens->{$id};
305             } else {
306             assign_path ($screens, "?", $screen_object);
307             }
308            
309             my $presenter_attrs = ();
310             foreach my $presenter_attr (($screen->findnodes ('presentation'))[0]->attributes) {
311             $presenter_attrs->{$presenter_attr->localName} =
312             $presenter_attr->value;
313             }
314            
315             die "Can't locate presenter type in screen '$id', ", $screen->toString (1)
316             unless $presenter_attrs->{type};
317            
318             my $regexp = $screen->findvalue ('@regexp');
319             $screen_object->{regexp} = $regexp
320             if $regexp;
321            
322             $screen_object->presentation ($presenter_attrs);
323            
324             my $req_max_size = $screen->findvalue ('@request-max-size');
325             $screen_object->request->{'max-size'} = $req_max_size
326             if defined $req_max_size and $req_max_size =~ /\d+/;
327            
328             foreach my $context ('call', 'init/call', 'process/call') {
329             my @call_nodes = $screen->findnodes ($context);
330            
331             my $type = ($context =~ /^(?:(init|process)\/)?call$/)[0];
332             my $call = 'add_call';
333             $call = "add_${type}_call"
334             if defined $type;
335            
336             foreach my $call_node (@call_nodes) {
337             my @param_attrs = $call_node->findnodes ('@*');
338            
339             my $params = {};
340            
341             foreach (@param_attrs) {
342            
343             my $param_name = $_->localname;
344             my $param_value = $_->nodeValue;
345            
346             $params->{$param_name} = $param_value;
347             }
348            
349             if (exists $params->{'sub'}) {
350            
351             my $sub = $params->{'sub'};
352             $screen_object->$call ($params);
353             # debug "added $call to $sub";
354             my $module = ($sub =~ /(.*)(?:->|::)/)[0];
355            
356             if ($module =~ /^\$([^:]+)/ and $app->can ($1)) {
357             # debug 'this is a call for web app internals';
358             } else {
359             $modules->{$module} = {type => 'use'};
360             }
361            
362             # try_to_use ($module);
363             }
364             }
365             }
366            
367             $screen_object->{'params'} = [];
368            
369             my @param_nodes = $screen->findnodes ('param');
370            
371             foreach my $param (@param_nodes) {
372             my $field_params = {
373             'name' => undef,
374             'required' => undef,
375             'type' => undef, # regexp:... or email, as example. available types in request.pm
376             'multi' => undef, # multivalued parameter
377             'filter' => undef, # trim-space, as example. available types in request.pm
378             'default' => undef, # default values for this object
379             };
380             push @{$screen_object->{'params'}}, $field_params;
381            
382             foreach my $var (keys %$field_params) {
383             $field_params->{$var} = $param->getAttribute ($var)
384             if $param->hasAttribute ($var);
385             }
386            
387             if ($field_params->{name}) {
388             $screen_object->{'params_hash'}->{$field_params->{name}} = $field_params;
389             }
390            
391             $field_params->{'default'} = [$field_params->{'default'}]
392             if defined $field_params->{'default'};
393             $field_params->{'default'} = []
394             if $param->findvalue ('count(default)') > 0;
395            
396             foreach my $defaults ($param->findnodes ('default')) {
397             push @{$field_params->{'default'}}, $defaults->textContent;
398             }
399             }
400             }
401            
402             # use Data::Dumper;
403             # debug Dumper $self;
404            
405             return;
406             }
407             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
408              
409             =pod
410              
411             sub path_from_request
412              
413             this procedure return screen object and path info.
414              
415             example:
416              
417             request is: http://some.com/web-app/admin/article/12345
418              
419             configuration is:
420              
421            
422             ...
423            
424             ...
425             /web-app
426             ...
427            
428            
429            
430              
431            
432            
433            
434             ...
435              
436            
437            
438              
439             sub return screen object for screen with
440             id = 'admin/article' and path info = '12345'
441              
442             =cut
443            
444             sub screen_from_request {
445             my $self = shift;
446             my $path = shift;
447            
448             my $screens = $self->screens;
449            
450             my $separators = $screens->{'#separators'};
451            
452             $separators =~ s/([\/\[\]\(\)])/\\$1/g;
453            
454             my $screen = $screens;
455            
456             my $matches = [];
457            
458             while (1) {
459             my ($path_element, $tail) = split /[$separators]/, $path, 2;
460            
461             #debug "path element: $path_element, tail: $tail";
462            
463             return ($screen->{'?'}, $path, $matches)
464             unless defined $path_element;
465            
466             if (defined $screen->{$path_element} and $screen->{$path_element}->{'?'}) {
467             $screen = $screen->{$path_element};
468             $path = $tail;
469             } else {
470             # try to find screen by regexp
471            
472             my $matched = 0;
473            
474             my @children_screen = grep {!/[\/\#\?]/} keys %$screen;
475              
476             # use Data::Dumper;
477             # debug Dumper \@children_screen;
478            
479             foreach my $match (@children_screen) {
480             my $is_regexp = $screen->{$match}->{'?'}->{regexp};
481            
482             next unless $is_regexp;
483            
484             if ($path_element =~ /$match/i and $screen->{$match}->{'?'}) {
485             $screen = $screen->{$match};
486             $path = $tail;
487             push @$matches, $1
488             if defined $1;
489             $matched = 1;
490             }
491             }
492            
493             return ($screen->{'?'}, $path, $matches)
494             unless $matched;
495             }
496             }
497             }
498              
499             1;