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) {
95 return this._dispatch(
96 'open-ils.pcrud.retrieve.' + fm_class,
97 [egAuth.token(), pkey, pcrud_ops]
101 this.retrieveAll = function(fm_class, pcrud_ops, req_ops) {
103 search[egIDL.classes[fm_class].pkey] = {'!=' : null};
104 return this.search(fm_class, search, pcrud_ops, req_ops);
107 this.search = function (fm_class, search, pcrud_ops, req_ops) {
108 req_ops = req_ops || {};
110 var return_type = req_ops.idlist ? 'id_list' : 'search';
111 var method = 'open-ils.pcrud.' + return_type + '.' + fm_class;
113 if (req_ops.atomic) method += '.atomic';
115 return this._dispatch(method,
116 [egAuth.token(), search, pcrud_ops]);
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)};
124 this.xactClose = function() {
125 return this._send_request(
126 'open-ils.pcrud.transaction.' + this.xact_close_mode,
131 this.xactBegin = function() {
132 return this._send_request(
133 'open-ils.pcrud.transaction.begin',
138 this._dispatch = function(method, params) {
139 if (this.authoritative) {
140 return this._wrap_xact(
142 return self._send_request(method, params);
146 return this._send_request(method, params)
154 // => xact_close(commit/rollback)
157 // main_func should return a promise
158 this._wrap_xact = function(main_func) {
159 var deferred = $q.defer();
162 this.connect().then(function() {
164 // 2. start the transaction
165 self.xactBegin().then(function() {
167 // 3. execute the main body
169 // main body complete
172 // 4. close the transaction
173 self.xactClose().then(function() {
177 deferred.resolve(lastResp);
181 // main body error handler
184 // main body notify() handler
185 function(data) {deferred.notify(data)}
188 })}); // close 'em all up.
190 return deferred.promise;
193 this._send_request = function(method, params) {
194 this.log('_send_request(' + method + ')');
195 var deferred = $q.defer();
197 this.session.request({
200 onresponse : function(r) {
202 if (resp && (lastResp = resp.content())) {
203 deferred.notify(lastResp);
205 // pcrud requests should always return something
206 self.err(method + " returned no response");
209 oncomplete : function() {
210 deferred.resolve(lastResp);
213 onmethoderror : function(req, stat, stat_text) {
214 self.err(method + " failed. \ncode => "
215 + stat + "\nstatus => " + stat_text
216 + "\nparams => " + js2JSON(params));
219 // 401 is the PCRUD equivalent of a NO_SESSION event
220 $rootScope.$broadcast('egAuthExpired');
223 deferred.reject(req);
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.
232 return deferred.promise;
235 this.CUD = function (action, list) {
236 this.log('CUD(): ' + action);
239 this.cud_action = action;
240 this.xact_close_mode = 'commit';
241 this.cud_list = list;
242 this.cud_deferred = $q.defer();
244 if (!angular.isArray(list) || list.classname)
245 this.cud_list = [list];
247 return this._wrap_xact(
249 self._CUD_next_request();
250 return self.cud_deferred.promise;
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.
260 this._CUD_next_request = function() {
262 if (this.cud_idx >= this.cud_list.length) {
263 this.cud_deferred.resolve(this.cud_last);
267 var action = this.cud_action;
268 var fm_obj = this.cud_list[this.cud_idx++];
270 if (action == 'apply') {
271 if (fm_obj.ischanged()) action = 'update';
272 if (fm_obj.isnew()) action = 'create';
273 if (fm_obj.isdeleted()) action = 'delete';
275 if (action == 'apply') {
276 // object does not need updating; move along
277 this._CUD_next_request();
282 'open-ils.pcrud.' + action + '.' + fm_obj.classname,
283 [egAuth.token(), fm_obj]).then(
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();