]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
132858e9179c61797fd4cc7edd40c9b51a4d6bfe
[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   %holdings_data_cache,
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 $mods3_xslt = $_parser->parse_file(
51                 OpenSRF::Utils::SettingsClient
52                         ->new
53                         ->config_value( dirs => 'xsl' ).
54                 "/MARC21slim2MODS3.xsl"
55         );
56         # and stash a transformer
57         $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
58         $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
59         $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
60         $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
61
62         # parse the MODS xslt ...
63         my $mods_xslt = $_parser->parse_file(
64                 OpenSRF::Utils::SettingsClient
65                         ->new
66                         ->config_value( dirs => 'xsl' ).
67                 "/MARC21slim2MODS.xsl"
68         );
69         # and stash a transformer
70         $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
71         $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
72         $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
73         $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
74
75         # parse the ATOM entry xslt ...
76         my $atom_xslt = $_parser->parse_file(
77                 OpenSRF::Utils::SettingsClient
78                         ->new
79                         ->config_value( dirs => 'xsl' ).
80                 "/MARC21slim2ATOM.xsl"
81         );
82         # and stash a transformer
83         $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
84         $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
85         $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
86
87         # parse the RDFDC xslt ...
88         my $rdf_dc_xslt = $_parser->parse_file(
89                 OpenSRF::Utils::SettingsClient
90                         ->new
91                         ->config_value( dirs => 'xsl' ).
92                 "/MARC21slim2RDFDC.xsl"
93         );
94         # and stash a transformer
95         $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
96         $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
97         $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
98
99         # parse the SRWDC xslt ...
100         my $srw_dc_xslt = $_parser->parse_file(
101                 OpenSRF::Utils::SettingsClient
102                         ->new
103                         ->config_value( dirs => 'xsl' ).
104                 "/MARC21slim2SRWDC.xsl"
105         );
106         # and stash a transformer
107         $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
108         $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
109         $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
110
111         # parse the OAIDC xslt ...
112         my $oai_dc_xslt = $_parser->parse_file(
113                 OpenSRF::Utils::SettingsClient
114                         ->new
115                         ->config_value( dirs => 'xsl' ).
116                 "/MARC21slim2OAIDC.xsl"
117         );
118         # and stash a transformer
119         $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
120         $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
121         $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
122
123         # parse the RSS xslt ...
124         my $rss_xslt = $_parser->parse_file(
125                 OpenSRF::Utils::SettingsClient
126                         ->new
127                         ->config_value( dirs => 'xsl' ).
128                 "/MARC21slim2RSS2.xsl"
129         );
130         # and stash a transformer
131         $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
132
133         register_record_transforms();
134
135         return 1;
136 }
137
138 sub register_record_transforms {
139         for my $type ( keys %record_xslt ) {
140                 __PACKAGE__->register_method(
141                         method    => 'retrieve_record_transform',
142                         api_name  => "open-ils.supercat.record.$type.retrieve",
143                         api_level => 1,
144                         argc      => 1,
145                         signature =>
146                                 { desc     => "Returns the \U$type\E representation ".
147                                               "of the requested bibliographic record",
148                                   params   =>
149                                         [
150                                                 { name => 'bibId',
151                                                   desc => 'An OpenILS biblio::record_entry id',
152                                                   type => 'number' },
153                                         ],
154                                 'return' =>
155                                         { desc => "The bib record in \U$type\E",
156                                           type => 'string' }
157                                 }
158                 );
159         }
160 }
161
162
163 sub entityize {
164         my $stuff = NFC(shift());
165         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
166         return $stuff;
167 }
168
169 sub new_record_holdings {
170         my $self = shift;
171         my $client = shift;
172         my $bib = shift;
173         my $ou = shift;
174
175         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
176
177         my $tree = $_storage->request(
178                 "open-ils.cstore.direct.biblio.record_entry.retrieve",
179                 $bib,
180                 {flesh => 3, flesh_fields => [qw/call_numbers copies location status owning_lib circ_lib/] }
181         )->gather(1);
182
183         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
184         $year += 1900;
185         $month += 1;
186
187         my $xml = "<hold:volumes xmlns:hold='http://open-ils.org/spec/holdings/v1'>";
188
189         for my $cn (@{$tree->call_numbers}) {
190
191                 if ($ou ne '-') {
192                         next unless grep {$_->circ_lib->shortname =~ /^$ou/} @{$cn->copies};
193                 }
194
195                 (my $cn_class = $cn->class_name) =~ s/::/-/gso;
196                 $cn_class =~ s/Fieldmapper-//gso;
197                 my $cn_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cn_class/".$cn->id, $month, $day);
198
199                 my $cn_lib = $cn->owning_lib->shortname;
200
201                 my $cn_label = $cn->label;
202
203                 $xml .= "<hold:volume id='$cn_tag' lib='$cn_lib' label='$cn_label'><hold:copies>";
204                 
205                 for my $cp (@{$cn->copies}) {
206
207                         if ($ou ne '-') {
208                                 next unless $cp->circ_lib->shortname =~ /^$ou/;
209                         }
210
211                         (my $cp_class = $cp->class_name) =~ s/::/-/gso;
212                         $cp_class =~ s/Fieldmapper-//gso;
213                         my $cp_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cp_class/".$cp->id, $month, $day);
214
215                         my $cp_stat = $cp->status->name;
216
217                         my $cp_loc = $cp->location->name;
218
219                         my $cp_lib = $cp->circ_lib->shortname;
220
221                         my $cp_bc = $cp->barcode;
222
223                         $xml .= "<hold:copy id='$cp_tag' barcode='$cp_bc'><hold:status>$cp_stat</hold:status><hold:location>$cp_loc</hold:location><hold:circlib>$cp_lib</hold:circlib><hold:notes>";
224
225                         #for my $note ( @{$_storage->request( "open-ils.cstore.direct.asset.copy_note.search.atomic" => {owning_copy => $cp->id, pub => "t" })->gather(1)} ) {
226                         #       $xml .= sprintf('<hold:note date="%s" title="%s">%s</hold:note>',$note->create_date, escape($note->title), escape($note->value));
227                         #}
228
229                         $xml .= "</hold:notes><hold:statcats>";
230
231                         #for my $sce ( @{$_storage->request( "open-ils.cstore.direct.asset.stat_cat_entry_copy_map.search.atomic" => { owning_copy => $cp->id })->gather(1)} ) {
232                         #       my $sc = $holdings_data_cache{statcat}{$sce->stat_cat_entry};
233                         #       $xml .= sprintf('<hold:statcat>%s</hold:statcat>',escape($sc->value));
234                         #}
235
236                         $xml .= "</hold:statcats></hold:copy>";
237                 }
238                 
239                 $xml .= "</hold:copies></hold:volume>";
240         }
241
242         $xml .= "</hold:volumes>";
243
244         return $xml;
245 }
246 __PACKAGE__->register_method(
247         method    => 'new_record_holdings',
248         api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
249         api_level => 1,
250         argc      => 1,
251         signature =>
252                 { desc     => <<"                 DESC",
253 Returns the XML representation of the requested bibliographic record's holdings
254                   DESC
255                   params   =>
256                         [
257                                 { name => 'bibId',
258                                   desc => 'An OpenILS biblio::record_entry id',
259                                   type => 'number' },
260                         ],
261                   'return' =>
262                         { desc => 'The bib record holdings hierarchy in XML',
263                           type => 'string' }
264                 }
265 );
266
267
268
269 sub record_holdings {
270         my $self = shift;
271         my $client = shift;
272         my $bib = shift;
273
274         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
275
276         if (!$holdings_data_cache{status}) {
277                 $holdings_data_cache{status} = {
278                         map { ($_->id => $_) } @{ $_storage->request( "open-ils.cstore.direct.config.copy_status.search.atomic", {id => {'<>' => undef}} )->gather(1) }
279                 };
280                 $holdings_data_cache{location} = {
281                         map { ($_->id => $_) } @{ $_storage->request( "open-ils.cstore.direct.asset.copy_location.retrieve.all.atomic", {id => {'<>' => undef}} )->gather(1) }
282                 };
283                 $holdings_data_cache{ou} =
284                 {
285                         map {
286                                 ($_->id => $_)
287                         } @{$_storage->request( "open-ils.cstore.direct.actor.org_unit.search.atomic" => { id => { '<>' => undef } } )->gather(1)}
288                 };
289                 $holdings_data_cache{statcat} =
290                 {
291                         map {
292                                 ($_->id => $_)
293                         } @{$_storage->request( "open-ils.cstore.direct.asset.stat_cat_entry.search.atomic" => { id => { '<>' => undef } } )->gather(1)}
294                 };
295         }
296
297
298         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
299         $year += 1900;
300         $month += 1;
301
302         my $xml = "<volumes xmlns='http://open-ils.org/spec/holdings/v1'>";
303         
304         for my $cn ( @{$_storage->request( "open-ils.cstore.direct.asset.call_number.search.atomic" => {record => $bib} )->gather(1)} ) {
305                 (my $cn_class = $cn->class_name) =~ s/::/-/gso;
306                 $cn_class =~ s/Fieldmapper-//gso;
307                 my $cn_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cn_class/".$cn->id, $month, $day);
308
309                 my $cn_lib = $holdings_data_cache{ou}{$cn->owning_lib}->shortname;
310
311                 my $cn_label = $cn->label;
312
313                 $xml .= "<volume id='$cn_tag' lib='$cn_lib' label='$cn_label'><copies>";
314                 
315                 for my $cp ( @{$_storage->request( "open-ils.cstore.direct.asset.copy.search.atomic" => {call_number => $cn->id} )->gather(1)} ) {
316                         (my $cp_class = $cn->class_name) =~ s/::/-/gso;
317                         $cp_class =~ s/Fieldmapper-//gso;
318                         my $cp_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cp_class/".$cp->id, $month, $day);
319
320                         my $cp_stat = $holdings_data_cache{status}{$cp->status}->name;
321
322                         my $cp_loc = $holdings_data_cache{location}{$cp->location}->name;
323
324                         my $cp_lib = $holdings_data_cache{ou}{$cp->circ_lib}->shortname;
325
326                         my $cp_bc = $cp->barcode;
327
328                         $xml .= "<copy id='$cp_tag' barcode='$cp_bc'><status>$cp_stat</status><location>$cp_loc</location><circlib>$cp_lib</circlib><notes>";
329
330                         for my $note ( @{$_storage->request( "open-ils.cstore.direct.asset.copy_note.search.atomic" => {id => $cp->id, pub => "t" })->gather(1)} ) {
331                                 $xml .= sprintf('<note date="%s" title="%s">%s</note>',$note->create_date, escape($note->title), escape($note->value));
332                         }
333
334                         $xml .= "</notes><statcats>";
335
336                         for my $sce ( @{$_storage->request( "open-ils.cstore.direct.asset.stat_cat_entry_copy_map.search.atomic" => { owning_copy => $cp->id })->gather(1)} ) {
337                                 my $sc = $holdings_data_cache{statcat}{$sce->stat_cat_entry};
338                                 $xml .= sprintf('<statcat>%s</statcat>',escape($sc->value));
339                         }
340
341                         $xml .= "</statcats></copy>";
342                 }
343                 
344                 $xml .= "</volume>";
345         }
346
347         $xml .= "</volumes>";
348
349         return $xml;
350 }
351
352 sub escape {
353         my $text = shift;
354         $text =~ s/&/&amp;/gsom;
355         $text =~ s/</&lt;/gsom;
356         $text =~ s/>/&gt;/gsom;
357         $text =~ s/"/\\"/gsom;
358         return $text;
359 }
360
361 sub recent_changes {
362         my $self = shift;
363         my $client = shift;
364         my $when = shift;
365         my $limit = shift;
366
367         if (!$when) {
368                 my ($d,$m,$y) = (localtime)[4,5,6];
369                 $when = sprintf('%4d-%02d-%02d', $y + 1900, $m + 1, $d);
370         }
371
372         my $type = 'biblio';
373         $type = 'authority' if ($self->api_name =~ /authority/o);
374
375         my $axis = 'create_date';
376         $axis = 'edit_date' if ($self->api_name =~ /edit/o);
377
378         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
379
380         return $_storage
381                 ->request(
382                         "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
383                         { $axis => { ">" => $when } },
384                         { order_by => "$axis desc", limit => $limit } )
385                 ->gather(1);
386 }
387
388 for my $t ( qw/biblio authority/ ) {
389         for my $a ( qw/import edit/ ) {
390
391                 __PACKAGE__->register_method(
392                         method    => 'recent_changes',
393                         api_name  => "open-ils.supercat.$t.record.$a.recent",
394                         api_level => 1,
395                         argc      => 0,
396                         signature =>
397                                 { desc     => "Returns a list of recently ${a}ed $t records",
398                                   params   =>
399                                         [
400                                                 { name => 'when',
401                                                   desc => "Date to start looking for ${a}ed records",
402                                                   default => 'today',
403                                                   type => 'string' },
404
405                                                 { name => 'limit',
406                                                   desc => "Maximum count to retrieve",
407                                                   type => 'number' },
408                                         ],
409                                   'return' =>
410                                         { desc => "An id list of $t records",
411                                           type => 'array' }
412                                 },
413                 );
414         }
415 }
416
417
418 sub retrieve_record_marcxml {
419         my $self = shift;
420         my $client = shift;
421         my $rid = shift;
422
423         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
424
425         return
426         entityize(
427                 $_storage
428                         ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )
429                         ->gather(1)
430                         ->marc
431         );
432 }
433
434 __PACKAGE__->register_method(
435         method    => 'retrieve_record_marcxml',
436         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
437         api_level => 1,
438         argc      => 1,
439         signature =>
440                 { desc     => <<"                 DESC",
441 Returns the MARCXML representation of the requested bibliographic record
442                   DESC
443                   params   =>
444                         [
445                                 { name => 'bibId',
446                                   desc => 'An OpenILS biblio::record_entry id',
447                                   type => 'number' },
448                         ],
449                   'return' =>
450                         { desc => 'The bib record in MARCXML',
451                           type => 'string' }
452                 }
453 );
454
455 sub retrieve_record_transform {
456         my $self = shift;
457         my $client = shift;
458         my $rid = shift;
459
460         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
461
462         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
463         $_storage->connect;
464
465         warn "Fetching record entry $rid\n";
466         my $marc = $_storage->request(
467                 'open-ils.cstore.direct.biblio.record_entry.retrieve',
468                 $rid
469         )->gather(1)->marc;
470         warn "Fetched record entry $rid\n";
471
472         $_storage->disconnect;
473
474         return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $marc ) )->toString);
475 }
476
477
478 sub retrieve_metarecord_mods {
479         my $self = shift;
480         my $client = shift;
481         my $rid = shift;
482
483         my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
484
485         # Get the metarecord in question
486         my $mr =
487         $_storage->request(
488                 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
489         )->gather(1);
490
491         # Now get the map of all bib records for the metarecord
492         my $recs =
493         $_storage->request(
494                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
495                 {metarecord => $rid}
496         )->gather(1);
497
498         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
499
500         # and retrieve the lead (master) record as MODS
501         my ($master) =
502                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
503                         ->run($mr->master_record);
504         my $master_mods = $_parser->parse_string($master)->documentElement;
505         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
506
507         # ... and a MODS clone to populate, with guts removed.
508         my $mods = $_parser->parse_string($master)->documentElement;
509         $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
510         ($mods) = $mods->findnodes('//mods:mods');
511         $mods->removeChildNodes;
512
513         # Add the metarecord ID as a (locally defined) info URI
514         my $recordInfo = $mods
515                 ->ownerDocument
516                 ->createElement("mods:recordInfo");
517
518         my $recordIdentifier = $mods
519                 ->ownerDocument
520                 ->createElement("mods:recordIdentifier");
521
522         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
523         $year += 1900;
524         $month += 1;
525
526         my $id = $mr->id;
527         $recordIdentifier->appendTextNode(
528                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
529         );
530
531         $recordInfo->appendChild($recordIdentifier);
532         $mods->appendChild($recordInfo);
533
534         # Grab the title, author and ISBN for the master record and populate the metarecord
535         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
536         
537         if ($title) {
538                 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
539                 $title = $mods->ownerDocument->importNode($title);
540                 $mods->appendChild($title);
541         }
542
543         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
544         if ($author) {
545                 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
546                 $author = $mods->ownerDocument->importNode($author);
547                 $mods->appendChild($author);
548         }
549
550         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
551         if ($isbn) {
552                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
553                 $isbn = $mods->ownerDocument->importNode($isbn);
554                 $mods->appendChild($isbn);
555         }
556
557         # ... and loop over the constituent records
558         for my $map ( @$recs ) {
559
560                 # get the MODS
561                 my ($rec) =
562                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
563                                 ->run($map->source);
564
565                 my $part_mods = $_parser->parse_string($rec);
566                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
567                 ($part_mods) = $part_mods->findnodes('//mods:mods');
568
569                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
570                         $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
571                         $node = $mods->ownerDocument->importNode($node);
572                         $mods->appendChild( $node );
573                 }
574
575                 my $relatedItem = $mods
576                         ->ownerDocument
577                         ->createElement("mods:relatedItem");
578
579                 $relatedItem->setAttribute( type => 'constituent' );
580
581                 my $identifier = $mods
582                         ->ownerDocument
583                         ->createElement("mods:identifier");
584
585                 $identifier->setAttribute( type => 'uri' );
586
587                 my $subRecordInfo = $mods
588                         ->ownerDocument
589                         ->createElement("mods:recordInfo");
590
591                 my $subRecordIdentifier = $mods
592                         ->ownerDocument
593                         ->createElement("mods:recordIdentifier");
594
595                 my $subid = $map->source;
596                 $subRecordIdentifier->appendTextNode(
597                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
598                                 $month,
599                                 $day
600                         )
601                 );
602                 $subRecordInfo->appendChild($subRecordIdentifier);
603
604                 $relatedItem->appendChild( $subRecordInfo );
605
606                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
607                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
608                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
609                 $relatedItem->appendChild($tor) if ($tor);
610
611                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
612                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
613                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
614                         $relatedItem->appendChild( $part_isbn );
615
616                         if (!$isbn) {
617                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
618                         }
619                 }
620
621                 $mods->appendChild( $relatedItem );
622
623         }
624
625         $_storage->disconnect;
626
627         return entityize($mods->toString);
628
629 }
630 __PACKAGE__->register_method(
631         method    => 'retrieve_metarecord_mods',
632         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
633         api_level => 1,
634         argc      => 1,
635         signature =>
636                 { desc     => <<"                 DESC",
637 Returns the MODS representation of the requested metarecord
638                   DESC
639                   params   =>
640                         [
641                                 { name => 'metarecordId',
642                                   desc => 'An OpenILS metabib::metarecord id',
643                                   type => 'number' },
644                         ],
645                   'return' =>
646                         { desc => 'The metarecord in MODS',
647                           type => 'string' }
648                 }
649 );
650
651 sub list_metarecord_formats {
652         my @list = (
653                 { mods =>
654                         { namespace_uri   => 'http://www.loc.gov/mods/',
655                           docs            => 'http://www.loc.gov/mods/',
656                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
657                         }
658                 }
659         );
660
661         for my $type ( keys %metarecord_xslt ) {
662                 push @list,
663                         { $type => 
664                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
665                                   docs            => $metarecord_xslt{$type}{docs},
666                                   schema_location => $metarecord_xslt{$type}{schema_location},
667                                 }
668                         };
669         }
670
671         return \@list;
672 }
673 __PACKAGE__->register_method(
674         method    => 'list_metarecord_formats',
675         api_name  => 'open-ils.supercat.metarecord.formats',
676         api_level => 1,
677         argc      => 0,
678         signature =>
679                 { desc     => <<"                 DESC",
680 Returns the list of valid metarecord formats that supercat understands.
681                   DESC
682                   'return' =>
683                         { desc => 'The format list',
684                           type => 'array' }
685                 }
686 );
687
688
689 sub list_record_formats {
690         my @list = (
691                 { marcxml =>
692                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
693                           docs            => 'http://www.loc.gov/marcxml/',
694                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
695                         }
696                 }
697         );
698
699         for my $type ( keys %record_xslt ) {
700                 push @list,
701                         { $type => 
702                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
703                                   docs            => $record_xslt{$type}{docs},
704                                   schema_location => $record_xslt{$type}{schema_location},
705                                 }
706                         };
707         }
708
709         return \@list;
710 }
711 __PACKAGE__->register_method(
712         method    => 'list_record_formats',
713         api_name  => 'open-ils.supercat.record.formats',
714         api_level => 1,
715         argc      => 0,
716         signature =>
717                 { desc     => <<"                 DESC",
718 Returns the list of valid record formats that supercat understands.
719                   DESC
720                   'return' =>
721                         { desc => 'The format list',
722                           type => 'array' }
723                 }
724 );
725
726
727 sub oISBN {
728         my $self = shift;
729         my $client = shift;
730         my $isbn = shift;
731
732         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
733                 unless (length($isbn) >= 10);
734
735         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
736
737         # Create a storage session, since we'll be making muliple requests.
738         $_storage->connect;
739
740         # Find the record that has that ISBN.
741         my $bibrec = $_storage->request(
742                 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
743                 { tag => '020', subfield => 'a', value => { ilike => $isbn.'%'} }
744         )->gather(1);
745
746         # Go away if we don't have one.
747         return {} unless (@$bibrec);
748
749         # Find the metarecord for that bib record.
750         my $mr = $_storage->request(
751                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
752                 {source => $bibrec->[0]->record}
753         )->gather(1);
754
755         # Find the other records for that metarecord.
756         my $records = $_storage->request(
757                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
758                 {metarecord => $mr->[0]->metarecord}
759         )->gather(1);
760
761         # Just to be safe.  There's currently no unique constraint on sources...
762         my %unique_recs = map { ($_->source, 1) } @$records;
763         my @rec_list = sort keys %unique_recs;
764
765         # And now fetch the ISBNs for thos records.
766         my $recs = $_storage->request(
767                 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
768                 { tag => '020', subfield => 'a', record => \@rec_list }
769         )->gather(1);
770
771         # We're done with the storage server session.
772         $_storage->disconnect;
773
774         # Return the oISBN data structure.  This will be XMLized at a higher layer.
775         return
776                 { metarecord => $mr->[0]->metarecord,
777                   record_list => { map { ($_->record, $_->value) } @$recs } };
778
779 }
780 __PACKAGE__->register_method(
781         method    => 'oISBN',
782         api_name  => 'open-ils.supercat.oisbn',
783         api_level => 1,
784         argc      => 1,
785         signature =>
786                 { desc     => <<"                 DESC",
787 Returns the ISBN list for the metarecord of the requested isbn
788                   DESC
789                   params   =>
790                         [
791                                 { name => 'isbn',
792                                   desc => 'An ISBN.  Duh.',
793                                   type => 'string' },
794                         ],
795                   'return' =>
796                         { desc => 'record to isbn map',
797                           type => 'object' }
798                 }
799 );
800
801 1;