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