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.
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:
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 |
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 |
|
Table Type |
Defines the structure of an internal table (not a database table) including row type and key. The closest concept in Java is |
Table type |
|
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 |
|
|
|
|
Numeric |
|
|
|
|
Date and Time |
|
Dates, timestamps |
|
|
Boolean |
|
Flags, indicators |
|
|
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
-
create the
JCoFunction
object -
manipulate its parameter lists
-
execute the function
-
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
-
A transaction is started for the connection in SAP
-
The RFCs are called requesting not to commit changes
-
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.