https://raw.githubusercontent.com/ajmaradiaga/feeds/main/scmt/topics/NW-ABAP-Internet-Communication-Framework-blog-posts.xml SAP Community - NW ABAP Internet Communication Framework 2024-05-20T11:11:13.378773+00:00 python-feedgen NW ABAP Internet Communication Framework blog posts in SAP Community https://community.sap.com/t5/technology-blogs-by-members/the-meaning-of-lifetime-rc/ba-p/12840530 The meaning of LIFETIME_RC 2004-10-12T10:34:40+02:00 qmacro https://community.sap.com/t5/user/viewprofilepage/user-id/53 <DIV><BR /> <BR /> Last week, during some preparation for my <A class="jive-link-external-small" href="http://localhost/qmacro/tech/sap/teched04.entry" target="_blank" rel="nofollow noopener noreferrer">talk at TechEd</A> on Thursday this week, I'd been wondering about something in the ICF that hadn't seemed quite right. Interface IF_HTTP_EXTENSION, which is what every ICF handler must implement (in the form of a single method HANDLE_REQUEST) has a couple of attributes, FLOW_RC and LIFETIME_RC. FLOW_RC is for controlling the flow of handler dispatching for a request. LIFETIME_RC is for controlling the lifetime of handlers for a sequence of requests. To quote the <A class="jive-link-external-small" href="http://help.sap.com/saphelp_nw04/helpdata/en/78/98529fc06b11d4ad310000e83539c3/content.htm" target="_blank" rel="noopener noreferrer">documentation at help.sap.com</A> on the latter:<BR /> <BLOCKQUOTE class="jive-quote"><EM>HTTP request handlers can control the lifetime of their instances if they are operating in stateful mode ... If the attribute IF_HTTP_EXTENSION~LIFETIME_RC is set to one of the following values, the HTTP request handler can specify whether the handler should be reinitiated for every request in a session, or whether the handler should be retained and reused for subsequent HTTP requests.</EM></BLOCKQUOTE><BR /> &nbsp;<BR /> <BR /> The default action is for the handler instance created to handle the request to be kept, so that instance-level data is retained (think of an incrementing counter value that keeps going up every new request). This is the equivalent of setting LIFETIME_RC to the value of the constant CO_LIFETIME_KEEP. But if LIFETIME_RC is set to the value of constant CO_LIFETIME_DESTROY:<BR /> <BLOCKQUOTE class="jive-quote"><EM>The current instance of the HTTP request handler is terminated after the request is processed. If stateful mode is active, a new instance of the HTTP request handler is created. This means that local data belonging to the instance is lost.</EM></BLOCKQUOTE><BR /> &nbsp;<BR /> <BR /> [This of course only makes sense in the context of stateful sessions, which you can create using the SET_SESSION_STATEFUL method (of IF_HTTP_SERVER) - one effect of which causes a context id cookie to be constructed and set in the next HTTP response.]<BR /> <BR /> Ok, so with the phrasing of the help text (such as "<EM>...can control the lifetime...</EM>") and the implication of the "DESTROY" part of the constant name, I did a little experiment to try and control the lifetime, by setting the LIFETIME_RC attribute so that the handler instance would be destroyed after it exited. Did it work as expected?<BR /> <BR /> No.<BR /> <BR /> Hmm. What's going on? Well, it seems that with LIFETIME_RC, it's either all or nothing. If you set your session to be stateful and specify that the handler instance should be kept (or let it default to that anyway), then you can't, later in the session, suddenly decide to have the session destroyed.<BR /> <BR /> Looking under the hood, we see this is confirmed in the ICF layer's code. The whole process of handling a request is triggered via PBO modules in SAPMHTTP, and via the HTTP_DISPATCH_REQUEST coordinator, we come to the EXECUTE_REQUEST (or EXECUTE_REQUEST_FROM_MEMORY which I've seen in 6.40) method of the CL_HTTP_SERVER class.<BR /> <BR /> When a request comes in, the appropriate handler is instantiated, and the HANDLE_REQUEST method called. Once this method returns, a decision based on LIFETIME_RC is made as to whether to save the instantiated handler object in an internal table, ready for a new request. Unless LIFETIME_RC is set to destroy, the object is saved, providing we're dealing with a stateful session:<BR /> <PRE class="sapCode">if server-&gt;stateful = 1 and extension-&gt;lifetime_rc = if_http_extension=&gt;co_lifetime_keep and ext_inst_idx = -1. * add extension to list of instantiated extensions ...</PRE><BR /> There's no facility for <EM>removing</EM> existing table entries though. And this is the key to understanding why manipulating the LIFETIME_RC attribute won't always do ... what you <EM>think</EM> it should do.<BR /> <BR /> &nbsp;<BR /> <BR /> I bet you're glad you know that now ... share and enjoy <span class="lia-unicode-emoji" title=":slightly_smiling_face:">πŸ™‚</span><BR /> <BR /> </DIV> 2004-10-12T10:34:40+02:00 https://community.sap.com/t5/technology-blogs-by-sap/identify-the-owner-of-the-process-holding-the-mutex-lock/ba-p/13404454 Identify the owner of the process holding the mutex lock 2018-12-19T12:39:38+01:00 former_member230159 https://community.sap.com/t5/user/viewprofilepage/user-id/230159 <H3 id="toc-hId-1057944956">Purpose</H3><BR /> To identify the process holding the mutex lock which will help in troubleshooting mutex related problems.There are different processes holding the mutex lock.In order to identify which is that process and how to release that held mutex we must first understand how to identify the process.<BR /> <H4 id="Identifytheowneroftheprocessholdingthemutexlock-BreakingdowntheMtxLock" id="toc-hId-990514170">Breaking down the MtxLock</H4><BR /> Firstly what is meaning of the MtxLock function ? By definition this means&nbsp;<EM>wait for a lock to become available.&nbsp;</EM>Mutexes can't be recovered&nbsp;automatically if a process dies while still holding a lock.<BR /> <H4 id="Identifytheowneroftheprocessholdingthemutexlock-TheMutexrelatedtraceentries" id="toc-hId-794000665">The Mutex related trace entries</H4><BR /> In the developer traces of the work processes&nbsp; (dev_w*) we can find the entries as:<BR /> <BR /> example #1<BR /> <DIV class="scn-scrollable-area"><BR /> <TABLE class="wrapped confluenceTable"><COLGROUP> <COL /></COLGROUP><BR /> <TBODY><BR /> <TR><BR /> <TH class="confluenceTh">*** WARNING =&gt; MtxLock &lt;hex address&gt; RQ_Q_WOD owner=30004 deadlock ? [mtxxx.c 641]</TH><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> </DIV><BR /> example #2<BR /> <DIV class="scn-scrollable-area"><BR /> <TABLE class="wrapped confluenceTable"><COLGROUP> <COL /></COLGROUP><BR /> <TBODY><BR /> <TR><BR /> <TH class="confluenceTh">*** WARNING =&gt; MtxLock &lt;hex address&gt;&nbsp;MTXGLOB owner=30001 deadlock ? [mtxxx.c 2574]</TH><BR /> </TR><BR /> </TBODY><BR /> </TABLE><BR /> </DIV><BR /> &nbsp;<BR /> <BR /> Now lets break down each example one by one.<BR /> <BR /> From example #1<BR /> <BR /> *** WARNING =&gt; MtxLock a00010020000270 RQ_Q_WOD&nbsp;<STRONG>owner=30004</STRONG>&nbsp;deadlock ? [mtxxx.c 641]<BR /> <BR /> Now here we can see the owner is :&nbsp;<STRONG>owner=30004</STRONG><BR /> <BR /> Similarly in&nbsp;example #2 we can see the owner is&nbsp;<STRONG>owner=30001&nbsp;</STRONG><BR /> <BR /> &nbsp;<BR /> <H4 id="Identifytheowneroftheprocessholdingthemutexlock-IdentifyingthecalleridakaclientidakaCID" id="toc-hId-597487160">Identifying the caller id aka client id aka CID</H4><BR /> First I would like to list out the predefined client id's in the kernel.<BR /> <BR /> The Client id range starts from 30000 and is end to 32767.<BR /> <BR /> By definition it states as ( part of internal SAP coding I cant provide more detail due to security reasons ) :<BR /> <BR /> &nbsp;<BR /> <BR /> 30000+0 = 30000 --&gt; Owner is dispatcher<BR /> <BR /> 30000+1 = 30001 --&gt; Owner is ICM<BR /> <BR /> 30000+2 = 30002 --&gt; Owner is the java control process<BR /> <BR /> 30000+3 = 30003 --&gt; Owner is dpmon process<BR /> <BR /> 30000+4 = 30004 --&gt; Owner is gateway<BR /> <BR /> 30000+5 = 30005 --&gt; Owner is VMC process<BR /> <BR /> 30000+6 = 30006 --&gt; Owner is ES (extended segment) memory area<BR /> <BR /> 30000+7 = 30007 --&gt; Owner is EM (extended memmory) memory area<BR /> <BR /> 30000+8 = 30008 --&gt; Owner is EM (extended global) memory area<BR /> <BR /> &nbsp;<BR /> <H4 id="Identifytheowneroftheprocessholdingthemutexlock-Troubleshootingtheentries" id="toc-hId-400973655">Troubleshooting the entries</H4><BR /> Going one step further we now know who is the owner of the mutex.Our next step is checking the related developer trace file to rectify the issue.<BR /> <BR /> From example #1<BR /> <BR /> *** WARNING =&gt; MtxLock &lt;hex address&gt; RQ_Q_WOD owner=30004 deadlock ? [mtxxx.c 641]<BR /> <BR /> <SPAN style="text-decoration: underline"><EM>Here we will check in the gateway trace (dev_rd) to see what is the issue.</EM></SPAN><BR /> <BR /> From example #2<BR /> <BR /> *** WARNING =&gt; MtxLock &lt;hex address&gt; MTXGLOB owner=30001 deadlock ? [mtxxx.c 2574]<BR /> <BR /> <SPAN style="text-decoration: underline"><EM>Here we will check in the ICM (dev_icm) to see what is the issue.</EM></SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> <EM>TIP: Please also check the dev_*.old traces respectively.</EM><BR /> <BR /> &nbsp;<BR /> <BR /> Let me know in case any queries.<BR /> <BR /> &nbsp;<BR /> <BR /> Kind regards,<BR /> <BR /> Manjunath Hanmantgad 2018-12-19T12:39:38+01:00 https://community.sap.com/t5/technology-blogs-by-members/sicf-on-pi-po-single-stack-shortening-long-urls-for-customers-inbound/ba-p/13404350 SICF on PI/PO single stack : Shortening Long URLs for customers inbound. 2018-12-20T12:46:01+01:00 former_member190389 https://community.sap.com/t5/user/viewprofilepage/user-id/190389 <H1 id="toc-hId-799778553">SICF alternative on PI/PO single stack.</H1><BR /> &nbsp;<BR /> <BR /> In case where customer sends us files on a dual stack, SICF is available to create service and shorten the long technical URLs with various security mechanisms.<BR /> <BR /> Which means something like this<BR /> <PRE class="language-java"><CODE>{server:port}/HttpAdapter/HttpMessageServlet?interfaceNamespace=&lt;namespace&gt; &amp;interface=&lt;interface name&gt;&amp;senderService=BS_Sender&amp;qos=EO</CODE></PRE><BR /> can be made to this<BR /> <PRE class="language-java"><CODE>{server:port}/&lt;context-root&gt;/&lt;app&gt;</CODE></PRE><BR /> without compromising on the authentication mechanism like Basic,&nbsp;certificate etc. in PI/PO .<BR /> <BR /> The 'test' service in the below pic<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/1-35.png" /><BR /> <BR /> can be accessed using<BR /> <PRE class="language-abap"><CODE><A href="https://&lt;server&gt;/&lt;service" target="test_blank" rel="nofollow noopener noreferrer">https://&lt;server&gt;/&lt;service</A> element&gt;/test?sap-client=300</CODE></PRE><BR /> Logon is a list of Logon Procedure in that order to be followed.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/2-36.png" /><BR /> <BR /> However On Java stack&nbsp; where we no more have such a service, the same can be achieved by using a servlet which will forward the received request to the HTTP adapter URL.<BR /> <PRE class="language-abap"><CODE>{server:port}/HttpAdapter/HttpMessageServlet?interfaceNamespace=&lt;namespace&gt; &amp;interface=&lt;interface name&gt;&amp;senderService=BS_Sender&amp;qos=EO</CODE></PRE><BR /> The customer actually posts to the servlet, which then forward it to the Http Adapter. This method can be used in other scenarios as well where we need to shorten a url or don't want the outside world to know about the&nbsp; technical details.Below is the procedure:<BR /> <BR /> &nbsp;<BR /> <H2 id="toc-hId-732347767">Procedure:</H2><BR /> In NWDS, create&nbsp; two DCs of type WebModule &amp; Enterprise Application in DI perspective.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/3-23.png" /><BR /> <BR /> Give a name for the project , here β€œsicf”.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/4-22.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> Chose Finish<BR /> <BR /> Create DC of type Enterprise Application<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/5-17.png" /><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/6-21.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> Link with the webmodule β€œsicf”<BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/7-18.png" /><BR /> <BR /> Choose&nbsp;<STRONG>Finish</STRONG>&nbsp;.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/8-19.png" /><BR /> <BR /> In the&nbsp;<STRONG>Project Explorer</STRONG>&nbsp;, select the Dynamic Web Project.<BR /> <BR /> In the context menu, choose&nbsp;<STRONG>New</STRONG>&nbsp;β†’&nbsp;<STRONG>Servlet</STRONG>&nbsp;.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/9-20.png" /><BR /> <BR /> Throughout the wizard pages, enter the servlet settings as required.<BR /> <BR /> Here we created a class named β€œtest” in the java package β€œcom.test”<BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/10-15.png" /><BR /> <BR /> To access the servlet, you need to add servlet mapping. Using this and the context-root specified in application.xml we will be able to access the URL. Each mapping represent a java class here test.java can be accessed using /test in the browser (GET method) or UI tools.<BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/11-9.png" /><BR /> <BR /> Here we select the doPost and doGet methods where we will write our code.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/12-12.png" /><BR /> <BR /> Chose&nbsp;<STRONG>Finish</STRONG><BR /> <BR /> &nbsp;<BR /> <BR /> You can see that in the project structure Servets, Its Mapping and the Java class has been created.<BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/13-10.png" /><BR /> <BR /> Inside test.java , the doGet &amp; doPost needs to be implemented.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/14-7.png" /><BR /> <BR /> Copy paste the below code. The Get method will just print out some message. The post method will redirect the request to the Http adapter .<BR /> <BR /> &nbsp;<BR /> <PRE class="language-java"><CODE>/**<BR /> * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)<BR /> */<BR /> protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<BR /> PrintWriter out= response.getWriter();<BR /> out.println("This is the GET Method ,use POST to post data ");<BR /> <BR /> }<BR /> <BR /> /**<BR /> * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)<BR /> */<BR /> protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<BR /> PrintWriter out= response.getWriter();<BR /> <BR /> ServletContext web1 = getServletContext();;<BR /> ServletContext web2 = web1.getContext("/HttpAdapter");<BR /> RequestDispatcher rd = <BR /> web2.getRequestDispatcher("/HttpMessageServlet?interfaceNamespace=urn:sap-com:document:sap:idoc:messages&amp;interface=GSVERF.GSVERF01&amp;senderService=SELFBILLING&amp;qos=EO");<BR /> <BR /> rd.include(request, response);<BR /> out.println("Response -&gt; Inside Post method");<BR /> out.close();<BR /> }<BR /> </CODE></PRE><BR /> Servlets can redirect client requests to other servlet and JSP components. This is done by using the β€œinclude” method of the &nbsp;request &nbsp;dispatcher as seen above in the doPost method.<BR /> <BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <BR /> For authentication we need to maintain web-descriptors , namely web.xml and web-j2ee-engine.xml in our application.<BR /> <BR /> In web-j2ee-engine.xml, we need to enter the security mechanism to be used.<BR /> <BR /> <STRONG>&lt;login-module-stack&gt;</STRONG> can have multiple <STRONG>&lt;login-module&gt; </STRONG>elements.<BR /> <PRE class="language-markup"><CODE>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<BR /> &lt;web-j2ee-engine xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="web-j2ee-engine.xsd"&gt;<BR /> &lt;spec-version&gt;2.4&lt;/spec-version&gt;<BR /> &lt;security-role-map&gt;<BR /> &lt;role-name&gt;Everyone&lt;/role-name&gt;<BR /> &lt;server-role-name&gt;Everyone&lt;/server-role-name&gt;<BR /> &lt;/security-role-map&gt;<BR /> &lt;login-module-configuration&gt;<BR /> &lt;login-module-stack&gt;<BR /> &lt;login-module&gt;<BR /> &lt;login-module-name&gt;BasicPasswordLoginModule&lt;/login-module-name&gt;<BR /> &lt;flag&gt;REQUIRED&lt;/flag&gt;<BR /> &lt;/login-module&gt;<BR /> <BR /> &lt;/login-module-stack&gt;<BR /> &lt;/login-module-configuration&gt;<BR /> &lt;/web-j2ee-engine&gt;</CODE></PRE><BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <BR /> The element <STRONG>&lt;login-module-name &gt;</STRONG> can be found in NWA path:<BR /> <BR /> NWA-&gt; Configuration-&gt;Security-&gt;Authentication &amp; SingleSign-On<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/18-3.png" /><BR /> <BR /> The element <STRONG>&lt;flag&gt;</STRONG> can be :<BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/19-2.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> In the web.xml descriptor , there is an entry for &nbsp;servlet and its URL mapping<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/20-2.png" /><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/21-2.png" /><BR /> <BR /> Include below code for the login mechanism after &lt;servlet-mapping&gt; node ends, we are using client-cert and basic. The role mapped here is the j2ee role and not PI.<BR /> <PRE class="language-markup"><CODE>&lt;security-role&gt;<BR /> &lt;description&gt;Everyone&lt;/description&gt;<BR /> &lt;role-name&gt;Everyone&lt;/role-name&gt;<BR /> &lt;/security-role&gt;<BR /> &lt;security-constraint&gt;<BR /> &lt;web-resource-collection&gt;<BR /> &lt;web-resource-name&gt;General access restriction&lt;/web-resource-name&gt;<BR /> &lt;url-pattern&gt;/*&lt;/url-pattern&gt;<BR /> &lt;/web-resource-collection&gt;<BR /> &lt;auth-constraint&gt;<BR /> &lt;description&gt;EveryoneH&lt;/description&gt;<BR /> &lt;role-name&gt;Everyone&lt;/role-name&gt;<BR /> &lt;/auth-constraint&gt;<BR /> &lt;user-data-constraint&gt;<BR /> &lt;transport-guarantee&gt;NONE&lt;/transport-guarantee&gt;<BR /> &lt;/user-data-constraint&gt;<BR /> &lt;/security-constraint&gt;<BR /> &lt;login-config&gt;<BR /> &lt;auth-method&gt;BASIC,CLIENT-CERT&lt;/auth-method&gt;<BR /> &lt;realm-name&gt;test&lt;/realm-name&gt;<BR /> &lt;/login-config&gt;</CODE></PRE><BR /> Multiple mechanism , If used , should be comma separated.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/22-3.png" /><BR /> <BR /> The sub-element&nbsp;auth-method&nbsp;configures the authentication mechanism for the web application. The element content must be either NONE, BASIC, DIGEST, FORM, or CLIENT-CERT. The&nbsp;realm-name&nbsp;element indicates the realm name to use when the basic authentication scheme is chosen for the web application.<BR /> <BR /> In the EAR project create application.xml file.<BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/23-3.png" /><BR /> <BR /> The application.xml links the war file to the context-root that helps us give application a name.<BR /> <BR /> Using the context-root our URL to access test.java is<BR /> <BR /> &lt;server&gt;:&lt;port&gt;/&lt;context-root&gt;/&lt;servlet-url-mapping&gt;<BR /> <BR /> i.e.<BR /> <BR /> &lt;server&gt;:&lt;port&gt;/sicftesting/test<BR /> <PRE class="language-markup"><CODE>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<BR /> &lt;application <BR /> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <BR /> xmlns="http://java.sun.com/xml/ns/javaee" <BR /> xmlns:application="http://java.sun.com/xml/ns/javaee"<BR /> xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" version="5"&gt;<BR /> &lt;display-name&gt;SICFTest&lt;/display-name&gt;<BR /> &lt;module&gt;<BR /> &lt;web&gt;<BR /> &lt;web-uri&gt;demo.sap.com~sicf.war&lt;/web-uri&gt;<BR /> &lt;context-root&gt;sicftesting&lt;/context-root&gt;<BR /> &lt;/web&gt;<BR /> &lt;/module&gt;<BR /> &lt;/application&gt;</CODE></PRE><BR /> Build&nbsp; both the application and EAR and deploy.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/25-4.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> Finally, as your application is on the server ,you&nbsp; can use a browser to call the application&nbsp; - the doGet method will be called. The output below is what we wrote in the doGET method.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/27-1.png" /><BR /> <BR /> Use tool like Postman or SoapUI to send the request/file and you will find an entry in your pimon tool.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/28-2.png" /><BR /> <BR /> Below is the scenario which got triggered after sending the request .<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/29-1.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> So ,as our HTTP adapter is itself implemented as a servlet , we create another servlet to redirect inbound requests to this adapter masking all the technical details with all the security mechanisms in place, making it easier for the partners to use and we can internally change the landscape , interfaces without the need to to communicate the change making task a lot easier.<BR /> <BR /> &nbsp;<BR /> <BR /> Please feel free to comment , suggest and ask any questions.<BR /> <BR /> Updated on 21st Dec 2018 :<BR /> <BR /> Forgot to mention ,that as we have the&nbsp;HttpServletRequest request&nbsp; &amp;&nbsp; HttpServletResponse response objects in the function call , they can be used to manipulate query or header parameters as well as the response codes .<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2018/12/30-4.png" /><BR /> <BR /> Example:<BR /> <PRE class="language-java"><CODE>//for all the query params <BR /> request.getQueryString();<BR /> <BR /> //username=james&amp;password=pwd<BR /> request.getParameter("username");</CODE></PRE><BR /> &nbsp; 2018-12-20T12:46:01+01:00 https://community.sap.com/t5/technology-blogs-by-sap/invalidating-cache-is-not-working-entries-in-icm-trace-as-quot/ba-p/13406338 Invalidating Cache is not working.Entries in ICM trace as "IctICmRemoveFile() failed. 1" 2019-01-29T12:08:52+01:00 former_member230159 https://community.sap.com/t5/user/viewprofilepage/user-id/230159 <H3 id="toc-hId-1058003519">Purpose</H3><BR /> While trying to invalidate cache I was constantly being faced by the issue that post invalidating the cache, my screen was turning blank or it was giving me with the blank page.<BR /> <BR /> Once I invalidated my cache it would work for some time (approx 10 minutes) but again when I executed the URL I would get a blank page again.<BR /> <H3 id="toc-hId-861490014"></H3><BR /> <H3 id="toc-hId-664976509">Troubleshooting</H3><BR /> The first thing that came to my mind when I came across this issue was to check in icm trace file ( dev_icm ).<BR /> <BR /> &nbsp;<BR /> <BR /> In icman trace file I could see the entries as:<BR /> <BR /> *** dev_icm<BR /> <BR /> [Thr 15696] Fri Jan 25 14:21:44 2019<BR /> [Thr 15696] *** ERROR =&gt; remove('\\*\sapmnt\&lt;SID&gt;\ASCS00\data\cache\M0x1AC4') failed. errno=2 [ictxxcache_r 4331]<BR /> [Thr 15696] *** ERROR =&gt; ENOENT*: No such file or directory OR: The system cannot find the file specified. [ictxxcache_r 4333]<BR /> [Thr 15696] *** ERROR =&gt; IctICmRemoveFile() failed. 1 [ictxxcache_r 3369]<BR /> [Thr 15696] *** ERROR =&gt; can't write resource '/sap/public/bsp/sap/system/E9207E7015E6D2F18F8EA41F72C7461F&amp;en&amp;&amp;GZ=0&amp;000&amp;A6E90000&amp;' (<BR /> [Thr 15696] WARNING: ISC object '/sap/public/bsp/sap/system/E9207E7015E6D2F18F8EA41F72C7461F&amp;en&amp;&amp;GZ=0&amp;000&amp;A6E90000&amp;' not found<BR /> <BR /> &nbsp;<BR /> <BR /> Based on the error entries the important functions that we need to check are&nbsp;<EM>ENOENT ,&nbsp;IctICmRemoveFile ,&nbsp;can't write resource ,&nbsp;ISC object.</EM><BR /> <BR /> From the above entries at the first instance we can start to visualize that there is something wrong in the header of the url.<BR /> <BR /> The entry :&nbsp; ERROR =&gt; remove('\\*\sapmnt\&lt;SID&gt;\ASCS00\data\cache\M0x1AC4') failed. errno=2 [ictxxcache_r 4331] ..proves this .<BR /> <BR /> I tried to check if some application is causing this? After some research I came to know the issue is something different.<BR /> <BR /> The entry :&nbsp;ERROR =&gt; ENOENT*: No such file or directory OR: The system cannot find the file specified. [ictxxcache_r 4333]<BR /> <BR /> this indicates that cache directory which is been set is not actually being taken. In other words it is WRONG!<BR /> <BR /> &nbsp;<BR /> <BR /> My next guess was to identify which parameter is related to cache directory and ICMan. Till now I was just trying get to understood what went wrong , but now I had the solution <span class="lia-unicode-emoji" title=":winking_face:">πŸ˜‰</span><BR /> <BR /> The parameter that is related to cache dir and icman is "<A href="https://help.sap.com/doc/saphelp_nw73ehp1/7.31.19/en-US/48/3e1b4e252f72d0e10000000a42189c/content.htm?no_cache=true" target="_blank" rel="noopener noreferrer"><STRONG>icm/HTTP/file_access_&lt;xx&gt;</STRONG></A>".<BR /> <BR /> &nbsp;<BR /> <BR /> If you look closely the standard value for this parameter is :<BR /> <BLOCKQUOTE><STRONG>icm/HTTP/file_access_0 = PREFIX=/sap/public/icmandir/ ,DOCROOT=$(DIR_ICMAN_ROOT)</STRONG></BLOCKQUOTE><BR /> Here the point of concern is "<EM>DOCROOT=$(DIR_ICMAN_ROOT)</EM>".<BR /> <BLOCKQUOTE>Next I checked the value for&nbsp;$(DIR_ICMAN_ROOT). This was the important step which resolved my issue.</BLOCKQUOTE><BR /> In&nbsp;icm/HTTP/file_access_0 I had set the DOCROOT to some other value which was not matching with ' <EM>DIR_ICMAN_ROOT</EM> '.<BR /> <BR /> Once I changed the value of&nbsp;'<STRONG>DOCROOT</STRONG>' in the <STRONG><EM>'icm/HTTP/file_access_0</EM></STRONG>' the issue got resolved. I was able to proceed further without the irritating BLANK PAGE!!<BR /> <BR /> &nbsp;<BR /> <BR /> Let me know if any queries.<BR /> <BR /> &nbsp;<BR /> <BR /> <SPAN style="text-decoration: underline"><EM>PS: I would like to also point out to a fact that always check the below set of parameters :</EM></SPAN><BR /> <BR /> <SPAN style="text-decoration: underline"><EM>ABAP-only or dual stack (&nbsp;<KBD class="ph userinput">system/type = ABAP| DS</KBD>&nbsp;)</EM></SPAN><BR /> <BR /> <SPAN style="text-decoration: underline"><EM>Java-only (&nbsp;<KBD class="ph userinput">system/type = J2EE</KBD>&nbsp;)</EM></SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <BR /> Kind regards,<BR /> <BR /> Manjunath Hanmantgad 2019-01-29T12:08:52+01:00 https://community.sap.com/t5/technology-blogs-by-members/developing-graphql-api-on-abap-the-inspiration/ba-p/13395503 Developing GraphQL API on ABAP: The Inspiration 2019-09-18T09:08:38+02:00 hareeshbabu82gmail_com https://community.sap.com/t5/user/viewprofilepage/user-id/295697 <IMG class="aligncenter" src="https://marmelab.com/images/blog/graphql/logo.png" alt="Image result for graphql logo" width="339" height="119" /><BR /> <BR /> This two-part article will discuss details we need to know about the GraphQL implementation in SAP ABAP. How it was born, what it offers, and how to use it. As this is my first blog post ever and I am not an expert in either ABAP or GraphQL, the article may contain few mistakes. Also, this article is not designed to be a comparison of <STRONG>REST</STRONG> or <STRONG>OData</STRONG> vs <STRONG>GraphQL</STRONG>.<BR /> <BR /> For those wants to skip to Technical details can go to <A href="https://blogs.sap.com/2019/09/12/developing-graphql-api-on-abap-architecture-and-technical-details" target="_blank" rel="noopener noreferrer">part 2</A>.<BR /> <P id="8b7e" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">As I was looking for better ways to expose existing data in SAP through APIs which helps development and maintenance times low. I have tried a few ways to produce APIs out of SAP.</P><BR /> <P id="5b8b" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">Earlier being&nbsp;<STRONG class="ju kg">ICF Services</STRONG> exposing data using ABAP to JSON converter utility (custom) classes. Which were bare minimum implementations of APIs that can be exposed out of SAP using <STRONG class="ju kg">SICF</STRONG>. Which is equivalent of&nbsp;<STRONG class="ju kg">Express Server</STRONG>&nbsp;in&nbsp;<STRONG class="ju kg">NodeJS</STRONG>&nbsp;world.</P><BR /> <P id="1a70" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">Although ICF services are quite powerful and provide total control over what can be sent over responses, I continued looking further for better options.</P><BR /> <BR /> <H1 id="892c" class="kp kq bm bc bb fz kr ks kt ku kv kw kx ky kz la lb" data-selectable-paragraph="" id="toc-hId-779492652">GraphQL</H1><BR /> <P id="9a83" class="js jt bm bc ju b jv lc jx ld jz le kb lf kd lg kf" data-selectable-paragraph="">As I also work with NodeJS to develop Web Applications and Server-Side APIs, I have been following GraphQL from its earlier stages since Facebook has made it public and I am very much fascinated by the way GraphQL standardizes the access of our backend Relational Data.</P><BR /> <P id="f3bc" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">Although this article is not in any way the introduction to GraphQL, I would like to just talk about a few features it brings to the table.</P><BR /> <P id="fa5d" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">The quote from <A class="ci ce lh li lj lk" href="https://graphql.org/" target="_blank" rel="noopener noreferrer nofollow">https://graphql.org</A>, <STRONG class="ju kg">Describe your data</STRONG>,&nbsp;<STRONG class="ju kg">Ask for what you want</STRONG>&nbsp;and&nbsp;<STRONG class="ju kg">Get predictable results</STRONG>&nbsp;provides all there is to know about GraphQL in a nutshell.</P><BR /> <P data-selectable-paragraph="">One of the advantages I love about GraphQL is the ability to traverse through the relationships of our Data model. Which provides superpowers to the clients requesting data and puts totally in charge. This makes sense in the multi-platform world that A mobile App can choose to fetch fewer data from the same API endpoint as opposed to a Web Application which has more screen area.</P><BR /> <P data-selectable-paragraph=""><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2019/09/GqlServer-RR-demo.png" /></P><BR /> <BR /> <H6 style="text-align: center" id="toc-hId-1228392742">Request and Response to GraphQL Server (image from <A class="ci ce lh li lj lk" href="https://www.howtographql.com/" target="_blank" rel="noopener noreferrer nofollow">https://www.howtographql.com</A>)</H6><BR /> &nbsp;<BR /> <P id="3424" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">Another advantage of GraphQL which simplifies API development and maintenance is that we can get rid of versioning APIs and maintaining several endpoints and flavors of the same domain data. As GraphQL provides&nbsp;<STRONG class="ju kg">Queries</STRONG>&nbsp;(Read),&nbsp;<STRONG class="ju kg">Mutations</STRONG>&nbsp;(Create, Update and Delete) and&nbsp;<STRONG class="ju kg">Subscriptions</STRONG> (Real-time Updates) all under single endpoint, scaling and maintaining is much simpler. Instead of versioning API, GraphQL has the functionality to flag fields for <STRONG class="ju kg">Depreciation</STRONG>&nbsp;along with the reason. Adding new fields will not be affecting existing API unless clients request for it.</P><BR /> <P id="4b84" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">The GraphQL&nbsp;<STRONG class="ju kg">Type System</STRONG>&nbsp;will provide the&nbsp;<STRONG class="ju kg">Documentation</STRONG>&nbsp;by default so that the consumers are well aware of what to request and expect back from the Server.</P><BR /> <P id="ec7f" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">As the popularity of GraphQL increases, so are the Tools and Libraries around it. The GraphQL community is really awesome and can find a lot of resources online for learning about it.</P><BR /> <BR /> <UL class=""><BR /> <LI id="5ab0" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf lq lr ls" data-selectable-paragraph="">The&nbsp;<STRONG class="ju kg">GraphiQL</STRONG> tool is one used to interact with GraphQL server and has several flavors to improve usability. GraphiQL provides auto-completion of queries and shows the documentation from Server.</LI><BR /> <LI id="9527" class="js jt bm bc ju b jv lt jx lu jz lv kb lw kd lx kf lq lr ls" data-selectable-paragraph=""><A class="ci ce lh li lj lk" href="https://www.apollographql.com/" target="_blank" rel="noopener noreferrer nofollow"><STRONG class="ju kg">Apollo</STRONG></A><STRONG class="ju kg">&nbsp;and&nbsp;</STRONG><A class="ci ce lh li lj lk" href="https://facebook.github.io/relay/" target="_blank" rel="noopener noreferrer nofollow"><STRONG class="ju kg">Relay</STRONG></A><STRONG class="ju kg">&nbsp;</STRONG>libraries provide easy to integrate GraphQL into the development workflow</LI><BR /> </UL><BR /> &nbsp;<BR /> <H1 id="f7e4" class="kp kq bm bc bb fz kr ks kt ku kv kw kx ky kz la lb" data-selectable-paragraph="" id="toc-hId-386465642">SAP</H1><BR /> <P id="4418" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">I would like to mention about&nbsp;<STRONG class="ju kg">SAP CRM</STRONG>&nbsp;module as well here, as my end goal is to expose CRM customer data as APIs. CRM has divided the functionality into Business Partners and One Order, which is already exposed to CRM UI with the help of&nbsp;<STRONG class="ju kg">BOL(Business Object Layer)/GenIL(Generic Interaction Layer)</STRONG>. My Idea is to leverage what has been working for several years and extend to expose as a GraphQL API.</P><BR /> <P id="e687" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">If we look closely, BOL/GenIL has several similarities with GraphQL.</P><BR /> <BR /> <UL class=""><BR /> <LI id="b8a4" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf lq lr ls" data-selectable-paragraph="">BOL defines Advanced Search Entities which can be related to Query Types in GraphQL</LI><BR /> <LI id="6414" class="js jt bm bc ju b jv lt jx lu jz lv kb lw kd lx kf lq lr ls" data-selectable-paragraph="">Query Parameters can be converted as Arguments</LI><BR /> <LI id="e4a7" class="js jt bm bc ju b jv lt jx lu jz lv kb lw kd lx kf lq lr ls" data-selectable-paragraph="">Root Entities/ Result Entities can be related to Custom Types</LI><BR /> <LI id="d8c4" class="js jt bm bc ju b jv lt jx lu jz lv kb lw kd lx kf lq lr ls" data-selectable-paragraph="">Each attribute of the Entity can be exposed as Type fields</LI><BR /> <LI id="6d76" class="js jt bm bc ju b jv lt jx lu jz lv kb lw kd lx kf lq lr ls" data-selectable-paragraph="">Each consequent relation can be considered as Related/Sub Types and can be resolved with getRelatedEntitie(s) function calls of GenIL</LI><BR /> <LI id="b9fb" class="js jt bm bc ju b jv lt jx lu jz lv kb lw kd lx kf lq lr ls" data-selectable-paragraph="">Data Element values or Value Tables for each BOL field can be converted as Enum</LI><BR /> <LI id="3103" class="js jt bm bc ju b jv lt jx lu jz lv kb lw kd lx kf lq lr ls" data-selectable-paragraph="">Last but not least, GenIL Create Entity, Update Entity and Delete Entities can be mapped to Mutations</LI><BR /> </UL><BR /> <P id="f891" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">This view of mapping from BOL/GenIL to GraphQL compelled me to invest more time in building the GraphQL server to expose as a unified interface into SAP CRM.</P><BR /> <P id="88ae" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">Initially, I have started to implement GraphQL in NodeJS and call custom REST endpoints into SAP. Even though this approach allowed me to build an initial Proof of concept. I am not convinced that simply calling REST endpoints behind GraphQL NodeJS implementation and applying filters outside SAP, is a huge task and we can not fully leverage features BOL/GenIL. I feel that it's just a hack to expose data from SAP.</P><BR /> <P id="4f4c" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">I have started looking into GraphQL implementation in ABAP and failed to find one at the time I started building one myself (around Jan 2018). I took the code of Java implementation as a reference in building GraphQL server within ABAP. There has to be a lot of adjustments and sacrifices in porting the Java code to ABAP, as ABAP OOPs is slightly different and I need to strip out Threading as our SAP ICM(Internet Communication Manager) will manage this for us at the request level in my understanding. Also changed the implementation to work with the idea of loading config from tables instead of Schema files as in other languages.</P><BR /> <P id="372d" class="js jt bm bc ju b jv jw jx jy jz ka kb kc kd ke kf" data-selectable-paragraph="">Once the GraphQL server is built and ready in ABAP, It was easy to expose BOL/GenIL to the server as reading from Config tables already available in SAP CRM.</P><BR /> <P data-selectable-paragraph="">Enough of the story, we shall now dive into technical stuff in this <A href="https://blogs.sap.com/2019/09/12/developing-graphql-api-on-abap-architecture-and-technical-details" target="_blank" rel="noopener noreferrer">second part</A> of the Article.</P><BR /> <P data-selectable-paragraph="">Other sources to track this topic.</P><BR /> <A href="https://medium.com/@hareeshbabu82/graphql-sap-abap-the-inspiration-eb6d4f305681" target="_blank" rel="nofollow noopener noreferrer">Part 1</A><BR /> <BR /> <A href="https://medium.com/@hareeshbabu82/graphql-sap-abap-demo-and-technical-details-92a511ed8e4a" target="_blank" rel="nofollow noopener noreferrer">Part 2</A> 2019-09-18T09:08:38+02:00 https://community.sap.com/t5/technology-blogs-by-members/developing-graphql-api-on-abap-architecture-and-technical-details/ba-p/13395518 Developing GraphQL API on ABAP: Architecture and Technical Details 2019-09-19T10:04:04+02:00 hareeshbabu82gmail_com https://community.sap.com/t5/user/viewprofilepage/user-id/295697 <IMG class="aligncenter" src="https://marmelab.com/images/blog/graphql/logo.png" alt="Image result for graphql logo" width="323" height="113" /><BR /> <BR /> This article is the second in series, explains the technical details and includes few screenshots to give a preview. If you want to read some background and boring stuff, is covered in the&nbsp;<A class="ci ce ko kp kq kr" href="https://blogs.sap.com/2019/09/18/developing-graphql-api-on-abap-the-inspiration/" target="_blank" rel="noopener noreferrer">first part</A>.<BR /> <BR /> &nbsp;<BR /> <BR /> Before going to the details, a few product vision key features I have considered.<BR /> <UL><BR /> <LI>Leverage what SAP/ABAP has to offer most. ex. ICM, OOPs</LI><BR /> <LI>Modularize the implementation<BR /> <UL><BR /> <LI>Independent JSON converters</LI><BR /> <LI>GraphQL Service Handler knows nothing about the schema</LI><BR /> <LI>Resolvers can be independent of Schema generation logic</LI><BR /> </UL><BR /> </LI><BR /> <LI>BOL Resolvers to generate Dynamic schema and resolve all CRUD operations using GENIL operations</LI><BR /> <LI>Schema can be saved to Database Tables to be re-loaded/configured manually</LI><BR /> <LI>SAP Sessions to improve performance with BOL and retaining Schema across requests</LI><BR /> <LI>Testable Code and ABAP Test Scripts for code coverage</LI><BR /> </UL><BR /> &nbsp;<BR /> <H1 id="3eab" class="ks kt bm bc bb fz ku kv kw kx ky kz la lb lc ld le" data-selectable-paragraph="" id="toc-hId-779492688">Architecture</H1><BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2019/09/GQL_SAP_Arch_blog.png" /><BR /> <H6 style="text-align: center" id="toc-hId-1228392778">Architecture Diagram</H6><BR /> &nbsp;<BR /> <UL class=""><BR /> <LI id="5478" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">GraphQL server is exposed as an ICF service from ABAP, which accepts and provides JSON data</LI><BR /> <LI id="b27e" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">ICF handler class takes JSON string and convert to ABAP GraphQL Schema classes and validated</LI><BR /> <LI id="9fa5" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Schema is built only for the first time if the Session is enabled</LI><BR /> <LI id="94da" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">The user input Query will be parsed and the Execution Context is created</LI><BR /> <LI id="0b1e" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Resolver class will be called for individual resolver identifications</LI><BR /> <LI id="8850" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Resolver will either go to BOL/GenIL or APIs or can directly talk to DB to fetch data</LI><BR /> <LI id="dbd8" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Data will be submitted back to ExecutionContext as JSON classes</LI><BR /> <LI id="d619" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Finally, JSON classes will be validated and converted to JSON string as ICF service response</LI><BR /> </UL><BR /> &nbsp;<BR /> <H1 id="341f" class="ks kt bm bc bb fz ku ma kw mb ky mc la md lc me le" data-selectable-paragraph="" id="toc-hId-386465678">Accessing the API</H1><BR /> Note: currently this is under active development, watch this space for updates and code<BR /> <BR /> <IMG class="aligncenter" src="https://miro.medium.com/max/5560/1*MIRC8H6cqontCMlI21RB8A.png" /><BR /> <H6 style="text-align: center" id="toc-hId-835365768">MYSAPSSO2 token for authentication</H6><BR /> <EM>Follows standard SAP authorization like MYSAPSSO2 header/cookie in the Request.</EM><BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="aligncenter" src="https://miro.medium.com/max/3888/1*csa7o63VYxwTxKXaZWpZcw.png" /><BR /> <H6 style="text-align: center" id="toc-hId-638852263">API Documentation</H6><BR /> <EM>GraphQL API provides API documentation by default and can be accessed using Schema Queries. Which will provide information to frontend team to browse through the contract to know what fields/operations are supported by the API</EM><BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="aligncenter" src="https://miro.medium.com/max/5584/1*gVqoyX-3tuKvu22aX1XSgQ.png" /><BR /> <H6 style="text-align: center" id="toc-hId-442338758">Queries β€” using BP Advanced Search</H6><BR /> <EM>Query is the way to get some data from API, which is same as HTTP β€œGET” operations. Query can have Arguments which will help filter the results from the API. Query should end with requesting necessary field by the UI.</EM><BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="aligncenter" src="https://miro.medium.com/max/4184/1*rHk_oltACDwf1ZObcU_zXQ.png" /><BR /> <H6 style="text-align: center" id="toc-hId-245825253">BP Advanced Search Results</H6><BR /> <EM>Here we can see the Results as sent back from API, which exactly matches the Requested 4 fields from the server.</EM><BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="aligncenter" src="https://miro.medium.com/max/4280/1*aptQ7L5WELIa1sNSAOlo3w.png" /><BR /> <H6 style="text-align: center" id="toc-hId-49311748">Mutations β€” Creating BP with Deep Data</H6><BR /> <EM>Mutations are a way to change some data on the server. Here we are creating a BP along with the child relations like Address and Marketing Attributes.</EM><BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="aligncenter" src="https://miro.medium.com/max/4072/1*OG1cStNfQoIeIaReHCIaGw.png" /><BR /> <H6 style="text-align: center" id="toc-hId--147201757">Mutations β€” Creating Child data of BP</H6><BR /> &nbsp;<BR /> <BR /> <IMG class="aligncenter" src="https://miro.medium.com/max/3944/1*PUZuQGxmWzvwJdFdix_JnQ.png" /><BR /> <H6 style="text-align: center" id="toc-hId--343715262">Mutations β€” Updates and Deletes</H6><BR /> &nbsp;<BR /> <H1 id="ce6e" class="ks kt bm bc bb fz ku ma kw mb ky mc la md lc me le" data-selectable-paragraph="" id="toc-hId--415902279">At a Glance</H1><BR /> <H2 id="a8de" class="my kt bm bc bb fz mz na nb nc nd ne nf ng nh ni nj" data-selectable-paragraph="" id="toc-hId--905818791">Packages</H2><BR /> <UL class=""><BR /> <LI id="cd05" class="ka kb bm bc kc b kd mf kf mg kh mh kj mi kl mj kn lk ll lm" data-selectable-paragraph="">ZGRAPHQL β€” Main Package</LI><BR /> <LI id="f56e" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">ZGRAPHQL_LANG β€” contains all the Language/Schema Implementation classes</LI><BR /> <LI id="3d38" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">ZGRAPHQL_API β€” contains base Service classes to expose CRM data as API</LI><BR /> <LI id="11ea" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">ZGRAPHQL_CORE β€” Base Server Classes</LI><BR /> </UL><BR /> <H2 id="dfe9" class="my kt bm bc bb fz mz na nb nc nd ne nf ng nh ni nj" data-selectable-paragraph="" id="toc-hId--1102332296">Message Class β€” ZGRAPHQL</H2><BR /> <H2 id="3645" class="my kt bm bc bb fz mz na nb nc nd ne nf ng nh ni nj" data-selectable-paragraph="" id="toc-hId--1298845801">Important Classes</H2><BR /> <P id="2a35" class="ka kb bm bc kc b kd mf kf mg kh mh kj mi kl mj kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLSCL_SCHEMA</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="b481" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">responsible for creating the GraphQL Schema (Type System)</LI><BR /> <LI id="7ad4" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">can create either from Internal Tables or from Database Tables</LI><BR /> <LI id="af34" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Validates and Creates Schema Object</LI><BR /> <LI id="1012" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">loads all required components into Memory</LI><BR /> <LI id="7dc8" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Builds Introspection Resolver and Holds User Resolver</LI><BR /> </UL><BR /> <P id="6472" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_API_SERVICE_BASE</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="26d3" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">Base SICF handler class to handle basic functionality of the GraphQL server</LI><BR /> <LI id="309b" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">All Sub-classes are only expected to provide Schema from PREPARE_SCHEMA method</LI><BR /> </UL><BR /> <P id="2900" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_TABLES_RESOLVER</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="9e89" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">which builds Schama using given ABAP table names</LI><BR /> </UL><BR /> <P id="2f33" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_BUIL_GENIL_RESOLVER</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="84af" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">Class to resolve all Business Partner related BOL operations</LI><BR /> <LI id="3d0a" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_BUIL_CUST_RESOLVER</STRONG>&nbsp;is a specialization which allows restricting</LI><BR /> <LI id="de99" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">BOL entities and Relationships exposed</LI><BR /> <LI id="69a6" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Mapping for the Relationship names</LI><BR /> </UL><BR /> <P id="5163" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLSIF_RESOLVER</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="516a" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">Resolver to provide links to Operation and Data</LI><BR /> <LI id="aa18" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">All Implementation classes must provide</LI><BR /> <LI id="d1f6" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Schema to be initiated</LI><BR /> <LI id="946e" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Resolving Method for each Operation</LI><BR /> </UL><BR /> <P id="7711" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLSCL_INTROSPEC_RESOLVER</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="22a0" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">responsible for resolving Schema Introspection queries</LI><BR /> </UL><BR /> <P id="2970" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZCX_GQL_EXCEPTION</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="83ab" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">Base Exception class available globally to throw Errors</LI><BR /> <LI id="1489" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">The server automatically handles this error and passes to Client as&nbsp;<STRONG class="kc nk">β€œerrors”</STRONG>&nbsp;array in response</LI><BR /> <LI id="a11d" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">can handle BOL errors and BAPI errors</LI><BR /> </UL><BR /> <P id="60dc" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_EXECUTION_CONTEXT</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="023e" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">the class is responsible for keeping the current Context until the end of the operation execution</LI><BR /> <LI id="27c5" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Validates the current Operation submitted by Client before starting the execution</LI><BR /> <LI id="d5c2" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">holds Schema, Operation currently being executed, Selections requested by Client</LI><BR /> </UL><BR /> <P id="e14b" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_EXECUTOR</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="a52b" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">the class is responsible for executing Query, Mutations and handle Errors</LI><BR /> </UL><BR /> <OL class=""><BR /> <LI id="f0ee" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn nl ll lm" data-selectable-paragraph="">EXECUTE_QUERY</LI><BR /> </OL><BR /> <UL class=""><BR /> <LI id="54fa" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">the method resolves Query operations</LI><BR /> </UL><BR /> <P id="0de8" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph="">2. EXECUTE_MUTATION</P><BR /> <BR /> <UL class=""><BR /> <LI id="757b" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">the method performs Mutation (update) operations</LI><BR /> </UL><BR /> <P id="e003" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph="">3. EXECUTE_SELECTION_SET</P><BR /> <BR /> <UL class=""><BR /> <LI id="b57b" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">method to resolve each field requested by the Client</LI><BR /> <LI id="ef8f" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">recursively resolves each Custom Type until it reaches Leaf Nodes</LI><BR /> </UL><BR /> <P id="ec62" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_LOG</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="879a" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">used for logging the requests to the GraphQL server</LI><BR /> <LI id="f33f" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">logs Request Operations, Response Errors</LI><BR /> <LI id="48d3" class="ka kb bm bc kc b kd ln kf lo kh lp kj lq kl lr kn lk ll lm" data-selectable-paragraph="">Transaction: SLG0, Object β€” ZGRAPHQL, Sub Object β€” ZGQL_GEN</LI><BR /> </UL><BR /> <P id="d4de" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn" data-selectable-paragraph=""><STRONG class="kc nk">ZGQLCL_UTILS</STRONG></P><BR /> <BR /> <UL class=""><BR /> <LI id="af3f" class="ka kb bm bc kc b kd ke kf kg kh ki kj kk kl km kn lk ll lm" data-selectable-paragraph="">utility functions for reading AppSet Entries and Domain Values</LI><BR /> </UL><BR /> &nbsp;<BR /> <BR /> The benefits I observe from this approach are:<BR /> <UL><BR /> <LI>Higher Abstraction of the API Implementation</LI><BR /> <LI>Easy Integration using widely popular toolchain on UI running outside SAP</LI><BR /> <LI>Above all, Performance can be tweaked within SAP as we run the logic near Database</LI><BR /> </UL><BR /> &nbsp;<BR /> <BR /> Even though I started this as an exploring project not knowing if I could finish it on my own or not, spending my evenings and holidays, After seeing the final result, I requested my Manager to spend some time for a small the Demo.<BR /> <BR /> Having no expectations, with the CRM experience my Manager immediately identified the potential of this solution and the advantages our Organization can get in exposing CRM data using GraphQL. With his support, I have managed to present to higher management.<BR /> <BR /> And today we are experimenting and building unified enterprise level services for multiple Web Applications with minimal friction and with High performance. The feedback from the Web developers is that 'Fast development times, Ease of use and more options in choosing client libraries'.<BR /> <BR /> &nbsp;<BR /> <P data-selectable-paragraph="">Other sources to track this topic.</P><BR /> <A href="https://medium.com/@hareeshbabu82/graphql-sap-abap-the-inspiration-eb6d4f305681" target="_blank" rel="nofollow noopener noreferrer">Part 1</A><BR /> <BR /> <A href="https://medium.com/@hareeshbabu82/graphql-sap-abap-demo-and-technical-details-92a511ed8e4a" target="_blank" rel="nofollow noopener noreferrer">Part 2</A> 2019-09-19T10:04:04+02:00 https://community.sap.com/t5/technology-blogs-by-members/logging-incoming-requests-when-creating-short-urls/ba-p/13456675 Logging incoming requests when creating short URLs 2020-01-14T14:40:02+01:00 former_member190389 https://community.sap.com/t5/user/viewprofilepage/user-id/190389 Based on my previous blog&nbsp; - <A href="https://blogs.sap.com/2018/12/20/sicf-on-pipo-single-stack-shortening-long-urls-for-customers-inbound./" target="_blank" rel="noopener noreferrer">SICF on PI/PO</A> , We may need to log incoming requests or process the incoming requests (e.g delete few header /prolog etc.) as per our needs before sending it to the adapter.<BR /> <BR /> But the challenge here is you cannot in anyway touch the actual request -&nbsp; the moment you do it - the stream will be all used up and nothing will be forwarded to Adapter resulting in an exception.<BR /> <BR /> This challenge can be mitigated by making a copy of the request first and then using a wrapper to access the content.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/copy.png" height="83" width="711" /><BR /> <BR /> The wrapper can also be used to read the content and ignore the request or else strip some unwanted data from it and then pass on the adapter .<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/read.png" height="149" width="388" /><BR /> <BR /> &nbsp;<BR /> <BR /> Here after stripping the unwanted content and extracting the xml -it is posted to the&nbsp; adapter.<BR /> <BR /> The Loggers are used to log data in debug mode and can be viewed in&nbsp; /nwa/logs<BR /> <BR /> The resetInputStream changes the content to the xml and we forward the wrapper instead of the actual request.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/strip-1.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <BR /> If you are not OK with the content you can send back a custom response like here 406<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/response.png" /><BR /> <BR /> Here I show you an example with OAGI format&nbsp; which sends a Prolog sort of text&nbsp; before actual xml.<BR /> <BR /> The xml starts after the string "Payload="<BR /> <BR /> (PS: I am using fasterjackson library to convert objects into string)<BR /> <BR /> The servlet with doPost<BR /> <PRE class="language-java"><CODE>package com.mycompany;<BR /> <BR /> import java.io.IOException;<BR /> import java.io.PrintWriter;<BR /> import java.util.logging.Logger;<BR /> <BR /> import javax.servlet.RequestDispatcher;<BR /> import javax.servlet.ServletContext;<BR /> import javax.servlet.ServletException;<BR /> import javax.servlet.http.HttpServlet;<BR /> import javax.servlet.http.HttpServletRequest;<BR /> import javax.servlet.http.HttpServletResponse;<BR /> <BR /> import com.mycompany.logging.entity.LoggingRequest;<BR /> import com.mycompany.logging.wrapper.LoggingHttpServletRequestWrapper;<BR /> import com.mycompany.logging.wrapper.LoggingHttpServletResponseWrapper;<BR /> import com.fasterxml.jackson.core.JsonProcessingException;<BR /> import com.sap.tc.logging.Configurator;<BR /> import com.sap.tc.logging.Log;<BR /> import com.sap.tc.logging.Severity;<BR /> import com.fasterxml.jackson.core.JsonProcessingException;<BR /> import com.fasterxml.jackson.databind.ObjectMapper;<BR /> <BR /> <BR /> <BR /> /**<BR /> * Servlet implementation class <BR /> */<BR /> public class oagi extends HttpServlet {<BR /> <BR /> private static final long serialVersionUID = 1L;<BR /> <BR /> private static final com.sap.tc.logging.Location LOGGER = com.sap.tc.logging.Location.getLocation(oagi.class);<BR /> ObjectMapper ObjectMapper = new ObjectMapper();<BR /> <BR /> /**<BR /> * @see HttpServlet#HttpServlet()<BR /> */<BR /> public oagi() {<BR /> super();<BR /> <BR /> // TODO Auto-generated constructor stub<BR /> }<BR /> <BR /> /**<BR /> * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)<BR /> */<BR /> protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<BR /> LOGGER.entering();<BR /> <BR /> response.getWriter().append("Served at: ").append(request.getContextPath());<BR /> LOGGER.exiting();<BR /> }<BR /> <BR /> /**<BR /> * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)<BR /> */<BR /> protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<BR /> LOGGER.entering("doPost");<BR /> <BR /> //PrintWriter out= response.getWriter();<BR /> ServletContext web1 = getServletContext();<BR /> ServletContext web2 = web1.getContext("/HttpAdapter");<BR /> <BR /> //send to adapter<BR /> RequestDispatcher rd = <BR /> web2.getRequestDispatcher("/HttpMessageServlet?interfaceNamespace=http://OAGI/test&amp;interface=SI_OrderRequest_OutS&amp;senderService=BC_EU_Orders_OAGI&amp;qos=BE");<BR /> <BR /> HttpServletRequest httpRequest = (HttpServletRequest) request;<BR /> HttpServletResponse httpResponse = (HttpServletResponse) response;<BR /> <BR /> LoggingHttpServletRequestWrapper requestWrapper = new LoggingHttpServletRequestWrapper(httpRequest);<BR /> <BR /> <BR /> String content = requestWrapper.getContent();<BR /> <BR /> String searchit = "PAYLOAD=";<BR /> <BR /> if(!content.isEmpty()) <BR /> {<BR /> <BR /> if(!(content.startsWith("&lt;")))<BR /> {<BR /> LOGGER.debugT("original content : " + content); <BR /> int off = content.indexOf(searchit);<BR /> if(off != -1)<BR /> {<BR /> off = off + searchit.length();<BR /> content = content.substring(off);<BR /> LOGGER.debugT("Stripped content : " + content); <BR /> requestWrapper.resetInputStream(content);<BR /> content = null;<BR /> <BR /> LOGGER.exiting("doPost" );<BR /> rd.forward(requestWrapper, response);<BR /> }<BR /> else<BR /> {<BR /> LOGGER.errorT("REQUEST: " + getRequestDescription(requestWrapper) +"Content:" +content );<BR /> <BR /> httpResponse.reset();<BR /> httpResponse.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); //send 406<BR /> httpResponse.setHeader("Location", requestWrapper.getLocalAddr());<BR /> return; <BR /> }<BR /> }<BR /> else <BR /> {<BR /> content = null;<BR /> LOGGER.exiting("doPost" );<BR /> rd.forward(requestWrapper, response);<BR /> }<BR /> <BR /> return;<BR /> }<BR /> <BR /> <BR /> <BR /> }<BR /> <BR /> <BR /> <BR /> protected String getRequestDescription(LoggingHttpServletRequestWrapper requestWrapper) {<BR /> LoggingRequest loggingRequest = new LoggingRequest();<BR /> loggingRequest.setSender(requestWrapper.getRemoteAddr());<BR /> loggingRequest.setMethod(requestWrapper.getMethod());<BR /> <BR /> loggingRequest.setUser(requestWrapper.getRemoteUser());<BR /> loggingRequest.setParams(requestWrapper.isFormPost() ? null : requestWrapper.getParameters());<BR /> loggingRequest.setHeaders(requestWrapper.getHeaders());<BR /> <BR /> //String content = requestWrapper.getContent();<BR /> <BR /> //loggingRequest.setBody(content);<BR /> <BR /> <BR /> try {<BR /> return ObjectMapper.writeValueAsString(loggingRequest);<BR /> } catch (JsonProcessingException e) {<BR /> LOGGER.warningT("Cannot serialize Request to JSON"+ e.getMessage());<BR /> return null;<BR /> }<BR /> }<BR /> <BR /> }<BR /> </CODE></PRE><BR /> &nbsp;<BR /> <BR /> Sample Request Wrapper (most of the methods are not used)<BR /> <PRE class="language-java"><CODE>package com.mycompany.logging.wrapper;<BR /> <BR /> import java.io.BufferedReader;<BR /> import java.io.ByteArrayInputStream;<BR /> import java.io.IOException;<BR /> import java.io.InputStream;<BR /> import java.io.InputStreamReader;<BR /> import java.io.UncheckedIOException;<BR /> import java.nio.charset.StandardCharsets;<BR /> import java.util.Arrays;<BR /> import java.util.Collections;<BR /> import java.util.Enumeration;<BR /> import java.util.HashMap;<BR /> import java.util.Iterator;<BR /> import java.util.Map;<BR /> import java.util.Map.Entry;<BR /> import java.util.Set;<BR /> import java.util.stream.Collectors;<BR /> <BR /> import javax.servlet.ServletInputStream;<BR /> import javax.servlet.http.HttpServletRequest;<BR /> import javax.servlet.http.HttpServletRequestWrapper;<BR /> <BR /> import org.apache.commons.io.IOUtils;<BR /> import org.apache.commons.lang3.ArrayUtils;<BR /> import org.apache.commons.lang3.StringUtils;<BR /> <BR /> import com.mycompany.logging.OAGIFilter;<BR /> <BR /> public class LoggingHttpServletRequestWrapper extends HttpServletRequestWrapper {<BR /> <BR /> private static final com.sap.tc.logging.Location LOGGER = com.sap.tc.logging.Location.getLocation(LoggingHttpServletRequestWrapper.class);<BR /> <BR /> private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";<BR /> <BR /> private static final String METHOD_POST = "POST";<BR /> <BR /> private byte[] content;<BR /> <BR /> private final Map&lt;String, String[]&gt; parameterMap;<BR /> <BR /> private final HttpServletRequest delegate;<BR /> private LoggingServletInputStream servletStream;<BR /> <BR /> <BR /> <BR /> public LoggingHttpServletRequestWrapper(HttpServletRequest request) {<BR /> super(request);<BR /> this.delegate = request;<BR /> this.servletStream = new LoggingServletInputStream();<BR /> <BR /> if (isFormPost()) {<BR /> this.parameterMap = request.getParameterMap();<BR /> } else {<BR /> this.parameterMap = Collections.emptyMap();<BR /> }<BR /> }<BR /> <BR /> public void resetInputStream(String content) {<BR /> this.content = content.getBytes();<BR /> <BR /> LOGGER.debugT("content : " + new String(this.content)); <BR /> <BR /> servletStream.is = new ByteArrayInputStream(content.getBytes());<BR /> }<BR /> <BR /> @Override<BR /> public ServletInputStream getInputStream() throws IOException {<BR /> <BR /> if (ArrayUtils.isEmpty(content)) {<BR /> return delegate.getInputStream();<BR /> }<BR /> LOGGER.debugT("inputstream content : " + new String(this.content)); <BR /> return new LoggingServletInputStream(content);<BR /> }<BR /> <BR /> @Override<BR /> public BufferedReader getReader() throws IOException {<BR /> if (ArrayUtils.isEmpty(content)) {<BR /> return delegate.getReader();<BR /> }<BR /> return new BufferedReader(new InputStreamReader(getInputStream()));<BR /> }<BR /> <BR /> @Override<BR /> public String getParameter(String name) {<BR /> if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {<BR /> return super.getParameter(name);<BR /> }<BR /> String[] values = this.parameterMap.get(name);<BR /> if (values != null &amp;&amp; values.length &gt; 0) {<BR /> return values[0];<BR /> }<BR /> return Arrays.toString(values);<BR /> }<BR /> <BR /> @Override<BR /> public Map&lt;String, String[]&gt; getParameterMap() {<BR /> if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {<BR /> return super.getParameterMap();<BR /> }<BR /> return this.parameterMap;<BR /> }<BR /> <BR /> @Override<BR /> public Enumeration&lt;String&gt; getParameterNames() {<BR /> if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {<BR /> return super.getParameterNames();<BR /> }<BR /> return new ParamNameEnumeration(this.parameterMap.keySet());<BR /> }<BR /> <BR /> @Override<BR /> public String[] getParameterValues(String name) {<BR /> if (ArrayUtils.isEmpty(content) || this.parameterMap.isEmpty()) {<BR /> return super.getParameterValues(name);<BR /> }<BR /> return this.parameterMap.get(name);<BR /> }<BR /> <BR /> public String getContent() {<BR /> try {<BR /> if (this.parameterMap.isEmpty()) {<BR /> content = IOUtils.toByteArray(delegate.getInputStream());<BR /> } else {<BR /> content = getContentFromParameterMap(this.parameterMap);<BR /> }<BR /> String requestEncoding = delegate.getCharacterEncoding();<BR /> String normalizedContent = StringUtils.normalizeSpace(new String(content, requestEncoding != null ? requestEncoding : StandardCharsets.UTF_8.name()));<BR /> return StringUtils.isBlank(normalizedContent) ? "[EMPTY]" : normalizedContent;<BR /> } catch (IOException e) {<BR /> throw new UncheckedIOException(e);<BR /> }<BR /> }<BR /> <BR /> <BR /> <BR /> <BR /> private byte[] getContentFromParameterMap(Map&lt;String, String[]&gt; parameterMap) {<BR /> return parameterMap.entrySet().stream().map(e -&gt; {<BR /> String[] value = e.getValue();<BR /> return e.getKey() + "=" + (value.length == 1 ? value[0] : Arrays.toString(value));<BR /> }).collect(Collectors.joining("&amp;")).getBytes();<BR /> }<BR /> <BR /> public Map&lt;String, String&gt; getHeaders() {<BR /> Map&lt;String, String&gt; headers = new HashMap&lt;&gt;(0);<BR /> Enumeration&lt;String&gt; headerNames = getHeaderNames();<BR /> while (headerNames.hasMoreElements()) {<BR /> String headerName = headerNames.nextElement();<BR /> if (headerName != null) {<BR /> headers.put(headerName, getHeader(headerName));<BR /> }<BR /> }<BR /> return headers;<BR /> }<BR /> <BR /> public Map&lt;String, String&gt; getParameters() {<BR /> return getParameterMap().entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -&gt; {<BR /> String[] values = e.getValue();<BR /> return values.length &gt; 0 ? values[0] : "[EMPTY]";<BR /> }));<BR /> }<BR /> <BR /> public boolean isFormPost() {<BR /> String contentType = getContentType();<BR /> return (contentType != null &amp;&amp; contentType.contains(FORM_CONTENT_TYPE) &amp;&amp; METHOD_POST.equalsIgnoreCase(getMethod()));<BR /> }<BR /> <BR /> <BR /> private class ParamNameEnumeration implements Enumeration&lt;String&gt; {<BR /> <BR /> private final Iterator&lt;String&gt; iterator;<BR /> <BR /> private ParamNameEnumeration(Set&lt;String&gt; values) {<BR /> this.iterator = values != null ? values.iterator() : Collections.emptyIterator();<BR /> }<BR /> <BR /> @Override<BR /> public boolean hasMoreElements() {<BR /> return iterator.hasNext();<BR /> }<BR /> <BR /> @Override<BR /> public String nextElement() {<BR /> return iterator.next();<BR /> }<BR /> }<BR /> <BR /> private class LoggingServletInputStream extends ServletInputStream {<BR /> <BR /> private InputStream is;<BR /> <BR /> private LoggingServletInputStream() {<BR /> <BR /> }<BR /> <BR /> private LoggingServletInputStream(byte[] content) {<BR /> this.is = new ByteArrayInputStream(content);<BR /> }<BR /> <BR /> public boolean isFinished() {<BR /> return true;<BR /> }<BR /> <BR /> public boolean isReady() {<BR /> return true;<BR /> }<BR /> <BR /> <BR /> <BR /> @Override<BR /> public int read() throws IOException {<BR /> return this.is.read();<BR /> }<BR /> <BR /> @Override<BR /> public void close() throws IOException {<BR /> super.close();<BR /> is.close();<BR /> }<BR /> }<BR /> <BR /> <BR /> }<BR /> <BR /> <BR /> </CODE></PRE><BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <BR /> Logging Request<BR /> <PRE class="language-java"><CODE>package com.mycompany.logging.entity;<BR /> <BR /> import java.io.Serializable;<BR /> import java.util.Map;<BR /> <BR /> public class LoggingRequest implements Serializable {<BR /> <BR /> private static final long serialVersionUID = -4702574169916528738L;<BR /> <BR /> private String sender;<BR /> <BR /> private String method;<BR /> <BR /> private String user;<BR /> <BR /> <BR /> private Map&lt;String, String&gt; params;<BR /> <BR /> private Map&lt;String, String&gt; headers;<BR /> <BR /> private String body;<BR /> <BR /> public String getSender() {<BR /> return sender;<BR /> }<BR /> <BR /> public void setSender(String sender) {<BR /> this.sender = sender;<BR /> }<BR /> <BR /> public String getMethod() {<BR /> return method;<BR /> }<BR /> <BR /> public void setMethod(String method) {<BR /> this.method = method;<BR /> }<BR /> <BR /> public String getUser() {<BR /> return user;<BR /> }<BR /> <BR /> <BR /> <BR /> public void setUser(String user) {<BR /> this.user = user;<BR /> }<BR /> <BR /> public Map&lt;String, String&gt; getParams() {<BR /> return params;<BR /> }<BR /> <BR /> public void setParams(Map&lt;String, String&gt; params) {<BR /> this.params = params;<BR /> }<BR /> <BR /> public Map&lt;String, String&gt; getHeaders() {<BR /> return headers;<BR /> }<BR /> <BR /> public void setHeaders(Map&lt;String, String&gt; headers) {<BR /> this.headers = headers;<BR /> }<BR /> <BR /> public String getBody() {<BR /> return body;<BR /> }<BR /> <BR /> public void setBody(String body) {<BR /> this.body = body;<BR /> }<BR /> }<BR /> </CODE></PRE><BR /> &nbsp;<BR /> <BR /> Example : Error logged in /nwa/logs when payload had some irregular data:<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/error-1.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> Sample wrong payload<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/test-2.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <BR /> Sample unacceptable response<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/not-1.png" /><BR /> <BR /> Here the request processing takes place in the doPost method itself - alternatively you can create a Filter in the project and use that to process the request.<BR /> <BR /> Hope this would be helpful !<BR /> <BR /> &nbsp; 2020-01-14T14:40:02+01:00 https://community.sap.com/t5/technology-blogs-by-members/now-get-target-server-root-certificate-in-the-comfort-of-your-own-browser/ba-p/13457036 Now get target server root certificate in the comfort of your own browser without getting your local IP white-listed 2020-01-15T06:27:09+01:00 former_member190389 https://community.sap.com/t5/user/viewprofilepage/user-id/190389 &nbsp;<BR /> <BR /> My obsession with this new way of hitting the PI server using servlets took new turn when a brilliant colleague of mine wanted me to program a way to get target root certificate without having to ask them for or without running a command on your cloud which is harder to access.<BR /> <BR /> And now get the target server root certificates without getting your local IP white-listed ,in the comfort of your own browser, through PI server. (Of course, your server should be able to access the target)<BR /> <BR /> It takes the URL as a query parameter&nbsp; and will print out the full chain of certificates and also write the root certificate which you can trust in your keystore&nbsp; in. CER format<BR /> <BR /> Here is&nbsp; a sample for google<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/cert-2.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> Scroll down to get&nbsp; X509 Certificate as .CER:<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/google.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> Here is the snippet which does the job for you.<BR /> <BR /> To build the servlet you can always refer to <A href="https://blogs.sap.com/2018/12/20/sicf-on-pipo-single-stack-shortening-long-urls-for-customers-inbound./" target="_blank" rel="noopener noreferrer">SICF on Pi/PO</A><BR /> <BR /> To enable logging you can refer to&nbsp; <A href="https://blogs.sap.com/2020/01/14/logging-incoming-requests-when-creating-short-urls/" target="_blank" rel="noopener noreferrer">Logging Incoming Requests</A><BR /> <PRE class="language-java"><CODE>protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<BR /> <BR /> try <BR /> {<BR /> String url = request.getParameter("url");<BR /> final String LINE_SEPARATOR = System.getProperty("line.separator");<BR /> <BR /> <BR /> HttpsURLConnection connection = (HttpsURLConnection) new URL(null,url,new sun.net.www.protocol.https.Handler()).openConnection();<BR /> connection.setRequestMethod("GET");<BR /> connection.connect();<BR /> <BR /> Certificate[] certs = connection.getServerCertificates();<BR /> for (Certificate cert : certs) {<BR /> <BR /> <BR /> response.getWriter().append("Certificate is : " + cert);<BR /> }<BR /> <BR /> if (certs == null || certs.length == 0 || (!(certs[0] instanceof X509Certificate))) {<BR /> throw new SSLPeerUnverifiedException("No server's end-entity certificate");<BR /> }<BR /> <BR /> X509Certificate x509cert = ((X509Certificate) certs[0]);<BR /> <BR /> Base64.Encoder encoder = Base64.getMimeEncoder(64, LINE_SEPARATOR.getBytes());<BR /> String cert_begin = "-----BEGIN CERTIFICATE-----\n";<BR /> String end_cert = "\n-----END CERTIFICATE-----";<BR /> <BR /> byte[] derCert = x509cert.getEncoded();<BR /> <BR /> String pemCertPre = new String(encoder.encode(derCert));<BR /> String pemCert = cert_begin + pemCertPre + end_cert;<BR /> <BR /> <BR /> <BR /> response.getWriter().append("X509 Certificate in encoded form : \n").append(pemCert);<BR /> <BR /> } catch (Exception e) {<BR /> // TODO Auto-generated catch block<BR /> response.getWriter().append("Exception occured : ").append(e.getMessage() +" :");<BR /> <BR /> e.printStackTrace(response.getWriter());<BR /> }<BR /> <BR /> <BR /> }</CODE></PRE><BR /> &nbsp;<BR /> <BR /> if the below snippet shows error, on the underlined part , you need to set the access restrictions to Warning as shown in the next picture.<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/ip.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/fb.png" /><BR /> <BR /> The import section:<BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/01/import.png" /><BR /> <BR /> &nbsp;<BR /> <BR /> This code was tested by my colleague and for him the formatting did not render properly on Microsoft edge but worked on Firefox and Chrome.<BR /> <BR /> Disclaimer : We are not having this for productive use and so should you.<BR /> <BR /> &nbsp;<BR /> <BR /> Regards<BR /> <BR /> Fariha 2020-01-15T06:27:09+01:00 https://community.sap.com/t5/technology-blogs-by-members/remediation-of-uncalled-active-icf-services/ba-p/13435159 Remediation of uncalled active ICF services 2020-06-18T10:23:53+02:00 JoeGoerlich https://community.sap.com/t5/user/viewprofilepage/user-id/2716 <H3 id="toc-hId-1060742432">Web applications and web services to rule them all</H3><BR /> &nbsp;<BR /> <H6 id="toc-hId-1251477084">Updates:<BR /> 2022-11-07: Added hint for new secure-by-default logs.</H6><BR /> <BR /> <HR /><BR /> <BR /> The time of SAP GUI as a proprietary interface for every user interaction seems to be over soon. SAP is moving towards serving more and more applications as web applications or web services - be it SAP Fiori, be it the Webgui utilising Screen Personas or OData or REST APIs.<BR /> <BR /> While this change counts in for sure to better user experience or using open standards for interfaces (e.g. towards cloud services), it also effects the SAP Basis Administrator:<BR /> A SAP basis administrator nowadays need to have a deep understanding of web technologies (additionally to understanding the SAP application server, the database as well as to the operating system like linux).<BR /> <BR /> I will not go into detail about the journey from the integration of Internet Communication Manager (ICM) as web server into SAP NW AS ABAP/Java or what to consider in general when operating a web server within your ERP system - maybe even exposed to the internet.<STRONG><BR /> In this blogpost I would like to catch up a topic which is more related to the content served by the Internet Communication Framework (ICF) through the ICM</STRONG>.<BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId-667715422">β€œPlease activate one or the other ICF service - it is urgent!”</H3><BR /> &nbsp;<BR /> <BR /> In the early days - before "Secure by Default" was a claim - SAP NetWeaver AS ABAP based systems were <STRONG>shipped with certain ICF services in status "activated".</STRONG> In addition to that, SAP Basis Administrators often where asked on short notice to quickly activate one or the other service in transaction SICF until some urgent needed functionality works. So it happened that there were more ICF service activated then ever needed, especially in the production system.<BR /> <BR /> While in the meantime SAP released many security notes to fix vulnerabilities in the SAP standard ICF services, <STRONG>in many systems a number of 500 up to more than 3000 ICF services are still enabled although they are not needed nor called</STRONG>.<BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId-471201917">Bring some light into the darkness</H3><BR /> <SPAN style="color: #ff0000"><STRONG>Please note: As of SAP S/HANA-Version 2022 / ABAP Platform 2022 the so called <EM>secure-by-default logs</EM> for ICF calls are collected. These logs can be evaluated in transaction SBDLOG. Those can be used to activate all ICF services that have ever been logged or deactivate services that were not logged by choosing '<SPAN class="ph menucascade"><SPAN class="ph uicontrol">Menu' -&gt; '</SPAN><SPAN class="ph uicontrol">ICF Integration' in transaction SBDLOG</SPAN></SPAN>. With this, the manual steps described below can be omitted.</STRONG></SPAN><BR /> <BR /> &nbsp;<BR /> <BR /> At first lets export a list of the active ICF services of <STRONG>all of your systems (Dev, Int, Prod)</STRONG>&nbsp;by the following steps:<BR /> <OL><BR /> <LI>Go to transaction SE38.</LI><BR /> <LI>Execute report RS_ICF_SERV_ADMIN_TASKS.</LI><BR /> <LI>Choose β€œExport of Active Services to CSV File” and execute to save the file, e.g. as &lt;SID&gt;_active_services.csv<BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/06/Bildschirmfoto-2020-06-17-um-10.03.29.png" height="391" width="403" /></LI><BR /> <LI>Again. Repeat these steps for every system in your landscape.</LI><BR /> </OL><BR /> To give yourself an overview about the active ICF services in your systems take a look at the exported lists. By screening every list you will figure out that <STRONG>the active services in the various systems of a system landscape will be different from system to system</STRONG>.<BR /> <BLOCKQUOTE>Please be aware <STRONG>for some services there is a good reason to be active in development or integration system only!</STRONG> There are services used for development purposes or there might be services for features which are not yet released to production. Besides that, there are web applications used only for one time tasks or initial setup. Or active services differ simply due to configuration drift.</BLOCKQUOTE><BR /> The main purpose of this lists is to use it a) <STRONG>as a template</STRONG> for the deactivation of uncalled ICF services in the next steps and b) <STRONG>as a backup</STRONG> to quickly mass re-activate all former active services in emergency case.<BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId-274688412">What about service usage?</H3><BR /> &nbsp;<BR /> <BR /> It is strictly advisable to activate ICF services only if they are necessary for business needs in order <STRONG>to reduce the attack surface</STRONG>. Often there is no documentation which services are involved in what business functionality, some of them are even involved in more than one since they provide a framework&nbsp; functionality (e.g. for Webdynpro ABAP or BSP). So it might be hard for you to determine which service is truly needed in the first.<BR /> <BR /> As a quick win you could analyse how often each service were called in <STRONG>each of your systems</STRONG>. To do so there are at least three methods:<BR /> <OL><BR /> <LI>extract the relevant info from the HTTP access log of the ICM (parameter icm/HTTP/logging_&lt;xx&gt;).</LI><BR /> <LI>enable and evaluate the ICF trace using report RS_ICF_SERV_ADMIN_TASKS as mentioned in SAP note 2430473.</LI><BR /> <LI>evaluate the Web Statistics.</LI><BR /> </OL><BR /> Method 1 might be time consuming to gather all log files and grep and normalize the relevant parts.<BR /> <BR /> Method 2 could have a performance impact as of SAP note 1611713.<BR /> <BR /> Since Web Statistics are collected by default I recommend to use <STRONG>method 3</STRONG>. However, I like to advise to prolong the retention period of Web Statistics for this purpose in advance by following these steps:<BR /> <OL><BR /> <LI>Go to transaction ST03.</LI><BR /> <LI>Go to Collector and Performance DB -&gt; Performance Database -&gt; Workload Collector Database -&gt; Reorganization -&gt; Control.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/06/Bildschirmfoto-2020-06-17-um-10.12.55.png" height="371" width="283" /></LI><BR /> <LI>Adjust retention period for the WEB statistics, e.g. TOTAL Monthly Aggregates Retention Period for `VC` and `VD`.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/06/Bildschirmfoto-2020-06-17-um-10.15.03.png" height="195" width="722" /></LI><BR /> <LI>Again: Repeat these steps for every system in your landscape.</LI><BR /> </OL><BR /> &nbsp;<BR /> <BR /> <STRONG>After a reasonable time frame</STRONG> in which the Web Statistics for relevant phases (month end closing or alike) have been collected, the statistics can be exported by following these steps:<BR /> <OL><BR /> <LI>Go to transaction ST03.</LI><BR /> <LI>Expand Workload -&gt; Total -&gt; Month -&gt; and perform for each available month the following steps.</LI><BR /> <LI>Expand Analysis Views -&gt; Web Statistics -&gt; WEB Server Statistics.</LI><BR /> <LI>Switch to tab β€œURL”.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/06/Bildschirmfoto-2020-06-17-um-10.20.05.png" /><SPAN style="font-size: 1rem"><B>Note:</B> The Web Statistics may contain entries for services which are not even present in the system but are requested for example by a scanning server.<BR /> <B>Hint: </B>You may have noticed the column β€œProtocol” indicates if a service is called via HTTP or HTTPS.</SPAN></LI><BR /> <LI>Export the list of URLs as spreadsheet (β€œExcel (in Office 2007 XLSX Format)β€œ) using the β€œExport” button. (Note: available as of SAP_BASIS 7.02).</LI><BR /> <LI>Again: Repeat these steps for every system in your landscape.</LI><BR /> </OL><BR /> &nbsp;<BR /> <H3 id="toc-hId-78174907">Put the puzzle together</H3><BR /> &nbsp;<BR /> <BR /> Now it is time to compare the list of active services with the list of called services to prepare the cleanup.<BR /> <BR /> You can also think of comparing the lists across your system landscape. As already stated, for your development and integration system you should consider that there are indeed services active which are necessary for development purposes only or services which are in acceptance test and not yet set productive. These should be evaluated for the relevant systems. Talk to the developer team!<BR /> <BR /> Besides that there may also be web applications activated which are used only for initial setup tasks, e.g. SAML2 configuration or troubleshooting e.g. sec_diag_tool. You should consider to activate these only at configuration time or as long as needed.<BR /> <BR /> There may also be services which are actively used in the production system but are not activated in one or another system of the system landscape. It's time to <STRONG>eliminate this configuration drift</STRONG>.<BR /> <BR /> Ok, lets start the preparation:<BR /> <OL><BR /> <LI>Copy the export of active services CSV file as a template.</LI><BR /> <LI>Remove all lines which have an entry in column REF_PATH. These are aliases which point to a service node. Aliases can not be deactivated, but they will stop working if the original service is disabled.</LI><BR /> <LI>Remove all lines for called services and their parent paths from the list so that you end up with a list of ICF services which can be deactivated in the relevant system.</LI><BR /> <LI>Make sure no parent node stays in the list to be deactivated while it has sub nodes which should remain active. This would lead to so called "stale nodes" and non-working services. For example if /sap/bc/echo should stay active remove /sap, /sap/bc and /sap/bc/echo from the list.</LI><BR /> <LI>Save this list of your to-be-deactivated services as CSV file.</LI><BR /> <LI>Again: Repeat these steps for every system in your landscape.</LI><BR /> </OL><BR /> &nbsp;<BR /> <H3 id="toc-hId--118338598">Time to cleanup</H3><BR /> &nbsp;<BR /> <BR /> Now you can start deactivating unused services in each system with your processed templates by following these steps:<BR /> <OL><BR /> <LI>Open the list of active ICF services, e.g. &lt;SID&gt;_acvite_services.csv and doublecheck if it holds only to-be-deactivated ICF services.</LI><BR /> <LI>Go to transaction SE38.</LI><BR /> <LI>Execute report RS_ICF_SERV_MASS_PROCESSING.</LI><BR /> <LI>Select β€œDeactivate ICF Services for All Virtual Hosts Listed in CSV File” and execute.<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2020/06/Bildschirmfoto-2020-06-17-um-10.21.16.png" height="470" width="359" /></LI><BR /> <LI>Confirm the popup and upload the list to deactivate all services in the list.</LI><BR /> <LI>Again: Repeat these steps for every system in your landscape.</LI><BR /> </OL><BR /> Did you reveal a configuration drift? Activate the missing services in the relevant systems either manually in transaction SICF or by preparing a CSV file for mass activation.<BR /> <BR /> Maybe you have overlooked some parent paths during creation of the list and they have now been deactivated accidentally. This is not that fatal. You simply can perform a fresh "Export of Active Services to CSV File" and use this to β€œActivate ICF Services for All Virtual Hosts Listed in CSV File”. The activation logic of the report will then take care of activating also all necessary parent nodes.<BR /> <BR /> Keep in mind if something goes totally wrong you have also the initial export of active services as a backup which can be used to mass activate everything again as it was before.<BR /> <BR /> &nbsp;<BR /> <BR /> <EM>Just as a side note:</EM><BR /> <EM>The UCON HTTP whitelist scenario or former HTTP_WHITELIST does not serve for the same purpose as the UCON RFC Basic scenario. Meaning it does not affect the general reachability of certain paths through ICM! But this is a topic for another blogpost.</EM><BR /> <BR /> &nbsp;<BR /> <H3 id="toc-hId--314852103">Whats next?</H3><BR /> &nbsp;<BR /> <BR /> After finishing the deactivation in all systems, I recommend to "Export of Active Services to CSV File" once again. With this list you should <STRONG>start documenting your web services and their business need</STRONG>.<BR /> <BR /> I started to collect information about SAP standard ICF services in <A href="https://github.com/J-Goerlich/Standard_ICF_services" target="_blank" rel="nofollow noopener noreferrer">https://github.com/J-Goerlich/Standard_ICF_services</A> which may give you a hint if a service is needed in your scenario.<BR /> <BR /> &nbsp; 2020-06-18T10:23:53+02:00 https://community.sap.com/t5/enterprise-resource-planning-blogs-by-members/smaintenance-automation-of-regular-maintenance-tasks/ba-p/13522648 SMAINTENANCE - Automation Of Regular Maintenance Tasks 2022-01-10T13:18:59+01:00 bhargav_bhatt https://community.sap.com/t5/user/viewprofilepage/user-id/209408 Hi All,<BR /> <BR /> Being a SAP Admin you need to frequently deal with various maintenance activities involving application database or operating system. SAP has come up with tcode - SMAINTENANCE to make SAP application ramp down/up activity easy for us.<BR /> <BR /> SMAINTENANCE tcode can be used to monitor and control the status of the system in terms of the so called "Maintenance Mode".<BR /> <BR /> &nbsp;<BR /> <BR /> <B>Prerequisite</B><BR /> <BR /> For using smaintenance t-code we need to create security policy from t-code SECPOL.&nbsp; Below two attributes are mandatory to use smaintenance t-code<BR /> <BR /> TENANT_RUNLEVEL_LOGON_PRIVILEGE = 1<BR /> <BR /> SERVER_LOGON_PRIVILEGE = 1<BR /> <BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/01/3-11.png" /></P><BR /> &nbsp;<BR /> <BR /> This security policy needs to be assigned to all system Admin users in SU01 which needs to be allowed when system is in maintenance mode<BR /> <BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/01/4-14.png" /></P><BR /> &nbsp;<BR /> <BR /> Below is the screen of t-code smaintenance<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/01/1-16.png" /></P><BR /> &nbsp;<BR /> <BR /> When we "Switch to Maintenance" only Admin users who are having security policy assigned are allowed to login to system and perform maintenance activity.<BR /> <BR /> Remaining all users including system users are blocked against SAP logon.<BR /> <BR /> <B>Switch to Maintenance</B><BR /> <BR /> The action "Switch to Maintenance" starts a workflow which sets the system to mode "In Maintenance". The workflow consists of the modes as described above: "Running -&gt; Web dispatcher closed -&gt; Cool down -&gt; In Maintenance". The current duration of the whole workflow is <STRONG>10 minutes</STRONG>.<BR /> <BR /> <B>Switch to Running</B><BR /> <BR /> The action "Switch to Running" starts a workflow which sets the system to mode "Running". The workflow consists of the modes "In Maintenance -&gt; Running". The switch to "Running" is performed immediately.<BR /> <BR /> &nbsp;<BR /> <BR /> On the top current system status is displayed as show in below<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/01/2-11.png" /></P><BR /> &nbsp;<BR /> <BR /> There are different modes system goes through:<BR /> <BR /> <B>Running (Runlevel - 0)</B><BR /> The system is fully operational for business end users and administrator. There are no restrictions for services (e.g. batch, rfc, ...) or for inbound and outbound connections.<BR /> <BR /> <B>Web dispatcher Closed (Runlevel - 4)</B><BR /> The system is fully operational for business end users and administrators but connections to the Web dispatcher from outside are blocked. That is users connected via the web dispatcher cannot work any longer.<BR /> <BR /> <B>Cool down (Runlevel - 9)</B><BR /> The system is fully operational for administrators only. Business users are expected to finish their work and logout. At the end of the cool down phase business end users' sessions are terminated.<BR /> <BR /> <B>In Maintenance (Runlevel - 100)</B><BR /> The system is fully operational for administrators only. Administrators are users which have a special security policy assigned. There are no business end users logged onto the system.<BR /> <BR /> <B>Error during mode switch</B><BR /> An error occurred during a system mode switch.<BR /> <BR /> &nbsp;<BR /> <BR /> &nbsp;<BR /> <H4 id="toc-hId-1217446181">Below you see the so called "Maintenance Periods" and a "Log" of the maintenance mode.</H4><BR /> &nbsp;<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/01/5-8.png" /></P><BR /> &nbsp;<BR /> <BR /> <B>Maintenance Periods</B><BR /> The maintenance periods are time periods in which the system is set to Maintenance Mode or where the system is shut down. Maintenance Periods may be defined by external monitoring tools or from within the system by creating a new entry in this view. Only entries created from within the system may be deleted here.<BR /> <BR /> There is no automatic mode switch implemented according to this schedule. By default the schedule of the current year is displayed. Use the selection button to modify this default selection.<BR /> <BR /> &nbsp;<BR /> <BR /> <B>Log</B><BR /> The log shows messages created by the framework which performs system mode switches. By default the log of the current month is displayed. Use the selection button to modify this default selection.<BR /> <BR /> &nbsp;<BR /> <BR /> When system is in maintenance mode below things are restricted<BR /> <OL><BR /> <LI>Users which are not having security policy assigned are prevented to logon</LI><BR /> <LI>Background jobs are not allowed to run during maintenance mode</LI><BR /> <LI>All sessions and users are logged off post 10 mins upon activating maintenance mode</LI><BR /> <LI>HTTP sessions(webservices) are also closed and users will get 503 error</LI><BR /> <LI>External interfaces/RFC are restricted to connect SAP system</LI><BR /> </OL><BR /> &nbsp;<BR /> <BR /> Message end users will see when system in maintenance mode<BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/01/6-8.png" /></P><BR /> &nbsp;<BR /> <BR /> By using this we need not worry about ramp down activities such as users locking/unlocking, Job suspend, system messages, etc during any maintenance related task<BR /> <BR /> &nbsp;<BR /> <BR /> References link :&nbsp;<A href="https://blogs.sap.com/2020/06/18/abap-platform-part-3-whats-new-for-the-basis-administrators/" target="_blank" rel="noopener noreferrer">https://blogs.sap.com/2020/06/18/abap-platform-part-3-whats-new-for-the-basis-administrators/</A><BR /> <BR /> <A href="https://help.sap.com/viewer/e067931e0b0a4b2089f4db327879cd55/202110.000/en-US/79b58c0b017f437aad8f4128980a06a7.html?q=Maintenance%20mode" target="_blank" rel="noopener noreferrer">https://help.sap.com/viewer/e067931e0b0a4b2089f4db327879cd55/202110.000/en-US/79b58c0b017f437aad8f4128980a06a7.html?q=Maintenance%20mode</A><BR /> <BR /> &nbsp; 2022-01-10T13:18:59+01:00 https://community.sap.com/t5/technology-blogs-by-members/fiori-like-web-app-development-in-pure-abap-with-htmx-and-fundamental/ba-p/13500763 Fiori-like web app development in pure ABAP with htmx and Fundamental 2022-02-10T22:21:30+01:00 pkve https://community.sap.com/t5/user/viewprofilepage/user-id/299753 <H1 id="toc-hId-828292418">Introduction</H1><BR /> Are you a die-hard SE80 ABAP developer, or allergic to JavaScript? Or perhaps you just want to create some <SPAN class="innerContentContainer">"freestyle" </SPAN>custom Fiori-like apps for your clients or your company without wasting too much time learning new frameworks or relying on some third party?<BR /> <BR /> If so, then this post might be of some interest to you.<BR /> <BR /> Don't get me wrong, I do like JavaScript as a programming language, but I would like to present an alternative to the Single Page Application (SPA) frameworks, like Angular, Vue, React and SAPUI5 that have taken the world by storm over the last decade.<BR /> <BR /> In this blog post, we take a look at the main differences between the SPA model and the model promoted by htmx, and explore a small app to demonstrate its usage.<BR /> <H1 id="toc-hId-631778913">From SPA to htmx</H1><BR /> <H2 id="toc-hId-564348127"><SPAN class="innerContentContainer">Single page application (JSON over the wire)</SPAN></H2><BR /> <SPAN class="innerContentContainer">As a reminder, Single Page Applications are applications built to run on a single webpage. After the initial load of one HTML page and some JavaScript, they rely upon Ajax ("Asynchronous Javascript and XML") requests to pass JSON data objects to and from the server to the client to update the HTML page via JavaScript and the Document object model (DOM) API, without the need to reload the entire page.</SPAN><BR /> <BR /> <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/draw_spa.png" /><BR /> <P class="image_caption" style="text-align: center;font-style: italic">Single Page Application lifecycle</P><BR /> <BR /> <H2 id="toc-hId-367834622">HTML (fragment) over the wire</H2><BR /> In December 2020, David Heinemeier Hansson, creator of Ruby on Rails, CTO and co-founder of Basecamp, got some attention (at least mine) with a blog post proposing a departure from the SPA model.<BR /> <BLOCKQUOTE>You can write fast, modern, responsive web applications by generating your HTML on the server, and delivering that (with a little help) directly to the browser. You don’t need JSON as an in-between format. You don’t need client-side MVC frameworks. You don’t need complicated bundling and transpiling pipelines. But you do need to think different. [...]<BR /> <BR /> When we embrace HTML as the format to send across the wire, we liberate ourselves from having to write all the code that creates that HTML in JavaScript. You now get to write it in Ruby or Erlang or Clojure or Smalltalk or any programming language that might set your heart aflutter. We return the web to a place full of diversity in the implementations, and HTML as the lingua franca of describing those applications directly to the browser.<BR /> <BR /> <A href="https://m.signalvnoise.com/html-over-the-wire/" target="_blank" rel="nofollow noopener noreferrer">https://m.signalvnoise.com/html-over-the-wire/</A></BLOCKQUOTE><BR /> <SPAN class="name" data-wfid="908ae981cde0"><SPAN class="innerContentContainer">This sounds like old SSR (server-side rendering) from the pre-SPA era, but with a twist: after receiving HTML from the server, the browser asynchronously requests <STRONG>fragments</STRONG> of HTML, instead of JSON data, to dynamically alter parts of the page based on user interactions or events occurring on the server. </SPAN></SPAN><SPAN class="name" data-wfid="67a02da419b1"><SPAN class="innerContentContainer">The logic, and the state, remain on the server.</SPAN></SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/draw_html_over_the_wire-1.png" /><BR /> <P class="image_caption" style="text-align: center;font-style: italic">HTML over the wire lifecycle</P><BR /> <SPAN class="name" data-wfid="24bd526b66d7"><SPAN class="innerContentContainer">I later learned that the model behind this "HTML over the wire" concept was not so new and already expressed in other frameworks such as <A href="https://elixirforum.com/t/phoenix-liveview-info/16569" target="_blank" rel="nofollow noopener noreferrer">Phoenix LiveView</A></SPAN></SPAN><SPAN class="name"><SPAN class="innerContentContainer"> (2018) or <A href="https://calebporzio.com/proof-of-concept-phoenix-liveview-for-laravel" target="_blank" rel="nofollow noopener noreferrer">Laravel Livewire</A>&nbsp;(2019).</SPAN></SPAN><BR /> <H2 id="toc-hId-171321117">Htmx library</H2><BR /> <SPAN class="name" data-wfid="6194eafbe47f"><SPAN class="innerContentContainer">But there’s an even longer-term advocate of this model: Carson Gross, creator of the <A href="https://htmx.org" target="_blank" rel="nofollow noopener noreferrer">htmx</A>&nbsp;library.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="7b46916a0d63"><SPAN class="innerContentContainer">While htmx is fairly new with its first release in 2020, its predecessor, <A href="https://intercoolerjs.org/" target="_blank" rel="nofollow noopener noreferrer">intercooler.js</A>, dates back to 2013.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="a84f600f6a4b"><SPAN class="innerContentContainer">Carson is a natural contrarian, as his pinned <A href="https://twitter.com/htmx_org/status/1306234341056344065" target="_blank" rel="nofollow noopener noreferrer">Twitter</A> post shows, and his <A href="https://bigsky.software/" target="_blank" rel="nofollow noopener noreferrer">company</A> motto too ("We find hot, new industry trends and then do the opposite of that").</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="7749d02269a7"><SPAN class="innerContentContainer">To quote the website, htmx allows you to access <A class="contentLink" href="https://htmx.org/docs#ajax" target="_blank" rel="noopener noreferrer nofollow">AJAX</A>, <A class="contentLink" href="https://htmx.org/docs#css_transitions" target="_blank" rel="noopener noreferrer nofollow">CSS Transitions</A>, <A class="contentLink" href="https://htmx.org/docs#websockets" target="_blank" rel="noopener noreferrer nofollow">WebSockets</A> and <A class="contentLink" href="https://htmx.org/docs#sse" target="_blank" rel="noopener noreferrer nofollow">Server-Sent Events</A> directly in HTML, using <A class="contentLink" href="https://htmx.org/reference#attributes" target="_blank" rel="noopener noreferrer nofollow">attributes</A>, so you can build <A class="contentLink" href="https://htmx.org/examples" target="_blank" rel="noopener noreferrer nofollow">modern user interfaces</A> with the <A class="contentLink" href="https://en.wikipedia.org/wiki/HATEOAS" target="_blank" rel="noopener noreferrer nofollow">simplicity</A> and <A class="contentLink" href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm" target="_blank" rel="noopener noreferrer nofollow">power</A> of hypertext.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="6634dd26cb69"><SPAN class="innerContentContainer">Htmx is small (<A class="contentLink" href="https://unpkg.com/htmx.org/dist/" target="_blank" rel="noopener noreferrer nofollow">~10k min.gz'd</A>), <A class="contentLink" href="https://github.com/bigskysoftware/htmx/blob/master/package.json" target="_blank" rel="noopener noreferrer nofollow">dependency-free</A>, <A class="contentLink" href="https://htmx.org/extensions" target="_blank" rel="noopener noreferrer nofollow">extendable</A> &amp; IE11 compatible.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="4d3d20db18f7"><SPAN class="innerContentContainer">It's also an attempt to complete HTML as a hypertext (as its name implies, htmx is an extension to HTML).</SPAN></SPAN><BR /> <H3 id="toc-hId-103890331">Basic examples<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/htmx_basic_example.png" /></H3><BR /> <SPAN class="name" data-wfid="d2d5bb637b09"><SPAN class="innerContentContainer">The htmx library is provided as a simple JavaScript file you can just reference from a CDN: no bundling is required.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="548b303bb9e4"><SPAN class="innerContentContainer">In this example, when you click on the button, htmx issues a POST request to the url "/clicked" (because of the "hx-post" tag) and swaps the button tag with the (html fragment) responses (because of the "hx-swap" tag), without reloading the full page.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="602115e38231"><SPAN class="innerContentContainer">And you can also issue a GET request or swap the innerHTML or any other tag of the current html page if you want.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="666eab888783"><SPAN class="innerContentContainer">More examples are provided on the website and I highly recommend that you have a look at them <A href="https://htmx.org/examples/" target="_blank" rel="nofollow noopener noreferrer">here</A>.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="f4b357ff0c53"><SPAN class="innerContentContainer">What makes htmx special is its minimalism:</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">You don't need any tooling.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">You can adopt htmx incrementally.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">You can do a lot purely in terms of HTML (without any JavaScript).</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">If you really want/need some interactivity for your pages without touching the server, you can always use lightweight client frameworks such as:</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer"> <A class="contentLink" href="https://alpinejs.dev" target="_blank" rel="noopener noreferrer nofollow">Alpine.js,</A> </SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer"><A class="contentLink" href="https://github.com/vuejs/petite-vue" target="_blank" rel="noopener noreferrer nofollow">petite-vue,</A></SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer"><A class="contentLink" href="https://hyperscript.org/" target="_blank" rel="noopener noreferrer nofollow">hyperscript</A>&nbsp;(Carson's own library) - look at <A href="https://putyourlightson.com/articles/a-first-look-at-hyperscript" target="_blank" rel="nofollow noopener noreferrer">this article</A> for a short introduction,</SPAN></SPAN></LI><BR /> <LI>or <A href="http://vanilla-js.com/" target="_blank" rel="nofollow noopener noreferrer">VanillaJS</A>.</LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> <SPAN class="name" data-wfid="a065766127a9"><SPAN class="innerContentContainer">If you want more information about the concepts behind htmx (REST, HATEOAS), I encourage you to <A href="https://youtu.be/L_UWY-zHlOA" target="_blank" rel="nofollow noopener noreferrer">watch this talk</A> from Carson (just mentally replace Django by SAP and Python by ABAP, and all his talk remains relevant, thanks to the "HOWL stack" β€” Hypertext On Whatever Language).</SPAN></SPAN><BR /> <H2 id="toc-hId--221705893"><SPAN class="innerContentContainer">Fundamental Library Styles</SPAN></H2><BR /> <SPAN class="name" data-wfid="eba7040f2cb3"><SPAN class="innerContentContainer">If you want to give your app a Fiori look, don't look any further than the <A href="https://github.com/SAP/fundamental-styles" target="_blank" rel="nofollow noopener noreferrer">Fundamental Library Styles</A>, a light-weight presentation layer that you can use with any SPA framework and even with plain HTML; a perfect match for htmx.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="eba7040f2cb3"><SPAN class="innerContentContainer">Another well-suited option for htmx might be <A href="https://blogs.sap.com/2021/11/10/lift-off-ui5-web-components-1.0.0-launched/" target="_blank" rel="noopener noreferrer">Web Components</A>, but I haven't experimented with them yet.</SPAN></SPAN><BR /> <H1 id="toc-hId--547302117">Small demo app<IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/SAP-htmx-Active-search.gif" /></H1><BR /> <H2 id="toc-hId--614732903"><SPAN class="name" data-wfid="4c7b9c0038a2"><SPAN class="innerContentContainer">Introduction</SPAN></SPAN></H2><BR /> <SPAN class="name"><SPAN class="innerContentContainer">To give you a better feel of the potential of a "<A href="https://htmx.org/essays/hypermedia-driven-applications/" target="_blank" rel="nofollow noopener noreferrer">Hypermedia-Driven Application</A>" on SAP, I created a micro "single class application" in just 195 single lines of ABAP code.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">It will illustrate 3 UX patterns made possible by htmx (among many others):</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">active search: search as the user enters text,</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">out-of-band (oob) content update: update multiple HTML fragments with the same http response,</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">and infinite scroll.</SPAN></SPAN></LI><BR /> </UL><BR /> <H2 id="toc-hId--811246408">Installation</H2><BR /> <SPAN class="name"><SPAN class="innerContentContainer">You can choose any IDE to develop such apps, even your tried and tested SE80.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">The code of this app/class is available on Github <A href="https://raw.githubusercontent.com/pkve/abap-htmx/main/ZTF_HANDLER.clas.abap" target="_blank" rel="nofollow noopener noreferrer">here</A>.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">It should work out of the box on any 7.51+ system. This requirement is not related to htmx, but to my use of some ABAP keywords. Actually, htmx should work with any system starting from the 6.10 release of Web Application Server.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">Just copy the raw data and paste it to a new ZTF_HANDLER class (after selecting "Source Code-Based" if you are using SE80).</SPAN></SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/2021-09-19_15-42.png" /><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">Once the code is activated, head over to SICF to create a new service.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">It can be on the root or, like in the example below, under a 'custom' parent service with no handler.</SPAN></SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/2021-09-11_12-36.png" /><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">Add your class name to the handler list and save.</SPAN></SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/2021-09-11_12-34.png" /><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">And finally, in the SICF tree, right-click on your service and select "Activate Service", then "Test Service".</SPAN></SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/2021-11-07_08-50.png" /><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">Your browser should open automatically and display your service for testing.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">Note that the search function of the app is really basic, and case sensitive.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">Also pay attention to the scrollbar handle on the right when you scroll down the results: it goes up every time a batch of 100 new rows is appended to the table.</SPAN></SPAN><BR /> <H2 id="toc-hId--660505556">Some code explanations</H2><BR /> <SPAN class="name" data-wfid="285c2515e7bf"><SPAN class="innerContentContainer">Before diving into the code, we have to keep in mind the structure of the app and its components.</SPAN></SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/ztf_layout.png" /><BR /> <BR /> <SPAN class="name" data-wfid="58f7a32fb77f"><SPAN class="innerContentContainer">Please also note that this app is bare-bones on purpose: to keep the code clear and simple, and focus on htmx.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="bd8cf4076244"><SPAN class="innerContentContainer">The ABAP code is straightforward, so I will only explain the parts involving the htmx library.</SPAN></SPAN><BR /> <H3 id="toc-hId--1150422068"><SPAN class="innerContentContainer">if_http_extension~handle_request</SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/handle_request-2.png" /></H3><BR /> <SPAN class="name" data-wfid="dd19a584b7ea"><SPAN class="innerContentContainer">Like every ICF service handler, the ZTF_HANDLER class implements the if_http_extension interface and its if_http_extension~handle_request method.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="aec78284c57d"><SPAN class="innerContentContainer">This method acts as the "controller" part of the app.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="a18c52a38046"><SPAN class="innerContentContainer">First we filter the HTTP method to only keep the GET requests, and then we get the current path of the app and the searched string, if any.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="db6ece7fb859"><SPAN class="innerContentContainer">Then we check if the HTTP request is issued by the browser following normal navigation or from the htmx library (the 'HX-Request' header field is always populated with the value "true" for every htmx-initiated XMLHttpRequest).</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">In the first case (initial load of the application), all the fragments making up the whole page are returned to the browser.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">In the second case, only the fragments required by the "app-action" are returned.</SPAN></SPAN></LI><BR /> </UL><BR /> <SPAN class="name" data-wfid="8b13a3799072"><SPAN class="innerContentContainer">Each HTML fragment is generated by its own method (prefixed with 'html_').</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="2708952323ed"><SPAN class="innerContentContainer">Note that the "app-action" is a custom header field and not provided by the htmx library; its name is arbitrary and we will see later where this header field is defined.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="9a81b4e32d74"><SPAN class="innerContentContainer">Each "app-action" (think of it as an "OK code") triggers a different response from the server.</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">search</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">The whole table is sent to the browser, with the first batch of rows (100 rows or less).</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">search_init</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Same as "search" but the searched_string is cleared and another HTML fragment is sent "out of band" with the response, the search bar itself, to clear its content. This "oob" feature allows you to update not only one, but as many HTML fragments of the original page within the same response as you want.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">scroll</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">When the user scrolls the list, a request is automatically sent to the server to get the next batch of rows (we will see how later).</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> <H3 id="toc-hId--1346935573"><SPAN class="innerContentContainer">html_page</SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/html_page-3.png" /></H3><BR /> <SPAN class="name" data-wfid="6a154d205e30"><SPAN class="innerContentContainer">This app only has three dependencies:</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">the htmx.js file for the core htmx library,</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">the fundamental-style.css file for the Fundamental Library Styles,</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">the fonts used by the Fundamental Library Styles (see <A href="https://sap.github.io/fundamental-styles/?path=/docs/introduction--overview" target="_blank" rel="nofollow noopener noreferrer">here</A> for more info).</SPAN></SPAN></LI><BR /> </UL><BR /> <SPAN class="name" data-wfid="f08be2cd403e"><SPAN class="innerContentContainer">Here we link to a CDN but you should probably store these libraries in the standard SAP MIME Repository or using the <A href="https://answers.sap.com/questions/5496583/viewing-a-file-which-is-in-application-server-al11.html" target="_blank" rel="noopener noreferrer">ICM</A> or on any other internal HTTP server.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="74b39e47c84a"><SPAN class="innerContentContainer">We also added a "htmx-config" meta tag to configure the default htmx swap mode with "outerHTML", to swap the whole fragment and not only its content (as with the default "innerHTML" mode).</SPAN></SPAN><BR /> <H3 id="toc-hId--1543449078"><SPAN class="innerContentContainer">html_searchbar</SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/html_searchbar.png" /></H3><BR /> <SPAN class="name" data-wfid="e0186d989d2a"><SPAN class="innerContentContainer">To get started, all htmx attributes begin with "hx-" or "data-hx-". The two forms are equivalent but the latter doesn't issue errors when you check your generated HTML code with some tools like <A href="https://validator.w3.org/nu/#textarea" target="_blank" rel="nofollow noopener noreferrer">this one</A>.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="b2d08f7e2889"><SPAN class="innerContentContainer">Here we have two reactive elements:</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">an input element for the search string,</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">a button to refresh the search bar and the list.</SPAN></SPAN></LI><BR /> </UL><BR /> <SPAN class="name" data-wfid="4991c8353a5a"><SPAN class="innerContentContainer">Here are some basic explanations about the various htmx attributes used here.</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">data-hx-push-url</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Note that this attribute, like the data-hx-target attribute, is inherited by the child tags; so, instead of repeating this attribute on the input and button tags, we can just place it on the parent div tag.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">This tag is used to push the URL into the location bar; each time we search a new table name, it creates a new entry history, so that we can navigate back to our previous searches.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">data-hx-target</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">This attribute allows us to target a different HTML element for swapping than the one issuing the request.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">The target can be any valid CSS query selector (and more).</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">data-hx-trigger</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">This attribute allows us to specify what triggers the request.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">As we want an "active search", the request will be triggered 250ms after the user stops typing (or if he leaves the search field).</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">data-hx-get</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">This attribute causes an element to issue a GET request to the specified URL.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">data-hx-headers</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">This attribute allows us to add to the headers that will be submitted with the request. Its value is in JSON format.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">"app-action" is the arbitrary header field name I chose to send to the if_http_extension~handle_request.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">data-hx-swap-oob (in the first &lt;div&gt;)</SPAN></SPAN><BR /> <UL><BR /> <LI>This attribute allows you to specify that some content in a response should be swapped into the DOM somewhere other than the target, that is "Out of Band". Thanks to this attribute, when the user initialises their search, the server response updates both the html table and the search bar. Of course, this attribute is not inherited.</LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> <SPAN class="name" data-wfid="7ecc6a7a96fe"><SPAN class="innerContentContainer">You can find more information about these attributes and the other htmx attributes <A href="https://htmx.org/reference/#attributes" target="_blank" rel="nofollow noopener noreferrer">here</A>.</SPAN></SPAN><BR /> <H3 id="toc-hId--1739962583"><SPAN class="innerContentContainer">html_table_rows</SPAN><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/02/html_table_rows.png" /></H3><BR /> <SPAN class="name" data-wfid="2adf4c79710e"><SPAN class="innerContentContainer">Here, we not only display the batch of table rows but also an invisible row whose purpose is to trigger the load of the next batch of rows when it is scrolled into the viewport (or "revealed" as defined in the "data-hx-trigger" attribute).</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="cf26dfba8f2f"><SPAN class="innerContentContainer">The "data-hx-vals" attribute allows us to add some parameters to those that will be submitted with the request. We have to use this attribute to get the next batch of rows with the same search string as the original request.</SPAN></SPAN><BR /> <BR /> <SPAN class="name" data-wfid="0ba4ba7fc7d3"><SPAN class="innerContentContainer">The "data-hx-target" and the "data-hx-swap" attributes are defined so as to add the new batch of rows to the end of the table.</SPAN></SPAN><BR /> <H1 id="toc-hId--1349670074">Advice from my own experience</H1><BR /> <H2 id="toc-hId--1839586586"><SPAN class="name"><SPAN class="innerContentContainer">Use htmx</SPAN></SPAN></H2><BR /> <SPAN class="name"><SPAN class="innerContentContainer">You can find some libraries comparable to htmx (I first tried <A href="https://unpoly.com/" target="_blank" rel="nofollow noopener noreferrer">unpoly</A>) but the minimalism of htmx pays off in the end.</SPAN></SPAN><BR /> <H2 id="toc-hId--2036100091"><SPAN class="name"><SPAN class="innerContentContainer">Embrace HTML and CSS</SPAN></SPAN></H2><BR /> <SPAN class="name"><SPAN class="innerContentContainer">When I first experimented with htmx, my initial idea was to create an ABAP library to encapsulate HTML, htmx and the Fundamental Library Styles, but it was tedious and without any real added value.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">HTML and CSS are two of the core technologies for building webpages: you will not waste your time adding those skills to your toolbox.</SPAN></SPAN><BR /> <BR /> You can head over to the <A href="https://sap.github.io/fundamental-styles/?path=/docs/components-action-bar--actions" target="_blank" rel="nofollow noopener noreferrer">Fundamental Library Styles website</A> to get started. Just select a component and click on "Show code" to get the required HTML and CSS.<BR /> <H2 id="toc-hId-2062353700"><SPAN class="name"><SPAN class="innerContentContainer">Learn how the SAP Internet Communication Framework works</SPAN></SPAN></H2><BR /> <SPAN class="name"><SPAN class="innerContentContainer">You can design your own app architecture in many different ways and with many different ABAP technologies (BSP, ABAP REST Library...) but using the ICF fully and <A href="https://blogs.sap.com/2003/09/30/bsp-in-depth-writing-an-http-handler" target="_blank" rel="noopener noreferrer">writing your own HTTP handlers</A> is a really good option.</SPAN></SPAN><BR /> <BR /> <SPAN class="name"><SPAN class="innerContentContainer">You can have <A href="https://help.sap.com/viewer/753088fc00704d0a80e7fbd6803c8adb/202110.000/en-US/48d60600553b3e49e10000000a421937.html" target="_blank" rel="noopener noreferrer">multiple HTTP handler classes handling the same request</A> and split your app in a tree of ICF services where each service corresponds to a component, or a group of components, which completes the main server response, quite similarly to "nested routes" in frameworks such as <A href="https://remix.run/" target="_blank" rel="nofollow noopener noreferrer">Remix</A> or in <A href="https://reactrouter.com/docs/en/v6/getting-started/overview#nested-routes" target="_blank" rel="nofollow noopener noreferrer">React Router v6</A>.</SPAN></SPAN><BR /> <BR /> Also, don't forget <A href="https://blogs.sap.com/2013/07/12/logging-calls-to-your-gateway-service-sicf-for-beginners/" target="_blank" rel="noopener noreferrer">ICF logging</A>.<BR /> <H2 id="toc-hId-1865840195"><SPAN class="name"><SPAN class="innerContentContainer">Simplify your state management</SPAN></SPAN></H2><BR /> <SPAN class="name"><SPAN class="innerContentContainer">If you are on HANA, store everything (including all user inputs) in the database; if you have to call BAPIs, store intermediate data in custom tables (just like SAPUI5 Draft Handling). You can even store each field update from the user.</SPAN></SPAN><BR /> <H2 id="toc-hId-1837510381">Stay humble</H2><BR /> <SPAN class="name"><SPAN class="innerContentContainer">When you think there is an issue with the htmx library, check your code instead (and the htmx <A href="https://htmx.org/docs" target="_blank" rel="nofollow noopener noreferrer">documentation</A> or <A href="https://htmx.org/discord" target="_blank" rel="nofollow noopener noreferrer">discord</A>).</SPAN></SPAN><BR /> <H1 id="toc-hId-1934399883"><SPAN class="name" data-wfid="18a69c18b44a"><SPAN class="innerContentContainer">Conclusion</SPAN></SPAN></H1><BR /> After a few weeks of using htmx, here are the main advantages and disadvantages of this library, from my point of view.<BR /> <H2 id="toc-hId-1444483371"><SPAN class="name"><SPAN class="innerContentContainer">Pros</SPAN></SPAN></H2><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Full ABAP with direct access to existing BAPI, FM, classes, CDS views...</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Easy to grasp and well-documented libraries (both htmx and Fundamental Library Styles)</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Fast iterative process</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">No build process, you just have to activate your ABAP code.</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">When building and testing HTML fragments, you don't even have to reload your HTML page.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">No mockup data</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">You have your full DB data available at your fingertips.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Simplified state management</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">No API / Frontend desynchronization</SPAN></SPAN></LI><BR /> <LI>No duplication of logic</LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Code editor of choice</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">I personally use VSCodium on Linux without a glitch, thanks to <SPAN class="mention-scrubbed">murbani</SPAN> and <SPAN class="mention-scrubbed">lars.hvam</SPAN>,</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">but ADT and SE80 are fine.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Easier debugging</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">All your code is in one place and you have no dependency on external code.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Architecture freedom</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">on the server-side</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">on the frontend: I used Fundamental Library Styles but you can use any CSS framework if you want.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Performance</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">It could be counter-intuitive because of all these round trips between the browser and the SAP server, but the fact is htmx is damn fast (faster than Fiori apps in our S/4HANA environment).</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Security</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">The htmx library itself is less than 2,500 lines of easily auditable JavaScript code and, with it, you avoid using any other JavaScript (npm) module, usually overwhelmingly found in current SPA frontend developments or toolings.</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI>Ubiquity<BR /> <UL><BR /> <LI>You can integrate htmx with any server-side framework using any language such as <A href="https://htmx.org/server-examples" target="_blank" rel="nofollow noopener noreferrer">Python, C#, Java, Ruby, Julia, Lisp, Clojure</A>, <A href="https://github.com/rajasegar/awesome-htmx#go" target="_blank" rel="nofollow noopener noreferrer">Go</A>, <A href="https://hackage.haskell.org/package/lucid-htmx" target="_blank" rel="nofollow noopener noreferrer">Haskell</A> or even <A href="https://github.com/rajasegar/awesome-htmx#express" target="_blank" rel="nofollow noopener noreferrer">JavaScript</A>!</LI><BR /> </UL><BR /> </LI><BR /> </UL><BR /> <H2 id="toc-hId-1247969866"><SPAN class="name"><SPAN class="innerContentContainer">Cons</SPAN></SPAN></H2><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Deviation from the web development model promoted by SAP</SPAN></SPAN></LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">Not the best solution for fast prototyping</SPAN></SPAN><BR /> <UL><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">But as a programmer once said: "In the beginning you always want results. In the end all you want is control".</SPAN></SPAN></LI><BR /> </UL><BR /> </LI><BR /> <LI><SPAN class="name"><SPAN class="innerContentContainer">No ecosystem of reusable components (yet!)</SPAN></SPAN></LI><BR /> </UL><BR /> <SPAN class="name"><SPAN class="innerContentContainer">I would really enjoy seeing some of you try this htmx library so that we can share our experiments and best practices <SPAN class="innercontentcontainer">(and soon, "hmtx" might even become a new SAP community tag!).</SPAN></SPAN></SPAN><BR /> <BR /> <EM>(This blog post is a tribute to <SPAN class="mention-scrubbed">brian.mckellar</SPAN> and <SPAN class="mention-scrubbed">thomas.jung</SPAN> whose book "Advanced BSP Programming" had been gathering dust on one of my shelves for too many years).</EM> 2022-02-10T22:21:30+01:00 https://community.sap.com/t5/technology-blogs-by-members/exploring-web-app-development-with-abap-htmx-in-comparison-with-abap-rap/ba-p/13543568 Exploring web app development with ABAP & htmx (in comparison with ABAP RAP) 2022-04-08T10:01:32+02:00 aoyang https://community.sap.com/t5/user/viewprofilepage/user-id/14700 <H3 id="toc-hId-1090239396"><STRONG>1. Introduction</STRONG></H3><BR /> This is a personal sequel to <A href="https://blogs.sap.com/2022/02/10/fiori-like-web-app-development-in-pure-abap-with-htmx-and-fundamental/#" target="_blank" rel="noopener noreferrer">Fiori-like web app development in pure ABAP with htmx and Fundamental</A> by <SPAN class="mention-scrubbed">patrick.villeneuve</SPAN> to explore further the possibilities of combining htmx and ABAP to develop business web apps.<BR /> <BR /> <STRONG>1.1 Motivation</STRONG><BR /> <BR /> The world is shifting towards SAP web development using SAPUI5 and ABAP RAP model on a platform like BTP, and it feels like all the years and efforts spent on developing pure ABAP applications and accumulating the know-how are starting to be forgotten.<BR /> <BR /> Then Patrick's blog caught my eyes. The blog introduces a web app development using htmx to control the frontend and ABAP on the backend. To put this in the modern SAPUI5 MVC architecture, "view" is managed by htmx, "controller" is managed by ABAP, and "Model" is not needed because ABAP can directly access the database.<BR /> <BR /> In this blog, I will go through my demo app to explain the interaction between htmx and ABAP, as well as how the CRUD operations can be handled. I aimed to create a business application which could be used by the real world business user, in order to share the feasibility and effort needed for this new development approach.<BR /> <BR /> <STRONG>1.2 htmx in a Nutshell</STRONG><BR /> <BR /> The most concrete explanation and philosophy behind htmx can be found in the <A href="http://blogs.sap.com/2022/02/10/fiori-like-web-app-development-in-pure-abap-with-htmx-and-fundamental/" target="_blank" rel="noopener noreferrer">original blog</A>. It's got nice illustrations too. But in a Nutshell, what makes it special are:<BR /> <UL><BR /> <LI>Any HTML element can issue an HTTP request to the backend</LI><BR /> <LI>Not only clicking can trigger HTTP request but also scrolling, as user enters value, and even with shortcut key</LI><BR /> <LI>Any element on the page can be updated(swapped) without updating the entire page</LI><BR /> <LI>A lot can be achieved on the frontend solely using htmx, without using JavaScript</LI><BR /> </UL><BR /> If you are new to htmx(like me) and can't wrap your head around these concepts, that's ok! Nothing helps you understand better than getting your hands dirty!<BR /> <H3 id="toc-hId-893725891"><STRONG>2. Demo app</STRONG></H3><BR /> Let's dive right into the demo app. This Sales Order Update App allows user to search the Sales order by certain selection filter and update line item information. The update result will be displayed on the Status column.<BR /> <P style="overflow: hidden;margin-bottom: 0px">&nbsp; &nbsp; &nbsp; <IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/04/giphy2.gif" height="324" width="576" /></P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/04/Model.png" height="215" width="393" /></P><BR /> <STRONG>2.1 Implementation steps</STRONG><BR /> <BR /> Go to SE24 or SE80 and implement the backend ABAP class/methods. <A href="https://raw.githubusercontent.com/Yoloyoda/abap_htmx/main/ZHTMX_HANDLER_SO.cl.abap" target="_blank" rel="nofollow noopener noreferrer">Here</A> is the code of this app.<BR /> <BR /> Create a new custom service in SICF and activate it. Detailed steps can be found in the <A href="https://blogs.sap.com/2022/02/10/fiori-like-web-app-development-in-pure-abap-with-htmx-and-fundamental/#" target="_blank" rel="noopener noreferrer">original blog</A>. In this demo, I named my class "ZHTMX_HANDLER_SO" so that class should be set in your service. Right click the service and "Test Service" will open the app on your default browser.<BR /> <BR /> &nbsp;<BR /> <BR /> <STRONG>2.2 Code explanation</STRONG><BR /> <BR /> <STRONG>2.2.1 Initial call</STRONG><BR /> <BR /> When the Service URL is accessed, the class will be called and method "<!--StartFragment -->IF_HTTP_EXTENSION<SPAN class="L0S70">~</SPAN>HANDLE_REQUEST" is triggered. Here, first we get the query string and form data send on the request from the browser. In the initial call there is no string query or form so will come back to that. Then Depending on the method type, the code goes to different logic. The initial call will be GET and "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>request" will always be empty. <!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>request will be true if the request is triggered by htmx element such as "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN><SPAN class="L0S52">get</SPAN>" and <!--StartFragment -->hx<SPAN class="L0S70">-put</SPAN>". So the code will call 4 different methods(<!--StartFragment -->HTML_PAGE, <!--StartFragment -->HTML_SHELLBAR, <!--StartFragment -->HTML_SELECTION, <!--StartFragment -->HTML_TABLE) in order to create the first view of the page. Each methods appends the html fragments into variable "<!--StartFragment -->HTML", which is also their return value.&nbsp; <!--StartFragment -->SERVER<SPAN class="L0S70">-&gt;</SPAN>RESPONSE<SPAN class="L0S70">-&gt;</SPAN>APPEND_CDATA will return the whole HTML code back to browser as the Response.<BR /> <PRE class="language-abap"><CODE>METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST.<BR /> <BR /> "Get Request parameters<BR /> ME-&gt;GV_PATH = SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD(<BR /> IF_HTTP_HEADER_FIELDS_SAP=&gt;PATH_TRANSLATED_EXPANDED ).<BR /> <BR /> "Get the query value Order type<BR /> ME-&gt;GV_AUART = ESCAPE( VAL = SERVER-&gt;REQUEST-&gt;GET_FORM_FIELD( `a` )<BR /> FORMAT = CL_ABAP_FORMAT=&gt;E_HTML_TEXT ).<BR /> "Get the query value Sales Org<BR /> ME-&gt;GV_VKORG = ESCAPE( VAL = SERVER-&gt;REQUEST-&gt;GET_FORM_FIELD( `v` )<BR /> FORMAT = CL_ABAP_FORMAT=&gt;E_HTML_TEXT ).<BR /> <BR /> "Get the SO number user is operating<BR /> ME-&gt;GV_SO_KEY = ESCAPE( VAL = SERVER-&gt;REQUEST-&gt;GET_FORM_FIELD( `k` )<BR /> FORMAT = CL_ABAP_FORMAT=&gt;E_HTML_TEXT ).<BR /> <BR /> "Get form data<BR /> CALL METHOD SERVER-&gt;REQUEST-&gt;GET_FORM_FIELDS( CHANGING FIELDS = GT_FORM ).<BR /> <BR /> <BR /> "Get method<BR /> IF SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD(<BR /> IF_HTTP_HEADER_FIELDS_SAP=&gt;REQUEST_METHOD ) = `GET`.<BR /> <BR /> IF SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD( `hx-request` ) IS INITIAL.<BR /> <BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_PAGE( ) ).<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_SHELLBAR( ) ).<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_SELECTION( ) ).<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_TABLE( EXPORTING FORM = GT_FORM ) ).<BR /> <BR /> ELSE.<BR /> <BR /> CASE SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD( `app-action` ).<BR /> <BR /> WHEN `search_init`.<BR /> "Implement Search Init logic of your choice<BR /> <BR /> WHEN `scroll`.<BR /> <BR /> GV_PAGE = SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD( `app-page` ).<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_TABLE_ROWS( EXPORTING FORM = GT_FORM ) ).<BR /> <BR /> WHEN `edit`.<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_EDIT_ROWS( EXPORTING FORM = GT_FORM ) ).<BR /> <BR /> WHEN `cancel`.<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_CANCEL_EDIT( EXPORTING FORM = GT_FORM ) ).<BR /> <BR /> ENDCASE.<BR /> <BR /> DATA(LV_ACTION) = SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD( `app-action` ).<BR /> <BR /> ENDIF.<BR /> <BR /> SERVER-&gt;RESPONSE-&gt;SET_STATUS( CODE = 200<BR /> REASON = IF_HTTP_STATUS=&gt;REASON_200 ).<BR /> SERVER-&gt;RESPONSE-&gt;SET_CONTENT_TYPE( `text/html` ).<BR /> <BR /> ENDIF.<BR /> <BR /> "Put Method<BR /> IF SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD(<BR /> IF_HTTP_HEADER_FIELDS_SAP=&gt;REQUEST_METHOD ) = `PUT`.<BR /> CASE SERVER-&gt;REQUEST-&gt;GET_HEADER_FIELD( `app-action` ).<BR /> <BR /> WHEN `search`.<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_TABLE( EXPORTING FORM = GT_FORM ) ).<BR /> <BR /> WHEN `save`.<BR /> SERVER-&gt;RESPONSE-&gt;APPEND_CDATA( HTML_SAVE_EDIT( EXPORTING FORM = GT_FORM<BR /> IMPORTING STATUS_MSG = GV_STATUS_MSG ) ).<BR /> ENDCASE.<BR /> <BR /> SERVER-&gt;RESPONSE-&gt;SET_STATUS( CODE = 200<BR /> REASON = IF_HTTP_STATUS=&gt;REASON_200 ).<BR /> SERVER-&gt;RESPONSE-&gt;SET_CONTENT_TYPE( `text/html` ).<BR /> ENDIF.<BR /> <BR /> <BR /> ENDMETHOD.</CODE></PRE><BR /> &nbsp;<BR /> <BR /> <STRONG>2.2.2 Fundamental library and htmx library&nbsp;</STRONG><BR /> <BR /> In <!--StartFragment -->HTML_PAGE, the header of the app page is defined. The important point here is that&nbsp; <A href="https://sap.github.io/fundamental-styles/?path=/docs/introduction--overview" target="_blank" rel="nofollow noopener noreferrer">Fundamental Library</A> and <A href="https://htmx.org/docs/" target="_blank" rel="nofollow noopener noreferrer">htmx library</A> are defined. In the later section of the code, you will find html class such as "fd-table" and "fd-input" and attributes such as "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>headers" and "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>target". "fd-" are referring to Fundamental library to give SAP Fiori theme UI and "hx-" attributes enables htmx function from htmx library.<BR /> <PRE class="language-abap"><CODE>`&lt;link href='https://unpkg.com/fundamental-styles@0.22.0/dist/fundamental-styles.css' rel='stylesheet'&gt;`<BR /> `&lt;script src="https://unpkg.com/htmx.org@latest/dist/htmx.js"&gt;&lt;/script&gt;`</CODE></PRE><BR /> &nbsp;<BR /> <BR /> <STRONG>2.2.3 Frontend/Backend interaction by htmx</STRONG><BR /> <BR /> By pressing the search button,&nbsp; PUT request is triggered with the service URL of this app. This behavior is defined in <!--StartFragment -->HTML_SELECTION. "<!--StartFragment --><SPAN class="L0S52">data</SPAN><SPAN class="L0S70">-</SPAN>hx<SPAN class="L0S70">-</SPAN><SPAN class="L0S52">put=" defines the service to be triggered when the search button is pressed. In this case, <!--StartFragment -->GV_PATH contains the URL path initially triggered, and that will be triggered again when the button is pressed. Whatever set in "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>headers=" will be added to Request header when the service is triggered. In this case, "app<SPAN class="L0S70">-</SPAN>action = search" will be added on the header and when the service is triggered by "data<SPAN class="L0S70">-</SPAN>hx<SPAN class="L0S70">-</SPAN>put", the code will process into the case statement where "<!--StartFragment -->WHEN&nbsp;<SPAN class="L0S33">`search`</SPAN> = true"&nbsp;in the main <!--StartFragment -->HANDLE_REQUEST method. <SPAN style="text-decoration: underline">In other words,</SPAN> <SPAN style="text-decoration: underline">whatever set in "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>headers" works like a OK code in ABAP dynpro and it gets picked up in the main <!--StartFragment -->HANDLE_REQUEST method</SPAN><STRONG>.</STRONG></SPAN><BR /> <BR /> Another point to mention here is that this button is inside a "form", which means that the input values(Document type and Sales Org) will be passed as form data on the Request when the <SPAN class="L0S52">search </SPAN>button is pressed. Form data is fetched by method <!--StartFragment -->GET_FORM_FIELDS in the main <!--StartFragment -->HANDLE_REQUEST and later used to filter the SELECT statement. The purpose of those input fields are to filter the output of sales orders.<BR /> <PRE class="language-abap"><CODE> METHOD HTML_SELECTION.<BR /> <BR /> CONCATENATE<BR /> `&lt;form class="fd-form__item"&gt;`<BR /> `&lt;label class="fd-form__label" for="auart"&gt; Document type &lt;/label&gt;`<BR /> `&lt;input id="auart" class="fd-input fd-input-group__input" style="max-width: 100px;" type="text" name="Document type" &gt;`<BR /> `&lt;label class="fd-form__label" for="vkorg"&gt; Sales Org &lt;/label&gt;`<BR /> `&lt;input id="vkorg" class="fd-input fd-input-group__input" style="max-width: 100px;" type="text" name="Sales Org" &gt;`<BR /> `&lt;button type="button" class="fd-button" `<BR /> `aria-label="clear search" `<BR /> `data-hx-get="` GV_PATH `" `<BR /> `data-hx-headers='{"app-action": "search_init"}'&gt;`<BR /> `&lt;i class="sap-icon--clear-all"&gt;&lt;/i&gt;`<BR /> `&lt;/button&gt;`<BR /> `&lt;button type="button" class="fd-button" `<BR /> `aria-label="saerch" `<BR /> `data-hx-target="#sap_tables" `<BR /> `data-hx-swap-oob="true" `<BR /> `data-hx-put="` GV_PATH `" `<BR /> `data-hx-headers='{"app-action": "search"}'&gt;`<BR /> `&lt;i class="sap-icon--search"&gt;&lt;/i&gt;`<BR /> `&lt;/button&gt;`<BR /> `&lt;/form&gt;`<BR /> INTO HTML.<BR /> <BR /> <BR /> ENDMETHOD.</CODE></PRE><BR /> Similarly, Edit button on each table rows are also calling back the class method every time they are pressed. Here, form is not used but "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>vals" allows you to add search string to the request and in this case, search string k with value of <!--StartFragment -->GV_SO_KEY is passed. <!--StartFragment -->GV_SO_KEY contains the sales order + item number key which is concatenated before <!--StartFragment -->TAB_ROW_DISP_MODE is called. This is for the callback program to identify which sales order row the user is trying to change.<BR /> <PRE class="language-abap"><CODE> METHOD TAB_ROW_DISP_MODE.<BR /> <BR /> "Put the row to display mode<BR /> CONCATENATE HTML<BR /> `&lt;tr class="fd-table__row fd-table__row--hoverable"&gt;`<BR /> `&lt;td class="fd-table__cell"&gt;` DATA-VBELN `&lt;/td&gt;`<BR /> `&lt;td class="fd-table__cell"&gt;` DATA-AUART `&lt;/td&gt;`<BR /> `&lt;td class="fd-table__cell"&gt;` DATA-VKORG `&lt;/td&gt;`<BR /> `&lt;td class="fd-table__cell"&gt;` DATA-POSNR `&lt;/td&gt;`<BR /> `&lt;td class="fd-table__cell"&gt;` DATA-MATNR `&lt;/td&gt;`<BR /> `&lt;td class="fd-table__cell"&gt;` DATA-KWMENG `&lt;/td&gt;`<BR /> `&lt;td class="fd-table__cell"&gt;` DATA-VRKME `&lt;/td&gt;`<BR /> `&lt;td&gt;`<BR /> `&lt;button class="btn btn-danger"`<BR /> `data-hx-vals='{"k": "` GV_SO_KEY `"}' `<BR /> `data-hx-get="` GV_PATH `" `<BR /> `data-hx-headers='{"app-action": "edit"}' &gt;`<BR /> `Edit`<BR /> `&lt;/button&gt;`<BR /> `&lt;/td&gt;`<BR /> `&lt;td class="fd-table__cell fd-info-label--accent-color-6" style="margin:0.2rem""&gt;` STATUS_MSG `&lt;/td&gt;`<BR /> `&lt;/tr&gt;` INTO HTML.<BR /> <BR /> <BR /> ENDMETHOD.</CODE></PRE><BR /> Other htmx initiated user interactions include, pressing cancel buttons, pressing save button and scrolling down to fetch more records. They all call back the backend ABAP class method and return with the response data.<BR /> <BR /> &nbsp;<BR /> <BR /> <STRONG>2.2.4 Swapping in htmx</STRONG><BR /> <BR /> One of the attractive feature of htmx is that <SPAN style="text-decoration: underline">you can swap and replace certain part of the page without returning the whole page of HTML.</SPAN> This is achieved by first defining the default swapping mode. "outerHTML" replaces the entire HTML element that is in process. <A href="https://htmx.org/attributes/hx-swap/" target="_blank" rel="nofollow noopener noreferrer">Here</A> you can find other swapping modes.<BR /> <PRE class="language-abap"><CODE>&lt;meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'&gt;</CODE></PRE><BR /> Let's look at an example when user presses Edit on one of the rows. <!--StartFragment --><SPAN class="L0S52">WHEN&nbsp;</SPAN><SPAN class="L0S33">`edit`</SPAN> will be true and <!--StartFragment -->HTML_EDIT_ROWS&nbsp; is called, in which calls <!--StartFragment -->TAB_ROW_EDIT_MODE. This method is only returning one row of table in HTML, NOT the whole page of the app starting from the Shell bar to the bottom of the table.<BR /> <BR /> OuterHTML swapping is used almost everywhere in this app, but the search button is using different kind of swapping. <A href="https://htmx.org/attributes/hx-swap-oob/" target="_blank" rel="nofollow noopener noreferrer">hx-swap-oob</A> comes in handy when you want to swap other part of the page instead of itself. When search button is pressed, no change on the button itself is required but the table contents below should be refreshed. So&nbsp; that's why <!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>target=<SPAN class="L0S31">"#sap_tables"</SPAN> is set, which means this button is intended to swap the contents of "<SPAN class="L0S31">sap_tables", which is the id for the HTML table.&nbsp;</SPAN><BR /> <PRE class="language-abap"><CODE> `&lt;button type="button" class="fd-button" `<BR /> `aria-label="saerch" `<BR /> `data-hx-target="#sap_tables" `<BR /> `data-hx-swap-oob="true" `<BR /> `data-hx-put="` GV_PATH `" `<BR /> `data-hx-headers='{"app-action": "search"}'&gt;`<BR /> `&lt;i class="sap-icon--search"&gt;&lt;/i&gt;`<BR /> `&lt;/button&gt;`</CODE></PRE><BR /> &nbsp;<BR /> <BR /> <STRONG>2.3 CRUD operation</STRONG><BR /> <BR /> This app uses Read and Update of the sales document data so I will only highlight how Create and Delete can by handled.<BR /> <BR /> <STRONG>Create: </STRONG>Define a "Add" button with attribute "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>headers=<SPAN class="L0S33">'{"app-action": "add"}'" and set app's callback URL on "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN><SPAN class="L0S52">get</SPAN>". In the main <!--StartFragment -->HANDLE_REQUEST method, create a CASE statement for "add" and call BAPI or BDC of your choice to create sales order.&nbsp;</SPAN><BR /> <BR /> <SPAN class="L0S33"><STRONG>Delete: </STRONG>Define a "Delete" button on each row of the table with attribute "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN>headers='{"app-action": "delete"}'" and set app's callback URL on "<!--StartFragment -->hx<SPAN class="L0S70">-</SPAN><SPAN class="L0S52">get</SPAN>". The rest is same as Create. Create your own logic to delete the sales order item. One thing you have to remember is that nothing should be returned by <!--StartFragment -->APPEND_CDATA on the request's response to make sure that the deleted item will be removed from the HTML table.</SPAN><BR /> <BR /> So it's very straightforward! Just need to use htmx initiated request &amp; htmx swapping wisely and combine it with backend ABAP to update the SAP database.<BR /> <BR /> <STRONG>2.4 </STRONG><B>Exercise(Try it!)</B><BR /> <BR /> Now I assume you have pretty good idea how swapping and htmx initiated request work. If you'd like, you can implement your own logic for the "search clear button", as I left it empty on purpose<span class="lia-unicode-emoji" title=":grinning_face:">πŸ˜€</span>.<BR /> <PRE class="language-abap"><CODE> WHEN `search_init`.<BR /> "Implement Search Init logic of your choice</CODE></PRE><BR /> &nbsp;<BR /> <H3 id="toc-hId-697212386"><STRONG>3. Compatibility with ABAP</STRONG></H3><BR /> Through building this app, here is my summary of this development approach.<BR /> <UL><BR /> <LI>Full access to objects in SAP application/DB server through ABAP<span class="lia-unicode-emoji" title=":thumbs_up:">πŸ‘</span></LI><BR /> <LI>No need to call API to fetch dataset thus no need to create/manage Odata in backend<span class="lia-unicode-emoji" title=":thumbs_up:">πŸ‘</span></LI><BR /> <LI>Entire code is wrapped in one ABAP class for good maintainability (but you can always setup multiple services and classes in SICF)<span class="lia-unicode-emoji" title=":thumbs_up:">πŸ‘</span></LI><BR /> <LI>The frontend code will be very light. All the complex logic is managed in backend ABAP<span class="lia-unicode-emoji" title=":thumbs_up:">πŸ‘</span></LI><BR /> </UL><BR /> &nbsp;<BR /> <UL><BR /> <LI>Input fields cannot directly refer to SAP data domain thus no automatic external/internal value conversion(had to manually convert doc number, sales org, etc.) <span class="lia-unicode-emoji" title=":confused_face:">πŸ˜•</span></LI><BR /> <LI>A lot of effort to create select-options and F4 value help. They're so easy in ABAP though...<span class="lia-unicode-emoji" title=":confused_face:">πŸ˜•</span></LI><BR /> <LI>Need better idea to maintain HTML code than using string concatenation<span class="lia-unicode-emoji" title=":confused_face:">πŸ˜•</span> -&gt; In the next release, it would be cool if SAP integrated HTML editor in SE80, like <A href="https://blogs.sap.com/2016/01/20/wysiwyg-html-editor/" target="_blank" rel="noopener noreferrer">this one</A>!</LI><BR /> </UL><BR /> There are pros and cons but since it's a new approach, there is always room for improvement!<BR /> <H3 id="toc-hId-500698881"><STRONG>4. Comparing the efforts with ABAP RAP Model</STRONG></H3><BR /> This new development model offers new possibilities for SAP web development and all, but does it save more time and hassle compared to the existing web app development framework? I used ABAP RAP to create nearly the identical app and turned out it took much less effort to build it<span class="lia-unicode-emoji" title=":grinning_face_with_sweat:">πŸ˜…</span>.<BR /> <BR /> On top of that, ABAP RAP model does not have the weakness of htmx &amp; ABAP model, which are:<BR /> <UL><BR /> <LI>Internal/external format conversion is performed automatically</LI><BR /> <LI>F4 search help comes as default</LI><BR /> <LI>Select options/multiple selection are possible</LI><BR /> </UL><BR /> on top of these advantages,<BR /> <UL><BR /> <LI>DB data is at your fingertip. BAPI call also possible to update database(not in the CRUD methods but only in the saver methods)</LI><BR /> <LI>Coding is only needed on behavior class, which was less than 200 lines</LI><BR /> </UL><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/04/ABAPRest1.png" height="163" width="641" /></P><BR /> <P style="overflow: hidden;margin-bottom: 0px"><IMG class="migrated-image" src="https://community.sap.com/legacyfs/online/storage/blog_attachments/2022/04/ABAPRest2.png" /></P><BR /> The only drawback I felt there was is that I couldn't call BAPI anywhere I wanted in the behavior class method. BAPI parameters must be prepared in the CRUD methods and pass them as ABAP memory to the saver methods.<BR /> <BR /> If you are not familiar with ABAP RAP model, please check <A href="https://blogs.sap.com/2019/05/23/sap-cloud-platform-abap-restful-programming-model-rap-for-beginners/" target="_blank" rel="noopener noreferrer">this blog</A> and I definitely recommend you to try it!<BR /> <H3 id="toc-hId-304185376"><STRONG>5. Conclusion</STRONG></H3><BR /> So I must admit that for this specific demo app requirement, ABAP RAP model is a better option. However, htmx is still super powerful and htmx &amp; ABAP model brings so much possibilities to SAP web app development. If the weakness I mentioned can be overcome somehow, it will offer much more flexibility in the UI design, easier frontend backend synchronization, and much simpler development model.<BR /> <BR /> The next step? I really hope that SAP will add new components to ABAP library to exploit the capability of htmx!<BR /> <BR /> &nbsp;<BR /> <BR /> <STRONG>References</STRONG><BR /> <BR /> <A href="https://blogs.sap.com/2022/02/10/fiori-like-web-app-development-in-pure-abap-with-htmx-and-fundamental/#" target="_blank" rel="noopener noreferrer">Fiori-like web app development in pure ABAP with htmx and Fundamental</A><BR /> <BR /> <A href="https://raw.githubusercontent.com/Yoloyoda/abap_htmx/main/ZHTMX_HANDLER_SO.cl.abap" target="_blank" rel="nofollow noopener noreferrer">Demo app source code</A><BR /> <BR /> <A href="https://htmx.org/" target="_blank" rel="nofollow noopener noreferrer">htmx library</A><BR /> <BR /> <A href="https://sap.github.io/fundamental-styles/?path=/docs/introduction--overview" target="_blank" rel="nofollow noopener noreferrer">Fundamental Library</A><BR /> <BR /> <A href="https://blogs.sap.com/2019/05/23/sap-cloud-platform-abap-restful-programming-model-rap-for-beginners/" target="_blank" rel="noopener noreferrer">ABAP on SAP Cloud platform – ABAP RESTful Programming Model (RAP) for beginners with CRUD example</A> 2022-04-08T10:01:32+02:00