RFC meets Web – Part II: How to build the proxy

Welcome back to the second blog post where we look into building the REST2RFC proxy! It’s a SAP Cloud SDK Java application running on SAP BTP Cloud Foundry runtime.

Introduction and BTP services used

When I started with the project (which was inspired by customers telling me “we don’t have APIs, we are still on S/4HANA 1909”) I explored the options to build a proxy to translate between the RFC and the REST world. A few things became clear:

  • That proxy must be agnostic – no function module specific logic should be in it, so it can be universal.
  • REST is channeled through SAP Cloud Connector and with that we have a granular way to white-list what should be exposed from the on-premise system.
  • SAP BTP offers the RFC-flavor for defining a destination. Great!
  • Using BTP RFC destinations only works through Java and the API For SAP BTP (Java Web Tomcat 9) which is not in the SAP Cloud SDK for Java (not to be confused with the JCo you can download as a separate package) and likely coming from Neo but working perfectly fine with CF and latest Cloud SDK, too.
  • All existing tutorials were outdated that explained the RFC integration so I hope this blog helps you.

As for the BTP project building the application needs these services:

  1. Authorization and Trust Management Service (xsuaa): Securing the application from the web entry point with OAuth2 and roles to be assigned to a user which define the scope during authentication as part of the JWT.
  2. Connectivity Service: To connect between cloud and on-premise through the Cloud Connector (CC).
  3. Destination Service: To retrieve the destinations in the Java app for RFC and offer the REST service to the caller.
  4. Application Logging Service: To allow for a really nice monitoring of the proxy. Not mandatory but I highly recommend it for debugging and day-to-day monitoring.

Proxy project details

Development IDE

I used Eclipse in connection with the SAP Cloud SDK for Java. You want to install Maven with

$ choco install maven

on a Windows device. To create a fresh project I ran

mvn archetype:generate "-DarchetypeGroupId=com.sap.cloud.sdk.archetypes" "-DarchetypeArtifactId=scp-cf-tomee" "-DarchetypeVersion=RELEASE"

since I built on TomcatEE and not Springboot. This create the framework you should open in Eclipse once done.

I deleted those elements which I didn’t need (like junit and integration tests 😂) so the build process just considered my code. You also need to define a manifest file and the obligatory xs-security.json.

---
applications:

- name: jco-rfc-rest-server
  memory: 1500M
  timeout: 300
  random-route: true
  path: application/target/jco-rfc-rest-server-application.war
  buildpacks:
    - sap_java_buildpack
  env:
    USE_JCO: "true"
    TARGET_RUNTIME: tomee7
    SET_LOGGING_LEVEL: '{ROOT: INFO, com.sap.cloud.sdk: INFO}'
    JBP_CONFIG_SAPJVM_MEMORY_SIZES: 'metaspace:128m..'
    xsuaa_connectivity_instance_name: "jco-rfc-rest-server-xsuaa"
    connectivity_instance_name: "jco_connectivity"
    destination_instance_name: "jco_destination"
    ENABLE_SECURITY_JAVA_API_V2: "true"
    
  services:
    - jco-rfc-rest-server-xsuaa
    - jco_connectivity
    - jco_destination
    - jco-app-logging

above is my manifest.yml.

{
  "xsappname": "jco-rfc-rest-server",
  "tenant-mode": "dedicated",
  "scopes": [
    {
      "name": "$XSAPPNAME.Display",
      "description": "GET requests on BAPI"
    },
    {
      "name": "$XSAPPNAME.Modify",
      "description": "POST requests on BAPI"
    }
  ],
  "role-templates": [
    {
      "name": "JCo-REST-RFC-Server-Viewer",
      "description": "Required to run GET requests for RFC BAPI calls.",
      "scope-references"     : [
        "$XSAPPNAME.Display"
      ]
    },
    {
      "name": "JCo-REST-RFC-Server-Administrator",
      "description": "Required to run POST requests for RFC BAPI calls.",
      "scope-references"     : [
        "$XSAPPNAME.Modify"
      ]
    }
  ]
}

Above is my xs-security.json. Based on that you can create 2 role collections in BTP later to allow for a display and and admin role for the proxy calls.

Java specifics: Authority checks

The Java class for the proxy is based on the HttpServlet class. Below I’d like to share the pattern for authority checks.

@WebServlet("/rfc/*")
@ServletSecurity(@HttpConstraint(rolesAllowed = { "Display", "Modify" }))
public class Rest2RfcProxy extends HttpServlet {

        // .... some code

	@Override
	@RolesAllowed({"Modify"})
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {	
               // ...
        }

