1 package OpenILS::Application::SuperCat;
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/;
11 # This is the client class, used for connecting to open-ils.storage
12 use OpenSRF::AppSession;
14 # This is an extention of Error.pm that supplies some error types to throw
15 use OpenSRF::EX qw(:try);
17 # This is a helper class for querying the OpenSRF Settings application ...
18 use OpenSRF::Utils::SettingsClient;
20 # ... and here we have the built in logging helper ...
21 use OpenSRF::Utils::Logger qw($logger);
23 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
24 use OpenILS::Utils::Fieldmapper;
27 # We'll be working with XML, so...
30 use Unicode::Normalize;
32 use OpenSRF::Utils::JSON;
43 # we need an XML parser
44 $_parser = new XML::LibXML;
47 $_xslt = new XML::LibXSLT;
49 # parse the MODS xslt ...
50 my $mods33_xslt = $_parser->parse_file(
51 OpenSRF::Utils::SettingsClient
53 ->config_value( dirs => 'xsl' ).
54 "/MARC21slim2MODS33.xsl"
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';
62 # parse the MODS xslt ...
63 my $mods32_xslt = $_parser->parse_file(
64 OpenSRF::Utils::SettingsClient
66 ->config_value( dirs => 'xsl' ).
67 "/MARC21slim2MODS32.xsl"
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';
75 # parse the MODS xslt ...
76 my $mods3_xslt = $_parser->parse_file(
77 OpenSRF::Utils::SettingsClient
79 ->config_value( dirs => 'xsl' ).
80 "/MARC21slim2MODS3.xsl"
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';
88 # parse the MODS xslt ...
89 my $mods_xslt = $_parser->parse_file(
90 OpenSRF::Utils::SettingsClient
92 ->config_value( dirs => 'xsl' ).
93 "/MARC21slim2MODS.xsl"
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';
101 # parse the ATOM entry xslt ...
102 my $atom_xslt = $_parser->parse_file(
103 OpenSRF::Utils::SettingsClient
105 ->config_value( dirs => 'xsl' ).
106 "/MARC21slim2ATOM.xsl"
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';
113 # parse the RDFDC xslt ...
114 my $rdf_dc_xslt = $_parser->parse_file(
115 OpenSRF::Utils::SettingsClient
117 ->config_value( dirs => 'xsl' ).
118 "/MARC21slim2RDFDC.xsl"
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/';
125 # parse the SRWDC xslt ...
126 my $srw_dc_xslt = $_parser->parse_file(
127 OpenSRF::Utils::SettingsClient
129 ->config_value( dirs => 'xsl' ).
130 "/MARC21slim2SRWDC.xsl"
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';
137 # parse the OAIDC xslt ...
138 my $oai_dc_xslt = $_parser->parse_file(
139 OpenSRF::Utils::SettingsClient
141 ->config_value( dirs => 'xsl' ).
142 "/MARC21slim2OAIDC.xsl"
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';
149 # parse the RSS xslt ...
150 my $rss_xslt = $_parser->parse_file(
151 OpenSRF::Utils::SettingsClient
153 ->config_value( dirs => 'xsl' ).
154 "/MARC21slim2RSS2.xsl"
156 # and stash a transformer
157 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
159 # parse the FGDC xslt ...
160 my $fgdc_xslt = $_parser->parse_file(
161 OpenSRF::Utils::SettingsClient
163 ->config_value( dirs => 'xsl' ).
164 "/MARC21slim2FGDC.xsl"
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';
171 register_record_transforms();
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",
184 { desc => "Returns the \U$type\E representation ".
185 "of the requested bibliographic record",
189 desc => 'An OpenILS biblio::record_entry id',
193 { desc => "The bib record in \U$type\E",
198 __PACKAGE__->register_method(
199 method => 'retrieve_isbn_transform',
200 api_name => "open-ils.supercat.isbn.$type.retrieve",
204 { desc => "Returns the \U$type\E representation ".
205 "of the requested bibliographic record",
213 { desc => "The bib record in \U$type\E",
222 my $stuff = NFC(shift());
223 $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
232 return unless ($tree && ref($tree->$field));
234 my @things = $filter->($tree);
235 for my $v ( @{$tree->$field} ){
236 push @things, $filter->($v);
237 push @things, tree_walker($v, $field, $filter);
248 my $page_size = shift || 9;
249 my $page = shift || 0;
251 my ($before_limit,$after_limit) = (0,0);
252 my ($before_offset,$after_offset) = (0,0);
255 $before_limit = $after_limit = int($page_size / 2);
256 $after_limit += 1 if ($page_size % 2);
258 $before_offset = $after_offset = int($page_size / 2);
259 $before_offset += 1 if ($page_size % 2);
260 $before_limit = $after_limit = $page_size;
263 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
265 my $o_search = { shortname => $ou };
266 if (!$ou || $ou eq '-') {
267 $o_search = { parent_ou => undef };
270 my $orgs = $_storage->request(
271 "open-ils.cstore.direct.actor.org_unit.search",
274 flesh_fields => { aou => [qw/children/] }
278 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
280 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
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,
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,
298 push @list, reverse(@$before);
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,
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,
320 __PACKAGE__->register_method(
321 method => 'cn_browse',
322 api_name => 'open-ils.supercat.call_number.browse',
327 Returns the XML representation of the requested bibliographic record's holdings
332 desc => 'The target call number lable',
334 { name => 'org_unit',
335 desc => 'The org unit shortname (or "-" or undef for global) to browse',
337 { name => 'page_size',
338 desc => 'Count of call numbers to retrieve, default is 9',
341 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
345 { desc => 'Call numbers with owning_lib and record fleshed',
351 sub new_books_by_item {
356 my $page_size = shift || 10;
357 my $page = shift || 1;
359 my $offset = $page_size * ($page - 1);
361 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
364 if ($ou && $ou ne '-') {
365 my $orgs = $_storage->request(
366 "open-ils.cstore.direct.actor.org_unit.search",
367 { shortname => $ou },
369 flesh_fields => { aou => [qw/children/] }
372 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
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'}]
381 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
383 { '+acp' => { deleted => 'f', (@ou_ids) ? ( circ_lib => \@ou_ids) : () },
384 '+acn' => { record => { '>' => 0 } },
386 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
392 return [ map { $_->{record} } @$cns ];
394 __PACKAGE__->register_method(
395 method => 'new_books_by_item',
396 api_name => 'open-ils.supercat.new_book_list',
401 Returns the XML representation of the requested bibliographic record's holdings
405 { name => 'org_unit',
406 desc => 'The org unit shortname (or "-" or undef for global) to list',
408 { name => 'page_size',
409 desc => 'Count of records to retrieve, default is 10',
412 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
416 { desc => 'Record IDs',
425 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
427 __PACKAGE__->register_method(
428 method => 'general_browse',
429 api_name => 'open-ils.supercat.title.browse',
430 tag => 'tnf', subfield => 'a',
434 { desc => "Returns a list of the requested org-scoped record ids held",
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' }
443 __PACKAGE__->register_method(
444 method => 'general_browse',
445 api_name => 'open-ils.supercat.author.browse',
446 tag => [qw/100 110 111/], subfield => 'a',
450 { desc => "Returns a list of the requested org-scoped record ids held",
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' }
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',
466 { desc => "Returns a list of the requested org-scoped record ids held",
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' }
475 __PACKAGE__->register_method(
476 method => 'general_browse',
477 api_name => 'open-ils.supercat.topic.browse',
478 tag => [qw/650 690/], subfield => 'a',
482 { desc => "Returns a list of the requested org-scoped record ids held",
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' }
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',
498 { desc => "Returns a list of the requested org-scoped record ids held",
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' }
514 my $subfield = shift;
517 my $page_size = shift || 9;
518 my $page = shift || 0;
520 my ($before_limit,$after_limit) = (0,0);
521 my ($before_offset,$after_offset) = (0,0);
524 $before_limit = $after_limit = int($page_size / 2);
525 $after_limit += 1 if ($page_size % 2);
527 $before_offset = $after_offset = int($page_size / 2);
528 $before_offset += 1 if ($page_size % 2);
529 $before_limit = $after_limit = $page_size;
532 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
535 if ($ou && $ou ne '-') {
536 my $orgs = $_storage->request(
537 "open-ils.cstore.direct.actor.org_unit.search",
538 { shortname => $ou },
540 flesh_fields => { aou => [qw/children/] }
543 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
546 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
551 my $before = $_storage->request(
552 "open-ils.cstore.json_query.atomic",
553 { select => { mfr => [qw/record value/] },
558 subfield => $subfield,
559 value => { '<' => lc($value) }
562 { select=> { acp => [ 'id' ] },
563 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
565 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
566 '+acp' => { deleted => 'f', (@ou_ids) ? ( circ_lib => \@ou_ids) : () }
571 order_by => { mfr => { value => 'desc' } },
572 limit => $before_limit,
573 offset => abs($page) * $page_size - $before_offset,
576 push @list, map { $_->{record} } reverse(@$before);
580 my $after = $_storage->request(
581 "open-ils.cstore.json_query.atomic",
582 { select => { mfr => [qw/record value/] },
587 subfield => $subfield,
588 value => { '>=' => lc($value) }
591 { select=> { acp => [ 'id' ] },
592 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
594 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
595 '+acp' => { deleted => 'f', (@ou_ids) ? ( circ_lib => \@ou_ids) : () }
600 order_by => { mfr => { value => 'asc' } },
601 limit => $after_limit,
602 offset => abs($page) * $page_size - $after_offset,
605 push @list, map { $_->{record} } @$after;
610 __PACKAGE__->register_method(
611 method => 'tag_sf_browse',
612 api_name => 'open-ils.supercat.tag.browse',
617 Returns a list of the requested org-scoped record ids held
622 desc => 'The target MARC tag',
624 { name => 'subfield',
625 desc => 'The target MARC subfield',
628 desc => 'The target string',
630 { name => 'org_unit',
631 desc => 'The org unit shortname (or "-" or undef for global) to browse',
633 { name => 'page_size',
634 desc => 'Count of call numbers to retrieve, default is 9',
637 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
641 { desc => 'Record IDs that have copies at the relevant org units',
647 sub new_record_holdings {
653 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
655 my $tree = $_storage->request(
656 "open-ils.cstore.direct.biblio.record_entry.retrieve",
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/],
668 my $o_search = { shortname => uc($ou) };
669 if (!$ou || $ou eq '-') {
670 $o_search = { parent_ou => undef };
673 my $orgs = $_storage->request(
674 "open-ils.cstore.direct.actor.org_unit.search",
677 flesh_fields => { aou => [qw/children/] }
681 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
683 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
685 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
689 $client->respond("<holdings:volumes xmlns:holdings='http://open-ils.org/spec/holdings/v1'>");
691 for my $cn (@{$tree->call_numbers}) {
692 next unless ( $cn->deleted eq 'f' || $cn->deleted == 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 );
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);
707 my $cn_lib = $cn->owning_lib->shortname;
709 my $cn_label = $cn->label;
711 my $xml = "<holdings:volume id='$cn_tag' lib='$cn_lib' label='$cn_label'><holdings:copies>";
713 for my $cp (@{$cn->copies}) {
715 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
716 next unless ( $cp->deleted eq 'f' || $cp->deleted == 0 );
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);
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);
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>";
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));
737 $xml .= "</holdings:copy_notes><holdings:statcats>";
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));
746 $xml .= "</holdings:statcats></holdings:copy>";
749 $xml .= "</holdings:copies></holdings:volume>";
751 $client->respond($xml)
754 return "</holdings:volumes>";
756 __PACKAGE__->register_method(
757 method => 'new_record_holdings',
758 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
764 Returns the XML representation of the requested bibliographic record's holdings
769 desc => 'An OpenILS biblio::record_entry id',
773 { desc => 'Stream of bib record holdings hierarchy in XML',
783 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
785 my $recs = $_storage->request(
786 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
787 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
790 return undef unless (@$recs);
792 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
794 __PACKAGE__->register_method(
795 method => 'isbn_holdings',
796 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
801 Returns the XML representation of the requested bibliographic record's holdings
810 { desc => 'The bib record holdings hierarchy in XML',
817 $text =~ s/&/&/gsom;
818 $text =~ s/</</gsom;
819 $text =~ s/>/>/gsom;
820 $text =~ s/"/\\"/gsom;
827 my $when = shift || '1-01-01';
831 $type = 'authority' if ($self->api_name =~ /authority/o);
833 my $axis = 'create_date';
834 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
836 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
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 }
845 for my $t ( qw/biblio authority/ ) {
846 for my $a ( qw/import edit/ ) {
848 __PACKAGE__->register_method(
849 method => 'recent_changes',
850 api_name => "open-ils.supercat.$t.record.$a.recent",
854 { desc => "Returns a list of recently ${a}ed $t records",
858 desc => "Date to start looking for ${a}ed records",
859 default => '1-01-01',
863 desc => "Maximum count to retrieve",
867 { desc => "An id list of $t records",
875 sub retrieve_record_marcxml {
880 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
882 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
883 return entityize( $record->marc ) if ($record);
887 __PACKAGE__->register_method(
888 method => 'retrieve_record_marcxml',
889 api_name => 'open-ils.supercat.record.marcxml.retrieve',
894 Returns the MARCXML representation of the requested bibliographic record
899 desc => 'An OpenILS biblio::record_entry id',
903 { desc => 'The bib record in MARCXML',
908 sub retrieve_isbn_marcxml {
913 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
915 my $recs = $_storage->request(
916 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
917 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
920 return undef unless (@$recs);
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);
927 __PACKAGE__->register_method(
928 method => 'retrieve_isbn_marcxml',
929 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
934 Returns the MARCXML representation of the requested ISBN
939 desc => 'An ... um ... ISBN',
943 { desc => 'The bib record in MARCXML',
948 sub retrieve_record_transform {
953 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
955 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
958 my $record = $_storage->request(
959 'open-ils.cstore.direct.biblio.record_entry.retrieve',
963 return undef unless ($record);
965 return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
968 sub retrieve_isbn_transform {
973 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
975 my $recs = $_storage->request(
976 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
977 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
980 return undef unless (@$recs);
982 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
984 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
986 return undef unless ($record);
988 return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
991 sub retrieve_record_objects {
996 $ids = [$ids] unless (ref $ids);
997 $ids = [grep {$_} @$ids];
999 return [] unless (@$ids);
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);
1004 __PACKAGE__->register_method(
1005 method => 'retrieve_record_objects',
1006 api_name => 'open-ils.supercat.record.object.retrieve',
1010 { desc => <<" DESC",
1011 Returns the Fieldmapper object representation of the requested bibliographic records
1016 desc => 'OpenILS biblio::record_entry ids',
1020 { desc => 'The bib records',
1026 sub retrieve_isbn_object {
1031 return undef unless ($isbn);
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\%"}}
1039 return undef unless (@$recs);
1041 return $_storage->request(
1042 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
1043 { id => $recs->[0]->record }
1046 __PACKAGE__->register_method(
1047 method => 'retrieve_isbn_object',
1048 api_name => 'open-ils.supercat.isbn.object.retrieve',
1052 { desc => <<" DESC",
1053 Returns the Fieldmapper object representation of the requested bibliographic record
1062 { desc => 'The bib record',
1069 sub retrieve_metarecord_mods {
1074 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
1076 # Get the metarecord in question
1079 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
1082 # Now get the map of all bib records for the metarecord
1085 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
1086 {metarecord => $rid}
1089 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
1091 # and retrieve the lead (master) record as MODS
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 );
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;
1104 # Add the metarecord ID as a (locally defined) info URI
1105 my $recordInfo = $mods
1107 ->createElement("mods:recordInfo");
1109 my $recordIdentifier = $mods
1111 ->createElement("mods:recordIdentifier");
1113 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
1118 $recordIdentifier->appendTextNode(
1119 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
1122 $recordInfo->appendChild($recordIdentifier);
1123 $mods->appendChild($recordInfo);
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)]' );
1129 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1130 $title = $mods->ownerDocument->importNode($title);
1131 $mods->appendChild($title);
1134 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
1136 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1137 $author = $mods->ownerDocument->importNode($author);
1138 $mods->appendChild($author);
1141 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
1143 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1144 $isbn = $mods->ownerDocument->importNode($isbn);
1145 $mods->appendChild($isbn);
1148 # ... and loop over the constituent records
1149 for my $map ( @$recs ) {
1153 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
1154 ->run($map->source);
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');
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 );
1166 my $relatedItem = $mods
1168 ->createElement("mods:relatedItem");
1170 $relatedItem->setAttribute( type => 'constituent' );
1172 my $identifier = $mods
1174 ->createElement("mods:identifier");
1176 $identifier->setAttribute( type => 'uri' );
1178 my $subRecordInfo = $mods
1180 ->createElement("mods:recordInfo");
1182 my $subRecordIdentifier = $mods
1184 ->createElement("mods:recordIdentifier");
1186 my $subid = $map->source;
1187 $subRecordIdentifier->appendTextNode(
1188 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
1193 $subRecordInfo->appendChild($subRecordIdentifier);
1195 $relatedItem->appendChild( $subRecordInfo );
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);
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 );
1208 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
1212 $mods->appendChild( $relatedItem );
1216 $_storage->disconnect;
1218 return entityize($mods->toString);
1221 __PACKAGE__->register_method(
1222 method => 'retrieve_metarecord_mods',
1223 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
1227 { desc => <<" DESC",
1228 Returns the MODS representation of the requested metarecord
1232 { name => 'metarecordId',
1233 desc => 'An OpenILS metabib::metarecord id',
1237 { desc => 'The metarecord in MODS',
1242 sub list_metarecord_formats {
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',
1252 for my $type ( keys %metarecord_xslt ) {
1255 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
1256 docs => $metarecord_xslt{$type}{docs},
1257 schema_location => $metarecord_xslt{$type}{schema_location},
1264 __PACKAGE__->register_method(
1265 method => 'list_metarecord_formats',
1266 api_name => 'open-ils.supercat.metarecord.formats',
1270 { desc => <<" DESC",
1271 Returns the list of valid metarecord formats that supercat understands.
1274 { desc => 'The format list',
1280 sub list_record_formats {
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',
1290 for my $type ( keys %record_xslt ) {
1293 { namespace_uri => $record_xslt{$type}{namespace_uri},
1294 docs => $record_xslt{$type}{docs},
1295 schema_location => $record_xslt{$type}{schema_location},
1302 __PACKAGE__->register_method(
1303 method => 'list_record_formats',
1304 api_name => 'open-ils.supercat.record.formats',
1308 { desc => <<" DESC",
1309 Returns the list of valid record formats that supercat understands.
1312 { desc => 'The format list',
1316 __PACKAGE__->register_method(
1317 method => 'list_record_formats',
1318 api_name => 'open-ils.supercat.isbn.formats',
1322 { desc => <<" DESC",
1323 Returns the list of valid record formats that supercat understands.
1326 { desc => 'The format list',
1339 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
1340 unless (length($isbn) >= 10);
1342 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1344 # Create a storage session, since we'll be making muliple requests.
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).'%'} }
1353 # Go away if we don't have one.
1354 return {} unless (@$bibrec);
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}
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}
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;
1372 # And now fetch the ISBNs for thos records.
1376 'open-ils.cstore.direct.metabib.full_rec.search',
1377 { tag => '020', subfield => 'a', record => $_ }
1378 )->gather(1) for (@rec_list);
1380 # We're done with the storage server session.
1381 $_storage->disconnect;
1383 # Return the oISBN data structure. This will be XMLized at a higher layer.
1385 { metarecord => $mr->[0]->metarecord,
1386 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
1389 __PACKAGE__->register_method(
1391 api_name => 'open-ils.supercat.oisbn',
1395 { desc => <<" DESC",
1396 Returns the ISBN list for the metarecord of the requested isbn
1401 desc => 'An ISBN. Duh.',
1405 { desc => 'record to isbn map',