d4ae0ba11c7b4691e8ba02ad747a0f48d2ab4a6e
[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   $_storage,
38   %record_xslt,
39   %metarecord_xslt,
40 );
41
42 sub child_init {
43         # we need an XML parser
44         $_parser = new XML::LibXML;
45
46         # and an xslt parser
47         $_xslt = new XML::LibXSLT;
48         
49         # parse the MODS xslt ...
50         my $mods_xslt = $_parser->parse_file(
51                 OpenSRF::Utils::SettingsClient
52                         ->new
53                         ->config_value( dirs => 'xsl' ).
54                 "/MARC21slim2MODS.xsl"
55         );
56         # and stash a transformer
57         $record_xslt{mods} = $_xslt->parse_stylesheet( $mods_xslt );
58
59
60         # parse the RDFDC xslt ...
61         my $rdfdc_xslt = $_parser->parse_file(
62                 OpenSRF::Utils::SettingsClient
63                         ->new
64                         ->config_value( dirs => 'xsl' ).
65                 "/MARC21slim2RDFDC.xsl"
66         );
67         # and stash a transformer
68         $record_xslt{rdfdc} = $_xslt->parse_stylesheet( $rdfdc_xslt );
69
70
71         # parse the SRWDC xslt ...
72         my $srwdc_xslt = $_parser->parse_file(
73                 OpenSRF::Utils::SettingsClient
74                         ->new
75                         ->config_value( dirs => 'xsl' ).
76                 "/MARC21slim2SRWDC.xsl"
77         );
78         # and stash a transformer
79         $record_xslt{srwdc} = $_xslt->parse_stylesheet( $srwdc_xslt );
80
81
82         # parse the OAIDC xslt ...
83         my $oaidc_xslt = $_parser->parse_file(
84                 OpenSRF::Utils::SettingsClient
85                         ->new
86                         ->config_value( dirs => 'xsl' ).
87                 "/MARC21slim2OAIDC.xsl"
88         );
89         # and stash a transformer
90         $record_xslt{oaidc} = $_xslt->parse_stylesheet( $oaidc_xslt );
91
92
93         # parse the RSS xslt ...
94         my $rss_xslt = $_parser->parse_file(
95                 OpenSRF::Utils::SettingsClient
96                         ->new
97                         ->config_value( dirs => 'xsl' ).
98                 "/MARC21slim2RSS2.xsl"
99         );
100         # and stash a transformer
101         $record_xslt{rss2} = $_xslt->parse_stylesheet( $rss_xslt );
102
103
104         # and finally, a storage server session
105         $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
106
107         register_record_transforms();
108
109         return 1;
110 }
111
112 sub register_record_transforms {
113         for my $type ( keys %record_xslt ) {
114                 __PACKAGE__->register_method(
115                         method    => 'retrieve_record_transform',
116                         api_name  => "open-ils.supercat.record.$type.retrieve",
117                         api_level => 1,
118                         argc      => 1,
119                         signature =>
120                                 { desc     => <<"                                 DESC",
121 Returns the \U$type\E representation of the requested bibliographic record
122                                   DESC
123                                   params   =>
124                                         [
125                                                 { name => 'bibId',
126                                                   desc => 'An OpenILS biblio::record_entry id',
127                                                   type => 'number' },
128                                         ],
129                                 'return' =>
130                                         { desc => "The bib record in \U$type\E",
131                                           type => 'string' }
132                                 }
133                 );
134         }
135 }
136
137
138 sub entityize {
139         my $stuff = NFC(shift());
140         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
141         return $stuff;
142 }
143
144
145 sub retrieve_record_marcxml {
146         my $self = shift;
147         my $client = shift;
148         my $rid = shift;
149
150         return
151         entityize(
152                 $_storage
153                         ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
154                         ->gather(1)
155                         ->marc
156         );
157 }
158
159 __PACKAGE__->register_method(
160         method    => 'retrieve_record_marcxml',
161         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
162         api_level => 1,
163         argc      => 1,
164         signature =>
165                 { desc     => <<"                 DESC",
166 Returns the MARCXML representation of the requested bibliographic record
167                   DESC
168                   params   =>
169                         [
170                                 { name => 'bibId',
171                                   desc => 'An OpenILS biblio::record_entry id',
172                                   type => 'number' },
173                         ],
174                   'return' =>
175                         { desc => 'The bib record in MARCXML',
176                           type => 'string' }
177                 }
178 );
179
180 sub retrieve_record_transform {
181         my $self = shift;
182         my $client = shift;
183         my $rid = shift;
184
185         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
186
187         my $marc = $_storage->request(
188                 'open-ils.storage.direct.biblio.record_entry.retrieve',
189                 $rid
190         )->gather(1)->marc;
191
192         return entityize($record_xslt{$transform}->transform( $_parser->parse_string( $marc ) )->toString);
193 }
194
195
196 sub retrieve_metarecord_mods {
197         my $self = shift;
198         my $client = shift;
199         my $rid = shift;
200
201         # We want a session
202         $_storage->connect;
203
204         # Get the metarecord in question
205         my $mr =
206         $_storage->request(
207                 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
208         )->gather(1);
209
210         # Now get the map of all bib records for the metarecord
211         my $recs =
212         $_storage->request(
213                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
214                 $rid
215         )->gather(1);
216
217         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
218
219         # and retrieve the lead (master) record as MODS
220         my ($master) =
221                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
222                         ->run($mr->master_record);
223         my $master_mods = $_parser->parse_string($master)->documentElement;
224         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
225
226         # ... and a MODS clone to populate, with guts removed.
227         my $mods = $_parser->parse_string($master)->documentElement;
228         $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
229         ($mods) = $mods->findnodes('//mods:mods');
230         $mods->removeChildNodes;
231
232         # Add the metarecord ID as a (locally defined) info URI
233         my $recordInfo = $mods
234                 ->ownerDocument
235                 ->createElement("mods:recordInfo");
236
237         my $recordIdentifier = $mods
238                 ->ownerDocument
239                 ->createElement("mods:recordIdentifier");
240
241         $recordIdentifier->setAttribute( source => 'oils:/metabib-metarecord/' );
242
243         my $id = $mr->id;
244         $recordIdentifier->appendTextNode( $id );
245
246         $recordInfo->appendChild($recordIdentifier);
247         $mods->appendChild($recordInfo);
248
249         # Grab the title, author and ISBN for the master record and populate the metarecord
250         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
251         
252         if ($title) {
253                 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
254                 $title = $mods->ownerDocument->importNode($title);
255                 $mods->appendChild($title);
256         }
257
258         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
259         if ($author) {
260                 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
261                 $author = $mods->ownerDocument->importNode($author);
262                 $mods->appendChild($author);
263         }
264
265         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
266         if ($isbn) {
267                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
268                 $isbn = $mods->ownerDocument->importNode($isbn);
269                 $mods->appendChild($isbn);
270         }
271
272         # ... and loop over the constituent records
273         for my $map ( @$recs ) {
274
275                 # get the MODS
276                 my ($rec) =
277                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
278                                 ->run($map->source);
279
280                 my $part_mods = $_parser->parse_string($rec);
281                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
282                 ($part_mods) = $part_mods->findnodes('//mods:mods');
283
284                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
285                         $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
286                         $node = $mods->ownerDocument->importNode($node);
287                         $mods->appendChild( $node );
288                 }
289
290                 my $relatedItem = $mods
291                         ->ownerDocument
292                         ->createElement("mods:relatedItem");
293
294                 $relatedItem->setAttribute( type => 'constituent' );
295
296                 my $identifier = $mods
297                         ->ownerDocument
298                         ->createElement("mods:identifier");
299
300                 $identifier->setAttribute( type => 'uri' );
301
302                 my $subRecordInfo = $mods
303                         ->ownerDocument
304                         ->createElement("mods:recordInfo");
305
306                 my $subRecordIdentifier = $mods
307                         ->ownerDocument
308                         ->createElement("mods:recordIdentifier");
309
310                 $subRecordIdentifier->setAttribute( source => 'oils:/biblio-record_entry/' );
311
312                 my $subid = $map->source;
313                 $subRecordIdentifier->appendTextNode( $subid );
314                 $subRecordInfo->appendChild($subRecordIdentifier);
315
316                 $relatedItem->appendChild( $subRecordInfo );
317
318                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
319                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
320                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
321                 $relatedItem->appendChild($tor) if ($tor);
322
323                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
324                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
325                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
326                         $relatedItem->appendChild( $part_isbn );
327
328                         if (!$isbn) {
329                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
330                         }
331                 }
332
333                 $mods->appendChild( $relatedItem );
334
335         }
336
337         $_storage->disconnect;
338
339         return entityize($mods->toString);
340
341 }
342 __PACKAGE__->register_method(
343         method    => 'retrieve_metarecord_mods',
344         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
345         api_level => 1,
346         argc      => 1,
347         signature =>
348                 { desc     => <<"                 DESC",
349 Returns the MODS representation of the requested metarecord
350                   DESC
351                   params   =>
352                         [
353                                 { name => 'metarecordId',
354                                   desc => 'An OpenILS metabib::metarecord id',
355                                   type => 'number' },
356                         ],
357                   'return' =>
358                         { desc => 'The metarecord in MODS',
359                           type => 'string' }
360                 }
361 );
362
363 sub list_metarecord_formats {
364         return ['mods', keys %metarecord_xslt];
365 }
366 __PACKAGE__->register_method(
367         method    => 'list_metarecord_formats',
368         api_name  => 'open-ils.supercat.metarecord.formats',
369         api_level => 1,
370         argc      => 0,
371         signature =>
372                 { desc     => <<"                 DESC",
373 Returns the list of valid metarecord formats that supercat understands.
374                   DESC
375                   'return' =>
376                         { desc => 'The format list',
377                           type => 'array' }
378                 }
379 );
380
381
382 sub list_record_formats {
383         return ['marcxml', keys %record_xslt];
384 }
385 __PACKAGE__->register_method(
386         method    => 'list_record_formats',
387         api_name  => 'open-ils.supercat.record.formats',
388         api_level => 1,
389         argc      => 0,
390         signature =>
391                 { desc     => <<"                 DESC",
392 Returns the list of valid record formats that supercat understands.
393                   DESC
394                   'return' =>
395                         { desc => 'The format list',
396                           type => 'array' }
397                 }
398 );
399
400
401 sub oISBN {
402         my $self = shift;
403         my $client = shift;
404         my $isbn = shift;
405
406         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
407                 unless (length($isbn) >= 10);
408
409         # Create a storage session, since we'll be making muliple requests.
410         $_storage->connect;
411
412         # Find the record that has that ISBN.
413         my $bibrec = $_storage->request(
414                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
415                 { tag => '020', subfield => 'a', value => { like => $isbn.'%'} }
416         )->gather(1);
417
418         # Go away if we don't have one.
419         return {} unless (@$bibrec);
420
421         # Find the metarecord for that bib record.
422         my $mr = $_storage->request(
423                 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
424                 $bibrec->[0]->record
425         )->gather(1);
426
427         # Find the other records for that metarecord.
428         my $records = $_storage->request(
429                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
430                 $mr->[0]->metarecord
431         )->gather(1);
432
433         # Just to be safe.  There's currently no unique constraint on sources...
434         my %unique_recs = map { ($_->source, 1) } @$records;
435         my @rec_list = sort keys %unique_recs;
436
437         # And now fetch the ISBNs for thos records.
438         my $recs = $_storage->request(
439                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
440                 { tag => '020', subfield => 'a', record => \@rec_list }
441         )->gather(1);
442
443         # We're done with the storage server session.
444         $_storage->disconnect;
445
446         # Return the oISBN data structure.  This will be XMLized at a higher layer.
447         return
448                 { metarecord => $mr->[0]->metarecord,
449                   record_list => { map { ($_->record, $_->value) } @$recs } };
450
451 }
452 __PACKAGE__->register_method(
453         method    => 'oISBN',
454         api_name  => 'open-ils.supercat.oisbn',
455         api_level => 1,
456         argc      => 1,
457         signature =>
458                 { desc     => <<"                 DESC",
459 Returns the ISBN list for the metarecord of the requested isbn
460                   DESC
461                   params   =>
462                         [
463                                 { name => 'isbn',
464                                   desc => 'An ISBN.  Duh.',
465                                   type => 'string' },
466                         ],
467                   'return' =>
468                         { desc => 'record to isbn map',
469                           type => 'object' }
470                 }
471 );
472
473 1;