]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher.pm
ignoring wide chars in cache keys
[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         OpenSRF::Utils::Cache->new->put_cache(
96                 $cache_key =>
97                 [@res[int($cache_page * $cache_args{cache_page_size}) .. int(($cache_page + 1) * $cache_args{cache_page_size}) ]] =>
98                 $cache_args{timeout}
99         );
100
101         return undef;
102 }
103
104 sub retrieve_node {
105         my $self = shift;
106         my $client = shift;
107         my @ids = @_;
108
109         my $cdbi = $self->{cdbi};
110
111         for my $id ( @ids ) {
112                 next unless ($id);
113
114                 my ($rec) = $cdbi->fast_fieldmapper($id);
115                 if ($self->api_name !~ /batch/o) {
116                         return $rec if ($rec);
117                 }
118                 $client->respond($rec);
119         }
120         return undef;
121 }
122
123 sub search {
124         my $self = shift;
125         my $client = shift;
126         my $searches = shift;
127
128         my $cdbi = $self->{cdbi};
129
130         $log->debug("Searching $cdbi for { ".join(',', map { "$_ => $$searches{$_}" } keys %$searches).' }',DEBUG);
131
132         for my $obj ($cdbi->search($searches)) {
133                 $client->respond( $obj->to_fieldmapper );
134         }
135         return undef;
136 }
137
138 sub search_one_field {
139         my $self = shift;
140         my $client = shift;
141         my @terms = @_;
142
143         (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
144         (my $col = $self->api_name) =~ s/.*\.$search_type\.([^.]+).*/$1/;
145         my $cdbi = $self->{cdbi};
146
147         my $like = 0;
148         $like = 1 if ($search_type =~ /like$/o);
149
150         for my $term (@terms) {
151                 $log->debug("Searching $cdbi for $col using type $search_type, value '$term'",DEBUG);
152                 if (@terms == 1) {
153                         return [ $cdbi->fast_fieldmapper($term,$col,$like) ];
154                 }
155                 $client->respond( [ $cdbi->fast_fieldmapper($term,$col,$like) ] );
156         }
157         return undef;
158 }
159
160
161 sub create_node {
162         my $self = shift;
163         my $client = shift;
164         my $node = shift;
165
166         my $cdbi = $self->{cdbi};
167
168         my $success;
169         try {
170                 my $rec = $cdbi->create($node);
171                 $success = $rec->id if ($rec);
172         } catch Error with {
173                 $success = 0;
174         };
175
176         return $success;
177 }
178
179 sub update_node {
180         my $self = shift;
181         my $client = shift;
182         my $node = shift;
183
184         my $cdbi = $self->{cdbi};
185
186         return $cdbi->update($node);
187 }
188
189 sub mass_delete {
190         my $self = shift;
191         my $client = shift;
192         my $search = shift;
193
194         my $where = 'WHERE ';
195
196         my $cdbi = $self->{cdbi};
197         my $table = $cdbi->table;
198
199         my @keys = sort keys %$search;
200         
201         my @binds;
202         my @wheres;
203         for my $col ( @keys ) {
204                 if (ref($$search{$col}) and ref($$search{$col}) =~ /ARRAY/o) {
205                         push @wheres, "$col IN (" . join(',', map { '?' } @{ $$search{$col} }) . ')';
206                         push @binds, map { "$_" } @{ $$search{$col} };
207                 } else {
208                         push @wheres, "$col = ?";
209                         push @binds, $$search{$col};
210                 }
211         }
212         $where .= join ' AND ', @wheres;
213
214         my $delete = "DELETE FROM $table $where";
215
216         $log->debug("Performing MASS deletion : $delete",DEBUG);
217
218         my $dbh = $cdbi->db_Main;
219         my $success = 1;
220         try {
221                 my $sth = $dbh->prepare($delete);
222                 $sth->execute( @binds );
223                 $sth->finish;
224                 $log->debug("MASS Delete succeeded",DEBUG);
225         } catch Error with {
226                 $log->debug("MASS Delete FAILED : ".shift(),DEBUG);
227                 $success = 0;
228         };
229         return $success;
230 }
231
232 sub delete_node {
233         my $self = shift;
234         my $client = shift;
235         my $node = shift;
236
237         my $cdbi = $self->{cdbi};
238
239         my $success = 1;
240         try {
241                 $success = $cdbi->delete($node);
242         } catch Error with {
243                 $success = 0;
244         };
245         return $success;
246 }
247
248 sub batch_call {
249         my $self = shift;
250         my $client = shift;
251         my @nodes = @_;
252
253         my $cdbi = $self->{cdbi};
254         my $api_name = $self->api_name;
255         (my $single_call_api_name = $api_name) =~ s/batch\.//o;
256
257         $log->debug("Default $api_name looking up $single_call_api_name...",INTERNAL);
258         my $method = $self->method_lookup($single_call_api_name);
259
260         my @success;
261         while ( my $node = shift(@nodes) ) {
262                 my ($res) = $method->run( $node ); 
263                 push(@success, 1) if ($res >= 0);
264         }
265
266         my $insert_total = 0;
267         $insert_total += $_ for (@success);
268
269         return $insert_total;
270 }
271
272 eval '
273 use OpenILS::Application::Storage::Publisher::actor;
274 use OpenILS::Application::Storage::Publisher::action;
275 use OpenILS::Application::Storage::Publisher::asset;
276 use OpenILS::Application::Storage::Publisher::biblio;
277 use OpenILS::Application::Storage::Publisher::config;
278 use OpenILS::Application::Storage::Publisher::metabib;
279 ';
280
281 for my $fmclass ( (Fieldmapper->classes) ) {
282         $log->debug("Generating methods for Fieldmapper class $fmclass", DEBUG);
283
284         (my $cdbi = $fmclass) =~ s/^Fieldmapper:://o;
285         (my $class = $cdbi) =~ s/::.*//o;
286         (my $api_class = $cdbi) =~ s/::/./go;
287         my $registration_class = __PACKAGE__ . "::$class";
288         my $api_prefix = 'open-ils.storage.direct.'.$api_class;
289
290         # Create the search method
291         unless ( __PACKAGE__->is_registered( $api_prefix.'.search' ) ) {
292                 __PACKAGE__->register_method(
293                         api_name        => $api_prefix.'.search',
294                         method          => 'search',
295                         api_level       => 1,
296                         stream          => 1,
297                         cdbi            => $cdbi,
298                         cachable        => 1,
299                 );
300         }
301
302         # Create the retrieve method
303         unless ( __PACKAGE__->is_registered( $api_prefix.'.retrieve' ) ) {
304                 __PACKAGE__->register_method(
305                         api_name        => $api_prefix.'.retrieve',
306                         method          => 'retrieve_node',
307                         api_level       => 1,
308                         cdbi            => $cdbi,
309                         cachable        => 1,
310                 );
311         }
312
313         # Create the batch retrieve method
314         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.retrieve' ) ) {
315                 __PACKAGE__->register_method(
316                         api_name        => $api_prefix.'.batch.retrieve',
317                         method          => 'retrieve_node',
318                         api_level       => 1,
319                         stream          => 1,
320                         cdbi            => $cdbi,
321                         cachable        => 1,
322                 );
323         }
324
325         unless ($fmclass->is_virtual) {
326                 for my $field ($fmclass->real_fields) {
327                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search.'.$field ) ) {
328                                 __PACKAGE__->register_method(
329                                         api_name        => $api_prefix.'.search.'.$field,
330                                         method          => 'search_one_field',
331                                         api_level       => 1,
332                                         cdbi            => $cdbi,
333                                         cachable        => 1,
334                                 );
335                         }
336                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like.'.$field ) ) {
337                                 __PACKAGE__->register_method(
338                                         api_name        => $api_prefix.'.search_like.'.$field,
339                                         method          => 'search_one_field',
340                                         api_level       => 1,
341                                         cdbi            => $cdbi,
342                                         cachable        => 1,
343                                 );
344                         }
345                 }
346         }
347
348
349         # Create the create method
350         unless ( __PACKAGE__->is_registered( $api_prefix.'.create' ) ) {
351                 __PACKAGE__->register_method(
352                         api_name        => $api_prefix.'.create',
353                         method          => 'create_node',
354                         api_level       => 1,
355                         cdbi            => $cdbi,
356                 );
357         }
358
359         # Create the batch create method
360         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.create' ) ) {
361                 __PACKAGE__->register_method(
362                         api_name        => $api_prefix.'.batch.create',
363                         method          => 'batch_call',
364                         api_level       => 1,
365                         cdbi            => $cdbi,
366                 );
367         }
368
369         # Create the update method
370         unless ( __PACKAGE__->is_registered( $api_prefix.'.update' ) ) {
371                 __PACKAGE__->register_method(
372                         api_name        => $api_prefix.'.update',
373                         method          => 'update_node',
374                         api_level       => 1,
375                         cdbi            => $cdbi,
376                 );
377         }
378
379         # Create the batch update method
380         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.update' ) ) {
381                 __PACKAGE__->register_method(
382                         api_name        => $api_prefix.'.batch.update',
383                         method          => 'batch_call',
384                         api_level       => 1,
385                         cdbi            => $cdbi,
386                 );
387         }
388
389         # Create the delete method
390         unless ( __PACKAGE__->is_registered( $api_prefix.'.delete' ) ) {
391                 __PACKAGE__->register_method(
392                         api_name        => $api_prefix.'.delete',
393                         method          => 'delete_node',
394                         api_level       => 1,
395                         cdbi            => $cdbi,
396                 );
397         }
398
399         # Create the batch delete method
400         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.delete' ) ) {
401                 __PACKAGE__->register_method(
402                         api_name        => $api_prefix.'.batch.delete',
403                         method          => 'batch_call',
404                         api_level       => 1,
405                         cdbi            => $cdbi,
406                 );
407         }
408
409         # Create the search-based mass delete method
410         unless ( __PACKAGE__->is_registered( $api_prefix.'.mass_delete' ) ) {
411                 __PACKAGE__->register_method(
412                         api_name        => $api_prefix.'.mass_delete',
413                         method          => 'mass_delete',
414                         api_level       => 1,
415                         cdbi            => $cdbi,
416                 );
417         }
418
419 }
420
421 1;