Skip to Content
Technical Articles

How to call function modules using SAP Cloud SDK for Java

Hi All,

I am writing this blog to describe steps to build Java application calling function modules on SAP Cloud Platform CF with SAP Cloud SDK.

Function modules are on on-premise environment and called by JCo via SAP Cloud Connector.

There are so many useful information here.(2020/8/21 Added)

Architecture

System architecture is as below image, which is on the SAP Help portal.

It takes time to test this for me, since I was unfamiliar with SAP Cloud Platform services like XSUAA and App Router.  To understand SAP Cloud Platform services, I posted an article about App Router, XSUAA and Java Application in Japanese.

Environment

I used both Windows and Ubuntu PC, just because my development environment is separated into Java and others.  It is not mandatory to use both windows and ubuntu.

Local PC(Java development on Windows)

  • OS: Windows10 64-bit
  • openJDK: 1.8.0_242
  • Chocolatey: 0.10.15
  • maven: 3.6.3
  • IDE: IntelliJ IDEA Community Edition 2019.3.5
  • CF cli: 6.51.0+2acd15650.2020-04-07
  • SAP Cloud SDK for Java: 3.19.1

Local PC(App router development on Ubuntu)

  • OS: Ubuntu18.04.01 LTS
  • nvm: 0.35.3
  • Node.js: 12.16.2
  • npm: 6.14.5
  • SAP Cloud SDK for JavaScript:1.19.0
  • SAP Cloud SDK cli: 0.1.8
  • CF cli: 6.51.0+2acd15650.2020-04-07

Cloud Foundry

  • Java Buildpack version: sap java build pack 1.25.0
  • CF (Europe – Frankfurt)

Netweaver ABAP

  • NetWeaver ABAP 7.53 SP0

Cloud Connector

  • SAP Cloud Connector 2.11.2

Seeing following stackoverflow question, invoking function modules/ BAPIs from localhost is not supported.

What you wanna do is technically not supported: You want to invoke a BAPI from your localhost via the transport protocol RFC using the JCo library (used behind the scenes via the SAP Cloud SDK). The usage of JCo requires to run your app on SAP Cloud Platform.

https://stackoverflow.com/questions/59819607/target-host-is-not-specified-exception-while-calling-bapi-from-java-using-sap-cl

I’m not sure if invoking them is possible with Neo SDK.

when deploying locally you must add a dependency to Neo SDK (which also contains the JCO libraries).

https://answers.sap.com/questions/13012430/classnotfoundexception-when-calling-remote-functio.html?childToView=13013904#answer-13013904

Steps

1. Setup Cloud Connector

Access SAP Cloud Connector and setup connection from SAP Cloud Platform to ABAP Server.  The steps are same as the one of  blog post “Configure Cloud Connector for RFC connection from cloud to on-premise”.

2. Create Destination

Create Destination service by CF cli command “cf create-service”.  “lite” is service plan name.  The steps are same as the one of  blog post “Configure Cloud Connector for RFC connection from cloud to on-premise”.

3. Create Routes for Java and App Router

I did this step on Ubuntu PC.

Firstly create routes for Java application and App Router by cf cli create-route command.

Please replace from <space name> to your space name.  Name <host name of XXX> as you like.

cf create-route <space name> cfapps.eu10.hana.ondemand.com --hostname <host name of App Router>
cf create-route <space name> cfapps.eu10.hana.ondemand.com --hostname <host name of Java App>

4. Create Java Application

I did this step on Windows PC.

With maven, create a Java Application project.

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

I set following information during prompt.

  • groupId: com.sap.cloud.sdk
  • artifactId: test-rfc
  • version: 1.0-SNAPSHOT
  • package: com.sap.cloud.sdk

Change “manifest.yml” file.

  • Add the created route in step 3 to “routers” -> “route”
  • Change values of “random-route”
  • Add the created destination to “services”
---
applications:

- name: test-rfc
  memory: 1024M
  timeout: 300
  routers:
    - route: https://<route host name>.<route domain name>
  random-route: false
  path: application/target/test-rfc-application.war
  buildpacks:
    - sap_java_buildpack
  env:
    TARGET_RUNTIME: tomee7
    SET_LOGGING_LEVEL: '{ROOT: INFO, com.sap.cloud.sdk: INFO}'
    JBP_CONFIG_SAPJVM_MEMORY_SIZES: 'metaspace:128m..'
  services:
#  - my-application-logs
  - <destination name>
#  - my-connectivity

 

Thought it is unusual, “Plugins Usage Analytics Maven Plugin” version 3.19.1 doesn’t exist on the maven repository.  So I changed the version on “<project root>/application/pom.xml”.  In addition, I changed skipUsageAnalytics value.

            <plugin>
                <groupId>com.sap.cloud.sdk.plugins</groupId>
                <artifactId>usage-analytics-maven-plugin</artifactId>
                <!-- changed from 3.19.1 to 3.18.0 -->
                <version>3.18.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>usage-analytics</goal>
                        </goals>
                        <configuration>
                            <!-- changed from false to true -->
                            <skipUsageAnalytics>true</skipUsageAnalytics>
                            <generateSalt>true</generateSalt>
                            <!--
                            Note: A random salt is auto-generated once the project is built for the first time.
                            Please keep the generated salt in the POM file, for example, when pushing to git.

                            To learn more, visit: https://blogs.sap.com/2018/10/23/usage-analytics-s4sdk/
                            -->
                            <salt>55578c0d7b7bff985020ad9ce2e033dbb9bd6425acb24eb8d4b40dcc1ba0f9b0</salt>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

