]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher.pm
fixing search_fts and adding param parsing to the "search*" interface
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / Publisher.pm
1 package OpenILS::Application::Storage::Publisher;
2 use base qw/OpenILS::Application::Storage/;
3 our $VERSION = 1;
4
5 use Digest::MD5 qw/md5_hex/;
6 use OpenSRF::EX qw/:try/;;
7 use OpenSRF::Utils::Logger qw/:level/;
8 use OpenILS::Utils::Fieldmapper;
9
10 my $log = 'OpenSRF::Utils::Logger';
11
12
13 sub register_method {
14         my $class = shift;
15         my %args = @_;
16         my %dup_args = %args;
17
18         $class = ref($class) || $class;
19
20         $args{package} ||= $class;
21         __PACKAGE__->SUPER::register_method( %args );
22
23         if (exists($dup_args{cachable}) and $dup_args{cachable}) {
24                 (my $name = $dup_args{api_name}) =~ s/^open-ils\.storage/open-ils.storage.cachable/o;
25                 if ($name ne $dup_args{api_name}) {
26                         $dup_args{real_api_name} = $dup_args{api_name};
27                         $dup_args{method} = 'cachable_wrapper';
28                         $dup_args{api_name} = $name;
29                         $dup_args{package} = __PACKAGE__;
30                         __PACKAGE__->SUPER::register_method( %dup_args );
31                 }
32         }
33 }
34
35 sub cachable_wrapper {
36         my $self = shift;
37         my $client = shift;
38         my @args = @_;
39
40         my %cache_args = (
41                 limit           => 100,
42                 offset          => 0,
43                 timeout         => 7200,
44                 cache_page_size => 1000,
45         );
46
47         my @real_args;
48         my $key_string = $self->api_name;
49         for (my $ind = 0; $ind < scalar(@args); $ind++) {
50                 if (    $args[$ind] eq 'limit' ||
51                         $args[$ind] eq 'offset' ||
52                         $args[$ind] eq 'cache_page_size' ||
53                         $args[$ind] eq 'timeout' ) {
54
55                         my $key_ind = $ind;
56                         $ind++;
57                         my $value_ind = $ind;
58                         $cache_args{$args[$key_ind]} = $args[$value_ind];
59                         $log->debug("Cache limiter value for $args[$key_ind] is $args[$value_ind]", INTERNAL);
60                         next;
61                 }
62                 $key_string .= $args[$ind];
63                 $log->debug("Partial cache key value is $args[$ind]", INTERNAL);
64                 push @real_args, $args[$ind];
65         }
66
67         my $cache_page = int($cache_args{offset} / $cache_args{cache_page_size});
68         my $cache_key;
69         {       use bytes;
70                 $cache_key = md5_hex($key_string.$cache_page);
71         }
72
73         $log->debug("Key string for cache lookup is $key_string -> $cache_key", DEBUG);
74         $log->debug("Cache page is $cache_page", DEBUG);
75
76         my $cached_res = OpenSRF::Utils::Cache->new->get_cache( $cache_key );
77         if (defined $cached_res) {
78                 $log->debug("Found ".scalar(@$cached_res)." records in the cache", INFO);
79                 $log->debug("Values from cache: ".join(', ', @$cached_res), INTERNAL);
80                 my $start = int($cache_args{offset} - ($cache_page * $cache_args{cache_page_size}));
81                 my $end = int($start + $cache_args{limit} - 1);
82                 $log->debug("Responding with values from ".$start.' to '.$end,DEBUG);
83                 $client->respond( $_ ) for ( grep { defined } @$cached_res[ $start .. $end ]);
84                 return undef;
85         }
86
87         my $method = $self->method_lookup($self->{real_api_name});
88         my @res = $method->run(@real_args);
89
90
91         $client->respond( $_ ) for ( grep { defined } @res[$cache_args{offset} .. int($cache_args{offset} + $cache_args{limit} - 1)] );
92
93         $log->debug("Saving values from ".int($cache_page * $cache_args{cache_page_size})." to ".
94                 int(($cache_page + 1) * $cache_args{cache_page_size}). "to the cache", INTERNAL);
95         try {
96                 OpenSRF::Utils::Cache->new->put_cache(
97                         $cache_key =>
98                         [@res[int($cache_page * $cache_args{cache_page_size}) .. int(($cache_page + 1) * $cache_args{cache_page_size}) ]] =>
99                         $cache_args{timeout}
100                 );
101         } catch Error with {
102                 my $e = shift;
103                 $log->error("Cache seems to be down, $e");
104         };
105
106         return undef;
107 }
108
109 sub retrieve_node {
110         my $self = shift;
111         my $client = shift;
112         my @ids = @_;
113
114         my $cdbi = $self->{cdbi};
115
116         for my $id ( @ids ) {
117                 next unless ($id);
118
119                 my ($rec) = $cdbi->fast_fieldmapper($id);
120                 if ($self->api_name !~ /batch/o) {
121                         return $rec if ($rec);
122                 }
123                 $client->respond($rec);
124         }
125         return undef;
126 }
127
128 sub search {
129         my $self = shift;
130         my $client = shift;
131         my @args = @_;
132         #my $searches = shift;
133         #my $options = shift;
134
135         my $cdbi = $self->{cdbi};
136
137         (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
138
139         #$log->debug("Searching $cdbi for { ".
140         #       join(',', map { "$_ => $$searches{$_}" } keys %$searches).
141         #       " } using $search_type",DEBUG);
142
143         #for my $obj ($cdbi->$search_type($searches, $options)) {
144         for my $obj ($cdbi->$search_type(@args)) {
145                 warn "$obj -> ".ref($obj);
146                 next unless ref($obj);
147                 $client->respond( $obj->to_fieldmapper );
148         }
149         return undef;
150 }
151
152 sub search_one_field {
153         my $self = shift;
154         my $client = shift;
155         my @args = @_;
156
157         (my $field = $self->api_name) =~ s/.*\.([^\.]+)$/$1/o;
158
159         return search( $self, $client, $field, @args );
160 }
161
162 sub old_search_one_field {
163         my $self = shift;
164         my $client = shift;
165         my @terms = @_;
166
167         (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
168         (my $col = $self->api_name) =~ s/.*\.$search_type\.([^.]+).*/$1/;
169         my $cdbi = $self->{cdbi};
170
171         my $like = 0;
172         $like = 1 if ($search_type =~ /like$/o);
173         $like = 2 if ($search_type =~ /fts$/o);
174         $like = 3 if ($search_type =~ /regex$/o);
175
176         for my $term (@terms) {
177                 $log->debug("Searching $cdbi for $col using type $search_type, value '$term'",DEBUG);
178                 if (@terms == 1) {
179                         return [ $cdbi->fast_fieldmapper($term,$col,$like) ];
180                 }
181                 $client->respond( [ $cdbi->fast_fieldmapper($term,$col,$like) ] );
182         }
183         return undef;
184 }
185
186
187 sub create_node {
188         my $self = shift;
189         my $client = shift;
190         my $node = shift;
191
192         my $cdbi = $self->{cdbi};
193
194         my $success;
195         try {
196                 my $rec = $cdbi->create($node);
197                 $success = $rec->id if ($rec);
198         } catch Error with {
199                 $success = 0;
200         };
201
202         return $success;
203 }
204
205 sub update_node {
206         my $self = shift;
207         my $client = shift;
208         my $node = shift;
209
210         my $cdbi = $self->{cdbi};
211
212         return $cdbi->update($node);
213 }
214
215 sub mass_delete {
216         my $self = shift;
217         my $client = shift;
218         my $search = shift;
219
220         my $where = 'WHERE ';
221
222         my $cdbi = $self->{cdbi};
223         my $table = $cdbi->table;
224
225         my @keys = sort keys %$search;
226         
227         my @binds;
228         my @wheres;
229         for my $col ( @keys ) {
230                 if (ref($$search{$col}) and ref($$search{$col}) =~ /ARRAY/o) {
231                         push @wheres, "$col IN (" . join(',', map { '?' } @{ $$search{$col} }) . ')';
232                         push @binds, map { "$_" } @{ $$search{$col} };
233                 } else {
234                         push @wheres, "$col = ?";
235                         push @binds, $$search{$col};
236                 }
237         }
238         $where .= join ' AND ', @wheres;
239
240         my $delete = "DELETE FROM $table $where";
241
242         $log->debug("Performing MASS deletion : $delete",DEBUG);
243
244         my $dbh = $cdbi->db_Main;
245         my $success = 1;
246         try {
247                 my $sth = $dbh->prepare($delete);
248                 $sth->execute( @binds );
249                 $sth->finish;
250                 $log->debug("MASS Delete succeeded",DEBUG);
251         } catch Error with {
252                 $log->debug("MASS Delete FAILED : ".shift(),DEBUG);
253                 $success = 0;
254         };
255         return $success;
256 }
257
258 sub delete_node {
259         my $self = shift;
260         my $client = shift;
261         my $node = shift;
262
263         my $cdbi = $self->{cdbi};
264
265         my $success = 1;
266         try {
267                 $success = $cdbi->delete($node);
268         } catch Error with {
269                 $success = 0;
270         };
271         return $success;
272 }
273
274 sub batch_call {
275         my $self = shift;
276         my $client = shift;
277         my @nodes = @_;
278
279         my $cdbi = $self->{cdbi};
280         my $api_name = $self->api_name;
281         (my $single_call_api_name = $api_name) =~ s/batch\.//o;
282
283         $log->debug("Default $api_name looking up $single_call_api_name...",INTERNAL);
284         my $method = $self->method_lookup($single_call_api_name);
285
286         my @success;
287         while ( my $node = shift(@nodes) ) {
288                 my ($res) = $method->run( $node ); 
289                 push(@success, 1) if ($res >= 0);
290         }
291
292         my $insert_total = 0;
293         $insert_total += $_ for (@success);
294
295         return $insert_total;
296 }
297
298 eval '
299 use OpenILS::Application::Storage::Publisher::actor;
300 use OpenILS::Application::Storage::Publisher::action;
301 use OpenILS::Application::Storage::Publisher::asset;
302 use OpenILS::Application::Storage::Publisher::biblio;
303 use OpenILS::Application::Storage::Publisher::config;
304 use OpenILS::Application::Storage::Publisher::metabib;
305 use OpenILS::Application::Storage::Publisher::permission;
306 ';
307
308 for my $fmclass ( (Fieldmapper->classes) ) {
309
310         next if ($fmclass->is_virtual);
311
312         $log->debug("Generating methods for Fieldmapper class $fmclass", DEBUG);
313
314         (my $cdbi = $fmclass) =~ s/^Fieldmapper:://o;
315         (my $class = $cdbi) =~ s/::.*//o;
316         (my $api_class = $cdbi) =~ s/::/./go;
317         my $registration_class = __PACKAGE__ . "::$class";
318         my $api_prefix = 'open-ils.storage.direct.'.$api_class;
319
320         # Create the search methods
321         unless ( __PACKAGE__->is_registered( $api_prefix.'.search' ) ) {
322                 __PACKAGE__->register_method(
323                         api_name        => $api_prefix.'.search',
324                         method          => 'search',
325                         api_level       => 1,
326                         stream          => 1,
327                         cdbi            => $cdbi,
328                         cachable        => 1,
329                 );
330         }
331
332         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like' ) ) {
333                 __PACKAGE__->register_method(
334                         api_name        => $api_prefix.'.search_like',
335                         method          => 'search',
336                         api_level       => 1,
337                         stream          => 1,
338                         cdbi            => $cdbi,
339                         cachable        => 1,
340                 );
341         }
342
343         if (\&Class::DBI::search_fts) {
344                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts' ) ) {
345                         __PACKAGE__->register_method(
346                                 api_name        => $api_prefix.'.search_fts',
347                                 method          => 'search',
348                                 api_level       => 1,
349                                 stream          => 1,
350                                 cdbi            => $cdbi,
351                                 cachable        => 1,
352                         );
353                 }
354         }
355
356         if (\&Class::DBI::search_regex) {
357                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex' ) ) {
358                         __PACKAGE__->register_method(
359                                 api_name        => $api_prefix.'.search_regex',
360                                 method          => 'search',
361                                 api_level       => 1,
362                                 stream          => 1,
363                                 cdbi            => $cdbi,
364                                 cachable        => 1,
365                         );
366                 }
367         }
368
369         if (\&Class::DBI::search_ilike) {
370                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike' ) ) {
371                         __PACKAGE__->register_method(
372                                 api_name        => $api_prefix.'.search_ilike',
373                                 method          => 'search',
374                                 api_level       => 1,
375                                 stream          => 1,
376                                 cdbi            => $cdbi,
377                                 cachable        => 1,
378                         );
379                 }
380         }
381
382         # Create the retrieve method
383         unless ( __PACKAGE__->is_registered( $api_prefix.'.retrieve' ) ) {
384                 __PACKAGE__->register_method(
385                         api_name        => $api_prefix.'.retrieve',
386                         method          => 'retrieve_node',
387                         api_level       => 1,
388                         cdbi            => $cdbi,
389                         cachable        => 1,
390                 );
391         }
392
393         # Create the batch retrieve method
394         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.retrieve' ) ) {
395                 __PACKAGE__->register_method(
396                         api_name        => $api_prefix.'.batch.retrieve',
397                         method          => 'retrieve_node',
398                         api_level       => 1,
399                         stream          => 1,
400                         cdbi            => $cdbi,
401                         cachable        => 1,
402                 );
403         }
404
405         for my $field ($fmclass->real_fields) {
406                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search.'.$field ) ) {
407                         __PACKAGE__->register_method(
408                                 api_name        => $api_prefix.'.search.'.$field,
409                                 method          => 'search_one_field',
410                                 api_level       => 1,
411                                 cdbi            => $cdbi,
412                                 cachable        => 1,
413                         );
414                 }
415                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like.'.$field ) ) {
416                         __PACKAGE__->register_method(
417                                 api_name        => $api_prefix.'.search_like.'.$field,
418                                 method          => 'search_one_field',
419                                 api_level       => 1,
420                                 cdbi            => $cdbi,
421                                 cachable        => 1,
422                         );
423                 }
424                 if (\&Class::DBI::search_fts) {
425                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts.'.$field ) ) {
426                                 __PACKAGE__->register_method(
427                                         api_name        => $api_prefix.'.search_fts.'.$field,
428                                         method          => 'search_one_field',
429                                         api_level       => 1,
430                                         cdbi            => $cdbi,
431                                         cachable        => 1,
432                                 );
433                         }
434                 }
435                 if (\&Class::DBI::search_regex) {
436                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex.'.$field ) ) {
437                                 __PACKAGE__->register_method(
438                                         api_name        => $api_prefix.'.search_regex.'.$field,
439                                         method          => 'search_one_field',
440                                         api_level       => 1,
441                                         cdbi            => $cdbi,
442                                         cachable        => 1,
443                                 );
444                         }
445                 }
446                 if (\&Class::DBI::search_ilike) {
447                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike.'.$field ) ) {
448                                 __PACKAGE__->register_method(
449                                         api_name        => $api_prefix.'.search_ilike.'.$field,
450                                         method          => 'search_one_field',
451                                         api_level       => 1,
452                                         cdbi            => $cdbi,
453                                         cachable        => 1,
454                                 );
455                         }
456                 }
457         }
458
459
460         # Create the create method
461         unless ( __PACKAGE__->is_registered( $api_prefix.'.create' ) ) {
462                 __PACKAGE__->register_method(
463                         api_name        => $api_prefix.'.create',
464                         method          => 'create_node',
465                         api_level       => 1,
466                         cdbi            => $cdbi,
467                 );
468         }
469
470         # Create the batch create method
471         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.create' ) ) {
472                 __PACKAGE__->register_method(
473                         api_name        => $api_prefix.'.batch.create',
474                         method          => 'batch_call',
475                         api_level       => 1,
476                         cdbi            => $cdbi,
477                 );
478         }
479
480         # Create the update method
481         unless ( __PACKAGE__->is_registered( $api_prefix.'.update' ) ) {
482                 __PACKAGE__->register_method(
483                         api_name        => $api_prefix.'.update',
484                         method          => 'update_node',
485                         api_level       => 1,
486                         cdbi            => $cdbi,
487                 );
488         }
489
490         # Create the batch update method
491         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.update' ) ) {
492                 __PACKAGE__->register_method(
493                         api_name        => $api_prefix.'.batch.update',
494                         method          => 'batch_call',
495                         api_level       => 1,
496                         cdbi            => $cdbi,
497                 );
498         }
499
500         # Create the delete method
501         unless ( __PACKAGE__->is_registered( $api_prefix.'.delete' ) ) {
502                 __PACKAGE__->register_method(
503                         api_name        => $api_prefix.'.delete',
504                         method          => 'delete_node',
505                         api_level       => 1,
506                         cdbi            => $cdbi,
507                 );
508         }
509
510         # Create the batch delete method
511         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.delete' ) ) {
512                 __PACKAGE__->register_method(
513                         api_name        => $api_prefix.'.batch.delete',
514                         method          => 'batch_call',
515                         api_level       => 1,
516                         cdbi            => $cdbi,
517                 );
518         }
519
520         # Create the search-based mass delete method
521         unless ( __PACKAGE__->is_registered( $api_prefix.'.mass_delete' ) ) {
522                 __PACKAGE__->register_method(
523                         api_name        => $api_prefix.'.mass_delete',
524                         method          => 'mass_delete',
525                         api_level       => 1,
526                         cdbi            => $cdbi,
527                 );
528         }
529
530 }
531
532 1;