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