File Coverage

blib/lib/SeeAlso/Source/BeaconAggregator/Publisher.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package SeeAlso::Source::BeaconAggregator::Publisher;
2 3     3   24788 use strict;
  3         3  
  3         76  
3 3     3   11 use warnings;
  3         2  
  3         150  
4              
5             our $VERSION = "0.2_90";
6              
7             =head1 NAME
8              
9             SeeAlso::Source::BeaconAggregator::Publisher - additional methods for SeeAlso::Source::BeaconAggregator
10              
11             =head1 SYNOPSIS
12              
13             $source = SeeAlso::Source::BeaconAggregator::Publisher->new(...);
14              
15             =head1 DESCRIPTION
16              
17             This package provides the functionallity to export a BEACON file from the
18             data connected with an SeeAlso::Source::BeaconAggregator instance and
19             also the additional formats "redirect" and "sources" which universally
20             can be used as callbacks for SeeAlso::Server (replacing the default
21             "seealso" method yielding JSON data).
22              
23             =cut
24              
25             our %Defaults = (
26             # "REVISIT" => 86400, # one day
27             "REVISIT" => undef, # no default (leave empty unless otherwise set)
28             "uAformatname" => "sources",
29             "beaconformatname" => "beacon",
30             "FORMAT" => "BEACON",
31             "VERSION" => "0.1", # no other exist
32             );
33              
34 3     3   666 use SeeAlso::Source::BeaconAggregator;
  0            
  0            
