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';
7 // Externally defined. Used here for debugging.
8 declare var js2JSON: (jsThing: any) => string;
9 declare var OpenSRF: any; // creating sessions
11 interface PcrudReqOps {
12 authoritative?: 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;
22 // For for documentation purposes.
23 type PcrudResponse = any;
25 export class PcrudContext {
27 static verboseLogging = true; //
28 static identGenerator = 0; // for debug logging
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[];
38 private idl: IdlService;
39 private net: NetService;
40 private auth: AuthService;
42 // Tracks nested CUD actions
43 cudObserver: Observer<PcrudResponse>;
45 session: any; // OpenSRF.ClientSession
47 constructor( // passed in by parent service -- not injected
55 this.xactCloseMode = 'rollback';
56 this.ident = PcrudContext.identGenerator++;
57 this.session = new OpenSRF.ClientSession('open-ils.pcrud');
61 return '[PCRUDContext ' + this.ident + ']';
64 log(msg: string): void {
65 if (PcrudContext.verboseLogging) {
66 console.debug(this + ': ' + msg);
70 err(msg: string): void {
71 console.error(this + ': ' + msg);
74 token(reqOps?: PcrudReqOps): string {
75 return (reqOps && reqOps.anonymous) ?
76 'ANONYMOUS' : this.auth.token();
79 connect(): Promise<PcrudContext> {
81 return new Promise( (resolve, reject) => {
82 this.session.connect({
83 onconnect : () => { resolve(this); }
89 this.log('disconnect');
90 this.session.disconnect();
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 || {};
98 if (!pcrudOps.flesh) {
102 if (!pcrudOps.flesh_fields) {
103 pcrudOps.flesh_fields = {};
106 this.idl.classes[fmClass].fields
108 f.datatype === 'link' && (
109 f.reltype === 'has_a' || f.reltype === 'might_have'
113 const selector = this.idl.getLinkSelector(fmClass, field.name);
114 if (!selector) { return; }
117 // For mapped fields, we only want to auto-flesh them
118 // if both steps along the path are single-row fleshers.
120 const mapClass = field['class'];
121 const mapField = field.map;
122 const def = this.idl.classes[mapClass].field_map[mapField];
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.
132 if (!pcrudOps.flesh_fields[fmClass]) {
133 pcrudOps.flesh_fields[fmClass] = [];
136 if (pcrudOps.flesh_fields[fmClass].indexOf(field.name) < 0) {
137 pcrudOps.flesh_fields[fmClass].push(field.name);
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);
149 return this.dispatch(
150 `open-ils.pcrud.retrieve.${fmClass}`,
151 [this.token(reqOps), pkey, pcrudOps]);
154 retrieveAll(fmClass: string, pcrudOps?: any,
155 reqOps?: PcrudReqOps): Observable<PcrudResponse> {
157 search[this.idl.classes[fmClass].pkey] = {'!=' : null};
158 return this.search(fmClass, search, pcrudOps, reqOps);
161 search(fmClass: string, search: any,
162 pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
163 reqOps = reqOps || {};
164 this.authoritative = reqOps.authoritative || false;
166 const returnType = reqOps.idlist ? 'id_list' : 'search';
167 let method = `open-ils.pcrud.${returnType}.${fmClass}`;
169 if (reqOps.atomic) { method += '.atomic'; }
171 if (reqOps.fleshSelectors) {
172 this.applySelectorFleshing(fmClass, pcrudOps);
175 return this.dispatch(method, [this.token(reqOps), search, pcrudOps]);
178 create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
179 return this.cud('create', list);
181 update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
182 return this.cud('update', list);
184 remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
185 return this.cud('delete', list);
187 autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> { // RENAMED
188 return this.cud('auto', list);
191 xactClose(): Observable<PcrudResponse> {
192 return this.sendRequest(
193 'open-ils.pcrud.transaction.' + this.xactCloseMode,
198 xactBegin(): Observable<PcrudResponse> {
199 return this.sendRequest(
200 'open-ils.pcrud.transaction.begin', [this.token()]
204 private dispatch(method: string, params: any[]): Observable<PcrudResponse> {
205 if (this.authoritative) {
206 return this.wrapXact(() => {
207 return this.sendRequest(method, params);
210 return this.sendRequest(method, params);
218 // => xact_close(commit/rollback)
220 wrapXact(mainFunc: () => Observable<PcrudResponse>): Observable<PcrudResponse> {
221 return Observable.create(observer => {
226 // 2. start the transaction
227 .then(() => this.xactBegin().toPromise())
229 // 3. execute the main body
232 mainFunc().subscribe(
233 res => observer.next(res),
234 err => observer.error(err),
236 this.xactClose().toPromise().then(
244 err => observer.error(err)
252 private sendRequest(method: string,
253 params: any[]): Observable<PcrudResponse> {
255 // this.log(`sendRequest(${method})`);
257 return this.net.requestCompiled(
259 'open-ils.pcrud', method, params, this.session)
263 private cud(action: string,
264 list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
265 this.cudList = [].concat(list); // value or array
267 this.log(`CUD(): ${action}`);
270 this.cudAction = action;
271 this.xactCloseMode = 'commit';
273 return this.wrapXact(() => {
274 return Observable.create(observer => {
275 this.cudObserver = observer;
276 this.nextCudRequest();
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.
286 nextCudRequest(): void {
287 if (this.cudIdx >= this.cudList.length) {
288 this.cudObserver.complete();
292 let action = this.cudAction;
293 const fmObj = this.cudList[this.cudIdx++];
295 if (action === 'auto') {
296 if (fmObj.ischanged()) { action = 'update'; }
297 if (fmObj.isnew()) { action = 'create'; }
298 if (fmObj.isdeleted()) { action = 'delete'; }
300 if (action === 'auto') {
301 // object does not need updating; move along
302 this.nextCudRequest();
307 `open-ils.pcrud.${action}.${fmObj.classname}`,
308 [this.token(), fmObj]
310 res => this.cudObserver.next(res),
311 err => this.cudObserver.error(err),
312 () => this.nextCudRequest()
317 @Injectable({providedIn: 'root'})
318 export class PcrudService {
321 private idl: IdlService,
322 private net: NetService,
323 private auth: AuthService
326 // Pass-thru functions for one-off PCRUD calls
328 connect(): Promise<PcrudContext> {
329 return this.newContext().connect();
332 newContext(): PcrudContext {
333 return new PcrudContext(this.idl, this.net, this.auth);
336 retrieve(fmClass: string, pkey: Number | string,
337 pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
338 return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps);
341 retrieveAll(fmClass: string, pcrudOps?: any,
342 reqOps?: PcrudReqOps): Observable<PcrudResponse> {
343 return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps);
346 search(fmClass: string, search: any,
347 pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
348 return this.newContext().search(fmClass, search, pcrudOps, reqOps);
351 create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
352 return this.newContext().create(list);
355 update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
356 return this.newContext().update(list);
359 remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
360 return this.newContext().remove(list);
363 autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
364 return this.newContext().autoApply(list);