File: | blib/lib/OpenSRF/Utils/Config.pm |
Coverage: | 19.2% |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package OpenSRF::Utils::Config::Section; | ||||||
2 | |||||||
3 | 13 13 13 | 70 57 82 | no strict 'refs'; | ||||
4 | |||||||
5 | 13 13 13 | 83 53 95 | use vars qw/@ISA $AUTOLOAD/; | ||||
6 | push @ISA, qw/OpenSRF::Utils/; | ||||||
7 | |||||||
8 | 13 13 13 | 144 58 139 | use OpenSRF::Utils (':common'); | ||||
9 | 13 13 13 | 186 55 162 | use Net::Domain qw/hostfqdn/; | ||||
10 | |||||||
11 | our $VERSION = "1.000"; | ||||||
12 | |||||||
13 | my %SECTIONCACHE; | ||||||
14 | my %SUBSECTION_FIXUP; | ||||||
15 | |||||||
16 | #use overload '""' => \&OpenSRF::Utils::Config::dump_ini; | ||||||
17 | |||||||
18 | sub SECTION { | ||||||
19 | 0 | 0 | my $sec = shift; | ||||
20 | 0 | 0 | return $sec->__id(@_); | ||||
21 | } | ||||||
22 | |||||||
23 | sub new { | ||||||
24 | 0 | 0 | my $self = shift; | ||||
25 | 0 | 0 | my $class = ref($self) || $self; | ||||
26 | |||||||
27 | 0 | 0 | $self = bless {}, $class; | ||||
28 | |||||||
29 | 0 | 0 | $self->_sub_builder('__id'); | ||||
30 | # Hard-code this to match old bootstrap.conf section name | ||||||
31 | 0 | 0 | $self->__id('bootstrap'); | ||||
32 | |||||||
33 | 0 | 0 | my $bootstrap = shift; | ||||
34 | |||||||
35 | 0 | 0 | foreach my $key (sort keys %$bootstrap) { | ||||
36 | 0 | 0 | $self->_sub_builder($key); | ||||
37 | 0 | 0 | $self->$key($bootstrap->{$key}); | ||||
38 | } | ||||||
39 | |||||||
40 | 0 | 0 | return $self; | ||||
41 | } | ||||||
42 | |||||||
43 | package OpenSRF::Utils::Config; | ||||||
44 | |||||||
45 | 13 13 13 | 99 50 126 | use vars qw/@ISA $AUTOLOAD $VERSION $OpenSRF::Utils::ConfigCache/; | ||||
46 | push @ISA, qw/OpenSRF::Utils/; | ||||||
47 | |||||||
48 | 13 13 13 | 92 49 154 | use FileHandle; | ||||
49 | 13 13 13 | 197 67 90 | use XML::LibXML; | ||||
50 | 13 13 13 | 95 50 148 | use OpenSRF::Utils (':common'); | ||||
51 | 13 13 13 | 112 145 161 | use OpenSRF::Utils::Logger; | ||||
52 | 13 13 13 | 95 50 99 | use Net::Domain qw/hostfqdn/; | ||||
53 | |||||||
54 | #use overload '""' => \&OpenSRF::Utils::Config::dump_ini; | ||||||
55 | |||||||
56 | sub import { | ||||||
57 | 79 | 418 | my $class = shift; | ||||
58 | 79 | 325 | my $config_file = shift; | ||||
59 | |||||||
60 | 79 | 410 | return unless $config_file; | ||||
61 | |||||||
62 | 0 | $class->load( config_file => $config_file); | |||||
63 | } | ||||||
64 | |||||||
65 | sub dump_ini { | ||||||
66 | 13 13 13 | 95 47 110 | no warnings; | ||||
67 | 0 | my $self = shift; | |||||
68 | 0 | my $string; | |||||
69 | 0 | my $included = 0; | |||||
70 | 0 | if ($self->isa('OpenSRF::Utils::Config')) { | |||||
71 | 0 | if (UNIVERSAL::isa(scalar(caller()), 'OpenSRF::Utils::Config' )) { | |||||
72 | 0 | $included = 1; | |||||
73 | } else { | ||||||
74 | 0 | $string = "# Main File: " . $self->FILE . "\n\n" . $string; | |||||
75 | } | ||||||
76 | } | ||||||
77 | 0 0 | for my $section ( ('__id', grep { $_ ne '__id' } sort keys %$self) ) { | |||||
78 | 0 | next if ($section eq 'env' && $self->isa('OpenSRF::Utils::Config')); | |||||
79 | 0 | if ($section eq '__id') { | |||||
80 | 0 | $string .= '['.$self->SECTION."]\n" if ($self->isa('OpenSRF::Utils::Config::Section')); | |||||
81 | } elsif (ref($self->$section)) { | ||||||
82 | 0 | if (ref($self->$section) =~ /ARRAY/o) { | |||||
83 | 0 0 | $string .= "list:$section = ". join(', ', @{$self->$section}) . "\n"; | |||||
84 | } elsif (UNIVERSAL::isa($self->$section,'OpenSRF::Utils::Config::Section')) { | ||||||
85 | 0 | if ($self->isa('OpenSRF::Utils::Config::Section')) { | |||||
86 | 0 | $string .= "subsection:$section = " . $self->$section->SECTION . "\n"; | |||||
87 | 0 | next; | |||||
88 | } else { | ||||||
89 | 0 | next if ($self->$section->{__sub} && !$included); | |||||
90 | 0 | $string .= $self->$section . "\n"; | |||||
91 | } | ||||||
92 | } elsif (UNIVERSAL::isa($self->$section,'OpenSRF::Utils::Config')) { | ||||||
93 | 0 | $string .= $self->$section . "\n"; | |||||
94 | } | ||||||
95 | } else { | ||||||
96 | 0 | next if $section eq '__sub'; | |||||
97 | 0 | $string .= "$section = " . $self->$section . "\n"; | |||||
98 | } | ||||||
99 | } | ||||||
100 | 0 | if ($included) { | |||||
101 | 0 | $string =~ s/^/## /gm; | |||||
102 | 0 | $string = "# Subfile: " . $self->FILE . "\n#" . '-'x79 . "\n".'#include "'.$self->FILE."\"\n". $string; | |||||
103 | } | ||||||
104 | |||||||
105 | 0 | return $string; | |||||
106 | } | ||||||
107 | |||||||
108 - 201 | =head1 NAME OpenSRF::Utils::Config =head1 SYNOPSIS use OpenSRF::Utils::Config; my $config_obj = OpenSRF::Utils::Config->load( config_file => '/config/file.cnf' ); my $attrs_href = $config_obj->bootstrap(); $config_obj->bootstrap->loglevel(0); open FH, '>'.$config_obj->FILE() . '.new'; print FH $config_obj; close FH; =head1 DESCRIPTION This module is mainly used by other OpenSRF modules to load an OpenSRF configuration file. OpenSRF configuration files are XML files that contain a C<< <config> >> root element and an C<< <opensrf> >> child element (in XPath notation, C</config/opensrf/>). Each child element is converted into a hash key=>value pair. Elements that contain other XML elements are pushed into arrays and added as an array reference to the hash. Scalar values have whitespace trimmed from the left and right sides. Child elements of C<< <config> >> other than C<< <opensrf> >> are currently ignored by this module. =head1 EXAMPLE Given an OpenSRF configuration file named F<opensrf_core.xml> with the following content: <?xml version='1.0'?> <config> <opensrf> <router_name>router</router_name> <routers> <router>localhost</router> <router>otherhost</router> </routers> <logfile>/var/log/osrfsys.log</logfile> </opensrf> </config> ... calling C<< OpenSRF::Utils::Config->load(config_file => 'opensrf_core.xml') >> will create a hash with the following structure: { router_name => 'router', routers => ['localhost', 'otherhost'], logfile => '/var/log/osrfsys.log' } You can retrieve any of these values by name from the bootstrap section of C<$config_obj>; for example: $config_obj->bootstrap->router_name =head1 NOTES For compatibility with a previous version of OpenSRF configuration files, the F</config/opensrf/> section has a hardcoded name of B<bootstrap>. However, future iterations of this module may extend the ability of the module to parse the entire OpenSRF configuration file and provide sections named after the sibling elements of C</config/opensrf>. Hashrefs of sections can be returned by calling a method of the object of the same name as the section. They can be set by passing a hashref back to the same method. Sections will B<NOT> be autovivicated, though. =head1 METHODS =head2 OpenSRF::Utils::Config->load( config_file => '/some/config/file.cnf' ) Returns a OpenSRF::Utils::Config object representing the config file that was loaded. The most recently loaded config file (hopefully the only one per app) is stored at $OpenSRF::Utils::ConfigCache. Use OpenSRF::Utils::Config::current() to get at it. =cut | ||||||
202 | |||||||
203 | sub load { | ||||||
204 | 0 | my $pkg = shift; | |||||
205 | 0 | $pkg = ref($pkg) || $pkg; | |||||
206 | |||||||
207 | 0 | my %args = @_; | |||||
208 | |||||||
209 | 0 | (my $new_pkg = $args{config_file}) =~ s/\W+/_/g; | |||||
210 | 0 | $new_pkg .= "::$pkg"; | |||||
211 | 0 | $new_section_pkg .= "${new_pkg}::Section"; | |||||
212 | |||||||
213 | 0 0 | { eval <<" PERL"; | |||||
214 | |||||||
215 | package $new_pkg; | ||||||
216 | use base $pkg; | ||||||
217 | sub section_pkg { return '$new_section_pkg'; } | ||||||
218 | |||||||
219 | package $new_section_pkg; | ||||||
220 | use base "${pkg}::Section"; | ||||||
221 | |||||||
222 | PERL | ||||||
223 | } | ||||||
224 | |||||||
225 | 0 | return $new_pkg->_load( %args ); | |||||
226 | } | ||||||
227 | |||||||
228 | sub _load { | ||||||
229 | 0 | my $pkg = shift; | |||||
230 | 0 | $pkg = ref($pkg) || $pkg; | |||||
231 | 0 | my $self = {@_}; | |||||
232 | 0 | bless $self, $pkg; | |||||
233 | |||||||
234 | 13 13 13 | 110 57 71 | no warnings; | ||||
235 | 0 | if ((exists $$self{config_file} and OpenSRF::Utils::Config->current) and (OpenSRF::Utils::Config->current->FILE eq $$self{config_file}) and (!$self->{force})) { | |||||
236 | 0 | delete $$self{force}; | |||||
237 | 0 | return OpenSRF::Utils::Config->current(); | |||||
238 | } | ||||||
239 | |||||||
240 | 0 | $self->_sub_builder('__id'); | |||||
241 | 0 | $self->FILE($$self{config_file}); | |||||
242 | 0 | delete $$self{config_file}; | |||||
243 | 0 | return undef unless ($self->FILE); | |||||
244 | |||||||
245 | 0 | $self->load_config(); | |||||
246 | 0 | $self->load_env(); | |||||
247 | 0 | $self->mangle_dirs(); | |||||
248 | 0 | $self->mangle_logs(); | |||||
249 | |||||||
250 | 0 | $OpenSRF::Utils::ConfigCache = $self unless $self->nocache; | |||||
251 | 0 | delete $$self{nocache}; | |||||
252 | 0 | delete $$self{force}; | |||||
253 | 0 | return $self; | |||||
254 | } | ||||||
255 | |||||||
256 | sub sections { | ||||||
257 | 0 | my $self = shift; | |||||
258 | 0 | my %filters = @_; | |||||
259 | |||||||
260 | 0 0 | my @parts = (grep { UNIVERSAL::isa($_,'OpenSRF::Utils::Config::Section') } values %$self); | |||||
261 | 0 | if (keys %filters) { | |||||
262 | 0 | my $must_match = scalar(keys %filters); | |||||
263 | 0 | my @ok_parts; | |||||
264 | 0 | foreach my $part (@parts) { | |||||
265 | 0 | my $part_count = 0; | |||||
266 | 0 | for my $fkey (keys %filters) { | |||||
267 | 0 | $part_count++ if ($part->$key eq $filters{$key}); | |||||
268 | } | ||||||
269 | 0 | push @ok_parts, $part if ($part_count == $must_match); | |||||
270 | } | ||||||
271 | 0 | return @ok_parts; | |||||
272 | } | ||||||
273 | 0 | return @parts; | |||||
274 | } | ||||||
275 | |||||||
276 | sub current { | ||||||
277 | 0 | return $OpenSRF::Utils::ConfigCache; | |||||
278 | } | ||||||
279 | |||||||
280 | sub FILE { | ||||||
281 | 0 | return shift()->__id(@_); | |||||
282 | } | ||||||
283 | |||||||
284 | sub load_env { | ||||||
285 | 0 | my $self = shift; | |||||
286 | 0 | my $host = $ENV{'OSRF_HOSTNAME'} || hostfqdn(); | |||||
287 | 0 | chomp $host; | |||||
288 | 0 | $$self{env} = $self->section_pkg->new; | |||||
289 | 0 | $$self{env}{hostname} = $host; | |||||
290 | } | ||||||
291 | |||||||
292 | sub mangle_logs { | ||||||
293 | 0 | my $self = shift; | |||||
294 | 0 | return unless ($self->logs && $self->dirs && $self->dirs->log_dir); | |||||
295 | 0 0 | for my $i ( keys %{$self->logs} ) { | |||||
296 | 0 | next if ($self->logs->$i =~ /^\//); | |||||
297 | 0 | $self->logs->$i($self->dirs->log_dir."/".$self->logs->$i); | |||||
298 | } | ||||||
299 | } | ||||||
300 | |||||||
301 | sub mangle_dirs { | ||||||
302 | 0 | my $self = shift; | |||||
303 | 0 | return unless ($self->dirs && $self->dirs->base_dir); | |||||
304 | 0 0 | for my $i ( keys %{$self->dirs} ) { | |||||
305 | 0 | if ( $i ne 'base_dir' ) { | |||||
306 | 0 | next if ($self->dirs->$i =~ /^\//); | |||||
307 | 0 | my $dir_tmp = $self->dirs->base_dir."/".$self->dirs->$i; | |||||
308 | 0 | $dir_tmp =~ s#//#/#go; | |||||
309 | 0 | $dir_tmp =~ s#/$##go; | |||||
310 | 0 | $self->dirs->$i($dir_tmp); | |||||
311 | } | ||||||
312 | } | ||||||
313 | } | ||||||
314 | |||||||
315 | sub load_config { | ||||||
316 | 0 | my $self = shift; | |||||
317 | 0 | my $parser = XML::LibXML->new(); | |||||
318 | |||||||
319 | # Hash of config values | ||||||
320 | 0 | my %bootstrap; | |||||
321 | |||||||
322 | # Return an XML::LibXML::Document object | ||||||
323 | 0 | my $config = $parser->parse_file($self->FILE); | |||||
324 | |||||||
325 | 0 | unless ($config) { | |||||
326 | 0 | OpenSRF::Utils::Logger->error("Could not open ".$self->FILE.": $!\n"); | |||||
327 | 0 | die "Could not open ".$self->FILE.": $!\n"; | |||||
328 | } | ||||||
329 | |||||||
330 | # Return an XML::LibXML::NodeList object matching all child elements | ||||||
331 | # of <config><opensrf>... | ||||||
332 | 0 | my $osrf_cfg = $config->findnodes('/config/opensrf/child::*'); | |||||
333 | |||||||
334 | # Iterate through the nodes to pull out key=>value pairs of config settings | ||||||
335 | 0 | foreach my $node ($osrf_cfg->get_nodelist()) { | |||||
336 | 0 | my $child_state = 0; | |||||
337 | |||||||
338 | # This will be overwritten if it's a scalar setting | ||||||
339 | 0 | $bootstrap{$node->nodeName()} = []; | |||||
340 | |||||||
341 | 0 | foreach my $child_node ($node->childNodes) { | |||||
342 | # from libxml/tree.h: nodeType 1 = ELEMENT_NODE | ||||||
343 | 0 | next if $child_node->nodeType() != 1; | |||||
344 | |||||||
345 | # If the child node is an element, this element may | ||||||
346 | # have multiple values; therefore, push it into an array | ||||||
347 | 0 | my $content = OpenSRF::Utils::Config::extract_child($child_node); | |||||
348 | 0 0 | push(@{$bootstrap{$node->nodeName()}}, $content) if $content; | |||||
349 | 0 | $child_state = 1; | |||||
350 | } | ||||||
351 | 0 | if (!$child_state) { | |||||
352 | 0 | $bootstrap{$node->nodeName()} = OpenSRF::Utils::Config::extract_text($node->textContent); | |||||
353 | } | ||||||
354 | } | ||||||
355 | |||||||
356 | 0 | my $section = $self->section_pkg->new(\%bootstrap); | |||||
357 | 0 | my $sub_name = $section->SECTION; | |||||
358 | 0 | $self->_sub_builder($sub_name); | |||||
359 | 0 | $self->$sub_name($section); | |||||
360 | |||||||
361 | } | ||||||
362 | sub extract_child { | ||||||
363 | 0 | my $node = shift; | |||||
364 | 13 13 13 | 177 57 203 | use OpenSRF::Utils::SettingsParser; | ||||
365 | 0 | return OpenSRF::Utils::SettingsParser::XML2perl($node); | |||||
366 | } | ||||||
367 | |||||||
368 | sub extract_text { | ||||||
369 | 0 | my $self = shift; | |||||
370 | 0 | $self =~ s/^\s*([.*?])\s*$//m; | |||||
371 | 0 | return $self; | |||||
372 | } | ||||||
373 | |||||||
374 | #------------------------------------------------------------------------------------------------------------------------------------ | ||||||
375 | |||||||
376 - 403 | =head1 SEE ALSO OpenSRF::Utils =head1 LIMITATIONS Elements containing heterogeneous child elements are treated as though they have the same element name; for example: <routers> <router>localhost</router> <furniture>chair</furniture> </routers> ... will simply generate a key=>value pair of C<< routers => ['localhost', 'chair'] >>. =head1 BUGS No known bugs, but report any to open-ils-dev@list.georgialibraries.org or mrylander@gmail.com. =head1 COPYRIGHT AND LICENSING Copyright (C) 2000-2007, Mike Rylander Copyright (C) 2007, Laurentian University, Dan Scott <dscott@laurentian.ca> The OpenSRF::Utils::Config module is free software. You may distribute under the terms of the GNU General Public License version 2 or greater. =cut | ||||||
404 | |||||||
405 | |||||||
406 | 1; |