Getting Started

Making Calls to SAP Systems

This chapter provides a short introduction to SAP Function Modules (FM) and their semantics.

Introduction to ABAP FMs, RFCs and the Data Dictionary

ABAP is one of SAP’s proprietary programming languages. In ABAP, an FM is a reusable procedure that encapsulates specific logic and can be called independently. Unlike functions in functional programming, FMs can be stateful. The closest concept in Java is a static method. Like a static method an FM can be stateful, use mutable data and have side effects.

An FM that is enabled to be called remotely is called a Remote Function Call (RFC).

An FM call in ABAP looks like this:

DATA: lv_username TYPE BAPIBNAME,
      lt_address TYPE BAPIADDR3_T,
      lt_return  TYPE BAPIRET2.

lv_username = 'some_user'.

CALL FUNCTION 'BAPI_USER_GET_DETAIL'
  EXPORTING
    USERNAME = 'some_user'
  IMPORTING
    RETURN = lt_return
  TABLES
    ADDRESS = lt_address
  EXCEPTIONS
    USER_NOT_FOUND = 1
    OTHERS = 2.

IF sy-subrc <> 0.
  " Handle exceptions
ENDIF.

An FM has a name and consists of the following components:

  • Import Parameters: The input parameters that the FM accepts.

  • Export Parameters: The output parameters that the FM returns after execution.

  • Changing Parameters: The parameters that the FM accepts and can change.

  • Tables: Lists of data of the same structure used to pass to and from the FM. Also changeable.

  • Exceptions: Numerical return codes to indicate exceptions, can be checked via system field sy-subrc.

Data Dictionary Type

Parameters can be of different types defined in the so-called ABAP Data Dictionary (DDIC).

DDIC Type Description Example JCo Interface

Data Element

Defines a single field and its domain. Internal and external representations can differ, see section Leading Zeros.

Data element BAPIBNAME is a CHAR12, a character sequence (string) of length 12.

See Data Domains.

Structure

A complex data type that consists of multiple fields (called components) grouped together. Fields can be of any type: data elements, structures or table structures.

Structure BAPIADDR3 has fields STREET, CITY, COUNTRY etc. that each have their own data element.

JCoStructure. Most of the time we will be using its super interface JCoRecord.

Table Type

Defines the structure of an internal table (not a database table) including row type and key. The closest concept in Java is java.util.List.

Table type BAPIADDR3_T is the table type for structure BAPIADDR3. Table types are used for lists of a structure, similar to a list of objects in Java.

JCoTable

Data Domains

A domain defines the technical properties of a field, such as data type, length, decimal places and possible values. Domains ensure consistency and validation of data across different fields. The DDIC provides several types of domains. The following table provides an overview of the most relevant domain types for our purposes as well as their representation in JCo and our choice. The conversion is described in a later section.

Domain Type Data Types Example JCo Types Our Choice

Character

CHAR (Character), NUMC (Numeric Character)

CHAR(10) for BP numbers, NUMC(5) for zip codes

String, char[]

String

Numeric

DEC (Decimal), INT (Integer), FLOAT (Floating Point)

DEC(10,2) for monetary values, INT(4) for counters

int, byte, short, long, float, double, BigInteger, BigDecimal

int, BigDecimal (we do not use float at this point)

Date and Time

DATS (Date), TIMS (Time)

Dates, timestamps

Date for both, date and time

LocalDate, LocalTime

Boolean

CHAR(1), fixed values 'X' for true and '' for false

Flags, indicators

char

boolean

There are several other domain types we do not use directly.

Introduction to SAP JCo

SAP Java Connector (JCo) is a Java library to call RFCs (remote-enabled FMs) provided by a SAP systems.

The Java interface representing an FM is com.sap.conn.jco.JCoFunction. Its most important methods are:

JCoParameterList getImportParameterList();

JCoParameterList getExportParameterList();

JCoParameterList getChangingParameterList();

JCoParameterList getTableParameterList();

AbapException[] getExceptionList();

void execute(JCoDestination jCoDestination);
Its javadoc is part of the zip file.

To call an RFC from Java we have to

  1. create the JCoFunction object

  2. manipulate its parameter lists

  3. execute the function

  4. read and process the exceptions and parameter lists

This can be quite imperative and cumbersome. That’s why the adapter provides a Java library built on top of SAP JCo.

Calling RFCs using the Faktor Zehn Jco Library

Package de.faktorzehn.fscdadapter.service.jco of module fscd-adapter-business contains classes to make calling RFCs easier.

