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