f023b6d2f594530f69c15f4a7365a10435a1f7f2
[working/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 => f.datatype === 'link' && !f.virtual)
108         .forEach(field => {
109             const selector = this.idl.getLinkSelector(fmClass, field.name);
110             if (!selector) { return; }
111
112             if (!pcrudOps.flesh_fields[fmClass]) {
113                 pcrudOps.flesh_fields[fmClass] = [];
114             }
115
116             if (pcrudOps.flesh_fields[fmClass].indexOf(field.name) < 0) {
117                 pcrudOps.flesh_fields[fmClass].push(field.name);
118             }
119         });
120     }
121
122     retrieve(fmClass: string, pkey: Number | string,
123             pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
124         reqOps = reqOps || {};
125         this.authoritative = reqOps.authoritative || false;
126         if (reqOps.fleshSelectors) {
127             this.applySelectorFleshing(fmClass, pcrudOps);
128         }
129         return this.dispatch(
130             `open-ils.pcrud.retrieve.${fmClass}`,
131              [this.token(reqOps), pkey, pcrudOps]);
132     }
133
134     retrieveAll(fmClass: string, pcrudOps?: any,
135             reqOps?: PcrudReqOps): Observable<PcrudResponse> {
136         const search = {};
137         search[this.idl.classes[fmClass].pkey] = {'!=' : null};
138         return this.search(fmClass, search, pcrudOps, reqOps);
139     }
140
141     search(fmClass: string, search: any,
142             pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
143         reqOps = reqOps || {};
144         this.authoritative = reqOps.authoritative || false;
145
146         const returnType = reqOps.idlist ? 'id_list' : 'search';
147         let method = `open-ils.pcrud.${returnType}.${fmClass}`;
148
149         if (reqOps.atomic) { method += '.atomic'; }
150
151         if (reqOps.fleshSelectors) {
152             this.applySelectorFleshing(fmClass, pcrudOps);
153         }
154
155         return this.dispatch(method, [this.token(reqOps), search, pcrudOps]);
156     }
157
158     create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
159         return this.cud('create', list);
160     }
161     update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
162         return this.cud('update', list);
163     }
164     remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
165         return this.cud('delete', list);
166     }
167     autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> { // RENAMED
168         return this.cud('auto',   list);
169     }
170
171     xactClose(): Observable<PcrudResponse> {
172         return this.sendRequest(
173             'open-ils.pcrud.transaction.' + this.xactCloseMode,
174             [this.token()]
175         );
176     }
177
178     xactBegin(): Observable<PcrudResponse> {
179         return this.sendRequest(
180             'open-ils.pcrud.transaction.begin', [this.token()]
181         );
182     }
183
184     private dispatch(method: string, params: any[]): Observable<PcrudResponse> {
185         if (this.authoritative) {
186             return this.wrapXact(() => {
187                 return this.sendRequest(method, params);
188             });
189         } else {
190             return this.sendRequest(method, params);
191         }
192     }
193
194
195     // => connect
196     // => xact_begin
197     // => action
198     // => xact_close(commit/rollback)
199     // => disconnect
200     wrapXact(mainFunc: () => Observable<PcrudResponse>): Observable<PcrudResponse> {
201         return Observable.create(observer => {
202
203             // 1. connect
204             this.connect()
205
206             // 2. start the transaction
207             .then(() => this.xactBegin().toPromise())
208
209             // 3. execute the main body
210             .then(() => {
211
212                 mainFunc().subscribe(
213                     res => observer.next(res),
214                     err => observer.error(err),
215                     ()  => {
216                         this.xactClose().toPromise().then(() => {
217                             // 5. disconnect
218                             this.disconnect();
219                             // 6. all done
220                             observer.complete();
221                         });
222                     }
223                 );
224             });
225         });
226     }
227
228     private sendRequest(method: string,
229             params: any[]): Observable<PcrudResponse> {
230
231         // this.log(`sendRequest(${method})`);
232
233         return this.net.requestCompiled(
234             new NetRequest(
235                 'open-ils.pcrud', method, params, this.session)
236         );
237     }
238
239     private cud(action: string,
240         list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
241         this.cudList = [].concat(list); // value or array
242
243         this.log(`CUD(): ${action}`);
244
245         this.cudIdx = 0;
246         this.cudAction = action;
247         this.xactCloseMode = 'commit';
248
249         return this.wrapXact(() => {
250             return Observable.create(observer => {
251                 this.cudObserver = observer;
252                 this.nextCudRequest();
253             });
254         });
255     }
256
257     /**
258      * Loops through the list of objects to update and sends
259      * them one at a time to the server for processing.  Once
260      * all are done, the cudObserver is resolved.
261      */
262     nextCudRequest(): void {
263         if (this.cudIdx >= this.cudList.length) {
264             this.cudObserver.complete();
265             return;
266         }
267
268         let action = this.cudAction;
269         const fmObj = this.cudList[this.cudIdx++];
270
271         if (action === 'auto') {
272             if (fmObj.ischanged()) { action = 'update'; }
273             if (fmObj.isnew())     { action = 'create'; }
274             if (fmObj.isdeleted()) { action = 'delete'; }
275
276             if (action === 'auto') {
277                 // object does not need updating; move along
278                 this.nextCudRequest();
279             }
280         }
281
282         this.sendRequest(
283             `open-ils.pcrud.${action}.${fmObj.classname}`,
284             [this.token(), fmObj]
285         ).subscribe(
286             res => this.cudObserver.next(res),
287             err => this.cudObserver.error(err),
288             ()  => this.nextCudRequest()
289         );
290     }
291 }
292
293 @Injectable({providedIn: 'root'})
294 export class PcrudService {
295
296     constructor(
297         private idl: IdlService,
298         private net: NetService,
299         private auth: AuthService
300     ) {}
301
302     // Pass-thru functions for one-off PCRUD calls
303
304     connect(): Promise<PcrudContext> {
305         return this.newContext().connect();
306     }
307
308     newContext(): PcrudContext {
309         return new PcrudContext(this.idl, this.net, this.auth);
310     }
311
312     retrieve(fmClass: string, pkey: Number | string,
313         pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
314         return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps);
315     }
316
317     retrieveAll(fmClass: string, pcrudOps?: any,
318         reqOps?: PcrudReqOps): Observable<PcrudResponse> {
319         return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps);
320     }
321
322     search(fmClass: string, search: any,
323         pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
324         return this.newContext().search(fmClass, search, pcrudOps, reqOps);
325     }
326
327     create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
328         return this.newContext().create(list);
329     }
330
331     update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
332         return this.newContext().update(list);
333     }
334
335     remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
336         return this.newContext().remove(list);
337     }
338
339     autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
340         return this.newContext().autoApply(list);
341     }
342 }
343
344