To make a call, autowire the JcoFunctionExecutorFactory bean into your class and create a JcoFunctionExecutor using JcoFunctionExecutorFactory.create(String functionName).

To map a domain object into a JCoFunction consider using JcoRecordWriter. To map from a JCoFunction to a domain object consider using BaseResultMapper, JcoTableIterable and JcoRecordReader.

In Java, the above FM call encapsulated in a service would look like this:

@Service
public class JcoUserDetailsService {

    private final JcoFunctionExecutorFactory functionExecutorFactory;
    private final DomainValueSource domainValueSource;

    public JcoUserDetailsService(JcoFunctionExecutorFactory functionExecutorFactory,
                                    DomainValueSource domainValueSource) {
        this.functionExecutorFactory = functionExecutorFactory;
        this.domainValueSource = domainValueSource;
    }

    public List<Address> readAddresses(String username) {
        return functionExecutorFactory.create("BAPI_USER_GET_DETAIL")
                .parameters(function -> function.getImportParameterList().setValue("USERNAME", username))
                .execute(() -> "Unable to read addresses for username '%s' ".formatted(username))
                .to(this::extractAddresses);
    }

    private List<Address> extractAddresses(JCoFunction function) {
        var table = function.getTableParameterList().getTable("ADDRESS");
        var addresses = new ArrayList<Address>();
        for (var row : JcoTableIterable.iterate(table)) {
            addresses.add(mapAddress(row));
        }
        return addresses;
    }

    private Address mapAddress(JCoRecord record) {
        Objects.requireNonNull(record);
        var read = new JcoRecordReader(record);

        return Address.builder()
                    .street(read.string("STREET"))
                    .city(read.string("CITY"))
                    .country(read.string("COUNTRY"))
                    .build();
    }

}
See the classes' javadoc and usages for further details and examples.
Conversion from and to JCo Types

For date and time, as well as boolean we chose not to use the JCo types. We use LocalDate and LocalTime instead of Date. We use boolean instead of char.

JcoRecordReader and JcoRecordWriter provide methods taking care of the conversion when reading from or writing to JcoRecord. Internally, they use the static methods of JcoHelper, which can be used directly when not interacting with JCoRecords.

Dealing with Leading Zeros

Data elements can have both internal and external representations. For instance, while '1001' may be the external representation, e.g. in the UI, of a BP number, a CHAR10, its internal representation is '0000001001' with leading zeros.

While some RFCs make this conversion implicitly, others do not. Unfortunately, neither we nor JCo can easily determine from the RFC’s signature whether leading zeros will be managed automatically. Therefore, it’s prudent to always add leading zeros to such fields.

Additionally, there’s also no straightforward way to identify which fields require conversion without accessing the DDIC in the SAP system and checking the conversion routines associated with the data elements. Please ask an SAP expert for help.

Known fields that require leading zeros:

  • BP number

  • Insurance Object number (e.g. policy number, claim number)

Adding Leading Zeros

JcoRecordWriter.alz(String,String,int) can be used to add leading zeros before calling an RFC. It uses the static method of the same name from JcoHelper.

A common way to avoid issues related to leading zeros is to use the full length of the field. For instance, ensuring all BP numbers start with a 1, such as 1000001001, eliminates the need for adding zeros. However, this approach is not the standard configuration in SAP systems for most fields. Moreover, values could be assigned externally, for instance by Faktor-IBP. In both cases there is no guarantee that leading zeros are present. Thus, the recommendation is to always convert and add leading zeros before calling an RFC.
Removing Leading Zeros

We do not want to display leading zeros in the adapter’s UI. Neither do we use leading zeros in the Faktor Zehn modules. The adapter serves as the single point to convert between the representations.

JcoRecordReader.rlz(String) can be used to remove leading zeros. It uses the static method of the same name from JcoHelper.

Transactional Behavior

The class JcoTransactionManager is an implementation of Spring’s PlatformTransactionManager. It allows making multiple RFCs within the same transaction instead of one transaction per RFC, as this can lead to complex error handling and compensating transactions etc.

How it works

  1. A transaction is started for the connection in SAP

  2. The RFCs are called requesting not to commit changes

  3. The transaction is committed or rolled back via RFC

For an RFC to support transactional behavior, it has to provide an importing parameter to control the commit on SAP side. This parameter is often named I_COMMIT or similar. To enable transactional behavior call JcoFunctionExecutor.transactional(parameterName) when creating the JCoFunction. Annotate a method or class with @Transactional("jcoTransactionManager") to make it transactional.