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);
18 # Houses biblio search utilites
20 __PACKAGE__->register_method(
21 method => "biblio_search_marc",
22 api_name => "open-ils.search.biblio.marc",
24 note => "Searches biblio information by marc tag",
27 sub biblio_search_marc {
29 my( $self, $client, $search_hash, $string ) = @_;
31 warn "Building biblio marc session\n";
32 my $session = OpenSRF::AppSession->create("open-ils.storage");
34 warn "Sending biblio marc request\n";
35 my $request = $session->request(
36 #"open-ils.storage.cachable.direct.metabib.full_rec.search_fts.index_vector",
37 "open-ils.storage.direct.metabib.full_rec.search_fts.index_vector",
38 restrict => $search_hash,
41 warn "Waiting complete\n";
42 $request->wait_complete();
44 warn "Calling recv\n";
45 my $response = $request->recv(20);
48 if($response and UNIVERSAL::isa($response,"OpenSRF::EX")) {
49 throw $response ($response->stringify);
54 if($response and UNIVERSAL::can($response,"content")) {
55 $data = $response->content;
57 warn "finishing request\n";
61 $session->disconnect();
69 # ---------------------------------------------------------------------------
70 # takes a list of record id's and turns the docs into friendly
71 # mods structures. Creates one MODS structure for each doc id.
72 # ---------------------------------------------------------------------------
73 sub _records_to_mods {
79 my $session = OpenSRF::AppSession->create("open-ils.storage");
80 my $request = $session->request(
81 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
83 my $last_content = undef;
85 while( my $response = $request->recv() ) {
88 my $u = OpenILS::Utils::ModsParser->new();
89 $u->start_mods_batch( $last_content->marc );
90 my $mods = $u->finish_mods_batch();
91 $mods->doc_id($last_content->id());
92 warn "Turning doc " . $mods->doc_id() . " into MODS\n";
93 $last_content = undef;
97 next unless $response;
99 if($response->isa("OpenSRF::EX")) {
100 throw $response ($response->stringify);
103 $last_content = $response->content;
107 if( $last_content ) {
108 my $u = OpenILS::Utils::ModsParser->new();
109 $u->start_mods_batch( $last_content->marc );
110 my $mods = $u->finish_mods_batch();
111 $mods->doc_id($last_content->id());
112 push @results, $mods;
117 $session->disconnect();
123 __PACKAGE__->register_method(
124 method => "record_id_to_mods",
125 api_name => "open-ils.search.biblio.record.mods.retrieve",
127 note => "Provide ID, we provide the mods"
130 # converts a record into a mods object with copy counts attached
131 sub record_id_to_mods {
133 my( $self, $client, $org_id, $id ) = @_;
135 my $mods_list = _records_to_mods( $id );
136 my $mods_obj = $mods_list->[0];
137 my $cmethod = $self->method_lookup(
138 "open-ils.search.biblio.record.copy_count");
139 my ($count) = $cmethod->run($org_id, $id);
140 $mods_obj->copy_count($count);
146 __PACKAGE__->register_method(
147 method => "record_id_to_mods_slim",
148 api_name => "open-ils.search.biblio.record.mods_slim.retrieve",
150 note => "Provide ID, we provide the mods"
153 # converts a record into a mods object with NO copy counts attached
154 sub record_id_to_mods_slim {
156 my( $self, $client, $id ) = @_;
157 warn "Retrieving MODS object for record $id\n";
158 return undef unless(defined $id);
160 my $mods_list = _records_to_mods( $id );
161 my $mods_obj = $mods_list->[0];
166 # Returns the number of copies attached to a record based on org location
167 __PACKAGE__->register_method(
168 method => "record_id_to_copy_count",
169 api_name => "open-ils.search.biblio.record.copy_count",
172 __PACKAGE__->register_method(
173 method => "record_id_to_copy_count",
174 api_name => "open-ils.search.biblio.metarecord.copy_count",
176 sub record_id_to_copy_count {
177 my( $self, $client, $org_id, $record_id ) = @_;
179 my $method = "open-ils.storage.biblio.record_entry.copy_count.atomic";
181 if($self->api_name =~ /metarecord/) {
182 $method = "open-ils.storage.metabib.metarecord.copy_count.atomic";
186 my $session = OpenSRF::AppSession->create("open-ils.storage");
187 warn "copy_count retrieve $record_id\n";
188 return undef unless(defined $record_id);
190 my $request = $session->request(
191 $method, org_unit => $org_id => $key => $record_id );
193 warn "copy_count wait $record_id\n";
195 my $count = $request->gather(1);
196 $session->disconnect();
197 return [ sort { $a->{depth} <=> $b->{depth} } @$count ];
202 # used for cat search classes
203 my $cat_search_hash = {
206 { tag => "100", subfield => "a"} ,
207 { tag => "700", subfield => "a"},
211 { tag => "245", subfield => "a"},
212 { tag => "242", subfield => "a"},
213 { tag => "240", subfield => "a"},
214 { tag => "210", subfield => "a"},
218 { tag => "650", subfield => "_" },
222 { tag => "035", subfield => "_" },
226 { tag => "020", subfield => "a" },
232 __PACKAGE__->register_method(
233 method => "biblio_search_tcn",
234 api_name => "open-ils.search.biblio.tcn",
236 note => "Retrieve a record by TCN",
239 sub biblio_search_tcn {
241 my( $self, $client, $tcn ) = @_;
243 $tcn =~ s/.*?(\w+)\s*$/$1/o;
244 warn "Searching TCN $tcn\n";
246 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
247 my $request = $session->request(
248 "open-ils.storage.direct.biblio.record_entry.search.tcn_value", $tcn );
249 warn "tcn going into recv\n";
250 my $response = $request->recv();
253 unless ($response) { return []; }
255 if(UNIVERSAL::isa($response,"OpenSRF::EX")) {
256 warn "Received exception for tcn search\n";
257 throw $response ($response->stringify);
260 my $record_entry = $response->content;
262 for my $record (@$record_entry) {
263 push @ids, $record->id;
266 warn "received ID's for tcn search @ids\n";
269 return { count => $size, ids => \@ids };
274 # --------------------------------------------------------------------------------
277 __PACKAGE__->register_method(
278 method => "biblio_search_isbn",
279 api_name => "open-ils.search.biblio.isbn",
282 sub biblio_search_isbn {
283 my( $self, $client, $isbn ) = @_;
284 throw OpenSRF::EX::InvalidArg
286 ("biblio_search_isbn needs an ISBN to search")
287 unless defined $isbn;
289 warn "biblio search for ISBN $isbn\n";
290 my $method = $self->method_lookup("open-ils.search.biblio.marc");
291 my ($records) = $method->run( $cat_search_hash->{isbn}, $isbn );
293 for my $i (@$records) {
294 if( ref($i) and defined($i->[0])) {
300 return { count => $size, ids => \@ids };
305 # --------------------------------------------------------------------------------
307 __PACKAGE__->register_method(
308 method => "biblio_barcode_to_copy",
309 api_name => "open-ils.search.biblio.barcode",
312 # turns a barcode into a copy object
313 sub biblio_barcode_to_copy {
314 my( $self, $client, $barcode ) = @_;
316 throw OpenSRF::EX::InvalidArg
317 ("search.biblio.barcode needs an ISBN to search")
318 unless defined $barcode;
320 warn "copy search for ISBN $barcode\n";
321 my $record = OpenILS::Application::AppUtils->simple_scalar_request(
323 "open-ils.storage.direct.asset.copy.search.barcode",
326 return undef unless($record);
332 __PACKAGE__->register_method(
333 method => "biblio_copy_to_mods",
334 api_name => "open-ils.search.biblio.copy.mods.retrieve",
337 # takes a copy object and returns it fleshed mods object
338 sub biblio_copy_to_mods {
339 my( $self, $client, $copy ) = @_;
341 throw OpenSRF::EX::InvalidArgs
342 ("copy.mods.retrieve needs a copy") unless( $copy );
344 new Fieldmapper::asset::copy($copy);
346 my $volume = OpenILS::Application::AppUtils->simple_scalar_request(
348 "open-ils.storage.direct.asset.call_number.retrieve",
349 $copy->call_number() );
351 my $mods = _records_to_mods($volume->record());
352 $mods = shift @$mods;
353 $volume->copies([$copy]);
354 push @{$mods->call_numbers()}, $volume;
360 sub barcode_to_mods {
365 # --------------------------------------------------------------------------------
367 __PACKAGE__->register_method(
368 method => "cat_biblio_search_class",
369 api_name => "open-ils.search.cat.biblio.class",
371 note => "Searches biblio information by search class",
374 sub cat_biblio_search_class {
376 my( $self, $client, $org_id, $class, $sort, $string ) = @_;
378 throw OpenSRF::EX::InvalidArg
379 ("Not enough args to open-ils.search.cat.biblio.class")
380 unless( defined($org_id) and $class and $sort and $string );
385 my $method = $self->method_lookup("open-ils.search.biblio.marc");
387 throw OpenSRF::EX::PANIC
388 ("Can't lookup method 'open-ils.search.biblio.marc'");
391 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
394 for my $i (@$records) { push @ids, $i->[0]; }
396 my $mods_list = _records_to_mods( @ids );
397 return undef unless (ref($mods_list) eq "ARRAY");
399 # ---------------------------------------------------------------
400 # append copy count information to the mods objects
401 my $session = OpenSRF::AppSession->create("open-ils.storage");
403 my $request = $session->request(
404 "open-ils.storage.direct.biblio.record_copy_count.batch", $org_id, @ids );
408 warn "receiving copy counts for doc $id\n";
410 my $response = $request->recv();
411 next unless $response;
413 if( $response and UNIVERSAL::isa($response, "Error")) {
414 throw $response ($response->stringify);
417 my $count = $response->content;
418 my $mods_obj = undef;
419 for my $m (@$mods_list) {
420 $mods_obj = $m if ($m->doc_id() == $id)
423 $mods_obj->copy_count($count);
426 $client->respond( $mods_obj );
432 $session->disconnect();
434 # ---------------------------------------------------------------
441 __PACKAGE__->register_method(
442 method => "cat_biblio_search_class_id",
443 api_name => "open-ils.search.cat.biblio.class.id",
445 note => "Searches biblio information by search class and returns the IDs",
448 sub cat_biblio_search_class_id {
450 my( $self, $client, $org_id, $class, $string, $limit, $offset ) = @_;
457 $string = OpenILS::Application::Search->filter_search($string);
458 if(!$string) { return undef; }
460 warn "Searching cat.biblio.class.id string: $string offset: $offset limit: $limit\n";
462 throw OpenSRF::EX::InvalidArg
463 ("Not enough args to open-ils.search.cat.biblio.class")
464 unless( defined($org_id) and $class and $string );
469 my $cache_key = md5_hex( $org_id . $class . $string );
470 my $id_array = OpenILS::Application::SearchCache->get_cache($cache_key);
473 warn "Return search from cache\n";
474 my $size = @$id_array;
475 my @ids = @$id_array[ $offset..($offset+$limit) ];
476 warn "Returning cat.biblio.class.id $string\n";
477 return { count => $size, ids => \@ids };
480 my $method = $self->method_lookup("open-ils.search.biblio.marc");
482 throw OpenSRF::EX::PANIC
483 ("Can't lookup method 'open-ils.search.biblio.marc'");
486 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
490 for my $i (@$records) {
491 if(defined($i->[0])) {
492 push @cache_ids, $i->[0];
496 my @ids = @cache_ids[ $offset..($offset+$limit) ];
497 my $size = @$records;
499 OpenILS::Application::SearchCache->put_cache(
500 $cache_key, \@cache_ids, $size );
502 warn "Returning cat.biblio.class.id $string\n";
503 return { count =>$size, ids => \@ids };
508 __PACKAGE__->register_method(
509 method => "biblio_search_class",
510 api_name => "open-ils.search.biblio.class",
512 note => "Searches biblio information by search class and returns the IDs",
515 sub biblio_search_class {
517 my( $self, $client, $class, $string,
518 $org_id, $org_type, $limit, $offset ) = @_;
520 warn "$org_id : $org_type : $limit : $offset\n";
523 $limit = 100 unless defined($limit and $limit > 0 );
524 $org_id = "1" unless defined($org_id); # xxx
525 $org_type = 0 unless defined($org_type);
527 warn "$org_id : $org_type : $limit : $offset\n";
528 warn "Searching biblio.class.id\n" .
530 "\noffset: $offset\n" .
532 "org_id: $org_id\n" .
533 "depth: $org_type\n" ;
535 $string = OpenILS::Application::Search->filter_search($string);
536 if(!$string) { return undef; }
538 if( !defined($org_id) or !$class or !$string ) {
539 warn "not enbough args to metarecord search\n";
540 throw OpenSRF::EX::InvalidArg
541 ("Not enough args to open-ils.search.cat.biblio.class")
546 if( ($class ne "title") and ($class ne "author") and
547 ($class ne "subject") and ($class ne "keyword") ) {
548 warn "Invalid search class: $class\n";
549 throw OpenSRF::EX::InvalidArg ("Not a valid search class: $class")
552 # grab the mr id's from storage
554 my $method = "open-ils.storage.metabib.$class.search_fts.metarecord_count";
555 warn "Performing count method $method\n";
556 my $session = OpenSRF::AppSession->create('open-ils.storage');
558 my $request = $session->request( $method,
563 my $response = $request->recv();
565 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
566 throw $response ($response->stringify);
569 my $count = $response->content;
570 warn "Received count $count\n";
571 # XXX check count size and respond accordingly
574 warn "performing mr search\n";
575 $request = $session->request(
576 #"open-ils.storage.cachable.metabib.$class.search_fts.metarecord.atomic",
577 "open-ils.storage.cachable.metabib.$class.search_fts.metarecord.atomic",
586 $response = $request->recv();
588 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
589 warn "Recieved Exception from storage: " . $response->stringify . "\n";
590 $response->{'msg'} = $response->stringify();
591 throw $response ($response->stringify);
596 my $records = $response->content;
598 warn Dumper $records;
602 # if we just get one, it won't be wrapped in an array
603 if(!ref($records->[0])) {
604 $records = [$records];
607 for my $i (@$records) {
608 if(defined($i->[0])) {
609 push @all_ids, $i->[0];
613 #my @ids = @all_ids[ $offset..($offset+$limit) ];
615 @ids = grep { defined($_) } @ids;
619 $session->disconnect();
621 warn "Returning biblio.class $string\n";
622 return { count =>$count, ids => \@ids };
629 __PACKAGE__->register_method(
630 method => "biblio_mrid_to_modsbatch",
631 api_name => "open-ils.search.biblio.metarecord.mods_slim.retrieve",
634 sub biblio_mrid_to_modsbatch {
635 my( $self, $client, $mrid ) = @_;
637 throw OpenSRF::EX::InvalidArg
638 ("search.biblio.metarecord_to_mods requires mr id")
639 unless defined( $mrid );
642 my $metarecord = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
643 "open-ils.storage.direct.metabib.metarecord.retrieve", $mrid );
646 throw OpenSRF::EX::ERROR ("No metarecord exists with the given id: $mrid");
649 my $master_id = $metarecord->master_record();
652 # check for existing mods
653 if($metarecord->mods()){
654 warn "We already have mods for " . $metarecord->id . "\n";
655 my $perl = JSON->JSON2perl($metarecord->mods());
658 return Fieldmapper::metabib::virtual_record->new($perl);
663 warn "Creating mods batch for metarecord $mrid\n";
664 my $id_hash = biblio_mrid_to_record_ids( undef, undef, $mrid );
665 my @ids = @{$id_hash->{ids}};
667 if(@ids < 1) { return undef; }
669 warn "Master ID is $master_id\n";
670 # grab the master record to start the mods batch
672 my $record = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
673 "open-ils.storage.direct.biblio.record_entry.retrieve", $master_id );
676 throw OpenSRF::EX::ERROR
677 ("No record returned with id $master_id");
680 my $u = OpenILS::Utils::ModsParser->new();
681 warn "marc:\n" . $record->marc;
682 $u->start_mods_batch( $record->marc );
683 my $main_doc_id = $record->id();
685 @ids = grep { $_ ne $master_id } @ids;
687 warn "NON-Master IDs are @ids\n";
689 # now we have to collect all of the marc objects and push them into a mods batch
690 my $session = OpenSRF::AppSession->create("open-ils.storage");
691 my $request = $session->request(
692 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
694 while( my $response = $request->recv() ) {
696 next unless $response;
697 if(UNIVERSAL::isa( $response,"OpenSRF::EX")) {
698 throw $response ($response->stringify);
701 my $content = $response->content;
704 $u->push_mods_batch( $content->marc );
708 my $mods = $u->finish_mods_batch();
709 $mods->doc_id($mrid);
712 $client->respond_complete($mods);
714 my $mods_string = JSON->perl2JSON($mods->decast);
715 warn "Mods String to DB:\n $mods_string\n";
717 $metarecord->mods($mods_string);
719 my $req = $session->request(
720 "open-ils.storage.direct.metabib.metarecord.update",
727 $session->disconnect();
735 # converts a mr id into a list of record ids
737 __PACKAGE__->register_method(
738 method => "biblio_mrid_to_record_ids",
739 api_name => "open-ils.search.biblio.metarecord_to_records",
742 sub biblio_mrid_to_record_ids {
743 my( $self, $client, $mrid ) = @_;
745 throw OpenSRF::EX::InvalidArg
746 ("search.biblio.metarecord_to_record_ids requires mr id")
747 unless defined( $mrid );
749 warn "Searching for record for MR $mrid\n";
751 my $mrmaps = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
752 "open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord", $mrid );
755 for my $map (@$mrmaps) { push @ids, $map->source(); }
757 warn "Recovered id's [@ids] for mr $mrid\n";
760 warn "Size: $size\n";
762 return { count => $size, ids => \@ids };