]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/services/pcrud.js
72cb94ec84a321d8fb3cfe9375d52b2374affe5a
[working/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, req_ops) {
95             req_ops = req_ops || {};
96             this.authoritative = req_ops.authoritative;
97             return this._dispatch(
98                 'open-ils.pcrud.retrieve.' + fm_class,
99                 [egAuth.token(), pkey, pcrud_ops]
100             );
101         };
102
103         this.retrieveAll = function(fm_class, pcrud_ops, req_ops) {
104             var search = {};
105             search[egIDL.classes[fm_class].pkey] = {'!=' : null};
106             return this.search(fm_class, search, pcrud_ops, req_ops);
107         };
108
109         this.search = function (fm_class, search, pcrud_ops, req_ops) {
110             req_ops = req_ops || {};
111             this.authoritative = req_ops.authoritative;
112
113             var return_type = req_ops.idlist ? 'id_list' : 'search';
114             var method = 'open-ils.pcrud.' + return_type + '.' + fm_class;
115
116             if (req_ops.atomic) method += '.atomic';
117
118             return this._dispatch(method, 
119                 [egAuth.token(), search, pcrud_ops]);
120         };
121
122         this.create = function(list) {return this.CUD('create', list)};
123         this.update = function(list) {return this.CUD('update', list)};
124         this.remove = function(list) {return this.CUD('delete', list)};
125         this.apply  = function(list) {return this.CUD('apply',  list)};
126
127         this.xactClose = function() {
128             return this._send_request(
129                 'open-ils.pcrud.transaction.' + this.xact_close_mode,
130                 [egAuth.token()]
131             );
132         };
133
134         this.xactBegin = function() {
135             return this._send_request(
136                 'open-ils.pcrud.transaction.begin',
137                 [egAuth.token()]
138             );
139         };
140
141         this._dispatch = function(method, params) {
142             if (this.authoritative) {
143                 return this._wrap_xact(
144                     function() {
145                         return self._send_request(method, params);
146                     }
147                 );
148             } else {
149                 return this._send_request(method, params)
150             }
151         };
152
153
154         // => connect
155         // => xact_begin 
156         // => action
157         // => xact_close(commit/rollback) 
158         // => disconnect
159         // Returns a promise
160         // main_func should return a promise
161         this._wrap_xact = function(main_func) {
162             var deferred = $q.defer();
163
164             // 1. connect
165             this.connect().then(function() {
166
167             // 2. start the transaction
168             self.xactBegin().then(function() {
169
170             // 3. execute the main body 
171             main_func().then(
172                 // main body complete
173                 function(lastResp) {  
174
175                     // 4. close the transaction
176                     self.xactClose().then(function() {
177                         // 5. disconnect
178                         self.disconnect();
179                         // 6. all done
180                         deferred.resolve(lastResp);
181                     });
182                 },
183
184                 // main body error handler
185                 function() {deferred.reject()}, 
186
187                 // main body notify() handler
188                 function(data) {deferred.notify(data)}
189             );
190
191             })}); // close 'em all up.
192
193             return deferred.promise;
194         };
195
196         this._send_request = function(method, params) {
197             this.log('_send_request(' + method + ')');
198             var deferred = $q.defer();
199             var lastResp;
200             this.session.request({
201                 method : method,
202                 params : params,
203                 onresponse : function(r) {
204                     var resp = r.recv();
205                     if (resp && (lastResp = resp.content())) {
206                         deferred.notify(lastResp);
207                     } else {
208                         // pcrud requests should always return something
209                         self.err(method + " returned no response");
210                     }
211                 },
212                 oncomplete : function() {
213                     deferred.resolve(lastResp);
214                 },
215
216                 onmethoderror : function(req, stat, stat_text) {
217                     self.err(method + " failed. \ncode => " 
218                         + stat + "\nstatus => " + stat_text 
219                         + "\nparams => " + js2JSON(params));
220
221                     if (stat == 401) {
222                         // 401 is the PCRUD equivalent of a NO_SESSION event
223                         $rootScope.$broadcast('egAuthExpired');
224                     }
225
226                     deferred.reject(req);
227                 }
228                 // Note: no onerror handler for websockets connections,
229                 // because errors exist and are reported as top-level
230                 // conditions, not request-specific conditions.
231                 // Practically every error we care about (minus loss of 
232                 // connection) will be reported as a method error.
233             }).send();
234
235             return deferred.promise;
236         };
237
238         this.CUD = function (action, list) {
239             this.log('CUD(): ' + action);
240
241             this.cud_idx = 0;
242             this.cud_action = action;
243             this.xact_close_mode = 'commit';
244             this.cud_list = list;
245             this.cud_deferred = $q.defer();
246
247             if (!angular.isArray(list) || list.classname)
248                 this.cud_list = [list];
249
250             return this._wrap_xact(
251                 function() {
252                     self._CUD_next_request();
253                     return self.cud_deferred.promise;
254                 }
255             );
256         }
257
258         /**
259          * Loops through the list of objects to update and sends
260          * them one at a time to the server for processing.  Once
261          * all are done, the cud_deferred promise is resolved.
262          */
263         this._CUD_next_request = function() {
264
265             if (this.cud_idx >= this.cud_list.length) {
266                 this.cud_deferred.resolve(this.cud_last);
267                 return;
268             }
269
270             var action = this.cud_action;
271             var fm_obj = this.cud_list[this.cud_idx++];
272
273             if (action == 'apply') {
274                 if (fm_obj.ischanged()) action = 'update';
275                 if (fm_obj.isnew())     action = 'create';
276                 if (fm_obj.isdeleted()) action = 'delete';
277
278                 if (action == 'apply') {
279                     // object does not need updating; move along
280                     this._CUD_next_request();
281                 }
282             }
283
284             this._send_request(
285                 'open-ils.pcrud.' + action + '.' + fm_obj.classname,
286                 [egAuth.token(), fm_obj]).then(
287                 function(data) {
288                     // update actions return one response.
289                     // no notify() handler needed.
290                     self.cud_last = data;
291                     self.cud_deferred.notify(data);
292                     self._CUD_next_request();
293                 },
294                 self.cud_deferred.reject
295             );
296            
297         };
298     }
299
300     return service;
301 }]);
302