From c321462128612e6a81403ed7de399c009e5d051e Mon Sep 17 00:00:00 2001 From: Mayur Bari <53168252+mayuraudev@users.noreply.github.com> Date: Tue, 6 Aug 2019 12:18:33 +0200 Subject: [PATCH] Two header are added - "verbose" & "nometa" 1. Allow for an option to send more verbose response in JSon so that consuming application can have ease of development. 2. Call based toggle for suppressing the meta information in response data. To improve performance. Solution : Two header are proposed "verbose" to respect the new format response - true=New format, false/header absent=Old format "nometa" to toggle the meta included in response - true=meta info excluded, false/header absent=meta info included. If the Restful API returning single object { "status": "Ok" or "Error" "value":{ } "error":{ "errorNo":"ABX011110", -- if any error number is there "errorText":"The name value can be be empty to process data." -- error description or text } } If the Restful API returning multiple objects { "status": "Ok" or "Error" "value":[ ] "count": "error":{ } } --- RestBCAdapter.bbj | 1282 ++++++++++++++-------------- RestBridge.bbj | 2080 +++++++++++++++++++++++---------------------- 2 files changed, 1692 insertions(+), 1670 deletions(-) diff --git a/RestBCAdapter.bbj b/RestBCAdapter.bbj index bd02927..a67f3d4 100644 --- a/RestBCAdapter.bbj +++ b/RestBCAdapter.bbj @@ -1,641 +1,641 @@ -use java.util.HashMap -use com.basiscomponents.db.ResultSet -use com.basiscomponents.db.DataRow - -seterr execerr - -debug=0 -debug=num(stbl("DEBUG",err=*next),err=*next) - -if debug then System.out.println("REST: new remote session ") - -BBjAPI().getConfig().setOptionSetting("ERROR_UNWINDS", 1) - -ses$ = argv(1) -authtype$=argv(2) -auth$ = argv(3) - -if debug then System.out.println("REST: WORKDIR "+dir("")) -ses$=stbl("SESSIONID",ses$) - -servletParams! = new HashMap() -servletParams! = BBjAPI().getGlobalNamespace().getValue(ses$+"_SERVLET_PARAMETERS",err=*next) - -if debug then System.out.println("Got auth header: "+authtype$+" "+auth$) -authtype$=cvs(authtype$,4) - -if authtype$="BASIC" then - auth$=java.util.Base64.getDecoder().decode(auth$) - user$=auth$(1,pos(":"=auth$)-1) - password$=auth$(pos(":"=auth$)+1) -fi - -if authtype$="BEARER" then - jwt$=auth$ -fi - -authenticated=0 - -rem changing the string encoding to the server's encoding -user$ = new String(user$,"utf-8").getBytes(info(1,2)) -password$ = new String(password$,"utf-8").getBytes(info(1,2)) - -authpgm$="authenticate.bbj" -if servletParams!.get("REST_AUTHPGM") <> null() then - authpgm$ = servletParams!.get("REST_AUTHPGM").toString() -endif - -call authpgm$ - -requestSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST_SEM") -responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") - -done_login: - - -timeout=60 -if servletParams!.containsKey("REST_TIMEOUT") then - timeout=num(servletParams!.get("REST_TIMEOUT").toString(),err=*next) -endif - -main_loop: - - CLEAR EXCEPT user$, ses$, requestSemaphore!, responseSemaphore!, ses!, authenticated, timeout, usecount, debug, authpgm$, servletParams! - - - seterr execerr - - dummy$=STBL("!SESSIONINFO",ses$+" - idle "+str(usecount)) - - rem requestSemaphore!.acquire() - if !requestSemaphore!.tryAcquire(1,timeout,java.util.concurrent.TimeUnit.SECONDS) then - rem a timeout has occurred while trying to acquire the semaphore - - rem remove the sessions' namespace values - namespace! = BBjAPI().getGlobalNamespace() - namespace!.removeValue(ses$+"_REQUEST") - namespace!.removeValue(ses$+"_RESPONSE") - namespace!.removeValue(ses$+"_REQUEST_SEM") - namespace!.removeValue(ses$+"_RESPONSE_SEM") - namespace!.removeValue(ses$+"_SERVLET_PARAMETERS") - - rem Calling the ON_CLEANUP label to perform possible user defined cleanup operations - call authpgm$+"::ON_CLEANUP",err=*next - - release - fi - - request!=BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST") - if (str(request!) = "PING") then - responseSemaphore!.release() - goto main_loop - fi - - call authpgm$+"::ON_REQUEST",err=*next - - if debug then System.out.println("REST: remote session running "+ses$) - dummy$=STBL("!SESSIONINFO",ses$+" - working "+str(request!)) - - response! = new HashMap() - params! = new HashMap() - - if request!.get("uri") = null() then - statuscode=404 - throw "Missing BC",12 - endif - - vec! = request!.get("preferredlocales") - if vec! <> null() and vec!.size() then - void$ = stbl("!LOCALE",vec!.getItem(0).getLanguage()) - endif - - uri$=request!.get("uri") - uri$=uri$(2) - if pos("/"=uri$)>0 - bc$=uri$(1,pos("/"=uri$)-1) - uri$=uri$(pos("/"=uri$)+1) - sel$=uri$ - else - bc$=uri$ - sel$="" - fi - - if cvs(bc$,3) = "" then - statuscode=404 - throw "Missing BC",12 - endif - - ns! = BBjAPI().getNamespace("RestBCAdapter_"+ses$,"BCList",1) - bcMap! = ns!.getValue("BCMAP",err=*next) - if bcMap! = null() then - bcMap! = new HashMap() - ns!.setValue("BCMAP",bcMap!) - endif - - if bc$="Token" then - rs! = new ResultSet() - token_rec! = new DataRow() - - rem token_rec! should be created in the customizable authpgm - call authpgm$+"::ISSUE_JWT" - - rs!.add(token_rec!) - response!.put("resultset",rs!) - response!.put("BC","Token") - response!.put("single",1) - goto send_response - fi - - - bc$=bc$+"BC" - if bcMap!.containsKey(bc$) then - bc! = bcMap!.get(bc$) - else - if servletParams!.containsKey("REST_PGM_PREFIX") then - prefix$ = servletParams!.get("REST_PGM_PREFIX") - void$ = stbl("REST_PGM_PREFIX", prefix$) - endif - - if servletParams!.containsKey("REST_PGM_SUFFIX") then - suffix$ = servletParams!.get("REST_PGM_SUFFIX") - void$ = stbl("REST_PGM_SUFFIX", suffix$) - endif - - bc! = null() - bc! = eval("new ::"+prefix$+bc$+suffix$+"::"+bc$+"()") - bcMap!.put(bc$,bc!) - endif - - rem Determine primary key field(s) - ar! = bc!.getAttributesRecord() - it! = ar!.getFieldNames().iterator() - while it!.hasNext() - f$=it!.next() - if ar!.getFieldAttribute(f$,"EDITABLE")="2" then - filterf$=filterf$+"/"+f$ - fi - wend - if filterf$ > "" then - filterf$ = filterf$(2) - endif - - - method$ = request!.get("method") - switch method$ - case "POST" - if request!.get("invoke") <> null() then - invoke$ = request!.get("invoke") - dr! = DataRow.fromJson(request!.get("body")) - if dr! <> null() and dr!.getColumnCount() > 0 then - variableNamePrefix! = new String(user$) - variableNamePrefix! = variableNamePrefix!.replaceAll("\W", "_") - - it! = dr!.getFieldNames().iterator() - while it!.hasNext() - f$ = it!.next() - execute variableNamePrefix!+f$ + "! = dr!.getFieldValue("""+f$+""")" - ex$ = ex$ + "," + variableNamePrefix! + f$ + "!" - wend - - ex$ = ex$(2) - endif - ex$ = "bc!."+invoke$+"("+ex$+")" - if request!.get("retvarname") <> null() then - retvar$ = request!.get("retvarname") - ex$ = "var! = " + ex$ - endif - execute ex$ - if retvar$ <> "" then - if var!.getClass().getName() = "com.basiscomponents.db.ResultSet" then - rs! = var! - else - rs! = new ResultSet() - if var!.getClass().getName() = "com.basiscomponents.db.DataRow" then - rs!.add(var!) - else - retdr! = new DataRow() - class$=var!.getClass().getName() - if class$ = "java.util.HashMap" then - retdr! = new DataRow(var!) - else - if class$ = "java.lang.String" or class$ = "java.lang.Boolean" or pos("BBjNumber"=class$) or pos("BBjVector"=class$) then - retdr!.setFieldValue(retvar$,var!) - endif - endif - rs!.add(retdr!) - endif - endif - - response!.put("resultset",rs!) - else - response!.put("resultset","") - response!.put("statuscode","204") - endif - response!.put("request",str(request!)) - response!.put("BC",bc$) - response!.put("sel",sel$) - response!.put("primarykey",filterf$) - else - errorOccurred=0 - rs! = new ResultSet() - b1$=request!.get("body") - System.out.println(b1$) - if b1$>"" and b1$(1,1)<>"[" then - b1$="["+b1$+"]" - fi - System.out.println(b1$) - res! = ResultSet.fromJson(b1$) - k! = java.util.Arrays.asList(new String(filterf$).split("/")) - v! = java.util.Arrays.asList(new String(sel$).split("/",-1)) - it! = res!.iterator() - while it!.hasNext() - dr! = it!.next() - for i=0 to min(k!.size(),v!.size())-1 - if !dr!.contains(k!.get(i)) then - dr!.setFieldValue(k!.get(i),v!.get(i)) - endif - next i - - ok=0; dr! = bc!.write(dr!,err=*next); ok=1 - if !ok then - errorOccurred=1 - response!.put("statuscode","409") - response!.put("errorcode",str(err)) - response!.put("errormsg",errmes(-1)) - rem throw errmes(-1),err - else - rs!.addItem(dr!) - created! = dr!.getAttribute("CREATED") - if !errorOccurred then - if created! <> null() and created!.equals("true") then - response!.put("statuscode","201") - else - response!.put("statuscode","200") - endif - endif - endif - wend - - response!.put("resultset",rs!) - response!.put("request",str(request!)) - response!.put("BC",bc$) - response!.put("sel",sel$) - response!.put("primarykey",filterf$) - response!.put("single",rs!.size()>1) - endif - break - - case "DELETE" - case "PUT" - if method$ = "PUT" and cvs(sel$,4) = "IGNOREKEYS" then - dr! = DataRow.fromJson(request!.get("body")) - else - if sel$ = "" or filterf$ = "" then - throw "Missing resource ID",999 - endif - dr! = DataRow.fromJson(request!.get("body")) - k! = java.util.Arrays.asList(new String(filterf$).split("/")) - v! = java.util.Arrays.asList(new String(sel$).split("/",-1)) - if v!.size() < k!.size() then - throw "Not enough parameters for "+method$,999 - endif - for i=0 to k!.size()-1 - if !dr!.contains(k!.get(i)) then - dr!.setFieldValue(k!.get(i),v!.get(i)) - else - if dr!.getFieldAsString(k!.get(i)) <> v!.get(i) then - throw "Different resource ID in URL path and request body!",999 - endif - endif - next i - endif - - rs! = new ResultSet() - if method$ = "PUT" then - dr! = bc!.write(dr!,err=*next); ok=1 - if !ok then - response!.put("statuscode","409") - throw errmes(-1),err - else - created! = dr!.getAttribute("CREATED") - if created! <> null() and created!.equals("true") then - response!.put("statuscode","201") - else - response!.put("statuscode","200") - endif - response!.put("single",1) - endif - rs!.addItem(dr!) - else - bc!.remove(dr!) - response!.put("statuscode","204") - endif - - response!.put("resultset",rs!) - response!.put("request",str(request!)) - response!.put("BC",bc$) - response!.put("sel",sel$) - response!.put("primarykey",filterf$) - break - - case default - params! = request!.get("params",err=*next) - - if sel$ = "_meta" then - rs! = new ResultSet() - rs!.add(bc!.getAttributesRecord()) - response!.put("resultset",rs!) - response!.put("BC",bc$) - response!.put("sel",sel$) - response!.put("single",1) - - goto send_response - endif - - filter! = new DataRow() - response!.put("single",0) - k! = java.util.Arrays.asList(new String(filterf$).split("/")) - v! = java.util.Arrays.asList(new String(sel$).split("/",-1)) - - rem Add fields from URI - if sel$ > "" then - if filterf$>"" then - filter! = new DataRow() - for i=0 to min(v!.size(),k!.size())-1 - filter!.setFieldValue(k!.get(i),v!.get(i)) - next i - response!.put("linkfield",filterf$) - if k!.size() = v!.size() then - response!.put("single",1) - endif - else - statuscode=500 - throw "BC has no filter",999 - fi - fi - - rem iterate params to fill in [[var]]=value into param=[[var]] - if params! <> null() and params!.size() >0 then - it! = params!.keySet().iterator() - while it!.hasNext() - p$=it!.next() - v$=params!.get(p$) - if len(v$)>4 and v$(1,2)="[[" then - x!=params!.get(v$,err=*next) - params!.put(p$,str(x!)) - fi - wend - fi - - rem Add parameters - if params! <> null() and params!.size() >0 then - if filter! = null() then - filter! = new DataRow() - endif - - af! = ar! - if servletParams!.containsKey("USE_GET_ALLOWED_FILTER") and servletParams!.get("USE_GET_ALLOWED_FILTER").toString().equals("true") then - af! = bc!.getAllowedFilter(err=*next) - endif - - REM check the field case insensitive - REM but pass them with the correct casing to the filter - afnames! = af!.getFieldNames() - afnames_uc! = new BBjVector() - it! = afnames!.iterator() - while it!.hasNext() - afnames_uc!.addItem(cvs(str(it!.next()),4)) - wend - - it! = params!.keySet().iterator() - while it!.hasNext() - p$=it!.next() - pv$=params!.get(p$) - if (p$="_FULLTEXT" OR P$="_SEARCH") - filter!.setFieldValue("%SEARCH",pv$) - else - if (p$(1,1)<>"_" and pv$>"" and (afnames_uc!.contains(p$) OR bc$="DataSourceDataBC")) OR P$(1,1)="%" then - rem todo: all BCs should accept filters with fields that are not part of the attributes record - rem and still honor all other fields in the filter correctly, instead of giving no result - filter!.setFieldValue(afnames!.getItem(afnames_uc!.indexOf(p$)),pv$) - fi - fi - wend - fi - - if filter! <> null() then - bc!.setFilter(filter!) - fi - - scope!=params!.get("_SCOPE") - fieldsel!=params!.get("_FIELDSEL",err=*next) - - if fieldsel! <> null() AND scope! <> null() then - ok=0 - vec! = new BBjVector(java.util.Arrays.asList(fieldsel!.split(","))) - bc!.setFieldSelection(scope!, vec!,err=*next); ok=1 - if !ok then - if pos("no match for method"=cvs(errmes(-1),8)) then - fs! = new DataRow() - it! = vec!.iterator() - while it!.hasNext() - fs!.setFieldValue(it!.next(),"") - wend - bc!.setFieldSelection(scope!, fs!) - else - throw errmes(-1),err() - fi - fi - else - if fieldsel! <> null() - ok=0 - vec! = new BBjVector(java.util.Arrays.asList(fieldsel!.split(","))) - bc!.setFieldSelection(vec!,err=*next); ok=1 - if !ok then - if pos("no match for method"=cvs(errmes(-1),8)) then - fs! = new DataRow() - it! = vec!.iterator() - while it!.hasNext() - fs!.setFieldValue(it!.next(),"") - wend - bc!.setFieldSelection(fs!) - else - throw errmes(-1),err() - fi - fi - fi - if scope! <> null() then - bc!.setScope(str(scope!)) - endif - fi - - first% = 0 - last% = 0 - limitSearch = 0 - - if params! <> null() then - if params!.containsKey("_PAGE") or params!.containsKey("_PER_PAGE") then - page = 1 - page = num(params!.get("_PAGE"),err=*next) - if page < 1 then - throw "Invalid value for _PAGE parameter: must be greater than 0",999 - endif - perPage = num(params!.get("_PER_PAGE"),err=*next) - if perPage < 1 then - throw "Invalid value for _PER_PAGE parameter: must be greater than 0",999 - endif - - first% = (int(page)-1)*int(perPage) - last% = first%+int(perPage)-1 - limitSearch = 1 - endif - - if params!.containsKey("_OFFSET") then - offset = num(params!.get("_OFFSET")) - if offset < 0 then - throw "Invalid parameter for _OFFSET parameter: must be greater than or equals 0",999 - endif - limit = 20 - if params!.containsKey("_LIMIT") then - limit = num(params!.get("_LIMIT")) - endif - if limit < 1 then - throw "Invalid parameter for _LIMIT parameter: must be greater than 0",999 - endif - - first% = int(offset) - last% = first% + int(limit)-1 - limitSearch = 1 - endif - endif - - if len(sel$)>8 and sel$(1,8) = "_lookup/" then - rs! = new ResultSet() - - if filter!<>null() then - rs!.add(filter!) - fi - - field$=sel$(9) - System.out.println(field$) - - rs! = bc!.retrieveLookup(field$,filter!) - - response!.put("resultset",rs!) - response!.put("BC",bc$) - response!.put("single",0) - - goto send_response - endif - - if limitSearch then - rs! = bc!.retrieve(first%,last%) - else - rs! = bc!.retrieve() - endif - - - if params!.containsKey("_ATTACHMENT") then - if rs!.size() =1 then - rec! = rs!.getItem(0) - attachfield$ = params!.get("_ATTACHMENT") - - rs! = new ResultSet() - dr! = new DataRow() - filename$=rec!.getFieldAsString(attachfield$) - dr!.setFieldValue("FILEDELIVERY",filename$) - rs!.addItem(dr!) - - response!.put("resultset",rs!) - response!.put("BC",bc$) - response!.put("single",1) - response!.put("file",filename$) - - goto send_response - else - throw "file / record not found",11 - endif - endif - - response!.put("resultset",rs!) - response!.put("request",str(request!)) - response!.put("BC",bc$) - response!.put("sel",sel$) - response!.put("primarykey",filterf$) - break - swend - - - - send_response: - usecount=usecount+1 - usecount=usecount+1 - BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE",response!) - responseSemaphore!.release() - if debug then System.out.println("REST: remote session done "+ses$) - goto main_loop - - - execerr: - errorcode = err - errormsg$ = errmes(-1) - sw! = new java.io.StringWriter() - pw! = new java.io.PrintWriter(sw!) - BBjAPI().getLastBBjException().printStackTrace(pw!) - stacktrace$ = sw!.toString() - sw!.close() - pw!.close() - - if debug then - ch=unt - open (ch,mode="O_CREATE,O_APPEND",err=notrace)"bridgeerrlog.txt" - print (ch,err=*next)date(0:"%Yl-%Mz-%Dz %Hz:%mz:%sz") - print (ch,err=*next)"!ERROR "+str(err)+" "+stacktrace$ - print (ch,err=*next)errmes(-1) - close (ch,err=*next) - endif - - notrace: - -REM if errormsg$(1,4)="java" then -REM errormsg$=errormsg$(pos(":"=errormsg$)+2) -REM endif - - - error_out: - if statuscode=0 - statuscode=500 - fi - - response! = new HashMap() - - if authenticated>0 then - response!.put("statuscode",str(statuscode)) - response!.put("errorcode",errorcode) - response!.put("errormsg",errormsg$) - rem consider: stack trace only with DEBUG flag in config? - response!.put("stacktrace",stacktrace$) - else - response!.put("statuscode","401") - response!.put("errorcode",errorcode) - response!.put("errormsg",errormsg$) - rem no stack trace for unauthenticated users - fi - - BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE",response!) - responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") - responseSemaphore!.release() - - wait 10; rem FIN_WAIT - - namespace! = BBjAPI().getGlobalNamespace() - namespace!.removeValue(ses$+"_REQUEST",err=*next) - namespace!.removeValue(ses$+"_RESPONSE",err=*next) - namespace!.removeValue(ses$+"_REQUEST_SEM",err=*next) - namespace!.removeValue(ses$+"_RESPONSE_SEM",err=*next) - namespace!.removeValue(ses$+"_SERVLET_PARAMETERS",err=*next) - - rem Calling the ON_CLEANUP label to perform possible user defined cleanup operations - call authpgm$+"::ON_CLEANUP",err=*next -release - +use java.util.HashMap +use com.basiscomponents.db.ResultSet +use com.basiscomponents.db.DataRow + +seterr execerr + +debug=0 +debug=num(stbl("DEBUG",err=*next),err=*next) + +if debug then System.out.println("REST: new remote session ") + +BBjAPI().getConfig().setOptionSetting("ERROR_UNWINDS", 1) + +ses$ = argv(1) +authtype$=argv(2) +auth$ = argv(3) + +if debug then System.out.println("REST: WORKDIR "+dir("")) +ses$=stbl("SESSIONID",ses$) + +servletParams! = new HashMap() +servletParams! = BBjAPI().getGlobalNamespace().getValue(ses$+"_SERVLET_PARAMETERS",err=*next) + +if debug then System.out.println("Got auth header: "+authtype$+" "+auth$) +authtype$=cvs(authtype$,4) + +if authtype$="BASIC" then + auth$=java.util.Base64.getDecoder().decode(auth$) + user$=auth$(1,pos(":"=auth$)-1) + password$=auth$(pos(":"=auth$)+1) +fi + +if authtype$="BEARER" then + jwt$=auth$ +fi + +authenticated=0 + +rem changing the string encoding to the server's encoding +user$ = new String(user$,"utf-8").getBytes(info(1,2)) +password$ = new String(password$,"utf-8").getBytes(info(1,2)) + +authpgm$="authenticate.bbj" +if servletParams!.get("REST_AUTHPGM") <> null() then + authpgm$ = servletParams!.get("REST_AUTHPGM").toString() +endif + +call authpgm$ + +requestSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST_SEM") +responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") + +done_login: + + +timeout=60 +if servletParams!.containsKey("REST_TIMEOUT") then + timeout=num(servletParams!.get("REST_TIMEOUT").toString(),err=*next) +endif + +main_loop: + + CLEAR EXCEPT user$, ses$, requestSemaphore!, responseSemaphore!, ses!, authenticated, timeout, usecount, debug, authpgm$, servletParams! + + + seterr execerr + + dummy$=STBL("!SESSIONINFO",ses$+" - idle "+str(usecount)) + + rem requestSemaphore!.acquire() + if !requestSemaphore!.tryAcquire(1,timeout,java.util.concurrent.TimeUnit.SECONDS) then + rem a timeout has occurred while trying to acquire the semaphore + + rem remove the sessions' namespace values + namespace! = BBjAPI().getGlobalNamespace() + namespace!.removeValue(ses$+"_REQUEST") + namespace!.removeValue(ses$+"_RESPONSE") + namespace!.removeValue(ses$+"_REQUEST_SEM") + namespace!.removeValue(ses$+"_RESPONSE_SEM") + namespace!.removeValue(ses$+"_SERVLET_PARAMETERS") + + rem Calling the ON_CLEANUP label to perform possible user defined cleanup operations + call authpgm$+"::ON_CLEANUP",err=*next + + release + fi + + request!=BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST") + if (str(request!) = "PING") then + responseSemaphore!.release() + goto main_loop + fi + + call authpgm$+"::ON_REQUEST",err=*next + + if debug then System.out.println("REST: remote session running "+ses$) + dummy$=STBL("!SESSIONINFO",ses$+" - working "+str(request!)) + + response! = new HashMap() + params! = new HashMap() + + if request!.get("uri") = null() then + statuscode=404 + throw "Missing BC",12 + endif + + vec! = request!.get("preferredlocales") + if vec! <> null() and vec!.size() then + void$ = stbl("!LOCALE",vec!.getItem(0).getLanguage()) + endif + + uri$=request!.get("uri") + uri$=uri$(2) + if pos("/"=uri$)>0 + bc$=uri$(1,pos("/"=uri$)-1) + uri$=uri$(pos("/"=uri$)+1) + sel$=uri$ + else + bc$=uri$ + sel$="" + fi + + if cvs(bc$,3) = "" then + statuscode=404 + throw "Missing BC",12 + endif + + ns! = BBjAPI().getNamespace("RestBCAdapter_"+ses$,"BCList",1) + bcMap! = ns!.getValue("BCMAP",err=*next) + if bcMap! = null() then + bcMap! = new HashMap() + ns!.setValue("BCMAP",bcMap!) + endif + + if bc$="Token" then + rs! = new ResultSet() + token_rec! = new DataRow() + + rem token_rec! should be created in the customizable authpgm + call authpgm$+"::ISSUE_JWT" + + rs!.add(token_rec!) + response!.put("resultset",rs!) + response!.put("BC","Token") + response!.put("single",1) + goto send_response + fi + + + bc$=bc$+"BC" + if bcMap!.containsKey(bc$) then + bc! = bcMap!.get(bc$) + else + if servletParams!.containsKey("REST_PGM_PREFIX") then + prefix$ = servletParams!.get("REST_PGM_PREFIX") + void$ = stbl("REST_PGM_PREFIX", prefix$) + endif + + if servletParams!.containsKey("REST_PGM_SUFFIX") then + suffix$ = servletParams!.get("REST_PGM_SUFFIX") + void$ = stbl("REST_PGM_SUFFIX", suffix$) + endif + + bc! = null() + bc! = eval("new ::"+prefix$+bc$+suffix$+"::"+bc$+"()") + bcMap!.put(bc$,bc!) + endif + + rem Determine primary key field(s) + ar! = bc!.getAttributesRecord() + it! = ar!.getFieldNames().iterator() + while it!.hasNext() + f$=it!.next() + if ar!.getFieldAttribute(f$,"EDITABLE")="2" then + filterf$=filterf$+"/"+f$ + fi + wend + if filterf$ > "" then + filterf$ = filterf$(2) + endif + + + method$ = request!.get("method") + switch method$ + case "POST" + if request!.get("invoke") <> null() then + invoke$ = request!.get("invoke") + dr! = DataRow.fromJson(request!.get("body")) + if dr! <> null() and dr!.getColumnCount() > 0 then + variableNamePrefix! = new String(user$) + variableNamePrefix! = variableNamePrefix!.replaceAll("\W", "_") + + it! = dr!.getFieldNames().iterator() + while it!.hasNext() + f$ = it!.next() + execute variableNamePrefix!+f$ + "! = dr!.getFieldValue("""+f$+""")" + ex$ = ex$ + "," + variableNamePrefix! + f$ + "!" + wend + + ex$ = ex$(2) + endif + ex$ = "bc!."+invoke$+"("+ex$+")" + if request!.get("retvarname") <> null() then + retvar$ = request!.get("retvarname") + ex$ = "var! = " + ex$ + endif + execute ex$ + if retvar$ <> "" then + if var!.getClass().getName() = "com.basiscomponents.db.ResultSet" then + rs! = var! + else + rs! = new ResultSet() + if var!.getClass().getName() = "com.basiscomponents.db.DataRow" then + rs!.add(var!) + else + retdr! = new DataRow() + class$=var!.getClass().getName() + if class$ = "java.util.HashMap" then + retdr! = new DataRow(var!) + else + if class$ = "java.lang.String" or class$ = "java.lang.Boolean" or pos("BBjString"=class$) or pos("BBjNumber"=class$) or pos("BBjVector"=class$) then + retdr!.setFieldValue(retvar$,var!) + endif + endif + rs!.add(retdr!) + endif + endif + + response!.put("resultset",rs!) + else + response!.put("resultset","") + response!.put("statuscode","204") + endif + response!.put("request",str(request!)) + response!.put("BC",bc$) + response!.put("sel",sel$) + response!.put("primarykey",filterf$) + else + errorOccurred=0 + rs! = new ResultSet() + b1$=request!.get("body") + System.out.println(b1$) + if b1$>"" and b1$(1,1)<>"[" then + b1$="["+b1$+"]" + fi + System.out.println(b1$) + res! = ResultSet.fromJson(b1$) + k! = java.util.Arrays.asList(new String(filterf$).split("/")) + v! = java.util.Arrays.asList(new String(sel$).split("/",-1)) + it! = res!.iterator() + while it!.hasNext() + dr! = it!.next() + for i=0 to min(k!.size(),v!.size())-1 + if !dr!.contains(k!.get(i)) then + dr!.setFieldValue(k!.get(i),v!.get(i)) + endif + next i + + ok=0; dr! = bc!.write(dr!,err=*next); ok=1 + if !ok then + errorOccurred=1 + response!.put("statuscode","409") + response!.put("errorcode",str(err)) + response!.put("errormsg",errmes(-1)) + rem throw errmes(-1),err + else + rs!.addItem(dr!) + created! = dr!.getAttribute("CREATED") + if !errorOccurred then + if created! <> null() and created!.equals("true") then + response!.put("statuscode","201") + else + response!.put("statuscode","200") + endif + endif + endif + wend + + response!.put("resultset",rs!) + response!.put("request",str(request!)) + response!.put("BC",bc$) + response!.put("sel",sel$) + response!.put("primarykey",filterf$) + response!.put("single",rs!.size()>1) + endif + break + + case "DELETE" + case "PUT" + if method$ = "PUT" and cvs(sel$,4) = "IGNOREKEYS" then + dr! = DataRow.fromJson(request!.get("body")) + else + if sel$ = "" or filterf$ = "" then + throw "Missing resource ID",999 + endif + dr! = DataRow.fromJson(request!.get("body")) + k! = java.util.Arrays.asList(new String(filterf$).split("/")) + v! = java.util.Arrays.asList(new String(sel$).split("/",-1)) + if v!.size() < k!.size() then + throw "Not enough parameters for "+method$,999 + endif + for i=0 to k!.size()-1 + if !dr!.contains(k!.get(i)) then + dr!.setFieldValue(k!.get(i),v!.get(i)) + else + if dr!.getFieldAsString(k!.get(i)) <> v!.get(i) then + throw "Different resource ID in URL path and request body!",999 + endif + endif + next i + endif + + rs! = new ResultSet() + if method$ = "PUT" then + dr! = bc!.write(dr!,err=*next); ok=1 + if !ok then + response!.put("statuscode","409") + throw errmes(-1),err + else + created! = dr!.getAttribute("CREATED") + if created! <> null() and created!.equals("true") then + response!.put("statuscode","201") + else + response!.put("statuscode","200") + endif + response!.put("single",1) + endif + rs!.addItem(dr!) + else + bc!.remove(dr!) + response!.put("statuscode","204") + endif + + response!.put("resultset",rs!) + response!.put("request",str(request!)) + response!.put("BC",bc$) + response!.put("sel",sel$) + response!.put("primarykey",filterf$) + break + + case default + params! = request!.get("params",err=*next) + + if sel$ = "_meta" then + rs! = new ResultSet() + rs!.add(bc!.getAttributesRecord()) + response!.put("resultset",rs!) + response!.put("BC",bc$) + response!.put("sel",sel$) + response!.put("single",1) + + goto send_response + endif + + filter! = new DataRow() + response!.put("single",0) + k! = java.util.Arrays.asList(new String(filterf$).split("/")) + v! = java.util.Arrays.asList(new String(sel$).split("/",-1)) + + rem Add fields from URI + if sel$ > "" then + if filterf$>"" then + filter! = new DataRow() + for i=0 to min(v!.size(),k!.size())-1 + filter!.setFieldValue(k!.get(i),v!.get(i)) + next i + response!.put("linkfield",filterf$) + if k!.size() = v!.size() then + response!.put("single",1) + endif + else + statuscode=500 + throw "BC has no filter",999 + fi + fi + + rem iterate params to fill in [[var]]=value into param=[[var]] + if params! <> null() and params!.size() >0 then + it! = params!.keySet().iterator() + while it!.hasNext() + p$=it!.next() + v$=params!.get(p$) + if len(v$)>4 and v$(1,2)="[[" then + x!=params!.get(v$,err=*next) + params!.put(p$,str(x!)) + fi + wend + fi + + rem Add parameters + if params! <> null() and params!.size() >0 then + if filter! = null() then + filter! = new DataRow() + endif + + af! = ar! + if servletParams!.containsKey("USE_GET_ALLOWED_FILTER") and servletParams!.get("USE_GET_ALLOWED_FILTER").toString().equals("true") then + af! = bc!.getAllowedFilter(err=*next) + endif + + REM check the field case insensitive + REM but pass them with the correct casing to the filter + afnames! = af!.getFieldNames() + afnames_uc! = new BBjVector() + it! = afnames!.iterator() + while it!.hasNext() + afnames_uc!.addItem(cvs(str(it!.next()),4)) + wend + + it! = params!.keySet().iterator() + while it!.hasNext() + p$=it!.next() + pv$=params!.get(p$) + if (p$="_FULLTEXT" OR P$="_SEARCH") + filter!.setFieldValue("%SEARCH",pv$) + else + if (p$(1,1)<>"_" and pv$>"" and (afnames_uc!.contains(p$) OR bc$="DataSourceDataBC")) OR P$(1,1)="%" then + rem todo: all BCs should accept filters with fields that are not part of the attributes record + rem and still honor all other fields in the filter correctly, instead of giving no result + filter!.setFieldValue(afnames!.getItem(afnames_uc!.indexOf(p$)),pv$) + fi + fi + wend + fi + + if filter! <> null() then + bc!.setFilter(filter!) + fi + + scope!=params!.get("_SCOPE") + fieldsel!=params!.get("_FIELDSEL",err=*next) + + if fieldsel! <> null() AND scope! <> null() then + ok=0 + vec! = new BBjVector(java.util.Arrays.asList(fieldsel!.split(","))) + bc!.setFieldSelection(scope!, vec!,err=*next); ok=1 + if !ok then + if pos("no match for method"=cvs(errmes(-1),8)) then + fs! = new DataRow() + it! = vec!.iterator() + while it!.hasNext() + fs!.setFieldValue(it!.next(),"") + wend + bc!.setFieldSelection(scope!, fs!) + else + throw errmes(-1),err() + fi + fi + else + if fieldsel! <> null() + ok=0 + vec! = new BBjVector(java.util.Arrays.asList(fieldsel!.split(","))) + bc!.setFieldSelection(vec!,err=*next); ok=1 + if !ok then + if pos("no match for method"=cvs(errmes(-1),8)) then + fs! = new DataRow() + it! = vec!.iterator() + while it!.hasNext() + fs!.setFieldValue(it!.next(),"") + wend + bc!.setFieldSelection(fs!) + else + throw errmes(-1),err() + fi + fi + fi + if scope! <> null() then + bc!.setScope(str(scope!)) + endif + fi + + first% = 0 + last% = 0 + limitSearch = 0 + + if params! <> null() then + if params!.containsKey("_PAGE") or params!.containsKey("_PER_PAGE") then + page = 1 + page = num(params!.get("_PAGE"),err=*next) + if page < 1 then + throw "Invalid value for _PAGE parameter: must be greater than 0",999 + endif + perPage = num(params!.get("_PER_PAGE"),err=*next) + if perPage < 1 then + throw "Invalid value for _PER_PAGE parameter: must be greater than 0",999 + endif + + first% = (int(page)-1)*int(perPage) + last% = first%+int(perPage)-1 + limitSearch = 1 + endif + + if params!.containsKey("_OFFSET") then + offset = num(params!.get("_OFFSET")) + if offset < 0 then + throw "Invalid parameter for _OFFSET parameter: must be greater than or equals 0",999 + endif + limit = 20 + if params!.containsKey("_LIMIT") then + limit = num(params!.get("_LIMIT")) + endif + if limit < 1 then + throw "Invalid parameter for _LIMIT parameter: must be greater than 0",999 + endif + + first% = int(offset) + last% = first% + int(limit)-1 + limitSearch = 1 + endif + endif + + if len(sel$)>8 and sel$(1,8) = "_lookup/" then + rs! = new ResultSet() + + if filter!<>null() then + rs!.add(filter!) + fi + + field$=sel$(9) + System.out.println(field$) + + rs! = bc!.retrieveLookup(field$,filter!) + + response!.put("resultset",rs!) + response!.put("BC",bc$) + response!.put("single",0) + + goto send_response + endif + + if limitSearch then + rs! = bc!.retrieve(first%,last%) + else + rs! = bc!.retrieve() + endif + + + if params!.containsKey("_ATTACHMENT") then + if rs!.size() =1 then + rec! = rs!.getItem(0) + attachfield$ = params!.get("_ATTACHMENT") + + rs! = new ResultSet() + dr! = new DataRow() + filename$=rec!.getFieldAsString(attachfield$) + dr!.setFieldValue("FILEDELIVERY",filename$) + rs!.addItem(dr!) + + response!.put("resultset",rs!) + response!.put("BC",bc$) + response!.put("single",1) + response!.put("file",filename$) + + goto send_response + else + throw "file / record not found",11 + endif + endif + + response!.put("resultset",rs!) + response!.put("request",str(request!)) + response!.put("BC",bc$) + response!.put("sel",sel$) + response!.put("primarykey",filterf$) + break + swend + + + + send_response: + usecount=usecount+1 + usecount=usecount+1 + BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE",response!) + responseSemaphore!.release() + if debug then System.out.println("REST: remote session done "+ses$) + goto main_loop + + + execerr: + errorcode = err + errormsg$ = errmes(-1) + sw! = new java.io.StringWriter() + pw! = new java.io.PrintWriter(sw!) + BBjAPI().getLastBBjException().printStackTrace(pw!) + stacktrace$ = sw!.toString() + sw!.close() + pw!.close() + + if debug then + ch=unt + open (ch,mode="O_CREATE,O_APPEND",err=notrace)"bridgeerrlog.txt" + print (ch,err=*next)date(0:"%Yl-%Mz-%Dz %Hz:%mz:%sz") + print (ch,err=*next)"!ERROR "+str(err)+" "+stacktrace$ + print (ch,err=*next)errmes(-1) + close (ch,err=*next) + endif + + notrace: + +REM if errormsg$(1,4)="java" then +REM errormsg$=errormsg$(pos(":"=errormsg$)+2) +REM endif + + + error_out: + if statuscode=0 + statuscode=500 + fi + + response! = new HashMap() + + if authenticated>0 then + response!.put("statuscode",str(statuscode)) + response!.put("errorcode",errorcode) + response!.put("errormsg",errormsg$) + rem consider: stack trace only with DEBUG flag in config? + response!.put("stacktrace",stacktrace$) + else + response!.put("statuscode","401") + response!.put("errorcode",errorcode) + response!.put("errormsg",errormsg$) + rem no stack trace for unauthenticated users + fi + + BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE",response!) + responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") + responseSemaphore!.release() + + wait 10; rem FIN_WAIT + + namespace! = BBjAPI().getGlobalNamespace() + namespace!.removeValue(ses$+"_REQUEST",err=*next) + namespace!.removeValue(ses$+"_RESPONSE",err=*next) + namespace!.removeValue(ses$+"_REQUEST_SEM",err=*next) + namespace!.removeValue(ses$+"_RESPONSE_SEM",err=*next) + namespace!.removeValue(ses$+"_SERVLET_PARAMETERS",err=*next) + + rem Calling the ON_CLEANUP label to perform possible user defined cleanup operations + call authpgm$+"::ON_CLEANUP",err=*next +release + diff --git a/RestBridge.bbj b/RestBridge.bbj index 6b24931..e9d626d 100644 --- a/RestBridge.bbj +++ b/RestBridge.bbj @@ -1,1029 +1,1051 @@ -use java.util.HashMap -use com.basiscomponents.db.ResultSet - -class public RestBridge - - field public static BBjString LOGTPL$="ID:C(16*),METHOD:C(1*),START:N(1*),END:N(1*),DURATION:N(1*),ADDR:C(1*),URL:C(1*),URI:C(1*),QUERY:C(1*),HEADERS:C(1*),PARAMS:C(1*),STATUS:C(1*)" - - METHOD PUBLIC void service(BBjspServletContext context!) - - declare BBjspWebRequest request! - declare BBjspWebResponse response! - - request! = context!.getRequest() - response! = context!.getResponse() - - debug = num(stbl("DEBUG",err=*next),err=*next) - restbridge_opt_jsonmeta=1 - restbridge_opt_jsonmeta=int(num(stbl("RESTBRIDGE_OPT_JSONMETA",err=*next),err=*next)) - - restbridge_opt_enablegzip=1 - restbridge_opt_enablegzip=int(num(stbl("RESTBRIDGE_OPT_ENABLEGZIP",err=*next),err=*next)) - - if debug then - System.out.println("REST request --------------------------") - System.out.println("REST request METHOD "+request!.getMethod()) - System.out.println("REST request HEADER "+request!.getRequestURL()) - System.out.println("REST request end -----------------------") - fi - - param! = context!.getInitParameter("REST_REQUESTLOG") - if param! <> null() then - void$ = stbl("REST_REQUESTLOG",param!) - endif - - req_id$ = #logRequest(request!) - - if request!.getHeader("Origin") <> null() then - response!.setHeader("Access-Control-Allow-Origin",request!.getHeader("Origin")) - else - response!.setHeader("Access-Control-Allow-Origin","*") - endif - response!.setHeader("Access-Control-Allow-Credentials","true") - response!.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS") - response!.setHeader("Access-Control-Allow-Headers",request!.getHeader("Access-Control-Request-Headers")) - - - files! = request!.getFileUploads() - if (files! <> null()) then - sz = files!.size() - if sz then - declare BBjFileUpload uploadFile! - hm_resp! = new HashMap() - for i=0 to sz-1 - uploadFile! = cast(BBjFileUpload,files!.get(i)) - if (debug) then - System.out.println(str("Field:" + uploadFile!.getFieldName())) - System.out.println(str("- Name:"+ uploadFile!.getOriginalName())) - System.out.println(str("- Temp Name:"+ uploadFile!.getTempName())) - System.out.println(str("- Type:"+ uploadFile!.getContentType())) - System.out.println(str("- Size:")) - System.out.println(str(uploadFile!.getContentLength())) - System.out.println(str("------------------------")) - fi - - tmpname$= uploadFile!.getTempName() - newname!=tmpname$ - - oname$=str(uploadFile!.getOriginalName()) - if oname$<>"" And oname$<>"null" then - newname$ = newname!.replace("\","/") - newname$=newname$(1,pos("/"=newname$,1,pos("/"=newname$,1,0))) - newname$=newname$+str(System.currentTimeMillis())+"/" - mkdir newname$ - newname$=newname$+uploadFile!.getOriginalName() - rename tmpname$ TO newname$ - hm_resp!.put(uploadFile!.getFieldName(),newname$) - fi - next i - - resp$="" - it! = hm_resp!.keySet().iterator() - while it!.hasNext() - x$=it!.next() - x1$=hm_resp!.get(x$) - if resp$>"" then - resp$=resp$+"," - fi - resp$=resp$+""""+x$+""":"""+x1$+"""" - wend - - response!.getOutputStream().write("{"+resp$+"}") - response!.setStatus(200) - #logResponse(req_id$,response!) - methodret - fi - fi - - - req! = new HashMap() - - switch request!.getMethod() - case "GET" - method$ = "GET" - break - case "OPTIONS" - response!.setStatus(200) - response!.setHeader("Access-Control-Max-Age","600") - #logResponse(req_id$,response!) - methodret - break - case "DELETE" - case "PUT" - method$ = request!.getMethod() - rem Only JSON is supported for now - if request!.getContentType() <> null() AND cvs(request!.getContentType(),8) <> "application/json" then - s! = response!.getOutputStream() - response!.sendError(415,"Unsupported media type """+request!.getContentType()+"""") - endif - break - case "POST" - method$ = "POST" - if request!.getHeader("ExecuteBCMethod") <> null() then - req!.put("invoke",request!.getHeader("ExecuteBCMethod")) - if request!.getHeader("BCReturnVarName") <> null() then - req!.put("retvarname",request!.getHeader("BCReturnVarName")) - endif - endif - break - case default - response!.setContentType("text/plain") - s! = response!.getOutputStream() - response!.sendError(501,"Unsupported HTTP method """+request!.getMethod()+"""") - #logResponse(req_id$,response!) - methodret - break - swend - - - - token$=request!.getParameter("_TOKEN") - if token$>"" then - auth$="Basic "+token$ - else - auth$=str(request!.getHeader("authorization",err=*next)) - fi - - if cvs(request!.getParameter("grant_type"),4) = "PASSWORD" then - user$=request!.getParameter("username") - password$=request!.getParameter("password") - auth$="Basic "+java.util.Base64.getEncoder().encode(user$+":"+password$) - rem we're using the Basic authentication way to pass username and password to the Adapter - rem so that the "usual" login can be used to check the credentials - fi - - if auth$="null" or len(auth$)<8 OR (cvs(auth$(1,5),4)<>"BASIC" AND cvs(auth$(1,6),4)<>"BEARER")then - tmp$=str(request!.getHeader("X-REQUESTED-WITH",err=*next)) - if tmp$<> "XMLHttpRequest" then - response!.setHeader("WWW-Authenticate","Basic realm=""REST Web Service""") - fi - response!.sendError(401,"Authentication required") - #logResponse(req_id$,response!) - methodret - else - rem create uid$ as a unique representant of the user - rem this needs to contain the config file and the prefix! - uid$ = hta(java.security.MessageDigest.getInstance("MD5").digest(auth$+BBjAPI().getConfig().getCurrentCommandLineObject().getConfigFile()+pfx)) - fi - - - ses_list! = BBjAPI().getGlobalNamespace().getValue(uid$+"_list",err=*next) - if ses_list! = null() then - BBjAPI().getGlobalNamespace().setValue(uid$+"_list",new java.util.Stack()) - ses_list! = BBjAPI().getGlobalNamespace().getValue(uid$+"_list") - endif - - - while 1 - if (ses_list!.empty()) - servletParams! = #getServletParameters(context!) - if !servletParams!.containsKey("REST_WD") then - goto setup_err - endif - if !servletParams!.containsKey("REST_ADAPTERPGM") then - goto setup_err - endif - - ses$=#newSession(auth$,servletParams!) - break - else - ses$=ses_list!.pop() - if (#checkSession(ses$)) then - break - endif - endif - wend - - params! = new HashMap() - it! = request!.getParameterNames().iterator() - while it!.hasNext() - p$=it!.next() - params!.put(cvs(p$,4),request!.getParameter(p$)) - wend - - uri!=request!.getPathInfo() - if uri! <> null() and uri!.lastIndexOf("/_output_") <> -1 then - uri! = uri!.substring(0,uri!.lastIndexOf("/_output_")) - fi - - req!.put("method",method$) - req!.put("uri",uri!) - req!.put("params",params!) - req!.put("body",request!.getBody(err=*next)) - req!.put("preferredlocales",request!.getLocales()) - - if str(uri!)="/admin/requestlog" then - answer! = #getRequestLog(request!) - else - answer!=#invoke(ses$,req!) - fi - ses_list!.push(ses$) - - accept$=str(params!.get("_ACCEPT")) - if accept$="null" then - accept$=str(request!.getHeader("Accept")) - endif - if accept$="null" then - accept$=str(request!.getHeader("Content-Type")) - if accept$="null" then - accept$="text/html" - fi - fi - - accept_enc$=str(request!.getHeader("Accept-Encoding")) - if pos("gzip"=accept_enc$) > 0 then - do_gzip=1 - fi - - rem check for multi-value accept headers - atmp$=accept$ - alist!=new java.util.ArrayList() - alist!.addAll(java.util.Arrays.asList(new String(accept$).split(","))) - accept$ = "" - it! = alist!.iterator() - while it!.hasNext() - accept$ = it!.next() - if mask(accept$,"^debug|^text/html|^application/json|^text/csv|^text/plain|^application/xml|^*/*|^text/xml|^application/xls") then - if pos(";"=accept$) then - accept$=cvs(accept$(1,pos(";"=accept$)-1),3) - endif - break - endif - wend - - if accept$ = "" then - if debug then - System.out.println("REST: error 406 Unsupported content type requested in Accept header: "+atmp$) - fi - response!.sendError(406, "Unsupported content type requested in Accept header: "+atmp$) - #logResponse(req_id$,response!) - methodret - fi - - if debug then - System.out.println("REST Accept: "+accept$) - fi - - statuscode! = answer!.get("statuscode",err=*next) - errormsg! = answer!.get("errormsg",err=*next) - errorcode$ = str(answer!.get("errorcode")) - - blobdata$=str(params!.get("_BLOBDATA")) - System.out.println(params!) - System.out.println("BLOBDATA : "+blobdata$) - if blobdata$<>"null" then - rs! = answer!.get("resultset",err=*next) - if rs! = null() or rs!.size()<>1 or !rs!.getItem(0).getFieldNames().contains(blobdata$) then - goto blob_err - fi - - blobname$=str(params!.get("_BLOBNAME")) - if blobname$="null" then - blobname$="attachment.dat" - fi - if pos("."=blobname$)>0 then - ext$=blobname$(pos("."=blobname$,1,pos("."=blobname$,1,0))+1) - fi - d$=System.getProperty("basis.cacheDirectory")+"/_output_/" - mkdir d$,err=*next - - wr! = new java.io.StringWriter() - f! = java.io.File.createTempFile("output_", ext$, new java.io.File(d$)) - ch=unt - open (ch)str(f!.toPath()) - write (ch)rs!.getItem(0).getFieldAsString(blobdata$,err=blob_err) - close (ch) - - java.nio.file.Files.copy(f!.toPath(), response!.getOutputStream()) - f!.delete() - - response!.setContentType("application/"+ext$) - response!.setHeader("Content-Disposition","inline; filename="""+blobname$+"""") - - #logResponse(req_id$,response!) - methodret - - blob_err: - response!.getOutputStream().write("BLOB not found") - response!.setStatus(404) - #logResponse(req_id$,response!) - methodret - endif - - - if answer!.containsKey("file") then - file$ = answer!.get("file") - - tryagain: - - ch=unt - open (ch,err=filenotfund)file$ - close (ch) - System.out.println(file$) - fullPath$ = BBjAPI().getFileSystem().resolvePath(file$) - System.out.println(fullPath$) - - filename$=fullPath$ - if (pos("/"=filename$)>0) then - filename$=filename$(pos("/"=filename$,1,pos("/"=filename$,1,0))+1) - fi - if (pos("\"=filename$)>0) then - filename$=filename$(pos("\"=filename$,1,pos("\"=filename$,1,0))+1) - fi - - ext$="dat" - - if pos("."=filename$)>0 then - ext$=filename$(pos("."=filename$,1,pos("."=filename$,1,0))+1) - fi - - f! = new java.io.File(fullPath$) - java.nio.file.Files.copy(f!.toPath(), response!.getOutputStream()) - - response!.setContentType("application/"+ext$) - response!.setHeader("Content-Disposition","inline; filename="""+filename$+"""") - - goto done_file - - filenotfund: - System.out.println("not found") - - response!.getOutputStream().write("file not found!") - response!.setStatus(404) - - done_file: - #logResponse(req_id$,response!) - methodret - endif - - if answer!.containsKey("stacktrace") then - stacktrace$ = answer!.get("stacktrace") - endif - - if statuscode! <> null() and !mask(statuscode!,"20\d") then - rem System.out.println("REST: error "+str(statuscode!)+" - "+str(errormsg!)) - if pos("application/json"=accept$) > 0 then - response!.setStatus(num(statuscode!)) - response!.setContentType("application/json") - rs! = answer!.get("resultset",err=*next) - if method$ = "POST" and rs! <> null() and rs!.size() > 0 then - wr! = new java.io.StringWriter() - com.basiscomponents.db.ResultSetExporter.writeJSON(rs!,wr!,restbridge_opt_jsonmeta) - wr!.flush() - wr!.close() - response!.getOutputStream().write(wr!.toString()) - #logResponse(req_id$,response!) - else - e$ = "{""code"":"""+str(errorcode$)+""",""message"":"""+org.apache.commons.lang.StringEscapeUtils.escapeJavaScript(errormsg!)+"""," - if debug and statuscode! <> "401" then - e$ = e$ + """stacktrace"":"""+org.apache.commons.lang.StringEscapeUtils.escapeJavaScript(stacktrace$)+"""," - endif - e$ = e$ + """ses"":"""+ses$+"""}" - response!.getOutputStream().write(e$) - endif - else - response!.setContentType("text/html") - s! = response!.getOutputStream() - s!.write(""+str(errormsg!)+"") - s!.write("

HTTP ERROR "+str(statuscode!)+"

Problem accessing "+request!.getRequestURI()+". Reason:


!ERROR="+errorcode$+" "+str(errormsg!))
-                if debug and statuscode! <> "401" then
-                    s!.write($0a$+stacktrace$)
-                endif
-                s!.write("

") - response!.setStatus(num(statuscode!)) - endif - rem ToDo: add error output for xml and csv - #logResponse(req_id$,response!) - methodret - fi - - - rs! = answer!.get("resultset",err=*next) - pk! = answer!.get("primarykey",err=*next) - if rs! = null() - s! = response!.getOutputStream() - s!.write(str(answer!)) - response!.setHeader("Reason-Phrase",str(errormsg!)) - System.out.println("REST: error 500 "+str(errormsg!)) - response!.sendError(500,str(errormsg!)) - #logResponse(req_id$,response!) - methodret - fi - - if method$ = "PUT" or method$ = "DELETE" then - response!.setStatus(num(statuscode!)) - if method$ = "DELETE" then - #logResponse(req_id$,response!) - methodret - endif - endif - - if method$ = "POST" then - if statuscode! <> null() then - response!.setStatus(num(statuscode!)) - endif - if str(rs!) = "" then - #logResponse(req_id$,response!) - methodret - endif - endif - - rem Set the String value's character set based on the Accept-Charset value(if set) - if request!.getHeader("Accept-Charset") <> null() then - charset! = request!.getHeader("Accept-Charset") - #setCharacterSet(rs!, charset!) - endif - - if pos("text/html"=accept$) > 0 OR pos("*/*"=accept$) > 0 then - if rs!.size() then - custom_css! = rs!.get(0).getAttribute("CUSTOM_CSS") - endif - response!.setContentType("text/html; charset="+info(1,2)) - - - - if custom_css! <> null() then - resp_str!="" - else - resp_str!="" - endif - - - - single = num(answer!.get("single",err=*next),err=*next) - if single>0 AND rs!.size()=1 then - rem TODO move this pivoted display into the writer - rec! = rs!.get(0) - it! = rec!.getFieldNames().iterator() - resp_str! = resp_str!+"" - if request!.getPathInfo().endsWith("/_meta") then - attrSet! = new java.util.HashSet() - while it!.hasNext() - f$ = it!.next() - attrSet!.addAll(rs!.getColumnMetaData(f$).keySet()) - wend - it! = attrSet!.iterator() - resp_str! = resp_str!+"" - while it!.hasNext() - metaName$ = it!.next() - resp_str! = resp_str!+"" - wend - resp_str! = resp_str!+"" - it! = rec!.getFieldNames().iterator() - while it!.hasNext() - f$ = it!.next() - metaMap! = rs!.getColumnMetaData(f$) - resp_str! = resp_str!+"" - it2! = attrSet!.iterator() - while it2!.hasNext() - metaName$ = it2!.next() - if metaMap!.containsKey(metaName$) then - resp_str! = resp_str!+"" - else - resp_str! = resp_str!+"" - endif - wend - resp_str! = resp_str!+"" - wend - else - while it!.hasNext() - f$=it!.next() - resp_str! = resp_str!+"" - wend - endif - resp_str! = resp_str!+"
"+metaName$+"
"+f$+""+str(metaMap!.get(metaName$))+"
"+f$+""+rec!.getFieldAsString(f$)+"
" - else - if (pk! <> null() ) then - pk$=str(pk!) - links! = new java.util.HashMap() - link$ = request!.getContextPath()+request!.getServletPath()+request!.getPathInfo().replaceAll("(/[^/]+)/.*|$","$1") - - while pk$>"" - if pos("/"=pk$)>0 then - f$=pk$(1,pos("/"=pk$)-1) - pk$=pk$(pos("/"=pk$)+1) - else - f$=pk$ - pk$="" - fi - link$=link$+"/{"+f$+"}" - links!.put(f$,link$) - wend - else - links! = null() - fi - wr! = new java.io.StringWriter() - - com.basiscomponents.db.ResultSetExporter.writeHTML(rs!,wr!,links!) - wr!.flush() - wr!.close() - resp_str! = resp_str!+wr!.toString() - fi - resp_str! = resp_str!+"" - - s! = response!.getOutputStream() - if (restbridge_opt_enablegzip>0 and do_gzip) then - response!.setHeader("content-encoding","gzip") - out!= new java.io.ByteArrayOutputStream() - gzip! = new java.util.zip.GZIPOutputStream(out!) - gzip!.write(resp_str!.getBytes()) - gzip!.close() - compressedData! = out!.toByteArray() - s!.write(compressedData!) - else - s!.write(resp_str!) - fi - - response!.setStatus(200) - #logResponse(req_id$,response!) - methodret - else - if pos("text/xml"=accept$) >0 or pos("application/xml"=accept$) >0 then - if pos("application/xml"=accept$) >0 then - response!.setContentType("application/xml") - else - response!.setContentType("text/xml") - endif - wr! = new java.io.StringWriter() - com.basiscomponents.db.ResultSetExporter.writeXML(rs!,"root","entity",wr!) - - - wr!.flush() - wr!.close() - - s! = response!.getOutputStream() - if (restbridge_opt_enablegzip>0 and do_gzip) then - response!.setHeader("content-encoding","gzip") - out!= new java.io.ByteArrayOutputStream() - gzip! = new java.util.zip.GZIPOutputStream(out!) - gzip!.write(wr!.toString().getBytes()) - gzip!.close() - compressedData! = out!.toByteArray() - s!.write(compressedData!) - else - s!.write(wr!.toString()) - fi - - - #logResponse(req_id$,response!) - methodret - else - if pos("application/json"=accept$) > 0 then - response!.setContentType("application/json") - wr! = new java.io.StringWriter() - com.basiscomponents.db.ResultSetExporter.writeJSON(rs!,wr!,restbridge_opt_jsonmeta) - wr!.flush() - wr!.close() - - - s! = response!.getOutputStream() - if (restbridge_opt_enablegzip>0 and do_gzip) then - response!.setHeader("content-encoding","gzip") - out!= new java.io.ByteArrayOutputStream() - gzip! = new java.util.zip.GZIPOutputStream(out!) - gzip!.write(wr!.toString().getBytes()) - gzip!.close() - compressedData! = out!.toByteArray() - s!.write(compressedData!) - else - s!.write(wr!.toString()) - fi - - #logResponse(req_id$,response!) - methodret - else - if pos("text/csv"=accept$) >0 OR pos("text/plain"=accept$) >0 then - response!.setContentType("text/csv") - wr! = new java.io.StringWriter() - com.basiscomponents.db.ResultSetExporter.writeTXT(rs!,wr!) - - wr!.flush() - wr!.close() - - s! = response!.getOutputStream() - if (restbridge_opt_enablegzip>0 and do_gzip) then - response!.setHeader("content-encoding","gzip") - out!= new java.io.ByteArrayOutputStream() - gzip! = new java.util.zip.GZIPOutputStream(out!) - gzip!.write(wr!.toString().getBytes()) - gzip!.close() - compressedData! = out!.toByteArray() - s!.write(compressedData!) - else - s!.write(wr!.toString()) - fi - - #logResponse(req_id$,response!) - methodret - else - if pos("application/xls"=accept$) >0 then - response!.setContentType("application/xls") - if request!.getPathInfo().matches("/[^/]+/_output_/([^/]+)$") then - filename$ = request!.getPathInfo().replaceAll("/[^/]+/_output_/([^/]+)$","$1") - else - filename$ = request!.getPathInfo().replaceAll("/([^/]+).*","$1")+".xlsx" - endif - - response!.setHeader("content-disposition","attachment; filename="""+filename$+"""") - d$=System.getProperty("basis.cacheDirectory")+"/_output_/" - mkdir d$,err=*next - - wr! = new java.io.StringWriter() - f! = java.io.File.createTempFile("output_", "xls", new java.io.File(d$)) - com.basiscomponents.db.ResultSetExporter.writeXLSX(rs!, f!, 1) - - if debug then - System.out.println("REST: WRITING Excel to "+d$) - System.out.println(f!.getAbsolutePath()) - System.out.println(str(rs!.size())+" Records written") - fi - - java.nio.file.Files.copy(f!.toPath(), response!.getOutputStream()) - f!.delete() - #logResponse(req_id$,response!) - methodret - else - if pos("debug"=accept$)>0 then - response!.setContentType("text/plain") - s! = response!.getOutputStream() - s!.write(str(req!)) - s!.write($0a$) - - s!.write(str(answer!)) - s!.write($0a$) - s!.write("Debug: ") - s!.write($0a$) - s!.write("getAttributeNames:"+str(request!.getAttributeNames())) - s!.write($0a$) - s!.write("getHeaderNames:"+str(request!.getHeaderNames())) - s!.write($0a$) - s!.write("getParameterNames:"+str(request!.getParameterNames())) - s!.write($0a$) - s!.write("getMethod:"+str(request!.getMethod())) - s!.write($0a$) - s!.write("getRequestURL:"+str(request!.getRequestURL())) - s!.write($0a$) - s!.write("getRequestURI:"+str(request!.getRequestURI())) - s!.write($0a$) - s!.write("Accept:"+str(request!.getHeader("Accept"))) - s!.write($0a$) - #logResponse(req_id$,response!) - - methodret - else - if debug then - System.out.println("REST: error 406 Unsupported content type requested in Accept header: "+accept$) - fi - response!.sendError(406, "Unsupported content type requested in Accept header: "+accept$) - #logResponse(req_id$,response!) - methodret - fi - fi - fi - fi - fi - fi - #logResponse(req_id$,response!) - methodret - - - setup_err: - response!.setContentType("text/plain") - s! = response!.getOutputStream() - s!.write("stbl not set up correctly!") - System.out.println("REST: error 500 STBLs not set up correctly! Need REST_WD and REST_ADAPTERPGM in config.bbx" ) - response!.sendError(500,"STBLs not set up correctly! Need REST_WD and REST_ADAPTERPGM in config.bbx" ) - #logResponse(req_id$,response!) - METHODEND - - - method private Boolean checkSession(BBjString ses$) - - requestSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST_SEM",err=noSes) - responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") - - BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST","PING") - - requestSemaphore!.release() - if !responseSemaphore!.tryAcquire(1,1,java.util.concurrent.TimeUnit.SECONDS) then - BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST_SEM") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE_SEM") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_SERVLET_PARAMETERS") - methodret Boolean.FALSE - fi - - methodret Boolean.TRUE - - - noSes: - methodret Boolean.FALSE - - methodend - - rem /** - rem * This method converts the VARCHAR and LONGVARCHAR field values - rem * of the given ResultSet to the given charset. - rem * - rem * This method is used when the Accept-Charset header was specified - rem * so that the RestBridge returns the Data in the requested charset. - rem * - rem * @param resultSet! The ResultSet whose VARCHAR and LONGVARCHAR fields will be converted - rem * @param charset! The character Set to use for the VARCHAR fields - rem * - rem */ - method private static void setCharacterSet(ResultSet resultSet!, String charset!) - charsetIsSupported! = Boolean.FALSE - charsetIsSupported! = java.nio.charset.Charset.isSupported(charset!, err=*next) - if !charsetIsSupported! then - methodret - endif - - if resultSet! = null() or resultSet!.isEmpty() then - methodret - endif - - rsIterator! = resultSet!.iterator() - while rsIterator!.hasNext() - dataRow! = rsIterator!.next() - - fieldNames! = dataRow!.getFieldNames() - if fieldNames!.isEmpty() then - continue - endif - - fieldNameIterator! = fieldNames!.iterator() - while fieldNameIterator!.hasNext() - fieldName! = fieldNameIterator!.next() - - fieldType! = dataRow!.getFieldType(fieldName!) - if fieldType! = java.sql.Types.VARCHAR OR fieldType! = java.sql.Types.LONGVARCHAR then - dataRow!.setFieldValue(fieldName!, new String(dataRow!.getFieldAsString(fieldName!), charset!)) - endif - wend - wend - methodend - - method public static BBjString newSession(BBjString auth$, HashMap servletParams!) - - rem create a new session - while 1 - ses$=str(java.util.UUID.randomUUID()) - print "trying to create session "+ses$ - BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST",err=*next); continue - break - wend - System.out.println("REST: create "+ses$) - BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST","") - BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE","") - BBjAPI().getGlobalNamespace().setValue(ses$+"_SERVLET_PARAMETERS",servletParams!) - - requestSemaphore! = new java.util.concurrent.Semaphore(0) - responseSemaphore! = new java.util.concurrent.Semaphore(0) - BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST_SEM",requestSemaphore!) - BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE_SEM",responseSemaphore!) - - cp$=BBjAPI().getConfig().getCurrentCommandLineObject().getOriginalClasspathName() - if cp$>"" cp$=" -CP"+cp$ - wd$=" -WD"+servletParams!.get("REST_WD").toString() - pgm$=" "+servletParams!.get("REST_ADAPTERPGM").toString() - - - cfg$=" -c"+BBjAPI().getConfig().getCurrentCommandLineObject().getConfigFile() - t$=" -t"+BBjAPI().getConfig().getCurrentCommandLineObject().getTerminal() - if servletParams!.containsKey("REST_ADAPTERTERM") then - t$=" -t"+servletParams!.get("REST_ADAPTERTERM").toString() - endif - - scmd$="bbj -q "+t$+cfg$+cp$+wd$+pgm$+" - "+ses$+" "+auth$+" &" - System.out.println("REST: create "+scmd$) - a=scall(scmd$) - - methodret ses$ - - methodend - - - method public static HashMap invoke(BBjString ses$, HashMap instructions!) - - System.out.println("REST: invoke "+ses$) - requestSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST_SEM",err=noSes) - responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") - - BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST",instructions!) - requestSemaphore!.release() - rem here's the remote session active - - if !responseSemaphore!.tryAcquire(1,60,java.util.concurrent.TimeUnit.SECONDS) then - rem System.out.println("Bridge: Timeout waiting on "+ses$) - BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST_SEM") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE_SEM") - BBjAPI().getGlobalNamespace().removeValue(ses$+"_SERVLET_PARAMETERS") - System.out.println("REST: timeout on invoke "+ses$) - throw "Timeout occurred",0 - fi - - response! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE",err=*next) - - System.out.println("REST: invoke done "+ses$) - methodret response! - - - noSes: - throw "Session invalid",11 - - methodend - - method private BBjString logRequest(BBjspWebRequest request!) - - rlog$=stbl("REST_REQUESTLOG",err=skip) - System.out.println("Logging request into "+rlog$) - id$="" - - ch=unt - open (ch)rlog$ - - dim log$:#LOGTPL$ - - id$=str(System.currentTimeMillis())+"_"+str(dec(INFO(3,0)):"000000") - - log.id$=id$ - log.METHOD$=str(request!.getMethod()) - log.START=System.currentTimeMillis() - log.ADDR$=str(request!.getRemoteAddr()) - log.URI$=str(request!.getRequestURI()) - log.URL$=str(request!.getRequestURL()) - log.QUERY$=str(request!.getQueryString()) - - h! = request!.getHeaderNames() - it! =h!.iterator() - while it!.hasNext() - hn$=it!.next() - h$=h$+hn$+"="+str(request!.getHeader(hn$))+"," - wend - log.HEADERS$=h$ - - p! = request!.getParameterNames() - it! =p!.iterator() - while it!.hasNext() - pn$=it!.next() - p$=p$+pn$+"="+str(request!.getParameter(pn$))+"," - wend - log.PARAMS$=p$ - - write record (ch)log$ - - close (ch) - - skip: - - methodret id$ - - methodend - - method private void logResponse(BBjString id$, BBjspWebResponse response!) - - if id$="" then - methodret - fi - - rlog$=stbl("REST_REQUESTLOG",err=skip) - - ch=unt - open (ch,err=skip)rlog$ - - dim log$:#LOGTPL$ - log.id$=id$ - System.out.println("------------------------") - System.out.println(log.id$) - System.out.println("------------------------") - read record (ch,dom=skip,key=log.id$)log$ - log.END=System.currentTimeMillis() - log.DURATION = log.END-log.START - - log.STATUS$=STR(response!.getStatus()) - - write record (ch)log$ - - close (ch) - - skip: - - methodend - - method private HashMap getRequestLog(BBjspWebRequest request!) - - answer! = new HashMap() - rs! = new ResultSet() - - rlog$=stbl("REST_REQUESTLOG",err=skip) - - - ch=unt - open (ch,err=skip)rlog$ - - tpl$=#LOGTPL$ - dim log$:tpl$ - - while 1 - read record (ch,end=*break)log$ - dr! = com.basiscomponents.db.DataRow.fromTemplate(tpl$,log$) - rs!.addItem(dr!) - wend - - close (ch) - - answer!.put("statuscode","200") - answer!.put("resultset",rs!) - answer!.put("single",0) - methodret answer! - - skip: - answer!.put("statuscode","404") - answer!.put("errormsg","request logging not enabled") - answer!.put("single",0) - methodret answer! - - methodend - - method private static HashMap getServletParameters(BBjspServletContext context!) - - declare BBjString param! - declare HashMap map! - - map! = new HashMap() - - param! = context!.getInitParameter("REST_PGM_PREFIX") - if param! <> null() then - map!.put("REST_PGM_PREFIX",param!) - else - map!.put("REST_PGM_PREFIX",stbl("REST_PGM_PREFIX",err=*next)) - endif - - param! = context!.getInitParameter("REST_PGM_SUFFIX") - if param! <> null() then - map!.put("REST_PGM_SUFFIX",param!) - else - map!.put("REST_PGM_SUFFIX",stbl("REST_PGM_SUFFIX",err=*next)) - endif - - param! = context!.getInitParameter("REST_WD") - if param! <> null() then - map!.put("REST_WD",param!) - void$ = stbl("REST_WD",param!) - else - map!.put("REST_WD",stbl("REST_WD",err=*next)) - endif - - param! = context!.getInitParameter("REST_ADAPTERPGM") - if param! <> null() then - map!.put("REST_ADAPTERPGM",param!) - void$ = stbl("REST_ADAPTERPGM",param!) - else - map!.put("REST_ADAPTERPGM",stbl("REST_ADAPTERPGM",err=*next)) - endif - - param! = context!.getInitParameter("REST_ADAPTERTERM") - if param! <> null() then - map!.put("REST_ADAPTERTERM",param!) - else - map!.put("REST_ADAPTERTERM",stbl("REST_ADAPTERTERM",err=*next)) - endif - - param! = context!.getInitParameter("REST_TIMEOUT") - if param! <> null() then - map!.put("REST_TIMEOUT",param!) - else - map!.put("REST_TIMEOUT",stbl("REST_TIMEOUT",err=*next)) - endif - - param! = context!.getInitParameter("REST_AUTHPGM") - if param! <> null() then - map!.put("REST_AUTHPGM",param!) - else - map!.put("REST_AUTHPGM",stbl("REST_AUTHPGM",err=*next)) - endif - - param! = context!.getInitParameter("REST_REQUESTLOG") - if param! <> null() then - map!.put("REST_REQUESTLOG",param!) - void$ = stbl("REST_REQUESTLOG",param!) - else - map!.put("REST_REQUESTLOG",stbl("REST_REQUESTLOG",err=*next)) - endif - - param! = context!.getInitParameter("USE_GET_ALLOWED_FILTER") - if param! <> null() then - map!.put("USE_GET_ALLOWED_FILTER",param!) - else - map!.put("USE_GET_ALLOWED_FILTER",stbl("USE_GET_ALLOWED_FILTER",err=*next)) - endif - - methodret map! - - methodend - -classend +use java.util.HashMap +use com.basiscomponents.db.ResultSet + +class public RestBridge + + field public static BBjString LOGTPL$="ID:C(16*),METHOD:C(1*),START:N(1*),END:N(1*),DURATION:N(1*),ADDR:C(1*),URL:C(1*),URI:C(1*),QUERY:C(1*),HEADERS:C(1*),PARAMS:C(1*),STATUS:C(1*)" + + METHOD PUBLIC void service(BBjspServletContext context!) + + declare BBjspWebRequest request! + declare BBjspWebResponse response! + + request! = context!.getRequest() + response! = context!.getResponse() + + debug = num(stbl("DEBUG",err=*next),err=*next) + restbridge_opt_jsonmeta=1 + restbridge_opt_jsonmeta=int(num(stbl("RESTBRIDGE_OPT_JSONMETA",err=*next),err=*next)) + blnAddMetaToResult=restbridge_opt_jsonmeta + + restbridge_opt_enablegzip=1 + restbridge_opt_enablegzip=int(num(stbl("RESTBRIDGE_OPT_ENABLEGZIP",err=*next),err=*next)) + + if debug then + System.out.println("REST request --------------------------") + System.out.println("REST request METHOD "+request!.getMethod()) + System.out.println("REST request HEADER "+request!.getRequestURL()) + System.out.println("REST request end -----------------------") + fi + + param! = context!.getInitParameter("REST_REQUESTLOG") + if param! <> null() then + void$ = stbl("REST_REQUESTLOG",param!) + endif + + req_id$ = #logRequest(request!) + + if request!.getHeader("Origin") <> null() then + response!.setHeader("Access-Control-Allow-Origin",request!.getHeader("Origin")) + else + response!.setHeader("Access-Control-Allow-Origin","*") + endif + response!.setHeader("Access-Control-Allow-Credentials","true") + response!.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS") + response!.setHeader("Access-Control-Allow-Headers",request!.getHeader("Access-Control-Request-Headers")) + + + files! = request!.getFileUploads() + if (files! <> null()) then + sz = files!.size() + if sz then + declare BBjFileUpload uploadFile! + hm_resp! = new HashMap() + for i=0 to sz-1 + uploadFile! = cast(BBjFileUpload,files!.get(i)) + if (debug) then + System.out.println(str("Field:" + uploadFile!.getFieldName())) + System.out.println(str("- Name:"+ uploadFile!.getOriginalName())) + System.out.println(str("- Temp Name:"+ uploadFile!.getTempName())) + System.out.println(str("- Type:"+ uploadFile!.getContentType())) + System.out.println(str("- Size:")) + System.out.println(str(uploadFile!.getContentLength())) + System.out.println(str("------------------------")) + fi + + tmpname$= uploadFile!.getTempName() + newname!=tmpname$ + + oname$=str(uploadFile!.getOriginalName()) + if oname$<>"" And oname$<>"null" then + newname$ = newname!.replace("\","/") + newname$=newname$(1,pos("/"=newname$,1,pos("/"=newname$,1,0))) + newname$=newname$+str(System.currentTimeMillis())+"/" + mkdir newname$ + newname$=newname$+uploadFile!.getOriginalName() + rename tmpname$ TO newname$ + hm_resp!.put(uploadFile!.getFieldName(),newname$) + fi + next i + + resp$="" + it! = hm_resp!.keySet().iterator() + while it!.hasNext() + x$=it!.next() + x1$=hm_resp!.get(x$) + if resp$>"" then + resp$=resp$+"," + fi + resp$=resp$+""""+x$+""":"""+x1$+"""" + wend + + response!.getOutputStream().write("{"+resp$+"}") + response!.setStatus(200) + #logResponse(req_id$,response!) + methodret + fi + fi + + + req! = new HashMap() + + switch request!.getMethod() + case "GET" + method$ = "GET" + break + case "OPTIONS" + response!.setStatus(200) + response!.setHeader("Access-Control-Max-Age","600") + #logResponse(req_id$,response!) + methodret + break + case "DELETE" + case "PUT" + method$ = request!.getMethod() + rem Only JSON is supported for now + if request!.getContentType() <> null() AND cvs(request!.getContentType(),8) <> "application/json" then + s! = response!.getOutputStream() + response!.sendError(415,"Unsupported media type """+request!.getContentType()+"""") + endif + break + case "POST" + method$ = "POST" + if request!.getHeader("ExecuteBCMethod") <> null() then + req!.put("invoke",request!.getHeader("ExecuteBCMethod")) + if request!.getHeader("BCReturnVarName") <> null() then + req!.put("retvarname",request!.getHeader("BCReturnVarName")) + endif + endif + break + case default + response!.setContentType("text/plain") + s! = response!.getOutputStream() + response!.sendError(501,"Unsupported HTTP method """+request!.getMethod()+"""") + #logResponse(req_id$,response!) + methodret + break + swend + + + + token$=request!.getParameter("_TOKEN") + if token$>"" then + auth$="Basic "+token$ + else + auth$=str(request!.getHeader("authorization",err=*next)) + fi + + if cvs(request!.getParameter("grant_type"),4) = "PASSWORD" then + user$=request!.getParameter("username") + password$=request!.getParameter("password") + auth$="Basic "+java.util.Base64.getEncoder().encode(user$+":"+password$) + rem we're using the Basic authentication way to pass username and password to the Adapter + rem so that the "usual" login can be used to check the credentials + fi + + if auth$="null" or len(auth$)<8 OR (cvs(auth$(1,5),4)<>"BASIC" AND cvs(auth$(1,6),4)<>"BEARER")then + tmp$=str(request!.getHeader("X-REQUESTED-WITH",err=*next)) + if tmp$<> "XMLHttpRequest" then + response!.setHeader("WWW-Authenticate","Basic realm=""REST Web Service""") + fi + response!.sendError(401,"Authentication required") + #logResponse(req_id$,response!) + methodret + else + rem create uid$ as a unique representant of the user + rem this needs to contain the config file and the prefix! + uid$ = hta(java.security.MessageDigest.getInstance("MD5").digest(auth$+BBjAPI().getConfig().getCurrentCommandLineObject().getConfigFile()+pfx)) + fi + + + ses_list! = BBjAPI().getGlobalNamespace().getValue(uid$+"_list",err=*next) + if ses_list! = null() then + BBjAPI().getGlobalNamespace().setValue(uid$+"_list",new java.util.Stack()) + ses_list! = BBjAPI().getGlobalNamespace().getValue(uid$+"_list") + endif + + + while 1 + if (ses_list!.empty()) + servletParams! = #getServletParameters(context!) + if !servletParams!.containsKey("REST_WD") then + goto setup_err + endif + if !servletParams!.containsKey("REST_ADAPTERPGM") then + goto setup_err + endif + + ses$=#newSession(auth$,servletParams!) + break + else + ses$=ses_list!.pop() + if (#checkSession(ses$)) then + break + endif + endif + wend + + params! = new HashMap() + it! = request!.getParameterNames().iterator() + while it!.hasNext() + p$=it!.next() + params!.put(cvs(p$,4),request!.getParameter(p$)) + wend + + uri!=request!.getPathInfo() + if uri! <> null() and uri!.lastIndexOf("/_output_") <> -1 then + uri! = uri!.substring(0,uri!.lastIndexOf("/_output_")) + fi + + req!.put("method",method$) + req!.put("uri",uri!) + req!.put("params",params!) + req!.put("body",request!.getBody(err=*next)) + req!.put("preferredlocales",request!.getLocales()) + + if str(uri!)="/admin/requestlog" then + answer! = #getRequestLog(request!) + else + answer!=#invoke(ses$,req!) + fi + ses_list!.push(ses$) + + accept$=str(params!.get("_ACCEPT")) + if accept$="null" then + accept$=str(request!.getHeader("Accept")) + endif + if accept$="null" then + accept$=str(request!.getHeader("Content-Type")) + if accept$="null" then + accept$="text/html" + fi + fi + + accept_enc$=str(request!.getHeader("Accept-Encoding")) + if pos("gzip"=accept_enc$) > 0 then + do_gzip=1 + fi + + rem check for multi-value accept headers + atmp$=accept$ + alist!=new java.util.ArrayList() + alist!.addAll(java.util.Arrays.asList(new String(accept$).split(","))) + accept$ = "" + it! = alist!.iterator() + while it!.hasNext() + accept$ = it!.next() + if mask(accept$,"^debug|^text/html|^application/json|^text/csv|^text/plain|^application/xml|^*/*|^text/xml|^application/xls") then + if pos(";"=accept$) then + accept$=cvs(accept$(1,pos(";"=accept$)-1),3) + endif + break + endif + wend + + if accept$ = "" then + if debug then + System.out.println("REST: error 406 Unsupported content type requested in Accept header: "+atmp$) + fi + response!.sendError(406, "Unsupported content type requested in Accept header: "+atmp$) + #logResponse(req_id$,response!) + methodret + fi + + if debug then + System.out.println("REST Accept: "+accept$) + fi + + statuscode! = answer!.get("statuscode",err=*next) + errormsg! = answer!.get("errormsg",err=*next) + errorcode$ = str(answer!.get("errorcode")) + + blobdata$=str(params!.get("_BLOBDATA")) + System.out.println(params!) + System.out.println("BLOBDATA : "+blobdata$) + if blobdata$<>"null" then + rs! = answer!.get("resultset",err=*next) + if rs! = null() or rs!.size()<>1 or !rs!.getItem(0).getFieldNames().contains(blobdata$) then + goto blob_err + fi + + blobname$=str(params!.get("_BLOBNAME")) + if blobname$="null" then + blobname$="attachment.dat" + fi + if pos("."=blobname$)>0 then + ext$=blobname$(pos("."=blobname$,1,pos("."=blobname$,1,0))+1) + fi + d$=System.getProperty("basis.cacheDirectory")+"/_output_/" + mkdir d$,err=*next + + wr! = new java.io.StringWriter() + f! = java.io.File.createTempFile("output_", ext$, new java.io.File(d$)) + ch=unt + open (ch)str(f!.toPath()) + write (ch)rs!.getItem(0).getFieldAsString(blobdata$,err=blob_err) + close (ch) + + java.nio.file.Files.copy(f!.toPath(), response!.getOutputStream()) + f!.delete() + + response!.setContentType("application/"+ext$) + response!.setHeader("Content-Disposition","inline; filename="""+blobname$+"""") + + #logResponse(req_id$,response!) + methodret + + blob_err: + response!.getOutputStream().write("BLOB not found") + response!.setStatus(404) + #logResponse(req_id$,response!) + methodret + endif + + + if answer!.containsKey("file") then + file$ = answer!.get("file") + + tryagain: + + ch=unt + open (ch,err=filenotfund)file$ + close (ch) + System.out.println(file$) + fullPath$ = BBjAPI().getFileSystem().resolvePath(file$) + System.out.println(fullPath$) + + filename$=fullPath$ + if (pos("/"=filename$)>0) then + filename$=filename$(pos("/"=filename$,1,pos("/"=filename$,1,0))+1) + fi + if (pos("\"=filename$)>0) then + filename$=filename$(pos("\"=filename$,1,pos("\"=filename$,1,0))+1) + fi + + ext$="dat" + + if pos("."=filename$)>0 then + ext$=filename$(pos("."=filename$,1,pos("."=filename$,1,0))+1) + fi + + f! = new java.io.File(fullPath$) + java.nio.file.Files.copy(f!.toPath(), response!.getOutputStream()) + + response!.setContentType("application/"+ext$) + response!.setHeader("Content-Disposition","inline; filename="""+filename$+"""") + + goto done_file + + filenotfund: + System.out.println("not found") + + response!.getOutputStream().write("file not found!") + response!.setStatus(404) + + done_file: + #logResponse(req_id$,response!) + methodret + endif + + if answer!.containsKey("stacktrace") then + stacktrace$ = answer!.get("stacktrace") + endif + + if statuscode! <> null() and !mask(statuscode!,"20\d") then + rem System.out.println("REST: error "+str(statuscode!)+" - "+str(errormsg!)) + if pos("application/json"=accept$) > 0 then + response!.setStatus(num(statuscode!)) + response!.setContentType("application/json") + rs! = answer!.get("resultset",err=*next) + if method$ = "POST" and rs! <> null() and rs!.size() > 0 then + wr! = new java.io.StringWriter() + blnAddMetaToResult=restbridge_opt_jsonmeta + if request!.getHeader("nometa", err=*next)="true" then + blnAddMetaToResult=0 + endif + com.basiscomponents.db.ResultSetExporter.writeJSON(rs!,wr!,blnAddMetaToResult) + wr!.flush() + wr!.close() + response!.getOutputStream().write(wr!.toString()) + #logResponse(req_id$,response!) + else + if request!.getHeader("verbose", err=*next)="true" then + e$ = "[{""status"":""error"",""value"":[],""count"":""0"", ""error"": {""code"":"""+str(errorcode$)+""",""message"":"""+org.apache.commons.lang.StringEscapeUtils.escapeJavaScript(errormsg!)+"""," + if debug and statuscode! <> "401" then + e$ = e$ + """stacktrace"":"""+org.apache.commons.lang.StringEscapeUtils.escapeJavaScript(stacktrace$)+"""," + endif + e$ = e$ + """ses"":"""+ses$+"""}}]" + else + e$ = "{""code"":"""+str(errorcode$)+""",""message"":"""+org.apache.commons.lang.StringEscapeUtils.escapeJavaScript(errormsg!)+"""," + if debug and statuscode! <> "401" then + e$ = e$ + """stacktrace"":"""+org.apache.commons.lang.StringEscapeUtils.escapeJavaScript(stacktrace$)+"""," + endif + e$ = e$ + """ses"":"""+ses$+"""}" + endif + response!.getOutputStream().write(e$) + endif + else + response!.setContentType("text/html") + s! = response!.getOutputStream() + s!.write(""+str(errormsg!)+"") + s!.write("

