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