1 package OpenILS::Application::Actor::Container;
2 use base 'OpenILS::Application';
3 use strict; use warnings;
4 use OpenILS::Application::AppUtils;
7 use OpenSRF::EX qw(:try);
8 use OpenILS::Utils::Fieldmapper;
9 use OpenILS::Utils::CStoreEditor qw/:funcs/;
10 use OpenSRF::Utils::SettingsClient;
11 use OpenSRF::Utils::Cache;
12 use Digest::MD5 qw(md5_hex);
13 use OpenSRF::Utils::JSON;
15 my $apputils = "OpenILS::Application::AppUtils";
17 my $logger = "OpenSRF::Utils::Logger";
19 sub initialize { return 1; }
21 my $svc = 'open-ils.cstore';
22 my $meth = 'open-ils.cstore.direct.container';
25 $types{'biblio'} = "$meth.biblio_record_entry_bucket";
26 $types{'callnumber'} = "$meth.call_number_bucket";
27 $types{'copy'} = "$meth.copy_bucket";
28 $types{'user'} = "$meth.user_bucket";
29 $ctypes{'biblio'} = "container_biblio_record_entry_bucket";
30 $ctypes{'callnumber'} = "container_call_number_bucket";
31 $ctypes{'copy'} = "container_copy_bucket";
32 $ctypes{'user'} = "container_user_bucket";
37 return $buckets unless ($buckets && $buckets->[0]);
38 return [ sort { $a->name cmp $b->name } @$buckets ];
41 __PACKAGE__->register_method(
42 method => "bucket_retrieve_all",
43 api_name => "open-ils.actor.container.all.retrieve_by_user",
46 Retrieves all un-fleshed buckets assigned to given user
47 PARAMS(authtoken, bucketOwnerId)
48 If requestor ID is different than bucketOwnerId, requestor must have
49 VIEW_CONTAINER permissions.
52 sub bucket_retrieve_all {
53 my($self, $client, $auth, $user_id) = @_;
54 my $e = new_editor(authtoken => $auth);
55 return $e->event unless $e->checkauth;
57 if($e->requestor->id ne $user_id) {
58 return $e->event unless $e->allowed('VIEW_CONTAINER');
62 for my $type (keys %ctypes) {
63 my $meth = "search_" . $ctypes{$type};
64 $buckets{$type} = $e->$meth({owner => $user_id});
70 __PACKAGE__->register_method(
71 method => "bucket_flesh",
72 api_name => "open-ils.actor.container.flesh",
77 __PACKAGE__->register_method(
78 method => "bucket_flesh_pub",
79 api_name => "open-ils.actor.container.public.flesh",
84 my($self, $conn, $auth, $class, $bucket_id) = @_;
85 my $e = new_editor(authtoken => $auth);
86 return $e->event unless $e->checkauth;
87 return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
90 sub bucket_flesh_pub {
91 my($self, $conn, $class, $bucket_id) = @_;
93 return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
97 my($self, $conn, $e, $class, $bucket_id) = @_;
98 my $meth = 'retrieve_' . $ctypes{$class};
99 my $bkt = $e->$meth($bucket_id) or return $e->event;
101 unless($U->is_true($bkt->pub)) {
102 return undef if $self->api_name =~ /public/;
103 unless($bkt->owner eq $e->requestor->id) {
104 my $owner = $e->retrieve_actor_user($bkt->owner)
105 or return $e->die_event;
106 return $e->event unless $e->allowed('VIEW_CONTAINER', $owner->home_ou);
110 my $fmclass = $bkt->class_name . "i";
111 $meth = 'search_' . $ctypes{$class} . '_item';
114 {bucket => $bucket_id},
115 { order_by => {$fmclass => "pos"},
117 flesh_fields => {$fmclass => ['notes']}
126 __PACKAGE__->register_method(
127 method => "item_note_cud",
128 api_name => "open-ils.actor.container.item_note.cud",
133 my($self, $conn, $auth, $class, $note) = @_;
134 my $e = new_editor(authtoken => $auth, xact => 1);
135 return $e->die_event unless $e->checkauth;
137 my $meth = 'retrieve_' . $ctypes{$class};
138 my $nclass = $note->class_name;
139 (my $iclass = $nclass) =~ s/n$//og;
141 my $db_note = $e->$meth($note->id, {
145 $iclass => ['bucket']
149 if($db_note->item->bucket->owner ne $e->requestor->id) {
150 return $e->die_event unless
151 $e->allowed('UPDATE_CONTAINER', $db_note->item->bucket);
154 $meth = 'create_' . $ctypes{$class} if $note->isnew;
155 $meth = 'update_' . $ctypes{$class} if $note->ischanged;
156 $meth = 'delete_' . $ctypes{$class} if $note->isdeleted;
157 return $e->die_event unless $e->$meth($note);
162 __PACKAGE__->register_method(
163 method => "bucket_retrieve_class",
164 api_name => "open-ils.actor.container.retrieve_by_class",
167 notes => <<" NOTES");
168 Retrieves all un-fleshed buckets by class assigned to given user
169 PARAMS(authtoken, bucketOwnerId, class [, type])
170 class can be one of "biblio", "callnumber", "copy", "user"
171 The optional "type" parameter allows you to limit the search by
173 If bucketOwnerId is not defined, the authtoken is used as the
175 If requestor ID is different than bucketOwnerId, requestor must have
176 VIEW_CONTAINER permissions.
179 sub bucket_retrieve_class {
180 my( $self, $client, $authtoken, $userid, $class, $type ) = @_;
182 my( $staff, $user, $evt ) =
183 $apputils->checkses_requestor( $authtoken, $userid, 'VIEW_CONTAINER' );
186 $logger->debug("User " . $staff->id .
187 " retrieving buckets for user $userid [class=$class, type=$type]");
189 my $meth = $types{$class} . ".search.atomic";
193 $buckets = $apputils->simplereq( $svc,
194 $meth, { owner => $userid, btype => $type } );
196 $logger->debug("Grabbing buckets by class $class: $svc : $meth : {owner => $userid}");
197 $buckets = $apputils->simplereq( $svc, $meth, { owner => $userid } );
200 return _sort_buckets($buckets);
203 __PACKAGE__->register_method(
204 method => "bucket_create",
205 api_name => "open-ils.actor.container.create",
206 notes => <<" NOTES");
207 Creates a new bucket object. If requestor is different from
208 bucketOwner, requestor needs CREATE_CONTAINER permissions
209 PARAMS(authtoken, bucketObject);
210 Returns the new bucket object
214 my( $self, $client, $authtoken, $class, $bucket ) = @_;
216 my $e = new_editor(xact=>1, authtoken=>$authtoken);
217 return $e->event unless $e->checkauth;
219 if( $bucket->owner ne $e->requestor->id ) {
220 return $e->event unless
221 $e->allowed('CREATE_CONTAINER');
224 return $e->event unless
225 $e->allowed('CREATE_MY_CONTAINER');
230 my $evt = OpenILS::Event->new('CONTAINER_EXISTS',
231 payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]);
232 my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype};
235 if( $class eq 'copy' ) {
236 return $evt if $e->search_container_copy_bucket($search)->[0];
237 return $e->event unless
238 $obj = $e->create_container_copy_bucket($bucket);
241 if( $class eq 'callnumber' ) {
242 return $evt if $e->search_container_call_number_bucket($search)->[0];
243 return $e->event unless
244 $obj = $e->create_container_call_number_bucket($bucket);
247 if( $class eq 'biblio' ) {
248 return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0];
249 return $e->event unless
250 $obj = $e->create_container_biblio_record_entry_bucket($bucket);
253 if( $class eq 'user') {
254 return $evt if $e->search_container_user_bucket($search)->[0];
255 return $e->event unless
256 $obj = $e->create_container_user_bucket($bucket);
264 __PACKAGE__->register_method(
265 method => "item_create",
266 api_name => "open-ils.actor.container.item.create",
269 Adds one or more items to an existing container
272 {desc => 'Authentication token', type => 'string'},
273 {desc => 'Container class. Can be "copy", "callnumber", "biblio", or "user"', type => 'string'},
274 {desc => 'Item or items. Can either be a single container item object, or an array of them', type => 'object'},
277 desc => 'The ID of the newly created item(s). In batch context, an array of IDs is returned'
284 my( $self, $client, $authtoken, $class, $item ) = @_;
286 my $e = new_editor(xact=>1, authtoken=>$authtoken);
287 return $e->die_event unless $e->checkauth;
288 my $items = (ref $item eq 'ARRAY') ? $item : [$item];
290 my ( $bucket, $evt ) = $apputils->fetch_container_e($e, $item->bucket, $class);
293 if( $bucket->owner ne $e->requestor->id ) {
294 return $e->die_event unless
295 $e->allowed('CREATE_CONTAINER_ITEM');
298 # return $e->event unless
299 # $e->allowed('CREATE_CONTAINER_ITEM'); # new perm here?
302 for my $one_item (@$items) {
307 if( $class eq 'copy' ) {
308 return $e->die_event unless
309 $stat = $e->create_container_copy_bucket_item($one_item);
312 if( $class eq 'callnumber' ) {
313 return $e->die_event unless
314 $stat = $e->create_container_call_number_bucket_item($one_item);
317 if( $class eq 'biblio' ) {
318 return $e->die_event unless
319 $stat = $e->create_container_biblio_record_entry_bucket_item($one_item);
322 if( $class eq 'user') {
323 return $e->die_event unless
324 $stat = $e->create_container_user_bucket_item($one_item);
330 # CStoreEeditor inserts the id (pkey) on newly created objects
331 return [ map { $_->id } @$items ] if ref $item eq 'ARRAY';
337 __PACKAGE__->register_method(
338 method => "item_delete",
339 api_name => "open-ils.actor.container.item.delete",
340 notes => <<" NOTES");
341 PARAMS(authtoken, class, itemId)
345 my( $self, $client, $authtoken, $class, $itemid ) = @_;
347 my $e = new_editor(xact=>1, authtoken=>$authtoken);
348 return $e->event unless $e->checkauth;
350 my $ret = __item_delete($e, $class, $itemid);
351 $e->commit unless $U->event_code($ret);
356 my( $e, $class, $itemid ) = @_;
357 my( $bucket, $item, $evt);
359 ( $item, $evt ) = $U->fetch_container_item_e( $e, $itemid, $class );
362 ( $bucket, $evt ) = $U->fetch_container_e($e, $item->bucket, $class);
365 if( $bucket->owner ne $e->requestor->id ) {
366 my $owner = $e->retrieve_actor_user($bucket->owner)
367 or return $e->die_event;
368 return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou);
372 if( $class eq 'copy' ) {
373 for my $note (@{$e->search_container_copy_bucket_item_note({item => $item->id})}) {
374 return $e->event unless
375 $e->delete_container_copy_bucket_item_note($note);
377 return $e->event unless
378 $stat = $e->delete_container_copy_bucket_item($item);
381 if( $class eq 'callnumber' ) {
382 for my $note (@{$e->search_container_call_number_bucket_item_note({item => $item->id})}) {
383 return $e->event unless
384 $e->delete_container_call_number_bucket_item_note($note);
386 return $e->event unless
387 $stat = $e->delete_container_call_number_bucket_item($item);
390 if( $class eq 'biblio' ) {
391 for my $note (@{$e->search_container_biblio_record_entry_bucket_item_note({item => $item->id})}) {
392 return $e->event unless
393 $e->delete_container_biblio_record_entry_bucket_item_note($note);
395 return $e->event unless
396 $stat = $e->delete_container_biblio_record_entry_bucket_item($item);
399 if( $class eq 'user') {
400 for my $note (@{$e->search_container_user_bucket_item_note({item => $item->id})}) {
401 return $e->event unless
402 $e->delete_container_user_bucket_item_note($note);
404 return $e->event unless
405 $stat = $e->delete_container_user_bucket_item($item);
412 __PACKAGE__->register_method(
413 method => 'full_delete',
414 api_name => 'open-ils.actor.container.full_delete',
415 notes => "Complety removes a container including all attached items",
419 my( $self, $client, $authtoken, $class, $containerId ) = @_;
420 my( $container, $evt);
422 my $e = new_editor(xact=>1, authtoken=>$authtoken);
423 return $e->event unless $e->checkauth;
425 ( $container, $evt ) = $apputils->fetch_container_e($e, $containerId, $class);
428 if( $container->owner ne $e->requestor->id ) {
429 my $owner = $e->retrieve_actor_user($container->owner)
430 or return $e->die_event;
431 return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou);
436 my @s = ({bucket => $containerId}, {idlist=>1});
438 if( $class eq 'copy' ) {
439 $items = $e->search_container_copy_bucket_item(@s);
442 if( $class eq 'callnumber' ) {
443 $items = $e->search_container_call_number_bucket_item(@s);
446 if( $class eq 'biblio' ) {
447 $items = $e->search_container_biblio_record_entry_bucket_item(@s);
450 if( $class eq 'user') {
451 $items = $e->search_container_user_bucket_item(@s);
454 __item_delete($e, $class, $_) for @$items;
457 if( $class eq 'copy' ) {
458 return $e->event unless
459 $stat = $e->delete_container_copy_bucket($container);
462 if( $class eq 'callnumber' ) {
463 return $e->event unless
464 $stat = $e->delete_container_call_number_bucket($container);
467 if( $class eq 'biblio' ) {
468 return $e->event unless
469 $stat = $e->delete_container_biblio_record_entry_bucket($container);
472 if( $class eq 'user') {
473 return $e->event unless
474 $stat = $e->delete_container_user_bucket($container);
481 __PACKAGE__->register_method(
482 method => 'container_update',
483 api_name => 'open-ils.actor.container.update',
485 Updates the given container item.
486 @param authtoken The login session key
487 @param class The container class
488 @param container The container item
489 @return true on success, 0 on no update, Event on error
493 sub container_update {
494 my( $self, $conn, $authtoken, $class, $container ) = @_;
496 my $e = new_editor(xact=>1, authtoken=>$authtoken);
497 return $e->event unless $e->checkauth;
499 my ( $dbcontainer, $evt ) = $U->fetch_container_e($e, $container->id, $class);
502 if( $e->requestor->id ne $container->owner ) {
503 return $e->event unless $e->allowed('UPDATE_CONTAINER');
507 if( $class eq 'copy' ) {
508 return $e->event unless
509 $stat = $e->update_container_copy_bucket($container);
512 if( $class eq 'callnumber' ) {
513 return $e->event unless
514 $stat = $e->update_container_call_number_bucket($container);
517 if( $class eq 'biblio' ) {
518 return $e->event unless
519 $stat = $e->update_container_biblio_record_entry_bucket($container);
522 if( $class eq 'user') {
523 return $e->event unless
524 $stat = $e->update_container_user_bucket($container);
533 __PACKAGE__->register_method(
534 method => "anon_cache",
535 api_name => "open-ils.actor.anon_cache.set_value",
538 Sets a value in the anon web cache. If the session key is
539 undefined, one will be automatically generated.
542 {desc => 'Session key', type => 'string'},
544 desc => q/Field name. The name of the field in this cache session whose value to set/,
548 desc => q/The cached value. This can be any type of object (hash, array, string, etc.)/,
553 desc => 'session key on success, undef on error',
559 __PACKAGE__->register_method(
560 method => "anon_cache",
561 api_name => "open-ils.actor.anon_cache.get_value",
564 Returns the cached data at the specified field within the specified cache session.
567 {desc => 'Session key', type => 'string'},
569 desc => q/Field name. The name of the field in this cache session whose value to set/,
574 desc => 'cached value on success, undef on error',
580 __PACKAGE__->register_method(
581 method => "anon_cache",
582 api_name => "open-ils.actor.anon_cache.delete_session",
585 Deletes a cache session.
588 {desc => 'Session key', type => 'string'},
591 desc => 'Session key',
598 my($self, $conn, $ses_key, $field_key, $value) = @_;
600 my $sc = OpenSRF::Utils::SettingsClient->new;
601 my $cache = OpenSRF::Utils::Cache->new('anon');
602 my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
603 my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
605 if($self->api_name =~ /delete_session/) {
607 return $cache->delete_cache($ses_key);
609 } elsif( $self->api_name =~ /set_value/ ) {
611 $ses_key = md5_hex(time . rand($$)) unless $ses_key;
612 my $blob = $cache->get_cache($ses_key) || {};
613 $blob->{$field_key} = $value;
615 length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
616 $cache->put_cache($ses_key, $blob, $cache_timeout);
621 my $blob = $cache->get_cache($ses_key) or return undef;
622 return $blob if (!defined($field_key));
623 return $blob->{$field_key};