c284c65fb1c6251034120908939a3b758dd98a04
[working/Evergreen.git] / Open-ILS / web / js / dojo / openils / PermaCrud.js
1 /* ---------------------------------------------------------------------------
2  * Copyright (C) 2008  Equinox Software, Inc
3  * Mike Rylander <miker@esilibrary.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * ---------------------------------------------------------------------------
15  */
16
17 if(!dojo._hasResource["openils.PermaCrud"]) {
18
19     dojo._hasResource["openils.PermaCrud"] = true;
20     dojo.provide("openils.PermaCrud");
21     dojo.require("fieldmapper.Fieldmapper");
22     dojo.require("openils.User");
23
24     dojo.declare('openils.PermaCrud', null, {
25
26         session : null,
27         authtoken : null,
28         connnected : false,
29         authoritative : false,
30
31         constructor : function ( kwargs ) {
32             kwargs = kwargs || {};
33
34             this.authtoken = kwargs.authtoken;
35             this.authoritative = kwargs.authoritative;
36
37             this.session =
38                 kwargs.session ||
39                 new OpenSRF.ClientSession('open-ils.pcrud');
40
41             if (
42                 this.session &&
43                 this.session.state == OSRF_APP_SESSION_CONNECTED
44             ) this.connected = true;
45         },
46
47         auth : function (token) {
48             if (token) this.authtoken = token;
49             return this.authtoken || openils.User.authtoken;
50         },
51
52         connect : function ( onerror ) {
53             if (!this.connected && !this.session.connect()) {
54                 this.connected = false;
55                 if (onerror) onerror(this.session);
56                 return false;
57             }
58             this.connected = true;
59             return true;
60         },
61
62         disconnect : function ( onerror ) {
63             this.connected = false;
64             return true;
65             // disconnect returns nothing, which is null, which is not true, cause the following to always run ... arg.
66             if (!this.session.disconnect()) {
67                 if (onerror) onerror(this.session);
68                 return false;
69             }
70         },
71
72         _session_request : function ( args /* hash */, commitOnComplete /* set to true, else no */ ) {
73
74             var me = this;
75             var endstyle = 'rollback';
76             var aopts = dojo.mixin({}, args);
77             args = aopts;
78             if (commitOnComplete) endstyle = 'commit';
79
80             if (me.authoritative) {
81                 if (!me.connected) me.connect();
82                 if (args.timeout && !args.oncomplete && !args.onresponse) { // pure sync call
83                     args.oncomplete = function (r) {
84                         me.session.request('open-ils.pcrud.transaction.' + endstyle, me.auth());
85                         me.session.disconnect();
86                         me.disconnect();
87                     };
88                 } else if (args.oncomplete) { // there's an oncomplete, fire that, and then end the transaction
89                     var orig_oncomplete = args.oncomplete;
90                     args.oncomplete = function (r) {
91                         var ret;
92                         try {
93                             ret = orig_oncomplete(r);
94                         } finally {
95                             me.session.request('open-ils.pcrud.transaction.' + endstyle, me.auth());
96                             me.session.disconnect();
97                             me.disconnect();
98                         }
99                         return ret;
100                     };
101                 }
102                 me.session.request('open-ils.pcrud.transaction.begin', me.auth());
103             }
104             return me.session.request( args );
105         },
106
107         retrieve : function ( fm_class /* Fieldmapper class hint */, id /* Fieldmapper object primary key value */,  opts /* Option hash */) {
108             if(!opts) opts = {};
109             var ffj = {};
110             if (opts.join) ffj.join = opts.join;
111             if (opts.flesh) ffj.flesh = opts.flesh;
112             if (opts.flesh_fields) ffj.flesh_fields = opts.flesh_fields;
113             var req_hash = dojo.mixin(
114                 opts, 
115                 { method : 'open-ils.pcrud.retrieve.' + fm_class,
116                   params : [ this.auth(), id, ffj ]
117                 }
118             );
119
120             if (!opts.async && !opts.timeout) req_hash.timeout = 10;
121
122             var _pcrud = this;
123             var req = this._session_request( req_hash );
124
125             if (!req.onerror)
126                 req.onerror = function (r) { throw js2JSON(r); };
127
128             // if it's an async call and the user does not care about 
129             // the responses, pull them off the network and discard them
130             if (!req_hash.timeout && !req.oncomplete)
131                 req.oncomplete = function (r) { while(r.recv()){}; };
132
133             req.send();
134
135             // for synchronous calls with no handlers, return the first received value
136             if (req_hash.timeout && !opts.oncomplete && !opts.onresponse) {
137                 var resp = req.recv();
138                 if(resp) return resp.content();
139                 return null;
140             }
141
142             return req;
143         },
144
145         retrieveAll : function ( fm_class /* Fieldmapper class hint */, opts /* Option hash */) {
146             var pkey = fieldmapper[fm_class].Identifier;
147
148             if(!opts) opts = {};
149             var order_by = {};
150             if (opts.order_by) order_by.order_by = opts.order_by;
151             if (opts.select) order_by.select = opts.select;
152             if (opts.limit) order_by.limit = opts.limit;
153             if (opts.offset) order_by.offset = opts.offset;
154             if (opts.join) order_by.join = opts.join;
155             if (opts.flesh) order_by.flesh = opts.flesh;
156             if (opts.flesh_fields) order_by.flesh_fields = opts.flesh_fields;
157             
158             var method = 'open-ils.pcrud.search.' + fm_class;
159             if(!opts.streaming) method += '.atomic';
160
161             var search = {};
162             search[pkey] = { '!=' : null };
163
164             var req_hash = dojo.mixin(
165                 opts, 
166                 { method : method,
167                   params : [ this.auth(), search, order_by ]
168                 }
169             );
170
171             if (!opts.async && !opts.timeout) req_hash.timeout = 10;
172
173             var _pcrud = this;
174             var req = this._session_request( req_hash );
175
176             if (!req.onerror)
177                 req.onerror = function (r) { throw js2JSON(r); };
178             
179             // if it's an async call and the user does not care about 
180             // the responses, pull them off the network and discard them
181             if (!req_hash.timeout && !req.oncomplete)
182                 req.oncomplete = function (r) { while(r.recv()){}; };
183
184             req.send();
185
186             // for synchronous calls with no handlers, return the first received value
187             if (req_hash.timeout && !opts.oncomplete && !opts.onresponse) {
188                 var resp = req.recv();
189                 if(resp) return resp.content();
190                 return null;
191             }
192
193             return req;
194         },
195
196         search : function ( fm_class /* Fieldmapper class hint */, search /* Fieldmapper query object */, opts /* Option hash */) {
197             var return_type = 'search';
198             if(!opts) opts = {};
199             var order_by = {};
200             if (opts.order_by) order_by.order_by = opts.order_by;
201             if (opts.select) order_by.select = opts.select;
202             if (opts.limit) order_by.limit = opts.limit;
203             if (opts.offset) order_by.offset = opts.offset;
204             if (opts.join) order_by.join = opts.join;
205             if (opts.flesh) order_by.flesh = opts.flesh;
206             if (opts.flesh_fields) order_by.flesh_fields = opts.flesh_fields;
207             if (opts.id_list) return_type = 'id_list';
208
209             var method = 'open-ils.pcrud.' + return_type + '.' + fm_class;
210             if(!opts.streaming) method += '.atomic';
211
212             var req_hash = dojo.mixin(
213                 opts, 
214                 { method : method,
215                   params : [ this.auth(), search, order_by ]
216                 }
217             );
218
219             if (!opts.async && !opts.timeout) req_hash.timeout = 10;
220
221             var _pcrud = this;
222             var req = this._session_request( req_hash );
223
224             if (!req.onerror)
225                 req.onerror = function (r) { throw js2JSON(r); };
226
227             // if it's an async call and the user does not care about 
228             // the responses, pull them off the network and discard them
229             if (!req_hash.timeout && !req.oncomplete)
230                 req.oncomplete = function (r) { while(r.recv()){}; };
231
232             req.send();
233
234             // for synchronous calls with no handlers, return the first received value
235             if (req_hash.timeout && !opts.oncomplete && !opts.onresponse) {
236                 var resp = req.recv();
237                 if(resp) return resp.content();
238                 return null;
239             }
240
241             return req;
242         },
243
244         _CUD : function ( method /* 'create' or 'update' or 'delete' */, list /* Fieldmapper object */, opts /* Option hash */) {
245             if(!opts) opts = {};
246
247             if (dojo.isArray(list)) {
248                 if (list.classname) list = [ list ];
249             } else {
250                 list = [ list ];
251             }
252
253             if (!this.connected) this.connect();
254
255             var _pcrud = this;
256             var _return_list = [];
257
258             function _CUD_recursive ( obj_list, pos, final_complete, final_error ) {
259                 var obj = obj_list[pos];
260                 var req_hash = {
261                     method : 'open-ils.pcrud.' + method + '.' + obj.classname,
262                     params : [ _pcrud.auth(), obj ],
263                     onerror : final_error || function (r) { _pcrud.disconnect(); throw '_CUD: Error creating, deleting or updating ' + js2JSON(obj); }
264                 };
265
266                 var req = _pcrud.session.request( req_hash );
267                 req._final_complete = final_complete;
268                 req._final_error = final_error;
269
270                 if (++pos == obj_list.length) {
271                     req.oncomplete = function (r) {
272                         var res = r.recv();
273
274                         if ( res && res.content() ) {
275                             _return_list.push( res.content() );
276                             _pcrud.session.request({
277                                 method : 'open-ils.pcrud.transaction.commit',
278                                 timeout : 10,
279                                 params : [ _pcrud.auth() ],
280                                 onerror : function (r) {
281                                     _pcrud.disconnect();
282                                     if (req._final_error) req._final_error(r)
283                                     else throw 'Transaction commit error';
284                                 },      
285                                 oncomplete : function (r) {
286                                     var res = r.recv();
287                                     if ( res && res.content() ) {
288                                         if(req._final_complete)
289                                             req._final_complete(req, _return_list);
290                                         _pcrud.disconnect();
291                                     } else {
292                                         _pcrud.disconnect();
293                                         if (req._final_error) req._final_error(r)
294                                         else throw 'Transaction commit error';
295                                     }
296                                 },
297                             }).send();
298                         } else {
299                             _pcrud.disconnect();
300                             if (req._final_error) req._final_error(r)
301                             else throw '_CUD: Error creating, deleting or updating ' + js2JSON(obj);
302                         }
303                     };
304
305                     req.onerror = function (r) {
306                         _pcrud.disconnect();
307                         if (req._final_error) req._final_error(r);
308                         else throw '_CUD: Error creating, deleting or updating ' + js2JSON(obj);
309                     };
310
311                 } else {
312                     req._pos = pos;
313                     req._obj_list = obj_list;
314                     req.oncomplete = function (r) {
315                         var res = r.recv();
316                         if ( res && res.content() ) {
317                             _return_list.push( res.content() );
318                             _CUD_recursive( r._obj_list, r._pos, req._final_complete, req._final_error );
319                         } else {
320                             _pcrud.disconnect();
321                             if (req._final_error) req._final_error(r);
322                             else throw '_CUD: Error creating, deleting or updating ' + js2JSON(obj);
323                         }
324                     };
325                     req.onerror = function (r) {
326                         _pcrud.disconnect();
327                         if (req._final_error) req._final_error(r);
328                         throw '_CUD: Error creating, deleting or updating ' + js2JSON(obj);
329                     };
330                 }
331
332                 req.send();
333             }
334
335             var f_complete = opts.oncomplete;
336             var f_error = opts.onerror;
337
338             this.session.request({
339                 method : 'open-ils.pcrud.transaction.begin',
340                 timeout : 10,
341                 params : [ _pcrud.auth() ],
342                 onerror : function (r) {
343                     _pcrud.disconnect();
344                     throw 'Transaction begin error';
345                 },      
346                 oncomplete : function (r) {
347                     var res = r.recv();
348                     if ( res && res.content() ) {
349                         _CUD_recursive( list, 0, f_complete, f_error );
350                     } else {
351                         _pcrud.disconnect();
352                         throw 'Transaction begin error';
353                     }
354                 },
355             }).send();
356
357             return _return_list;
358
359         },
360
361         create : function ( list, opts ) {
362             return this._CUD( 'create', list, opts );
363         },
364
365         update : function ( list, opts ) {
366             var id_list = this._CUD( 'update', list, opts );
367             var obj_list = [];
368
369             for (var idx = 0; idx < id_list.length; idx++) {
370                 obj_list.push(
371                     this.retrieve( list[idx].classname, id_list[idx] )
372                 );
373             }
374
375             return obj_list;
376         },
377
378         /* 
379          * 'delete' is a reserved keyword in JavaScript and can't be used
380          * in browsers like IE or Chrome, so we define a safe synonym
381      * NOTE: delete() is now removed -- use eliminate instead
382
383         delete : function ( list, opts ) {
384             return this._CUD( 'delete', list, opts );
385         },
386
387          */
388         eliminate: function ( list, opts ) {
389             return this._CUD( 'delete', list, opts );
390         },
391
392         apply : function ( list, opts ) {
393             this._auto_CUD( list, opts );
394         },
395
396         _auto_CUD : function ( list /* Fieldmapper object */, opts /* Option hash */) {
397
398             if(!opts) opts = {};
399
400             if (dojo.isArray(list)) {
401                 if (list.classname) list = [ list ];
402             } else {
403                 list = [ list ];
404             }
405
406             if (!this.connected) this.connect();
407
408             var _pcrud = this;
409             var _return_list = [];
410
411             function _auto_CUD_recursive ( obj_list, pos, final_complete, final_error ) {
412                 var obj = obj_list[pos];
413
414                 var method;
415                 if (obj.ischanged()) method = 'update';
416                 if (obj.isnew())     method = 'create';
417                 if (obj.isdeleted()) method = 'delete';
418                 if (!method) {
419                     return _auto_CUD_recursive(obj_list, pos+1, final_complete, final_error);
420                 }
421
422                 var req_hash = {
423                     method : 'open-ils.pcrud.' + method + '.' + obj.classname,
424                     timeout : 10,
425                     params : [ _pcrud.auth(), obj ],
426                     onerror : final_error || function (r) { _pcrud.disconnect(); throw '_auto_CUD: Error creating, deleting or updating ' + js2JSON(obj); }
427                 };
428
429                 var req = _pcrud.session.request( req_hash );
430                 req._final_complete = final_complete;
431                 req._final_error = final_error;
432
433                 if (++pos == obj_list.length) {
434                     req.oncomplete = function (r) {
435                         var res = r.recv();
436
437                         if ( res && res.content() ) {
438                             _return_list.push( res.content() );
439                             _pcrud.session.request({
440                                 method : 'open-ils.pcrud.transaction.commit',
441                                 timeout : 10,
442                                 params : [ _pcrud.auth() ],
443                                 onerror : function (r) {
444                                     _pcrud.disconnect();
445                                     throw 'Transaction commit error';
446                                 },      
447                                 oncomplete : function (r) {
448                                     var res = r.recv();
449                                     if ( res && res.content() ) {
450                                         if (req._final_complete) 
451                                             req._final_complete(req, _return_list);
452                                         _pcrud.disconnect();
453                                     } else {
454                                         _pcrud.disconnect();
455                                         if (req._final_error) req._final_error(r);
456                                         else throw 'Transaction commit error';
457                                     }
458                                 },
459                             }).send();
460                         } else {
461                             _pcrud.disconnect();
462                             if (req._final_error) req._final_error(r)
463                             else throw '_auto_CUD: Error creating, deleting or updating ' + js2JSON(obj);
464                         }
465                     };
466
467                     req.onerror = function (r) {
468                         _pcrud.disconnect();
469                         if (req._final_error) req._final_error(r);
470                     };
471
472                 } else {
473                     req._pos = pos;
474                     req._obj_list = obj_list;
475                     req.oncomplete = function (r) {
476                         var res = r.recv();
477                         if ( res && res.content() ) {
478                             _return_list.push( res.content() );
479                             _auto_CUD_recursive( r._obj_list, r._pos, req._final_complete, req._final_error );
480                         } else {
481                             _pcrud.disconnect();
482                             if (req._final_error) req._final_error(r);
483                             else throw '_auto_CUD: Error creating, deleting or updating ' + js2JSON(obj);
484                         }
485                     };
486                 }
487
488                 req.send();
489             }
490
491             var f_complete = opts.oncomplete;
492             var f_error = opts.onerror;
493
494             this.session.request({
495                 method : 'open-ils.pcrud.transaction.begin',
496                 timeout : 10,
497                 params : [ _pcrud.auth() ],
498                 onerror : function (r) {
499                     _pcrud.disconnect();
500                     throw 'Transaction begin error';
501                 },      
502                 oncomplete : function (r) {
503                     var res = r.recv();
504                     if ( res && res.content() ) {
505                         _auto_CUD_recursive( list, 0, f_complete, f_error );
506                     } else {
507                         _pcrud.disconnect();
508                         throw 'Transaction begin error';
509                     }
510                 },
511             }).send();
512
513             return _return_list;
514         }
515
516     });
517 }
518
519