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;
12 use Time::HiRes qw(time);
13 use OpenSRF::EX qw(:try);
14 use Digest::MD5 qw(md5_hex);
16 # Houses biblio search utilites
18 __PACKAGE__->register_method(
19 method => "biblio_search_marc",
20 api_name => "open-ils.search.biblio.marc",
22 note => "Searches biblio information by marc tag",
25 sub biblio_search_marc {
27 my( $self, $client, $search_hash, $string ) = @_;
29 warn "Building biblio marc session\n";
30 my $session = OpenSRF::AppSession->create("open-ils.storage");
32 warn "Sending biblio marc request\n";
33 my $request = $session->request(
34 "open-ils.storage.direct.metabib.full_rec.search_fts.index_vector",
35 $search_hash, $string );
37 warn "Waiting complete\n";
38 $request->wait_complete();
40 warn "Calling recv\n";
41 my $response = $request->recv(20);
44 if($response and UNIVERSAL::isa($response,"OpenSRF::EX")) {
45 throw $response ($response->stringify);
50 if($response and UNIVERSAL::can($response,"content")) {
51 $data = $response->content;
53 warn "finishing request\n";
57 $session->disconnect();
67 # ---------------------------------------------------------------------------
68 # takes a list of record id's and turns the docs into friendly
69 # mods structures. Creates one MODS structure for each doc id.
70 # ---------------------------------------------------------------------------
71 sub _records_to_mods {
77 my $session = OpenSRF::AppSession->create("open-ils.storage");
78 my $request = $session->request(
79 "open-ils.storage.direct.biblio.record_marc.batch.retrieve", @ids );
81 my $last_content = undef;
83 while( my $response = $request->recv() ) {
86 my $u = OpenILS::Utils::ModsParser->new();
87 $u->start_mods_batch( $last_content->marc );
88 my $mods = $u->finish_mods_batch();
89 $mods->{doc_id} = $last_content->id();
90 warn "Turning doc " . $mods->{doc_id} . " into MODS\n";
91 $last_content = undef;
95 next unless $response;
97 if($response->isa("OpenSRF::EX")) {
98 throw $response ($response->stringify);
101 $last_content = $response->content;
105 if( $last_content ) {
106 my $u = OpenILS::Utils::ModsParser->new();
107 $u->start_mods_batch( $last_content->marc );
108 my $mods = $u->finish_mods_batch();
109 $mods->{doc_id} = $last_content->id();
110 push @results, $mods;
115 $session->disconnect();
121 __PACKAGE__->register_method(
122 method => "record_id_to_mods",
123 api_name => "open-ils.search.biblio.record.mods.retrieve",
125 note => "Provide ID, we provide the mods"
128 # converts a record into a mods object with copy counts attached
129 sub record_id_to_mods {
131 my( $self, $client, $org_id, $id ) = @_;
133 my $mods_list = _records_to_mods( $id );
134 my $mods_obj = $mods_list->[0];
135 my $cmethod = $self->method_lookup(
136 "open-ils.search.biblio.record.copy_count");
137 my ($count) = $cmethod->run($org_id, $id);
138 $mods_obj->{copy_count} = $count;
144 __PACKAGE__->register_method(
145 method => "record_id_to_mods_slim",
146 api_name => "open-ils.search.biblio.record.mods_slim.retrieve",
148 note => "Provide ID, we provide the mods"
151 # converts a record into a mods object with NO copy counts attached
152 sub record_id_to_mods_slim {
154 my( $self, $client, $id ) = @_;
155 warn "Retrieving MODS object for record $id\n";
156 return undef unless(defined $id);
158 my $mods_list = _records_to_mods( $id );
159 my $mods_obj = $mods_list->[0];
164 # Returns the number of copies attached to a record based on org location
165 __PACKAGE__->register_method(
166 method => "record_id_to_copy_count",
167 api_name => "open-ils.search.biblio.record.copy_count",
169 note => "Provide ID, we provide the copy count"
172 sub record_id_to_copy_count {
173 my( $self, $client, $org_id, $record_id ) = @_;
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 "open-ils.storage.direct.biblio.record_copy_count", $org_id, $record_id );
182 warn "copy_count wait $record_id\n";
183 $request->wait_complete;
185 warn "copy_count recv $record_id\n";
186 my $response = $request->recv();
187 return undef unless $response;
189 warn "copy_count after recv $record_id\n";
191 if( $response and UNIVERSAL::isa($response, "Error")) {
192 throw $response ($response->stringify);
195 my $count = $response->content;
199 $session->disconnect();
205 # used for cat search classes
206 my $cat_search_hash = {
209 { tag => "100", subfield => "a"} ,
210 { tag => "700", subfield => "a"},
214 { tag => "245", subfield => "a"},
215 { tag => "242", subfield => "a"},
216 { tag => "240", subfield => "a"},
217 { tag => "210", subfield => "a"},
221 { tag => "650", subfield => "_" },
225 { tag => "035", subfield => "_" },
229 { tag => "020", subfield => "a" },
235 __PACKAGE__->register_method(
236 method => "biblio_search_tcn",
237 api_name => "open-ils.search.biblio.tcn",
239 note => "Retrieve a record by TCN",
242 sub biblio_search_tcn {
244 my( $self, $client, $tcn ) = @_;
246 $tcn =~ s/.*?(\w+)\s*$/$1/o;
247 warn "Searching TCN $tcn\n";
249 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
250 my $request = $session->request(
251 "open-ils.storage.direct.biblio.record_entry.search.tcn_value", $tcn );
252 warn "tcn going into recv\n";
253 my $response = $request->recv();
256 unless ($response) { return []; }
258 if(UNIVERSAL::isa($response,"OpenSRF::EX")) {
259 warn "Received exception for tcn search\n";
260 throw $response ($response->stringify);
263 my $record_entry = $response->content;
265 for my $record (@$record_entry) {
266 push @ids, $record->id;
269 warn "received ID's for tcn search @ids\n";
272 return { count => $size, ids => \@ids };
277 # --------------------------------------------------------------------------------
280 __PACKAGE__->register_method(
281 method => "biblio_search_isbn",
282 api_name => "open-ils.search.biblio.isbn",
285 sub biblio_search_isbn {
286 my( $self, $client, $isbn ) = @_;
287 throw OpenSRF::EX::InvalidArg
289 ("biblio_search_isbn needs an ISBN to search")
290 unless defined $isbn;
292 warn "biblio search for ISBN $isbn\n";
293 my $method = $self->method_lookup("open-ils.search.biblio.marc");
294 my ($records) = $method->run( $cat_search_hash->{isbn}, $isbn );
296 for my $i (@$records) {
297 if( ref($i) and defined($i->[0])) {
303 return { count => $size, ids => \@ids };
307 __PACKAGE__->register_method(
308 method => "biblio_search_barcode",
309 api_name => "open-ils.search.biblio.barcode",
312 sub biblio_search_barcode {
313 my( $self, $client, $barcode ) = @_;
314 throw OpenSRF::EX::InvalidArg
316 ("biblio_search_barcode needs an ISBN to search")
317 unless defined $barcode;
319 warn "biblio search for ISBN $barcode\n";
320 my $records = OpenILS::Application::AppUtils->simple_scalar_request(
321 "open-ils.storage", "open-ils.storage.direct.asset.copy.search.barcode",
325 for my $i (@$records) {
326 if( ref($i) and defined($i->[0])) {
332 return { count => $size, ids => \@ids };
337 # --------------------------------------------------------------------------------
339 __PACKAGE__->register_method(
340 method => "cat_biblio_search_class",
341 api_name => "open-ils.search.cat.biblio.class",
343 note => "Searches biblio information by search class",
346 sub cat_biblio_search_class {
348 my( $self, $client, $org_id, $class, $sort, $string ) = @_;
350 throw OpenSRF::EX::InvalidArg
351 ("Not enough args to open-ils.search.cat.biblio.class")
352 unless( defined($org_id) and $class and $sort and $string );
357 my $method = $self->method_lookup("open-ils.search.biblio.marc");
359 throw OpenSRF::EX::PANIC
360 ("Can't lookup method 'open-ils.search.biblio.marc'");
363 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
366 for my $i (@$records) { push @ids, $i->[0]; }
368 my $mods_list = _records_to_mods( @ids );
369 return undef unless (ref($mods_list) eq "ARRAY");
371 # ---------------------------------------------------------------
372 # append copy count information to the mods objects
373 my $session = OpenSRF::AppSession->create("open-ils.storage");
375 my $request = $session->request(
376 "open-ils.storage.direct.biblio.record_copy_count.batch", $org_id, @ids );
380 warn "receiving copy counts for doc $id\n";
382 my $response = $request->recv();
383 next unless $response;
385 if( $response and UNIVERSAL::isa($response, "Error")) {
386 throw $response ($response->stringify);
389 my $count = $response->content;
390 my $mods_obj = undef;
391 for my $m (@$mods_list) {
392 $mods_obj = $m if ($m->{doc_id} == $id)
395 $mods_obj->{copy_count} = $count;
398 $client->respond( $mods_obj );
404 $session->disconnect();
406 # ---------------------------------------------------------------
413 __PACKAGE__->register_method(
414 method => "cat_biblio_search_class_id",
415 api_name => "open-ils.search.cat.biblio.class.id",
417 note => "Searches biblio information by search class and returns the IDs",
420 sub cat_biblio_search_class_id {
422 my( $self, $client, $org_id, $class, $string, $limit, $offset ) = @_;
429 $string = OpenILS::Application::Search->filter_search($string);
430 if(!$string) { return undef; }
432 warn "Searching cat.biblio.class.id string: $string offset: $offset limit: $limit\n";
434 throw OpenSRF::EX::InvalidArg
435 ("Not enough args to open-ils.search.cat.biblio.class")
436 unless( defined($org_id) and $class and $string );
441 my $cache_key = md5_hex( $org_id . $class . $string );
442 my $id_array = OpenILS::Application::SearchCache->get_cache($cache_key);
445 warn "Return search from cache\n";
446 my $size = @$id_array;
447 my @ids = @$id_array[ $offset..($offset+$limit) ];
448 warn "Returning cat.biblio.class.id $string\n";
449 return { count => $size, ids => \@ids };
452 my $method = $self->method_lookup("open-ils.search.biblio.marc");
454 throw OpenSRF::EX::PANIC
455 ("Can't lookup method 'open-ils.search.biblio.marc'");
458 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
462 for my $i (@$records) {
463 if(defined($i->[0])) {
464 push @cache_ids, $i->[0];
468 my @ids = @cache_ids[ $offset..($offset+$limit) ];
469 my $size = @$records;
471 OpenILS::Application::SearchCache->put_cache(
472 $cache_key, \@cache_ids, $size );
474 warn "Returning cat.biblio.class.id $string\n";
475 return { count =>$size, ids => \@ids };
480 __PACKAGE__->register_method(
481 method => "biblio_search_class",
482 api_name => "open-ils.search.biblio.class",
484 note => "Searches biblio information by search class and returns the IDs",
487 sub biblio_search_class {
489 my( $self, $client, $class, $string, $org_id, $org_type, $limit, $offset ) = @_;
492 $limit = 100 unless defined($limit and $limit > 0 );
493 $org_id = "1" unless defined($org_id); # xxx
494 $org_type = 0 unless defined($org_type);
497 warn "Searching biblio.class.id string: $string offset: $offset limit: $limit\n";
499 $string = OpenILS::Application::Search->filter_search($string);
500 if(!$string) { return undef; }
502 if( !defined($org_id) or !$class or !$string ) {
503 warn "not enbough args to metarecord searcn\n";
504 throw OpenSRF::EX::InvalidArg
505 ("Not enough args to open-ils.search.cat.biblio.class")
510 if( ($class ne "title") and ($class ne "author") and
511 ($class ne "subject") and ($class ne "keyword") ) {
512 warn "Invalid search class: $class\n";
513 throw OpenSRF::EX::InvalidArg ("Not a valid search class: $class")
516 # grab the mr id's from storage
518 my $method = "open-ils.storage.metabib.$class.search_fts.metarecord_count";
519 warn "Performing count method $method\n";
520 my $session = OpenSRF::AppSession->create('open-ils.storage');
521 my $request = $session->request( $method, $string, $org_id, $org_type );
522 my $response = $request->recv();
524 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
525 throw $response ($response->stringify);
528 my $count = $response->content;
529 warn "Received count $count\n";
531 # XXX check count size and respond accordingly
534 warn "performing mr search\n";
535 $request = $session->request(
536 "open-ils.storage.metabib.$class.search_fts.metarecord",
537 $string, $org_id, $org_type, $limit );
540 $response = $request->recv();
542 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
543 warn "Recieved Exception from storage: " . $response->stringify . "\n";
544 $response->{'msg'} = $response->stringify();
545 throw $response ($response->stringify);
550 my $records = $response->content;
554 for my $i (@$records) {
555 if(defined($i->[0])) {
556 push @all_ids, $i->[0];
560 my @ids = @all_ids[ $offset..($offset+$limit) ];
561 @ids = grep { defined($_) } @ids;
562 #my $size = @$records;
566 $session->disconnect();
568 warn "Returning biblio.class $string\n";
569 return { count =>$count, ids => \@ids };
576 __PACKAGE__->register_method(
577 method => "biblio_mrid_to_modsbatch",
578 api_name => "open-ils.search.biblio.metarecord.mods_slim.retrieve",
581 sub biblio_mrid_to_modsbatch {
582 my( $self, $client, $mrid ) = @_;
584 throw OpenSRF::EX::InvalidArg
585 ("search.biblio.metarecord_to_mods requires mr id")
586 unless defined( $mrid );
588 warn "Creating mods batch for metarecord $mrid\n";
589 my $id_hash = biblio_mrid_to_record_ids( undef, undef, $mrid );
590 my @ids = @{$id_hash->{ids}};
592 if(@ids < 1) { return undef; }
594 # grab the master record...
596 my $master_id = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
597 "open-ils.storage.direct.metabib.metarecord.search.master_record", $mrid );
599 $master_id = $master_id->[0]; # there should only be one
602 warn "Master Record: " . Dumper($master_id);
604 if (!ref($master_id) or !defined($master_id->id())) {
605 warn "No Master Record Found, using first found id\n";
606 $master_id = shift @ids;
608 $master_id = $master_id->id();
611 warn "Master ID is $master_id\n";
613 # grab the master record to start the mods batch
615 my $record = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
616 "open-ils.storage.direct.biblio.record_marc.retrieve", $master_id );
619 throw OpenSRF::EX::ERROR
620 ("No record returned with id $master_id");
623 my $u = OpenILS::Utils::ModsParser->new();
624 $u->start_mods_batch( $record->marc );
625 my $main_doc_id = $record->id();
627 @ids = grep { $_ ne $master_id } @ids;
629 warn "NON-Master IDs are @ids\n";
631 # now we have to collect all of the marc objects and push them into a mods batch
632 my $session = OpenSRF::AppSession->create("open-ils.storage");
633 my $request = $session->request(
634 "open-ils.storage.direct.biblio.record_marc.batch.retrieve", @ids );
636 while( my $response = $request->recv() ) {
638 next unless $response;
639 if(UNIVERSAL::isa( $response,"OpenSRF::EX")) {
640 throw $response ($response->stringify);
643 my $content = $response->content;
646 $u->push_mods_batch( $content->marc );
650 my $mods = $u->finish_mods_batch();
651 $mods->{doc_id} = $main_doc_id;
655 $session->disconnect();
663 # converts a mr id into a list of record ids
665 __PACKAGE__->register_method(
666 method => "biblio_mrid_to_record_ids",
667 api_name => "open-ils.search.biblio.metarecord_to_records",
670 sub biblio_mrid_to_record_ids {
671 my( $self, $client, $mrid ) = @_;
673 throw OpenSRF::EX::InvalidArg
674 ("search.biblio.metarecord_to_record_ids requires mr id")
675 unless defined( $mrid );
677 warn "Searching for record for MR $mrid\n";
679 my $mrmaps = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
680 "open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord", $mrid );
683 for my $map (@$mrmaps) { push @ids, $map->source(); }
685 warn "Recovered id's [@ids] for mr $mrid\n";
689 return { count => $size, ids => \@ids };