After project generation, build and deploy the Java application.

mvn clean package && cf push

Check if the deployment is successful using Curl.  Only this command run on Ubuntu PC.

$ curl https://test-rfc.cfapps.eu10.hana.ondemand.com/hello
Hello World!

5. Create XSUAA service and App Router

I did this step on Ubuntu PC.

5.1. Create App Router Project

I created App Router using SAP Cloud SDK for Javascript cli. During prompt, input Java application name “test-rfc”.

It is not mandatory to use the cli.  App Router and relevant codes are very simple.

$ sap-cloud-sdk add-approuter
No 'manifest.yml' found.
Enter project name as maintained in Cloud Foundry: test-rfc
  ✔ Creating files
✅ Successfully added approuter to your project.

Generated files might need customization. Documentation available here:
- xs-security.json (for help check https://help.sap.com/viewer/4505d0bdaf4948449b7f7379d24d0f0d/2.0.02/en-US/e6fc90df44464a29952e1c2c36dd9861.html)
- xs-app.json (for help check https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/c103fb414988447ead2023f768096dcc.html)
- mainfest.yml (for help check https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/ba527058dc4d423a9e0a69ecc67f4593.html)

5.2. Change “xs-security.json”

Change “tenant-mode” value on “aprouter/xs-security.json” file.  I have never used “shared”.  Please refer to tutorial and help document for further detail.

{
  "xsappname": "test-rfc",
  "tenant-mode": "dedicated"
}

5.3. Create XSUAA service

Create XSUAA service using xs-security.json by cf cli command.

cf create-service xsuaa application <xsuaa name> -c xs-security.json

 

bind XSUAA service and the Java application.

cf bind-service test-rfc <xsuaa name>

5.4. Create App Router

Change manifest.yml file.

applications:
  - name: test-rfc-approuter
    routes:
      - route: >-
          https://<App Router Route host name><App Router Route domain>
    path: .
    memory: 128M
    buildpacks:
      - nodejs_buildpack
    env:
#      TENANT_HOST_PATTERN: >-
#        "test-rfc-(.*).cfapps.sap.hana.ondemand.com"
      destinations: >-
        [{"name":"test-rfc","url":"https://<Java Route host name>.<Java Route domain>","forwardAuthToken":true}]
    services:
      - <xsuaa name>

Deploy the App Router.

cf push

Now go to App router route url via browser and redirect to XSUAA service.  After authentication, “Hello World!” is displayed on browser.

7. Create Connectivity

I did this step on Ubuntu PC.

Create Connectivity service.  The Connectivity service is necessary to connect from cloud to on-premise environment according to Help document.

cf create-service connectivity lite <connectivity name>

8. Update Java Application

I did this step on Windows PC.

Change “<project root>/manifest.yml” to bind created services.  Make sure that “env” is also updated.

---
applications:

- name: test-rfc
  memory: 1024M
  timeout: 300
  random-route: false
  routers:
    - route: https://test-rfc-java.cfapps.eu10.hana.ondemand.com
  path: application/target/test-rfc-application.war
  buildpacks:
    - sap_java_buildpack
  env:
    TARGET_RUNTIME: tomee7
    SET_LOGGING_LEVEL: '{ROOT: INFO, com.sap.cloud.sdk: INFO}'
    JBP_CONFIG_SAPJVM_MEMORY_SIZES: 'metaspace:128m..'
    xsuaa_connectivity_instance_name: "<xsuaa name>"
  services:
  - <destination name>
  - <xsuaa name>
  - <connectivity name>

Create “<project root>/application/src/main/java/com/sap/cloud/sdk/RfcServlet.java”.  Please replace from <destination name> to your destination name.  The servlet receive “name” url parameter, which is function module name.  If there is no “name” parameter, “RFC PING” is called.

I haven’t implemented security in this Java App, so the app can be called without App Router though it fails.  I posted about the security implementation in another article, though it is in Japanese.

package com.sap.cloud.sdk;


import com.google.gson.Gson;
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor;
import com.sap.cloud.sdk.s4hana.connectivity.exception.RequestExecutionException;
import com.sap.cloud.sdk.s4hana.connectivity.rfc.RfmRequest;
import com.sap.cloud.sdk.s4hana.connectivity.rfc.RfmRequestResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/rfc")
public class RfcServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final Logger logger = LoggerFactory.getLogger(RfcServlet.class);
    private static final Destination destinationRfc =
            DestinationAccessor.getDestination("<destination name>");

    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
            throws IOException {

        logger.info("Start get method: " + request.getRequestURI());
        String parameter = request.getParameter("name");
        logger.info("Get parameter 'name': " + parameter);
        if (parameter == null) {
            parameter = "RFCPING";
        }
        Iterable names = destinationRfc.getPropertyNames();
        logger.info(new Gson().toJson(names));

        try {
            final RfmRequestResult rfmTest = new RfmRequest(parameter, false) //false is for non-commit
                    .execute(destinationRfc);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write(new Gson().toJson(rfmTest));
        } catch (RequestExecutionException e) {
            e.printStackTrace();
        }
    }
}

Finally, build and deploy the Java application.

# from application directory
mvn clean package && cd .. && cf push

 

Result

When I open the Java Application via App router, then result is like this.

https://<App Router route host name>.<App Router route domain>/rfc?name=RFC_TEST_EXTERNAL

There are many words “Error” on the screen. but actually it is not errors.  It just displays error mapping information.

If you access the app without App router, Internal Server Error occurs.

Be the first to leave a comment
You must be Logged on to comment or reply to a post.