Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
mm_createwebservice.sas
Go to the documentation of this file.
1 /**
2  @file mm_createwebservice.sas
3  @brief Create a Web Ready Stored Process
4  @details This macro creates a Type 2 Stored Process with the mm_webout macro
5  included as pre-code.
6 Usage:
7 
8  %* compile macros ;
9  filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
10  %inc mc;
11 
12  %* parmcards lets us write to a text file from open code ;
13  filename ft15f001 temp;
14  parmcards4;
15  %* do some sas, any inputs are now already WORK tables;
16  data example1 example2;
17  set sashelp.class;
18  run;
19  %* send data back;
20  %webout(OPEN)
21  %webout(ARR,example1) * Array format, fast, suitable for large tables ;
22  %webout(OBJ,example2) * Object format, easier to work with ;
23  %webout(CLOSE)
24  ;;;;
25  %mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001,replace=YES)
26 
27  <h4> Dependencies </h4>
28  @li mm_createstp.sas
29  @li mf_getuser.sas
30  @li mm_createfolder.sas
31  @li mm_deletestp.sas
32 
33  @param path= The full path (in SAS Metadata) where the service will be created
34  @param name= Stored Process name. Avoid spaces - testing has shown that
35  the check to avoid creating multiple STPs in the same folder with the same
36  name does not work when the name contains spaces.
37  @param desc= The description of the service (optional)
38  @param precode= Space separated list of filerefs, pointing to the code that
39  needs to be attached to the beginning of the service (optional)
40  @param code= Space seperated fileref(s) of the actual code to be added
41  @param server= The server which will run the STP. Server name or uri is fine.
42  @param mDebug= set to 1 to show debug messages in the log
43  @param replace= select YES to replace any existing service in that location
44  @param adapter= the macro uses the sasjs adapter by default. To use another
45  adapter, add a (different) fileref here.
46 
47  @version 9.2
48  @author Allan Bowe
49 
50 **/
51 
52 %macro mm_createwebservice(path=
53  ,name=initService
54  ,precode=
55  ,code=
56  ,desc=This stp was created automagically by the mm_createwebservice macro
57  ,mDebug=0
58  ,server=SASApp
59  ,replace=NO
60  ,adapter=sasjs
61 )/*/STORE SOURCE*/;
62 
63 %if &syscc ge 4 %then %do;
64  %put &=syscc - &sysmacroname will not execute in this state;
65  %return;
66 %end;
67 
68 %local mD;
69 %if &mDebug=1 %then %let mD=;
70 %else %let mD=%str(*);
71 %&mD.put Executing mm_createwebservice.sas;
72 %&mD.put _local_;
73 
74 * remove any trailing slash ;
75 %if "%substr(&path,%length(&path),1)" = "/" %then
76  %let path=%substr(&path,1,%length(&path)-1);
77 
78 /**
79  * Add webout macro
80  * These put statements are auto generated - to change the macro, change the
81  * source (mm_webout) and run `build.py`
82  */
83 filename sasjs temp;
84 data _null_;
85  file sasjs lrecl=3000 ;
86  put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
87 /* WEBOUT BEGIN */
88  put ' ';
89  put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0 ';
90  put ')/*/STORE SOURCE*/; ';
91  put '%put output location=&jref; ';
92  put '%if &action=OPEN %then %do; ';
93  put ' data _null_;file &jref encoding=''utf-8''; ';
94  put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
95  put ' run; ';
96  put '%end; ';
97  put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
98  put ' options validvarname=upcase; ';
99  put ' data _null_;file &jref mod encoding=''utf-8''; ';
100  put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
101  put ' ';
102  put ' %if &engine=PROCJSON %then %do; ';
103  put ' data;run;%let tempds=&syslast; ';
104  put ' proc sql;drop table &tempds; ';
105  put ' data &tempds /view=&tempds;set &ds; ';
106  put ' %if &fmt=N %then format _numeric_ best32.;; ';
107  put ' proc json out=&jref pretty ';
108  put ' %if &action=ARR %then nokeys ; ';
109  put ' ;export &tempds / nosastags fmtnumeric; ';
110  put ' run; ';
111  put ' proc sql;drop view &tempds; ';
112  put ' %end; ';
113  put ' %else %if &engine=DATASTEP %then %do; ';
114  put ' %local cols i tempds; ';
115  put ' %let cols=0; ';
116  put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; ';
117  put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
118  put ' %return; ';
119  put ' %end; ';
120  put ' data _null_;file &jref mod ; ';
121  put ' put "["; call symputx(''cols'',0,''l''); ';
122  put ' proc sort data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
123  put ' out=_data_; ';
124  put ' by varnum; ';
125  put ' ';
126  put ' data _null_; ';
127  put ' set _last_ end=last; ';
128  put ' call symputx(cats(''name'',_n_),name,''l''); ';
129  put ' call symputx(cats(''type'',_n_),type,''l''); ';
130  put ' call symputx(cats(''len'',_n_),length,''l''); ';
131  put ' if last then call symputx(''cols'',_n_,''l''); ';
132  put ' run; ';
133  put ' ';
134  put ' proc format; /* credit yabwon for special null removal */ ';
135  put ' value bart ._ - .z = null ';
136  put ' other = [best.]; ';
137  put ' ';
138  put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ ';
139  put ' proc sql; drop table &tempds; ';
140  put ' data &tempds/view=&tempds; ';
141  put ' attrib _all_ label=''''; ';
142  put ' %do i=1 %to &cols; ';
143  put ' %if &&type&i=char %then %do; ';
144  put ' length &&name&i $32767; ';
145  put ' format &&name&i $32767.; ';
146  put ' %end; ';
147  put ' %end; ';
148  put ' set &ds; ';
149  put ' format _numeric_ bart.; ';
150  put ' %do i=1 %to &cols; ';
151  put ' %if &&type&i=char %then %do; ';
152  put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, ';
153  put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, ';
154  put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, ';
155  put ' prxchange(''s/''!!''09''x!!''/\t/'',-1, ';
156  put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
157  put ' )))))!!''"''; ';
158  put ' %end; ';
159  put ' %end; ';
160  put ' run; ';
161  put ' /* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */ ';
162  put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
163  put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
164  put ' set &tempds; ';
165  put ' if _n_>1 then put "," @; put ';
166  put ' %if &action=ARR %then "[" ; %else "{" ; ';
167  put ' %do i=1 %to &cols; ';
168  put ' %if &i>1 %then "," ; ';
169  put ' %if &action=OBJ %then """&&name&i"":" ; ';
170  put ' &&name&i ';
171  put ' %end; ';
172  put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
173  put ' proc sql; ';
174  put ' drop view &tempds; ';
175  put ' /* now write the long strings to _webout 1 byte at a time */ ';
176  put ' data _null_; ';
177  put ' length filein 8 fileid 8; ';
178  put ' filein = fopen("_sjs",''I'',1,''B''); ';
179  put ' fileid = fopen("&jref",''A'',1,''B''); ';
180  put ' rec = ''20''x; ';
181  put ' do while(fread(filein)=0); ';
182  put ' rc = fget(filein,rec,1); ';
183  put ' rc = fput(fileid, rec); ';
184  put ' rc =fwrite(fileid); ';
185  put ' end; ';
186  put ' rc = fclose(filein); ';
187  put ' rc = fclose(fileid); ';
188  put ' run; ';
189  put ' filename _sjs clear; ';
190  put ' data _null_; file &jref mod encoding=''utf-8''; ';
191  put ' put "]"; ';
192  put ' run; ';
193  put ' %end; ';
194  put '%end; ';
195  put ' ';
196  put '%else %if &action=CLOSE %then %do; ';
197  put ' data _null_;file &jref encoding=''utf-8''; ';
198  put ' put "}"; ';
199  put ' run; ';
200  put '%end; ';
201  put '%mend; ';
202  put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); ';
203  put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
204  put ' sasjs_tables; ';
205  put '%local i tempds; ';
206  put ' ';
207  put '%if &action=FETCH %then %do; ';
208  put ' %if %str(&_debug) ge 131 %then %do; ';
209  put ' options mprint notes mprintnest; ';
210  put ' %end; ';
211  put ' %let _webin_file_count=%eval(&_webin_file_count+0); ';
212  put ' /* now read in the data */ ';
213  put ' %do i=1 %to &_webin_file_count; ';
214  put ' %if &_webin_file_count=1 %then %do; ';
215  put ' %let _webin_fileref1=&_webin_fileref; ';
216  put ' %let _webin_name1=&_webin_name; ';
217  put ' %end; ';
218  put ' data _null_; ';
219  put ' infile &&_webin_fileref&i termstr=crlf; ';
220  put ' input; ';
221  put ' call symputx(''input_statement'',_infile_); ';
222  put ' putlog "&&_webin_name&i input statement: " _infile_; ';
223  put ' stop; ';
224  put ' data &&_webin_name&i; ';
225  put ' infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8''; ';
226  put ' input &input_statement; ';
227  put ' %if %str(&_debug) ge 131 %then %do; ';
228  put ' if _n_<20 then putlog _infile_; ';
229  put ' %end; ';
230  put ' run; ';
231  put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; ';
232  put ' %end; ';
233  put '%end; ';
234  put ' ';
235  put '%else %if &action=OPEN %then %do; ';
236  put ' /* fix encoding */ ';
237  put ' OPTIONS NOBOMFILE; ';
238  put ' data _null_; ';
239  put ' rc = stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
240  put ' run; ';
241  put ' ';
242  put ' /* setup json */ ';
243  put ' data _null_;file &fref encoding=''utf-8''; ';
244  put ' %if %str(&_debug) ge 131 %then %do; ';
245  put ' put ''>>weboutBEGIN<<''; ';
246  put ' %end; ';
247  put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; ';
248  put ' run; ';
249  put ' ';
250  put '%end; ';
251  put ' ';
252  put '%else %if &action=ARR or &action=OBJ %then %do; ';
253  put ' %if &sysver=9.4 %then %do; ';
254  put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
255  put ' ,engine=PROCJSON,dbg=%str(&_debug) ';
256  put ' ) ';
257  put ' %end; ';
258  put ' %else %do; ';
259  put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
260  put ' ,engine=DATASTEP,dbg=%str(&_debug) ';
261  put ' ) ';
262  put ' %end; ';
263  put '%end; ';
264  put '%else %if &action=CLOSE %then %do; ';
265  put ' %if %str(&_debug) ge 131 %then %do; ';
266  put ' /* if debug mode, send back first 10 records of each work table also */ ';
267  put ' options obs=10; ';
268  put ' data;run;%let tempds=%scan(&syslast,2,.); ';
269  put ' ods output Members=&tempds; ';
270  put ' proc datasets library=WORK memtype=data; ';
271  put ' %local wtcnt;%let wtcnt=0; ';
272  put ' data _null_; ';
273  put ' set &tempds; ';
274  put ' if not (name =:"DATA"); ';
275  put ' i+1; ';
276  put ' call symputx(''wt''!!left(i),name,''l''); ';
277  put ' call symputx(''wtcnt'',i,''l''); ';
278  put ' data _null_; file &fref encoding=''utf-8''; ';
279  put ' put ",""WORK"":{"; ';
280  put ' %do i=1 %to &wtcnt; ';
281  put ' %let wt=&&wt&i; ';
282  put ' proc contents noprint data=&wt ';
283  put ' out=_data_ (keep=name type length format:); ';
284  put ' run;%let tempds=%scan(&syslast,2,.); ';
285  put ' data _null_; file &fref encoding=''utf-8''; ';
286  put ' dsid=open("WORK.&wt",''is''); ';
287  put ' nlobs=attrn(dsid,''NLOBS''); ';
288  put ' nvars=attrn(dsid,''NVARS''); ';
289  put ' rc=close(dsid); ';
290  put ' if &i>1 then put '',''@; ';
291  put ' put " ""&wt"" : {"; ';
292  put ' put ''"nlobs":'' nlobs; ';
293  put ' put '',"nvars":'' nvars; ';
294  put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) ';
295  put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) ';
296  put ' data _null_; file &fref encoding=''utf-8''; ';
297  put ' put "}"; ';
298  put ' %end; ';
299  put ' data _null_; file &fref encoding=''utf-8''; ';
300  put ' put "}"; ';
301  put ' run; ';
302  put ' %end; ';
303  put ' /* close off json */ ';
304  put ' data _null_;file &fref mod encoding=''utf-8''; ';
305  put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
306  put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
307  put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
308  put ' put ",""_DEBUG"" : ""&_debug"" "; ';
309  put ' _METAUSER=quote(trim(symget(''_METAUSER''))); ';
310  put ' put ",""_METAUSER"": " _METAUSER; ';
311  put ' _METAPERSON=quote(trim(symget(''_METAPERSON''))); ';
312  put ' put '',"_METAPERSON": '' _METAPERSON; ';
313  put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
314  put ' put ",""SYSCC"" : ""&syscc"" "; ';
315  put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
316  put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
317  put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
318  put ' put ",""SYSSITE"" : ""&syssite"" "; ';
319  put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; ';
320  put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''" ''; ';
321  put ' put "}" @; ';
322  put ' %if %str(&_debug) ge 131 %then %do; ';
323  put ' put ''>>weboutEND<<''; ';
324  put ' %end; ';
325  put ' run; ';
326  put '%end; ';
327  put ' ';
328  put '%mend; ';
329  put ' ';
330  put '%macro mf_getuser(type=META ';
331  put ')/*/STORE SOURCE*/; ';
332  put ' %local user metavar; ';
333  put ' %if &type=OS %then %let metavar=_secureusername; ';
334  put ' %else %let metavar=_metaperson; ';
335  put ' ';
336  put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
337  put ' %else %if %symexist(&metavar) %then %do; ';
338  put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
339  put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
340  put ' %else %let user=%scan(&&&metavar,1,@); ';
341  put ' %end; ';
342  put ' %else %let user=&sysuserid; ';
343  put ' ';
344  put ' %quote(&user) ';
345  put ' ';
346  put '%mend; ';
347 /* WEBOUT END */
348  put '%macro webout(action,ds,dslabel=,fmt=);';
349  put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)';
350  put '%mend;';
351 run;
352 
353 /* add precode and code */
354 %local work tmpfile;
355 %let work=%sysfunc(pathname(work));
356 %let tmpfile=__mm_createwebservice.temp;
357 %local x fref freflist mod;
358 %let freflist= &adapter &precode &code ;
359 %do x=1 %to %sysfunc(countw(&freflist));
360  %if &x>1 %then %let mod=mod;
361 
362  %let fref=%scan(&freflist,&x);
363  %put &sysmacroname: adding &fref;
364  data _null_;
365  file "&work/&tmpfile" lrecl=3000 &mod;
366  infile &fref;
367  input;
368  put _infile_;
369  run;
370 %end;
371 
372 /* create the metadata folder if not already there */
373 %mm_createfolder(path=&path)
374 %if &syscc ge 4 %then %return;
375 
376 %if %upcase(&replace)=YES %then %do;
377  %mm_deletestp(target=&path/&name)
378 %end;
379 
380 /* create the web service */
381 %mm_createstp(stpname=&name
382  ,filename=&tmpfile
383  ,directory=&work
384  ,tree=&path
385  ,stpdesc=&desc
386  ,mDebug=&mdebug
387  ,server=&server
388  ,stptype=2)
389 
390 /* find the web app url */
391 %local url;
392 %let url=localhost/SASStoredProcess;
393 data _null_;
394  length url $128;
395  rc=METADATA_GETURI("Stored Process Web App",url);
396  if rc=0 then call symputx('url',url,'l');
397 run;
398 
399 %put ;%put ;%put ;%put ;%put ;%put ;
400 %put &sysmacroname: STP &name successfully created in &path;
401 %put ;%put ;%put ;
402 %put Check it out here:;
403 %put ;%put ;%put ;
404 %put &url?_PROGRAM=&path/&name;
405 %put ;%put ;%put ;%put ;%put ;%put ;
406 
407 %mend;