	@Override
	@RolesAllowed({"Display", "Modify"})
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
               // ...
        }

        // .... some more code

}

So that’s how the security scopes defined in xs-security.json are checked in the Java code. You must enable that in the web.xml file of the webapp like so:

    <login-config>
        <auth-method>XSUAA</auth-method>
    </login-config>

	<security-constraint>
        <web-resource-collection>
            <web-resource-name>Baseline Security</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>*</role-name>
        </auth-constraint>
    </security-constraint>

    <security-role>
        <role-name>Display</role-name>
    </security-role>
     <security-role>
        <role-name>Modify</role-name>
    </security-role>

Only then it will work with the data injected by the JWT.

Info: Per default BTP destination service will work with X-CSRF token. I turned it off for testing – this is also defined in web.xml!

Java specifics: JCo REST/RFC

The communication through JCo via RFC follows this sequence:

  1. Get the RFC destination with details that we specified in the SAP BTP (see the first blog).
  2. Define the function to be called in Java.
  3. Define the import/ export and table parameter list from the function module. These variables hold the according structures.
  4. Load the metadata of the 3 objects from the SAP ECC or S/4HANA data dictionary.
  5. Set the import or tables parameters as needed.
  6. Execute the function module call.
  7. If it’s a POST, also execute a call to BAPI BAPI_TRANSACTION_COMMIT. If you call a Z-function module it depends how your organized your LUW whether this has any impact at all. Certainly it won’t hurt but the commit might have been done before that in your ABAP code.
  8. Feed back the data to the caller

Example for the IMPORTS:

if (jImports != null) {
	logger.info("Filling BAPI import: " + jImports.toString());
	bapiImports.getMetaData();
	bapiImports.fromJSON(jImports.toString());
}

This uses the .fromJSON method to fill the structure. On the way back to the caller you can then use the .toJSON method to obtain the JSON data for REST.

Application logging

I recommend to consider using the application logging service on BTP, particularly for this middleware application to facilitate error analysis. It builds on the common org.slf4j framework. You activate it with

private static final Logger logger = LoggerFactory.getLogger(<your class name>.class);

declared on class level to have it available in every method and exception. In the manifest.yml you can then set the logging level:

SET_LOGGING_LEVEL: '{ROOT: INFO, com.sap.cloud.sdk: INFO}'

here it means everything is logged. You can reduce it to WARN or ERROR.

Image%201%3A%20Application%20Logging%20service%20on%20BTP

Image 1: Application Logging service on BTP

The dashboard offers detailed filtering and statistics which are very handy.

Image%202%3A%20Application%20logging%20dashboard%20overview

Image 2: Application logging dashboard overview

Troubleshooting

I faced a few issues when developing and building it because the tutorials were outdated or didn’t support the use case. Maybe this can help you.

java.lang.UnsupportedClassVersionError

“java.lang.UnsupportedClassVersionError: com/sap/demo/jco/ConnectivityRFCExample has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0 (unable to load class [com.sap.demo.jco.ConnectivityRFCExample])

This means the bytecode of the WAR file isn’t matching the BTP Java Buildpack. First you want to check the buildpack on BTP:

$ cf buildpacks

E.g. 52.0 = Java 8, 61.0 = Java 17. I had to set my Eclipse JDK to create code for 52.0 by setting it to Java 8 from Java 17. Also this help is useful (standard is Java 8, if using SAP Machine it’s Java 11).

POST, PUT, PATCH. DELETE will require X-CSRF-Token

If you don’t want that, turn it off, out-comment the section for the filter RestCsrfPreventionFilter in web.xml. This can help during testing. For production, better put it back in.

After setting up a fresh project with Maven Eclipse looks “funny”

You got to switch the facet in the project properties to Java.

Maven repository not found

Add it to the Java Build Path in the project’s properties.

Image%203%3A%20Project%20properties%2C%20Java%20build%20path

Image 3: Project properties, Java build path

“Where is the source code?”

Please check my LinkedIn post in that matter.

Conclusion

Building a proxy to translate between the RFC and REST world requires an agnostic proxy. It is a universal solution without function module specific logic, and the REST service is channeled through the SAP Cloud Connector. Using the RFC-flavor for defining a destination on the SAP BTP platform with Java Web Tomcat 9 API allowed for easy integration. By following this blog, building an efficient proxy is now possible, and it will be useful for businesses using older SAP S/4HANA or ECC systems.

Let me know your feedback – I’m eager to learn how you are using the approach for your web use cases!

 

Sara Sampaio

Sara Sampaio

Author Since: March 10, 2022

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x