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",
171 note => "Provide ID, we provide the copy count"
174 sub record_id_to_copy_count {
175 my( $self, $client, $org_id, $record_id ) = @_;
177 my $session = OpenSRF::AppSession->create("open-ils.storage");
178 warn "copy_count retrieve $record_id\n";
179 return undef unless(defined $record_id);
181 my $request = $session->request(
182 "open-ils.storage.direct.biblio.record_copy_count", $org_id, $record_id );
184 warn "copy_count wait $record_id\n";
185 $request->wait_complete;
187 warn "copy_count recv $record_id\n";
188 my $response = $request->recv();
189 return undef unless $response;
191 warn "copy_count after recv $record_id\n";
193 if( $response and UNIVERSAL::isa($response, "Error")) {
194 throw $response ($response->stringify);
197 my $count = $response->content;
201 $session->disconnect();
207 # used for cat search classes
208 my $cat_search_hash = {
211 { tag => "100", subfield => "a"} ,
212 { tag => "700", subfield => "a"},
216 { tag => "245", subfield => "a"},
217 { tag => "242", subfield => "a"},
218 { tag => "240", subfield => "a"},
219 { tag => "210", subfield => "a"},
223 { tag => "650", subfield => "_" },
227 { tag => "035", subfield => "_" },
231 { tag => "020", subfield => "a" },
237 __PACKAGE__->register_method(
238 method => "biblio_search_tcn",
239 api_name => "open-ils.search.biblio.tcn",
241 note => "Retrieve a record by TCN",
244 sub biblio_search_tcn {
246 my( $self, $client, $tcn ) = @_;
248 $tcn =~ s/.*?(\w+)\s*$/$1/o;
249 warn "Searching TCN $tcn\n";
251 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
252 my $request = $session->request(
253 "open-ils.storage.direct.biblio.record_entry.search.tcn_value", $tcn );
254 warn "tcn going into recv\n";
255 my $response = $request->recv();
258 unless ($response) { return []; }
260 if(UNIVERSAL::isa($response,"OpenSRF::EX")) {
261 warn "Received exception for tcn search\n";
262 throw $response ($response->stringify);
265 my $record_entry = $response->content;
267 for my $record (@$record_entry) {
268 push @ids, $record->id;
271 warn "received ID's for tcn search @ids\n";
274 return { count => $size, ids => \@ids };
279 # --------------------------------------------------------------------------------
282 __PACKAGE__->register_method(
283 method => "biblio_search_isbn",
284 api_name => "open-ils.search.biblio.isbn",
287 sub biblio_search_isbn {
288 my( $self, $client, $isbn ) = @_;
289 throw OpenSRF::EX::InvalidArg
291 ("biblio_search_isbn needs an ISBN to search")
292 unless defined $isbn;
294 warn "biblio search for ISBN $isbn\n";
295 my $method = $self->method_lookup("open-ils.search.biblio.marc");
296 my ($records) = $method->run( $cat_search_hash->{isbn}, $isbn );
298 for my $i (@$records) {
299 if( ref($i) and defined($i->[0])) {
305 return { count => $size, ids => \@ids };
310 # --------------------------------------------------------------------------------
312 __PACKAGE__->register_method(
313 method => "biblio_barcode_to_copy",
314 api_name => "open-ils.search.biblio.barcode",
317 # turns a barcode into a copy object
318 sub biblio_barcode_to_copy {
319 my( $self, $client, $barcode ) = @_;
321 throw OpenSRF::EX::InvalidArg
322 ("search.biblio.barcode needs an ISBN to search")
323 unless defined $barcode;
325 warn "copy search for ISBN $barcode\n";
326 my $record = OpenILS::Application::AppUtils->simple_scalar_request(
328 "open-ils.storage.direct.asset.copy.search.barcode",
331 return undef unless($record);
337 __PACKAGE__->register_method(
338 method => "biblio_copy_to_mods",
339 api_name => "open-ils.search.biblio.copy.mods.retrieve",
342 # takes a copy object and returns it fleshed mods object
343 sub biblio_copy_to_mods {
344 my( $self, $client, $copy ) = @_;
346 throw OpenSRF::EX::InvalidArgs
347 ("copy.mods.retrieve needs a copy") unless( $copy );
349 new Fieldmapper::asset::copy($copy);
351 my $volume = OpenILS::Application::AppUtils->simple_scalar_request(
353 "open-ils.storage.direct.asset.call_number.retrieve",
354 $copy->call_number() );
356 my $mods = _records_to_mods($volume->record());
357 $mods = shift @$mods;
358 $volume->copies([$copy]);
359 push @{$mods->call_numbers()}, $volume;
365 sub barcode_to_mods {
370 # --------------------------------------------------------------------------------
372 __PACKAGE__->register_method(
373 method => "cat_biblio_search_class",
374 api_name => "open-ils.search.cat.biblio.class",
376 note => "Searches biblio information by search class",
379 sub cat_biblio_search_class {
381 my( $self, $client, $org_id, $class, $sort, $string ) = @_;
383 throw OpenSRF::EX::InvalidArg
384 ("Not enough args to open-ils.search.cat.biblio.class")
385 unless( defined($org_id) and $class and $sort and $string );
390 my $method = $self->method_lookup("open-ils.search.biblio.marc");
392 throw OpenSRF::EX::PANIC
393 ("Can't lookup method 'open-ils.search.biblio.marc'");
396 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
399 for my $i (@$records) { push @ids, $i->[0]; }
401 my $mods_list = _records_to_mods( @ids );
402 return undef unless (ref($mods_list) eq "ARRAY");
404 # ---------------------------------------------------------------
405 # append copy count information to the mods objects
406 my $session = OpenSRF::AppSession->create("open-ils.storage");
408 my $request = $session->request(
409 "open-ils.storage.direct.biblio.record_copy_count.batch", $org_id, @ids );
413 warn "receiving copy counts for doc $id\n";
415 my $response = $request->recv();
416 next unless $response;
418 if( $response and UNIVERSAL::isa($response, "Error")) {
419 throw $response ($response->stringify);
422 my $count = $response->content;
423 my $mods_obj = undef;
424 for my $m (@$mods_list) {
425 $mods_obj = $m if ($m->doc_id() == $id)
428 $mods_obj->copy_count($count);
431 $client->respond( $mods_obj );
437 $session->disconnect();
439 # ---------------------------------------------------------------
446 __PACKAGE__->register_method(
447 method => "cat_biblio_search_class_id",
448 api_name => "open-ils.search.cat.biblio.class.id",
450 note => "Searches biblio information by search class and returns the IDs",
453 sub cat_biblio_search_class_id {
455 my( $self, $client, $org_id, $class, $string, $limit, $offset ) = @_;
462 $string = OpenILS::Application::Search->filter_search($string);
463 if(!$string) { return undef; }
465 warn "Searching cat.biblio.class.id string: $string offset: $offset limit: $limit\n";
467 throw OpenSRF::EX::InvalidArg
468 ("Not enough args to open-ils.search.cat.biblio.class")
469 unless( defined($org_id) and $class and $string );
474 my $cache_key = md5_hex( $org_id . $class . $string );
475 my $id_array = OpenILS::Application::SearchCache->get_cache($cache_key);
478 warn "Return search from cache\n";
479 my $size = @$id_array;
480 my @ids = @$id_array[ $offset..($offset+$limit) ];
481 warn "Returning cat.biblio.class.id $string\n";
482 return { count => $size, ids => \@ids };
485 my $method = $self->method_lookup("open-ils.search.biblio.marc");
487 throw OpenSRF::EX::PANIC
488 ("Can't lookup method 'open-ils.search.biblio.marc'");
491 my ($records) = $method->run( $cat_search_hash->{$class}, $string );
495 for my $i (@$records) {
496 if(defined($i->[0])) {
497 push @cache_ids, $i->[0];
501 my @ids = @cache_ids[ $offset..($offset+$limit) ];
502 my $size = @$records;
504 OpenILS::Application::SearchCache->put_cache(
505 $cache_key, \@cache_ids, $size );
507 warn "Returning cat.biblio.class.id $string\n";
508 return { count =>$size, ids => \@ids };
513 __PACKAGE__->register_method(
514 method => "biblio_search_class",
515 api_name => "open-ils.search.biblio.class",
517 note => "Searches biblio information by search class and returns the IDs",
520 sub biblio_search_class {
522 my( $self, $client, $class, $string,
523 $org_id, $org_type, $limit, $offset ) = @_;
525 warn "$org_id : $org_type : $limit : $offset\n";
528 $limit = 100 unless defined($limit and $limit > 0 );
529 $org_id = "1" unless defined($org_id); # xxx
530 $org_type = 0 unless defined($org_type);
532 warn "$org_id : $org_type : $limit : $offset\n";
533 warn "Searching biblio.class.id\n" .
535 "\noffset: $offset\n" .
537 "org_id: $org_id\n" .
538 "depth: $org_type\n" ;
540 $string = OpenILS::Application::Search->filter_search($string);
541 if(!$string) { return undef; }
543 if( !defined($org_id) or !$class or !$string ) {
544 warn "not enbough args to metarecord search\n";
545 throw OpenSRF::EX::InvalidArg
546 ("Not enough args to open-ils.search.cat.biblio.class")
551 if( ($class ne "title") and ($class ne "author") and
552 ($class ne "subject") and ($class ne "keyword") ) {
553 warn "Invalid search class: $class\n";
554 throw OpenSRF::EX::InvalidArg ("Not a valid search class: $class")
557 # grab the mr id's from storage
559 my $method = "open-ils.storage.metabib.$class.search_fts.metarecord_count";
560 warn "Performing count method $method\n";
561 my $session = OpenSRF::AppSession->create('open-ils.storage');
563 my $request = $session->request( $method,
568 my $response = $request->recv();
570 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
571 throw $response ($response->stringify);
574 my $count = $response->content;
575 warn "Received count $count\n";
576 # XXX check count size and respond accordingly
579 warn "performing mr search\n";
580 $request = $session->request(
581 #"open-ils.storage.cachable.metabib.$class.search_fts.metarecord.atomic",
582 "open-ils.storage.metabib.$class.search_fts.metarecord.atomic",
591 $response = $request->recv();
593 if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
594 warn "Recieved Exception from storage: " . $response->stringify . "\n";
595 $response->{'msg'} = $response->stringify();
596 throw $response ($response->stringify);
601 my $records = $response->content;
603 warn Dumper $records;
607 # if we just get one, it won't be wrapped in an array
608 if(!ref($records->[0])) {
609 $records = [$records];
612 for my $i (@$records) {
613 if(defined($i->[0])) {
614 push @all_ids, $i->[0];
618 #my @ids = @all_ids[ $offset..($offset+$limit) ];
620 @ids = grep { defined($_) } @ids;
624 $session->disconnect();
626 warn "Returning biblio.class $string\n";
627 return { count =>$count, ids => \@ids };
634 __PACKAGE__->register_method(
635 method => "biblio_mrid_to_modsbatch",
636 api_name => "open-ils.search.biblio.metarecord.mods_slim.retrieve",
639 sub biblio_mrid_to_modsbatch {
640 my( $self, $client, $mrid ) = @_;
642 throw OpenSRF::EX::InvalidArg
643 ("search.biblio.metarecord_to_mods requires mr id")
644 unless defined( $mrid );
647 my $metarecord = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
648 "open-ils.storage.direct.metabib.metarecord.retrieve", $mrid );
651 throw OpenSRF::EX::ERROR ("No metarecord exists with the given id: $mrid");
654 my $master_id = $metarecord->master_record();
657 # check for existing mods
658 if($metarecord->mods()){
659 warn "We already have mods for " . $metarecord->id . "\n";
660 my $perl = JSON->JSON2perl($metarecord->mods());
661 return Fieldmapper::metabib::virtual_record->new($perl);
666 warn "Creating mods batch for metarecord $mrid\n";
667 my $id_hash = biblio_mrid_to_record_ids( undef, undef, $mrid );
668 my @ids = @{$id_hash->{ids}};
670 if(@ids < 1) { return undef; }
672 warn "Master ID is $master_id\n";
673 # grab the master record to start the mods batch
675 my $record = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
676 "open-ils.storage.direct.biblio.record_entry.retrieve", $master_id );
679 throw OpenSRF::EX::ERROR
680 ("No record returned with id $master_id");
683 my $u = OpenILS::Utils::ModsParser->new();
684 $u->start_mods_batch( $record->marc );
685 my $main_doc_id = $record->id();
687 @ids = grep { $_ ne $master_id } @ids;
689 warn "NON-Master IDs are @ids\n";
691 # now we have to collect all of the marc objects and push them into a mods batch
692 my $session = OpenSRF::AppSession->create("open-ils.storage");
693 my $request = $session->request(
694 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
696 while( my $response = $request->recv() ) {
698 next unless $response;
699 if(UNIVERSAL::isa( $response,"OpenSRF::EX")) {
700 throw $response ($response->stringify);
703 my $content = $response->content;
706 $u->push_mods_batch( $content->marc );
710 my $mods = $u->finish_mods_batch();
711 $mods->doc_id($mrid);
714 $client->respond_complete($mods);
716 my $mods_string = JSON->perl2JSON($mods->decast);
717 warn "Mods String to DB:\n $mods_string\n";
719 $metarecord->mods($mods_string);
721 my $req = $session->request(
722 "open-ils.storage.direct.metabib.metarecord.update",
729 $session->disconnect();
737 # converts a mr id into a list of record ids
739 __PACKAGE__->register_method(
740 method => "biblio_mrid_to_record_ids",
741 api_name => "open-ils.search.biblio.metarecord_to_records",
744 sub biblio_mrid_to_record_ids {
745 my( $self, $client, $mrid ) = @_;
747 throw OpenSRF::EX::InvalidArg
748 ("search.biblio.metarecord_to_record_ids requires mr id")
749 unless defined( $mrid );
751 warn "Searching for record for MR $mrid\n";
753 my $mrmaps = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage",
754 "open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord", $mrid );
757 for my $map (@$mrmaps) { push @ids, $map->source(); }
759 warn "Recovered id's [@ids] for mr $mrid\n";
762 warn "Size: $size\n";
764 return { count => $size, ids => \@ids };