1 package OpenILS::Application::Search::Biblio;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
6 use OpenILS::Utils::Fieldmapper;
7 use OpenILS::Utils::ModsParser;
8 use OpenSRF::Utils::SettingsClient;
10 use OpenILS::Application::AppUtils;
14 use Time::HiRes qw(time);
15 use OpenSRF::EX qw(:try);
16 use Digest::MD5 qw(md5_hex);
21 my $apputils = "OpenILS::Application::AppUtils";
23 # Houses biblio search utilites
25 __PACKAGE__->register_method(
26 method => "biblio_search_marc",
27 api_name => "open-ils.search.biblio.marc",
29 note => "Searches biblio information by marc tag",
32 sub biblio_search_marc {
34 my( $self, $client, $search_hash, $string ) = @_;
36 warn "Building biblio marc session\n";
37 my $session = OpenSRF::AppSession->create("open-ils.storage");
40 warn "Sending biblio marc request. String $string\nSearch hash: " . Dumper($search_hash);
41 my $request = $session->request(
42 "open-ils.storage.direct.metabib.full_rec.search_fts.index_vector.atomic",
43 restrict => $search_hash,
45 my $data = $request->gather(1);
50 $session->disconnect();
58 # ---------------------------------------------------------------------------
59 # takes a list of record id's and turns the docs into friendly
60 # mods structures. Creates one MODS structure for each doc id.
61 # ---------------------------------------------------------------------------
62 sub _records_to_mods {
68 my $session = OpenSRF::AppSession->create("open-ils.storage");
69 my $request = $session->request(
70 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
72 my $last_content = undef;
74 while( my $response = $request->recv() ) {
77 my $u = OpenILS::Utils::ModsParser->new();
78 $u->start_mods_batch( $last_content->marc );
79 my $mods = $u->finish_mods_batch();
80 $mods->doc_id($last_content->id());
81 warn "Turning doc " . $mods->doc_id() . " into MODS\n";
82 $last_content = undef;
86 next unless $response;
88 if($response->isa("OpenSRF::EX")) {
89 throw $response ($response->stringify);
92 $last_content = $response->content;
97 my $u = OpenILS::Utils::ModsParser->new();
98 $u->start_mods_batch( $last_content->marc );
99 my $mods = $u->finish_mods_batch();
100 $mods->doc_id($last_content->id());
101 push @results, $mods;
106 $session->disconnect();
112 __PACKAGE__->register_method(
113 method => "record_id_to_mods",
114 api_name => "open-ils.search.biblio.record.mods.retrieve",
116 note => "Provide ID, we provide the mods"
119 # converts a record into a mods object with copy counts attached
120 sub record_id_to_mods {
122 my( $self, $client, $org_id, $id ) = @_;
124 my $mods_list = _records_to_mods( $id );
125 my $mods_obj = $mods_list->[0];
126 my $cmethod = $self->method_lookup(
127 "open-ils.search.biblio.record.copy_count");
128 my ($count) = $cmethod->run($org_id, $id);
129 $mods_obj->copy_count($count);
135 __PACKAGE__->register_method(
136 method => "record_id_to_mods_slim",
137 api_name => "open-ils.search.biblio.record.mods_slim.retrieve",
139 note => "Provide ID, we provide the mods"
142 # converts a record into a mods object with NO copy counts attached
143 sub record_id_to_mods_slim {
145 my( $self, $client, $id ) = @_;
146 warn "Retrieving MODS object for record $id\n";
147 return undef unless(defined $id);
149 my $mods_list = _records_to_mods( $id );
150 my $mods_obj = $mods_list->[0];
155 # Returns the number of copies attached to a record based on org location
156 __PACKAGE__->register_method(
157 method => "record_id_to_copy_count",
158 api_name => "open-ils.search.biblio.record.copy_count",
161 __PACKAGE__->register_method(
162 method => "record_id_to_copy_count",
163 api_name => "open-ils.search.biblio.metarecord.copy_count",
165 sub record_id_to_copy_count {
166 my( $self, $client, $org_id, $record_id ) = @_;
168 my $method = "open-ils.storage.biblio.record_entry.copy_count.atomic";
170 if($self->api_name =~ /metarecord/) {
171 $method = "open-ils.storage.metabib.metarecord.copy_count.atomic";
175 my $session = OpenSRF::AppSession->create("open-ils.storage");
176 warn "copy_count retrieve $record_id\n";
177 return undef unless(defined $record_id);
179 my $request = $session->request(
180 $method, org_unit => $org_id => $key => $record_id );
183 my $count = $request->gather(1);
184 $session->disconnect();
185 return [ sort { $a->{depth} <=> $b->{depth} } @$count ];
190 # used for cat search classes
191 my $cat_search_hash = {
194 { tag => "100", subfield => "a"} ,
195 { tag => "700", subfield => "a"},
199 { tag => "245", subfield => "a"},
200 { tag => "242", subfield => "a"},
201 { tag => "240", subfield => "a"},
202 { tag => "210", subfield => "a"},
206 { tag => "650", subfield => "_" },
210 { tag => "035", subfield => "_" },
214 { tag => "020", subfield => "a" },
220 __PACKAGE__->register_method(
221 method => "biblio_search_tcn",
222 api_name => "open-ils.search.biblio.tcn",
224 note => "Retrieve a record by TCN",
227 sub biblio_search_tcn {
229 my( $self, $client, $tcn ) = @_;
231 $tcn =~ s/.*?(\w+)\s*$/$1/o;
232 warn "Searching TCN $tcn\n";
234 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
235 my $request = $session->request(
236 "open-ils.storage.direct.biblio.record_entry.search.tcn_value", $tcn );
237 my $response = $request->recv();
240 unless ($response) { return []; }
242 if(UNIVERSAL::isa($response,"OpenSRF::EX")) {
243 warn "Received exception for tcn search\n";
244 throw $response ($response->stringify);
247 my $record_entry = $response->content;
249 for my $record (@$record_entry) {
250 push @ids, $record->id;
253 warn "received ID's for tcn search @ids\n";
256 return { count => $size, ids => \@ids };
261 # --------------------------------------------------------------------------------
264 __PACKAGE__->register_method(
265 method => "biblio_search_isbn",
266 api_name => "open-ils.search.biblio.isbn",
269 sub biblio_search_isbn {
270 my( $self, $client, $isbn ) = @_;
271 throw OpenSRF::EX::InvalidArg
273 ("biblio_search_isbn needs an ISBN to search")
274 unless defined $isbn;
276 warn "biblio search for ISBN $isbn\n";
277 my $method = $self->method_lookup("open-ils.search.biblio.marc");
278 my ($records) = $method->run( $cat_search_hash->{isbn}, $isbn );
280 my $size = @$records;
281 return { count => $size, ids => $records };
286 # --------------------------------------------------------------------------------
288 __PACKAGE__->register_method(
289 method => "biblio_barcode_to_copy",
290 api_name => "open-ils.search.asset.copy.find_by_barcode",
293 # turns a barcode into a copy object
294 sub biblio_barcode_to_copy {
295 my( $self, $client, $barcode ) = @_;
297 throw OpenSRF::EX::InvalidArg
298 ("search.biblio.barcode needs a barcode to search")
299 unless defined $barcode;
301 warn "copy search for barcode $barcode\n";
302 my $record = OpenILS::Application::AppUtils->simple_scalar_request(
304 "open-ils.storage.direct.asset.copy.search.barcode",
307 return undef unless($record);
312 __PACKAGE__->register_method(
313 method => "biblio_id_to_copy",
314 api_name => "open-ils.search.asset.copy.batch.retrieve",
317 # turns a barcode into a copy object
318 sub biblio_id_to_copy {
319 my( $self, $client, $ids ) = @_;
321 throw OpenSRF::EX::InvalidArg
322 ("search.biblio.batch.retrieve needs a id to search")
325 warn "copy search for ids @$ids\n";
326 my $record = OpenILS::Application::AppUtils->simple_scalar_request(
328 "open-ils.storage.direct.asset.copy.batch.retrieve.atomic",
336 __PACKAGE__->register_method(
337 method => "fleshed_copy_retrieve",
338 api_name => "open-ils.search.asset.copy.fleshed.batch.retrieve",
341 # turns a barcode into a copy object
342 sub fleshed_copy_retrieve {
343 my( $self, $client, $ids ) = @_;
345 throw OpenSRF::EX::InvalidArg
346 ("search.biblio.batch.retrieve needs a id to search")
349 warn "fleshed copy search for id @$ids\n";
350 my $copy = OpenILS::Application::AppUtils->simple_scalar_request(
352 "open-ils.storage.fleshed.asset.copy.batch.retrieve.atomic",
360 __PACKAGE__->register_method(
361 method => "biblio_barcode_to_title",
362 api_name => "open-ils.search.biblio.find_by_barcode",
365 sub biblio_barcode_to_title {
366 my( $self, $client, $barcode ) = @_;
369 throw OpenSRF::EX::ERROR
370 ("Not enough args to find_by_barcode");
373 my $title = $apputils->simple_scalar_request(
375 "open-ils.storage.biblio.record_entry.retrieve_by_barcode",
378 return { ids => $title->id, count => 1 };
381 my $u = OpenILS::Utils::ModsParser->new();
382 $u->start_mods_batch( $title->marc );
383 my $mods = $u->finish_mods_batch();
384 $mods->doc_id($title->id());
391 __PACKAGE__->register_method(
392 method => "biblio_copy_to_mods",
393 api_name => "open-ils.search.biblio.copy.mods.retrieve",
396 # takes a copy object and returns it fleshed mods object
397 sub biblio_copy_to_mods {
398 my( $self, $client, $copy ) = @_;
400 throw OpenSRF::EX::InvalidArgs
401 ("copy.mods.retrieve needs a copy") unless( $copy );
403 new Fieldmapper::asset::copy($copy);
405 my $volume = OpenILS::Application::AppUtils->simple_scalar_request(
407 "open-ils.storage.direct.asset.call_number.retrieve",
408 $copy->call_number() );
410 my $mods = _records_to_mods($volume->record());
411 $mods = shift @$mods;
412 $volume->copies([$copy]);
413 push @{$mods->call_numbers()}, $volume;
419 sub barcode_to_mods {
424 # --------------------------------------------------------------------------------
426 __PACKAGE__->register_method(
427 method => "cat_biblio_search_class",
428 api_name => "open-ils.search.cat.biblio.class",
430 note => "Searches biblio information by search class",
433 sub cat_biblio_search_class {
435 my( $self, $client, $org_id, $class, $sort, $string ) = @_;
437 throw OpenSRF::EX::InvalidArg
438 ("Not enough args to open-ils.search.cat.biblio.class")
439 unless( defined($org_id) and $class and $sort and $string );
444 my $method = $self->method_lookup("open-ils.search.biblio.marc");
446 throw OpenSRF::EX::PANIC
447 ("Can't lookup method 'open-ils.search.biblio.marc'");
450 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
453 for my $i (@$records) { push @ids, $i->[0]; }
455 my $mods_list = _records_to_mods( @ids );
456 return undef unless (ref($mods_list) eq "ARRAY");
458 # ---------------------------------------------------------------
459 # append copy count information to the mods objects
460 my $session = OpenSRF::AppSession->create("open-ils.storage");
462 my $request = $session->request(
463 "open-ils.storage.direct.biblio.record_copy_count.batch", $org_id, @ids );
467 warn "receiving copy counts for doc $id\n";
469 my $response = $request->recv();
470 next unless $response;
472 if( $response and UNIVERSAL::isa($response, "Error")) {
473 throw $response ($response->stringify);
476 my $count = $response->content;
477 my $mods_obj = undef;
478 for my $m (@$mods_list) {
479 $mods_obj = $m if ($m->doc_id() == $id)
482 $mods_obj->copy_count($count);
485 $client->respond( $mods_obj );
491 $session->disconnect();
493 # ---------------------------------------------------------------
500 __PACKAGE__->register_method(
501 method => "cat_biblio_search_class_id",
502 api_name => "open-ils.search.cat.biblio.class.id",
504 note => "Searches biblio information by search class and returns the IDs",
507 sub cat_biblio_search_class_id {
509 my( $self, $client, $org_id, $class, $string, $limit, $offset ) = @_;
516 $string = OpenILS::Application::Search->filter_search($string);
517 if(!$string) { return undef; }
519 warn "Searching cat.biblio.class.id string: $string offset: $offset limit: $limit\n";
521 throw OpenSRF::EX::InvalidArg
522 ("Not enough args to open-ils.search.cat.biblio.class")
523 unless( defined($org_id) and $class and $string );
528 my $cache_key = md5_hex( $org_id . $class . $string );
529 my $id_array = OpenILS::Application::SearchCache->get_cache($cache_key);
532 warn "Returning class search from cache\n";
533 my $size = @$id_array;
534 my @ids = @$id_array[ $offset..($offset+$limit) ];
535 return { count => $size, ids => \@ids };
538 my $method = $self->method_lookup("open-ils.search.biblio.marc");
540 throw OpenSRF::EX::PANIC
541 ("Can't lookup method 'open-ils.search.biblio.marc'");
544 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
548 for my $i (@$records) {
549 if(defined($i->[0])) {
550 push @cache_ids, $i->[0];
554 my @ids = @cache_ids[ $offset..($offset+$limit) ];
555 my $size = @$records;
557 OpenILS::Application::SearchCache->put_cache(
558 $cache_key, \@cache_ids, $size );
560 return { count =>$size, ids => \@ids };
565 __PACKAGE__->register_method(
566 method => "biblio_search_class",
567 api_name => "open-ils.search.biblio.class",
569 note => "Searches biblio information by search class and returns the IDs",
572 sub biblio_search_class {
574 my( $self, $client, $class, $string,
575 $org_id, $org_type, $limit, $offset ) = @_;
577 warn "org: $org_id : depth: $org_type : limit: $limit : offset: $offset\n";
580 $limit = 100 unless defined($limit and $limit > 0 );
581 $org_id = "1" unless defined($org_id); # xxx
582 $org_type = 0 unless defined($org_type);
584 warn "Searching biblio.class.id\n" .
586 "\noffset: $offset\n" .
588 "org_id: $org_id\n" .
589 "depth: $org_type\n" ;
591 $string = OpenILS::Application::Search->filter_search($string);
592 if(!$string) { return undef; }
594 if( !defined($org_id) or !$class or !$string ) {
595 warn "not enbough args to metarecord search\n";
596 throw OpenSRF::EX::InvalidArg
597 ("Not enough args to open-ils.search.cat.biblio.class")
602 if( ($class ne "title") and ($class ne "author") and
603 ($class ne "subject") and ($class ne "keyword") ) {
604 warn "Invalid search class: $class\n";
605 throw OpenSRF::EX::InvalidArg ("Not a valid search class: $class")
608 # grab the mr id's from storage
610 my $method = "open-ils.storage.metabib.$class.search_fts.metarecord_count";
611 warn "Performing count method $method\n";
612 my $session = OpenSRF::AppSession->create('open-ils.storage');
614 my $request = $session->request( $method,
619 my $response = $request->recv();
621 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
622 throw $response ($response->stringify);
625 my $count = $response->content;
626 warn "Received count $count\n";
627 # XXX check count size and respond accordingly
630 $request = $session->request(
631 #"open-ils.storage.cachable.metabib.$class.search_fts.metarecord.atomic",
632 "open-ils.storage.cachable.metabib.$class.search_fts.metarecord.atomic",
640 my $records = $request->gather(1);
644 warn Dumper $records;
646 # if we just get one, it won't be wrapped in an array
647 if(!ref($records->[0])) {
648 $records = [$records];
651 for my $i (@$records) {
657 #my @ids = @all_ids[ $offset..($offset+$limit) ];
659 @ids = grep { defined($_->[0]) } @ids;
662 $session->disconnect();
664 return { count =>$count, ids => \@ids };
671 __PACKAGE__->register_method(
672 method => "biblio_mrid_to_modsbatch",
673 api_name => "open-ils.search.biblio.metarecord.mods_slim.retrieve",
676 sub biblio_mrid_to_modsbatch {
677 my( $self, $client, $mrid ) = @_;
679 throw OpenSRF::EX::InvalidArg
680 ("search.biblio.metarecord_to_mods requires mr id")
681 unless defined( $mrid );
684 my $metarecord = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
685 "open-ils.storage.direct.metabib.metarecord.retrieve", $mrid );
688 throw OpenSRF::EX::ERROR ("No metarecord exists with the given id: $mrid");
691 my $master_id = $metarecord->master_record();
694 # check for existing mods
695 if($metarecord->mods()){
696 warn "We already have mods for " . $metarecord->id . "\n";
697 my $perl = JSON->JSON2perl($metarecord->mods());
698 return Fieldmapper::metabib::virtual_record->new($perl);
703 warn "Creating mods batch for metarecord $mrid\n";
704 my $id_hash = biblio_mrid_to_record_ids( undef, undef, $mrid );
705 my @ids = @{$id_hash->{ids}};
707 if(@ids < 1) { return undef; }
709 warn "Master ID is $master_id\n";
710 # grab the master record to start the mods batch
712 my $record = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
713 "open-ils.storage.direct.biblio.record_entry.retrieve", $master_id );
716 throw OpenSRF::EX::ERROR
717 ("No record returned with id $master_id");
720 my $u = OpenILS::Utils::ModsParser->new();
722 $u->start_mods_batch( $record->marc );
723 my $main_doc_id = $record->id();
725 @ids = grep { $_ ne $master_id } @ids;
727 # now we have to collect all of the marc objects and push them into a mods batch
728 my $session = OpenSRF::AppSession->create("open-ils.storage");
729 my $request = $session->request(
730 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
732 while( my $response = $request->recv() ) {
734 next unless $response;
735 if(UNIVERSAL::isa( $response,"OpenSRF::EX")) {
736 throw $response ($response->stringify);
739 my $content = $response->content;
742 $u->push_mods_batch( $content->marc );
746 my $mods = $u->finish_mods_batch();
747 $mods->doc_id($mrid);
750 $client->respond_complete($mods);
752 my $mods_string = JSON->perl2JSON($mods->decast);
754 $metarecord->mods($mods_string);
756 my $req = $session->request(
757 "open-ils.storage.direct.metabib.metarecord.update",
764 $session->disconnect();
772 # converts a mr id into a list of record ids
774 __PACKAGE__->register_method(
775 method => "biblio_mrid_to_record_ids",
776 api_name => "open-ils.search.biblio.metarecord_to_records",
779 sub biblio_mrid_to_record_ids {
780 my( $self, $client, $mrid ) = @_;
782 throw OpenSRF::EX::InvalidArg
783 ("search.biblio.metarecord_to_record_ids requires mr id")
784 unless defined( $mrid );
786 warn "Searching for record for MR $mrid\n";
788 my $mrmaps = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
789 "open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord", $mrid );
792 for my $map (@$mrmaps) { push @ids, $map->source(); }
794 warn "Recovered id's [@ids] for mr $mrid\n";
798 return { count => $size, ids => \@ids };
804 __PACKAGE__->register_method(
805 method => "biblio_record_to_marc_html",
806 api_name => "open-ils.search.biblio.record.html" );
808 my $parser = XML::LibXML->new();
809 my $xslt = XML::LibXSLT->new();
812 my $settings_client = OpenSRF::Utils::SettingsClient->new();
813 sub biblio_record_to_marc_html {
814 my( $self, $client, $recordid ) = @_;
817 my $dir = $settings_client->config_value( "dirs", "xsl" );
818 my $xsl = $settings_client->config_value(
819 "apps", "open-ils.search", "app_settings", "marc_html_xsl" );
821 $xsl = $parser->parse_file("$dir/$xsl");
822 $marc_sheet = $xslt->parse_stylesheet( $xsl );
826 my $record = $apputils->simple_scalar_request(
828 "open-ils.storage.direct.biblio.record_entry.retrieve",
831 my $xmldoc = $parser->parse_string($record->marc);
832 my $html = $marc_sheet->transform($xmldoc);
833 $html = $html->toString();
840 __PACKAGE__->register_method(
841 method => "retrieve_all_copy_locations",
842 api_name => "open-ils.search.config.copy_location.retrieve.all" );
844 my $shelving_locations;
845 sub retrieve_all_copy_locations {
846 my( $self, $client ) = @_;
847 if(!$shelving_locations) {
848 $shelving_locations = $apputils->simple_scalar_request(
850 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
852 return $shelving_locations;
857 __PACKAGE__->register_method(
858 method => "retrieve_all_copy_statuses",
859 api_name => "open-ils.search.config.copy_status.retrieve.all" );
862 sub retrieve_all_copy_statuses {
863 my( $self, $client ) = @_;
864 if(!$copy_statuses) {
865 $copy_statuses = $apputils->simple_scalar_request(
867 "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
869 return $copy_statuses;