35             use Carp;
36              
37             =head2 new ( ... )
38              
39             Creates an SeeAlso::Source::BeaconAggregator object with additional methods from
40             this package enabled
41              
42             =cut
43              
44             sub new { # directly create BeaconAggregator instance with extended features...
45             my $class = shift @_;
46             push(@SeeAlso::Source::BeaconAggregator::ISA, $class);
47             return SeeAlso::Source::BeaconAggregator->new(@_);
48             }
49              
50              
51             =head2 activate ()
52              
53             Makes SeeAlso::Source::BeaconAggregator objects member of this class,
54             globally enabling the additional methods
55              
56             Usage:
57              
58             $db = SeeAlso::Source::BeaconAggregator::Maintenance->new(...);
59             ...
60             do stuff
61             ...
62             require SeeAlso::Source::BeaconAggregator::Publisher
63             or die "could not require Publisher extension";
64             SeeAlso::Source::BeaconAggregator::Publisher->activate(); # "recast" all objects
65             ...
66             do more stuff
67              
68             =cut
69             sub activate { # enrich SeeAlso::Source and derived classes with our methods
70             my $class = shift @_;
71             push(@SeeAlso::Source::BeaconAggregator::ISA, $class);
72             return 1;
73             }
74              
75              
76             ### Produktion der Beacon-Datei
77              
78             =head2 beacon ( [dumpmeta arguments] )
79              
80             produces a BEACON file (however, $cgibase is mandatory)
81              
82             =head2 dumpmeta ( [$cgibase, [$uAformatname, [$headersonly]]] [, $preset])
83              
84             produces only the meta fields of a BEACON file
85              
86             Meta fields are generated from the $preset Hashref, falling back to
87             values stored in the database, falling back to reasonable default
88             values.
89              
90             Arguments:
91              
92             =over 8
93              
94             =item $cgibase
95              
96             URL of the SeeAlso service the BEACON file is provided for
97              
98             =item $uAformatname
99              
100             unAPI format name to be used as target (Default: "sources")
101              
102             =item $headersonly
103              
104             currently unused
105              
106             =item $preset
107              
108             Hashref of Beacon header fields overriding the contents of the database
109              
110             =back
111              
112             Regular Usage:
113              
114             $db = SeeAlso::Source::BeaconAggregator::Publisher->new(...);
115             binmode(STDOUT, ":utf8");
116             my $cgibase = "http://address/of/service";
117             my ( $error, $headerref) = $db->beacon($cgibase, @ARGV, {'FORMAT' => 'PND-BEACON'});
118              
119              
120             CGI Usage:
121              
122             $format = $CGI->param('format') || "";
123             if ( $format eq "beacon" ) { # bypass SeeAlso::Server->query() b/c performance / interim storage
124             insert access restrictions here...
125             do_beacon($source, $CGI);
126             }
127             ...
128              
129             sub do_beacon {
130             my ($self, $cgi) = @_; # Of type SeeAlso::Source::BeaconAggregator
131             unless ( $self->can("beacon") ) {
132             croak "On the fly generation of beacon Files not supported by this service";}
133             my $cgibase = $cgi->url(-path_info=>1);
134              
135             print $cgi->header( -status => 200,
136             -expires => '+1d',
137             -type => 'text/plain',
138             -charset => 'utf-8',
139             );
140             return $self->beacon($cgibase, "sources", {}); # prints directly to stdout..., returns $error, $headerref
141             }
142              
143             =cut
144              
145             sub beacon {
146             my ($self) = shift @_ or croak("beacon is a method!"); # Of type SeeAlso::Source::BeaconAggregator
147             my ($error, $headerref) = $self->dumpmeta(@_);
148             croak("Error generating Header, will not proceed") if $error;
149              
150             print @$headerref;
151              
152             my $c = (defined $self->{identifierClass}) ? $self->{identifierClass} : $self->autoIdentifier();
153              
154             my ($sth, $sthexpl) = $self->stmtHdl(<<"XxX");
155             SELECT hash, COUNT(DISTINCT seqno) FROM beacons GROUP BY hash ORDER BY hash;
156             XxX
157             $self->stmtExplain($sthexpl) if $ENV{'DBI_PROFILE'};
158             $sth->execute() or croak("Could not execute >".$sth->{Statement}."<: ".$sth->errstr);
159             my $rows = 0;
160             while ( my $row = $sth->fetchrow_arrayref ) {
161             $rows++;
162             my $expanded = $row->[0];
163             if ( defined $c ) {
164             # compat: hash might not take an argument, must resort to value, has to be cleared before...
165             $c->value("");
166             my $did = $c->hash($row->[0]) || $c->value($row->[0]);
167             $expanded = $c->can("pretty") ? $c->pretty() : $c->value();
168             # illegal identifier b/c different classes for loading and exporting?
169             next unless defined $expanded && ($expanded ne "");
170             }
171             print $expanded.(($row->[1] > 1) ? "|".$row->[1] : "")."\n";
172             }
173              
174             return $rows, $headerref;
175             }
176              
177             sub dumpmeta { # cgibase unAPIformatname headers_only {preset}
178             my ($self) = shift @_ or croak("dumpmeta is a method!"); # Of type SeeAlso::Source::BeaconAggregator
179             my ($error, @result) = (0, ());
180              
181             my $cgibase = shift @_ if @_ && !ref($_[0]);
182             my $uAformatname = shift @_ if @_ && !ref($_[0]);
183             $uAformatname ||= $Defaults{'uAformatname'};
184             my $headersonly = shift @_ if @_ && !ref($_[0]);
185             my $preset = (@_ && ref($_[0])) ? (shift @_) : {};
186              
187             my ($metasth, $metasthexpl) = $self->stmtHdl(<<"XxX");
188             SELECT key, val FROM osd;
189             XxX
190             $self->stmtExplain($metasthexpl) if $ENV{'DBI_PROFILE'};
191             $metasth->execute() or croak("Could not execute >".$metasth->{Statement}."<: ".$metasth->errstr);
192              
193             my (%osd, %beaconmeta);
194             while ( my $aryref = $metasth->fetchrow_arrayref ) {
195             my ($key, $val) = @$aryref;
196             next unless $val;
197             if ($key =~ s/^bc// ) { # BeaconMeta Fields
198             $beaconmeta{$key} = $val}
199             elsif ( exists $osd{$key} ) {
200             if ( ref($osd{$key}) ) {
201             push(@{$osd{$key}}, $val)}
202             else {
203             $osd{$key} = [$osd{$key}, $val]};
204             }
205             else {
206             $osd{$key} = $val};
207             };
208             my @osdexamples;
209             if ( $osd{'Examples'} && ref($osd{'Examples'}) ) {
210             foreach my $expl ( @{$osd{'Examples'}} ) {
211             $expl =~ s/\s*\|.*$//;
212             push(@osdexamples, $expl);
213             }
214             }
215             elsif ( my $expl = $osd{'Examples'} ) {
216             $expl =~ s/\s*\|.*$//;
217             push(@osdexamples, $expl);
218             };
219              
220             foreach ( grep /^[A-Z]+$/, keys %$preset ) {
221             $beaconmeta{$_} = $preset->{$_}}
222             # Mandatory fields
223             push(@result, "#FORMAT: ".($beaconmeta{'FORMAT'} || $Defaults{'FORMAT'})."\n");
224             push(@result, "#VERSION: ".($beaconmeta{'VERSION'} || $Defaults{'VERSION'})."\n");
225             if ( $beaconmeta{'TARGET'} ) {
226             $beaconmeta{'TARGET'} =~ s/^\{BASE\}/$cgibase/;
227             push(@result, "#TARGET: $beaconmeta{'TARGET'}\n");
228             }
229             elsif ( $cgibase ) {
230             push(@result, "#TARGET: $cgibase?format=$uAformatname&id={ID}\n")}
231             else {
232             carp "Don't know how to construct the mandatory #TARGET field!";
233             $error ++;
234             }
235              
236             my $timestamp = $preset->{'TIMESTAMP'} || $osd{DateModified} || $^T;
237             push(@result, "#TIMESTAMP: ".SeeAlso::Source::BeaconAggregator::tToISO($timestamp)."\n") if $timestamp > 0;
238             my $revisit = ($beaconmeta{'REVISIT'} || $Defaults{'REVISIT'}) || "";
239             $revisit =~ tr/ //d;
240             $revisit =~ s/(\d+)mo\w*/($1*30)."d"/ei;
241             $revisit =~ s/(\d+)M\w*/($1*30)."d"/e;
242             $revisit =~ s/(\d+)w\w*/($1*7)."d"/ei;
243             $revisit =~ s/(\d+)d\w*/($1*24)."h"/ei;
244             $revisit =~ s/(\d+)h\w*/($1*60)."m"/ei;
245             $revisit =~ s/(\d+)m\w*/($1*60)."s"/ei;
246             $revisit =~ s/(\d+)s\w*/$1/i;
247             push(@result, "#REVISIT: ".SeeAlso::Source::BeaconAggregator::tToISO($timestamp + $revisit)."\n") if $revisit && ($revisit =~ /^[+-]?\d+$/) && ($revisit > 0);;
248              
249             # $beaconmeta{'UPDATE'} ||= "daily";
250             $beaconmeta{'FEED'} ||= "$cgibase?format=".$Defaults{'beaconformatname'} if $cgibase;
251             $beaconmeta{'EXAMPLES'} ||= join("|", @osdexamples);
252             $beaconmeta{'CONTACT'} ||= $self->{Contact} || $osd{'Contact'};
253             $beaconmeta{'DESCRIPTION'} ||= $self->{Description} || $osd{'Description'};
254             $beaconmeta{'NAME'} ||= $self->{ShortName} || $osd{'ShortName'};
255             foreach ( grep !/^(FORMAT|REVISIT|TARGET|TIMESTAMP|VERSION)$/, SeeAlso::Source::BeaconAggregator->beaconfields() ) {
256             next unless my $val = $beaconmeta{$_};
257             next if $val =~ /^-/;
258             $val =~ s/\s+/ /g; $val =~ s/^\s+//; $val =~ s/\s+$//;
259             push(@result, "#$_: $val\n");
260             }
261              
262             # extract admin info of last transaction (i.e. last possible modification of underlying data)
263             # alternatively: SELECT seqno, utime FROM repos WHERE seqno=(SELECT MAX(seqno) FROM repos);
264             my ($laststh, $laststhexpl) = $self->stmtHdl(<<"XxX");
265             SELECT MAX(seqno), MAX(mtime) FROM repos;
266             XxX
267             $self->stmtExplain($laststhexpl) if $ENV{'DBI_PROFILE'};
268             $laststh->execute() or croak("Could not execute >".$laststh->{Statement}."<: ".$laststh->errstr);
269             if ( my $aryref = $laststh->fetchrow_arrayref ) {
270             my ($sq, $ut) = @$aryref;
271             push(@result, "#X-REVISION: $sq [".SeeAlso::Source::BeaconAggregator::tToISO($ut)."]\n") if $sq;
272             };
273             my $admref = $self->admhash();
274             if ( my $cu = $admref->{'gcountu'} ) {
275             my $type = $admref->{'IDENTIFIER_CLASS'} || "";
276             push(@result, "#X-EXTENT: $cu unique identifiers".($type ? " of type $type" : "")."\n");
277             };
278              
279              
280             ## PND-BEACON
281             # CONTACT => ['VARCHAR(63)'],
282             # INSTITUTION => ['VARCHAR(255)'],
283             # ISIL => ['VARCHAR(63)'],
284             # DESCRIPTION => ['VARCHAR(255)'],
285             ## BEACON
286             # MESSAGE => ['VARCHAR(255)'], # enthaelt {hits}
287             # ONEMESSAGE => ['VARCHAR(255)'],
288             # SOMEMESSAGE => ['VARCHAR(255)'],
289             # PREFIX => ['VARCHAR(255)'],
290             ## WInofficial
291             # NAME => ['VARCHAR(255)'],
292              
293             return $error, \@result;
294             }
295              
296              
297             =head2 redirect ( $server, $format, $extra, $query )
298              
299             Produces an HTTP redirect page, HTML content contains very terse details in case
300             of multiple results.
301              
302             This subroutine may be used as callback method in SeeAlso::Server
303              
304             Usage is a bit cludgy due to author's lack of understanding of SeeAlso::Server
305              
306             $source = SeeAlso::Sources::BeaconAggregator::Publisher->new(...);
307             $CGI = CGI->new();
308              
309             $formats = {
310             ...
311             redirect => {
312             type => "text/html",
313             docs => "http://www.endofthe.net/",
314             # method => \&SeeAlso::Source::BeaconAggregator::Publisher::redirect,
315             #redirect_300 => 'sources',
316             }
317             };
318              
319             $server = SeeAlso::Server->new (
320             'cgi' => $CGI,
321             'formats' => $formats,
322             ...
323             );
324              
325             # Closure as fix: Server.pm does not expose self, $source and the CGI object to the format methods
326             my $oref = \&SeeAlso::Source::BeaconAggregator::Publisher::redirect;
327             $server->{'formats'}->{'redirect'}->{method}
328             = sub {return &$oref($source, $server, $method, $formats->{$method}, @_)};
329              
330             my $result = $server->query($source);
331              
332             Arguments:
333              
334             =over 8
335              
336             =item $server
337              
338             SeeAlso::Server object. Must contain a CGI object
339              
340             =item $format
341              
342             Name of a format registered with the $server object ()
343              
344             =item $extra
345              
346             Hashref with the following configuration directives
347              
348             redirect_300 => CGI 'format' parameter to be used in HTML content (eg. format=sources)
349              
350             force_single => Only regard the first hit (thus always redirect)
351              
352             =item $query
353              
354             Identifier to be queried
355              
356             =back
357              
358             =cut
359              
360             sub redirect { # Liste der Beacon-Header fuer Treffer oder einfaches redirect
361             my ($self, $server, $format, $extra, $query) = @_;
362             my $formatprops = $server->{'formats'}->{$format} || {};
363             my $cgi = $server->{'cgi'} or croak("I rely on a prepared CGI.pm object");
364              
365             my %headerdefaults = ( -type => ($formatprops->{'type'} || 'text/html'),
366             # ($formatprops->{'charset'} ? (-charset => $formatprops->{'charset'}) : ()),
367             -charset => ($formatprops->{'charset'} || 'UTF-8'),
368             -expires => ($server->{'expires'} || '+1h'),
369             );
370              
371             my ($hash, $pretty, $canon) = $self->prepare_query($query);
372             unless ( $hash ) {
373             print $cgi->header(-status => "400 Bad Request (Identifier '$query' not valid)",
374             -expires => "+1y",
375             -type => 'text/html',
376             ),
377             $cgi->start_html (-dtd => "-//W3C//DTD HTML 3.2 Final//EN",
378             -title => "No valid identifier",
379             ),
380             $cgi->p("Malformed identifier '$query'"),
381             $cgi->end_html;
382             return "";
383             };
384              
385             my $clusterid;
386             if ( $self->{cluster} ) {
387             my ($clusterh, $clusterexpl) = $self->stmtHdl("SELECT beacons.altid FROM cluster.beacons WHERE beacons.hash=? OR beacons.altid=? LIMIT 1;");
388             $self->stmtExplain($clusterexpl, $hash, $hash) if $ENV{'DBI_PROFILE'};
389             $clusterh->execute($hash, $hash);
390             while ( my $onerow = $clusterh->fetchrow_arrayref() ) {
391             $clusterid = $onerow->[0];}
392             }
393              
394             my $clause = $extra->{force_single} ? "LIMIT 1" : "ORDER BY repos.sort, repos.alias";
395             my ( $tfield,$afield, $gfield, $mfield,$nfield,$ifield) = map{ scalar $self->beaconfields($_) }
396             qw(TARGET ALTTARGET IMGTARGET MESSAGE NAME INSTITUTION);
397             # above 5 6 7 8 9 10
398             # below 0 1 2 3 4
399             # 11
400             my ($sth, $sthexpl);
401             if ( $clusterid ) { # query IN cluster
402             ($sth, $sthexpl) = $self->stmtHdl(<<"XxX");
403             SELECT beacons.hash, beacons.altid, beacons.hits, beacons.info, beacons.link,
404             repos.$tfield, repos.$afield, repos.$gfield, repos.$mfield, repos.$nfield, repos.$ifield,
405             repos.alias
406             FROM beacons NATURAL LEFT JOIN repos
407             WHERE ( (beacons.hash=?)
408             OR (beacons.hash IN (SELECT cluster.beacons.hash FROM cluster.beacons WHERE cluster.beacons.altid=?)) )
409             $clause;
410             XxX
411             $self->stmtExplain($sthexpl, $clusterid, $clusterid) if $ENV{'DBI_PROFILE'};
412             $sth->execute($clusterid, $clusterid) or croak("Could not execute >".$sth->{Statement}."<: ".$sth->errstr);
413             }
414             else {
415             ($sth, $sthexpl) = $self->stmtHdl(<<"XxX");
416             SELECT beacons.hash, beacons.altid, beacons.hits, beacons.info, beacons.link,
417             repos.$tfield, repos.$afield, repos.$gfield, repos.$mfield, repos.$nfield, repos.$ifield,
418             repos.alias
419             FROM beacons NATURAL LEFT JOIN repos
420             WHERE beacons.hash=?
421             $clause;
422             XxX
423             $self->stmtExplain($sthexpl, $hash) if $ENV{'DBI_PROFILE'};
424             $sth->execute($hash) or croak("Could not execute >".$sth->{Statement}."<: ".$sth->errstr);
425             }
426              
427             my $c = $self->{identifierClass} || undef;
428             my @rawres;
429             my %didalready;
430             while ( my $onerow = $sth->fetchrow_arrayref ) {
431             next if $onerow->[11] && exists $self->{'aliasfilter'}->{$onerow->[11]};
432             my $uri = $onerow->[4]; # Evtl. Expliziter Link
433             my $guri = "";
434              
435             my $h = $onerow->[0];
436             my $p;
437             if ( $h eq $hash ) {
438             $p = $pretty}
439             elsif ( $clusterid && ref($c) ) {
440             $c->value("");
441             my $did = $c->hash($h) || $c->value($h) || $h;
442             $p = $c->can("pretty") ? $c->pretty() : $c->value();
443             };
444             $p = ($clusterid ? $h : $pretty) unless defined $p;
445              
446             if ( $onerow->[1] ) { # Konkordanzformat
447             $uri ||= sprintf($onerow->[6] || $onerow->[5], $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($onerow->[1]));
448             $guri = sprintf($onerow->[7], $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($onerow->[1])) if $onerow->[7];
449             }
450             elsif ( $onerow->[5] ) { # normales Beacon-Format
451             $uri ||= sprintf($onerow->[5], $p);
452             $guri = sprintf($onerow->[7], $p) if $onerow->[7];
453             };
454             next unless $uri;
455              
456             # #NAME #INSTITUTION _alias
457             my $label;
458             if ( $label = $onerow->[8] ) { #MESSAGE
459             $label = sprintf($label, $onerow->[2] || "...")}
460             elsif ( $label = $onerow->[9] || $onerow->[10] || $onerow->[11] || "???" ) {
461             $label .= " (".$onerow->[1].")" if $onerow->[1]}
462              
463             push(@rawres, [$uri, $guri, $label, $onerow->[11], $onerow->[3]]) unless $didalready{join("\x7f", $label, $uri)}++;;
464             };
465             my $hits = scalar @rawres;
466              
467             if ( ! $hits ) {
468             print $cgi->header(-status => "404 Not Found (identifier '$canon')",
469             %headerdefaults),
470             $cgi->start_html (-dtd => "-//W3C//DTD HTML 3.2 Final//EN",
471             -title => "No References for $pretty",
472             ),
473             $cgi->p("No References found for ", $cgi->a({href=>"$canon"}, $pretty)),
474             $cgi->end_html;
475             return "";
476             }
477             elsif ( $hits == 1 ) {
478             return $cgi->redirect(-status => "302 Found (Redirecting for identifier '$canon')",
479             -uri => $rawres[0]->[0],
480             %headerdefaults);
481             }
482              
483             my $sources = new CGI($cgi);
484             $sources->param(-name => 'id', -value=>"$canon");
485             unless ( $canon =~ /:\/\// ) {
486             my ($osd, $beaconmeta) = $self->get_meta;
487             my $prefix = $beaconmeta->{'PREFIX'} || "";
488             $canon = "$prefix$pretty" if $prefix;
489             };
490             if ( my $multired = $extra->{redirect_300} ) {
491             $sources->param(-name => 'format', -value=>$multired);
492             print $cgi->redirect(-status => "300 Multiple Choices for identifier '$canon'",
493             -uri => $sources->url(-path_info=>1, -query=>1),
494             %headerdefaults);
495             }
496             else {
497             print $cgi->header(-status => "300 Multiple Choices for identifier '$canon'",
498             # -nph => 1, # for older CGI/mod_perl???
499             %headerdefaults);
500             # mod_perl overrides the header and adds a custom document at the end of everything
501             # therefore we force the header out (a simple print "" does not suffice) and then can
502             # safely reset the status to OK via CGI.pm leaking the Apache2::Request object
503             if ( my $r = $sources->r ) {
504             local($|) = 1;
505             print "\n";
506             $r->status(200);
507             };
508             };
509             my @result;
510             push(@result, $cgi->start_html ( -title => "$hits References for $pretty",
511             -dtd => "-//W3C//DTD HTML 3.2 Final//EN"),
512             $cgi->h1("$hits References for ", $cgi->a({href=>"$canon"}, $pretty)),
513             '
    ');
514              
515             my $rowcnt = 0;
516             foreach ( @rawres ) { # uri, guri, label, alias, info
517             if ( $_->[1] ) {
518             my $tooltip = $_->[4] ? ($_->[4]." [".$_->[2]."]") : $_->[2];
519             my $img = $cgi->a({href=>$_->[0], title=>$tooltip}, $cgi->img({src=>$_->[1], alt=>$_->[4]||$_->[2], style=>"width: 5em; border: 0pt;"}));
520             push(@result, $cgi->li({id=>"$_->[3]".++$rowcnt}, $img, $cgi->a({href=>$_->[0]}, $_->[2]), ($_->[4] ? " [".$_->[4]."]" : "")));
521             }
522             else {
523             push(@result, $cgi->li({id=>"$_->[3]".++$rowcnt}, $cgi->a({href=>$_->[0]}, $_->[2]), $_->[4] ? " [".$_->[4]."]" : ""))};
524             };
525              
526             push(@result, '');
527              
528             if ( $server->{'formats'}->{'sources'} ) {
529             $sources->param(-name => 'format', -value=>"sources");
530             push(@result, $cgi->p("[", $cgi->a({href=>($sources->url(-path_info=>1, -query=>1))}, "Details"), "]"));
531             };
532              
533             my($tu, $ts, $tcu, $tcs) = times();
534             push(@result, sprintf("", $tu, $ts, $tu+$ts), $cgi->end_html());
535             return join("\n", @result);
536             }
537              
538             =head2 sources ( $server, $format, $extra, $query )
539              
540             Produces an HTML page with details to the queried identifier (description of sources)
541              
542             This subroutine may be used as callback method in SeeAlso::Server (cf. description
543             of redirect above
544              
545             =over 8
546              
547             =item $server
548              
549             SeeAlso::Server object
550              
551              
552             =item $format
553              
554             Format selected for $server
555              
556              
557             =item $extra
558              
559             Hashref with the following configuration directives
560              
561             css => URL of css file to be referenced
562              
563             =item $query
564              
565             Identifier to be queried
566              
567             =back
568              
569             =cut
570              
571             sub sources { # Liste der Beacon-Header fuer Treffer
572             # We escape all characters except US-ASCII, because older CGI.pm's set an xml declaration
573             # which somehow interferes with IE8's adherence to the character set...
574             my ($self, $server, $format, $extra, $query) = @_;
575             my $formatprops = $server->{'formats'}->{$format} || {};
576             my $cgi = $server->{'cgi'} || CGI->new();
577              
578             my ($hash, $pretty, $canon) = $self->prepare_query($query);
579             unless ( $hash ) {
580             print $cgi->header(-status => "400 Bad Request (Identifier '$query' not valid)",
581             -expires => "+1y",
582             -type => 'text/html',
583             ),
584             $cgi->start_html (-dtd => "-//W3C//DTD HTML 3.2 Final//EN",
585             -title => "No valid identifier",
586             ),
587             $cgi->p("Malformed identifier '$query'"),
588             $cgi->end_html;
589             return "";
590             };
591              
592             my ($clusterid, %idlist);
593             my $c = $self->{identifierClass} || undef;
594             if ( $self->{cluster} ) {
595             my ($clusterh, $clusterexpl) = $self->stmtHdl("SELECT beacons.hash, beacons.altid FROM cluster.beacons WHERE beacons.hash=? OR beacons.altid=? LIMIT 1;");
596             $self->stmtExplain($clusterexpl, $hash, $hash) if $ENV{'DBI_PROFILE'};
597             $clusterh->execute($hash, $hash) or croak("Could not execute >".$clusterh->{Statement}."<: ".$clusterh->errstr);
598             while ( my $onerow = $clusterh->fetchrow_arrayref() ) {
599             $clusterid = $onerow->[1];
600             my $h = $onerow->[0];
601             if ( $c ) {
602             $c->value("");
603             my $did = $c->hash($h) || $c->value($h);
604             my $p = $c->can("pretty") ? $c->pretty() : $c->value();
605             $idlist{$p} = "";
606             }
607             else {
608             $idlist{$h} = "";
609             }
610             };
611             $idlist{$pretty} = "queriedid";
612             if ( $clusterid ) {
613             if ( $clusterid eq $hash ) {
614             $idlist{$pretty} .= " preferredid"}
615             elsif ( $c ) {
616             $c->value("");
617             my $did = $c->hash($clusterid) || $c->value($clusterid);
618             my $p = $c->can("pretty") ? $c->pretty() : $c->value();
619             $idlist{$p} = "variantid preferredid";
620             }
621             else {
622             $idlist{$clusterid} = "variantid preferredid";
623             };
624             my ($varianth, $variantexpl) = $self->stmtHdl("SELECT beacons.hash FROM cluster.beacons WHERE beacons.altid=?;");
625             $self->stmtExplain($variantexpl, $clusterid) if $ENV{'DBI_PROFILE'};
626             $varianth->execute($clusterid) or croak("Could not execute >".$varianth->{Statement}."<: ".$varianth->errstr);
627             while ( my $onerow = $varianth->fetchrow_arrayref() ) {
628             my $v = $onerow->[0];
629             if ( $c ) {
630             $c->value("");
631             my $did = $c->hash($v) || $c->value($v);
632             my $p = $c->can("pretty") ? $c->pretty() : $c->value();
633             (exists $idlist{$p}) || ($idlist{$p} = "variantid");
634             }
635             else {
636             (exists $idlist{$v}) || ($idlist{$v} = "variantid");
637             }
638             }
639             }
640             }
641              
642             my ($countsth, $countexpl);
643             if ( $clusterid ) {
644             ($countsth, $countexpl) = $self->stmtHdl(<<"XxX");
645             SELECT COUNT(DISTINCT seqno) FROM beacons
646             WHERE ( (hash=?) OR (hash IN (SELECT beacons.hash FROM cluster.beacons WHERE cluster.beacons.altid=?)) );
647             XxX
648             $self->stmtExplain($countexpl, $clusterid, $clusterid) if $ENV{'DBI_PROFILE'};
649             $countsth->execute($clusterid, $clusterid) or croak("Could not execute >".$countsth->{Statement}."<: ".$countsth->errstr);
650             }
651             else {
652             ($countsth, $countexpl) = $self->stmtHdl(<<"XxX");
653             SELECT COUNT(DISTINCT seqno) FROM beacons WHERE hash=?;
654             XxX
655             $self->stmtExplain($countexpl, $hash) if $ENV{'DBI_PROFILE'};
656             $countsth->execute($hash) or croak("Could not execute >".$countsth->{Statement}."<: ".$countsth->errstr);
657             };
658             my $hitsref = $countsth->fetchrow_arrayref;
659             my $hits = $hitsref->[0] || 0;
660              
661             my ($osd, $beaconmeta) = $self->get_meta;
662             my $prefix = $beaconmeta->{'PREFIX'} || "";
663             (my $servicename = $beaconmeta->{'NAME'} || $osd->{'ShortName'} || "") =~ s/([<>&"]|[^\x00-\x7f])/'&#'.ord($1).';'/ge;
664            
665             my $target = $cgi->url(-path=>1);
666              
667             my @result;
668             push(@result, $cgi->start_html(
669             -encoding => "UTF-8",
670             -title => "$servicename referring ".$query->as_string(),
671             -meta => {'robots'=>'noindex'},
672             ($extra->{'css'} ? (-style => {'src'=>$extra->{'css'}}) : ()),
673             -head => [$cgi->Link({-rel=>'unapi-server',
674             -type=>'application/xml',
675             title=>'unAPI',
676             -href=>$target}),
677             $cgi->Link({-rel=>'start',
678             -href=>$target}),
679             ],
680             ));
681              
682             push(@result, '');
683             push(@result, '');
684              
685             push(@result, $cgi->h1("$hits References for ".$cgi->abbr({class=>"unapi-id", title=>"$canon"}, $query)));
686              
687             push(@result, '
');
688             push(@result, $cgi->p($cgi->span("Identifier:"), $cgi->a({href=>"$prefix$pretty"}, "$prefix$pretty"))) if $prefix;
689             # delete $idlist{$pretty} if $prefix;
690             push(@result, $cgi->p($cgi->span("Variant Identifiers:"), map {$cgi->span({class=>($idlist{$_} || "variantid")}, $_)} sort keys %idlist)) if %idlist;
691             push(@result, '');
692              
693             my ($srcsth, $srcexpl);
694             if ( $clusterid ) {
695             ($srcsth, $srcexpl) = $self->stmtHdl(<<"XxX");
696             SELECT beacons.*, repos.*
697             FROM beacons NATURAL LEFT JOIN repos
698             WHERE ( (beacons.hash=?)
699             OR (beacons.hash IN (SELECT beacons.hash FROM cluster.beacons WHERE cluster.beacons.altid=?)) )
700             ORDER BY repos.sort, repos.alias;
701             XxX
702             $self->stmtExplain($srcexpl, $clusterid, $clusterid) if $ENV{'DBI_PROFILE'};
703             $srcsth->execute($clusterid, $clusterid) or croak("Could not execute >".$srcsth->{Statement}."<: ".$srcsth->errstr);
704             }
705             else {
706             ($srcsth, $srcexpl) = $self->stmtHdl(<<"XxX");
707             SELECT beacons.*, repos.*
708             FROM beacons NATURAL LEFT JOIN repos
709             WHERE beacons.hash=?
710             ORDER BY repos.sort, repos.alias;
711             XxX
712             $self->stmtExplain($srcexpl, $hash) if $ENV{'DBI_PROFILE'};
713             $srcsth->execute($hash) or croak("Could not execute >".$srcsth->{Statement}."<: ".$srcsth->errstr);
714             }
715              
716             my $rows = 0;
717             push(@result, '
');
718             my ($lastseq, @groups) = (0, ());
719             while ( my $onerow = $srcsth->fetchrow_hashref ) {
720             $rows ++;
721             if ( $lastseq and $onerow->{'seqno'} == $lastseq ) {
722             my %vary;
723             foreach my $key ( grep /^(hash|altid|hits|info|link)$/, keys %$onerow ) {
724             my $pval = $onerow->{$key};
725             next unless defined $pval;
726             $pval =~ s/([<>&"]|[^\x00-\x7f])/'&#'.ord($1).';'/ge if $key eq "link";
727             $vary{$key} = $pval;
728             }
729             push(@{$groups[$#groups]}, \%vary);
730             }
731             else {
732             my (%vary, %repos, %meta);
733             while ( my($key, $val) = each %$onerow ) {
734             my $pval = $val;
735             unless ( $key =~ /altid|feed|target|uri|link/i ) {
736             $pval =~ s/([<>&"]|[^\x00-\x7f])/'&#'.ord($1).';'/ge if defined $pval};
737             if ( $key =~ /time|revisit/i ) {
738             next unless $val;
739             $pval = HTTP::Date::time2str($val);
740             };
741             if ( $key =~ /^bc(\w+)$/ ) {
742             $repos{$1} = $pval if $pval}
743             elsif ( $key =~ /^(hash|altid|hits|info|link)$/ ) {
744             $vary{$key} = $pval}
745             else {
746             $meta{"_$key"} = $pval if $pval}
747             };
748             push(@groups, [\%repos, \%meta, \%vary]);
749             };
750             $lastseq = $onerow->{'seqno'};
751             };
752             # Grouping done, now display...
753              
754             my %didalreadysee;
755             foreach my $groupref ( @groups ) {
756             my ($repos, $meta, @vary) = @$groupref;
757              
758             my $aos = $meta->{'_alias'} || $meta->{'_seqno'};
759              
760             my $multi = (scalar @vary > 1) ? "multi" : "single";
761             push(@result, qq!
!);
762             push(@result, $cgi->h3({class=>"aggregator", onClick=>"toggle('ag$aos')"}, "Administrative Details"));
763              
764             push(@result, $cgi->h3({class=>"beacon", onClick=>"toggle('bc$aos')"}, "Repository Details"));
765              
766             if ( $multi eq "single" ) {
767             push(@result, $cgi->h3({class=>"hit", onClick=>"toggle('ht$aos')"}, "Result Details"));
768              
769             my $vary = $vary[0];
770              
771             my $hits = $vary->{'hits'};
772             my $description = $hits;
773              
774             my $h = $vary->{'hash'};
775             my $variantid = ($h eq $hash) ? "" : "variantid";
776             my $p;
777             if ( $h eq $hash ) {
778             $p = $pretty}
779             elsif ( $clusterid && ref($c) ) {
780             $c->value("");
781             my $did = $c->hash($h) || $c->value($h) || $h;
782             $p = $c->can("pretty") ? $c->pretty() : $c->value();
783             };
784             $p = ($clusterid ? $h : $pretty) unless defined $p;
785              
786             my $uri = "???";
787             if ( $uri = $vary->{'link'} ) { # o.k.
788             }
789             elsif ( $repos->{'ALTTARGET'} && $vary->{'altid'} ) {
790             $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))}
791             elsif ( $repos->{'TARGET'} ) {
792             $uri = sprintf($repos->{'TARGET'}, $p)}
793             elsif ( $repos->{'ALTTARGET'} ) {
794             $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($p))};
795              
796             my $redundant = ($didalreadysee{$uri}++) ? "subsequent" : "";
797              
798             my $guri = "";
799             if ( $repos->{'IMGTARGET'} ) {
800             $guri = sprintf($repos->{'IMGTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))}
801              
802             my @labels = grep /\S/, $repos->{'NAME'} || "", $repos->{'DESCRIPTION'} || "", $repos->{'INSTITUTION'} || "";
803             my $rlabel;
804             if ( $hits == 1 ) {
805             $rlabel = $repos->{'ONEMESSAGE'} if $repos->{'ONEMESSAGE'}}
806             elsif ( $hits == 0 ) {
807             $rlabel = $repos->{'SOMEMESSAGE'} if $repos->{'SOMEMESSAGE'}};
808             unless ( $rlabel ) {
809             $rlabel = $repos->{'MESSAGE'} || shift @labels || "???"};
810             my $label = sprintf($rlabel, $hits);
811              
812             my $ttip = pop @labels || "";
813             $ttip =~ s/&#(\d+);/chr($1)/ge;
814              
815             push(@result, $cgi->a({style=>"float: right; clear: right;", href=>$uri}, $cgi->img({alt=>$vary->{'info'}||$label,src=>$guri}))) if $guri;
816              
817             push(@result, $cgi->h2({class=>"label $redundant $variantid ident_$p", id=>"head$aos"}, $cgi->a({href=>$uri, title=>$ttip}, $label)));
818              
819             push(@result, qq!
!);
820             push(@result, $cgi->span($vary->{'info'})) if $vary->{'info'};
821             push(@result, $cgi->span("($hits Treffer)")) if $hits && ($rlabel !~ /%s/);
822             push(@result, '');
823              
824             push(@result, qq!
825             push(@result, $cgi->p({class=>"ht_uri"}, $cgi->span("Target URL:"), $cgi->a({href=>$uri}, CGI::escapeHTML($uri))));
826             push(@result, $cgi->p({class=>"ht_guri"}, $cgi->span("Preview URL:"), $cgi->a({href=>$guri}, $guri))) if $guri;
827             push(@result, $cgi->p({class=>"ht_hits"}, $cgi->span("Hits:"), $hits)) if $hits;
828             push(@result, $cgi->p({class=>"ht_info"}, $cgi->span("Additional Info:"), $vary->{'info'})) if $vary->{'info'};
829             push(@result, $cgi->p({class=>"ht_idnote"}, $cgi->span("Variant Identifier:"), $p)) if $variantid;
830             push(@result, '');
831             }
832             else {
833             push(@result, $cgi->h3({class=>"hit", onClick=>"mtoggle('res$aos', 'hit')"}, "Result Details"));
834             my $hits = scalar @vary;
835              
836             my @labels = grep /\S/, $repos->{'NAME'} || "", $repos->{'DESCRIPTION'} || "", $repos->{'INSTITUTION'} || "";
837             my $rlabel = $repos->{'MESSAGE'} || shift @labels || "???";
838             my $ttip = pop @labels || "";
839             $ttip =~ s/&#(\d+);/chr($1)/ge;
840              
841             # my $rlabel = $repos->{'SOMEMESSAGE'} || $repos->{'MESSAGE'} || $repos->{'DESCRIPTION'} || $repos->{'NAME'} || $repos->{'INSTITUTION'} || "???";
842              
843             # my $ttip = $repos->{'MESSAGE'} ? $repos->{'DESCRIPTION'} || $repos->{'NAME'} || $repos->{'INSTITUTION'} || ""
844              
845             # : $repos->{'INSTITUTION'} || $repos->{'NAME'} || "";
846              
847             # $ttip = "" if $ttip eq $rlabel;
848             $ttip =~ s/&#(\d+);/chr($1)/ge;
849              
850             my $label = sprintf($rlabel, $hits);
851             push(@result, $cgi->h2({class=>"label", id=>"head$aos"}, $label));
852              
853             push(@result, qq!
!);
854             my $cnt = 0;
855             foreach my $vary ( @vary ) {
856             $cnt ++;
857              
858             my $h = $vary->{'hash'};
859             my $variantid = ($h eq $hash) ? "" : "variantid";
860             my $p;
861             if ( $h eq $hash ) {
862             $p = $pretty}
863             elsif ( $clusterid && ref($c) ) {
864             $c->value("");
865             my $did = $c->hash($h) || $c->value($h) || $h;
866             $p = $c->can("pretty") ? $c->pretty() : $c->value();
867             };
868             $p = ($clusterid ? $h : $pretty) unless defined $p;
869              
870             my $uri = "???";
871             if ( $uri = $vary->{'link'} ) { # o.k.
872             }
873             elsif ( $repos->{'ALTTARGET'} && $vary->{'altid'} ) {
874             $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))}
875             elsif ( $repos->{'TARGET'} ) {
876             $uri = sprintf($repos->{'TARGET'}, $p)}
877             elsif ( $repos->{'ALTTARGET'} ) {
878             $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($p))};
879              
880             my $redundant = ($didalreadysee{$uri}++) ? "subsequent" : "";
881              
882             my $guri = "";
883             if ( $repos->{'IMGTARGET'} ) {
884             $guri = sprintf($repos->{'IMGTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))}
885              
886             my $hits = $vary->{hits} if $vary->{hits} and $vary->{hits} != 1;
887              
888             push(@result, qq!
!);
889             push(@result, $cgi->div({style=>"float: right;"}, $cgi->a({href=>$uri}, $cgi->img({src=>$guri})))) if $guri;
890             push(@result, $cgi->a({href=>$uri}, $cgi->span($vary->{'info'} || "[$cnt.]")));
891             push(@result, $cgi->span("($hits Treffer)")) if $hits;
892             push(@result, '');
893              
894             push(@result, qq!
895             push(@result, $cgi->p({class=>"ht_uri"}, $cgi->span("Target URL:"), $cgi->a({href=>$uri}, $uri)));
896             push(@result, $cgi->p({class=>"ht_guri"}, $cgi->span("Preview URL:"), $cgi->a({href=>$guri}, $guri))) if $guri;
897             push(@result, $cgi->p({class=>"ht_hits"}, $cgi->span("Hits:"), $vary->{hits})) if $vary->{hits};
898             push(@result, $cgi->p({class=>"ht_info"}, $cgi->span("Additional Info:"), $vary->{'info'})) if $vary->{'info'};
899             push(@result, $cgi->p({class=>"ht_idnote"}, $cgi->span("Variant Identifier:"), $p)) if $variantid;
900            
901             push(@result, '
');
902             push(@result, '');
903             };
904             push(@result, qq!!);
905             }
906              
907             push(@result, qq!
908             foreach ( sort keys %$repos ) {
909             next if /(MESSAGE|TARGET)$/;
910             next unless $repos->{$_};
911             $repos->{$_} =~ s!([a-z]+://\S+)!$cgi->a({href=>"$1", target=>"_blank"}, "$1")!ge; # URL
912             $repos->{$_} =~ s!(?:\<\s*)?(\w[\w.-]*)\@((?:\w[\w-]*\.)+\w+)(?:\s*\>)?!<$1 (at) $2>!g; # Mail Addr
913             $repos->{$_} =~ s/\s*\|\s*/ | /g; # Examples
914             next if /^(FORMAT|PREFIX|REVISIT|VERSION)$/; # Postpone to "administrative Details"
915             push(@result, $cgi->p({class=>"bc_$_"}, $cgi->span("#$_:"), $repos->{$_}));
916             };
917             push(@result, $cgi->p({class=>"ag_mtime"}, $cgi->span("Modified:"), $meta->{'_mtime'})) if $meta->{'_mtime'};
918             push(@result, '');
919              
920             push(@result, qq!
921             foreach ( sort keys %$repos ) {
922             next unless /^(FORMAT|PREFIX|REVISIT|VERSION)$/;
923             next unless $repos->{$_};
924             push(@result, $cgi->p({class=>"bc_$_"}, $cgi->span("#$_:"), $repos->{$_}));
925             };
926             push(@result, $cgi->p({class=>"ag_ftime"}, $cgi->span("Loaded:"), $meta->{'_ftime'})) if $meta->{'_ftime'};
927             push(@result, $cgi->p({class=>"ag_fstat"}, $cgi->span("Load status:"), $meta->{'_fstat'})) if $meta->{'_fstat'};
928             push(@result, $cgi->p({class=>"ag_utime"}, $cgi->span("Update attempt:"), $meta->{'_utime'})) if $meta->{'_utime'};
929             push(@result, $cgi->p({class=>"ag_ustat"}, $cgi->span("Update status:"), $meta->{'_ustat'})) if $meta->{'_ustat'};
930             push(@result, $cgi->p({class=>"ag_counti"}, $cgi->span("Identifiers:"), $meta->{'_counti'})) if $meta->{'_counti'};
931             push(@result, $cgi->p({class=>"ag_countu"}, $cgi->span("Distinct Ids:"), $meta->{'_countu'})) if $meta->{'_countu'};
932             push(@result, $cgi->p({class=>"ag_sort"}, $cgi->span("Sort key:"), $meta->{'_sort'})) if $meta->{'_sort'};
933             push(@result, $cgi->p({class=>"ag_admin"}, $cgi->span("Remark:"), $meta->{'_admin'})) if $meta->{'_admin'};
934             push(@result, '');
935              
936             push(@result, '
');
937              
938             push(@result, '');
939             };
940             push(@result, '');
941              
942             push(@result, '
');
943             # $cgi->span("provided by:"),
944             push(@result, $cgi->p({class=>"mt_NAME"}, $cgi->a({href=>$target}, $servicename)));
945             # $cgi->span("Service description:"),
946             (my $descr = $beaconmeta->{'DESCRIPTION'} || $osd->{'Description'} || "") =~ s/([<>&"]|[^\x00-\x7f])/'&#'.ord($1).';'/ge;
947             push(@result, $cgi->p({class=>"mt_DESCRIPTION"}, $descr));
948             push(@result, '');
949              
950             my($tu, $ts, $tcu, $tcs) = times();
951             push(@result, sprintf("", $tu, $ts, $tu+$ts), $cgi->end_html());
952             return join("\n", @result);
953             }
954              
955              
956             =head2 get_meta ()
957              
958             Returns a pair of hash references:
959              
960             =over 8
961              
962             =item 1
963              
964             OSD fields
965              
966             =item 2
967              
968             Beacon header fields
969              
970             =back
971              
972             =cut
973              
974             sub get_meta {
975             my ($self) = @_;
976              
977             my ($metasth, $metaexpl) = $self->stmtHdl(<<"XxX");
978             SELECT key, val FROM osd;
979             XxX
980             $self->stmtExplain($metaexpl) if $ENV{'DBI_PROFILE'};
981             $metasth->execute() or croak("Could not execute >".$metasth->{Statement}."<: ".$metasth->errstr);
982             my (%osd, %beaconmeta);
983             while ( my $aryref = $metasth->fetchrow_arrayref ) {
984             my ($key, $val) = @$aryref;
985             next unless $val;
986             if ($key =~ s/^bc// ) { # BeaconMeta Fields
987             $beaconmeta{$key} = $val}
988             elsif ( exists $osd{$key} ) {
989             if ( ref($osd{$key}) ) {
990             push(@{$osd{$key}}, $val)}
991             else {
992             $osd{$key} = [$osd{$key}, $val]};
993             }
994             else {
995             $osd{$key} = $val};
996             };
997             return (\%osd, \%beaconmeta);
998             }
999              
1000             =head1 AUTHOR
1001              
1002             Thomas Berger
1003             CPAN ID: THB
1004             gymel.com
1005             THB@cpan.org
1006              
1007             =head1 COPYRIGHT
1008              
1009             This program is free software; you can redistribute
1010             it and/or modify it under the same terms as Perl itself.
1011              
1012             The full text of the license can be found in the
1013             LICENSE file included with this module.
1014              
1015              
1016             =cut
1017              
1018             1;
1019             # The preceding line will help the module return a true value
1020