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} = _sort_buckets($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) = @_;
135 return new OpenILS::Event("BAD_PARAMS") unless
136 $note->class_name =~ /bucket_item_note$/;
138 my $e = new_editor(authtoken => $auth, xact => 1);
139 return $e->die_event unless $e->checkauth;
141 my $meat = $ctypes{$class} . "_item_note";
142 my $meth = "retrieve_$meat";
144 my $item_meat = $ctypes{$class} . "_item";
145 my $item_meth = "retrieve_$item_meat";
147 my $nhint = $Fieldmapper::fieldmap->{$note->class_name}->{hint};
148 (my $ihint = $nhint) =~ s/n$//og;
150 my ($db_note, $item);
155 $item = $e->$item_meth([
157 flesh => 1, flesh_fields => {$ihint => ["bucket"]}
159 ]) or return $e->die_event;
161 $db_note = $e->$meth([
169 ]) or return $e->die_event;
171 $item = $db_note->item;
174 if($item->bucket->owner ne $e->requestor->id) {
175 return $e->die_event unless $e->allowed("UPDATE_CONTAINER");
178 $meth = 'create_' . $meat if $note->isnew;
179 $meth = 'update_' . $meat if $note->ischanged;
180 $meth = 'delete_' . $meat if $note->isdeleted;
181 return $e->die_event unless $e->$meth($note);
186 __PACKAGE__->register_method(
187 method => "bucket_retrieve_class",
188 api_name => "open-ils.actor.container.retrieve_by_class",
191 notes => <<" NOTES");
192 Retrieves all un-fleshed buckets by class assigned to given user
193 PARAMS(authtoken, bucketOwnerId, class [, type])
194 class can be one of "biblio", "callnumber", "copy", "user"
195 The optional "type" parameter allows you to limit the search by
197 If bucketOwnerId is not defined, the authtoken is used as the
199 If requestor ID is different than bucketOwnerId, requestor must have
200 VIEW_CONTAINER permissions.
203 sub bucket_retrieve_class {
204 my( $self, $client, $authtoken, $userid, $class, $type ) = @_;
206 my( $staff, $user, $evt ) =
207 $apputils->checkses_requestor( $authtoken, $userid, 'VIEW_CONTAINER' );
210 $logger->debug("User " . $staff->id .
211 " retrieving buckets for user $userid [class=$class, type=$type]");
213 my $meth = $types{$class} . ".search.atomic";
217 $buckets = $apputils->simplereq( $svc,
218 $meth, { owner => $userid, btype => $type } );
220 $logger->debug("Grabbing buckets by class $class: $svc : $meth : {owner => $userid}");
221 $buckets = $apputils->simplereq( $svc, $meth, { owner => $userid } );
224 return _sort_buckets($buckets);
227 __PACKAGE__->register_method(
228 method => "bucket_create",
229 api_name => "open-ils.actor.container.create",
230 notes => <<" NOTES");
231 Creates a new bucket object. If requestor is different from
232 bucketOwner, requestor needs CREATE_CONTAINER permissions
233 PARAMS(authtoken, bucketObject);
234 Returns the new bucket object
238 my( $self, $client, $authtoken, $class, $bucket ) = @_;
240 my $e = new_editor(xact=>1, authtoken=>$authtoken);
241 return $e->event unless $e->checkauth;
243 if( $bucket->owner ne $e->requestor->id ) {
244 return $e->event unless
245 $e->allowed('CREATE_CONTAINER');
248 return $e->event unless
249 $e->allowed('CREATE_MY_CONTAINER');
254 my $evt = OpenILS::Event->new('CONTAINER_EXISTS',
255 payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]);
256 my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype};
259 if( $class eq 'copy' ) {
260 return $evt if $e->search_container_copy_bucket($search)->[0];
261 return $e->event unless
262 $obj = $e->create_container_copy_bucket($bucket);
265 if( $class eq 'callnumber' ) {
266 return $evt if $e->search_container_call_number_bucket($search)->[0];
267 return $e->event unless
268 $obj = $e->create_container_call_number_bucket($bucket);
271 if( $class eq 'biblio' ) {
272 return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0];
273 return $e->event unless
274 $obj = $e->create_container_biblio_record_entry_bucket($bucket);
277 if( $class eq 'user') {
278 return $evt if $e->search_container_user_bucket($search)->[0];
279 return $e->event unless
280 $obj = $e->create_container_user_bucket($bucket);
288 __PACKAGE__->register_method(
289 method => "item_create",
290 api_name => "open-ils.actor.container.item.create",
293 Adds one or more items to an existing container
296 {desc => 'Authentication token', type => 'string'},
297 {desc => 'Container class. Can be "copy", "callnumber", "biblio", or "user"', type => 'string'},
298 {desc => 'Item or items. Can either be a single container item object, or an array of them', type => 'object'},
301 desc => 'The ID of the newly created item(s). In batch context, an array of IDs is returned'
308 my( $self, $client, $authtoken, $class, $item ) = @_;
310 my $e = new_editor(xact=>1, authtoken=>$authtoken);
311 return $e->die_event unless $e->checkauth;
312 my $items = (ref $item eq 'ARRAY') ? $item : [$item];
314 my ( $bucket, $evt ) = $apputils->fetch_container_e($e, $item->bucket, $class);
317 if( $bucket->owner ne $e->requestor->id ) {
318 return $e->die_event unless
319 $e->allowed('CREATE_CONTAINER_ITEM');
322 # return $e->event unless
323 # $e->allowed('CREATE_CONTAINER_ITEM'); # new perm here?
326 for my $one_item (@$items) {
331 if( $class eq 'copy' ) {
332 return $e->die_event unless
333 $stat = $e->create_container_copy_bucket_item($one_item);
336 if( $class eq 'callnumber' ) {
337 return $e->die_event unless
338 $stat = $e->create_container_call_number_bucket_item($one_item);
341 if( $class eq 'biblio' ) {
342 return $e->die_event unless
343 $stat = $e->create_container_biblio_record_entry_bucket_item($one_item);
346 if( $class eq 'user') {
347 return $e->die_event unless
348 $stat = $e->create_container_user_bucket_item($one_item);
354 # CStoreEeditor inserts the id (pkey) on newly created objects
355 return [ map { $_->id } @$items ] if ref $item eq 'ARRAY';
361 __PACKAGE__->register_method(
362 method => "item_delete",
363 api_name => "open-ils.actor.container.item.delete",
364 notes => <<" NOTES");
365 PARAMS(authtoken, class, itemId)
369 my( $self, $client, $authtoken, $class, $itemid ) = @_;
371 my $e = new_editor(xact=>1, authtoken=>$authtoken);
372 return $e->event unless $e->checkauth;
374 my $ret = __item_delete($e, $class, $itemid);
375 $e->commit unless $U->event_code($ret);
380 my( $e, $class, $itemid ) = @_;
381 my( $bucket, $item, $evt);
383 ( $item, $evt ) = $U->fetch_container_item_e( $e, $itemid, $class );
386 ( $bucket, $evt ) = $U->fetch_container_e($e, $item->bucket, $class);
389 if( $bucket->owner ne $e->requestor->id ) {
390 my $owner = $e->retrieve_actor_user($bucket->owner)
391 or return $e->die_event;
392 return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou);
396 if( $class eq 'copy' ) {
397 for my $note (@{$e->search_container_copy_bucket_item_note({item => $item->id})}) {
398 return $e->event unless
399 $e->delete_container_copy_bucket_item_note($note);
401 return $e->event unless
402 $stat = $e->delete_container_copy_bucket_item($item);
405 if( $class eq 'callnumber' ) {
406 for my $note (@{$e->search_container_call_number_bucket_item_note({item => $item->id})}) {
407 return $e->event unless
408 $e->delete_container_call_number_bucket_item_note($note);
410 return $e->event unless
411 $stat = $e->delete_container_call_number_bucket_item($item);
414 if( $class eq 'biblio' ) {
415 for my $note (@{$e->search_container_biblio_record_entry_bucket_item_note({item => $item->id})}) {
416 return $e->event unless
417 $e->delete_container_biblio_record_entry_bucket_item_note($note);
419 return $e->event unless
420 $stat = $e->delete_container_biblio_record_entry_bucket_item($item);
423 if( $class eq 'user') {
424 for my $note (@{$e->search_container_user_bucket_item_note({item => $item->id})}) {
425 return $e->event unless
426 $e->delete_container_user_bucket_item_note($note);
428 return $e->event unless
429 $stat = $e->delete_container_user_bucket_item($item);
436 __PACKAGE__->register_method(
437 method => 'full_delete',
438 api_name => 'open-ils.actor.container.full_delete',
439 notes => "Complety removes a container including all attached items",
443 my( $self, $client, $authtoken, $class, $containerId ) = @_;
444 my( $container, $evt);
446 my $e = new_editor(xact=>1, authtoken=>$authtoken);
447 return $e->event unless $e->checkauth;
449 ( $container, $evt ) = $apputils->fetch_container_e($e, $containerId, $class);
452 if( $container->owner ne $e->requestor->id ) {
453 my $owner = $e->retrieve_actor_user($container->owner)
454 or return $e->die_event;
455 return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou);
460 my @s = ({bucket => $containerId}, {idlist=>1});
462 if( $class eq 'copy' ) {
463 $items = $e->search_container_copy_bucket_item(@s);
466 if( $class eq 'callnumber' ) {
467 $items = $e->search_container_call_number_bucket_item(@s);
470 if( $class eq 'biblio' ) {
471 $items = $e->search_container_biblio_record_entry_bucket_item(@s);
474 if( $class eq 'user') {
475 $items = $e->search_container_user_bucket_item(@s);
478 __item_delete($e, $class, $_) for @$items;
481 if( $class eq 'copy' ) {
482 return $e->event unless
483 $stat = $e->delete_container_copy_bucket($container);
486 if( $class eq 'callnumber' ) {
487 return $e->event unless
488 $stat = $e->delete_container_call_number_bucket($container);
491 if( $class eq 'biblio' ) {
492 return $e->event unless
493 $stat = $e->delete_container_biblio_record_entry_bucket($container);
496 if( $class eq 'user') {
497 return $e->event unless
498 $stat = $e->delete_container_user_bucket($container);
505 __PACKAGE__->register_method(
506 method => 'container_update',
507 api_name => 'open-ils.actor.container.update',
509 Updates the given container item.
510 @param authtoken The login session key
511 @param class The container class
512 @param container The container item
513 @return true on success, 0 on no update, Event on error
517 sub container_update {
518 my( $self, $conn, $authtoken, $class, $container ) = @_;
520 my $e = new_editor(xact=>1, authtoken=>$authtoken);
521 return $e->event unless $e->checkauth;
523 my ( $dbcontainer, $evt ) = $U->fetch_container_e($e, $container->id, $class);
526 if( $e->requestor->id ne $container->owner ) {
527 return $e->event unless $e->allowed('UPDATE_CONTAINER');
531 if( $class eq 'copy' ) {
532 return $e->event unless
533 $stat = $e->update_container_copy_bucket($container);
536 if( $class eq 'callnumber' ) {
537 return $e->event unless
538 $stat = $e->update_container_call_number_bucket($container);
541 if( $class eq 'biblio' ) {
542 return $e->event unless
543 $stat = $e->update_container_biblio_record_entry_bucket($container);
546 if( $class eq 'user') {
547 return $e->event unless
548 $stat = $e->update_container_user_bucket($container);
557 __PACKAGE__->register_method(
558 method => "anon_cache",
559 api_name => "open-ils.actor.anon_cache.set_value",
562 Sets a value in the anon web cache. If the session key is
563 undefined, one will be automatically generated.
566 {desc => 'Session key', type => 'string'},
568 desc => q/Field name. The name of the field in this cache session whose value to set/,
572 desc => q/The cached value. This can be any type of object (hash, array, string, etc.)/,
577 desc => 'session key on success, undef on error',
583 __PACKAGE__->register_method(
584 method => "anon_cache",
585 api_name => "open-ils.actor.anon_cache.get_value",
588 Returns the cached data at the specified field within the specified cache session.
591 {desc => 'Session key', type => 'string'},
593 desc => q/Field name. The name of the field in this cache session whose value to set/,
598 desc => 'cached value on success, undef on error',
604 __PACKAGE__->register_method(
605 method => "anon_cache",
606 api_name => "open-ils.actor.anon_cache.delete_session",
609 Deletes a cache session.
612 {desc => 'Session key', type => 'string'},
615 desc => 'Session key',
622 my($self, $conn, $ses_key, $field_key, $value) = @_;
624 my $sc = OpenSRF::Utils::SettingsClient->new;
625 my $cache = OpenSRF::Utils::Cache->new('anon');
626 my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
627 my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
629 if($self->api_name =~ /delete_session/) {
631 return $cache->delete_cache($ses_key);
633 } elsif( $self->api_name =~ /set_value/ ) {
635 $ses_key = md5_hex(time . rand($$)) unless $ses_key;
636 my $blob = $cache->get_cache($ses_key) || {};
637 $blob->{$field_key} = $value;
639 length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
640 $cache->put_cache($ses_key, $blob, $cache_timeout);
645 my $blob = $cache->get_cache($ses_key) or return undef;
646 return $blob if (!defined($field_key));
647 return $blob->{$field_key};