]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher.pm
abstract search method
[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_where {
129         my $self = shift;
130         my $client = shift;
131         my @args = @_;
132
133         my $cdbi = $self->{cdbi};
134
135         for my $obj ($cdbi->search_where(@args)) {
136                 next unless ref($obj);
137                 $client->respond( $obj->to_fieldmapper );
138         }
139         return undef;
140 }
141
142 sub search {
143         my $self = shift;
144         my $client = shift;
145         my @args = @_;
146
147         my $cdbi = $self->{cdbi};
148
149         (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
150
151         for my $obj ($cdbi->$search_type(@args)) {
152                 next unless ref($obj);
153                 $client->respond( $obj->to_fieldmapper );
154         }
155         return undef;
156 }
157
158 sub search_one_field {
159         my $self = shift;
160         my $client = shift;
161         my @args = @_;
162
163         (my $field = $self->api_name) =~ s/.*\.([^\.]+)$/$1/o;
164
165         return search( $self, $client, $field, @args );
166 }
167
168 sub old_search_one_field {
169         my $self = shift;
170         my $client = shift;
171         my @terms = @_;
172
173         (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
174         (my $col = $self->api_name) =~ s/.*\.$search_type\.([^.]+).*/$1/;
175         my $cdbi = $self->{cdbi};
176
177         my $like = 0;
178         $like = 1 if ($search_type =~ /like$/o);
179         $like = 2 if ($search_type =~ /fts$/o);
180         $like = 3 if ($search_type =~ /regex$/o);
181
182         for my $term (@terms) {
183                 $log->debug("Searching $cdbi for $col using type $search_type, value '$term'",DEBUG);
184                 if (@terms == 1) {
185                         return [ $cdbi->fast_fieldmapper($term,$col,$like) ];
186                 }
187                 $client->respond( [ $cdbi->fast_fieldmapper($term,$col,$like) ] );
188         }
189         return undef;
190 }
191
192
193 sub create_node {
194         my $self = shift;
195         my $client = shift;
196         my $node = shift;
197
198         my $cdbi = $self->{cdbi};
199
200         my $success;
201         try {
202                 my $rec = $cdbi->create($node);
203                 $success = $rec->id if ($rec);
204         } catch Error with {
205                 $success = 0;
206         };
207
208         return $success;
209 }
210
211 sub update_node {
212         my $self = shift;
213         my $client = shift;
214         my $node = shift;
215
216         my $cdbi = $self->{cdbi};
217
218         return $cdbi->update($node);
219 }
220
221 sub mass_delete {
222         my $self = shift;
223         my $client = shift;
224         my $search = shift;
225
226         my $where = 'WHERE ';
227
228         my $cdbi = $self->{cdbi};
229         my $table = $cdbi->table;
230
231         my @keys = sort keys %$search;
232         
233         my @binds;
234         my @wheres;
235         for my $col ( @keys ) {
236                 if (ref($$search{$col}) and ref($$search{$col}) =~ /ARRAY/o) {
237                         push @wheres, "$col IN (" . join(',', map { '?' } @{ $$search{$col} }) . ')';
238                         push @binds, map { "$_" } @{ $$search{$col} };
239                 } else {
240                         push @wheres, "$col = ?";
241                         push @binds, $$search{$col};
242                 }
243         }
244         $where .= join ' AND ', @wheres;
245
246         my $delete = "DELETE FROM $table $where";
247
248         $log->debug("Performing MASS deletion : $delete",DEBUG);
249
250         my $dbh = $cdbi->db_Main;
251         my $success = 1;
252         try {
253                 my $sth = $dbh->prepare($delete);
254                 $sth->execute( @binds );
255                 $sth->finish;
256                 $log->debug("MASS Delete succeeded",DEBUG);
257         } catch Error with {
258                 $log->debug("MASS Delete FAILED : ".shift(),DEBUG);
259                 $success = 0;
260         };
261         return $success;
262 }
263
264 sub delete_node {
265         my $self = shift;
266         my $client = shift;
267         my $node = shift;
268
269         my $cdbi = $self->{cdbi};
270
271         my $success = 1;
272         try {
273                 $success = $cdbi->delete($node);
274         } catch Error with {
275                 $success = 0;
276         };
277         return $success;
278 }
279
280 sub batch_call {
281         my $self = shift;
282         my $client = shift;
283         my @nodes = @_;
284
285         my $cdbi = $self->{cdbi};
286         my $api_name = $self->api_name;
287         (my $single_call_api_name = $api_name) =~ s/batch\.//o;
288
289         $log->debug("Default $api_name looking up $single_call_api_name...",INTERNAL);
290         my $method = $self->method_lookup($single_call_api_name);
291
292         my @success;
293         while ( my $node = shift(@nodes) ) {
294                 my ($res) = $method->run( $node ); 
295                 push(@success, 1) if ($res >= 0);
296         }
297
298         my $insert_total = 0;
299         $insert_total += $_ for (@success);
300
301         return $insert_total;
302 }
303
304 eval '
305 use OpenILS::Application::Storage::Publisher::actor;
306 use OpenILS::Application::Storage::Publisher::action;
307 use OpenILS::Application::Storage::Publisher::asset;
308 use OpenILS::Application::Storage::Publisher::biblio;
309 use OpenILS::Application::Storage::Publisher::config;
310 use OpenILS::Application::Storage::Publisher::metabib;
311 use OpenILS::Application::Storage::Publisher::permission;
312 ';
313
314 for my $fmclass ( (Fieldmapper->classes) ) {
315
316         next if ($fmclass->is_virtual);
317
318         $log->debug("Generating methods for Fieldmapper class $fmclass", DEBUG);
319
320         (my $cdbi = $fmclass) =~ s/^Fieldmapper:://o;
321         (my $class = $cdbi) =~ s/::.*//o;
322         (my $api_class = $cdbi) =~ s/::/./go;
323         my $registration_class = __PACKAGE__ . "::$class";
324         my $api_prefix = 'open-ils.storage.direct.'.$api_class;
325
326         # Create the search methods
327         unless ( __PACKAGE__->is_registered( $api_prefix.'.search' ) ) {
328                 __PACKAGE__->register_method(
329                         api_name        => $api_prefix.'.search',
330                         method          => 'search',
331                         api_level       => 1,
332                         stream          => 1,
333                         cdbi            => $cdbi,
334                         cachable        => 1,
335                 );
336         }
337
338         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_where' ) ) {
339                 __PACKAGE__->register_method(
340                         api_name        => $api_prefix.'.search_where',
341                         method          => 'search_where',
342                         api_level       => 1,
343                         stream          => 1,
344                         cdbi            => $cdbi,
345                         cachable        => 1,
346                 );
347         }
348
349         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like' ) ) {
350                 __PACKAGE__->register_method(
351                         api_name        => $api_prefix.'.search_like',
352                         method          => 'search',
353                         api_level       => 1,
354                         stream          => 1,
355                         cdbi            => $cdbi,
356                         cachable        => 1,
357                 );
358         }
359
360         if (\&Class::DBI::search_fts) {
361                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts' ) ) {
362                         __PACKAGE__->register_method(
363                                 api_name        => $api_prefix.'.search_fts',
364                                 method          => 'search',
365                                 api_level       => 1,
366                                 stream          => 1,
367                                 cdbi            => $cdbi,
368                                 cachable        => 1,
369                         );
370                 }
371         }
372
373         if (\&Class::DBI::search_regex) {
374                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex' ) ) {
375                         __PACKAGE__->register_method(
376                                 api_name        => $api_prefix.'.search_regex',
377                                 method          => 'search',
378                                 api_level       => 1,
379                                 stream          => 1,
380                                 cdbi            => $cdbi,
381                                 cachable        => 1,
382                         );
383                 }
384         }
385
386         if (\&Class::DBI::search_ilike) {
387                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike' ) ) {
388                         __PACKAGE__->register_method(
389                                 api_name        => $api_prefix.'.search_ilike',
390                                 method          => 'search',
391                                 api_level       => 1,
392                                 stream          => 1,
393                                 cdbi            => $cdbi,
394                                 cachable        => 1,
395                         );
396                 }
397         }
398
399         # Create the retrieve method
400         unless ( __PACKAGE__->is_registered( $api_prefix.'.retrieve' ) ) {
401                 __PACKAGE__->register_method(
402                         api_name        => $api_prefix.'.retrieve',
403                         method          => 'retrieve_node',
404                         api_level       => 1,
405                         cdbi            => $cdbi,
406                         cachable        => 1,
407                 );
408         }
409
410         # Create the batch retrieve method
411         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.retrieve' ) ) {
412                 __PACKAGE__->register_method(
413                         api_name        => $api_prefix.'.batch.retrieve',
414                         method          => 'retrieve_node',
415                         api_level       => 1,
416                         stream          => 1,
417                         cdbi            => $cdbi,
418                         cachable        => 1,
419                 );
420         }
421
422         for my $field ($fmclass->real_fields) {
423                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search.'.$field ) ) {
424                         __PACKAGE__->register_method(
425                                 api_name        => $api_prefix.'.search.'.$field,
426                                 method          => 'search_one_field',
427                                 api_level       => 1,
428                                 cdbi            => $cdbi,
429                                 cachable        => 1,
430                                 stream          => 1,
431                         );
432                 }
433                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like.'.$field ) ) {
434                         __PACKAGE__->register_method(
435                                 api_name        => $api_prefix.'.search_like.'.$field,
436                                 method          => 'search_one_field',
437                                 api_level       => 1,
438                                 cdbi            => $cdbi,
439                                 cachable        => 1,
440                                 stream          => 1,
441                         );
442                 }
443                 if (\&Class::DBI::search_fts) {
444                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts.'.$field ) ) {
445                                 __PACKAGE__->register_method(
446                                         api_name        => $api_prefix.'.search_fts.'.$field,
447                                         method          => 'search_one_field',
448                                         api_level       => 1,
449                                         cdbi            => $cdbi,
450                                         cachable        => 1,
451                                         stream          => 1,
452                                 );
453                         }
454                 }
455                 if (\&Class::DBI::search_regex) {
456                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex.'.$field ) ) {
457                                 __PACKAGE__->register_method(
458                                         api_name        => $api_prefix.'.search_regex.'.$field,
459                                         method          => 'search_one_field',
460                                         api_level       => 1,
461                                         cdbi            => $cdbi,
462                                         cachable        => 1,
463                                         stream          => 1,
464                                 );
465                         }
466                 }
467                 if (\&Class::DBI::search_ilike) {
468                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike.'.$field ) ) {
469                                 __PACKAGE__->register_method(
470                                         api_name        => $api_prefix.'.search_ilike.'.$field,
471                                         method          => 'search_one_field',
472                                         api_level       => 1,
473                                         cdbi            => $cdbi,
474                                         cachable        => 1,
475                                         stream          => 1,
476                                 );
477                         }
478                 }
479         }
480
481
482         # Create the create method
483         unless ( __PACKAGE__->is_registered( $api_prefix.'.create' ) ) {
484                 __PACKAGE__->register_method(
485                         api_name        => $api_prefix.'.create',
486                         method          => 'create_node',
487                         api_level       => 1,
488                         cdbi            => $cdbi,
489                 );
490         }
491
492         # Create the batch create method
493         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.create' ) ) {
494                 __PACKAGE__->register_method(
495                         api_name        => $api_prefix.'.batch.create',
496                         method          => 'batch_call',
497                         api_level       => 1,
498                         cdbi            => $cdbi,
499                 );
500         }
501
502         # Create the update method
503         unless ( __PACKAGE__->is_registered( $api_prefix.'.update' ) ) {
504                 __PACKAGE__->register_method(
505                         api_name        => $api_prefix.'.update',
506                         method          => 'update_node',
507                         api_level       => 1,
508                         cdbi            => $cdbi,
509                 );
510         }
511
512         # Create the batch update method
513         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.update' ) ) {
514                 __PACKAGE__->register_method(
515                         api_name        => $api_prefix.'.batch.update',
516                         method          => 'batch_call',
517                         api_level       => 1,
518                         cdbi            => $cdbi,
519                 );
520         }
521
522         # Create the delete method
523         unless ( __PACKAGE__->is_registered( $api_prefix.'.delete' ) ) {
524                 __PACKAGE__->register_method(
525                         api_name        => $api_prefix.'.delete',
526                         method          => 'delete_node',
527                         api_level       => 1,
528                         cdbi            => $cdbi,
529                 );
530         }
531
532         # Create the batch delete method
533         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.delete' ) ) {
534                 __PACKAGE__->register_method(
535                         api_name        => $api_prefix.'.batch.delete',
536                         method          => 'batch_call',
537                         api_level       => 1,
538                         cdbi            => $cdbi,
539                 );
540         }
541
542         # Create the search-based mass delete method
543         unless ( __PACKAGE__->is_registered( $api_prefix.'.mass_delete' ) ) {
544                 __PACKAGE__->register_method(
545                         api_name        => $api_prefix.'.mass_delete',
546                         method          => 'mass_delete',
547                         api_level       => 1,
548                         cdbi            => $cdbi,
549                 );
550         }
551
552 }
553
554 1;