Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
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. Leave
58 blank to use the default context. From Viya 3.5 it is possible to configure
59 a shared context - see
60https://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
118options 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();
134proc 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;
139run;
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();
155libname &libref1 JSON fileref=&fname1;
156
157data _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');
162run;
163data _null_;
164 set &libref1..root;
165 call symputx('folderid',id,'l');
166run;
167%local fname2;
168%let fname2=%mf_getuniquefileref();
169proc 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;
182run;
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();
213data _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;
227run;
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 */
234filename &adapter temp lrecl=3000;
235data _null_;
236 file &adapter;
237 put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */";
238/* WEBOUT BEGIN */
239 put ' ';
240 put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 ';
241 put ')/*/STORE SOURCE*/; ';
242 put '%put output location=&jref; ';
243 put '%if &action=OPEN %then %do; ';
244 put ' options nobomfile; ';
245 put ' data _null_;file &jref encoding=''utf-8''; ';
246 put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
247 put ' run; ';
248 put '%end; ';
249 put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
250 put ' options validvarname=upcase; ';
251 put ' data _null_;file &jref mod encoding=''utf-8''; ';
252 put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
253 put ' ';
254 put ' %if &engine=PROCJSON %then %do; ';
255 put ' data;run;%let tempds=&syslast; ';
256 put ' proc sql;drop table &tempds; ';
257 put ' data &tempds /view=&tempds;set &ds; ';
258 put ' %if &fmt=N %then format _numeric_ best32.;; ';
259 put ' proc json out=&jref pretty ';
260 put ' %if &action=ARR %then nokeys ; ';
261 put ' ;export &tempds / nosastags fmtnumeric; ';
262 put ' run; ';
263 put ' proc sql;drop view &tempds; ';
264 put ' %end; ';
265 put ' %else %if &engine=DATASTEP %then %do; ';
266 put ' %local cols i tempds; ';
267 put ' %let cols=0; ';
268 put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; ';
269 put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
270 put ' %return; ';
271 put ' %end; ';
272 put ' %if &fmt=Y %then %do; ';
273 put ' %put converting every variable to a formatted variable; ';
274 put ' /* see mp_ds2fmtds.sas for source */ ';
275 put ' proc contents noprint data=&ds ';
276 put ' out=_data_(keep=name type length format formatl formatd varnum); ';
277 put ' run; ';
278 put ' proc sort; ';
279 put ' by varnum; ';
280 put ' run; ';
281 put ' %local fmtds; ';
282 put ' %let fmtds=%scan(&syslast,2,.); ';
283 put ' /* prepare formats and varnames */ ';
284 put ' data _null_; ';
285 put ' if _n_=1 then call symputx(''nobs'',nobs,''l''); ';
286 put ' set &fmtds 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 ' length fmt $49.; ';
291 put ' if format='''' then fmt=cats(''$'',length,''.''); ';
292 put ' else if formatl=0 then fmt=cats(format,''.''); ';
293 put ' else fmt=cats(format,formatl,''.''); ';
294 put ' newlen=max(formatl,length); ';
295 put ' end; ';
296 put ' else do; ';
297 put ' if format='''' then fmt=''best.''; ';
298 put ' else if formatl=0 then fmt=cats(format,''.''); ';
299 put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
300 put ' else fmt=cats(format,formatl,''.'',formatd); ';
301 put ' /* needs to be wide, for datetimes etc */ ';
302 put ' newlen=max(length,formatl,24); ';
303 put ' end; ';
304 put ' /* 32 char unique name */ ';
305 put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
306 put ' ';
307 put ' call symputx(cats(''name'',_n_),name,''l''); ';
308 put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
309 put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
310 put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
311 put ' call symputx(cats(''type'',_n_),type,''l''); ';
312 put ' run; ';
313 put ' data &fmtds; ';
314 put ' /* rename on entry */ ';
315 put ' set &ds(rename=( ';
316 put ' %local i; ';
317 put ' %do i=1 %to &nobs; ';
318 put ' &&name&i=&&newname&i ';
319 put ' %end; ';
320 put ' )); ';
321 put ' %do i=1 %to &nobs; ';
322 put ' length &&name&i $&&len&i; ';
323 put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
324 put ' drop &&newname&i; ';
325 put ' %end; ';
326 put ' if _error_ then call symputx(''syscc'',1012); ';
327 put ' run; ';
328 put ' %let ds=&fmtds; ';
329 put ' %end; /* &fmt=Y */ ';
330 put ' data _null_;file &jref mod encoding=''utf-8''; ';
331 put ' put "["; call symputx(''cols'',0,''l''); ';
332 put ' proc sort ';
333 put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
334 put ' out=_data_; ';
335 put ' by varnum; ';
336 put ' ';
337 put ' data _null_; ';
338 put ' set _last_ end=last; ';
339 put ' call symputx(cats(''name'',_n_),name,''l''); ';
340 put ' call symputx(cats(''type'',_n_),type,''l''); ';
341 put ' call symputx(cats(''len'',_n_),length,''l''); ';
342 put ' if last then call symputx(''cols'',_n_,''l''); ';
343 put ' run; ';
344 put ' ';
345 put ' proc format; /* credit yabwon for special null removal */ ';
346 put ' value bart ._ - .z = null ';
347 put ' other = [best.]; ';
348 put ' ';
349 put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ ';
350 put ' proc sql; drop table &tempds; ';
351 put ' data &tempds/view=&tempds; ';
352 put ' attrib _all_ label=''''; ';
353 put ' %do i=1 %to &cols; ';
354 put ' %if &&type&i=char %then %do; ';
355 put ' length &&name&i $32767; ';
356 put ' format &&name&i $32767.; ';
357 put ' %end; ';
358 put ' %end; ';
359 put ' set &ds; ';
360 put ' format _numeric_ bart.; ';
361 put ' %do i=1 %to &cols; ';
362 put ' %if &&type&i=char %then %do; ';
363 put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, ';
364 put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, ';
365 put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, ';
366 put ' prxchange(''s/''!!''09''x!!''/\t/'',-1, ';
367 put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
368 put ' )))))!!''"''; ';
369 put ' %end; ';
370 put ' %end; ';
371 put ' run; ';
372 put ' /* write to temp loc to avoid _webout truncation ';
373 put ' - https://support.sas.com/kb/49/325.html */ ';
374 put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
375 put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
376 put ' set &tempds; ';
377 put ' if _n_>1 then put "," @; put ';
378 put ' %if &action=ARR %then "[" ; %else "{" ; ';
379 put ' %do i=1 %to &cols; ';
380 put ' %if &i>1 %then "," ; ';
381 put ' %if &action=OBJ %then """&&name&i"":" ; ';
382 put ' &&name&i ';
383 put ' %end; ';
384 put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
385 put ' proc sql; ';
386 put ' drop view &tempds; ';
387 put ' /* now write the long strings to _webout 1 byte at a time */ ';
388 put ' data _null_; ';
389 put ' length filein 8 fileid 8; ';
390 put ' filein = fopen("_sjs",''I'',1,''B''); ';
391 put ' fileid = fopen("&jref",''A'',1,''B''); ';
392 put ' rec = ''20''x; ';
393 put ' do while(fread(filein)=0); ';
394 put ' rc = fget(filein,rec,1); ';
395 put ' rc = fput(fileid, rec); ';
396 put ' rc =fwrite(fileid); ';
397 put ' end; ';
398 put ' rc = fclose(filein); ';
399 put ' rc = fclose(fileid); ';
400 put ' run; ';
401 put ' filename _sjs clear; ';
402 put ' data _null_; file &jref mod encoding=''utf-8''; ';
403 put ' put "]"; ';
404 put ' run; ';
405 put ' %end; ';
406 put '%end; ';
407 put ' ';
408 put '%else %if &action=CLOSE %then %do; ';
409 put ' data _null_;file &jref encoding=''utf-8'' mod; ';
410 put ' put "}"; ';
411 put ' run; ';
412 put '%end; ';
413 put '%mend mp_jsonout; ';
414 put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y); ';
415 put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name ';
416 put ' sasjs_tables SYS_JES_JOB_URI; ';
417 put '%if %index("&_debug",log) %then %let _debug=131; ';
418 put ' ';
419 put '%local i tempds; ';
420 put '%let action=%upcase(&action); ';
421 put ' ';
422 put '%if &action=FETCH %then %do; ';
423 put ' %if %upcase(&_omittextlog)=FALSE or %str(&_debug) ge 131 %then %do; ';
424 put ' options mprint notes mprintnest; ';
425 put ' %end; ';
426 put ' ';
427 put ' %if not %symexist(_webin_fileuri1) %then %do; ';
428 put ' %let _webin_file_count=%eval(&_webin_file_count+0); ';
429 put ' %let _webin_fileuri1=&_webin_fileuri; ';
430 put ' %let _webin_name1=&_webin_name; ';
431 put ' %end; ';
432 put ' ';
433 put ' /* if the sasjs_tables param is passed, we expect param based upload */ ';
434 put ' %if %length(&sasjs_tables.XX)>2 %then %do; ';
435 put ' filename _sasjs "%sysfunc(pathname(work))/sasjs.lua"; ';
436 put ' data _null_; ';
437 put ' file _sasjs; ';
438 put ' put ''s=sas.symget("sasjs_tables")''; ';
439 put ' put ''if(s:sub(1,7) == "%nrstr(")''; ';
440 put ' put ''then''; ';
441 put ' put '' tablist=s:sub(8,s:len()-1)''; ';
442 put ' put ''else''; ';
443 put ' put '' tablist=s''; ';
444 put ' put ''end''; ';
445 put ' put ''for i = 1,sas.countw(tablist) ''; ';
446 put ' put ''do ''; ';
447 put ' put '' tab=sas.scan(tablist,i)''; ';
448 put ' put '' sasdata=""''; ';
449 put ' put '' if (sas.symexist("sasjs"..i.."data0")==0)''; ';
450 put ' put '' then''; ';
451 put ' /* TODO - condense this logic */ ';
452 put ' put '' s=sas.symget("sasjs"..i.."data")''; ';
453 put ' put '' if(s:sub(1,7) == "%nrstr(")''; ';
454 put ' put '' then''; ';
455 put ' put '' sasdata=s:sub(8,s:len()-1)''; ';
456 put ' put '' else''; ';
457 put ' put '' sasdata=s''; ';
458 put ' put '' end''; ';
459 put ' put '' else''; ';
460 put ' put '' for d = 1, sas.symget("sasjs"..i.."data0")''; ';
461 put ' put '' do''; ';
462 put ' put '' s=sas.symget("sasjs"..i.."data"..d)''; ';
463 put ' put '' if(s:sub(1,7) == "%nrstr(")''; ';
464 put ' put '' then''; ';
465 put ' put '' sasdata=sasdata..s:sub(8,s:len()-1)''; ';
466 put ' put '' else''; ';
467 put ' put '' sasdata=sasdata..s''; ';
468 put ' put '' end''; ';
469 put ' put '' end''; ';
470 put ' put '' end''; ';
471 put ' put '' file = io.open(sas.pathname("work").."/"..tab..".csv", "a")''; ';
472 put ' put '' io.output(file)''; ';
473 put ' put '' io.write(sasdata)''; ';
474 put ' put '' io.close(file)''; ';
475 put ' put ''end''; ';
476 put ' run; ';
477 put ' %inc _sasjs; ';
478 put ' ';
479 put ' /* now read in the data */ ';
480 put ' %do i=1 %to %sysfunc(countw(&sasjs_tables)); ';
481 put ' %local table; %let table=%scan(&sasjs_tables,&i); ';
482 put ' data _null_; ';
483 put ' infile "%sysfunc(pathname(work))/&table..csv" termstr=crlf ; ';
484 put ' input; ';
485 put ' if _n_=1 then call symputx(''input_statement'',_infile_); ';
486 put ' list; ';
487 put ' data &table; ';
488 put ' infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd ';
489 put ' termstr=crlf; ';
490 put ' input &input_statement; ';
491 put ' run; ';
492 put ' %end; ';
493 put ' %end; ';
494 put ' %else %do i=1 %to &_webin_file_count; ';
495 put ' /* read in any files that are sent */ ';
496 put ' /* this part needs refactoring for wide files */ ';
497 put ' filename indata filesrvc "&&_webin_fileuri&i" lrecl=999999; ';
498 put ' data _null_; ';
499 put ' infile indata termstr=crlf lrecl=32767; ';
500 put ' input; ';
501 put ' if _n_=1 then call symputx(''input_statement'',_infile_); ';
502 put ' %if %str(&_debug) ge 131 %then %do; ';
503 put ' if _n_<20 then putlog _infile_; ';
504 put ' else stop; ';
505 put ' %end; ';
506 put ' %else %do; ';
507 put ' stop; ';
508 put ' %end; ';
509 put ' run; ';
510 put ' data &&_webin_name&i; ';
511 put ' infile indata firstobs=2 dsd termstr=crlf ; ';
512 put ' input &input_statement; ';
513 put ' run; ';
514 put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; ';
515 put ' %end; ';
516 put '%end; ';
517 put '%else %if &action=OPEN %then %do; ';
518 put ' /* setup webout */ ';
519 put ' OPTIONS NOBOMFILE; ';
520 put ' %if "X&SYS_JES_JOB_URI.X"="XX" %then %do; ';
521 put ' filename _webout temp lrecl=999999 mod; ';
522 put ' %end; ';
523 put ' %else %do; ';
524 put ' filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" ';
525 put ' name="_webout.json" lrecl=999999 mod; ';
526 put ' %end; ';
527 put ' ';
528 put ' /* setup temp ref */ ';
529 put ' %if %upcase(&fref) ne _WEBOUT %then %do; ';
530 put ' filename &fref temp lrecl=999999 permission=''A::u::rwx,A::g::rw-,A::o::---'' ';
531 put ' mod; ';
532 put ' %end; ';
533 put ' ';
534 put ' /* setup json */ ';
535 put ' data _null_;file &fref; ';
536 put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
537 put ' run; ';
538 put '%end; ';
539 put '%else %if &action=ARR or &action=OBJ %then %do; ';
540 put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
541 put ' ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) ';
542 put ' ) ';
543 put '%end; ';
544 put '%else %if &action=CLOSE %then %do; ';
545 put ' %if %str(&_debug) ge 131 %then %do; ';
546 put ' /* send back first 10 records of each work table for debugging */ ';
547 put ' options obs=10; ';
548 put ' data;run;%let tempds=%scan(&syslast,2,.); ';
549 put ' ods output Members=&tempds; ';
550 put ' proc datasets library=WORK memtype=data; ';
551 put ' %local wtcnt;%let wtcnt=0; ';
552 put ' data _null_; ';
553 put ' set &tempds; ';
554 put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
555 put ' i+1; ';
556 put ' call symputx(''wt''!!left(i),name); ';
557 put ' call symputx(''wtcnt'',i); ';
558 put ' data _null_; file &fref mod; put ",""WORK"":{"; ';
559 put ' %do i=1 %to &wtcnt; ';
560 put ' %let wt=&&wt&i; ';
561 put ' proc contents noprint data=&wt ';
562 put ' out=_data_ (keep=name type length format:); ';
563 put ' run;%let tempds=%scan(&syslast,2,.); ';
564 put ' data _null_; file &fref mod; ';
565 put ' dsid=open("WORK.&wt",''is''); ';
566 put ' nlobs=attrn(dsid,''NLOBS''); ';
567 put ' nvars=attrn(dsid,''NVARS''); ';
568 put ' rc=close(dsid); ';
569 put ' if &i>1 then put '',''@; ';
570 put ' put " ""&wt"" : {"; ';
571 put ' put ''"nlobs":'' nlobs; ';
572 put ' put '',"nvars":'' nvars; ';
573 put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) ';
574 put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) ';
575 put ' data _null_; file &fref mod;put "}"; ';
576 put ' %end; ';
577 put ' data _null_; file &fref mod;put "}";run; ';
578 put ' %end; ';
579 put ' ';
580 put ' /* close off json */ ';
581 put ' data _null_;file &fref mod; ';
582 put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
583 put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
584 put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
585 put ' SYS_JES_JOB_URI=quote(trim(resolve(symget(''SYS_JES_JOB_URI'')))); ';
586 put ' put '',"SYS_JES_JOB_URI" : '' SYS_JES_JOB_URI ; ';
587 put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
588 put ' put ",""_DEBUG"" : ""&_debug"" "; ';
589 put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
590 put ' put ",""SYSCC"" : ""&syscc"" "; ';
591 put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
592 put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
593 put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
594 put ' put ",""SYSSITE"" : ""&syssite"" "; ';
595 put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
596 put ' put '',"SYSVLONG" : '' sysvlong; ';
597 put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; ';
598 put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''" ''; ';
599 put ' put "}"; ';
600 put ' ';
601 put ' %if %upcase(&fref) ne _WEBOUT and &stream=Y %then %do; ';
602 put ' data _null_; rc=fcopy("&fref","_webout");run; ';
603 put ' %end; ';
604 put ' ';
605 put '%end; ';
606 put ' ';
607 put '%mend mv_webout; ';
608 put ' ';
609 put '%macro mf_getuser(type=META ';
610 put ')/*/STORE SOURCE*/; ';
611 put ' %local user metavar; ';
612 put ' %if &type=OS %then %let metavar=_secureusername; ';
613 put ' %else %let metavar=_metaperson; ';
614 put ' ';
615 put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
616 put ' %else %if %symexist(&metavar) %then %do; ';
617 put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
618 put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
619 put ' %else %let user=%scan(&&&metavar,1,@); ';
620 put ' %end; ';
621 put ' %else %let user=&sysuserid; ';
622 put ' ';
623 put ' %quote(&user) ';
624 put ' ';
625 put '%mend mf_getuser; ';
626/* WEBOUT END */
627 put '/* if calling viya service with _job param, _program will conflict */';
628 put '/* so it is provided by SASjs instead as __program */';
629 put '%global __program _program;';
630 put '%let _program=%sysfunc(coalescec(&__program,&_program));';
631 put ' ';
632 put '%macro webout(action,ds,dslabel=,fmt=);';
633 put ' %mv_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)';
634 put '%mend;';
635run;
636
637/* insert the code, escaping double quotes and carriage returns */
638%&dbg.put &sysmacroname: Creating final input file;
639%local x fref freflist;
640%let freflist= &adapter &precode &code ;
641%do x=1 %to %sysfunc(countw(&freflist));
642 %let fref=%scan(&freflist,&x);
643 %&dbg.put &sysmacroname: adding &fref fileref;
644 data _null_;
645 length filein 8 fileid 8;
646 filein = fopen("&fref","I",1,"B");
647 fileid = fopen("&fname3","A",1,"B");
648 rec = "20"x;
649 do while(fread(filein)=0);
650 rc = fget(filein,rec,1);
651 if rec='"' then do; /* DOUBLE QUOTE */
652 rc =fput(fileid,'\');rc =fwrite(fileid);
653 rc =fput(fileid,'"');rc =fwrite(fileid);
654 end;
655 else if rec='0A'x then do; /* LF */
656 rc =fput(fileid,'\');rc =fwrite(fileid);
657 rc =fput(fileid,'n');rc =fwrite(fileid);
658 end;
659 else if rec='0D'x then do; /* CR */
660 rc =fput(fileid,'\');rc =fwrite(fileid);
661 rc =fput(fileid,'r');rc =fwrite(fileid);
662 end;
663 else if rec='09'x then do; /* TAB */
664 rc =fput(fileid,'\');rc =fwrite(fileid);
665 rc =fput(fileid,'t');rc =fwrite(fileid);
666 end;
667 else if rec='5C'x then do; /* BACKSLASH */
668 rc =fput(fileid,'\');rc =fwrite(fileid);
669 rc =fput(fileid,'\');rc =fwrite(fileid);
670 end;
671 else if rec='01'x then do; /* Unprintable */
672 rc =fput(fileid,'\');rc =fwrite(fileid);
673 rc =fput(fileid,'u');rc =fwrite(fileid);
674 rc =fput(fileid,'0');rc =fwrite(fileid);
675 rc =fput(fileid,'0');rc =fwrite(fileid);
676 rc =fput(fileid,'0');rc =fwrite(fileid);
677 rc =fput(fileid,'1');rc =fwrite(fileid);
678 end;
679 else if rec='07'x then do; /* Bell Char */
680 rc =fput(fileid,'\');rc =fwrite(fileid);
681 rc =fput(fileid,'u');rc =fwrite(fileid);
682 rc =fput(fileid,'0');rc =fwrite(fileid);
683 rc =fput(fileid,'0');rc =fwrite(fileid);
684 rc =fput(fileid,'0');rc =fwrite(fileid);
685 rc =fput(fileid,'7');rc =fwrite(fileid);
686 end;
687 else if rec='1B'x then do; /* escape char */
688 rc =fput(fileid,'\');rc =fwrite(fileid);
689 rc =fput(fileid,'u');rc =fwrite(fileid);
690 rc =fput(fileid,'0');rc =fwrite(fileid);
691 rc =fput(fileid,'0');rc =fwrite(fileid);
692 rc =fput(fileid,'1');rc =fwrite(fileid);
693 rc =fput(fileid,'B');rc =fwrite(fileid);
694 end;
695 else do;
696 rc =fput(fileid,rec);
697 rc =fwrite(fileid);
698 end;
699 end;
700 rc=fclose(filein);
701 rc=fclose(fileid);
702 run;
703%end;
704
705/* finish off the body of the code file loaded to JES */
706data _null_;
707 file &fname3 mod TERMSTR=' ';
708 put '"}';
709run;
710
711%if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do;
712 %put &sysmacroname: input about to be POSTed;
713 data _null_;infile &fname3;input;putlog _infile_;run;
714%end;
715
716%&dbg.put &sysmacroname: Creating the actual service!;
717%local fname4;
718%let fname4=%mf_getuniquefileref();
719proc http method='POST'
720 in=&fname3
721 out=&fname4
722 &oauth_bearer
723 url="&base_uri/jobDefinitions/definitions?parentFolderUri=&parentFolderUri";
724 headers 'Content-Type'='application/vnd.sas.job.definition+json'
725 %if &grant_type=authorization_code %then %do;
726 "Authorization"="Bearer &&&access_token_var"
727 %end;
728 "Accept"="application/vnd.sas.job.definition+json";
729%if &mdebug=1 %then %do;
730 debug level = 3;
731%end;
732run;
733%if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do;
734 %put &sysmacroname: output from POSTing job definition;
735 data _null_;infile &fname4;input;putlog _infile_;run;
736%end;
737%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 201)
738 ,mac=&sysmacroname
739 ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
740)
741
742/* get the url so we can give a helpful log message */
743%local url;
744data _null_;
745 if symexist('_baseurl') then do;
746 url=symget('_baseurl');
747 if subpad(url,length(url)-9,9)='SASStudio'
748 then url=substr(url,1,length(url)-11);
749 else url="&systcpiphostname";
750 end;
751 else url="&systcpiphostname";
752 call symputx('url',url);
753run;
754
755%if &mdebug=1 %then %do;
756 %put &sysmacroname exit vars:;
757 %put _local_;
758%end;
759%else %do;
760 /* clear refs */
761 filename &fname1 clear;
762 filename &fname2 clear;
763 filename &fname3 clear;
764 filename &fname4 clear;
765 filename &adapter clear;
766 libname &libref1 clear;
767%end;
768
769%put &sysmacroname: Job &name successfully created in &path;
770%put &sysmacroname:;
771%put &sysmacroname: Check it out here:;
772%put &sysmacroname:;%put;
773%put &url/SASJobExecution?_PROGRAM=&path/&name;%put;
774%put &sysmacroname:;
775%put &sysmacroname:;
776
777%mend mv_createwebservice;