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 restrict => $search_hash,
40 warn "Waiting complete\n";
41 $request->wait_complete();
43 warn "Calling recv\n";
44 my $response = $request->recv(20);
47 if($response and UNIVERSAL::isa($response,"OpenSRF::EX")) {
48 throw $response ($response->stringify);
53 if($response and UNIVERSAL::can($response,"content")) {
54 $data = $response->content;
56 warn "finishing request\n";
60 $session->disconnect();
68 # ---------------------------------------------------------------------------
69 # takes a list of record id's and turns the docs into friendly
70 # mods structures. Creates one MODS structure for each doc id.
71 # ---------------------------------------------------------------------------
72 sub _records_to_mods {
78 my $session = OpenSRF::AppSession->create("open-ils.storage");
79 my $request = $session->request(
80 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
82 my $last_content = undef;
84 while( my $response = $request->recv() ) {
87 my $u = OpenILS::Utils::ModsParser->new();
88 $u->start_mods_batch( $last_content->marc );
89 my $mods = $u->finish_mods_batch();
90 $mods->{doc_id} = $last_content->id();
91 warn "Turning doc " . $mods->{doc_id} . " into MODS\n";
92 $last_content = undef;
96 next unless $response;
98 if($response->isa("OpenSRF::EX")) {
99 throw $response ($response->stringify);
102 $last_content = $response->content;
106 if( $last_content ) {
107 my $u = OpenILS::Utils::ModsParser->new();
108 $u->start_mods_batch( $last_content->marc );
109 my $mods = $u->finish_mods_batch();
110 $mods->{doc_id} = $last_content->id();
111 push @results, $mods;
116 $session->disconnect();
122 __PACKAGE__->register_method(
123 method => "record_id_to_mods",
124 api_name => "open-ils.search.biblio.record.mods.retrieve",
126 note => "Provide ID, we provide the mods"
129 # converts a record into a mods object with copy counts attached
130 sub record_id_to_mods {
132 my( $self, $client, $org_id, $id ) = @_;
134 my $mods_list = _records_to_mods( $id );
135 my $mods_obj = $mods_list->[0];
136 my $cmethod = $self->method_lookup(
137 "open-ils.search.biblio.record.copy_count");
138 my ($count) = $cmethod->run($org_id, $id);
139 $mods_obj->{copy_count} = $count;
145 __PACKAGE__->register_method(
146 method => "record_id_to_mods_slim",
147 api_name => "open-ils.search.biblio.record.mods_slim.retrieve",
149 note => "Provide ID, we provide the mods"
152 # converts a record into a mods object with NO copy counts attached
153 sub record_id_to_mods_slim {
155 my( $self, $client, $id ) = @_;
156 warn "Retrieving MODS object for record $id\n";
157 return undef unless(defined $id);
159 my $mods_list = _records_to_mods( $id );
160 my $mods_obj = $mods_list->[0];
165 # Returns the number of copies attached to a record based on org location
166 __PACKAGE__->register_method(
167 method => "record_id_to_copy_count",
168 api_name => "open-ils.search.biblio.record.copy_count",
170 note => "Provide ID, we provide the copy count"
173 sub record_id_to_copy_count {
174 my( $self, $client, $org_id, $record_id ) = @_;
176 my $session = OpenSRF::AppSession->create("open-ils.storage");
177 warn "copy_count retrieve $record_id\n";
178 return undef unless(defined $record_id);
180 my $request = $session->request(
181 "open-ils.storage.direct.biblio.record_copy_count", $org_id, $record_id );
183 warn "copy_count wait $record_id\n";
184 $request->wait_complete;
186 warn "copy_count recv $record_id\n";
187 my $response = $request->recv();
188 return undef unless $response;
190 warn "copy_count after recv $record_id\n";
192 if( $response and UNIVERSAL::isa($response, "Error")) {
193 throw $response ($response->stringify);
196 my $count = $response->content;
200 $session->disconnect();
206 # used for cat search classes
207 my $cat_search_hash = {
210 { tag => "100", subfield => "a"} ,
211 { tag => "700", subfield => "a"},
215 { tag => "245", subfield => "a"},
216 { tag => "242", subfield => "a"},
217 { tag => "240", subfield => "a"},
218 { tag => "210", subfield => "a"},
222 { tag => "650", subfield => "_" },
226 { tag => "035", subfield => "_" },
230 { tag => "020", subfield => "a" },
236 __PACKAGE__->register_method(
237 method => "biblio_search_tcn",
238 api_name => "open-ils.search.biblio.tcn",
240 note => "Retrieve a record by TCN",
243 sub biblio_search_tcn {
245 my( $self, $client, $tcn ) = @_;
247 $tcn =~ s/.*?(\w+)\s*$/$1/o;
248 warn "Searching TCN $tcn\n";
250 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
251 my $request = $session->request(
252 "open-ils.storage.direct.biblio.record_entry.search.tcn_value", $tcn );
253 warn "tcn going into recv\n";
254 my $response = $request->recv();
257 unless ($response) { return []; }
259 if(UNIVERSAL::isa($response,"OpenSRF::EX")) {
260 warn "Received exception for tcn search\n";
261 throw $response ($response->stringify);
264 my $record_entry = $response->content;
266 for my $record (@$record_entry) {
267 push @ids, $record->id;
270 warn "received ID's for tcn search @ids\n";
273 return { count => $size, ids => \@ids };
278 # --------------------------------------------------------------------------------
281 __PACKAGE__->register_method(
282 method => "biblio_search_isbn",
283 api_name => "open-ils.search.biblio.isbn",
286 sub biblio_search_isbn {
287 my( $self, $client, $isbn ) = @_;
288 throw OpenSRF::EX::InvalidArg
290 ("biblio_search_isbn needs an ISBN to search")
291 unless defined $isbn;
293 warn "biblio search for ISBN $isbn\n";
294 my $method = $self->method_lookup("open-ils.search.biblio.marc");
295 my ($records) = $method->run( $cat_search_hash->{isbn}, $isbn );
297 for my $i (@$records) {
298 if( ref($i) and defined($i->[0])) {
304 return { count => $size, ids => \@ids };
308 __PACKAGE__->register_method(
309 method => "biblio_search_barcode",
310 api_name => "open-ils.search.biblio.barcode",
313 sub biblio_search_barcode {
314 my( $self, $client, $barcode ) = @_;
315 throw OpenSRF::EX::InvalidArg
317 ("biblio_search_barcode needs an ISBN to search")
318 unless defined $barcode;
320 warn "biblio search for ISBN $barcode\n";
321 my $records = OpenILS::Application::AppUtils->simple_scalar_request(
322 "open-ils.storage", "open-ils.storage.direct.asset.copy.search.barcode",
326 for my $i (@$records) {
327 if( ref($i) and defined($i->[0])) {
333 return { count => $size, ids => \@ids };
338 # --------------------------------------------------------------------------------
340 __PACKAGE__->register_method(
341 method => "cat_biblio_search_class",
342 api_name => "open-ils.search.cat.biblio.class",
344 note => "Searches biblio information by search class",
347 sub cat_biblio_search_class {
349 my( $self, $client, $org_id, $class, $sort, $string ) = @_;
351 throw OpenSRF::EX::InvalidArg
352 ("Not enough args to open-ils.search.cat.biblio.class")
353 unless( defined($org_id) and $class and $sort and $string );
358 my $method = $self->method_lookup("open-ils.search.biblio.marc");
360 throw OpenSRF::EX::PANIC
361 ("Can't lookup method 'open-ils.search.biblio.marc'");
364 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
367 for my $i (@$records) { push @ids, $i->[0]; }
369 my $mods_list = _records_to_mods( @ids );
370 return undef unless (ref($mods_list) eq "ARRAY");
372 # ---------------------------------------------------------------
373 # append copy count information to the mods objects
374 my $session = OpenSRF::AppSession->create("open-ils.storage");
376 my $request = $session->request(
377 "open-ils.storage.direct.biblio.record_copy_count.batch", $org_id, @ids );
381 warn "receiving copy counts for doc $id\n";
383 my $response = $request->recv();
384 next unless $response;
386 if( $response and UNIVERSAL::isa($response, "Error")) {
387 throw $response ($response->stringify);
390 my $count = $response->content;
391 my $mods_obj = undef;
392 for my $m (@$mods_list) {
393 $mods_obj = $m if ($m->{doc_id} == $id)
396 $mods_obj->{copy_count} = $count;
399 $client->respond( $mods_obj );
405 $session->disconnect();
407 # ---------------------------------------------------------------
414 __PACKAGE__->register_method(
415 method => "cat_biblio_search_class_id",
416 api_name => "open-ils.search.cat.biblio.class.id",
418 note => "Searches biblio information by search class and returns the IDs",
421 sub cat_biblio_search_class_id {
423 my( $self, $client, $org_id, $class, $string, $limit, $offset ) = @_;
430 $string = OpenILS::Application::Search->filter_search($string);
431 if(!$string) { return undef; }
433 warn "Searching cat.biblio.class.id string: $string offset: $offset limit: $limit\n";
435 throw OpenSRF::EX::InvalidArg
436 ("Not enough args to open-ils.search.cat.biblio.class")
437 unless( defined($org_id) and $class and $string );
442 my $cache_key = md5_hex( $org_id . $class . $string );
443 my $id_array = OpenILS::Application::SearchCache->get_cache($cache_key);
446 warn "Return search from cache\n";
447 my $size = @$id_array;
448 my @ids = @$id_array[ $offset..($offset+$limit) ];
449 warn "Returning cat.biblio.class.id $string\n";
450 return { count => $size, ids => \@ids };
453 my $method = $self->method_lookup("open-ils.search.biblio.marc");
455 throw OpenSRF::EX::PANIC
456 ("Can't lookup method 'open-ils.search.biblio.marc'");
459 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
463 for my $i (@$records) {
464 if(defined($i->[0])) {
465 push @cache_ids, $i->[0];
469 my @ids = @cache_ids[ $offset..($offset+$limit) ];
470 my $size = @$records;
472 OpenILS::Application::SearchCache->put_cache(
473 $cache_key, \@cache_ids, $size );
475 warn "Returning cat.biblio.class.id $string\n";
476 return { count =>$size, ids => \@ids };
481 __PACKAGE__->register_method(
482 method => "biblio_search_class",
483 api_name => "open-ils.search.biblio.class",
485 note => "Searches biblio information by search class and returns the IDs",
488 sub biblio_search_class {
490 my( $self, $client, $class, $string,
491 $org_id, $org_type, $limit, $offset ) = @_;
493 warn "$org_id : $org_type : $limit : $offset\n";
496 $limit = 100 unless defined($limit and $limit > 0 );
497 $org_id = "1" unless defined($org_id); # xxx
498 $org_type = 0 unless defined($org_type);
500 warn "$org_id : $org_type : $limit : $offset\n";
501 warn "Searching biblio.class.id string: $string offset: $offset limit: $limit\n";
503 $string = OpenILS::Application::Search->filter_search($string);
504 if(!$string) { return undef; }
506 if( !defined($org_id) or !$class or !$string ) {
507 warn "not enbough args to metarecord search\n";
508 throw OpenSRF::EX::InvalidArg
509 ("Not enough args to open-ils.search.cat.biblio.class")
514 if( ($class ne "title") and ($class ne "author") and
515 ($class ne "subject") and ($class ne "keyword") ) {
516 warn "Invalid search class: $class\n";
517 throw OpenSRF::EX::InvalidArg ("Not a valid search class: $class")
520 # grab the mr id's from storage
522 my $method = "open-ils.storage.cachable.metabib.$class.search_fts.metarecord_count";
523 warn "Performing count method $method\n";
524 my $session = OpenSRF::AppSession->create('open-ils.storage');
526 my $request = $session->request( $method,
531 my $response = $request->recv();
533 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
534 throw $response ($response->stringify);
537 my $count = $response->content;
538 warn "Received count $count\n";
539 # XXX check count size and respond accordingly
542 warn "performing mr search\n";
543 $request = $session->request(
544 "open-ils.storage.cachable.metabib.$class.search_fts.metarecord",
553 $response = $request->recv();
555 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
556 warn "Recieved Exception from storage: " . $response->stringify . "\n";
557 $response->{'msg'} = $response->stringify();
558 throw $response ($response->stringify);
563 my $records = $response->content;
567 for my $i (@$records) {
568 if(defined($i->[0])) {
569 push @all_ids, $i->[0];
573 #my @ids = @all_ids[ $offset..($offset+$limit) ];
575 @ids = grep { defined($_) } @ids;
579 $session->disconnect();
581 warn "Returning biblio.class $string\n";
582 return { count =>$count, ids => \@ids };
589 __PACKAGE__->register_method(
590 method => "biblio_mrid_to_modsbatch",
591 api_name => "open-ils.search.biblio.metarecord.mods_slim.retrieve",
594 sub biblio_mrid_to_modsbatch {
595 my( $self, $client, $mrid ) = @_;
597 throw OpenSRF::EX::InvalidArg
598 ("search.biblio.metarecord_to_mods requires mr id")
599 unless defined( $mrid );
602 my $metarecord = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
603 "open-ils.storage.direct.metabib.metarecord.retrieve", $mrid );
604 my $master_id = $metarecord->master_record();
607 # check for existing mods
608 if($metarecord->mods()){
609 warn "We already have mods for " . $metarecord->id . "\n";
610 return JSON->JSON2perl($metarecord->mods());
615 warn "Creating mods batch for metarecord $mrid\n";
616 my $id_hash = biblio_mrid_to_record_ids( undef, undef, $mrid );
617 my @ids = @{$id_hash->{ids}};
621 if(@ids < 1) { return undef; }
624 warn "Master ID is $master_id\n";
625 # grab the master record to start the mods batch
627 my $record = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
628 "open-ils.storage.direct.biblio.record_entry.retrieve", $master_id );
631 throw OpenSRF::EX::ERROR
632 ("No record returned with id $master_id");
635 my $u = OpenILS::Utils::ModsParser->new();
636 $u->start_mods_batch( $record->marc );
637 my $main_doc_id = $record->id();
639 @ids = grep { $_ ne $master_id } @ids;
641 warn "NON-Master IDs are @ids\n";
643 # now we have to collect all of the marc objects and push them into a mods batch
644 my $session = OpenSRF::AppSession->create("open-ils.storage");
645 my $request = $session->request(
646 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
648 while( my $response = $request->recv() ) {
650 next unless $response;
651 if(UNIVERSAL::isa( $response,"OpenSRF::EX")) {
652 throw $response ($response->stringify);
655 my $content = $response->content;
658 $u->push_mods_batch( $content->marc );
662 my $mods = $u->finish_mods_batch();
663 $mods->{doc_id} = $mrid;
666 $client->respond_complete($mods);
668 $metarecord->mods(JSON->perl2JSON($mods));
670 my $req = $session->request(
671 "open-ils.storage.direct.metabib.metarecord.update",
674 my $resp = $req->recv;
675 throw $req->failed if $req->failed;
678 $session->disconnect();
686 # converts a mr id into a list of record ids
688 __PACKAGE__->register_method(
689 method => "biblio_mrid_to_record_ids",
690 api_name => "open-ils.search.biblio.metarecord_to_records",
693 sub biblio_mrid_to_record_ids {
694 my( $self, $client, $mrid ) = @_;
696 throw OpenSRF::EX::InvalidArg
697 ("search.biblio.metarecord_to_record_ids requires mr id")
698 unless defined( $mrid );
700 warn "Searching for record for MR $mrid\n";
702 my $mrmaps = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
703 "open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord", $mrid );
706 for my $map (@$mrmaps) { push @ids, $map->source(); }
708 warn "Recovered id's [@ids] for mr $mrid\n";
711 warn "Size: $size\n";
713 return { count => $size, ids => \@ids };