]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Authority.pm
LP#1745462: guard against scanning entire authority table
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Search / Authority.pm
1 package OpenILS::Application::Search::Authority;
2 use base qw/OpenILS::Application/;
3 use strict; use warnings;
4
5 use OpenILS::Utils::Fieldmapper;
6 use OpenILS::Application::AppUtils;
7 use XML::LibXML;
8 use XML::LibXSLT;
9 use OpenILS::Utils::CStoreEditor q/:funcs/;
10 use OpenSRF::Utils::Logger qw/$logger/;
11
12 use OpenSRF::Utils::JSON;
13
14 use Time::HiRes qw(time);
15 use OpenSRF::EX qw(:try);
16 use Digest::MD5 qw(md5_hex);
17
18 my $cache;
19
20
21 sub validate_authority {
22     my $self = shift;
23     my $client = shift;
24
25     my $session = OpenSRF::AppSession->create("open-ils.storage");
26     return $session->request( 'open-ils.storage.authority.validate.tag' => @_ )->gather(1);
27 }
28 __PACKAGE__->register_method(
29         method      => "validate_authority",
30         api_name    => "open-ils.search.authority.validate.tag",
31         argc        => 4, 
32         note        => "Validates authority data from existing controlled terms",
33 );              
34
35 sub validate_authority_return_records_by_id {
36     my $self = shift;
37     my $client = shift;
38
39     my $session = OpenSRF::AppSession->create("open-ils.storage");
40     return $session->request( 'open-ils.storage.authority.validate.tag.id_list' => @_ )->gather(1);
41 }
42 __PACKAGE__->register_method(
43         method      => "validate_authority_return_records_by_id",
44         api_name    => "open-ils.search.authority.validate.tag.id_list",
45         argc        => 4, 
46         note        => "Validates authority data from existing controlled terms",
47 );              
48
49 sub search_authority {
50     my $self = shift;
51     my $client = shift;
52
53     my $session = OpenSRF::AppSession->create("open-ils.storage");
54     return $session->request( 'open-ils.storage.authority.search.marc.atomic' => @_ )->gather(1);
55 }
56 __PACKAGE__->register_method(
57         method      => "search_authority",
58         api_name    => "open-ils.search.authority.fts",
59         argc        => 2, 
60         note        => "Searches authority data for existing controlled terms and crossrefs",
61 );              
62
63 sub search_authority_by_simple_normalize_heading {
64     my $self = shift;
65     my $client = shift;
66     my $marcxml = shift;
67     my $controlset = shift;
68
69     my $norm_heading_query = {
70         from => [ 'authority.simple_normalize_heading' => $marcxml ]
71     };
72
73     my $e = new_editor();
74     my $norm_heading = $e->json_query($norm_heading_query)->[0]->{'authority.simple_normalize_heading'};
75
76     unless (defined($norm_heading) && $norm_heading != '') {
77         return OpenILS::Event->new('BAD_PARAMS', note => 'Heading normalized to null or empty string');
78     }
79
80     my $query = {
81         select => { are => ['id'] },
82         from   => 'are',
83         where  => {
84             deleted => 'f',
85             simple_heading => {
86                 'startwith' => $norm_heading
87             },
88             defined($controlset) ? ( control_set => $controlset ) : ()
89         }
90     };
91
92     $client->respond($_->{id}) for @{ $e->json_query( $query ) };
93     $client->respond_complete;
94 }
95 __PACKAGE__->register_method(
96         method      => "search_authority_by_simple_normalize_heading",
97         api_name    => "open-ils.search.authority.simple_heading.from_xml",
98         argc        => 1, 
99         stream      => 1,
100         note        => "Searches authority data by main entry using marcxml, returning 'are' ids; params are marcxml and optional control-set-id",
101 );
102
103 sub search_authority_batch_by_simple_normalize_heading {
104     my $self = shift;
105     my $client = shift;
106     my $search_set = [@_];
107
108     my $m = $self->method_lookup('open-ils.search.authority.simple_heading.from_xml.atomic');
109
110     for my $s ( @$search_set ) {
111         for my $k ( keys %$s ) {
112             $client->respond( { $k => $m->run( $s->{$k}, $k ) } );
113         }
114     }
115
116     $client->respond_complete;
117 }
118 __PACKAGE__->register_method(
119         method      => "search_authority_batch_by_simple_normalize_heading",
120         api_name    => "open-ils.search.authority.simple_heading.from_xml.batch",
121         argc        => 1, 
122         stream      => 1,
123         note        => "Searches authority data by main entry using marcxml, in control-set batches, returning 'are' ids; params are hashes of { control-set-id => marcxml }",
124 );
125
126
127 sub crossref_authority {
128     my $self = shift;
129     my $client = shift;
130     my $class = shift;
131     my $term = shift;
132     my $limit = shift || 10;
133
134     my $session = OpenSRF::AppSession->create("open-ils.storage");
135
136     # Avoid generating spurious errors for more granular indexes, like author|personal
137     $class =~ s/^(.*?)\|.*?$/$1/;
138
139     $logger->info("authority xref search for $class=$term, limit=$limit");
140     my $fr = $session->request(
141         "open-ils.storage.authority.$class.see_from.controlled.atomic",$term, $limit)->gather(1);
142     my $al = $session->request(
143         "open-ils.storage.authority.$class.see_also_from.controlled.atomic",$term, $limit)->gather(1);
144
145     my $data = _auth_flatten( $term, $fr, $al, 1 );
146
147     return $data;
148 }
149
150 sub _auth_flatten {
151     my $term = shift;
152     my $fr = shift;
153     my $al = shift;
154     my $limit = shift;
155
156     my %hash = ();
157     for my $x (@$fr) {
158         my $string = $$x[0];
159         for my $i (1..10) {
160             last unless ($$x[$i]);
161             if ($string =~ /\W$/o) {
162                 $string .= ' '.$$x[$i];
163             } else {
164                 $string .= ' -- '.$$x[$i];
165             }
166         }
167         next if (lc($string) eq lc($term));
168         $hash{$string}++;
169         $hash{$string}++ if (lc($$x[0]) eq lc($term));
170     }
171     my $from = [keys %hash]; #[ sort { $hash{$b} <=> $hash{$a} || $a cmp $b } keys %hash ];
172
173 #   $from = [ @$from[0..4] ] if $limit;
174
175     %hash = ();
176     for my $x (@$al) {
177         my $string = $$x[0];
178         for my $i (1..10) {
179             last unless ($$x[$i]);
180             if ($string =~ /\W$/o) {
181                 $string .= ' '.$$x[$i];
182             } else {
183                 $string .= ' -- '.$$x[$i];
184             }
185         }
186         next if (lc($string) eq lc($term));
187         $hash{$string}++;
188         $hash{$string}++ if (lc($$x[0]) eq lc($term));
189     }
190     my $also = [keys %hash]; #[ sort { $hash{$b} <=> $hash{$a} || $a cmp $b } keys %hash ];
191
192 #   $also = [ @$also[0..4] ] if $limit;
193
194     #warn Dumper( { from => $from, also => $also } );
195
196     return { from => $from, also => $also };
197 }
198
199 __PACKAGE__->register_method(
200         method      => "crossref_authority",
201         api_name    => "open-ils.search.authority.crossref",
202         argc        => 2, 
203         note        => "Searches authority data for existing controlled terms and crossrefs",
204 );              
205
206 __PACKAGE__->register_method(
207     #method     => "new_crossref_authority_batch",
208     method      => "crossref_authority_batch2",
209     api_name    => "open-ils.search.authority.crossref.batch",
210     argc        => 1, 
211     note        => <<"    NOTE");
212     Takes an array of class,term pair sub-arrays and performs an authority lookup for each
213
214     PARAMS( [ ["subject", "earth"], ["author","shakespeare"] ] );
215
216     Returns an object like so:
217     {
218         "classname" : {
219             "term" : { "from" : [ ...], "also" : [...] }
220             "term2" : { "from" : [ ...], "also" : [...] }
221         }
222     }
223     NOTE
224
225 sub new_crossref_authority_batch {
226     my( $self, $client, $reqs ) = @_;
227
228     my $response = {};
229     my $lastr = [];
230     my $session = OpenSRF::AppSession->create("open-ils.storage");
231
232     for my $req (@$reqs) {
233
234         my $class = $req->[0];
235         my $term = $req->[1];
236         next unless $class and $term;
237         $logger->info("Sending authority request for $class : $term");
238         my $fr = $session->request("open-ils.storage.authority.$class.see_from.controlled.atomic",$term, 10)->gather(1);
239         my $al = $session->request("open-ils.storage.authority.$class.see_also_from.controlled.atomic",$term, 10)->gather(1);
240
241         $response->{$class} = {} unless exists $response->{$class};
242         $response->{$class}->{$term} = _auth_flatten( $term, $fr, $al, 1 );
243
244     }
245
246     #warn Dumper( $response );
247     return $response;
248 }
249
250 sub crossref_authority_batch {
251     my( $self, $client, $reqs ) = @_;
252
253     my $response = {};
254     my $lastr = [];
255     my $session = OpenSRF::AppSession->create("open-ils.storage");
256
257     for my $req (@$reqs) {
258
259         my $class = $req->[0];
260         my $term = $req->[1];
261         next unless $class and $term;
262         $logger->info("Sending authority request for $class : $term");
263         my $freq = $session->request("open-ils.storage.authority.$class.see_from.controlled.atomic",$term, 10);
264         my $areq = $session->request("open-ils.storage.authority.$class.see_also_from.controlled.atomic",$term, 10);
265
266         if( $lastr->[0] ) { #process old data while waiting on new data
267             my $cls = $lastr->[0];
268             my $trm = $lastr->[1];
269             my $fr  = $lastr->[2];
270             my $al  = $lastr->[3];
271             $response->{$cls} = {} unless exists $response->{$cls};
272             $response->{$cls}->{$trm} = _auth_flatten( $trm, $fr, $al, 1 );
273         }
274
275         $lastr->[0] = $class;
276         $lastr->[1] = $term; 
277         $lastr->[2] = $freq->gather(1);
278         $lastr->[3] = $areq->gather(1);
279     }
280
281     if( $lastr->[0] ) { #process old data while waiting on new data
282         my $cls = $lastr->[0];
283         my $trm = $lastr->[1];
284         my $fr  = $lastr->[2];
285         my $al  = $lastr->[3];
286         $response->{$cls} = {} unless exists $response->{$cls};
287         $response->{$cls}->{$trm} = _auth_flatten( $trm, $fr, $al, 1);
288     }
289
290     return $response;
291 }
292
293
294
295
296 sub crossref_authority_batch2 {
297     my( $self, $client, $reqs ) = @_;
298
299     my $response = {};
300     my $lastr = [];
301     my $session = OpenSRF::AppSession->create("open-ils.storage");
302
303     $cache = OpenSRF::Utils::Cache->new('global') unless $cache;
304
305     for my $req (@$reqs) {
306
307         my $class = $req->[0];
308         my $term = $req->[1];
309         next unless $class and $term;
310
311         my $t = $term;
312         $t =~ s/\s//og;
313         my $cdata = $cache->get_cache("oils_authority_${class}_$t");
314
315         if( $cdata ) {
316             $logger->debug("returning authority response from cache..");
317             $response->{$class} = {} unless exists $response->{$class};
318             $response->{$class}->{$term} = $cdata;
319             next;
320         }
321
322         $logger->debug("authority data not found in cache.. fetching from storage");
323
324         $logger->info("Sending authority request for $class : $term");
325         my $freq = $session->request("open-ils.storage.authority.$class.see_from.controlled.atomic",$term, 10);
326         my $areq = $session->request("open-ils.storage.authority.$class.see_also_from.controlled.atomic",$term, 10);
327         my $fr = $freq->gather(1);  
328         my $al = $areq->gather(1);
329         $response->{$class} = {} unless exists $response->{$class};
330         my $auth = _auth_flatten( $term, $fr, $al, 1 );
331
332         my $timeout = 7200; #two hours
333         $timeout = 300 if @{$auth->{from}} or @{$auth->{also}}; # 5 minutes
334         $response->{$class}->{$term} = $auth;
335         $logger->debug("adding authority lookup to cache with timeout $timeout");
336         $cache->put_cache("oils_authority_${class}_$t", $auth, $timeout);
337     }
338     return $response;
339 }
340
341
342
343 1;