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