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