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';
9 // Externally defined. Used here for debugging.
10 declare var js2JSON: (jsThing: any) => string;
11 declare var OpenSRF: any; // creating sessions
13 interface PcrudReqOps {
14 authoritative?: 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;
24 // For for documentation purposes.
25 type PcrudResponse = any;
27 export class PcrudContext {
29 static verboseLogging = true; //
30 static identGenerator = 0; // for debug logging
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[];
40 private idl: IdlService;
41 private net: NetService;
42 private auth: AuthService;
44 // Tracks nested CUD actions
45 cudObserver: Observer<PcrudResponse>;
47 session: any; // OpenSRF.ClientSession
49 constructor( // passed in by parent service -- not injected
57 this.xactCloseMode = 'rollback';
58 this.ident = PcrudContext.identGenerator++;
59 this.session = new OpenSRF.ClientSession('open-ils.pcrud');
63 return '[PCRUDContext ' + this.ident + ']';
66 log(msg: string): void {
67 if (PcrudContext.verboseLogging) {
68 console.debug(this + ': ' + msg);
72 err(msg: string): void {
73 console.error(this + ': ' + msg);
76 token(reqOps?: PcrudReqOps): string {
77 return (reqOps && reqOps.anonymous) ?
78 'ANONYMOUS' : this.auth.token();
81 connect(): Promise<PcrudContext> {
83 return new Promise( (resolve, reject) => {
84 this.session.connect({
85 onconnect : () => { resolve(this); }
91 this.log('disconnect');
92 this.session.disconnect();
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 || {};
100 if (!pcrudOps.flesh) {
104 if (!pcrudOps.flesh_fields) {
105 pcrudOps.flesh_fields = {};
108 this.idl.classes[fmClass].fields
110 f.datatype === 'link' && (
111 f.reltype === 'has_a' || f.reltype === 'might_have'
115 const selector = this.idl.getLinkSelector(fmClass, field.name);
116 if (!selector) { return; }
119 // For mapped fields, we only want to auto-flesh them
120 // if both steps along the path are single-row fleshers.
122 const mapClass = field['class'];
123 const mapField = field.map;
124 const def = this.idl.classes[mapClass].field_map[mapField];
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.
134 if (!pcrudOps.flesh_fields[fmClass]) {
135 pcrudOps.flesh_fields[fmClass] = [];
138 if (pcrudOps.flesh_fields[fmClass].indexOf(field.name) < 0) {
139 pcrudOps.flesh_fields[fmClass].push(field.name);
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);
151 return this.dispatch(
152 `open-ils.pcrud.retrieve.${fmClass}`,
153 [this.token(reqOps), pkey, pcrudOps]);
156 retrieveAll(fmClass: string, pcrudOps?: any,
157 reqOps?: PcrudReqOps): Observable<PcrudResponse> {
159 search[this.idl.classes[fmClass].pkey] = {'!=' : null};
160 return this.search(fmClass, search, pcrudOps, reqOps);
163 search(fmClass: string, search: any,
164 pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
165 reqOps = reqOps || {};
166 this.authoritative = reqOps.authoritative || false;
168 const returnType = reqOps.idlist ? 'id_list' : 'search';
169 let method = `open-ils.pcrud.${returnType}.${fmClass}`;
171 if (reqOps.atomic) { method += '.atomic'; }
173 if (reqOps.fleshSelectors) {
174 this.applySelectorFleshing(fmClass, pcrudOps);
177 return this.dispatch(method, [this.token(reqOps), search, pcrudOps]);
180 create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
181 return this.cud('create', list);
183 update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
184 return this.cud('update', list);
186 remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
187 return this.cud('delete', list);
189 autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> { // RENAMED
190 return this.cud('auto', list);
193 xactClose(): Observable<PcrudResponse> {
194 return this.sendRequest(
195 'open-ils.pcrud.transaction.' + this.xactCloseMode,
200 xactBegin(): Observable<PcrudResponse> {
201 return this.sendRequest(
202 'open-ils.pcrud.transaction.begin', [this.token()]
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);
212 return this.sendRequest(method, params);
220 // => xact_close(commit/rollback)
222 wrapXact(mainFunc: () => Observable<PcrudResponse>): Observable<PcrudResponse> {
223 return new Observable(observer => {
228 // 2. start the transaction
229 .then(() => this.xactBegin().toPromise())
231 // 3. execute the main body
234 mainFunc().subscribe(
235 res => observer.next(res),
236 (err: unknown) => observer.error(err),
238 this.xactClose().toPromise().then(
246 err => observer.error(err)
254 private sendRequest(method: string,
255 params: any[]): Observable<PcrudResponse> {
257 // this.log(`sendRequest(${method})`);
259 return this.net.requestCompiled(
261 'open-ils.pcrud', method, params, this.session)
265 private cud(action: string,
266 list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
267 this.cudList = [].concat(list); // value or array
269 this.log(`CUD(): ${action}`);
272 this.cudAction = action;
273 this.xactCloseMode = 'commit';
275 return this.wrapXact(() => {
276 return new Observable(observer => {
277 this.cudObserver = observer;
278 this.nextCudRequest();
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.
288 nextCudRequest(): void {
289 if (this.cudIdx >= this.cudList.length) {
290 this.cudObserver.complete();
294 let action = this.cudAction;
295 const fmObj = this.cudList[this.cudIdx++];
297 if (action === 'auto') {
298 if (fmObj.ischanged()) { action = 'update'; }
299 if (fmObj.isnew()) { action = 'create'; }
300 if (fmObj.isdeleted()) { action = 'delete'; }
302 if (action === 'auto') {
303 // object does not need updating; move along
304 return this.nextCudRequest();
309 `open-ils.pcrud.${action}.${fmObj.classname}`,
310 [this.token(), fmObj]
312 res => this.cudObserver.next(res),
313 (err: unknown) => this.cudObserver.error(err),
314 () => this.nextCudRequest()
319 @Injectable({providedIn: 'root'})
320 export class PcrudService {
321 static useAuthoritative = true;
324 private idl: IdlService,
325 private store: StoreService,
326 private net: NetService,
327 private auth: AuthService
330 // Pass-thru functions for one-off PCRUD calls
332 connect(): Promise<PcrudContext> {
333 return this.newContext().connect();
336 newContext(): PcrudContext {
337 return new PcrudContext(this.idl, this.net, this.auth);
340 retrieve(fmClass: string, pkey: Number | string,
341 pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
342 return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps);
345 retrieveAll(fmClass: string, pcrudOps?: any,
346 reqOps?: PcrudReqOps): Observable<PcrudResponse> {
347 return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps);
350 search(fmClass: string, search: any,
351 pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
352 return this.newContext().search(fmClass, search, pcrudOps, reqOps);
355 create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
356 return this.newContext().create(list);
359 update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
360 return this.newContext().update(list);
363 remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
364 return this.newContext().remove(list);
367 autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
368 return this.newContext().autoApply(list);
371 setAuthoritative(): void {
372 const key = 'eg.sys.use_authoritative';
374 // Track the value as clearable on login/logout.
375 this.store.addLoginSessionKey(key);
377 const enabled = this.store.getLoginSessionItem(key);
379 if (typeof enabled === 'boolean') {
380 PcrudService.useAuthoritative = enabled;
384 'opensrf.open-ils.system.use_authoritative'
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);
393 // eslint-disable-next-line rxjs/no-implicit-any-catch
395 PcrudService.useAuthoritative = true;
396 this.store.setLoginSessionItem(key, true);
397 console.debug('authoriative check function failed somehow, assuming TRUE');
399 complete: () => console.debug('authoriative check function complete')