File Coverage

blib/lib/Keystone/Resolver.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             # $Id: Resolver.pm,v 1.37 2008-05-01 09:25:39 mike Exp $
2              
3             package Keystone::Resolver;
4              
5 4     4   45406 use 5.008;
  4         13  
  4         152  
6 4     4   23 use strict;
  4         6  
  4         134  
7 4     4   20 use warnings;
  4         7  
  4         110  
8 4     4   2092 use Keystone::Resolver::Utils;
  4         12  
  4         169  
9 4     4   2281 use Keystone::Resolver::LogLevel;
  4         1338  
  4         117  
10 4     4   2736 use Keystone::Resolver::OpenURL;
  0            
  0            
11             use Keystone::Resolver::ContextObject;
12             use Keystone::Resolver::Descriptor;
13             use Keystone::Resolver::Database;
14             use Keystone::Resolver::ResultSet;
15              
16             our $VERSION = '1.23';
17              
18              
19             =head1 NAME
20              
21             Keystone::Resolver - an OpenURL resolver
22              
23             =head1 SYNOPSIS
24              
25             use Keystone::Resolver;
26             $resolver = new Keystone::Resolver();
27             $openURL = $resolver->openURL($args, $base, $referer);
28             ($type, $content) = $openURL->resolve();
29             print "Content-type: $type\r\n\r\n$content";
30              
31             =head1 DESCRIPTION
32              
33             This is the top-level class of Index Data's Keystone Resolver. It
34             delegates the process of resolving OpenURLs to a swarm of worker
35             classes.
36              
37             =head1 METHODS
38              
39             =cut
40              
41             =head2 new()
42              
43             $resolver = new Keystone::Resolver();
44             $resolver = new Keystone::Resolver(logprefix => "Keystone Resolver");
45             $resolver = new Keystone::Resolver(logprefix => "Keystone Resolver",
46             xsltdir => "/home/me/xslt");
47              
48             Creates a new resolver that can be used to resolve OpenURLs. If
49             arguments are provided, they are taken to be pairs that specify the
50             names and values of options. See the documentation of the C
51             method below for information about specific options.
52              
53             One option is special to this constructor: if C<_rw> is provided and
54             true, then the database is opened readwrite rather then readonly
55             (which is the default).
56              
57             The resolver object accumulates some state as it goes along, so it
58             is generally more efficient to keep using a single resolver than to
59             make new one every time you need to resolve an OpenURL.
60              
61             =cut
62              
63             sub new {
64             my $class = shift();
65             my(%options) = @_;
66              
67             my $rw = delete $options{_rw};
68             my $xsltdir = $ENV{KRxsltdir} || "../etc/xslt";
69             my $this = bless {
70             parser => undef, # set when needed in parser()
71             xslt => undef, # set when needed in xslt()
72             ua => undef, # set when needed in ua()
73             stylesheets => {}, # cache, populated by stylesheet()
74             db => {}, # cache, populated by db()
75             rw => $rw,
76             options => {},
77             }, $class;
78              
79             # Initial options can be overridden by creation-time arguments.
80             # They should probably take default values from the Config table
81             # of the RDB instead of hard-wired values.
82             $this->option(logprefix => $0);
83             $this->option(loglevel => $ENV{KRloglevel} || 0);
84             $this->option(xsltdir => $xsltdir);
85             foreach my $key (keys %options) {
86             $this->option($key, $options{$key});
87             }
88              
89             $this->log(Keystone::Resolver::LogLevel::LIFECYCLE, "new resolver $this");
90             return $this;
91             }
92              
93              
94             sub DESTROY {
95             my $this = shift();
96             static_log(Keystone::Resolver::LogLevel::LIFECYCLE, "dead resolver $this");
97             return; # The rest of this is unnecessary
98             my @names = sort keys %{ $this->{db} };
99             foreach my $name (@names) {
100             static_log(Keystone::Resolver::LogLevel::LIFECYCLE,
101             "killing DB '$name'");
102             undef $this->{db}->{$name};
103             }
104             }
105              
106              
107             =head2 option()
108              
109             $level = $resolver->option("loglevel");
110             $oldpath = $resolver->option(xsltdir => "/home/me/xslt");
111              
112             Gets and sets options in a C object. When called with a
113             single argument, returns the value the resolver has for that key.
114             When called with two arguments, a key and a value, sets the specified
115             new value for that key and returns the old value anyway.
116              
117             Supported options include:
118              
119             =over 4
120              
121             =item logprefix
122              
123             The initial string emitted at the beginning of each line of debugging
124             output generated by the C method. The default value is the
125             name of the running program.
126              
127             =item loglevel
128              
129             A bit mask indicating the categories of message that should be logged
130             by calls to the C method. Should be set to the bitwise AND of
131             zero or more of the symbolic constants defined in
132             C. See the documentation of that module for a
133             description of the recognised categories.
134              
135             =item xsltdir
136              
137             The directory where the resolver looks for XSLT files.
138              
139             =back
140              
141             =cut
142              
143             use vars qw($_last_loglevel $_last_logprefix);
144             # These must be set, probably by new(), before being used
145             $_last_loglevel = undef;
146             $_last_logprefix = undef;
147              
148             # ### There is an issue with logging modality here: if a call is made
149             # with loglevel or dbi_trace > 0, then subsequent requests on the same
150             # resolver will inherit that logging level. Maybe each request
151             # should explicitly zero the logging levels?
152             #
153             sub option {
154             my $this = shift();
155             my($key, $value) = @_;
156              
157             my $old = $this->{options}->{$key};
158             if (defined $value) {
159             # Special cases for "loglevel" to allow hex and octal bitmasks
160             # and to parse non-numeric level-lists.
161             if ($key eq "loglevel") {
162             $value = oct($value)
163             if $value =~ /^0/;
164             $value = Keystone::Resolver::LogLevel::num($value)
165             if $value !~ /^\d+$/;
166             }
167             #print STDERR "setting '$key' to '$value'\n";
168             $this->{options}->{$key} = $value;
169              
170             # Save logging configuration for use of static_log()
171             $_last_loglevel = $value if $key eq "loglevel";
172             $_last_logprefix = $value if $key eq "logprefix";
173              
174             if ($key eq "dbi_trace") {
175             ### Two nastinesses here: the peek inside the database's
176             # internal structures, and the fact that we are operating
177             # on the default database. We could "fix" the latter by
178             # changing the global state of the DBI library, but that
179             # would probably be even worse; or by getting db() from
180             # the OpenURL object (which might have a query parameter
181             # specifying which DB to work on) but we don't know what
182             # OpenURL object we're using.
183             $this->db()->{dbh}->trace($value);
184             }
185              
186             }
187              
188             return $old;
189             }
190              
191              
192             =head2 log(), static_log()
193              
194             $resolver->log(Keystone::Resolver::LogLevel::CHITCHAT, "starting up");
195             Keystone::Resolver::static_log(Keystone::Resolver::LogLevel::CHITCHAT, "end");
196              
197             C Logs a message to the standard error stream if the log-level
198             of the resolver includes the level specified as the first argument in
199             its bitmask. If so, the message consists of the logging prefix (by
200             default the name of the program), the label of the specified level in
201             parentheses, and all other arguments concatenated, finishing with a
202             newline.
203              
204             C is provided for situtation in which no resolver object
205             is available, e.g. in C methods. It behaves the same as
206             C but is a function, not a method. Since it cannot consult the
207             options of a resolver object, it uses
208             I.
209             For most applications, in which only a single resolver is in use, this
210             will work just fine. Complex applications that use multiple resolvers
211             should not rely on the integrity of static logging.
212              
213             =cut
214              
215             sub log {
216             my $this = shift();
217             _log($this->option("loglevel"), $this->option("logprefix"), @_);
218             }
219              
220              
221             sub static_log {
222             _log($_last_loglevel, $_last_logprefix, @_);
223             }
224              
225              
226             sub _log {
227             my($loglevel, $logprefix, $level, @args) = @_;
228              
229             if ($loglevel & $level) {
230             ### could check another option for whether to include PID
231             my $label = Keystone::Resolver::LogLevel::label($level);
232             print STDERR "$logprefix ($label): ", @args, "\n";
233             #use Carp; carp "$logprefix ($label): ", @args;
234             }
235             }
236              
237              
238             =head2 openURL()
239              
240             $openURL = $resolver->openURL($args, $base, $referer);
241              
242             Creates a new C object using this
243             C and the specified arguments and referer. This
244             is a shortcut for
245              
246             new Keystone::Resolver::OpenURL($resolver, $args, $base, $referer)
247              
248             =cut
249              
250             sub openURL {
251             my $this = shift();
252              
253             #use Carp qw(cluck); cluck("$$: creating new OpenURL(" . join(", ", map { defined $_ ? "'$_'" : "undef" } @_) . ")");
254             return new Keystone::Resolver::OpenURL($this, @_);
255             }
256              
257              
258             =head2 parser()
259              
260             $parser = $resolver->parser();
261              
262             Returns the XML parser associated with this resolver. If it does not
263             yet have a parser, then one is created for it, cached for next time,
264             and returned. The parser is an C object: see the
265             documentation of that class for how to use it.
266              
267             =cut
268              
269             sub parser {
270             my $this = shift();
271              
272             if (!defined $this->{parser}) {
273             $this->{parser} = new XML::LibXML();
274             }
275              
276             return $this->{parser};
277             }
278              
279              
280             =head2 xslt()
281              
282             $xslt = $resolver->xslt();
283              
284             Returns the XSLT processor associated with this resolver. If it does
285             not yet have a XSLT processor, then one is created for it, cached for
286             next time, and returned. The XSLT processor is an C
287             object: see the documentation of that class for how to use it.
288              
289             =cut
290              
291             sub xslt {
292             my $this = shift();
293              
294             if (!defined $this->{xslt}) {
295             $this->{xslt} = new XML::LibXSLT();
296             }
297              
298             return $this->{xslt};
299             }
300              
301              
302             =head2 ua()
303              
304             $ua = $resolver->ua();
305              
306             Returns the LWP User Agent associated with this resolver. If it does
307             not yet have a User Agent, then one is created for it, cached for next
308             time, and returned.
309              
310             =cut
311              
312             sub ua {
313             my $this = shift();
314              
315             if (!defined $this->{ua}) {
316             $this->{ua} = new LWP::UserAgent();
317             }
318              
319             return $this->{ua};
320             }
321              
322              
323             =head2 stylesheet()
324              
325             $stylesheet1 = $resolver->stylesheet();
326             $stylesheet2 = $resolver->stylesheet("foo");
327              
328             Returns a stylesheet object for the XSLT stylesheet named in the
329             argument, or for the default stylesheet if no argument is supplied.
330             The returned object is an : see the
331             documentation of that class for how to use it.
332              
333             =cut
334              
335             # $this->{stylesheets} is used only in this function. It's a cache
336             # mapping a full stylesheet pathname to a duple consisting of that
337             # file's last modification time and the compiled stylesheet described
338             # by it. The file is compiled if we're asked for it for the first
339             # time or if it's changed since the last compilation.
340             #
341             sub stylesheet {
342             my $this = shift();
343             my($name) = @_;
344              
345             $name ||= "default";
346             my $cache = $this->{stylesheets};
347             my $filename = $this->option("xsltdir") . "/$name.xsl";
348             my(@stat) = stat($filename)
349             or die "can't stat XSLT file '$filename': $!";
350             my $mtime = $stat[9];
351             $this->log(Keystone::Resolver::LogLevel::CACHECHECK,
352             "checking cache for XSLT file '$name', age $mtime");
353              
354             if (!defined $cache->{$name} ||
355             $mtime > $cache->{$name}->[0]) {
356             my $style_doc = $this->parser()->parse_file($filename);
357             my $stylesheet = $this->xslt()->parse_stylesheet($style_doc);
358             $cache->{$name} = [ $mtime, $stylesheet ];
359             $this->log(Keystone::Resolver::LogLevel::PARSEXSLT,
360             "parsed XSLT file '$name', age $mtime");
361             }
362              
363             return $cache->{$name}->[1];
364             }
365              
366              
367             =head2 db()
368              
369             $db = $resolver->db();
370             $db = $resolver->db("kr-backup");
371              
372             Returns the database object associated with this specified name for
373             this resolver. If no name is provided, the default name specified by
374             the C environment variable is used; if this is also missing,
375             "kr" is used. If the resolver does not yet have a
376             database handle associated with this name, then one is created for it,
377             cached for next time, and returned. The handle is a
378             C object: see the documentation for how
379             to use it.
380              
381             =cut
382              
383             sub db {
384             my $this = shift();
385             my($name) = @_;
386              
387             $name ||= $ENV{KRdb} || "kr";
388             my $cache = $this->{db};
389             return $cache->{$name}
390             if defined $cache->{$name};
391              
392             $cache->{$name} =
393             new Keystone::Resolver::Database($this, $name, $this->{rw});
394             ### We want the cached Database references to be weak, so that
395             # the databases get destroyed before the resolver that they
396             # depend on. Weakening the reference should do this but doesn't
397             # seem to have any effect (suggesting a bug in Perl?). So we
398             # won't do it, in case it has unanticipated side-effects.
399             #Scalar::Util::weaken($cache->{$name});
400             return $cache->{$name};
401             }
402              
403              
404             =head1 AUTHOR
405              
406             Mike Taylor Emike@indexdata.comE
407              
408             First version Tuesday 9th March 2004.
409              
410             =head1 SEE ALSO
411              
412             C,
413             C,
414             C,
415             C,
416             C,
417             C,
418             C.
419              
420             =cut
421              
422              
423             1;