]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/perl/lib/OpenSRF/Utils/Config.pm
9ecfc0eaa6c04e3bbe6c8c9b0e533bfae156ef25
[OpenSRF.git] / src / perl / lib / OpenSRF / Utils / Config.pm
1 package OpenSRF::Utils::Config::Section;
2
3 no strict 'refs';
4
5 use vars qw/@ISA $AUTOLOAD/;
6 push @ISA, qw/OpenSRF::Utils/;
7
8 use OpenSRF::Utils (':common');
9 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         my $sec = shift;
20         return $sec->__id(@_);
21 }
22
23 sub new {
24         my $self = shift;
25         my $class = ref($self) || $self;
26
27         $self = bless {}, $class;
28
29         $self->_sub_builder('__id');
30         # Hard-code this to match old bootstrap.conf section name
31         $self->__id('bootstrap');
32
33         my $bootstrap = shift;
34
35         foreach my $key (sort keys %$bootstrap) {
36                 $self->_sub_builder($key);
37                 $self->$key($bootstrap->{$key});
38         }
39
40         return $self;
41 }
42
43 package OpenSRF::Utils::Config;
44
45 use vars qw/@ISA $AUTOLOAD $VERSION $OpenSRF::Utils::ConfigCache/;
46 push @ISA, qw/OpenSRF::Utils/;
47
48 use FileHandle;
49 use XML::LibXML;
50 use OpenSRF::Utils (':common');  
51 use OpenSRF::Utils::Logger;
52 use Net::Domain qw/hostfqdn/;
53
54 #use overload '""' => \&OpenSRF::Utils::Config::dump_ini;
55
56 sub import {
57         my $class = shift;
58         my $config_file = shift;
59
60         return unless $config_file;
61
62         $class->load( config_file => $config_file);
63 }
64
65 sub dump_ini {
66         no warnings;
67         my $self = shift;
68         my $string;
69         my $included = 0;
70         if ($self->isa('OpenSRF::Utils::Config')) {
71                 if (UNIVERSAL::isa(scalar(caller()), 'OpenSRF::Utils::Config' )) {
72                         $included = 1;
73                 } else {
74                         $string = "# Main File:  " . $self->FILE . "\n\n" . $string;
75                 }
76         }
77         for my $section ( ('__id', grep { $_ ne '__id' } sort keys %$self) ) {
78                 next if ($section eq 'env' && $self->isa('OpenSRF::Utils::Config'));
79                 if ($section eq '__id') {
80                         $string .= '['.$self->SECTION."]\n" if ($self->isa('OpenSRF::Utils::Config::Section'));
81                 } elsif (ref($self->$section)) {
82                         if (ref($self->$section) =~ /ARRAY/o) {
83                                 $string .= "list:$section = ". join(', ', @{$self->$section}) . "\n";
84                         } elsif (UNIVERSAL::isa($self->$section,'OpenSRF::Utils::Config::Section')) {
85                                 if ($self->isa('OpenSRF::Utils::Config::Section')) {
86                                         $string .= "subsection:$section = " . $self->$section->SECTION . "\n";
87                                         next;
88                                 } else {
89                                         next if ($self->$section->{__sub} && !$included);
90                                         $string .= $self->$section . "\n";
91                                 }
92                         } elsif (UNIVERSAL::isa($self->$section,'OpenSRF::Utils::Config')) {
93                                 $string .= $self->$section . "\n";
94                         }
95                 } else {
96                         next if $section eq '__sub';
97                         $string .= "$section = " . $self->$section . "\n";
98                 }
99         }
100         if ($included) {
101                 $string =~ s/^/## /gm;
102                 $string = "# Subfile:  " . $self->FILE . "\n#" . '-'x79 . "\n".'#include "'.$self->FILE."\"\n". $string;
103         }
104
105         return $string;
106 }
107
108 =head1 NAME
109  
110 OpenSRF::Utils::Config
111  
112
113 =head1 SYNOPSIS
114
115   use OpenSRF::Utils::Config;
116
117   my $config_obj = OpenSRF::Utils::Config->load( config_file   => '/config/file.cnf' );
118
119   my $attrs_href = $config_obj->bootstrap();
120
121   $config_obj->bootstrap->loglevel(0);
122
123   open FH, '>'.$config_obj->FILE() . '.new';
124   print FH $config_obj;
125   close FH;
126
127 =head1 DESCRIPTION
128
129 This module is mainly used by other OpenSRF modules to load an OpenSRF
130 configuration file.  OpenSRF configuration files are XML files that
131 contain a C<< <config> >> root element and an C<< <opensrf> >> child
132 element (in XPath notation, C</config/opensrf/>). Each child element
133 is converted into a hash key=>value pair. Elements that contain other
134 XML elements are pushed into arrays and added as an array reference to
135 the hash. Scalar values have whitespace trimmed from the left and
136 right sides.
137
138 Child elements of C<< <config> >> other than C<< <opensrf> >> are
139 currently ignored by this module.
140
141 =head1 EXAMPLE
142
143 Given an OpenSRF configuration file named F<opensrf_core.xml> with the
144 following content:
145
146   <?xml version='1.0'?>
147   <config>
148     <opensrf>
149       <router_name>router</router_name>
150
151       <routers> 
152         <router>localhost</router>
153         <router>otherhost</router>
154       </routers>
155
156       <logfile>/var/log/osrfsys.log</logfile>
157     </opensrf>
158   </config>
159
160 ... calling C<< OpenSRF::Utils::Config->load(config_file =>
161 'opensrf_core.xml') >> will create a hash with the following
162 structure:
163
164   {
165     router_name => 'router',
166     routers => ['localhost', 'otherhost'],
167     logfile => '/var/log/osrfsys.log'
168   }
169
170 You can retrieve any of these values by name from the bootstrap
171 section of C<$config_obj>; for example:
172
173   $config_obj->bootstrap->router_name
174
175 =head1 NOTES
176
177 For compatibility with a previous version of OpenSRF configuration
178 files, the F</config/opensrf/> section has a hardcoded name of
179 B<bootstrap>. However, future iterations of this module may extend the
180 ability of the module to parse the entire OpenSRF configuration file
181 and provide sections named after the sibling elements of
182 C</config/opensrf>.
183
184 Hashrefs of sections can be returned by calling a method of the object
185 of the same name as the section.  They can be set by passing a hashref
186 back to the same method.  Sections will B<NOT> be autovivicated,
187 though.
188
189
190 =head1 METHODS
191
192
193
194 =head2 OpenSRF::Utils::Config->load( config_file => '/some/config/file.cnf' )
195
196 Returns a OpenSRF::Utils::Config object representing the config file
197 that was loaded.  The most recently loaded config file (hopefully the
198 only one per app) is stored at $OpenSRF::Utils::ConfigCache. Use
199 OpenSRF::Utils::Config::current() to get at it.
200
201 =cut
202
203 sub load {
204         my $pkg = shift;
205         $pkg = ref($pkg) || $pkg;
206
207         my %args = @_;
208
209         (my $new_pkg = $args{config_file}) =~ s/\W+/_/g;
210         $new_pkg .= "::$pkg";
211         $new_section_pkg .= "${new_pkg}::Section";
212
213         {       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         return $new_pkg->_load( %args );
226 }
227
228 sub _load {
229         my $pkg = shift;
230         $pkg = ref($pkg) || $pkg;
231         my $self = {@_};
232         bless $self, $pkg;
233
234         no warnings;
235         if ((exists $$self{config_file} and OpenSRF::Utils::Config->current) and (OpenSRF::Utils::Config->current->FILE eq $$self{config_file}) and (!$self->{force})) {
236                 delete $$self{force};
237                 return OpenSRF::Utils::Config->current();
238         }
239
240         $self->_sub_builder('__id');
241         $self->FILE($$self{config_file});
242         delete $$self{config_file};
243         return undef unless ($self->FILE);
244
245         $self->load_config();
246         $self->load_env();
247         $self->mangle_dirs();
248         $self->mangle_logs();
249
250         $OpenSRF::Utils::ConfigCache = $self unless $self->nocache;
251         delete $$self{nocache};
252         delete $$self{force};
253         return $self;
254 }
255
256 sub sections {
257         my $self = shift;
258         my %filters = @_;
259
260         my @parts = (grep { UNIVERSAL::isa($_,'OpenSRF::Utils::Config::Section') } values %$self);
261         if (keys %filters) {
262                 my $must_match = scalar(keys %filters);
263                 my @ok_parts;
264                 foreach my $part (@parts) {
265                         my $part_count = 0;
266                         for my $fkey (keys %filters) {
267                                 $part_count++ if ($part->$key eq $filters{$key});
268                         }
269                         push @ok_parts, $part if ($part_count == $must_match);
270                 }
271                 return @ok_parts;
272         }
273         return @parts;
274 }
275
276 sub current {
277         return $OpenSRF::Utils::ConfigCache;
278 }
279
280 sub FILE {
281         return shift()->__id(@_);
282 }
283
284 sub load_env {
285         my $self = shift;
286         my $host = $ENV{'OSRF_HOSTNAME'} || hostfqdn();
287         chomp $host;
288         $$self{env} = $self->section_pkg->new;
289         $$self{env}{hostname} = $host;
290 }
291
292 sub mangle_logs {
293         my $self = shift;
294         return unless ($self->logs && $self->dirs && $self->dirs->log_dir);
295         for my $i ( keys %{$self->logs} ) {
296                 next if ($self->logs->$i =~ /^\//);
297                 $self->logs->$i($self->dirs->log_dir."/".$self->logs->$i);
298         }
299 }
300
301 sub mangle_dirs {
302         my $self = shift;
303         return unless ($self->dirs && $self->dirs->base_dir);
304         for my $i ( keys %{$self->dirs} ) {
305                 if ( $i ne 'base_dir' ) {
306                         next if ($self->dirs->$i =~ /^\//);
307                         my $dir_tmp = $self->dirs->base_dir."/".$self->dirs->$i;
308                         $dir_tmp =~ s#//#/#go;
309                         $dir_tmp =~ s#/$##go;
310                         $self->dirs->$i($dir_tmp);
311                 }
312         }
313 }
314
315 sub load_config {
316         my $self = shift;
317         my $parser = XML::LibXML->new();
318
319         # Hash of config values
320         my %bootstrap;
321         
322         # Return an XML::LibXML::Document object
323         my $config = $parser->parse_file($self->FILE);
324
325         unless ($config) {
326                 OpenSRF::Utils::Logger->error("Could not open ".$self->FILE.": $!\n");
327                 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         my $osrf_cfg = $config->findnodes('/config/opensrf/child::*');
333
334         # Iterate through the nodes to pull out key=>value pairs of config settings
335         foreach my $node ($osrf_cfg->get_nodelist()) {
336                 my $child_state = 0;
337
338                 # This will be overwritten if it's a scalar setting
339                 $bootstrap{$node->nodeName()} = [];
340
341                 foreach my $child_node ($node->childNodes) {
342                         # from libxml/tree.h: nodeType 1 = ELEMENT_NODE
343                         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             my $content = OpenSRF::Utils::Config::extract_child($child_node);
348                         push(@{$bootstrap{$node->nodeName()}}, $content) if $content;
349                         $child_state = 1;
350                 }
351                 if (!$child_state) {
352                         $bootstrap{$node->nodeName()} = OpenSRF::Utils::Config::extract_text($node->textContent);
353                 }
354         }
355
356         my $section = $self->section_pkg->new(\%bootstrap);
357         my $sub_name = $section->SECTION;
358         $self->_sub_builder($sub_name);
359         $self->$sub_name($section);
360
361 }
362 sub extract_child {
363     my $node = shift;
364     use OpenSRF::Utils::SettingsParser;
365     return OpenSRF::Utils::SettingsParser::XML2perl($node);
366 }
367
368 sub extract_text {
369         my $self = shift;
370         $self =~ s/^\s*([.*?])\s*$//m;
371         return $self;
372 }
373
374 #------------------------------------------------------------------------------------------------------------------------------------
375
376 =head1 SEE ALSO
377
378         OpenSRF::Utils
379
380 =head1 LIMITATIONS
381
382 Elements containing heterogeneous child elements are treated as though they have the same element name;
383 for example:
384   <routers>
385     <router>localhost</router>
386     <furniture>chair</furniture>
387   </routers>
388
389 ... will simply generate a key=>value pair of C<< routers => ['localhost', 'chair'] >>.
390
391 =head1 BUGS
392
393 No known bugs, but report any to open-ils-dev@list.georgialibraries.org or mrylander@gmail.com.
394
395 =head1 COPYRIGHT AND LICENSING
396
397 Copyright (C) 2000-2007, Mike Rylander
398 Copyright (C) 2007, Laurentian University, Dan Scott <dscott@laurentian.ca>
399
400 The OpenSRF::Utils::Config module is free software. You may distribute under the terms
401 of the GNU General Public License version 2 or greater.
402
403 =cut
404
405
406 1;