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