]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
changed return object key names for consistency
[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 OpenILS::Application;
9 use base qw/OpenILS::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 OpenSRF::Utils::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 $mods32_xslt = $_parser->parse_file(
51                 OpenSRF::Utils::SettingsClient
52                         ->new
53                         ->config_value( dirs => 'xsl' ).
54                 "/MARC21slim2MODS32.xsl"
55         );
56         # and stash a transformer
57         $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
58         $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
59         $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
60         $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
61
62         # parse the MODS xslt ...
63         my $mods3_xslt = $_parser->parse_file(
64                 OpenSRF::Utils::SettingsClient
65                         ->new
66                         ->config_value( dirs => 'xsl' ).
67                 "/MARC21slim2MODS3.xsl"
68         );
69         # and stash a transformer
70         $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
71         $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
72         $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
73         $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
74
75         # parse the MODS xslt ...
76         my $mods_xslt = $_parser->parse_file(
77                 OpenSRF::Utils::SettingsClient
78                         ->new
79                         ->config_value( dirs => 'xsl' ).
80                 "/MARC21slim2MODS.xsl"
81         );
82         # and stash a transformer
83         $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
84         $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
85         $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
86         $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
87
88         # parse the ATOM entry xslt ...
89         my $atom_xslt = $_parser->parse_file(
90                 OpenSRF::Utils::SettingsClient
91                         ->new
92                         ->config_value( dirs => 'xsl' ).
93                 "/MARC21slim2ATOM.xsl"
94         );
95         # and stash a transformer
96         $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
97         $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
98         $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
99
100         # parse the RDFDC xslt ...
101         my $rdf_dc_xslt = $_parser->parse_file(
102                 OpenSRF::Utils::SettingsClient
103                         ->new
104                         ->config_value( dirs => 'xsl' ).
105                 "/MARC21slim2RDFDC.xsl"
106         );
107         # and stash a transformer
108         $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
109         $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
110         $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
111
112         # parse the SRWDC xslt ...
113         my $srw_dc_xslt = $_parser->parse_file(
114                 OpenSRF::Utils::SettingsClient
115                         ->new
116                         ->config_value( dirs => 'xsl' ).
117                 "/MARC21slim2SRWDC.xsl"
118         );
119         # and stash a transformer
120         $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
121         $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
122         $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
123
124         # parse the OAIDC xslt ...
125         my $oai_dc_xslt = $_parser->parse_file(
126                 OpenSRF::Utils::SettingsClient
127                         ->new
128                         ->config_value( dirs => 'xsl' ).
129                 "/MARC21slim2OAIDC.xsl"
130         );
131         # and stash a transformer
132         $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
133         $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
134         $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
135
136         # parse the RSS xslt ...
137         my $rss_xslt = $_parser->parse_file(
138                 OpenSRF::Utils::SettingsClient
139                         ->new
140                         ->config_value( dirs => 'xsl' ).
141                 "/MARC21slim2RSS2.xsl"
142         );
143         # and stash a transformer
144         $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
145
146         register_record_transforms();
147
148         return 1;
149 }
150
151 sub register_record_transforms {
152         for my $type ( keys %record_xslt ) {
153                 __PACKAGE__->register_method(
154                         method    => 'retrieve_record_transform',
155                         api_name  => "open-ils.supercat.record.$type.retrieve",
156                         api_level => 1,
157                         argc      => 1,
158                         signature =>
159                                 { desc     => "Returns the \U$type\E representation ".
160                                               "of the requested bibliographic record",
161                                   params   =>
162                                         [
163                                                 { name => 'bibId',
164                                                   desc => 'An OpenILS biblio::record_entry id',
165                                                   type => 'number' },
166                                         ],
167                                 'return' =>
168                                         { desc => "The bib record in \U$type\E",
169                                           type => 'string' }
170                                 }
171                 );
172
173                 __PACKAGE__->register_method(
174                         method    => 'retrieve_isbn_transform',
175                         api_name  => "open-ils.supercat.isbn.$type.retrieve",
176                         api_level => 1,
177                         argc      => 1,
178                         signature =>
179                                 { desc     => "Returns the \U$type\E representation ".
180                                               "of the requested bibliographic record",
181                                   params   =>
182                                         [
183                                                 { name => 'isbn',
184                                                   desc => 'An ISBN',
185                                                   type => 'string' },
186                                         ],
187                                 'return' =>
188                                         { desc => "The bib record in \U$type\E",
189                                           type => 'string' }
190                                 }
191                 );
192         }
193 }
194
195
196 sub entityize {
197         my $stuff = NFC(shift());
198         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
199         return $stuff;
200 }
201
202 sub tree_walker {
203         my $tree = shift;
204         my $field = shift;
205         my $filter = shift;
206
207         return unless ($tree && ref($tree->$field));
208
209         my @things = $filter->($tree);
210         for my $v ( @{$tree->$field} ){
211                 push @things, $filter->($v);
212                 push @things, tree_walker($v, $field, $filter);
213         }
214         return @things
215 }
216
217 sub cn_browse {
218         my $self = shift;
219         my $client = shift;
220
221         my $label = shift;
222         my $ou = shift;
223         my $page_size = shift || 9;
224         my $page = shift || 0;
225
226         my ($before_limit,$after_limit) = (0,0);
227         my ($before_offset,$after_offset) = (0,0);
228
229         if (!$page) {
230                 $before_limit = $after_limit = int($page_size / 2);
231                 $after_limit += 1 if ($page_size % 2);
232         } else {
233                 $before_offset = $after_offset = int($page_size / 2);
234                 $before_offset += 1 if ($page_size % 2);
235                 $before_limit = $after_limit = $page_size;
236         }
237
238         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
239
240         my $o_search = { shortname => $ou };
241         if (!$ou || $ou eq '-') {
242                 $o_search = { parent_ou => undef };
243         }
244
245         my $orgs = $_storage->request(
246                 "open-ils.cstore.direct.actor.org_unit.search",
247                 $o_search,
248                 { flesh         => 3,
249                   flesh_fields  => { aou        => [qw/children/] }
250                 }
251         )->gather(1);
252
253         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
254
255         $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
256
257         my @list = ();
258
259         if ($page <= 0) {
260                 my $before = $_storage->request(
261                         "open-ils.cstore.direct.asset.call_number.search.atomic",
262                         { label         => { "<" => { transform => "upper", value => ["upper", $label] } },
263                           owning_lib    => \@ou_ids,
264               deleted => 'f',
265                         },
266                         { flesh         => 1,
267                           flesh_fields  => { acn => [qw/record owning_lib/] },
268                           order_by      => { acn => "upper(label) desc, id desc, owning_lib desc" },
269                           limit         => $before_limit,
270                           offset        => abs($page) * $page_size - $before_offset,
271                         }
272                 )->gather(1);
273                 push @list, reverse(@$before);
274         }
275
276         if ($page >= 0) {
277                 my $after = $_storage->request(
278                         "open-ils.cstore.direct.asset.call_number.search.atomic",
279                         { label         => { ">=" => { transform => "upper", value => ["upper", $label] } },
280                           owning_lib    => \@ou_ids,
281               deleted => 'f',
282                         },
283                         { flesh         => 1,
284                           flesh_fields  => { acn => [qw/record owning_lib/] },
285                           order_by      => { acn => "upper(label), id, owning_lib" },
286                           limit         => $after_limit,
287                           offset        => abs($page) * $page_size - $after_offset,
288                         }
289                 )->gather(1);
290                 push @list, @$after;
291         }
292
293         return \@list;
294 }
295 __PACKAGE__->register_method(
296         method    => 'cn_browse',
297         api_name  => 'open-ils.supercat.call_number.browse',
298         api_level => 1,
299         argc      => 1,
300         signature =>
301                 { desc     => <<"                 DESC",
302 Returns the XML representation of the requested bibliographic record's holdings
303                   DESC
304                   params   =>
305                         [
306                                 { name => 'label',
307                                   desc => 'The target call number lable',
308                                   type => 'string' },
309                                 { name => 'org_unit',
310                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
311                                   type => 'string' },
312                                 { name => 'page_size',
313                                   desc => 'Count of call numbers to retrieve, default is 9',
314                                   type => 'number' },
315                                 { name => 'page',
316                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
317                                   type => 'number' },
318                         ],
319                   'return' =>
320                         { desc => 'Call numbers with owning_lib and record fleshed',
321                           type => 'array' }
322                 }
323 );
324
325
326 sub new_record_holdings {
327         my $self = shift;
328         my $client = shift;
329         my $bib = shift;
330         my $ou = shift;
331
332         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
333
334         my $tree = $_storage->request(
335                 "open-ils.cstore.direct.biblio.record_entry.retrieve",
336                 $bib,
337                 { flesh         => 5,
338                   flesh_fields  => {
339                                         bre     => [qw/call_numbers/],
340                                         acn     => [qw/copies owning_lib/],
341                                         acp     => [qw/location status circ_lib stat_cat_entries notes/],
342                                         asce    => [qw/stat_cat/],
343                                 }
344                 }
345         )->gather(1);
346
347         my $o_search = { shortname => uc($ou) };
348         if (!$ou || $ou eq '-') {
349                 $o_search = { parent_ou => undef };
350         }
351
352         my $orgs = $_storage->request(
353                 "open-ils.cstore.direct.actor.org_unit.search",
354                 $o_search,
355                 { flesh         => 3,
356                   flesh_fields  => { aou        => [qw/children/] }
357                 }
358         )->gather(1);
359
360         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
361
362         $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
363
364         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
365         $year += 1900;
366         $month += 1;
367
368         $client->respond("<hold:volumes xmlns:hold='http://open-ils.org/spec/holdings/v1'>");
369
370         for my $cn (@{$tree->call_numbers}) {
371
372                 my $found = 0;
373                 for my $c (@{$cn->copies}) {
374                         next unless grep {$c->circ_lib->id == $_} @ou_ids;
375                         $found = 1;
376                         last;
377                 }
378                 next unless $found;
379
380                 (my $cn_class = $cn->class_name) =~ s/::/-/gso;
381                 $cn_class =~ s/Fieldmapper-//gso;
382                 my $cn_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cn_class/".$cn->id, $month, $day);
383
384                 my $cn_lib = $cn->owning_lib->shortname;
385
386                 my $cn_label = $cn->label;
387
388                 my $xml = "<hold:volume id='$cn_tag' lib='$cn_lib' label='$cn_label'><hold:copies>";
389                 
390                 for my $cp (@{$cn->copies}) {
391
392                         next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
393
394                         (my $cp_class = $cp->class_name) =~ s/::/-/gso;
395                         $cp_class =~ s/Fieldmapper-//gso;
396                         my $cp_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cp_class/".$cp->id, $month, $day);
397
398                         my $cp_stat = escape($cp->status->name);
399                         my $cp_loc = escape($cp->location->name);
400                         my $cp_lib = escape($cp->circ_lib->shortname);
401                         my $cp_bc = escape($cp->barcode);
402
403                         $xml .= "<hold:copy id='$cp_tag' barcode='$cp_bc'><hold:status>$cp_stat</hold:status>".
404                                 "<hold:location>$cp_loc</hold:location><hold:circlib>$cp_lib</hold:circlib><hold:copy_notes>";
405
406                         if ($cp->notes) {
407                                 for my $note ( @{$cp->notes} ) {
408                                         next unless ( $note->pub eq 't' );
409                                         $xml .= sprintf('<hold:copy_note date="%s" title="%s">%s</hold:copy_note>',$note->create_date, escape($note->title), escape($note->value));
410                                 }
411                         }
412
413                         $xml .= "</hold:copy_notes><hold:statcats>";
414
415                         if ($cp->stat_cat_entries) {
416                                 for my $sce ( @{$cp->stat_cat_entries} ) {
417                                         next unless ( $sce->stat_cat->opac_visible eq 't' );
418                                         $xml .= sprintf('<hold:statcat name="%s">%s</hold:statcat>',escape($sce->stat_cat->name) ,escape($sce->value));
419                                 }
420                         }
421
422                         $xml .= "</hold:statcats></hold:copy>";
423                 }
424                 
425                 $xml .= "</hold:copies></hold:volume>";
426
427                 $client->respond($xml)
428         }
429
430         return "</hold:volumes>";
431 }
432 __PACKAGE__->register_method(
433         method    => 'new_record_holdings',
434         api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
435         api_level => 1,
436         argc      => 1,
437         stream    => 1,
438         signature =>
439                 { desc     => <<"                 DESC",
440 Returns the XML representation of the requested bibliographic record's holdings
441                   DESC
442                   params   =>
443                         [
444                                 { name => 'bibId',
445                                   desc => 'An OpenILS biblio::record_entry id',
446                                   type => 'number' },
447                         ],
448                   'return' =>
449                         { desc => 'Stream of bib record holdings hierarchy in XML',
450                           type => 'string' }
451                 }
452 );
453
454 sub isbn_holdings {
455         my $self = shift;
456         my $client = shift;
457         my $isbn = shift;
458
459         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
460
461         my $recs = $_storage->request(
462                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
463                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
464         )->gather(1);
465
466         return undef unless (@$recs);
467
468         return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
469 }
470 __PACKAGE__->register_method(
471         method    => 'isbn_holdings',
472         api_name  => 'open-ils.supercat.isbn.holdings_xml.retrieve',
473         api_level => 1,
474         argc      => 1,
475         signature =>
476                 { desc     => <<"                 DESC",
477 Returns the XML representation of the requested bibliographic record's holdings
478                   DESC
479                   params   =>
480                         [
481                                 { name => 'isbn',
482                                   desc => 'An isbn',
483                                   type => 'string' },
484                         ],
485                   'return' =>
486                         { desc => 'The bib record holdings hierarchy in XML',
487                           type => 'string' }
488                 }
489 );
490
491 sub escape {
492         my $text = shift;
493         $text =~ s/&/&amp;/gsom;
494         $text =~ s/</&lt;/gsom;
495         $text =~ s/>/&gt;/gsom;
496         $text =~ s/"/\\"/gsom;
497         return $text;
498 }
499
500 sub recent_changes {
501         my $self = shift;
502         my $client = shift;
503         my $when = shift || '1-01-01';
504         my $limit = shift;
505
506         my $type = 'biblio';
507         $type = 'authority' if ($self->api_name =~ /authority/o);
508
509         my $axis = 'create_date';
510         $axis = 'edit_date' if ($self->api_name =~ /edit/o);
511
512         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
513
514         return $_storage->request(
515                 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
516                 { $axis => { ">" => $when }, id => { '>' => 0 } },
517                 { order_by => { bre => "$axis desc" }, limit => $limit }
518         )->gather(1);
519 }
520
521 for my $t ( qw/biblio authority/ ) {
522         for my $a ( qw/import edit/ ) {
523
524                 __PACKAGE__->register_method(
525                         method    => 'recent_changes',
526                         api_name  => "open-ils.supercat.$t.record.$a.recent",
527                         api_level => 1,
528                         argc      => 0,
529                         signature =>
530                                 { desc     => "Returns a list of recently ${a}ed $t records",
531                                   params   =>
532                                         [
533                                                 { name => 'when',
534                                                   desc => "Date to start looking for ${a}ed records",
535                                                   default => '1-01-01',
536                                                   type => 'string' },
537
538                                                 { name => 'limit',
539                                                   desc => "Maximum count to retrieve",
540                                                   type => 'number' },
541                                         ],
542                                   'return' =>
543                                         { desc => "An id list of $t records",
544                                           type => 'array' }
545                                 },
546                 );
547         }
548 }
549
550
551 sub retrieve_record_marcxml {
552         my $self = shift;
553         my $client = shift;
554         my $rid = shift;
555
556         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
557
558         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
559         return entityize( $record->marc ) if ($record);
560         return undef;
561 }
562
563 __PACKAGE__->register_method(
564         method    => 'retrieve_record_marcxml',
565         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
566         api_level => 1,
567         argc      => 1,
568         signature =>
569                 { desc     => <<"                 DESC",
570 Returns the MARCXML representation of the requested bibliographic record
571                   DESC
572                   params   =>
573                         [
574                                 { name => 'bibId',
575                                   desc => 'An OpenILS biblio::record_entry id',
576                                   type => 'number' },
577                         ],
578                   'return' =>
579                         { desc => 'The bib record in MARCXML',
580                           type => 'string' }
581                 }
582 );
583
584 sub retrieve_isbn_marcxml {
585         my $self = shift;
586         my $client = shift;
587         my $isbn = shift;
588
589         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
590
591         my $recs = $_storage->request(
592                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
593                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
594         )->gather(1);
595
596         return undef unless (@$recs);
597
598         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
599         return entityize( $record->marc ) if ($record);
600         return undef;
601 }
602
603 __PACKAGE__->register_method(
604         method    => 'retrieve_isbn_marcxml',
605         api_name  => 'open-ils.supercat.isbn.marcxml.retrieve',
606         api_level => 1,
607         argc      => 1,
608         signature =>
609                 { desc     => <<"                 DESC",
610 Returns the MARCXML representation of the requested ISBN
611                   DESC
612                   params   =>
613                         [
614                                 { name => 'ISBN',
615                                   desc => 'An ... um ... ISBN',
616                                   type => 'string' },
617                         ],
618                   'return' =>
619                         { desc => 'The bib record in MARCXML',
620                           type => 'string' }
621                 }
622 );
623
624 sub retrieve_record_transform {
625         my $self = shift;
626         my $client = shift;
627         my $rid = shift;
628
629         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
630
631         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
632         #$_storage->connect;
633
634         my $record = $_storage->request(
635                 'open-ils.cstore.direct.biblio.record_entry.retrieve',
636                 $rid
637         )->gather(1);
638
639         return undef unless ($record);
640
641         return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
642 }
643
644 sub retrieve_isbn_transform {
645         my $self = shift;
646         my $client = shift;
647         my $isbn = shift;
648
649         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
650
651         my $recs = $_storage->request(
652                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
653                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
654         )->gather(1);
655
656         return undef unless (@$recs);
657
658         (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
659
660         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
661
662         return undef unless ($record);
663
664         return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
665 }
666
667 sub retrieve_record_objects {
668         my $self = shift;
669         my $client = shift;
670         my $ids = shift;
671
672         $ids = [$ids] unless (ref $ids);
673         $ids = [grep {$_} @$ids];
674
675         return [] unless (@$ids);
676
677         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
678         return $_storage->request('open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => [grep {$_} @$ids] })->gather(1);
679 }
680 __PACKAGE__->register_method(
681         method    => 'retrieve_record_objects',
682         api_name  => 'open-ils.supercat.record.object.retrieve',
683         api_level => 1,
684         argc      => 1,
685         signature =>
686                 { desc     => <<"                 DESC",
687 Returns the Fieldmapper object representation of the requested bibliographic records
688                   DESC
689                   params   =>
690                         [
691                                 { name => 'bibIds',
692                                   desc => 'OpenILS biblio::record_entry ids',
693                                   type => 'array' },
694                         ],
695                   'return' =>
696                         { desc => 'The bib records',
697                           type => 'array' }
698                 }
699 );
700
701
702 sub retrieve_isbn_object {
703         my $self = shift;
704         my $client = shift;
705         my $isbn = shift;
706
707         return undef unless ($isbn);
708
709         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
710         my $recs = $_storage->request(
711                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
712                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
713         )->gather(1);
714
715         return undef unless (@$recs);
716
717         return $_storage->request(
718                 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
719                 { id => $recs->[0]->record }
720         )->gather(1);
721 }
722 __PACKAGE__->register_method(
723         method    => 'retrieve_isbn_object',
724         api_name  => 'open-ils.supercat.isbn.object.retrieve',
725         api_level => 1,
726         argc      => 1,
727         signature =>
728                 { desc     => <<"                 DESC",
729 Returns the Fieldmapper object representation of the requested bibliographic record
730                   DESC
731                   params   =>
732                         [
733                                 { name => 'isbn',
734                                   desc => 'an ISBN',
735                                   type => 'string' },
736                         ],
737                   'return' =>
738                         { desc => 'The bib record',
739                           type => 'object' }
740                 }
741 );
742
743
744
745 sub retrieve_metarecord_mods {
746         my $self = shift;
747         my $client = shift;
748         my $rid = shift;
749
750         my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
751
752         # Get the metarecord in question
753         my $mr =
754         $_storage->request(
755                 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
756         )->gather(1);
757
758         # Now get the map of all bib records for the metarecord
759         my $recs =
760         $_storage->request(
761                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
762                 {metarecord => $rid}
763         )->gather(1);
764
765         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
766
767         # and retrieve the lead (master) record as MODS
768         my ($master) =
769                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
770                         ->run($mr->master_record);
771         my $master_mods = $_parser->parse_string($master)->documentElement;
772         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
773
774         # ... and a MODS clone to populate, with guts removed.
775         my $mods = $_parser->parse_string($master)->documentElement;
776         $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
777         ($mods) = $mods->findnodes('//mods:mods');
778         $mods->removeChildNodes;
779
780         # Add the metarecord ID as a (locally defined) info URI
781         my $recordInfo = $mods
782                 ->ownerDocument
783                 ->createElement("mods:recordInfo");
784
785         my $recordIdentifier = $mods
786                 ->ownerDocument
787                 ->createElement("mods:recordIdentifier");
788
789         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
790         $year += 1900;
791         $month += 1;
792
793         my $id = $mr->id;
794         $recordIdentifier->appendTextNode(
795                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
796         );
797
798         $recordInfo->appendChild($recordIdentifier);
799         $mods->appendChild($recordInfo);
800
801         # Grab the title, author and ISBN for the master record and populate the metarecord
802         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
803         
804         if ($title) {
805                 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
806                 $title = $mods->ownerDocument->importNode($title);
807                 $mods->appendChild($title);
808         }
809
810         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
811         if ($author) {
812                 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
813                 $author = $mods->ownerDocument->importNode($author);
814                 $mods->appendChild($author);
815         }
816
817         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
818         if ($isbn) {
819                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
820                 $isbn = $mods->ownerDocument->importNode($isbn);
821                 $mods->appendChild($isbn);
822         }
823
824         # ... and loop over the constituent records
825         for my $map ( @$recs ) {
826
827                 # get the MODS
828                 my ($rec) =
829                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
830                                 ->run($map->source);
831
832                 my $part_mods = $_parser->parse_string($rec);
833                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
834                 ($part_mods) = $part_mods->findnodes('//mods:mods');
835
836                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
837                         $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
838                         $node = $mods->ownerDocument->importNode($node);
839                         $mods->appendChild( $node );
840                 }
841
842                 my $relatedItem = $mods
843                         ->ownerDocument
844                         ->createElement("mods:relatedItem");
845
846                 $relatedItem->setAttribute( type => 'constituent' );
847
848                 my $identifier = $mods
849                         ->ownerDocument
850                         ->createElement("mods:identifier");
851
852                 $identifier->setAttribute( type => 'uri' );
853
854                 my $subRecordInfo = $mods
855                         ->ownerDocument
856                         ->createElement("mods:recordInfo");
857
858                 my $subRecordIdentifier = $mods
859                         ->ownerDocument
860                         ->createElement("mods:recordIdentifier");
861
862                 my $subid = $map->source;
863                 $subRecordIdentifier->appendTextNode(
864                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
865                                 $month,
866                                 $day
867                         )
868                 );
869                 $subRecordInfo->appendChild($subRecordIdentifier);
870
871                 $relatedItem->appendChild( $subRecordInfo );
872
873                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
874                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
875                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
876                 $relatedItem->appendChild($tor) if ($tor);
877
878                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
879                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
880                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
881                         $relatedItem->appendChild( $part_isbn );
882
883                         if (!$isbn) {
884                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
885                         }
886                 }
887
888                 $mods->appendChild( $relatedItem );
889
890         }
891
892         $_storage->disconnect;
893
894         return entityize($mods->toString);
895
896 }
897 __PACKAGE__->register_method(
898         method    => 'retrieve_metarecord_mods',
899         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
900         api_level => 1,
901         argc      => 1,
902         signature =>
903                 { desc     => <<"                 DESC",
904 Returns the MODS representation of the requested metarecord
905                   DESC
906                   params   =>
907                         [
908                                 { name => 'metarecordId',
909                                   desc => 'An OpenILS metabib::metarecord id',
910                                   type => 'number' },
911                         ],
912                   'return' =>
913                         { desc => 'The metarecord in MODS',
914                           type => 'string' }
915                 }
916 );
917
918 sub list_metarecord_formats {
919         my @list = (
920                 { mods =>
921                         { namespace_uri   => 'http://www.loc.gov/mods/',
922                           docs            => 'http://www.loc.gov/mods/',
923                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
924                         }
925                 }
926         );
927
928         for my $type ( keys %metarecord_xslt ) {
929                 push @list,
930                         { $type => 
931                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
932                                   docs            => $metarecord_xslt{$type}{docs},
933                                   schema_location => $metarecord_xslt{$type}{schema_location},
934                                 }
935                         };
936         }
937
938         return \@list;
939 }
940 __PACKAGE__->register_method(
941         method    => 'list_metarecord_formats',
942         api_name  => 'open-ils.supercat.metarecord.formats',
943         api_level => 1,
944         argc      => 0,
945         signature =>
946                 { desc     => <<"                 DESC",
947 Returns the list of valid metarecord formats that supercat understands.
948                   DESC
949                   'return' =>
950                         { desc => 'The format list',
951                           type => 'array' }
952                 }
953 );
954
955
956 sub list_record_formats {
957         my @list = (
958                 { marcxml =>
959                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
960                           docs            => 'http://www.loc.gov/marcxml/',
961                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
962                         }
963                 }
964         );
965
966         for my $type ( keys %record_xslt ) {
967                 push @list,
968                         { $type => 
969                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
970                                   docs            => $record_xslt{$type}{docs},
971                                   schema_location => $record_xslt{$type}{schema_location},
972                                 }
973                         };
974         }
975
976         return \@list;
977 }
978 __PACKAGE__->register_method(
979         method    => 'list_record_formats',
980         api_name  => 'open-ils.supercat.record.formats',
981         api_level => 1,
982         argc      => 0,
983         signature =>
984                 { desc     => <<"                 DESC",
985 Returns the list of valid record formats that supercat understands.
986                   DESC
987                   'return' =>
988                         { desc => 'The format list',
989                           type => 'array' }
990                 }
991 );
992 __PACKAGE__->register_method(
993         method    => 'list_record_formats',
994         api_name  => 'open-ils.supercat.isbn.formats',
995         api_level => 1,
996         argc      => 0,
997         signature =>
998                 { desc     => <<"                 DESC",
999 Returns the list of valid record formats that supercat understands.
1000                   DESC
1001                   'return' =>
1002                         { desc => 'The format list',
1003                           type => 'array' }
1004                 }
1005 );
1006
1007
1008 sub oISBN {
1009         my $self = shift;
1010         my $client = shift;
1011         my $isbn = shift;
1012
1013         $isbn =~ s/-//gso;
1014
1015         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
1016                 unless (length($isbn) >= 10);
1017
1018         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1019
1020         # Create a storage session, since we'll be making muliple requests.
1021         $_storage->connect;
1022
1023         # Find the record that has that ISBN.
1024         my $bibrec = $_storage->request(
1025                 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1026                 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
1027         )->gather(1);
1028
1029         # Go away if we don't have one.
1030         return {} unless (@$bibrec);
1031
1032         # Find the metarecord for that bib record.
1033         my $mr = $_storage->request(
1034                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
1035                 {source => $bibrec->[0]->record}
1036         )->gather(1);
1037
1038         # Find the other records for that metarecord.
1039         my $records = $_storage->request(
1040                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
1041                 {metarecord => $mr->[0]->metarecord}
1042         )->gather(1);
1043
1044         # Just to be safe.  There's currently no unique constraint on sources...
1045         my %unique_recs = map { ($_->source, 1) } @$records;
1046         my @rec_list = sort keys %unique_recs;
1047
1048         # And now fetch the ISBNs for thos records.
1049         my $recs = [];
1050         push @$recs,
1051                 $_storage->request(
1052                         'open-ils.cstore.direct.metabib.full_rec.search',
1053                         { tag => '020', subfield => 'a', record => $_ }
1054                 )->gather(1) for (@rec_list);
1055
1056         # We're done with the storage server session.
1057         $_storage->disconnect;
1058
1059         # Return the oISBN data structure.  This will be XMLized at a higher layer.
1060         return
1061                 { metarecord => $mr->[0]->metarecord,
1062                   record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
1063
1064 }
1065 __PACKAGE__->register_method(
1066         method    => 'oISBN',
1067         api_name  => 'open-ils.supercat.oisbn',
1068         api_level => 1,
1069         argc      => 1,
1070         signature =>
1071                 { desc     => <<"                 DESC",
1072 Returns the ISBN list for the metarecord of the requested isbn
1073                   DESC
1074                   params   =>
1075                         [
1076                                 { name => 'isbn',
1077                                   desc => 'An ISBN.  Duh.',
1078                                   type => 'string' },
1079                         ],
1080                   'return' =>
1081                         { desc => 'record to isbn map',
1082                           type => 'object' }
1083                 }
1084 );
1085
1086 1;