]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/core/pcrud.service.ts
LP2045292 Color contrast for AngularJS patron bills
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / core / pcrud.service.ts
1 /* eslint-disable no-shadow, no-var */
2 import {Injectable} from '@angular/core';
3 import {Observable, Observer} from 'rxjs';
4 import {IdlService, IdlObject} from './idl.service';
5 import {NetService, NetRequest} from './net.service';
6 import {AuthService} from './auth.service';
7 import {StoreService} from './store.service';
8
9 // Externally defined.  Used here for debugging.
10 declare var js2JSON: (jsThing: any) => string;
11 declare var OpenSRF: any; // creating sessions
12
13 interface PcrudReqOps {
14     authoritative?: boolean;
15     anonymous?: boolean;
16     idlist?: boolean;
17     atomic?: boolean;
18     // If true, link-type fields which link to a class that defines a
19     // selector will be fleshed with the linked value.  This affects
20     // retrieve(), retrieveAll(), and search() calls.
21     fleshSelectors?: boolean;
22 }
23
24 // For for documentation purposes.
25 type PcrudResponse = any;
26
27 export class PcrudContext {
28
29     static verboseLogging = true; //
30     static identGenerator = 0; // for debug logging
31
32     private ident: number;
33     private authoritative: boolean;
34     private xactCloseMode: string;
35     private cudIdx: number;
36     private cudAction: string;
37     private cudLast: PcrudResponse;
38     private cudList: IdlObject[];
39
40     private idl: IdlService;
41     private net: NetService;
42     private auth: AuthService;
43
44     // Tracks nested CUD actions
45     cudObserver: Observer<PcrudResponse>;
46
47     session: any; // OpenSRF.ClientSession
48
49     constructor( // passed in by parent service -- not injected
50         egIdl: IdlService,
51         egNet: NetService,
52         egAuth: AuthService
53     ) {
54         this.idl = egIdl;
55         this.net = egNet;
56         this.auth = egAuth;
57         this.xactCloseMode = 'rollback';
58         this.ident = PcrudContext.identGenerator++;
59         this.session = new OpenSRF.ClientSession('open-ils.pcrud');
60     }
61
62     toString(): string {
63         return '[PCRUDContext ' + this.ident + ']';
64     }
65
66     log(msg: string): void {
67         if (PcrudContext.verboseLogging) {
68             console.debug(this + ': ' + msg);
69         }
70     }
71
72     err(msg: string): void {
73         console.error(this + ': ' + msg);
74     }
75
76     token(reqOps?: PcrudReqOps): string {
77         return (reqOps && reqOps.anonymous) ?
78             'ANONYMOUS' : this.auth.token();
79     }
80
81     connect(): Promise<PcrudContext> {
82         this.log('connect');
83         return new Promise( (resolve, reject) => {
84             this.session.connect({
85                 onconnect : () => { resolve(this); }
86             });
87         });
88     }
89
90     disconnect(): void {
91         this.log('disconnect');
92         this.session.disconnect();
93     }
94
95     // Adds "flesh" logic to retrieve linked values for all fields
96     // that link to a class which defines a selector field.
97     applySelectorFleshing(fmClass: string, pcrudOps: any) {
98         pcrudOps = pcrudOps || {};
99
100         if (!pcrudOps.flesh) {
101             pcrudOps.flesh = 1;
102         }
103
104         if (!pcrudOps.flesh_fields) {
105             pcrudOps.flesh_fields = {};
106         }
107
108         this.idl.classes[fmClass].fields
109             .filter(f =>
110                 f.datatype === 'link' && (
111                     f.reltype === 'has_a' || f.reltype === 'might_have'
112                 )
113             ).forEach(field => {
114
115                 const selector = this.idl.getLinkSelector(fmClass, field.name);
116                 if (!selector) { return; }
117
118                 if (field.map) {
119                 // For mapped fields, we only want to auto-flesh them
120                 // if both steps along the path are single-row fleshers.
121
122                     const mapClass = field['class'];
123                     const mapField = field.map;
124                     const def = this.idl.classes[mapClass].field_map[mapField];
125
126                     if (!(def.reltype === 'has_a' ||
127                       def.reltype === 'might_have')) {
128                     // Field maps to a remote field which may contain
129                     // multiple rows.  Skip it.
130                         return;
131                     }
132                 }
133
134                 if (!pcrudOps.flesh_fields[fmClass]) {
135                     pcrudOps.flesh_fields[fmClass] = [];
136                 }
137
138                 if (pcrudOps.flesh_fields[fmClass].indexOf(field.name) < 0) {
139                     pcrudOps.flesh_fields[fmClass].push(field.name);
140                 }
141             });
142     }
143
144     retrieve(fmClass: string, pkey: Number | string,
145         pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
146         reqOps = reqOps || {};
147         this.authoritative = reqOps.authoritative || false;
148         if (reqOps.fleshSelectors) {
149             this.applySelectorFleshing(fmClass, pcrudOps);
150         }
151         return this.dispatch(
152             `open-ils.pcrud.retrieve.${fmClass}`,
153             [this.token(reqOps), pkey, pcrudOps]);
154     }
155
156     retrieveAll(fmClass: string, pcrudOps?: any,
157         reqOps?: PcrudReqOps): Observable<PcrudResponse> {
158         const search = {};
159         search[this.idl.classes[fmClass].pkey] = {'!=' : null};
160         return this.search(fmClass, search, pcrudOps, reqOps);
161     }
162
163     search(fmClass: string, search: any,
164         pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
165         reqOps = reqOps || {};
166         this.authoritative = reqOps.authoritative || false;
167
168         const returnType = reqOps.idlist ? 'id_list' : 'search';
169         let method = `open-ils.pcrud.${returnType}.${fmClass}`;
170
171         if (reqOps.atomic) { method += '.atomic'; }
172
173         if (reqOps.fleshSelectors) {
174             this.applySelectorFleshing(fmClass, pcrudOps);
175         }
176
177         return this.dispatch(method, [this.token(reqOps), search, pcrudOps]);
178     }
179
180     create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
181         return this.cud('create', list);
182     }
183     update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
184         return this.cud('update', list);
185     }
186     remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
187         return this.cud('delete', list);
188     }
189     autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> { // RENAMED
190         return this.cud('auto',   list);
191     }
192
193     xactClose(): Observable<PcrudResponse> {
194         return this.sendRequest(
195             'open-ils.pcrud.transaction.' + this.xactCloseMode,
196             [this.token()]
197         );
198     }
199
200     xactBegin(): Observable<PcrudResponse> {
201         return this.sendRequest(
202             'open-ils.pcrud.transaction.begin', [this.token()]
203         );
204     }
205
206     private dispatch(method: string, params: any[]): Observable<PcrudResponse> {
207         if (this.authoritative && PcrudService.useAuthoritative) {
208             return this.wrapXact(() => {
209                 return this.sendRequest(method, params);
210             });
211         } else {
212             return this.sendRequest(method, params);
213         }
214     }
215
216
217     // => connect
218     // => xact_begin
219     // => action
220     // => xact_close(commit/rollback)
221     // => disconnect
222     wrapXact(mainFunc: () => Observable<PcrudResponse>): Observable<PcrudResponse> {
223         return new Observable(observer => {
224
225             // 1. connect
226             this.connect()
227
228             // 2. start the transaction
229                 .then(() => this.xactBegin().toPromise())
230
231             // 3. execute the main body
232                 .then(() => {
233
234                     mainFunc().subscribe(
235                         res => observer.next(res),
236                         (err: unknown) => observer.error(err),
237                         ()  => {
238                             this.xactClose().toPromise().then(
239                                 ok => {
240                                 // 5. disconnect
241                                     this.disconnect();
242                                     // 6. all done
243                                     observer.complete();
244                                 },
245                                 // xact close error
246                                 err => observer.error(err)
247                             );
248                         }
249                     );
250                 });
251         });
252     }
253
254     private sendRequest(method: string,
255         params: any[]): Observable<PcrudResponse> {
256
257         // this.log(`sendRequest(${method})`);
258
259         return this.net.requestCompiled(
260             new NetRequest(
261                 'open-ils.pcrud', method, params, this.session)
262         );
263     }
264
265     private cud(action: string,
266         list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
267         this.cudList = [].concat(list); // value or array
268
269         this.log(`CUD(): ${action}`);
270
271         this.cudIdx = 0;
272         this.cudAction = action;
273         this.xactCloseMode = 'commit';
274
275         return this.wrapXact(() => {
276             return new Observable(observer => {
277                 this.cudObserver = observer;
278                 this.nextCudRequest();
279             });
280         });
281     }
282
283     /**
284      * Loops through the list of objects to update and sends
285      * them one at a time to the server for processing.  Once
286      * all are done, the cudObserver is resolved.
287      */
288     nextCudRequest(): void {
289         if (this.cudIdx >= this.cudList.length) {
290             this.cudObserver.complete();
291             return;
292         }
293
294         let action = this.cudAction;
295         const fmObj = this.cudList[this.cudIdx++];
296
297         if (action === 'auto') {
298             if (fmObj.ischanged()) { action = 'update'; }
299             if (fmObj.isnew())     { action = 'create'; }
300             if (fmObj.isdeleted()) { action = 'delete'; }
301
302             if (action === 'auto') {
303                 // object does not need updating; move along
304                 return this.nextCudRequest();
305             }
306         }
307
308         this.sendRequest(
309             `open-ils.pcrud.${action}.${fmObj.classname}`,
310             [this.token(), fmObj]
311         ).subscribe(
312             res => this.cudObserver.next(res),
313             (err: unknown) => this.cudObserver.error(err),
314             ()  => this.nextCudRequest()
315         );
316     }
317 }
318
319 @Injectable({providedIn: 'root'})
320 export class PcrudService {
321     static useAuthoritative = true;
322
323     constructor(
324         private idl: IdlService,
325         private store: StoreService,
326         private net: NetService,
327         private auth: AuthService
328     ) {}
329
330     // Pass-thru functions for one-off PCRUD calls
331
332     connect(): Promise<PcrudContext> {
333         return this.newContext().connect();
334     }
335
336     newContext(): PcrudContext {
337         return new PcrudContext(this.idl, this.net, this.auth);
338     }
339
340     retrieve(fmClass: string, pkey: Number | string,
341         pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
342         return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps);
343     }
344
345     retrieveAll(fmClass: string, pcrudOps?: any,
346         reqOps?: PcrudReqOps): Observable<PcrudResponse> {
347         return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps);
348     }
349
350     search(fmClass: string, search: any,
351         pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
352         return this.newContext().search(fmClass, search, pcrudOps, reqOps);
353     }
354
355     create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
356         return this.newContext().create(list);
357     }
358
359     update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
360         return this.newContext().update(list);
361     }
362
363     remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
364         return this.newContext().remove(list);
365     }
366
367     autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
368         return this.newContext().autoApply(list);
369     }
370
371     setAuthoritative(): void {
372         const key = 'eg.sys.use_authoritative';
373
374         // Track the value as clearable on login/logout.
375         this.store.addLoginSessionKey(key);
376
377         const enabled = this.store.getLoginSessionItem(key);
378
379         if (typeof enabled === 'boolean') {
380             PcrudService.useAuthoritative = enabled;
381         } else {
382             this.net.request(
383                 'open-ils.actor',
384                 'opensrf.open-ils.system.use_authoritative'
385             ).subscribe({
386                 next: enabled => {
387                     enabled = Boolean(Number(enabled));
388                     PcrudService.useAuthoritative = enabled;
389                     this.store.setLoginSessionItem(key, enabled);
390                     console.debug('authoriative check function returned a value of ', enabled);
391                     return enabled;
392                 },
393                 // eslint-disable-next-line rxjs/no-implicit-any-catch
394                 error: err => {
395                     PcrudService.useAuthoritative = true;
396                     this.store.setLoginSessionItem(key, true);
397                     console.debug('authoriative check function failed somehow, assuming TRUE');
398                 },
399                 complete: () => console.debug('authoriative check function complete')
400             });
401         }
402     }
403 }
404
405