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