HTTP ERROR "+str(statuscode!)+"

Problem accessing "+request!.getRequestURI()+". Reason:


!ERROR="+errorcode$+" "+str(errormsg!))
+                if debug and statuscode! <> "401" then
+                    s!.write($0a$+stacktrace$)
+                endif
+                s!.write("

") + response!.setStatus(num(statuscode!)) + endif + rem ToDo: add error output for xml and csv + #logResponse(req_id$,response!) + methodret + fi + + + rs! = answer!.get("resultset",err=*next) + pk! = answer!.get("primarykey",err=*next) + if rs! = null() + s! = response!.getOutputStream() + s!.write(str(answer!)) + response!.setHeader("Reason-Phrase",str(errormsg!)) + System.out.println("REST: error 500 "+str(errormsg!)) + response!.sendError(500,str(errormsg!)) + #logResponse(req_id$,response!) + methodret + fi + + if method$ = "PUT" or method$ = "DELETE" then + response!.setStatus(num(statuscode!)) + if method$ = "DELETE" then + #logResponse(req_id$,response!) + methodret + endif + endif + + if method$ = "POST" then + if statuscode! <> null() then + response!.setStatus(num(statuscode!)) + endif + if str(rs!) = "" then + #logResponse(req_id$,response!) + methodret + endif + endif + + rem Set the String value's character set based on the Accept-Charset value(if set) + if request!.getHeader("Accept-Charset") <> null() then + charset! = request!.getHeader("Accept-Charset") + #setCharacterSet(rs!, charset!) + endif + + if pos("text/html"=accept$) > 0 OR pos("*/*"=accept$) > 0 then + if rs!.size() then + custom_css! = rs!.get(0).getAttribute("CUSTOM_CSS") + endif + response!.setContentType("text/html; charset="+info(1,2)) + + + + if custom_css! <> null() then + resp_str!="" + else + resp_str!="" + endif + + + + single = num(answer!.get("single",err=*next),err=*next) + if single>0 AND rs!.size()=1 then + rem TODO move this pivoted display into the writer + rec! = rs!.get(0) + it! = rec!.getFieldNames().iterator() + resp_str! = resp_str!+"" + if request!.getPathInfo().endsWith("/_meta") then + attrSet! = new java.util.HashSet() + while it!.hasNext() + f$ = it!.next() + attrSet!.addAll(rs!.getColumnMetaData(f$).keySet()) + wend + it! = attrSet!.iterator() + resp_str! = resp_str!+"" + while it!.hasNext() + metaName$ = it!.next() + resp_str! = resp_str!+"" + wend + resp_str! = resp_str!+"" + it! = rec!.getFieldNames().iterator() + while it!.hasNext() + f$ = it!.next() + metaMap! = rs!.getColumnMetaData(f$) + resp_str! = resp_str!+"" + it2! = attrSet!.iterator() + while it2!.hasNext() + metaName$ = it2!.next() + if metaMap!.containsKey(metaName$) then + resp_str! = resp_str!+"" + else + resp_str! = resp_str!+"" + endif + wend + resp_str! = resp_str!+"" + wend + else + while it!.hasNext() + f$=it!.next() + resp_str! = resp_str!+"" + wend + endif + resp_str! = resp_str!+"
"+metaName$+"
"+f$+""+str(metaMap!.get(metaName$))+"
"+f$+""+rec!.getFieldAsString(f$)+"
" + else + if (pk! <> null() ) then + pk$=str(pk!) + links! = new java.util.HashMap() + link$ = request!.getContextPath()+request!.getServletPath()+request!.getPathInfo().replaceAll("(/[^/]+)/.*|$","$1") + + while pk$>"" + if pos("/"=pk$)>0 then + f$=pk$(1,pos("/"=pk$)-1) + pk$=pk$(pos("/"=pk$)+1) + else + f$=pk$ + pk$="" + fi + link$=link$+"/{"+f$+"}" + links!.put(f$,link$) + wend + else + links! = null() + fi + wr! = new java.io.StringWriter() + + com.basiscomponents.db.ResultSetExporter.writeHTML(rs!,wr!,links!) + wr!.flush() + wr!.close() + resp_str! = resp_str!+wr!.toString() + fi + resp_str! = resp_str!+"" + + s! = response!.getOutputStream() + if (restbridge_opt_enablegzip>0 and do_gzip) then + response!.setHeader("content-encoding","gzip") + out!= new java.io.ByteArrayOutputStream() + gzip! = new java.util.zip.GZIPOutputStream(out!) + gzip!.write(resp_str!.getBytes()) + gzip!.close() + compressedData! = out!.toByteArray() + s!.write(compressedData!) + else + s!.write(resp_str!) + fi + + response!.setStatus(200) + #logResponse(req_id$,response!) + methodret + else + if pos("text/xml"=accept$) >0 or pos("application/xml"=accept$) >0 then + if pos("application/xml"=accept$) >0 then + response!.setContentType("application/xml") + else + response!.setContentType("text/xml") + endif + wr! = new java.io.StringWriter() + com.basiscomponents.db.ResultSetExporter.writeXML(rs!,"root","entity",wr!) + + + wr!.flush() + wr!.close() + + s! = response!.getOutputStream() + if (restbridge_opt_enablegzip>0 and do_gzip) then + response!.setHeader("content-encoding","gzip") + out!= new java.io.ByteArrayOutputStream() + gzip! = new java.util.zip.GZIPOutputStream(out!) + gzip!.write(wr!.toString().getBytes()) + gzip!.close() + compressedData! = out!.toByteArray() + s!.write(compressedData!) + else + s!.write(wr!.toString()) + fi + + + #logResponse(req_id$,response!) + methodret + else + if pos("application/json"=accept$) > 0 then + response!.setContentType("application/json") + wr! = new java.io.StringWriter() + blnAddMetaToResult=restbridge_opt_jsonmeta + if request!.getHeader("nometa", err=*next)="true" then + blnAddMetaToResult=0 + endif + com.basiscomponents.db.ResultSetExporter.writeJSON(rs!,wr!,blnAddMetaToResult) + outStr!=wr!.toString() + if request!.getHeader("verbose", err=*next)="true" then + rsCount$ = STR(rs!.size()) + outStr! = "[{""status"":""OK"",""value"":"+wr!.toString()+",""count"":"""+rsCount$+""", ""error"": {}}]" + endif + wr!.flush() + wr!.close() + + + s! = response!.getOutputStream() + if (restbridge_opt_enablegzip>0 and do_gzip) then + response!.setHeader("content-encoding","gzip") + out!= new java.io.ByteArrayOutputStream() + gzip! = new java.util.zip.GZIPOutputStream(out!) + gzip!.write(outStr!.toString().getBytes()) + gzip!.close() + compressedData! = out!.toByteArray() + s!.write(compressedData!) + else + s!.write(outStr!.toString()) + fi + + #logResponse(req_id$,response!) + methodret + else + if pos("text/csv"=accept$) >0 OR pos("text/plain"=accept$) >0 then + response!.setContentType("text/csv") + wr! = new java.io.StringWriter() + com.basiscomponents.db.ResultSetExporter.writeTXT(rs!,wr!) + wr!.toString() + wr!.flush() + wr!.close() + + s! = response!.getOutputStream() + if (restbridge_opt_enablegzip>0 and do_gzip) then + response!.setHeader("content-encoding","gzip") + out!= new java.io.ByteArrayOutputStream() + gzip! = new java.util.zip.GZIPOutputStream(out!) + gzip!.write(wr!.toString().getBytes()) + gzip!.close() + compressedData! = out!.toByteArray() + s!.write(compressedData!) + else + s!.write(outputString$) + fi + + #logResponse(req_id$,response!) + methodret + else + if pos("application/xls"=accept$) >0 then + response!.setContentType("application/xls") + if request!.getPathInfo().matches("/[^/]+/_output_/([^/]+)$") then + filename$ = request!.getPathInfo().replaceAll("/[^/]+/_output_/([^/]+)$","$1") + else + filename$ = request!.getPathInfo().replaceAll("/([^/]+).*","$1")+".xlsx" + endif + + response!.setHeader("content-disposition","attachment; filename="""+filename$+"""") + d$=System.getProperty("basis.cacheDirectory")+"/_output_/" + mkdir d$,err=*next + + wr! = new java.io.StringWriter() + f! = java.io.File.createTempFile("output_", "xls", new java.io.File(d$)) + com.basiscomponents.db.ResultSetExporter.writeXLSX(rs!, f!, 1) + + if debug then + System.out.println("REST: WRITING Excel to "+d$) + System.out.println(f!.getAbsolutePath()) + System.out.println(str(rs!.size())+" Records written") + fi + + java.nio.file.Files.copy(f!.toPath(), response!.getOutputStream()) + f!.delete() + #logResponse(req_id$,response!) + methodret + else + if pos("debug"=accept$)>0 then + response!.setContentType("text/plain") + s! = response!.getOutputStream() + s!.write(str(req!)) + s!.write($0a$) + + s!.write(str(answer!)) + s!.write($0a$) + s!.write("Debug: ") + s!.write($0a$) + s!.write("getAttributeNames:"+str(request!.getAttributeNames())) + s!.write($0a$) + s!.write("getHeaderNames:"+str(request!.getHeaderNames())) + s!.write($0a$) + s!.write("getParameterNames:"+str(request!.getParameterNames())) + s!.write($0a$) + s!.write("getMethod:"+str(request!.getMethod())) + s!.write($0a$) + s!.write("getRequestURL:"+str(request!.getRequestURL())) + s!.write($0a$) + s!.write("getRequestURI:"+str(request!.getRequestURI())) + s!.write($0a$) + s!.write("Accept:"+str(request!.getHeader("Accept"))) + s!.write($0a$) + #logResponse(req_id$,response!) + + methodret + else + if debug then + System.out.println("REST: error 406 Unsupported content type requested in Accept header: "+accept$) + fi + response!.sendError(406, "Unsupported content type requested in Accept header: "+accept$) + #logResponse(req_id$,response!) + methodret + fi + fi + fi + fi + fi + fi + #logResponse(req_id$,response!) + methodret + + + setup_err: + response!.setContentType("text/plain") + s! = response!.getOutputStream() + s!.write("stbl not set up correctly!") + System.out.println("REST: error 500 STBLs not set up correctly! Need REST_WD and REST_ADAPTERPGM in config.bbx" ) + response!.sendError(500,"STBLs not set up correctly! Need REST_WD and REST_ADAPTERPGM in config.bbx" ) + #logResponse(req_id$,response!) + METHODEND + + + method private Boolean checkSession(BBjString ses$) + + requestSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST_SEM",err=noSes) + responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") + + BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST","PING") + + requestSemaphore!.release() + if !responseSemaphore!.tryAcquire(1,1,java.util.concurrent.TimeUnit.SECONDS) then + BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST_SEM") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE_SEM") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_SERVLET_PARAMETERS") + methodret Boolean.FALSE + fi + + methodret Boolean.TRUE + + + noSes: + methodret Boolean.FALSE + + methodend + + rem /** + rem * This method converts the VARCHAR and LONGVARCHAR field values + rem * of the given ResultSet to the given charset. + rem * + rem * This method is used when the Accept-Charset header was specified + rem * so that the RestBridge returns the Data in the requested charset. + rem * + rem * @param resultSet! The ResultSet whose VARCHAR and LONGVARCHAR fields will be converted + rem * @param charset! The character Set to use for the VARCHAR fields + rem * + rem */ + method private static void setCharacterSet(ResultSet resultSet!, String charset!) + charsetIsSupported! = Boolean.FALSE + charsetIsSupported! = java.nio.charset.Charset.isSupported(charset!, err=*next) + if !charsetIsSupported! then + methodret + endif + + if resultSet! = null() or resultSet!.isEmpty() then + methodret + endif + + rsIterator! = resultSet!.iterator() + while rsIterator!.hasNext() + dataRow! = rsIterator!.next() + + fieldNames! = dataRow!.getFieldNames() + if fieldNames!.isEmpty() then + continue + endif + + fieldNameIterator! = fieldNames!.iterator() + while fieldNameIterator!.hasNext() + fieldName! = fieldNameIterator!.next() + + fieldType! = dataRow!.getFieldType(fieldName!) + if fieldType! = java.sql.Types.VARCHAR OR fieldType! = java.sql.Types.LONGVARCHAR then + dataRow!.setFieldValue(fieldName!, new String(dataRow!.getFieldAsString(fieldName!), charset!)) + endif + wend + wend + methodend + + method public static BBjString newSession(BBjString auth$, HashMap servletParams!) + + rem create a new session + while 1 + ses$=str(java.util.UUID.randomUUID()) + print "trying to create session "+ses$ + BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST",err=*next); continue + break + wend + System.out.println("REST: create "+ses$) + BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST","") + BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE","") + BBjAPI().getGlobalNamespace().setValue(ses$+"_SERVLET_PARAMETERS",servletParams!) + + requestSemaphore! = new java.util.concurrent.Semaphore(0) + responseSemaphore! = new java.util.concurrent.Semaphore(0) + BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST_SEM",requestSemaphore!) + BBjAPI().getGlobalNamespace().setValue(ses$+"_RESPONSE_SEM",responseSemaphore!) + + cp$=BBjAPI().getConfig().getCurrentCommandLineObject().getOriginalClasspathName() + if cp$>"" cp$=" -CP"+cp$ + wd$=" -WD"+servletParams!.get("REST_WD").toString() + pgm$=" "+servletParams!.get("REST_ADAPTERPGM").toString() + + + cfg$=" -c"+BBjAPI().getConfig().getCurrentCommandLineObject().getConfigFile() + t$=" -t"+BBjAPI().getConfig().getCurrentCommandLineObject().getTerminal() + if servletParams!.containsKey("REST_ADAPTERTERM") then + t$=" -t"+servletParams!.get("REST_ADAPTERTERM").toString() + endif + + scmd$="bbj -q "+t$+cfg$+cp$+wd$+pgm$+" - "+ses$+" "+auth$+" &" + System.out.println("REST: create "+scmd$) + a=scall(scmd$) + + methodret ses$ + + methodend + + + method public static HashMap invoke(BBjString ses$, HashMap instructions!) + + System.out.println("REST: invoke "+ses$) + requestSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_REQUEST_SEM",err=noSes) + responseSemaphore! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE_SEM") + + BBjAPI().getGlobalNamespace().setValue(ses$+"_REQUEST",instructions!) + requestSemaphore!.release() + rem here's the remote session active + + if !responseSemaphore!.tryAcquire(1,60,java.util.concurrent.TimeUnit.SECONDS) then + rem System.out.println("Bridge: Timeout waiting on "+ses$) + BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_REQUEST_SEM") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_RESPONSE_SEM") + BBjAPI().getGlobalNamespace().removeValue(ses$+"_SERVLET_PARAMETERS") + System.out.println("REST: timeout on invoke "+ses$) + throw "Timeout occurred",0 + fi + + response! = BBjAPI().getGlobalNamespace().getValue(ses$+"_RESPONSE",err=*next) + + System.out.println("REST: invoke done "+ses$) + methodret response! + + + noSes: + throw "Session invalid",11 + + methodend + + method private BBjString logRequest(BBjspWebRequest request!) + + rlog$=stbl("REST_REQUESTLOG",err=skip) + System.out.println("Logging request into "+rlog$) + id$="" + + ch=unt + open (ch)rlog$ + + dim log$:#LOGTPL$ + + id$=str(System.currentTimeMillis())+"_"+str(dec(INFO(3,0)):"000000") + + log.id$=id$ + log.METHOD$=str(request!.getMethod()) + log.START=System.currentTimeMillis() + log.ADDR$=str(request!.getRemoteAddr()) + log.URI$=str(request!.getRequestURI()) + log.URL$=str(request!.getRequestURL()) + log.QUERY$=str(request!.getQueryString()) + + h! = request!.getHeaderNames() + it! =h!.iterator() + while it!.hasNext() + hn$=it!.next() + h$=h$+hn$+"="+str(request!.getHeader(hn$))+"," + wend + log.HEADERS$=h$ + + p! = request!.getParameterNames() + it! =p!.iterator() + while it!.hasNext() + pn$=it!.next() + p$=p$+pn$+"="+str(request!.getParameter(pn$))+"," + wend + log.PARAMS$=p$ + + write record (ch)log$ + + close (ch) + + skip: + + methodret id$ + + methodend + + method private void logResponse(BBjString id$, BBjspWebResponse response!) + + if id$="" then + methodret + fi + + rlog$=stbl("REST_REQUESTLOG",err=skip) + + ch=unt + open (ch,err=skip)rlog$ + + dim log$:#LOGTPL$ + log.id$=id$ + System.out.println("------------------------") + System.out.println(log.id$) + System.out.println("------------------------") + read record (ch,dom=skip,key=log.id$)log$ + log.END=System.currentTimeMillis() + log.DURATION = log.END-log.START + + log.STATUS$=STR(response!.getStatus()) + + write record (ch)log$ + + close (ch) + + skip: + + methodend + + method private HashMap getRequestLog(BBjspWebRequest request!) + + answer! = new HashMap() + rs! = new ResultSet() + + rlog$=stbl("REST_REQUESTLOG",err=skip) + + + ch=unt + open (ch,err=skip)rlog$ + + tpl$=#LOGTPL$ + dim log$:tpl$ + + while 1 + read record (ch,end=*break)log$ + dr! = com.basiscomponents.db.DataRow.fromTemplate(tpl$,log$) + rs!.addItem(dr!) + wend + + close (ch) + + answer!.put("statuscode","200") + answer!.put("resultset",rs!) + answer!.put("single",0) + methodret answer! + + skip: + answer!.put("statuscode","404") + answer!.put("errormsg","request logging not enabled") + answer!.put("single",0) + methodret answer! + + methodend + + method private static HashMap getServletParameters(BBjspServletContext context!) + + declare BBjString param! + declare HashMap map! + + map! = new HashMap() + + param! = context!.getInitParameter("REST_PGM_PREFIX") + if param! <> null() then + map!.put("REST_PGM_PREFIX",param!) + else + map!.put("REST_PGM_PREFIX",stbl("REST_PGM_PREFIX",err=*next)) + endif + + param! = context!.getInitParameter("REST_PGM_SUFFIX") + if param! <> null() then + map!.put("REST_PGM_SUFFIX",param!) + else + map!.put("REST_PGM_SUFFIX",stbl("REST_PGM_SUFFIX",err=*next)) + endif + + param! = context!.getInitParameter("REST_WD") + if param! <> null() then + map!.put("REST_WD",param!) + void$ = stbl("REST_WD",param!) + else + map!.put("REST_WD",stbl("REST_WD",err=*next)) + endif + + param! = context!.getInitParameter("REST_ADAPTERPGM") + if param! <> null() then + map!.put("REST_ADAPTERPGM",param!) + void$ = stbl("REST_ADAPTERPGM",param!) + else + map!.put("REST_ADAPTERPGM",stbl("REST_ADAPTERPGM",err=*next)) + endif + + param! = context!.getInitParameter("REST_ADAPTERTERM") + if param! <> null() then + map!.put("REST_ADAPTERTERM",param!) + else + map!.put("REST_ADAPTERTERM",stbl("REST_ADAPTERTERM",err=*next)) + endif + + param! = context!.getInitParameter("REST_TIMEOUT") + if param! <> null() then + map!.put("REST_TIMEOUT",param!) + else + map!.put("REST_TIMEOUT",stbl("REST_TIMEOUT",err=*next)) + endif + + param! = context!.getInitParameter("REST_AUTHPGM") + if param! <> null() then + map!.put("REST_AUTHPGM",param!) + else + map!.put("REST_AUTHPGM",stbl("REST_AUTHPGM",err=*next)) + endif + + param! = context!.getInitParameter("REST_REQUESTLOG") + if param! <> null() then + map!.put("REST_REQUESTLOG",param!) + void$ = stbl("REST_REQUESTLOG",param!) + else + map!.put("REST_REQUESTLOG",stbl("REST_REQUESTLOG",err=*next)) + endif + + param! = context!.getInitParameter("USE_GET_ALLOWED_FILTER") + if param! <> null() then + map!.put("USE_GET_ALLOWED_FILTER",param!) + else + map!.put("USE_GET_ALLOWED_FILTER",stbl("USE_GET_ALLOWED_FILTER",err=*next)) + endif + + methodret map! + + methodend + +classend