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