mv_createwebservice.sas
Go to the documentation of this file.
1 /**
2  @file
3  @brief Creates a JobExecution web service if it doesn't already exist
4  @details
5  Code is passed in as one or more filerefs.
6 
7  %* Step 1 - compile macros ;
8  filename mc url
9  "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
10  %inc mc;
11 
12  %* Step 2 - Create some code and add it to a web service;
13  filename ft15f001 temp;
14  parmcards4;
15  %webout(FETCH) %* fetch any tables sent from frontend;
16  %* do some sas, any inputs are now already WORK tables;
17  data example1 example2;
18  set sashelp.class;
19  run;
20  %* send data back;
21  %webout(OPEN)
22  %webout(ARR,example1) * Array format, fast, suitable for large tables;
23  %webout(OBJ,example2) * Object format, easier to work with ;
24  %webout(CLOSE)
25  ;;;;
26  %mv_createwebservice(path=/Public/app/common,name=appinit)
27 
28 
29  Notes:
30  To minimise postgres requests, output json is stored in a temporary file
31  and then sent to _webout in one go at the end.
32 
33  <h4> SAS Macros </h4>
34  @li mp_abort.sas
35  @li mv_createfolder.sas
36  @li mf_getuniquelibref.sas
37  @li mf_getuniquefileref.sas
38  @li mf_getplatform.sas
39  @li mf_isblank.sas
40  @li mv_deletejes.sas
41 
42  @param [in] path= The full path (on SAS Drive) where the service will be
43  created
44  @param [in] name= The name of the service
45  @param [in] desc= The description of the service
46  @param [in] precode= Space separated list of filerefs, pointing to the code
47  that needs to be attached to the beginning of the service
48  @param [in] code= Fileref(s) of the actual code to be added
49  @param [in] access_token_var= The global macro variable to contain the access
50  token
51  @param [in] grant_type= valid values are "password" or "authorization_code"
52  (unquoted). The default is authorization_code.
53  @param [in] replace=(YES) Select NO to avoid replacing any existing service in
54  that location
55  @param [in] adapter= the macro uses the sasjs adapter by default. To use
56  another adapter, add a (different) fileref here.
57  @param [in] contextname= Choose a specific context on which to run the Job.
58  Leave blank to use the default context. From Viya 3.5 it is possible to
59  configure a shared context - see
60 https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en
61  @param [in] mdebug=(0) set to 1 to enable DEBUG messages
62 
63  @version VIYA V.03.04
64  @author Allan Bowe, source: https://github.com/sasjs/core
65 
66 **/
67 
68 %macro mv_createwebservice(path=
69  ,name=
70  ,desc=Created by the mv_createwebservice.sas macro
71  ,precode=
72  ,code=ft15f001
73  ,access_token_var=ACCESS_TOKEN
74  ,grant_type=sas_services
75  ,replace=YES
76  ,adapter=sasjs
77  ,mdebug=0
78  ,contextname=
79  ,debug=0 /* @TODO - Deprecate */
80  );
81 %local dbg;
82 %if &mdebug=1 %then %do;
83  %put &sysmacroname entry vars:;
84  %put _local_;
85 %end;
86 %else %let dbg=*;
87 
88 %local oauth_bearer;
89 %if &grant_type=detect %then %do;
90  %if %symexist(&access_token_var) %then %let grant_type=authorization_code;
91  %else %let grant_type=sas_services;
92 %end;
93 %if &grant_type=sas_services %then %do;
94  %let oauth_bearer=oauth_bearer=sas_services;
95  %let &access_token_var=;
96 %end;
97 
98 /* initial validation checking */
99 %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
100  and &grant_type ne sas_services
101  )
102  ,mac=&sysmacroname
103  ,msg=%str(Invalid value for grant_type: &grant_type)
104 )
105 %mp_abort(iftrue=(%mf_isblank(&path)=1)
106  ,mac=&sysmacroname
107  ,msg=%str(path value must be provided)
108 )
109 %mp_abort(iftrue=(%length(&path)=1)
110  ,mac=&sysmacroname
111  ,msg=%str(path value must be provided)
112 )
113 %mp_abort(iftrue=(%mf_isblank(&name)=1)
114  ,mac=&sysmacroname
115  ,msg=%str(name value must be provided)
116 )
117 
118 options noquotelenmax;
119 
120 * remove any trailing slash ;
121 %if "%substr(&path,%length(&path),1)" = "/" %then
122  %let path=%substr(&path,1,%length(&path)-1);
123 
124 /* ensure folder exists */
125 %put &sysmacroname: Path &path being checked / created;
126 %mv_createfolder(path=&path)
127 
128 %local base_uri; /* location of rest apis */
129 %let base_uri=%mf_getplatform(VIYARESTAPI);
130 
131 /* fetching folder details for provided path */
132 %local fname1;
133 %let fname1=%mf_getuniquefileref();
134 proc http method='GET' out=&fname1 &oauth_bearer
135  url="&base_uri/folders/folders/@item?path=&path";
136 %if &grant_type=authorization_code %then %do;
137  headers "Authorization"="Bearer &&&access_token_var";
138 %end;
139 run;
140 %if &mdebug=1 %then %do;
141  data _null_;
142  infile &fname1;
143  input;
144  putlog _infile_;
145  run;
146 %end;
147 %mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
148  ,mac=&sysmacroname
149  ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
150 )
151 
152 /* path exists. Grab follow on link to check members */
153 %local libref1;
154 %let libref1=%mf_getuniquelibref();
155 libname &libref1 JSON fileref=&fname1;
156 
157 data _null_;
158  set &libref1..links;
159  if rel='members' then
160  call symputx('membercheck',quote("&base_uri"!!trim(href)),'l');
161  else if rel='self' then call symputx('parentFolderUri',href,'l');
162 run;
163 data _null_;
164  set &libref1..root;
165  call symputx('folderid',id,'l');
166 run;
167 %local fname2;
168 %let fname2=%mf_getuniquefileref();
169 proc http method='GET'
170  out=&fname2
171  &oauth_bearer
172  url=%unquote(%superq(membercheck));
173  headers
174  %if &grant_type=authorization_code %then %do;
175  "Authorization"="Bearer &&&access_token_var"
176  %end;
177  'Accept'='application/vnd.sas.collection+json'
178  'Accept-Language'='string';
179 %if &mdebug=1 %then %do;
180  debug level = 3;
181 %end;
182 run;
183 /*data _null_;infile &fname2;input;putlog _infile_;run;*/
184 %mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
185  ,mac=&sysmacroname
186  ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
187 )
188 
189 %if %upcase(&replace)=YES %then %do;
190  %mv_deletejes(path=&path, name=&name)
191 %end;
192 %else %do;
193  /* check that job does not already exist in that folder */
194  %local libref2;
195  %let libref2=%mf_getuniquelibref();
196  libname &libref2 JSON fileref=&fname2;
197  %local exists; %let exists=0;
198  data _null_;
199  set &libref2..items;
200  if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then
201  call symputx('exists',1,'l');
202  run;
203  %mp_abort(iftrue=(&exists=1)
204  ,mac=&sysmacroname
205  ,msg=%str(Job &name already exists in &path)
206  )
207  libname &libref2 clear;
208 %end;
209 
210 /* set up the body of the request to create the service */
211 %local fname3;
212 %let fname3=%mf_getuniquefileref();
213 data _null_;
214  file &fname3 TERMSTR=' ';
215  length string $32767;
216  string=cats('{"version": 0,"name":"'
217  ,"&name"
218  ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"'
219  ,',"type":"CHARACTER","defaultValue":"false"}');
220  context=quote(cats(symget('contextname')));
221  if context ne '""' then do;
222  string=cats(string,',{"version": 1,"name": "_contextName","defaultValue":'
223  ,context,',"type":"CHARACTER","label":"Context Name","required": false}');
224  end;
225  string=cats(string,'],"code":"');
226  put string;
227 run;
228 
229 /**
230  * Add webout macro
231  * These put statements are auto generated - to change the macro, change the
232  * source (mv_webout) and run `build.py`
233  */
234 filename &adapter temp lrecl=3000;
235 data _null_;
236  file &adapter;
237  put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */";
238 /* WEBOUT BEGIN */
239  put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
240  put ' ,engine=DATASTEP ';
241  put ' ,missing=NULL ';
242  put ' ,showmeta=N ';
243  put ' ,maxobs=MAX ';
244  put ')/*/STORE SOURCE*/; ';
245  put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval ';
246  put ' tmpds1 tmpds2 tmpds3 tmpds4; ';
247  put '%let numcols=0; ';
248  put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;); ';
249  put ' ';
250  put '%if &action=OPEN %then %do; ';
251  put ' options nobomfile; ';
252  put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
253  put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
254  put ' run; ';
255  put '%end; ';
256  put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
257  put ' /* force variable names to always be uppercase in the JSON */ ';
258  put ' options validvarname=upcase; ';
259  put ' /* To avoid issues with _webout on EBI - such as encoding diffs and truncation ';
260  put ' (https://support.sas.com/kb/49/325.html) we use temporary files */ ';
261  put ' filename _sjs1 temp lrecl=200 ; ';
262  put ' data _null_; file _sjs1 encoding=''utf-8''; ';
263  put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
264  put ' run; ';
265  put ' /* now write to _webout 1 char at a time */ ';
266  put ' data _null_; ';
267  put ' infile _sjs1 lrecl=1 recfm=n; ';
268  put ' file &jref mod lrecl=1 recfm=n; ';
269  put ' input sourcechar $char1. @@; ';
270  put ' format sourcechar hex2.; ';
271  put ' put sourcechar char1. @@; ';
272  put ' run; ';
273  put ' filename _sjs1 clear; ';
274  put ' ';
275  put ' /* grab col defs */ ';
276  put ' proc contents noprint data=&ds ';
277  put ' out=_data_(keep=name type length format formatl formatd varnum label); ';
278  put ' run; ';
279  put ' %let colinfo=%scan(&syslast,2,.); ';
280  put ' proc sort data=&colinfo; ';
281  put ' by varnum; ';
282  put ' run; ';
283  put ' /* move meta to mac vars */ ';
284  put ' data &colinfo; ';
285  put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
286  put ' set &colinfo end=last nobs=nobs; ';
287  put ' name=upcase(name); ';
288  put ' /* fix formats */ ';
289  put ' if type=2 or type=6 then do; ';
290  put ' typelong=''char''; ';
291  put ' length fmt $49.; ';
292  put ' if format='''' then fmt=cats(''$'',length,''.''); ';
293  put ' else if formatl=0 then fmt=cats(format,''.''); ';
294  put ' else fmt=cats(format,formatl,''.''); ';
295  put ' end; ';
296  put ' else do; ';
297  put ' typelong=''num''; ';
298  put ' if format='''' then fmt=''best.''; ';
299  put ' else if formatl=0 then fmt=cats(format,''.''); ';
300  put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
301  put ' else fmt=cats(format,formatl,''.'',formatd); ';
302  put ' end; ';
303  put ' /* 32 char unique name */ ';
304  put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
305  put ' ';
306  put ' call symputx(cats(''name'',_n_),name,''l''); ';
307  put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
308  put ' call symputx(cats(''length'',_n_),length,''l''); ';
309  put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
310  put ' call symputx(cats(''type'',_n_),type,''l''); ';
311  put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
312  put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
313  put ' /* overwritten when fmt=Y and a custom format exists in catalog */ ';
314  put ' if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l''); ';
315  put ' else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l''); ';
316  put ' run; ';
317  put ' ';
318  put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
319  put ' proc sql; ';
320  put ' select count(*) into: lastobs from &ds; ';
321  put ' %if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs)); ';
322  put ' ';
323  put ' %if &engine=PROCJSON %then %do; ';
324  put ' %if &missing=STRING %then %do; ';
325  put ' %put &sysmacroname: Special Missings not supported in proc json.; ';
326  put ' %put &sysmacroname: Switching to DATASTEP engine; ';
327  put ' %goto datastep; ';
328  put ' %end; ';
329  put ' data &tempds; ';
330  put ' set &ds; ';
331  put ' &stmt_obs; ';
332  put ' %if &fmt=N %then format _numeric_ best32.;; ';
333  put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
334  put ' filename _sjs2 temp lrecl=131068 encoding=''utf-8''; ';
335  put ' proc json out=_sjs2 pretty ';
336  put ' %if &action=ARR %then nokeys ; ';
337  put ' ;export &tempds / nosastags fmtnumeric; ';
338  put ' run; ';
339  put ' /* send back to webout */ ';
340  put ' data _null_; ';
341  put ' infile _sjs2 lrecl=1 recfm=n; ';
342  put ' file &jref mod lrecl=1 recfm=n; ';
343  put ' input sourcechar $char1. @@; ';
344  put ' format sourcechar hex2.; ';
345  put ' put sourcechar char1. @@; ';
346  put ' run; ';
347  put ' filename _sjs2 clear; ';
348  put ' %end; ';
349  put ' %else %if &engine=DATASTEP %then %do; ';
350  put ' %datastep: ';
351  put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 ';
352  put ' %then %do; ';
353  put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
354  put ' %return; ';
355  put ' %end; ';
356  put ' ';
357  put ' %if &fmt=Y %then %do; ';
358  put ' /** ';
359  put ' * Extract format definitions ';
360  put ' * First, by getting library locations from dictionary.formats ';
361  put ' * Then, by exporting the width using proc format ';
362  put ' * Cannot use maxw from sashelp.vformat as not always populated ';
363  put ' * Cannot use fmtinfo() as not supported in all flavours ';
364  put ' */ ';
365  put ' %let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
366  put ' %let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
367  put ' %let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
368  put ' %let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
369  put ' proc sql noprint; ';
370  put ' create table &tmpds1 as ';
371  put ' select cats(libname,''.'',memname) as FMTCAT, ';
372  put ' FMTNAME ';
373  put ' from dictionary.formats ';
374  put ' where fmttype=''F'' and libname is not null ';
375  put ' and fmtname in (select format from &colinfo where format is not null) ';
376  put ' order by 1; ';
377  put ' create table &tmpds2( ';
378  put ' FMTNAME char(32), ';
379  put ' LENGTH num ';
380  put ' ); ';
381  put ' %local catlist cat fmtlist i; ';
382  put ' select distinct fmtcat into: catlist separated by '' '' from &tmpds1; ';
383  put ' %do i=1 %to %sysfunc(countw(&catlist,%str( ))); ';
384  put ' %let cat=%scan(&catlist,&i,%str( )); ';
385  put ' proc sql; ';
386  put ' select distinct fmtname into: fmtlist separated by '' '' ';
387  put ' from &tmpds1 where fmtcat="&cat"; ';
388  put ' proc format lib=&cat cntlout=&tmpds3(keep=fmtname length); ';
389  put ' select &fmtlist; ';
390  put ' run; ';
391  put ' proc sql; ';
392  put ' insert into &tmpds2 select distinct fmtname,length from &tmpds3; ';
393  put ' %end; ';
394  put ' ';
395  put ' proc sql; ';
396  put ' create table &tmpds4 as ';
397  put ' select a.*, b.length as MAXW ';
398  put ' from &colinfo a ';
399  put ' left join &tmpds2 b ';
400  put ' on cats(a.format)=cats(upcase(b.fmtname)) ';
401  put ' order by a.varnum; ';
402  put ' data _null_; ';
403  put ' set &tmpds4; ';
404  put ' if not missing(maxw); ';
405  put ' call symputx( ';
406  put ' cats(''fmtlen'',_n_), ';
407  put ' /* vars need extra padding due to JSON escaping of special chars */ ';
408  put ' min(32767,ceil((max(length,maxw)+10)*1.5)) ';
409  put ' ,''l'' ';
410  put ' ); ';
411  put ' run; ';
412  put ' ';
413  put ' /* configure varlenchk - as we are explicitly shortening the variables */ ';
414  put ' %let optval=%sysfunc(getoption(varlenchk)); ';
415  put ' options varlenchk=NOWARN; ';
416  put ' data _data_(compress=char); ';
417  put ' /* shorten the new vars */ ';
418  put ' length ';
419  put ' %do i=1 %to &numcols; ';
420  put ' &&name&i $&&fmtlen&i ';
421  put ' %end; ';
422  put ' ; ';
423  put ' /* rename on entry */ ';
424  put ' set &ds(rename=( ';
425  put ' %do i=1 %to &numcols; ';
426  put ' &&name&i=&&newname&i ';
427  put ' %end; ';
428  put ' )); ';
429  put ' &stmt_obs; ';
430  put ' ';
431  put ' drop ';
432  put ' %do i=1 %to &numcols; ';
433  put ' &&newname&i ';
434  put ' %end; ';
435  put ' ; ';
436  put ' %do i=1 %to &numcols; ';
437  put ' %if &&typelong&i=num %then %do; ';
438  put ' &&name&i=cats(put(&&newname&i,&&fmt&i)); ';
439  put ' %end; ';
440  put ' %else %do; ';
441  put ' &&name&i=put(&&newname&i,&&fmt&i); ';
442  put ' %end; ';
443  put ' %end; ';
444  put ' if _error_ then do; ';
445  put ' call symputx(''syscc'',1012); ';
446  put ' stop; ';
447  put ' end; ';
448  put ' run; ';
449  put ' %let fmtds=&syslast; ';
450  put ' options varlenchk=&optval; ';
451  put ' %end; ';
452  put ' ';
453  put ' proc format; /* credit yabwon for special null removal */ ';
454  put ' value bart (default=40) ';
455  put ' %if &missing=NULL %then %do; ';
456  put ' ._ - .z = null ';
457  put ' %end; ';
458  put ' %else %do; ';
459  put ' ._ = [quote()] ';
460  put ' . = null ';
461  put ' .a - .z = [quote()] ';
462  put ' %end; ';
463  put ' other = [best.]; ';
464  put ' ';
465  put ' data &tempds; ';
466  put ' attrib _all_ label=''''; ';
467  put ' %do i=1 %to &numcols; ';
468  put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
469  put ' length &&name&i $&&fmtlen&i...; ';
470  put ' format &&name&i $&&fmtlen&i...; ';
471  put ' %end; ';
472  put ' %end; ';
473  put ' %if &fmt=Y %then %do; ';
474  put ' set &fmtds; ';
475  put ' %end; ';
476  put ' %else %do; ';
477  put ' set &ds; ';
478  put ' %end; ';
479  put ' &stmt_obs; ';
480  put ' format _numeric_ bart.; ';
481  put ' %do i=1 %to &numcols; ';
482  put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
483  put ' if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
484  put ' &&name&i=''"''!!trim( ';
485  put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
486  put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
487  put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
488  put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
489  put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
490  put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
491  put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
492  put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
493  put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
494  put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
495  put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
496  put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
497  put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
498  put ' )))))))))))))!!''"''; ';
499  put ' end; ';
500  put ' else &&name&i=quote(cats(&&name&i)); ';
501  put ' %end; ';
502  put ' %end; ';
503  put ' run; ';
504  put ' ';
505  put ' filename _sjs3 temp lrecl=131068 ; ';
506  put ' data _null_; ';
507  put ' file _sjs3 encoding=''utf-8''; ';
508  put ' if _n_=1 then put "["; ';
509  put ' set &tempds; ';
510  put ' if _n_>1 then put "," @; put ';
511  put ' %if &action=ARR %then "[" ; %else "{" ; ';
512  put ' %do i=1 %to &numcols; ';
513  put ' %if &i>1 %then "," ; ';
514  put ' %if &action=OBJ %then """&&name&i"":" ; ';
515  put ' "&&name&i"n /* name literal for reserved variable names */ ';
516  put ' %end; ';
517  put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
518  put ' ';
519  put ' /* close out the table */ ';
520  put ' data _null_; ';
521  put ' file _sjs3 mod encoding=''utf-8''; ';
522  put ' put '']''; ';
523  put ' run; ';
524  put ' data _null_; ';
525  put ' infile _sjs3 lrecl=1 recfm=n; ';
526  put ' file &jref mod lrecl=1 recfm=n; ';
527  put ' input sourcechar $char1. @@; ';
528  put ' format sourcechar hex2.; ';
529  put ' put sourcechar char1. @@; ';
530  put ' run; ';
531  put ' filename _sjs3 clear; ';
532  put ' %end; ';
533  put ' ';
534  put ' proc sql; ';
535  put ' drop table &colinfo, &tempds; ';
536  put ' ';
537  put ' %if %substr(&showmeta,1,1)=Y %then %do; ';
538  put ' filename _sjs4 temp lrecl=131068 encoding=''utf-8''; ';
539  put ' data _null_; ';
540  put ' file _sjs4; ';
541  put ' length label $350; ';
542  put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
543  put ' do i=1 to &numcols; ';
544  put ' name=quote(trim(symget(cats(''name'',i)))); ';
545  put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
546  put ' label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i))))); ';
547  put ' length=quote(trim(symget(cats(''length'',i)))); ';
548  put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
549  put ' if i>1 then put "," @@; ';
550  put ' put name '':{"format":'' format '',"label":'' label ';
551  put ' '',"length":'' length '',"type":'' type ''}''; ';
552  put ' end; ';
553  put ' put ''}}''; ';
554  put ' run; ';
555  put ' /* send back to webout */ ';
556  put ' data _null_; ';
557  put ' infile _sjs4 lrecl=1 recfm=n; ';
558  put ' file &jref mod lrecl=1 recfm=n; ';
559  put ' input sourcechar $char1. @@; ';
560  put ' format sourcechar hex2.; ';
561  put ' put sourcechar char1. @@; ';
562  put ' run; ';
563  put ' filename _sjs4 clear; ';
564  put ' %end; ';
565  put '%end; ';
566  put ' ';
567  put '%else %if &action=CLOSE %then %do; ';
568  put ' data _null_; file &jref encoding=''utf-8'' mod ; ';
569  put ' put "}"; ';
570  put ' run; ';
571  put '%end; ';
572  put '%mend mp_jsonout; ';
573  put ' ';
574  put '%macro mf_getuser( ';
575  put ')/*/STORE SOURCE*/; ';
576  put ' %local user; ';
577  put ' ';
578  put ' %if %symexist(_sasjs_username) %then %let user=&_sasjs_username; ';
579  put ' %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do; ';
580  put ' %let user=&SYS_COMPUTE_SESSION_OWNER; ';
581  put ' %end; ';
582  put ' %else %if %symexist(_metaperson) %then %do; ';
583  put ' %if %length(&_metaperson)=0 %then %let user=&sysuserid; ';
584  put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
585  put ' /* but be sure to quote in case of usernames with commas */ ';
586  put ' %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); ';
587  put ' %end; ';
588  put ' %else %let user=&sysuserid; ';
589  put ' ';
590  put ' %quote(&user) ';
591  put ' ';
592  put '%mend mf_getuser; ';
593  put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=N,stream=Y,missing=NULL ';
594  put ' ,showmeta=N,maxobs=MAX,workobs=0 ';
595  put '); ';
596  put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name ';
597  put ' sasjs_tables SYS_JES_JOB_URI; ';
598  put '%if %index("&_debug",log) %then %let _debug=131; ';
599  put ' ';
600  put '%local i tempds table; ';
601  put '%let action=%upcase(&action); ';
602  put ' ';
603  put '%if &action=FETCH %then %do; ';
604  put ' %if %upcase(&_omittextlog)=FALSE or %str(&_debug) ge 131 %then %do; ';
605  put ' options mprint notes mprintnest; ';
606  put ' %end; ';
607  put ' ';
608  put ' %if not %symexist(_webin_fileuri1) %then %do; ';
609  put ' %let _webin_file_count=%eval(&_webin_file_count+0); ';
610  put ' %let _webin_fileuri1=&_webin_fileuri; ';
611  put ' %let _webin_name1=&_webin_name; ';
612  put ' %end; ';
613  put ' ';
614  put ' /* if the sasjs_tables param is passed, we expect param based upload */ ';
615  put ' %if %length(&sasjs_tables.X)>1 %then %do; ';
616  put ' ';
617  put ' /* convert data from macro variables to datasets */ ';
618  put ' %do i=1 %to %sysfunc(countw(&sasjs_tables)); ';
619  put ' %let table=%scan(&sasjs_tables,&i,%str( )); ';
620  put ' %if %symexist(sasjs&i.data0)=0 %then %let sasjs&i.data0=1; ';
621  put ' data _null_; ';
622  put ' file "%sysfunc(pathname(work))/&table..csv" recfm=n; ';
623  put ' retain nrflg 0; ';
624  put ' length line $32767; ';
625  put ' do i=1 to &&sasjs&i.data0; ';
626  put ' if &&sasjs&i.data0=1 then line=symget("sasjs&i.data"); ';
627  put ' else line=symget(cats("sasjs&i.data",i)); ';
628  put ' if i=1 and substr(line,1,7)=''%nrstr('' then do; ';
629  put ' nrflg=1; ';
630  put ' line=substr(line,8); ';
631  put ' end; ';
632  put ' if i=&&sasjs&i.data0 and nrflg=1 then do; ';
633  put ' line=substr(line,1,length(line)-1); ';
634  put ' end; ';
635  put ' put line +(-1) @; ';
636  put ' end; ';
637  put ' run; ';
638  put ' data _null_; ';
639  put ' infile "%sysfunc(pathname(work))/&table..csv" termstr=crlf ; ';
640  put ' input; ';
641  put ' if _n_=1 then call symputx(''input_statement'',_infile_); ';
642  put ' list; ';
643  put ' data work.&table; ';
644  put ' infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd ';
645  put ' termstr=crlf; ';
646  put ' input &input_statement; ';
647  put ' run; ';
648  put ' %end; ';
649  put ' %end; ';
650  put ' %else %do i=1 %to &_webin_file_count; ';
651  put ' /* read in any files that are sent */ ';
652  put ' /* this part needs refactoring for wide files */ ';
653  put ' filename indata filesrvc "&&_webin_fileuri&i" lrecl=999999; ';
654  put ' data _null_; ';
655  put ' infile indata termstr=crlf lrecl=32767; ';
656  put ' input; ';
657  put ' if _n_=1 then call symputx(''input_statement'',_infile_); ';
658  put ' %if %str(&_debug) ge 131 %then %do; ';
659  put ' if _n_<20 then putlog _infile_; ';
660  put ' else stop; ';
661  put ' %end; ';
662  put ' %else %do; ';
663  put ' stop; ';
664  put ' %end; ';
665  put ' run; ';
666  put ' data &&_webin_name&i; ';
667  put ' infile indata firstobs=2 dsd termstr=crlf ; ';
668  put ' input &input_statement; ';
669  put ' run; ';
670  put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; ';
671  put ' %end; ';
672  put '%end; ';
673  put '%else %if &action=OPEN %then %do; ';
674  put ' /* setup webout */ ';
675  put ' OPTIONS NOBOMFILE; ';
676  put ' %if "X&SYS_JES_JOB_URI.X"="XX" %then %do; ';
677  put ' filename _webout temp lrecl=999999 mod; ';
678  put ' %end; ';
679  put ' %else %do; ';
680  put ' filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" ';
681  put ' name="_webout.json" lrecl=999999 mod; ';
682  put ' %end; ';
683  put ' ';
684  put ' /* setup temp ref */ ';
685  put ' %if %upcase(&fref) ne _WEBOUT %then %do; ';
686  put ' filename &fref temp lrecl=999999 permission=''A::u::rwx,A::g::rw-,A::o::---''; ';
687  put ' %end; ';
688  put ' ';
689  put ' /* setup json */ ';
690  put ' data _null_;file &fref; ';
691  put ' put ''{"SYSDATE" : "'' "&SYSDATE" ''"''; ';
692  put ' put '',"SYSTIME" : "'' "&SYSTIME" ''"''; ';
693  put ' run; ';
694  put '%end; ';
695  put '%else %if &action=ARR or &action=OBJ %then %do; ';
696  put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
697  put ' ,engine=DATASTEP,missing=&missing,showmeta=&showmeta,maxobs=&maxobs ';
698  put ' ) ';
699  put '%end; ';
700  put '%else %if &action=CLOSE %then %do; ';
701  put ' %if %str(&workobs) > 0 %then %do; ';
702  put ' /* send back first XX records of each work table for debugging */ ';
703  put ' data;run;%let tempds=%scan(&syslast,2,.); ';
704  put ' ods output Members=&tempds; ';
705  put ' proc datasets library=WORK memtype=data; ';
706  put ' %local wtcnt;%let wtcnt=0; ';
707  put ' data _null_; ';
708  put ' set &tempds; ';
709  put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
710  put ' i+1; ';
711  put ' call symputx(cats(''wt'',i),name,''l''); ';
712  put ' call symputx(''wtcnt'',i,''l''); ';
713  put ' data _null_; file &fref mod; put ",""WORK"":{"; ';
714  put ' %do i=1 %to &wtcnt; ';
715  put ' %let wt=&&wt&i; ';
716  put ' data _null_; file &fref mod; ';
717  put ' dsid=open("WORK.&wt",''is''); ';
718  put ' nlobs=attrn(dsid,''NLOBS''); ';
719  put ' nvars=attrn(dsid,''NVARS''); ';
720  put ' rc=close(dsid); ';
721  put ' if &i>1 then put '',''@; ';
722  put ' put " ""&wt"" : {"; ';
723  put ' put ''"nlobs":'' nlobs; ';
724  put ' put '',"nvars":'' nvars; ';
725  put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=Y ';
726  put ' ,maxobs=&workobs ';
727  put ' ) ';
728  put ' data _null_; file &fref mod;put "}"; ';
729  put ' %end; ';
730  put ' data _null_; file &fref mod;put "}";run; ';
731  put ' %end; ';
732  put ' ';
733  put ' /* close off json */ ';
734  put ' data _null_;file &fref mod; ';
735  put ' length SYSPROCESSNAME syserrortext syswarningtext autoexec $512; ';
736  put ' put ",""_DEBUG"" : ""&_debug"" "; ';
737  put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
738  put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
739  put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
740  put ' put '',"AUTOEXEC" : '' autoexec; ';
741  put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
742  put ' SYS_JES_JOB_URI=quote(trim(resolve(symget(''SYS_JES_JOB_URI'')))); ';
743  put ' put '',"SYS_JES_JOB_URI" : '' SYS_JES_JOB_URI ; ';
744  put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
745  put ' put ",""SYSCC"" : ""&syscc"" "; ';
746  put ' syserrortext=cats(symget(''syserrortext'')); ';
747  put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
748  put ' syserrortext=''"''!!trim( ';
749  put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
750  put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
751  put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
752  put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
753  put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
754  put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
755  put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
756  put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
757  put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
758  put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
759  put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
760  put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
761  put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) ';
762  put ' )))))))))))))!!''"''; ';
763  put ' end; ';
764  put ' else syserrortext=cats(''"'',syserrortext,''"''); ';
765  put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
766  put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
767  put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
768  put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; ';
769  put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); ';
770  put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; ';
771  put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
772  put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
773  put ' put ",""SYSSITE"" : ""&syssite"" "; ';
774  put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
775  put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
776  put ' put '',"SYSVLONG" : '' sysvlong; ';
777  put ' syswarningtext=cats(symget(''syswarningtext'')); ';
778  put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
779  put ' syswarningtext=''"''!!trim( ';
780  put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
781  put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
782  put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
783  put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
784  put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
785  put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
786  put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
787  put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
788  put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
789  put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
790  put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
791  put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
792  put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) ';
793  put ' )))))))))))))!!''"''; ';
794  put ' end; ';
795  put ' else syswarningtext=cats(''"'',syswarningtext,''"''); ';
796  put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
797  put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
798  put ' length memsize $32; ';
799  put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
800  put ' memsize=quote(cats(memsize)); ';
801  put ' put '',"MEMSIZE" : '' memsize; ';
802  put ' put "}"; ';
803  put ' ';
804  put ' %if %upcase(&fref) ne _WEBOUT and &stream=Y %then %do; ';
805  put ' data _null_; rc=fcopy("&fref","_webout");run; ';
806  put ' %end; ';
807  put ' ';
808  put '%end; ';
809  put ' ';
810  put '%mend mv_webout; ';
811 /* WEBOUT END */
812  put '/* if calling viya service with _job param, _program will conflict */';
813  put '/* so it is provided by SASjs instead as __program */';
814  put '%global __program _program;';
815  put '%let _program=%sysfunc(coalescec(&__program,&_program));';
816  put ' ';
817  put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO';
818  put ' ,maxobs=MAX';
819  put ');';
820  put ' %mv_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
821  put ' ,showmeta=&showmeta,maxobs=&maxobs';
822  put ' )';
823  put '%mend;';
824 run;
825 
826 /* insert the code, escaping double quotes and carriage returns */
827 %&dbg.put &sysmacroname: Creating final input file;
828 %local x fref freflist;
829 %let freflist= &adapter &precode &code ;
830 %do x=1 %to %sysfunc(countw(&freflist));
831  %let fref=%scan(&freflist,&x);
832  %&dbg.put &sysmacroname: adding &fref fileref;
833  data _null_;
834  length filein 8 fileid 8;
835  filein = fopen("&fref","I",1,"B");
836  fileid = fopen("&fname3","A",1,"B");
837  rec = "20"x;
838  do while(fread(filein)=0);
839  rc = fget(filein,rec,1);
840  if rec='"' then do; /* DOUBLE QUOTE */
841  rc =fput(fileid,'\');rc =fwrite(fileid);
842  rc =fput(fileid,'"');rc =fwrite(fileid);
843  end;
844  else if rec='0A'x then do; /* LF */
845  rc =fput(fileid,'\');rc =fwrite(fileid);
846  rc =fput(fileid,'n');rc =fwrite(fileid);
847  end;
848  else if rec='0D'x then do; /* CR */
849  rc =fput(fileid,'\');rc =fwrite(fileid);
850  rc =fput(fileid,'r');rc =fwrite(fileid);
851  end;
852  else if rec='09'x then do; /* TAB */
853  rc =fput(fileid,'\');rc =fwrite(fileid);
854  rc =fput(fileid,'t');rc =fwrite(fileid);
855  end;
856  else if rec='5C'x then do; /* BACKSLASH */
857  rc =fput(fileid,'\');rc =fwrite(fileid);
858  rc =fput(fileid,'\');rc =fwrite(fileid);
859  end;
860  else if rec='01'x then do; /* Unprintable */
861  rc =fput(fileid,'\');rc =fwrite(fileid);
862  rc =fput(fileid,'u');rc =fwrite(fileid);
863  rc =fput(fileid,'0');rc =fwrite(fileid);
864  rc =fput(fileid,'0');rc =fwrite(fileid);
865  rc =fput(fileid,'0');rc =fwrite(fileid);
866  rc =fput(fileid,'1');rc =fwrite(fileid);
867  end;
868  else if rec='07'x then do; /* Bell Char */
869  rc =fput(fileid,'\');rc =fwrite(fileid);
870  rc =fput(fileid,'u');rc =fwrite(fileid);
871  rc =fput(fileid,'0');rc =fwrite(fileid);
872  rc =fput(fileid,'0');rc =fwrite(fileid);
873  rc =fput(fileid,'0');rc =fwrite(fileid);
874  rc =fput(fileid,'7');rc =fwrite(fileid);
875  end;
876  else if rec='1B'x then do; /* escape char */
877  rc =fput(fileid,'\');rc =fwrite(fileid);
878  rc =fput(fileid,'u');rc =fwrite(fileid);
879  rc =fput(fileid,'0');rc =fwrite(fileid);
880  rc =fput(fileid,'0');rc =fwrite(fileid);
881  rc =fput(fileid,'1');rc =fwrite(fileid);
882  rc =fput(fileid,'B');rc =fwrite(fileid);
883  end;
884  else do;
885  rc =fput(fileid,rec);
886  rc =fwrite(fileid);
887  end;
888  end;
889  rc=fclose(filein);
890  rc=fclose(fileid);
891  run;
892 %end;
893 
894 /* finish off the body of the code file loaded to JES */
895 data _null_;
896  file &fname3 mod TERMSTR=' ';
897  put '"}';
898 run;
899 
900 %if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do;
901  %put &sysmacroname: input about to be POSTed;
902  data _null_;infile &fname3;input;putlog _infile_;run;
903 %end;
904 
905 %&dbg.put &sysmacroname: Creating the actual service!;
906 %local fname4;
907 %let fname4=%mf_getuniquefileref();
908 proc http method='POST'
909  in=&fname3
910  out=&fname4
911  &oauth_bearer
912  url="&base_uri/jobDefinitions/definitions?parentFolderUri=&parentFolderUri";
913  headers 'Content-Type'='application/vnd.sas.job.definition+json'
914  %if &grant_type=authorization_code %then %do;
915  "Authorization"="Bearer &&&access_token_var"
916  %end;
917  "Accept"="application/vnd.sas.job.definition+json";
918 %if &mdebug=1 %then %do;
919  debug level = 3;
920 %end;
921 run;
922 %if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do;
923  %put &sysmacroname: output from POSTing job definition;
924  data _null_;infile &fname4;input;putlog _infile_;run;
925 %end;
926 %mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 201)
927  ,mac=&sysmacroname
928  ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
929 )
930 
931 /* get the url so we can give a helpful log message */
932 %local url;
933 data _null_;
934  if symexist('_baseurl') then do;
935  url=symget('_baseurl');
936  if subpad(url,length(url)-9,9)='SASStudio'
937  then url=substr(url,1,length(url)-11);
938  else url="&systcpiphostname";
939  end;
940  else url="&systcpiphostname";
941  call symputx('url',url);
942 run;
943 
944 %if &mdebug=1 %then %do;
945  %put &sysmacroname exit vars:;
946  %put _local_;
947 %end;
948 %else %do;
949  /* clear refs */
950  filename &fname1 clear;
951  filename &fname2 clear;
952  filename &fname3 clear;
953  filename &fname4 clear;
954  filename &adapter clear;
955  libname &libref1 clear;
956 %end;
957 
958 %put &sysmacroname: Job &name successfully created in &path;
959 %put &sysmacroname:;
960 %put &sysmacroname: Check it out here:;
961 %put &sysmacroname:;%put;
962 %put &url/SASJobExecution?_PROGRAM=&path/&name;%put;
963 %put &sysmacroname:;
964 %put &sysmacroname:;
965 
966 %mend mv_createwebservice;