]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
creating storage session on demand
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / SuperCat.pm
1 package OpenILS::Application::SuperCat;
2
3 use strict;
4 use warnings;
5
6 # All OpenSRF applications must be based on OpenSRF::Application or
7 # a subclass thereof.  Makes sense, eh?
8 use OpenSRF::Application;
9 use base qw/OpenSRF::Application/;
10
11 # This is the client class, used for connecting to open-ils.storage
12 use OpenSRF::AppSession;
13
14 # This is an extention of Error.pm that supplies some error types to throw
15 use OpenSRF::EX qw(:try);
16
17 # This is a helper class for querying the OpenSRF Settings application ...
18 use OpenSRF::Utils::SettingsClient;
19
20 # ... and here we have the built in logging helper ...
21 use OpenSRF::Utils::Logger qw($logger);
22
23 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
24 use OpenILS::Utils::Fieldmapper;
25
26
27 # We'll be working with XML, so...
28 use XML::LibXML;
29 use XML::LibXSLT;
30 use Unicode::Normalize;
31
32 use JSON;
33
34 our (
35   $_parser,
36   $_xslt,
37   %record_xslt,
38   %metarecord_xslt,
39 );
40
41 sub child_init {
42         # we need an XML parser
43         $_parser = new XML::LibXML;
44
45         $logger->debug("Got here!");
46
47         # and an xslt parser
48         $_xslt = new XML::LibXSLT;
49         
50         # parse the MODS xslt ...
51         my $mods3_xslt = $_parser->parse_file(
52                 OpenSRF::Utils::SettingsClient
53                         ->new
54                         ->config_value( dirs => 'xsl' ).
55                 "/MARC21slim2MODS3.xsl"
56         );
57         # and stash a transformer
58         $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
59         $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
60         $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
61         $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
62
63         # parse the MODS xslt ...
64         my $mods_xslt = $_parser->parse_file(
65                 OpenSRF::Utils::SettingsClient
66                         ->new
67                         ->config_value( dirs => 'xsl' ).
68                 "/MARC21slim2MODS.xsl"
69         );
70         # and stash a transformer
71         $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
72         $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
73         $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
74         $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
75
76         $logger->debug("Got here!");
77
78         # parse the ATOM entry xslt ...
79         my $atom_xslt = $_parser->parse_file(
80                 OpenSRF::Utils::SettingsClient
81                         ->new
82                         ->config_value( dirs => 'xsl' ).
83                 "/MARC21slim2ATOM.xsl"
84         );
85         # and stash a transformer
86         $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
87         $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
88         $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
89
90         # parse the RDFDC xslt ...
91         my $rdf_dc_xslt = $_parser->parse_file(
92                 OpenSRF::Utils::SettingsClient
93                         ->new
94                         ->config_value( dirs => 'xsl' ).
95                 "/MARC21slim2RDFDC.xsl"
96         );
97         # and stash a transformer
98         $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
99         $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
100         $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
101
102         $logger->debug("Got here!");
103
104         # parse the SRWDC xslt ...
105         my $srw_dc_xslt = $_parser->parse_file(
106                 OpenSRF::Utils::SettingsClient
107                         ->new
108                         ->config_value( dirs => 'xsl' ).
109                 "/MARC21slim2SRWDC.xsl"
110         );
111         # and stash a transformer
112         $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
113         $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
114         $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
115
116         $logger->debug("Got here!");
117
118         # parse the OAIDC xslt ...
119         my $oai_dc_xslt = $_parser->parse_file(
120                 OpenSRF::Utils::SettingsClient
121                         ->new
122                         ->config_value( dirs => 'xsl' ).
123                 "/MARC21slim2OAIDC.xsl"
124         );
125         # and stash a transformer
126         $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
127         $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
128         $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
129
130         $logger->debug("Got here!");
131
132         # parse the RSS xslt ...
133         my $rss_xslt = $_parser->parse_file(
134                 OpenSRF::Utils::SettingsClient
135                         ->new
136                         ->config_value( dirs => 'xsl' ).
137                 "/MARC21slim2RSS2.xsl"
138         );
139         # and stash a transformer
140         $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
141
142         $logger->debug("Got here!");
143
144         # and finally, a storage server session
145
146         register_record_transforms();
147
148         return 1;
149 }
150
151 sub register_record_transforms {
152         for my $type ( keys %record_xslt ) {
153                 __PACKAGE__->register_method(
154                         method    => 'retrieve_record_transform',
155                         api_name  => "open-ils.supercat.record.$type.retrieve",
156                         api_level => 1,
157                         argc      => 1,
158                         signature =>
159                                 { desc     => <<"                                 DESC",
160 Returns the \U$type\E representation of the requested bibliographic record
161                                   DESC
162                                   params   =>
163                                         [
164                                                 { name => 'bibId',
165                                                   desc => 'An OpenILS biblio::record_entry id',
166                                                   type => 'number' },
167                                         ],
168                                 'return' =>
169                                         { desc => "The bib record in \U$type\E",
170                                           type => 'string' }
171                                 }
172                 );
173         }
174 }
175
176
177 sub entityize {
178         my $stuff = NFC(shift());
179         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
180         return $stuff;
181 }
182
183
184 sub retrieve_record_marcxml {
185         my $self = shift;
186         my $client = shift;
187         my $rid = shift;
188
189         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
190
191         return
192         entityize(
193                 $_storage
194                         ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
195                         ->gather(1)
196                         ->marc
197         );
198 }
199
200 __PACKAGE__->register_method(
201         method    => 'retrieve_record_marcxml',
202         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
203         api_level => 1,
204         argc      => 1,
205         signature =>
206                 { desc     => <<"                 DESC",
207 Returns the MARCXML representation of the requested bibliographic record
208                   DESC
209                   params   =>
210                         [
211                                 { name => 'bibId',
212                                   desc => 'An OpenILS biblio::record_entry id',
213                                   type => 'number' },
214                         ],
215                   'return' =>
216                         { desc => 'The bib record in MARCXML',
217                           type => 'string' }
218                 }
219 );
220
221 sub retrieve_record_transform {
222         my $self = shift;
223         my $client = shift;
224         my $rid = shift;
225
226         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
227
228         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
229
230         warn "Fetching record entry $rid\n";
231         my $marc = $_storage->request(
232                 'open-ils.storage.direct.biblio.record_entry.retrieve',
233                 $rid
234         )->gather(1)->marc;
235         warn "Fetched record entry $rid\n";
236
237         return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $marc ) )->toString);
238 }
239
240
241 sub retrieve_metarecord_mods {
242         my $self = shift;
243         my $client = shift;
244         my $rid = shift;
245
246         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
247
248         # We want a session
249         $_storage->connect;
250
251         # Get the metarecord in question
252         my $mr =
253         $_storage->request(
254                 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
255         )->gather(1);
256
257         # Now get the map of all bib records for the metarecord
258         my $recs =
259         $_storage->request(
260                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
261                 $rid
262         )->gather(1);
263
264         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
265
266         # and retrieve the lead (master) record as MODS
267         my ($master) =
268                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
269                         ->run($mr->master_record);
270         my $master_mods = $_parser->parse_string($master)->documentElement;
271         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
272
273         # ... and a MODS clone to populate, with guts removed.
274         my $mods = $_parser->parse_string($master)->documentElement;
275         $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
276         ($mods) = $mods->findnodes('//mods:mods');
277         $mods->removeChildNodes;
278
279         # Add the metarecord ID as a (locally defined) info URI
280         my $recordInfo = $mods
281                 ->ownerDocument
282                 ->createElement("mods:recordInfo");
283
284         my $recordIdentifier = $mods
285                 ->ownerDocument
286                 ->createElement("mods:recordIdentifier");
287
288         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
289         $year += 1900;
290         $month += 1;
291
292         my $id = $mr->id;
293         $recordIdentifier->appendTextNode(
294                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
295         );
296
297         $recordInfo->appendChild($recordIdentifier);
298         $mods->appendChild($recordInfo);
299
300         # Grab the title, author and ISBN for the master record and populate the metarecord
301         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
302         
303         if ($title) {
304                 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
305                 $title = $mods->ownerDocument->importNode($title);
306                 $mods->appendChild($title);
307         }
308
309         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
310         if ($author) {
311                 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
312                 $author = $mods->ownerDocument->importNode($author);
313                 $mods->appendChild($author);
314         }
315
316         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
317         if ($isbn) {
318                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
319                 $isbn = $mods->ownerDocument->importNode($isbn);
320                 $mods->appendChild($isbn);
321         }
322
323         # ... and loop over the constituent records
324         for my $map ( @$recs ) {
325
326                 # get the MODS
327                 my ($rec) =
328                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
329                                 ->run($map->source);
330
331                 my $part_mods = $_parser->parse_string($rec);
332                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
333                 ($part_mods) = $part_mods->findnodes('//mods:mods');
334
335                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
336                         $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
337                         $node = $mods->ownerDocument->importNode($node);
338                         $mods->appendChild( $node );
339                 }
340
341                 my $relatedItem = $mods
342                         ->ownerDocument
343                         ->createElement("mods:relatedItem");
344
345                 $relatedItem->setAttribute( type => 'constituent' );
346
347                 my $identifier = $mods
348                         ->ownerDocument
349                         ->createElement("mods:identifier");
350
351                 $identifier->setAttribute( type => 'uri' );
352
353                 my $subRecordInfo = $mods
354                         ->ownerDocument
355                         ->createElement("mods:recordInfo");
356
357                 my $subRecordIdentifier = $mods
358                         ->ownerDocument
359                         ->createElement("mods:recordIdentifier");
360
361                 my $subid = $map->source;
362                 $subRecordIdentifier->appendTextNode(
363                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
364                                 $month,
365                                 $day
366                         )
367                 );
368                 $subRecordInfo->appendChild($subRecordIdentifier);
369
370                 $relatedItem->appendChild( $subRecordInfo );
371
372                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
373                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
374                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
375                 $relatedItem->appendChild($tor) if ($tor);
376
377                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
378                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
379                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
380                         $relatedItem->appendChild( $part_isbn );
381
382                         if (!$isbn) {
383                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
384                         }
385                 }
386
387                 $mods->appendChild( $relatedItem );
388
389         }
390
391         $_storage->disconnect;
392
393         return entityize($mods->toString);
394
395 }
396 __PACKAGE__->register_method(
397         method    => 'retrieve_metarecord_mods',
398         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
399         api_level => 1,
400         argc      => 1,
401         signature =>
402                 { desc     => <<"                 DESC",
403 Returns the MODS representation of the requested metarecord
404                   DESC
405                   params   =>
406                         [
407                                 { name => 'metarecordId',
408                                   desc => 'An OpenILS metabib::metarecord id',
409                                   type => 'number' },
410                         ],
411                   'return' =>
412                         { desc => 'The metarecord in MODS',
413                           type => 'string' }
414                 }
415 );
416
417 sub list_metarecord_formats {
418         my @list = (
419                 { mods =>
420                         { namespace_uri   => 'http://www.loc.gov/mods/',
421                           docs            => 'http://www.loc.gov/mods/',
422                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
423                         }
424                 }
425         );
426
427         for my $type ( keys %metarecord_xslt ) {
428                 push @list,
429                         { $type => 
430                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
431                                   docs            => $metarecord_xslt{$type}{docs},
432                                   schema_location => $metarecord_xslt{$type}{schema_location},
433                                 }
434                         };
435         }
436
437         return \@list;
438 }
439 __PACKAGE__->register_method(
440         method    => 'list_metarecord_formats',
441         api_name  => 'open-ils.supercat.metarecord.formats',
442         api_level => 1,
443         argc      => 0,
444         signature =>
445                 { desc     => <<"                 DESC",
446 Returns the list of valid metarecord formats that supercat understands.
447                   DESC
448                   'return' =>
449                         { desc => 'The format list',
450                           type => 'array' }
451                 }
452 );
453
454
455 sub list_record_formats {
456         my @list = (
457                 { marcxml =>
458                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
459                           docs            => 'http://www.loc.gov/marcxml/',
460                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
461                         }
462                 }
463         );
464
465         for my $type ( keys %record_xslt ) {
466                 push @list,
467                         { $type => 
468                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
469                                   docs            => $record_xslt{$type}{docs},
470                                   schema_location => $record_xslt{$type}{schema_location},
471                                 }
472                         };
473         }
474
475         return \@list;
476 }
477 __PACKAGE__->register_method(
478         method    => 'list_record_formats',
479         api_name  => 'open-ils.supercat.record.formats',
480         api_level => 1,
481         argc      => 0,
482         signature =>
483                 { desc     => <<"                 DESC",
484 Returns the list of valid record formats that supercat understands.
485                   DESC
486                   'return' =>
487                         { desc => 'The format list',
488                           type => 'array' }
489                 }
490 );
491
492
493 sub oISBN {
494         my $self = shift;
495         my $client = shift;
496         my $isbn = shift;
497
498         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
499                 unless (length($isbn) >= 10);
500
501         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
502
503         # Create a storage session, since we'll be making muliple requests.
504         $_storage->connect;
505
506         # Find the record that has that ISBN.
507         my $bibrec = $_storage->request(
508                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
509                 { tag => '020', subfield => 'a', value => { ilike => $isbn.'%'} }
510         )->gather(1);
511
512         # Go away if we don't have one.
513         return {} unless (@$bibrec);
514
515         # Find the metarecord for that bib record.
516         my $mr = $_storage->request(
517                 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
518                 $bibrec->[0]->record
519         )->gather(1);
520
521         # Find the other records for that metarecord.
522         my $records = $_storage->request(
523                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
524                 $mr->[0]->metarecord
525         )->gather(1);
526
527         # Just to be safe.  There's currently no unique constraint on sources...
528         my %unique_recs = map { ($_->source, 1) } @$records;
529         my @rec_list = sort keys %unique_recs;
530
531         # And now fetch the ISBNs for thos records.
532         my $recs = $_storage->request(
533                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
534                 { tag => '020', subfield => 'a', record => \@rec_list }
535         )->gather(1);
536
537         # We're done with the storage server session.
538         $_storage->disconnect;
539
540         # Return the oISBN data structure.  This will be XMLized at a higher layer.
541         return
542                 { metarecord => $mr->[0]->metarecord,
543                   record_list => { map { ($_->record, $_->value) } @$recs } };
544
545 }
546 __PACKAGE__->register_method(
547         method    => 'oISBN',
548         api_name  => 'open-ils.supercat.oisbn',
549         api_level => 1,
550         argc      => 1,
551         signature =>
552                 { desc     => <<"                 DESC",
553 Returns the ISBN list for the metarecord of the requested isbn
554                   DESC
555                   params   =>
556                         [
557                                 { name => 'isbn',
558                                   desc => 'An ISBN.  Duh.',
559                                   type => 'string' },
560                         ],
561                   'return' =>
562                         { desc => 'record to isbn map',
563                           type => 'object' }
564                 }
565 );
566
567 1;