]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/services/pcrud.js
LP#1350042 Browser client templates/scripts (phase 1)
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / services / pcrud.js
1 /**
2  * Core Service - egPCRUD
3  *
4  * PCRUD client.
5  *
6  * Factory for PCRUDContext objects with pass-through service-level API.
7  *
8  * For most types of communication, where the client expects to make a
9  * single request which egPCRUD manages internally, use the service-
10  * level API.
11  *
12  * All service-level APIs (except connect()) return a promise, whose
13  * notfiy() channels individual responses (think: onresponse) and 
14  * whose resolve() channels the last received response (think: 
15  * oncomplete), consistent with egNet.request().  If only one response
16  * is expected (e.g. retrieve(), or .atomic searches), notify() 
17  * handlers are not required.
18  *
19  * egPCRUD.retrieve('aou', 1)
20  * .then(function(org) { console.log(org.shortname()) });
21  *
22  * egPCRUD.search('aou', {id : [1,2,3]})
23  * .then(function(orgs) { console.log(orgs.length) } );
24  *
25  * egPCRUD.search('aou', {id : {'!=' : null}}, {limit : 10})
26  * .then(...);
27  *
28  * For requests where the caller needs to manually connect and make
29  * individual API calls, the service.connect() call will create and
30  * pass a PCRUDContext object as the argument to the connect promise 
31  * resolver.  The PCRUDContext object can be used to make subsequent 
32  * pcrud calls directly.
33  *
34  * egPCRUD.connnect()
35  * .then(function(ctx) { return ctx.retrieve('aou', 1) })
36  * .then(function(org) { console.log(org.id()); ctx.disconnect() })
37  *
38  */
39 angular.module('egCoreMod')
40
41 .factory('egPCRUD', ['$q','$rootScope','egAuth','egIDL', 
42              function($q , $rootScope , egAuth , egIDL) { 
43     
44     var service = {};
45
46     // create service-level pass through functions 
47     // for one-off PCRUDContext actions.
48     angular.forEach(['connect', 'retrieve', 'retrieveAll', 
49         'search', 'create', 'update', 'remove', 'apply'],
50         function(action) {
51             service[action] = function() {
52                 var ctx = new PCRUDContext();
53                 return ctx[action].apply(ctx, arguments);
54             }
55         }
56     );
57
58     /*
59      * Since services are singleton objectss, we need an internal 
60      * class to manage individual PCRUD conversations.  
61      */
62     var PCRUDContextIdent = 0; // useful for debug logging
63     function PCRUDContext() {
64         var self = this;
65         this.xact_close_mode = 'rollback';
66         this.ident = PCRUDContextIdent++;
67         this.session = new OpenSRF.ClientSession('open-ils.pcrud');
68
69         this.toString = function() {
70             return '[PCRUDContext ' + this.ident + ']';
71         };
72
73         this.log = function(msg) {
74             console.debug(this + ': ' + msg);
75         };
76
77         this.err = function(msg) {
78             console.error(this + ': ' + msg);
79         };
80
81         this.connect = function() {
82             this.log('connect');
83             var deferred = $q.defer();
84             this.session.connect({onconnect : 
85                 function() {deferred.resolve(self)}});
86             return deferred.promise;
87         };
88
89         this.disconnect = function() {
90             this.log('disconnect');
91             this.session.disconnect();
92         };
93
94         this.retrieve = function(fm_class, pkey, pcrud_ops) {
95             return this._dispatch(
96                 'open-ils.pcrud.retrieve.' + fm_class,
97                 [egAuth.token(), pkey, pcrud_ops]
98             );
99         };
100
101         this.retrieveAll = function(fm_class, pcrud_ops, req_ops) {
102             var search = {};
103             search[egIDL.classes[fm_class].pkey] = {'!=' : null};
104             return this.search(fm_class, search, pcrud_ops, req_ops);
105         };
106
107         this.search = function (fm_class, search, pcrud_ops, req_ops) {
108             req_ops = req_ops || {};
109
110             var return_type = req_ops.idlist ? 'id_list' : 'search';
111             var method = 'open-ils.pcrud.' + return_type + '.' + fm_class;
112
113             if (req_ops.atomic) method += '.atomic';
114
115             return this._dispatch(method, 
116                 [egAuth.token(), search, pcrud_ops]);
117         };
118
119         this.create = function(list) {return this.CUD('create', list)};
120         this.update = function(list) {return this.CUD('update', list)};
121         this.remove = function(list) {return this.CUD('delete', list)};
122         this.apply  = function(list) {return this.CUD('apply',  list)};
123
124         this.xactClose = function() {
125             return this._send_request(
126                 'open-ils.pcrud.transaction.' + this.xact_close_mode,
127                 [egAuth.token()]
128             );
129         };
130
131         this.xactBegin = function() {
132             return this._send_request(
133                 'open-ils.pcrud.transaction.begin',
134                 [egAuth.token()]
135             );
136         };
137
138         this._dispatch = function(method, params) {
139             if (this.authoritative) {
140                 return this._wrap_xact(
141                     function() {
142                         return self._send_request(method, params);
143                     }
144                 );
145             } else {
146                 return this._send_request(method, params)
147             }
148         };
149
150
151         // => connect
152         // => xact_begin 
153         // => action
154         // => xact_close(commit/rollback) 
155         // => disconnect
156         // Returns a promise
157         // main_func should return a promise
158         this._wrap_xact = function(main_func) {
159             var deferred = $q.defer();
160
161             // 1. connect
162             this.connect().then(function() {
163
164             // 2. start the transaction
165             self.xactBegin().then(function() {
166
167             // 3. execute the main body 
168             main_func().then(
169                 // main body complete
170                 function(lastResp) {  
171
172                     // 4. close the transaction
173                     self.xactClose().then(function() {
174                         // 5. disconnect
175                         self.disconnect();
176                         // 6. all done
177                         deferred.resolve(lastResp);
178                     });
179                 },
180
181                 // main body error handler
182                 function() {}, 
183
184                 // main body notify() handler
185                 function(data) {deferred.notify(data)}
186             );
187
188             })}); // close 'em all up.
189
190             return deferred.promise;
191         };
192
193         this._send_request = function(method, params) {
194             this.log('_send_request(' + method + ')');
195             var deferred = $q.defer();
196             var lastResp;
197             this.session.request({
198                 method : method,
199                 params : params,
200                 onresponse : function(r) {
201                     var resp = r.recv();
202                     if (resp && (lastResp = resp.content())) {
203                         deferred.notify(lastResp);
204                     } else {
205                         // pcrud requests should always return something
206                         self.err(method + " returned no response");
207                     }
208                 },
209                 oncomplete : function() {
210                     deferred.resolve(lastResp);
211                 },
212
213                 onmethoderror : function(req, stat, stat_text) {
214                     self.err(method + " failed. \ncode => " 
215                         + stat + "\nstatus => " + stat_text 
216                         + "\nparams => " + js2JSON(params));
217
218                     if (stat == 401) {
219                         // 401 is the PCRUD equivalent of a NO_SESSION event
220                         $rootScope.$broadcast('egAuthExpired');
221                     }
222
223                     deferred.reject(req);
224                 }
225                 // Note: no onerror handler for websockets connections,
226                 // because errors exist and are reported as top-level
227                 // conditions, not request-specific conditions.
228                 // Practically every error we care about (minus loss of 
229                 // connection) will be reported as a method error.
230             }).send();
231
232             return deferred.promise;
233         };
234
235         this.CUD = function (action, list) {
236             this.log('CUD(): ' + action);
237
238             this.cud_idx = 0;
239             this.cud_action = action;
240             this.xact_close_mode = 'commit';
241             this.cud_list = list;
242             this.cud_deferred = $q.defer();
243
244             if (!angular.isArray(list) || list.classname)
245                 this.cud_list = [list];
246
247             return this._wrap_xact(
248                 function() {
249                     self._CUD_next_request();
250                     return self.cud_deferred.promise;
251                 }
252             );
253         }
254
255         /**
256          * Loops through the list of objects to update and sends
257          * them one at a time to the server for processing.  Once
258          * all are done, the cud_deferred promise is resolved.
259          */
260         this._CUD_next_request = function() {
261
262             if (this.cud_idx >= this.cud_list.length) {
263                 this.cud_deferred.resolve(this.cud_last);
264                 return;
265             }
266
267             var action = this.cud_action;
268             var fm_obj = this.cud_list[this.cud_idx++];
269
270             if (action == 'auto') {
271                 if (fm_obj.ischanged()) action = 'update';
272                 if (fm_obj.isnew())     action = 'create';
273                 if (fm_obj.isdeleted()) action = 'delete';
274
275                 if (action == 'auto') {
276                     // object does not need updating; move along
277                     this._CUD_next_request();
278                 }
279             }
280
281             this._send_request(
282                 'open-ils.pcrud.' + action + '.' + fm_obj.classname,
283                 [egAuth.token(), fm_obj]).then(
284                 function(data) {
285                     // update actions return one response.
286                     // no notify() handler needed.
287                     self.cud_last = data;
288                     self.cud_deferred.notify(data);
289                     self._CUD_next_request();
290                 }
291             );
292            
293         };
294     }
295
296     return service;
297 }]);
298