2 * Core Service - egPCRUD
6 * Factory for PCRUDContext objects with pass-through service-level API.
8 * For most types of communication, where the client expects to make a
9 * single request which egPCRUD manages internally, use the service-
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.
19 * egPCRUD.retrieve('aou', 1)
20 * .then(function(org) { console.log(org.shortname()) });
22 * egPCRUD.search('aou', {id : [1,2,3]})
23 * .then(function(orgs) { console.log(orgs.length) } );
25 * egPCRUD.search('aou', {id : {'!=' : null}}, {limit : 10})
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.
35 * .then(function(ctx) { return ctx.retrieve('aou', 1) })
36 * .then(function(org) { console.log(org.id()); ctx.disconnect() })
39 angular.module('egCoreMod')
41 .factory('egPCRUD', ['$q','$rootScope','egAuth','egIDL',
42 function($q , $rootScope , egAuth , egIDL) {
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'],
51 service[action] = function() {
52 var ctx = new PCRUDContext();
53 return ctx[action].apply(ctx, arguments);
59 * Since services are singleton objectss, we need an internal
60 * class to manage individual PCRUD conversations.
62 var PCRUDContextIdent = 0; // useful for debug logging
63 function PCRUDContext() {
65 this.xact_close_mode = 'rollback';
66 this.ident = PCRUDContextIdent++;
67 this.session = new OpenSRF.ClientSession('open-ils.pcrud');
69 this.toString = function() {
70 return '[PCRUDContext ' + this.ident + ']';
73 this.log = function(msg) {
74 console.debug(this + ': ' + msg);
77 this.err = function(msg) {
78 console.error(this + ': ' + msg);
81 this.connect = function() {
83 var deferred = $q.defer();
84 this.session.connect({onconnect :
85 function() {deferred.resolve(self)}});
86 return deferred.promise;
89 this.disconnect = function() {
90 this.log('disconnect');
91 this.session.disconnect();
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]
103 this.retrieveAll = function(fm_class, pcrud_ops, req_ops) {
105 search[egIDL.classes[fm_class].pkey] = {'!=' : null};
106 return this.search(fm_class, search, pcrud_ops, req_ops);
109 this.search = function (fm_class, search, pcrud_ops, req_ops) {
110 req_ops = req_ops || {};
111 this.authoritative = req_ops.authoritative;
113 var return_type = req_ops.idlist ? 'id_list' : 'search';
114 var method = 'open-ils.pcrud.' + return_type + '.' + fm_class;
116 if (req_ops.atomic) method += '.atomic';
118 return this._dispatch(method,
119 [egAuth.token(), search, pcrud_ops]);
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)};
127 this.xactClose = function() {
128 return this._send_request(
129 'open-ils.pcrud.transaction.' + this.xact_close_mode,
134 this.xactBegin = function() {
135 return this._send_request(
136 'open-ils.pcrud.transaction.begin',
141 this._dispatch = function(method, params) {
142 if (this.authoritative) {
143 return this._wrap_xact(
145 return self._send_request(method, params);
149 return this._send_request(method, params)
157 // => xact_close(commit/rollback)
160 // main_func should return a promise
161 this._wrap_xact = function(main_func) {
162 var deferred = $q.defer();
165 this.connect().then(function() {
167 // 2. start the transaction
168 self.xactBegin().then(function() {
170 // 3. execute the main body
172 // main body complete
175 // 4. close the transaction
176 self.xactClose().then(function() {
180 deferred.resolve(lastResp);
184 // main body error handler
185 function() {deferred.reject()},
187 // main body notify() handler
188 function(data) {deferred.notify(data)}
191 })}); // close 'em all up.
193 return deferred.promise;
196 this._send_request = function(method, params) {
197 this.log('_send_request(' + method + ')');
198 var deferred = $q.defer();
200 this.session.request({
203 onresponse : function(r) {
205 if (resp && (lastResp = resp.content())) {
206 deferred.notify(lastResp);
208 // pcrud requests should always return something
209 self.err(method + " returned no response");
212 oncomplete : function() {
213 deferred.resolve(lastResp);
216 onmethoderror : function(req, stat, stat_text) {
217 self.err(method + " failed. \ncode => "
218 + stat + "\nstatus => " + stat_text
219 + "\nparams => " + js2JSON(params));
222 // 401 is the PCRUD equivalent of a NO_SESSION event
223 $rootScope.$broadcast('egAuthExpired');
226 deferred.reject(req);
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.
235 return deferred.promise;
238 this.CUD = function (action, list) {
239 this.log('CUD(): ' + action);
242 this.cud_action = action;
243 this.xact_close_mode = 'commit';
244 this.cud_list = list;
245 this.cud_deferred = $q.defer();
247 if (!angular.isArray(list) || list.classname)
248 this.cud_list = [list];
250 return this._wrap_xact(
252 self._CUD_next_request();
253 return self.cud_deferred.promise;
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.
263 this._CUD_next_request = function() {
265 if (this.cud_idx >= this.cud_list.length) {
266 this.cud_deferred.resolve(this.cud_last);
270 var action = this.cud_action;
271 var fm_obj = this.cud_list[this.cud_idx++];
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';
278 if (action == 'apply') {
279 // object does not need updating; move along
280 this._CUD_next_request();
286 'open-ils.pcrud.' + action + '.' + fm_obj.classname,
287 [egAuth.token(), fm_obj]).then(
289 // update actions return one response.
290 // no notify() handler needed.
291 self.cud_last = data;
292 self.cud_deferred.notify(data);
293 self._CUD_next_request();
295 self.cud_deferred.reject