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