| 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; | ||||||