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