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