File Coverage

blib/lib/Config/XPath/Reloadable.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2006-2010 -- leonerd@leonerd.org.uk
5              
6             package Config::XPath::Reloadable;
7              
8 5     5   126984 use strict;
  5         13  
  5         183  
9 5     5   28 use warnings;
  5         8  
  5         208  
10 5     5   34 use base qw( Config::XPath );
  5         10  
  5         3569  
11              
12             use Carp;
13              
14             our $VERSION = '0.16';
15              
16             =head1 NAME
17              
18             C - a subclass of C that supports
19             reloading
20              
21             =head1 SYNOPSIS
22              
23             use Config::XPath::Reloadable;
24              
25             my $conf = Config::XPath::Reloadable->new( filename => 'addressbook.xml' );
26              
27             $SIG{HUP} = sub { $conf->reload };
28              
29             $conf->associate_nodeset( '//user', '@name',
30             add => sub {
31             my ( $name, $user_conf ) = @_;
32             print "New user called $name, whose phone is " .
33             $user_conf->get_string( '@phone' ) . "\n";
34             },
35              
36             keep => sub {
37             my ( $name, $user_conf ) = @_;
38             print "User $name phone is now " .
39             $user_conf->get_string( '@phone' ) . "\n";
40             },
41              
42             remove => sub {
43             my ( $name ) = @_;
44             print "User $name has now gone\n";
45             },
46             );
47              
48             # Main body of code here ...
49              
50             =head1 DESCRIPTION
51              
52             This subclass of C supports reloading the underlying XML file
53             and updating the containing program's data structures. This is achieved by
54             taking control of the lifetimes of the program's data structures that use it.
55              
56             Where a simple C config file could be reloaded just by reapplying
57             string values, a whole range of new problems occur with the richer layout
58             afforded to XML-based files. New nodes can appear, old nodes can move, change
59             their data, or disappear. All these changes may involve data structure changes
60             within the containing program. To cope with these types of events, callbacks
61             in the form of closures can be registered that are called when various changes
62             happen to the underlying XML data.
63              
64             As with the non-reloadable parent class, configuration is generally processed
65             by forming a tree of objects which somehow maps onto the XML data tree. The
66             way this is done in this class, is to use the $node parameter passed in to the
67             C and C event callbacks. This parameter will hold a child
68             C object with its XPath context pointing at the
69             corresponding node in the XML data, much like the C method does.
70              
71             =cut
72              
73             =head1 CONSTRUCTOR
74              
75             =cut
76              
77             =head2 $conf = Config::XPath::Reloadable->new( %args )
78              
79             This function returns a new instance of a C object,
80             initially containing the configuration in the named XML file. The file is
81             closed by the time this method returns, so any changes of the file itself will
82             not be noticed until the C method is called.
83              
84             The C<%args> hash takes the following keys
85              
86             =over 8
87              
88             =item filename => $file
89              
90             The filename of the XML file to read
91              
92             =back
93              
94             =cut
95              
96             sub new
97             {
98             my $class = shift;
99             my %args = @_;
100              
101             if( !defined $args{filename} ) {
102             croak "Expected 'filename' argument";
103             }
104              
105             my $self = $class->SUPER::new( %args );
106              
107             $self->{nodelists} = [];
108              
109             $self;
110             }
111              
112             =head1 METHODS
113              
114             All of the simple data access methods of L are supported:
115              
116             $str = $config->get_string( $path, %args )
117              
118             $attrs = $config->get_attrs( $path )
119              
120             @values = $config->get_list( $path )
121              
122             $map = $config->get_map( $listpath, $keypath, $valuepath )
123              
124             Because of the dynamically-reloadable nature of objects in this class, the
125             C and C methods are no longer allowed. They will
126             instead throw exceptions. The event callbacks in nodelists and nodesets
127             should be used instead, to obtain subconfigurations.
128              
129             =cut
130              
131             =head2 $conf->reload()
132              
133             This method requests that the configuration object reloads the configuration
134             data that constructed it.
135              
136             If called on the root object, the XML file that was named in the constructor
137             is reopened and reparsed. The file is re-opened by name, rather than by
138             rereading the filehandle that was opened in the constructor. (This distinction
139             is only of significance for systems that allow open files to be renamed). If
140             called on a child object, the stored XPath data tree is updated from the
141             parent.
142              
143             In either case, after the data is reloaded, each nodelist stored by the object
144             is reevlauated, by requerying the XML nodeset using the stored XPaths, and the
145             event callbacks being invoked as appropriate.
146              
147             =cut
148              
149             sub reload
150             {
151             my $self = shift;
152             if( exists $self->{filename} ) {
153             $self->_reload_file;
154             }
155              
156             foreach my $nodelist ( @{ $self->{nodelists} } ) {
157             $self->_run_nodelist( $nodelist );
158             }
159             }
160              
161             # Override - no POD
162             sub get_sub
163             {
164             croak "Can't generate subconfig of a " . __PACKAGE__;
165             }
166              
167             # Override - no POD
168             sub get_sub_list
169             {
170             croak "Can't generate subconfig list of a " . __PACKAGE__;
171             }
172              
173             =head2 $conf->associate_nodelist( $listpath, %events )
174              
175             This method associates callback closures with events that happen to a given
176             nodelist in the XML data. When the function is first called, and every time
177             the C<< $conf->reload() >> method is called, the nodeset given by the XPath
178             string $listpath is obtained. The C or C callback is then called as
179             appropriate on each node, in the order they appear in the current XML data.
180              
181             Finally, the list of nodes that were present last time which no longer exist
182             is determined, and the C callback called for those, in no particular
183             order.
184              
185             When this method is called, the C callbacks will be invoked before the
186             method returns, for any matching items found in the data.
187              
188             The C<%events> hash should be passed keys for the following events:
189              
190             =over 8
191              
192             =item add => CODE
193              
194             Called when a node is returned in the list that has a name that wasn't present
195             on the last loading of the file. Called as:
196              
197             $add->( $index, $node )
198              
199             =item keep => CODE
200              
201             Called when a node is returned in the list that has a name that was present on
202             the last loading of the file. Note that the contents of this node may or may
203             not have changed; the containing program would have to requery the config node
204             to determine if this is the case. Called as:
205              
206             $keep->( $index, $node )
207              
208             =item remove => CODE
209              
210             Called at the end of the list enumeration, when a node was present last time
211             but is not present in the latest loading of the file. Called as:
212              
213             $remove->( $index )
214              
215             =back
216              
217             In each callback, the $index parameter will contain the index of the config
218             nodewithin the nodelist given by the $listpath, and the $node parameter will
219             contain a C object reference, with the XPath
220             context at the respective XML data node.
221              
222             If further recursive nodesets are associated on the inner config node given
223             to the C or C callbacks, then the C callback should invoke
224             the C method on the node, to ensure full recursive reloading of the
225             content.
226              
227             =cut
228              
229             sub associate_nodelist
230             {
231             my $self = shift;
232             my ( $listpath, %events ) = @_;
233              
234             my %nodelistitem = (
235             listpath => $listpath,
236             );
237              
238             foreach (qw( add keep remove )) {
239             $nodelistitem{$_} = $events{$_} if exists $events{$_};
240             }
241              
242             push @{ $self->{nodelists} }, \%nodelistitem;
243              
244             $self->_run_nodelist( \%nodelistitem );
245             }
246              
247             =head2 $conf->associate_nodeset( $listpath, $namepath, %events )
248              
249             This method is similar in operation to C, except that
250             each node in the set is identified by some value, rather than just its
251             index within the list. The value given by $namepath is obtained by using the
252             get_string() method (so it must be a plain text node, attribute value, or any
253             other XPath query that gives a string value). This name is then used to
254             determine whether the node has been added, or kept since the last time.
255              
256             The C<%events> hash should be passed keys for the following events:
257              
258             =over 8
259              
260             =item add => CODE
261              
262             Called when a node is returned in the list that has a name that wasn't present
263             on the last loading of the file. Called as:
264              
265             $add->( $name, $node )
266              
267             =item keep => CODE
268              
269             Called when a node is returned in the list that has a name that was present on
270             the last loading of the file. Note that the contents of this node may or may
271             not have changed; the containing program would have to requery the config node
272             to determine if this is the case. Called as:
273              
274             $keep->( $name, $node )
275              
276             =item remove => CODE
277              
278             Called at the end of the list enumeration, when a node was present last time
279             but is not present in the latest loading of the file. Called as:
280              
281             $remove->( $name )
282              
283             =back
284              
285             In each callback, the $name parameter will contain the string value returned by
286             the $namepath path on each node, and the $node parameter will contain a
287             C object reference, with the XPath context at the
288             respective XML data node.
289              
290             =cut
291              
292             sub associate_nodeset
293             {
294             my $self = shift;
295             my ( $listpath, $namepath, %events ) = @_;
296              
297             my %nodelistitem = (
298             listpath => $listpath,
299             namepath => $namepath,
300             );
301              
302             foreach (qw( add keep remove )) {
303             $nodelistitem{$_} = $events{$_} if exists $events{$_};
304             }
305              
306             push @{ $self->{nodelists} }, \%nodelistitem;
307              
308             $self->_run_nodelist( \%nodelistitem );
309             }
310              
311             sub _run_nodelist
312             {
313             my $self = shift;
314             my ( $nodelist ) = @_;
315              
316             my $class = ref( $self );
317              
318             my %lastitems;
319             %lastitems = %{ $nodelist->{items} } if defined $nodelist->{items};
320              
321             my %newitems;
322              
323             my $listpath = $nodelist->{listpath};
324             my $namepath = $nodelist->{namepath};
325              
326             my @nodes = $self->get_config_nodes( $listpath );
327              
328             foreach my $index ( 0 .. $#nodes ) {
329             my $n = $nodes[$index];
330              
331             my $name = defined $namepath ? $self->get_string( $namepath, context => $n ) : $index;
332              
333             my $item;
334              
335             if( exists $lastitems{$name} ) {
336             $item = delete $lastitems{$name};
337              
338             $item->{xp} = $self->{xp};
339             $item->{context} = $n;
340              
341             $nodelist->{keep}->( $name, $item ) if defined $nodelist->{keep};
342             }
343             else {
344             $item = $class->newContext( $self, $n );
345              
346             $nodelist->{add}->( $name, $item ) if defined $nodelist->{add};
347             }
348              
349             $newitems{$name} = $item;
350             }
351              
352             foreach my $name ( keys %lastitems ) {
353             $nodelist->{remove}->( $name ) if defined $nodelist->{remove};
354             }
355              
356             $nodelist->{items} = \%newitems;
357             }
358              
359             # Keep perl happy; keep Britain tidy
360             1;
361              
362             __END__