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",
166 notes => <<" NOTES");
167 Retrieves all un-fleshed buckets by class assigned to given user
168 PARAMS(authtoken, bucketOwnerId, class [, type])
169 class can be one of "biblio", "callnumber", "copy", "user"
170 The optional "type" parameter allows you to limit the search by
172 If bucketOwnerId is not defined, the authtoken is used as the
174 If requestor ID is different than bucketOwnerId, requestor must have
175 VIEW_CONTAINER permissions.
178 sub bucket_retrieve_class {
179 my( $self, $client, $authtoken, $userid, $class, $type ) = @_;
181 my( $staff, $user, $evt ) =
182 $apputils->checkses_requestor( $authtoken, $userid, 'VIEW_CONTAINER' );
185 $logger->debug("User " . $staff->id .
186 " retrieving buckets for user $userid [class=$class, type=$type]");
188 my $meth = $types{$class} . ".search.atomic";
192 $buckets = $apputils->simplereq( $svc,
193 $meth, { owner => $userid, btype => $type } );
195 $logger->debug("Grabbing buckets by class $class: $svc : $meth : {owner => $userid}");
196 $buckets = $apputils->simplereq( $svc, $meth, { owner => $userid } );
199 return _sort_buckets($buckets);
202 __PACKAGE__->register_method(
203 method => "bucket_create",
204 api_name => "open-ils.actor.container.create",
205 notes => <<" NOTES");
206 Creates a new bucket object. If requestor is different from
207 bucketOwner, requestor needs CREATE_CONTAINER permissions
208 PARAMS(authtoken, bucketObject);
209 Returns the new bucket object
213 my( $self, $client, $authtoken, $class, $bucket ) = @_;
215 my $e = new_editor(xact=>1, authtoken=>$authtoken);
216 return $e->event unless $e->checkauth;
218 if( $bucket->owner ne $e->requestor->id ) {
219 return $e->event unless
220 $e->allowed('CREATE_CONTAINER');
223 return $e->event unless
224 $e->allowed('CREATE_MY_CONTAINER');
229 my $evt = OpenILS::Event->new('CONTAINER_EXISTS',
230 payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]);
231 my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype};
234 if( $class eq 'copy' ) {
235 return $evt if $e->search_container_copy_bucket($search)->[0];
236 return $e->event unless
237 $obj = $e->create_container_copy_bucket($bucket);
240 if( $class eq 'callnumber' ) {
241 return $evt if $e->search_container_call_number_bucket($search)->[0];
242 return $e->event unless
243 $obj = $e->create_container_call_number_bucket($bucket);
246 if( $class eq 'biblio' ) {
247 return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0];
248 return $e->event unless
249 $obj = $e->create_container_biblio_record_entry_bucket($bucket);
252 if( $class eq 'user') {
253 return $evt if $e->search_container_user_bucket($search)->[0];
254 return $e->event unless
255 $obj = $e->create_container_user_bucket($bucket);
263 __PACKAGE__->register_method(
264 method => "item_create",
265 api_name => "open-ils.actor.container.item.create",
268 Adds one or more items to an existing container
271 {desc => 'Authentication token', type => 'string'},
272 {desc => 'Container class. Can be "copy", "callnumber", "biblio", or "user"', type => 'string'},
273 {desc => 'Item or items. Can either be a single container item object, or an array of them', type => 'object'},
276 desc => 'The ID of the newly created item(s). In batch context, an array of IDs is returned'
283 my( $self, $client, $authtoken, $class, $item ) = @_;
285 my $e = new_editor(xact=>1, authtoken=>$authtoken);
286 return $e->die_event unless $e->checkauth;
287 my $items = (ref $item eq 'ARRAY') ? $item : [$item];
289 my ( $bucket, $evt ) = $apputils->fetch_container_e($e, $item->bucket, $class);
292 if( $bucket->owner ne $e->requestor->id ) {
293 return $e->die_event unless
294 $e->allowed('CREATE_CONTAINER_ITEM');
297 # return $e->event unless
298 # $e->allowed('CREATE_CONTAINER_ITEM'); # new perm here?
301 for my $one_item (@$items) {
306 if( $class eq 'copy' ) {
307 return $e->die_event unless
308 $stat = $e->create_container_copy_bucket_item($one_item);
311 if( $class eq 'callnumber' ) {
312 return $e->die_event unless
313 $stat = $e->create_container_call_number_bucket_item($one_item);
316 if( $class eq 'biblio' ) {
317 return $e->die_event unless
318 $stat = $e->create_container_biblio_record_entry_bucket_item($one_item);
321 if( $class eq 'user') {
322 return $e->die_event unless
323 $stat = $e->create_container_user_bucket_item($one_item);
329 # CStoreEeditor inserts the id (pkey) on newly created objects
330 return [ map { $_->id } @$items ] if ref $item eq 'ARRAY';
336 __PACKAGE__->register_method(
337 method => "item_delete",
338 api_name => "open-ils.actor.container.item.delete",
339 notes => <<" NOTES");
340 PARAMS(authtoken, class, itemId)
344 my( $self, $client, $authtoken, $class, $itemid ) = @_;
346 my $e = new_editor(xact=>1, authtoken=>$authtoken);
347 return $e->event unless $e->checkauth;
349 my $ret = __item_delete($e, $class, $itemid);
350 $e->commit unless $U->event_code($ret);
355 my( $e, $class, $itemid ) = @_;
356 my( $bucket, $item, $evt);
358 ( $item, $evt ) = $U->fetch_container_item_e( $e, $itemid, $class );
361 ( $bucket, $evt ) = $U->fetch_container_e($e, $item->bucket, $class);
364 if( $bucket->owner ne $e->requestor->id ) {
365 my $owner = $e->retrieve_actor_user($bucket->owner)
366 or return $e->die_event;
367 return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou);
371 if( $class eq 'copy' ) {
372 for my $note (@{$e->search_container_copy_bucket_item_note({item => $item->id})}) {
373 return $e->event unless
374 $e->delete_container_copy_bucket_item_note($note);
376 return $e->event unless
377 $stat = $e->delete_container_copy_bucket_item($item);
380 if( $class eq 'callnumber' ) {
381 for my $note (@{$e->search_container_call_number_bucket_item_note({item => $item->id})}) {
382 return $e->event unless
383 $e->delete_container_call_number_bucket_item_note($note);
385 return $e->event unless
386 $stat = $e->delete_container_call_number_bucket_item($item);
389 if( $class eq 'biblio' ) {
390 for my $note (@{$e->search_container_biblio_record_entry_bucket_item_note({item => $item->id})}) {
391 return $e->event unless
392 $e->delete_container_biblio_record_entry_bucket_item_note($note);
394 return $e->event unless
395 $stat = $e->delete_container_biblio_record_entry_bucket_item($item);
398 if( $class eq 'user') {
399 for my $note (@{$e->search_container_user_bucket_item_note({item => $item->id})}) {
400 return $e->event unless
401 $e->delete_container_user_bucket_item_note($note);
403 return $e->event unless
404 $stat = $e->delete_container_user_bucket_item($item);
411 __PACKAGE__->register_method(
412 method => 'full_delete',
413 api_name => 'open-ils.actor.container.full_delete',
414 notes => "Complety removes a container including all attached items",
418 my( $self, $client, $authtoken, $class, $containerId ) = @_;
419 my( $container, $evt);
421 my $e = new_editor(xact=>1, authtoken=>$authtoken);
422 return $e->event unless $e->checkauth;
424 ( $container, $evt ) = $apputils->fetch_container_e($e, $containerId, $class);
427 if( $container->owner ne $e->requestor->id ) {
428 my $owner = $e->retrieve_actor_user($container->owner)
429 or return $e->die_event;
430 return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou);
435 my @s = ({bucket => $containerId}, {idlist=>1});
437 if( $class eq 'copy' ) {
438 $items = $e->search_container_copy_bucket_item(@s);
441 if( $class eq 'callnumber' ) {
442 $items = $e->search_container_call_number_bucket_item(@s);
445 if( $class eq 'biblio' ) {
446 $items = $e->search_container_biblio_record_entry_bucket_item(@s);
449 if( $class eq 'user') {
450 $items = $e->search_container_user_bucket_item(@s);
453 __item_delete($e, $class, $_) for @$items;
456 if( $class eq 'copy' ) {
457 return $e->event unless
458 $stat = $e->delete_container_copy_bucket($container);
461 if( $class eq 'callnumber' ) {
462 return $e->event unless
463 $stat = $e->delete_container_call_number_bucket($container);
466 if( $class eq 'biblio' ) {
467 return $e->event unless
468 $stat = $e->delete_container_biblio_record_entry_bucket($container);
471 if( $class eq 'user') {
472 return $e->event unless
473 $stat = $e->delete_container_user_bucket($container);
480 __PACKAGE__->register_method(
481 method => 'container_update',
482 api_name => 'open-ils.actor.container.update',
484 Updates the given container item.
485 @param authtoken The login session key
486 @param class The container class
487 @param container The container item
488 @return true on success, 0 on no update, Event on error
492 sub container_update {
493 my( $self, $conn, $authtoken, $class, $container ) = @_;
495 my $e = new_editor(xact=>1, authtoken=>$authtoken);
496 return $e->event unless $e->checkauth;
498 my ( $dbcontainer, $evt ) = $U->fetch_container_e($e, $container->id, $class);
501 if( $e->requestor->id ne $container->owner ) {
502 return $e->event unless $e->allowed('UPDATE_CONTAINER');
506 if( $class eq 'copy' ) {
507 return $e->event unless
508 $stat = $e->update_container_copy_bucket($container);
511 if( $class eq 'callnumber' ) {
512 return $e->event unless
513 $stat = $e->update_container_call_number_bucket($container);
516 if( $class eq 'biblio' ) {
517 return $e->event unless
518 $stat = $e->update_container_biblio_record_entry_bucket($container);
521 if( $class eq 'user') {
522 return $e->event unless
523 $stat = $e->update_container_user_bucket($container);
532 __PACKAGE__->register_method(
533 method => "anon_cache",
534 api_name => "open-ils.actor.anon_cache.set_value",
537 Sets a value in the anon web cache. If the session key is
538 undefined, one will be automatically generated.
541 {desc => 'Session key', type => 'string'},
543 desc => q/Field name. The name of the field in this cache session whose value to set/,
547 desc => q/The cached value. This can be any type of object (hash, array, string, etc.)/,
552 desc => 'session key on success, undef on error',
558 __PACKAGE__->register_method(
559 method => "anon_cache",
560 api_name => "open-ils.actor.anon_cache.get_value",
563 Returns the cached data at the specified field within the specified cache session.
566 {desc => 'Session key', type => 'string'},
568 desc => q/Field name. The name of the field in this cache session whose value to set/,
573 desc => 'cached value on success, undef on error',
579 __PACKAGE__->register_method(
580 method => "anon_cache",
581 api_name => "open-ils.actor.anon_cache.delete_session",
584 Deletes a cache session.
587 {desc => 'Session key', type => 'string'},
590 desc => 'Session key',
597 my($self, $conn, $ses_key, $field_key, $value) = @_;
599 my $sc = OpenSRF::Utils::SettingsClient->new;
600 my $cache = OpenSRF::Utils::Cache->new('anon');
601 my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
602 my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
604 if($self->api_name =~ /delete_session/) {
606 return $cache->delete_cache($ses_key);
608 } elsif( $self->api_name =~ /set_value/ ) {
610 $ses_key = md5_hex(time . rand($$)) unless $ses_key;
611 my $blob = $cache->get_cache($ses_key) || {};
612 $blob->{$field_key} = $value;
614 length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
615 $cache->put_cache($ses_key, $blob, $cache_timeout);
620 my $blob = $cache->get_cache($ses_key) or return undef;
621 return $blob if (!defined($field_key));
622 return $blob->{$field_key};