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