]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Search/Biblio.pm
moved to using the virtual_record fieldmapper instead of the mods hash
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Search / Biblio.pm
1 package OpenILS::Application::Search::Biblio;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
4
5 use JSON;
6 use OpenILS::Utils::Fieldmapper;
7 use OpenILS::Utils::ModsParser;
8 use OpenSRF::Utils::SettingsClient;
9
10 use OpenILS::Application::AppUtils;
11
12 use JSON;
13
14 use Time::HiRes qw(time);
15 use OpenSRF::EX qw(:try);
16 use Digest::MD5 qw(md5_hex);
17
18 # Houses biblio search utilites 
19
20 __PACKAGE__->register_method(
21         method  => "biblio_search_marc",
22         api_name        => "open-ils.search.biblio.marc",
23         argc            => 1, 
24         note            => "Searches biblio information by marc tag",
25 );
26
27 sub biblio_search_marc {
28
29         my( $self, $client, $search_hash, $string ) = @_;
30
31         warn "Building biblio marc session\n";
32         my $session = OpenSRF::AppSession->create("open-ils.storage");
33
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, 
39                         term            => $string );
40
41         warn "Waiting complete\n";
42         $request->wait_complete();
43
44         warn "Calling recv\n";
45         my $response = $request->recv(20);
46
47         warn "out of recv\n";
48         if($response and UNIVERSAL::isa($response,"OpenSRF::EX")) {
49                 throw $response ($response->stringify);
50         }
51
52
53         my $data = [];
54         if($response and UNIVERSAL::can($response,"content")) {
55                 $data = $response->content;
56         }
57         warn "finishing request\n";
58
59         $request->finish();
60         $session->finish();
61         $session->disconnect();
62
63         return $data;
64
65 }
66
67
68
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 {
74         my @ids = @_;
75         
76         my @results;
77         my @marcxml_objs;
78
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 );
82
83         my $last_content = undef;
84
85         while( my $response = $request->recv() ) {
86
87                 if( $last_content ) {
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;
94                         push @results, $mods;
95                 }
96
97                 next unless $response;
98
99                 if($response->isa("OpenSRF::EX")) {
100                         throw $response ($response->stringify);
101                 }
102
103                 $last_content = $response->content;
104
105         }
106
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;
113         }
114
115         $request->finish();
116         $session->finish();
117         $session->disconnect();
118
119         return \@results;
120
121 }
122
123 __PACKAGE__->register_method(
124         method  => "record_id_to_mods",
125         api_name        => "open-ils.search.biblio.record.mods.retrieve",
126         argc            => 1, 
127         note            => "Provide ID, we provide the mods"
128 );
129
130 # converts a record into a mods object with copy counts attached
131 sub record_id_to_mods {
132
133         my( $self, $client, $org_id, $id ) = @_;
134
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);
141
142         return $mods_obj;
143 }
144
145
146 __PACKAGE__->register_method(
147         method  => "record_id_to_mods_slim",
148         api_name        => "open-ils.search.biblio.record.mods_slim.retrieve",
149         argc            => 1, 
150         note            => "Provide ID, we provide the mods"
151 );
152
153 # converts a record into a mods object with NO copy counts attached
154 sub record_id_to_mods_slim {
155
156         my( $self, $client, $id ) = @_;
157         warn "Retrieving MODS object for record $id\n";
158         return undef unless(defined $id);
159
160         my $mods_list = _records_to_mods( $id );
161         my $mods_obj = $mods_list->[0];
162         return $mods_obj;
163 }
164
165
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",
170         argc            => 2, 
171         note            => "Provide ID, we provide the copy count"
172 );
173
174 sub record_id_to_copy_count {
175         my( $self, $client, $org_id, $record_id ) = @_;
176
177         my $session = OpenSRF::AppSession->create("open-ils.storage");
178         warn "copy_count retrieve $record_id\n";
179         return undef unless(defined $record_id);
180
181         my $request = $session->request(
182                 "open-ils.storage.direct.biblio.record_copy_count",  $org_id, $record_id );
183
184         warn "copy_count wait $record_id\n";
185         $request->wait_complete;
186
187         warn "copy_count recv $record_id\n";
188         my $response = $request->recv();
189         return undef unless $response;
190
191         warn "copy_count after recv $record_id\n";
192
193         if( $response and UNIVERSAL::isa($response, "Error")) {
194                 throw $response ($response->stringify);
195         }
196
197         my $count = $response->content;
198
199         $request->finish();
200         $session->finish();
201         $session->disconnect();
202
203         return $count;
204 }
205
206
207 # used for cat search classes
208 my $cat_search_hash =  {
209
210         author => [ 
211                 { tag => "100", subfield => "a"} ,
212                 { tag => "700", subfield => "a"}, 
213         ],
214
215         title => [ 
216                 { tag => "245", subfield => "a"},
217                 { tag => "242", subfield => "a"}, 
218                 { tag => "240", subfield => "a"},
219                 { tag => "210", subfield => "a"},
220         ],
221
222         subject => [ 
223                 { tag => "650", subfield => "_" }, 
224         ],
225
226         tcn     => [
227                 { tag => "035", subfield => "_" },
228         ],
229
230         isbn    => [
231                 { tag => "020", subfield => "a" },
232         ],
233
234 };
235
236
237 __PACKAGE__->register_method(
238         method  => "biblio_search_tcn",
239         api_name        => "open-ils.search.biblio.tcn",
240         argc            => 3, 
241         note            => "Retrieve a record by TCN",
242 );
243
244 sub biblio_search_tcn {
245
246         my( $self, $client, $tcn ) = @_;
247
248         $tcn =~ s/.*?(\w+)\s*$/$1/o;
249         warn "Searching TCN $tcn\n";
250
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();
256
257
258         unless ($response) { return []; }
259
260         if(UNIVERSAL::isa($response,"OpenSRF::EX")) {
261                 warn "Received exception for tcn search\n";
262                 throw $response ($response->stringify);
263         }
264
265         my $record_entry = $response->content;
266         my @ids;
267         for my $record (@$record_entry) {
268                 push @ids, $record->id;
269         }
270
271         warn "received ID's for tcn search @ids\n";
272
273         my $size = @ids;
274         return { count => $size, ids => \@ids };
275
276 }
277
278
279 # --------------------------------------------------------------------------------
280 # ISBN
281
282 __PACKAGE__->register_method(
283         method  => "biblio_search_isbn",
284         api_name        => "open-ils.search.biblio.isbn",
285 );
286
287 sub biblio_search_isbn { 
288         my( $self, $client, $isbn ) = @_;
289         throw OpenSRF::EX::InvalidArg 
290
291                 ("biblio_search_isbn needs an ISBN to search")
292                         unless defined $isbn;
293
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 );
297         my @ids;
298         for my $i (@$records) { 
299                 if( ref($i) and defined($i->[0])) { 
300                         push @ids, $i->[0]; 
301                 }
302         }
303
304         my $size = @ids;
305         return { count => $size, ids => \@ids };
306 }
307
308
309
310 # --------------------------------------------------------------------------------
311
312 __PACKAGE__->register_method(
313         method  => "biblio_barcode_to_copy",
314         api_name        => "open-ils.search.biblio.barcode",
315 );
316
317 # turns a barcode into a copy object
318 sub biblio_barcode_to_copy { 
319         my( $self, $client, $barcode ) = @_;
320
321         throw OpenSRF::EX::InvalidArg 
322                 ("search.biblio.barcode needs an ISBN to search")
323                         unless defined $barcode;
324
325         warn "copy search for ISBN $barcode\n";
326         my $record = OpenILS::Application::AppUtils->simple_scalar_request(
327                         "open-ils.storage", 
328                         "open-ils.storage.direct.asset.copy.search.barcode",
329                         $barcode );
330
331         return undef unless($record);
332         return $record->[0];
333
334 }
335
336
337 __PACKAGE__->register_method(
338         method  => "biblio_copy_to_mods",
339         api_name        => "open-ils.search.biblio.copy.mods.retrieve",
340 );
341
342 # takes a copy object and returns it fleshed mods object
343 sub biblio_copy_to_mods {
344         my( $self, $client, $copy ) = @_;
345
346         throw OpenSRF::EX::InvalidArgs 
347                 ("copy.mods.retrieve needs a copy") unless( $copy );
348
349         new Fieldmapper::asset::copy($copy);
350
351         my $volume = OpenILS::Application::AppUtils->simple_scalar_request(
352                 "open-ils.storage",
353                 "open-ils.storage.direct.asset.call_number.retrieve",
354                 $copy->call_number() );
355
356         my $mods = _records_to_mods($volume->record());
357         $mods = shift @$mods;
358         $volume->copies([$copy]);
359         push @{$mods->call_numbers()}, $volume;
360
361         return $mods;
362 }
363
364
365 sub barcode_to_mods {
366
367 }
368
369
370 # --------------------------------------------------------------------------------
371
372 __PACKAGE__->register_method(
373         method  => "cat_biblio_search_class",
374         api_name        => "open-ils.search.cat.biblio.class",
375         argc            => 3, 
376         note            => "Searches biblio information by search class",
377 );
378
379 sub cat_biblio_search_class {
380
381         my( $self, $client, $org_id, $class, $sort, $string ) = @_;
382
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 );
386
387
388         my $search_hash;
389
390         my $method = $self->method_lookup("open-ils.search.biblio.marc");
391         if(!$method) {
392                 throw OpenSRF::EX::PANIC 
393                         ("Can't lookup method 'open-ils.search.biblio.marc'");
394         }
395
396         my ($records) = $method->run( $cat_search_hash->{$class}, $string );
397
398         my @ids;
399         for my $i (@$records) { push @ids, $i->[0]; }
400
401         my $mods_list = _records_to_mods( @ids );
402         return undef unless (ref($mods_list) eq "ARRAY");
403
404         # ---------------------------------------------------------------
405         # append copy count information to the mods objects
406         my $session = OpenSRF::AppSession->create("open-ils.storage");
407
408         my $request = $session->request(
409                 "open-ils.storage.direct.biblio.record_copy_count.batch",  $org_id, @ids );
410
411         for my $id (@ids) {
412
413                 warn "receiving copy counts for doc $id\n";
414
415                 my $response = $request->recv();
416                 next unless $response;
417
418                 if( $response and UNIVERSAL::isa($response, "Error")) {
419                         throw $response ($response->stringify);
420                 }
421
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)
426                 }
427                 if($mods_obj) {
428                         $mods_obj->copy_count($count);
429                 }
430
431                 $client->respond( $mods_obj );
432
433         }       
434         $request->finish();
435
436         $session->finish();
437         $session->disconnect();
438         $session->kill_me();
439         # ---------------------------------------------------------------
440
441         return undef;
442 }
443
444
445
446 __PACKAGE__->register_method(
447         method  => "cat_biblio_search_class_id",
448         api_name        => "open-ils.search.cat.biblio.class.id",
449         argc            => 3, 
450         note            => "Searches biblio information by search class and returns the IDs",
451 );
452
453 sub cat_biblio_search_class_id {
454
455         my( $self, $client, $org_id, $class, $string, $limit, $offset ) = @_;
456
457         $offset ||= 0;
458         $limit  ||= 100;
459         $limit -= 1;
460
461
462         $string = OpenILS::Application::Search->filter_search($string);
463         if(!$string) { return undef; }
464
465         warn "Searching cat.biblio.class.id string: $string offset: $offset limit: $limit\n";
466
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 );
470
471
472         my $search_hash;
473
474         my $cache_key = md5_hex( $org_id . $class . $string );
475         my $id_array = OpenILS::Application::SearchCache->get_cache($cache_key);
476
477         if(ref($id_array)) {
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 };
483         }
484
485         my $method = $self->method_lookup("open-ils.search.biblio.marc");
486         if(!$method) {
487                 throw OpenSRF::EX::PANIC 
488                         ("Can't lookup method 'open-ils.search.biblio.marc'");
489         }
490
491         my ($records) = $method->run( $cat_search_hash->{$class}, $string );
492
493         my @cache_ids;
494
495         for my $i (@$records) { 
496                 if(defined($i->[0])) {
497                         push @cache_ids, $i->[0]; 
498                 }
499         }
500
501         my @ids = @cache_ids[ $offset..($offset+$limit) ];
502         my $size = @$records;
503
504         OpenILS::Application::SearchCache->put_cache( 
505                         $cache_key, \@cache_ids, $size );
506
507         warn "Returning cat.biblio.class.id $string\n";
508         return { count =>$size, ids => \@ids };
509
510 }
511
512
513 __PACKAGE__->register_method(
514         method  => "biblio_search_class",
515         api_name        => "open-ils.search.biblio.class",
516         argc            => 3, 
517         note            => "Searches biblio information by search class and returns the IDs",
518 );
519
520 sub biblio_search_class {
521
522         my( $self, $client, $class, $string, 
523                         $org_id, $org_type, $limit, $offset ) = @_;
524
525         warn "$org_id : $org_type : $limit :  $offset\n";
526
527         $offset         ||= 0;
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);
531
532         warn "$org_id : $org_type : $limit :  $offset\n";
533         warn "Searching biblio.class.id\n" . 
534                 "string: $string "              . 
535                 "\noffset: $offset\n"   .
536                 "limit: $limit\n"                       .
537                 "org_id: $org_id\n"             .
538                 "depth: $org_type\n" ;
539
540         $string = OpenILS::Application::Search->filter_search($string);
541         if(!$string) { return undef; }
542
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")
547         }
548
549         $class =~ s/\s+//g;
550
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")
555         }
556
557         # grab the mr id's from storage
558
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');
562
563         my $request = $session->request( $method, 
564                         term => $string, 
565                         org_unit => $org_id, 
566                         depth =>$org_type );
567
568         my $response = $request->recv();
569
570         if(UNIVERSAL::isa($response, "OpenSRF::EX")) {
571                 throw $response ($response->stringify);
572         }
573
574         my $count = $response->content;
575         warn "Received count $count\n";
576         # XXX check count size and respond accordingly
577
578         $request->finish();
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",
583                 term            => $string, 
584                 org_unit => $org_id, 
585                 depth           => $org_type, 
586                 limit           => $limit,
587                 offset  => $offset,
588                 );
589
590         warn "a\n";
591         $response = $request->recv();
592
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);
597         }
598
599         warn "b\n";
600
601         my $records = $response->content;
602         use Data::Dumper;
603         warn Dumper $records;
604
605         my @all_ids;
606
607         # if we just get one, it won't be wrapped in an array
608         if(!ref($records->[0])) {
609                 $records = [$records];
610         }
611
612         for my $i (@$records) { 
613                 if(defined($i->[0])) {
614                         push @all_ids, $i->[0]; 
615                 }
616         }
617
618         #my @ids = @all_ids[ $offset..($offset+$limit) ];
619         my @ids = @all_ids;
620         @ids = grep { defined($_) } @ids;
621
622         $request->finish();
623         $session->finish();
624         $session->disconnect();
625
626         warn "Returning biblio.class $string\n";
627         return { count =>$count, ids => \@ids };
628
629 }
630
631
632
633
634 __PACKAGE__->register_method(
635         method  => "biblio_mrid_to_modsbatch",
636         api_name        => "open-ils.search.biblio.metarecord.mods_slim.retrieve",
637 );
638
639 sub biblio_mrid_to_modsbatch {
640         my( $self, $client, $mrid ) = @_;
641
642         throw OpenSRF::EX::InvalidArg 
643                 ("search.biblio.metarecord_to_mods requires mr id")
644                         unless defined( $mrid );
645
646
647         my $metarecord = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage", 
648                         "open-ils.storage.direct.metabib.metarecord.retrieve", $mrid );
649
650         if(!$metarecord) {
651                 throw OpenSRF::EX::ERROR ("No metarecord exists with the given id: $mrid");
652         }
653
654         my $master_id = $metarecord->master_record();
655
656
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);
662         }
663
664
665
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}};
669
670         if(@ids < 1) { return undef; }
671
672         warn "Master ID is $master_id\n";
673         # grab the master record to start the mods batch 
674
675         my $record = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage", 
676                         "open-ils.storage.direct.biblio.record_entry.retrieve", $master_id );
677
678         if(!$record) {
679                 throw OpenSRF::EX::ERROR 
680                         ("No record returned with id $master_id");
681         }
682
683         my $u = OpenILS::Utils::ModsParser->new();
684         $u->start_mods_batch( $record->marc );
685         my $main_doc_id = $record->id();
686
687         @ids = grep { $_ ne $master_id } @ids;
688
689         warn "NON-Master IDs are @ids\n";
690
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 );
695
696         while( my $response = $request->recv() ) {
697
698                 next unless $response;
699                 if(UNIVERSAL::isa( $response,"OpenSRF::EX")) {
700                         throw $response ($response->stringify);
701                 }
702
703                 my $content = $response->content;
704
705                 if( $content ) {
706                         $u->push_mods_batch( $content->marc );
707                 }
708         }
709
710         my $mods = $u->finish_mods_batch();
711         $mods->doc_id($mrid);
712         $request->finish();
713
714         $client->respond_complete($mods);
715
716         my $mods_string = JSON->perl2JSON($mods->decast);
717         warn "Mods String to DB:\n $mods_string\n";
718
719         $metarecord->mods($mods_string);
720
721         my $req = $session->request( 
722                         "open-ils.storage.direct.metabib.metarecord.update", 
723                         $metarecord );
724
725
726         $req->gather(1);
727
728         $session->finish();
729         $session->disconnect();
730
731         return undef;
732
733 }
734
735
736
737 # converts a mr id into a list of record ids
738
739 __PACKAGE__->register_method(
740         method  => "biblio_mrid_to_record_ids",
741         api_name        => "open-ils.search.biblio.metarecord_to_records",
742 );
743
744 sub biblio_mrid_to_record_ids {
745         my( $self, $client, $mrid ) = @_;
746
747         throw OpenSRF::EX::InvalidArg 
748                 ("search.biblio.metarecord_to_record_ids requires mr id")
749                         unless defined( $mrid );
750
751         warn "Searching for record for MR $mrid\n";
752
753         my $mrmaps = OpenILS::Application::AppUtils->simple_scalar_request( "open-ils.storage", 
754                         "open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord", $mrid );
755
756         my @ids;
757         for my $map (@$mrmaps) { push @ids, $map->source(); }
758
759         warn "Recovered id's [@ids] for mr $mrid\n";
760
761         my $size = @ids;
762         warn "Size: $size\n";
763
764         return { count => $size, ids => \@ids };
765
766 }
767
768
769
770 1;