Deep Dive 18 with SAP Cloud SDK: Convenient Consum...
Enterprise Resource Planning Blogs by SAP
Get insights and updates about cloud ERP and RISE with SAP, SAP S/4HANA and SAP S/4HANA Cloud, and more enterprise management capabilities with SAP blog posts.
In June 2020 we have launched a brand new documentation portal which consolidates all the knowledge, feature documentation, comprehensive guides, video tutorials, troubleshooting, release notes, API documentation and more. Please, use it as a single reference point about SAP Cloud SDK. From now on it's the most complete and up to date source of information about the product.
Disclaimer: This blog post is only applicable for the SAP Cloud SDK version of at most 2.19.2. We plan to continuously migrate these blog posts into our List of Tutorials. Feel free to check out our updated Tutorials on the SAP Cloud SDK.
This deep dive focuses on how the SAP Cloud SDK simplifies the consumption of SOAP APIs exposed from SAP S/4HANA. It presents a sample application to demonstrate the invocation of a chosen SOAP API from the SAP API Business Hub.
This deep dive is part of a series. For a complete overview visit the SAP Cloud SDK Overview.
This tutorial requires access to an SAP S/4HANA system.
Goal of this blog post
So far the SAP Cloud SDK has provided extensive support for the consumption of S/4HANA OData APIs. While OData still remains the primary and preferred approach, SAP exposes numerous SOAP APIs in the context of SAP S/4HANA. As of version 2.4.1, the SDK simplifies the usage of such SOAP APIs.
In this deep dive, we will start by discovering an example SOAP API in the SAP API Business Hub. Subsequently, we will elaborate on the integration between the SAP Cloud SDK and the Apache Axis2 framework and conclude this deep dive by creating a sample application that demonstrates how you can leverage the SAP Cloud SDK to call the SOAP API.
For better understanding of this deep dive, we suggest to read the following tutorials beforehand:
The API documentation in the SAP API Business Hub reveals that this synchronous API is included in the communication scenario Finance - Contract Accounting Integration (SAP_COM_0167).
Moreover, it provides three operations:
Create Contract Accounts (ContractAccountCreate)
Retrieve Contract Accounts (
Update Contract Accounts (Contract AccountUpdate)
The sample application we're going to create focuses on retrieving contract accounts from SAP S/4HANA.
Download API Specification
As we will need it later, download the formal API specification in the WSDL format. The Web Services Description Language (WSDL) has been standardized by the World Wide Web Consortium (W3C) and is an XML-based language to describe the characteristics of web services.
Under API References, click on Download Specification.
Inspecting the downloaded WSDL file in a text editor reveals the target URL of the SOAP service. Search for the XML tag <soap:address> and look for its attribute "location".
At runtime, the SAP Cloud SDK makes sure that the SOAP call gets sent to the correct SOAP API URL in your connected SAP S/4HANA system.
Looking at the service name, we see that it is named "service" by default.
We recommend to adjust this value to represent the purpose of this service, that is, managing contract accounts. Thus, we change the service name to "ManageContractAccountService" and save the file.
Simplified SOAP API consumption using SAP Cloud SDK
Leveraging Apache Axis2 as SOAP consumption framework
In the following, we elaborate on our decision to use Apache Axis2 as SOAP consumption framework and its integration with the SAP Cloud SDK.
What is Axis2 and why did we choose it?
The SOAP protocol is based on HTTP as application protocol and, therefore, you could go ahead and manage the SOAP API request and response flow yourself by creating and processing the SOAP envelopes in a hand-crafted manner on your own. However, this approach appears to be a bad idea as it is highly error-prone and time-consuming. Furthermore, generally engineers do know that there is no need to reinvent the wheel over and over again.
There are various frameworks and libraries out there in the Java ecosystem that take over the interaction with the SOAP API for you. While they all have their benefits and drawbacks, we have decided to integrate the SAP Cloud SDK with the Apache Axis2 framework.
From a general perspective, we have found it to provide the best ratio of maturity to simplicity. On a more concrete level, facts such as providing an out-of-the-box Maven plugin for code generation and the support of the new Apache HttpClient in version 4 were convincing arguments. Also, it is under continuous maintenance and bug fixes and security patches are expected to be published on time.
At the time of writing, we leverage the latest version Apache Axis2 1.7.8.
How does Apache Axis2 help us?
Instead of dealing with the SOAP API on HTTP level, Apache Axis2 introduces API-specific Java classes as abstraction layer. We refer to them as SOAP client classes henceforth.
This abstraction layer gets generated based on the service metadata contained in the corresponding WSDL file. As a result, you deal with Java objects representing the business objects from the underlying application domain.
These classes allow to access your SOAP API in a type-safe manner and hide certain technical details from you, such as payload (de-)serialization and authentication. For example, you invoke the Java method contractAccountRetrieve to call the respective operation provided by the SOAP API.
Apache Axis2 and SAP Cloud SDK are well-integrated
As of release 2.4.1 of the SAP Cloud SDK, an integration between Apache Axis2 and the SDK has been introduced.
That is, while consuming the SOAP API by leveraging the SOAP client classes, at runtime the SAP Cloud SDK hooks into the communication flow and ensures that the URL and all necessary HTTP headers (e.g. for proxy and trust settings) are set for the respective SAP S/4HANA system.
Custom data type conversion in Axis2
While deserializing the received SOAP envelope in the SOAP API response into API-specific Java objects, data type conversion takes place in a dedicated Axis2 converter utility class. The SAP Cloud SDK registers an SAP S/4HANA-specific custom converter class at application startup that handles conversion from SAP S/4HANA-specific date and time values into Java objects.
If needed, you can register your own custom converter class as well as follows.
Note that your custom converter class has to extend the ConverterUtil class from Apache Axis2.
Create sample application
In the following, we will present all necessary steps to create the sample application. They involve generating a fresh project skeleton, generating the SOAP client classes for consuming the SOAP API and implementing the servlet that uses the generated client classes.
Generate project skeleton for SAP Cloud Platform, Cloud Foundry
In order to create our sample application, we have to generate a fresh project skeleton based on the Maven archetypes provided by the SAP Cloud SDK. In this example we focus on the TomEE archetype for SAP Cloud Platform, Cloud Foundry.
We assume that we have a newly generated project having artifactId "contractaccounting" that can be built with
mvn clean install
on the command line.
Generate SOAP client classes
The SOAP client classes are API-specific and act as abstraction layer on the Java code level. Before we implement our sample application, we have to generate the SOAP client classes based on the API documentation specified in the respective WSDL file.
There exist two options to start the code generation:
We recommend to use the Maven plugin for code generation. As the SAP Cloud SDK manages dependencies via Maven, using this plugin fits nicely into the development flow of SDK-based cloud applications. After configuring the plugin in your application pom file, you get the Java classes generated each time automatically.
We consider this approach as more advantageous as it is less error-prone (i.e., no manual activity involved) and also ensures that changes to your WSDL file are always reflected throughout your entire code base.
Code Generation using Axis2 Tool WSDL2Code Maven Plugin
Here is how to integrate the plugin into the sample application.
In the application root pom, we introduce the maven plugin under <build>.
Note that we mention the package name and the path to the WSDL file. The databinding is set to ADB explicitly. The sync mode set to "sync" indicates that we intend to use the SOAP API in a synchronous manner. Unpacking the classes results in a separate Java file per class, rather than all classes stored in few large code files.
Running
mvn clean install
from within the application directory reveals that the Maven plugin gets invoked and generates the Java classes.
Code Generation using Axis2 Code Generator Command Line Tool
Apart from the Maven plugin, you can use the command line interface (CLI) wsdl2java that is shipped as part of the Apache Axis2 binary distribution.
After downloading and extracting the zip archive, you can find the wsdl2java CLI in the bin folder.
As preparation for the code generation, you have to specify the environment variable AXIS2_HOME to point to the Axis2 directory on your file system. The following screenshot shows the environment variable management UI under Windows.
If you can call the command wsdl2java on the command line, then the AXIS2_HOME variable is specified correctly.
We recommend to check out the CLI documentation and get familiar with the CLI parameters.
As a next step, we call the wsdl2java tool by passing the following parameters.
Parameter
Explanation
-uri
Specifies where the tool finds your WSDL file.
-o
Specifies output directory the generated code gets stored in.
-p
Specifies Java package where generated code resides in.
-s
Specifies that SOAP API responds synchronously.
-u
Specifies to create one java file per java class. Otherwise, all code is put into one large file.
In our environment under Windows 10, I put the WSDL file into a directory named wsdl and executed the CLI from the folder above.
After couple of seconds, the generated SOAP client classes reside in the directory soap-client on the same level as the wsdl folder.
Notice that the Java interface ManageContractAccountService is implemented by the Java class ManageContractAcountServiceStub. We will mainly use these artifacts in our sample application later on.
At next, we will present the Maven plugin that can also generate the Java classes for you.
If you want to use the manual generation via CLI instead, you have to move the generated classes manually into your application source code directory.
Implement the sample application
Once the SOAP client classes are present (regardless of generated via CLI or Maven plugin), we can proceed by using them in our servlet to invoke the SOAP API.
Application use case
The application should expose a servlet that receives a business partner key as parameter and presents an overview of all contract accounts from the SAP S/4HANA system belonging to the given business partner.
From a more technical perspective, the servlet shall listen on the relative path /soap and expect business partner key as GET parameter. For instance, /soap?businessPartner=PARTNER01 leads to the invocation of the SOAP API to return contract accounts for business partner PARTNER01.
Implement the servlet
At first, we rename the HelloWorldServlet to SoapServiceServlet and the relative path to soap.
@WebServlet("/soap")
public class SoapServiceServlet extends HttpServlet {
Of course, this is optional, but we prefer to make the purpose of the servlet explicit by giving it a representative name.
We implement the method doGet as a GET operation suffices to deliver the business partner key in the query string of the request URL.
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
At first, we obtain a reference to the response writer and instantiate an ERP configuration context.
final PrintWriter writer = response.getWriter();
final ErpConfigContext configContext = new ErpConfigContext();
Subsequently, the servlet reads the business partner key from the URL, conducts error handling for the case that this parameter was not provided and instantiates the BusinessPartner class which originates from the Java Virtual Data Model for OData.
final String businessPartnerKey = request.getParameter("businessPartner");
if (businessPartnerKey == null || businessPartnerKey.isEmpty()) {
throw new IllegalArgumentException("No business partner key was specified.");
}
final BusinessPartner businessPartner = BusinessPartner.builder().businessPartner(businessPartnerKey).build();
As of version 2.4.1, the SAP Cloud SDK simplifies the SOAP API consumption by introducing the class SoapQuery.
Consider this example.
final SoapQuery<ManageContractAccountServiceStub> soapQuery = new SoapQuery<>(ManageContractAccountServiceStub.class, configContext);
We instantiate a SoapQuery and pass the name of the generated client class representing the SOAP API (we refer to it as the service class in the remainder of this blog) to the type argument. We can leave the diamond operator empty as the Java compiler can derive the type argument value applying type inference. Additionally, we pass the service class type and the ERP configuration context instance to the SoapQuery constructor. Under the hood, the SoapQuery instance takes care of instantiating the actual service class.
Since version 2.0.0 the SAP Cloud SDK runs on Java 8 and, therefore, we may leverage nice Java 8 features, such as lambda expressions, to keep the code concise.
We can access the service class instance by using a lambda expression as follows.
final ContractAccountByIdentifyingElementsResponse_sync soapResponse = soapQuery.execute(service -> {
final ContractAccountByIdentifyingElementsQuery_sync queryParameters = getSoapQueryParameterObjectForContractAccountRetrieve(Calendar.getInstance(), businessPartner);
We call the execute method on the SoapQuery instance and call the operation to retrieve contract accounts on the the service class instance inside the lambda expression. The input parameter object for the API call is created inside a private helper method we will look at later. Note that the return type of execute is the same as the one from contractAccountRetrieve. That is, you can directly assign the execute call to the SOAP response object.
Thereafter, we look at the the SOAP response object to see if there was any contract account returned.
if (soapResponse.getContractAccountByIdentifyingElementsResponse_sync().getContractAccount().length == 0) {
writer.write("No contract account found for business partner " + businessPartner.getBusinessPartner());
return;
}
writer.write("Successfully retrieved contract accounts for business partner " + businessPartner.getBusinessPartner());
In case there were contract accounts returned, we can iterate over the respective array and print the information we want.
Regarding error handling, the SDK introduces the SoapException class that covers all error situations related to the SOAP API invocation. We recommend to put the code that deals with SoapQuery into a try catch statement.
try {
final SoapQuery<ManageContractAccountServiceStub> soapQuery = new SoapQuery<>(ManageContractAccountServiceStub.class, configContext);
//omitted the other stuff
} catch (SoapException e) {
e.printStackTrace(writer);
}
Creating the input parameter object
Depending on the underlying data model, creating the Java object that acts as input for the SOAP API call can become relatively verbose. That is why we recommend to encapsulate this code into a helper method which expects the parameters of relevance and returns the fully-fledged query parameter object.
In the following example, we pass the business partner and we retrieve the respective Java object.
private ContractAccountByIdentifyingElementsQuery_sync getSoapQueryParameterObjectForContractAccountRetrieve(Calendar calendar, BusinessPartner businessPartner) {
final BusinessPartnerInternalID businessPartnerInternalID = new BusinessPartnerInternalID();
businessPartnerInternalID.setBusinessPartnerInternalID(new Token(businessPartner.getBusinessPartner()));
final BusinessPartnerIdentification businessPartnerIdentification = new BusinessPartnerIdentification();
businessPartnerIdentification.setInternalID(businessPartnerInternalID);
final ContractAccountByIdentifyingElements selection = new ContractAccountByIdentifyingElements();
selection.setBusinessPartner(businessPartnerIdentification);
final GLOBAL_DateTime creationDateTime = new GLOBAL_DateTime();
creationDateTime.setGLOBAL_DateTime(calendar);
final BusinessDocumentMessageHeader messageHeader = new BusinessDocumentMessageHeader();
messageHeader.setCreationDateTime(creationDateTime);
final ContractAccountByIdentifyingElementsQueryMessage message = new ContractAccountByIdentifyingElementsQueryMessage();
message.setMessageHeader(messageHeader);
final ContractAccountByIdentifyingElements[] selections = {selection};
message.setContractAccountSelectionByIdentifyingElements(selections);
final ContractAccountByIdentifyingElementsQuery_sync queryParameterObject = new ContractAccountByIdentifyingElementsQuery_sync();
After we have finished the implementation of the servlet, we can proceed to write an integration test connecting it to a real SAP S/4HANA system.
At first, we assume that you have a class SoapServiceTestServlet. The former name HelloWorldServlet should have been changed already while renaming the HelloWorldServlet.
@RunWith( Arquillian.class )
public class SoapServiceServletTest
The custom converter class for data type conversion that the SDK introduces with version 2.4.1. is registered by the means of a servlet context listener. Therefore, we have to ensure that this servlet context listener is part of the deployment for our integration test.
@Deployment
public static WebArchive createDeployment()
{
return TestUtil.createDeployment(SoapServiceServlet.class, Axis2CustomConverterListener.class);
}
Make sure that you mock the ERP destination using the mocking capabilities of the SAP Cloud SDK. In this sample code we use ERP_100 as destination name for demonstration purposes.
@BeforeClass
public static void beforeClass()
{
mockUtil.mockDefaults();
mockUtil.mockErpDestination("ERP_100");
}
Within the test method we invoke the SoapServiceServlet and pass a given business partner key in the URL.
@Test
public void testService()
{
final String businessPartnerKey = "BP01";
final String body = given().get("/soap?businessPartner=" + businessPartnerKey).body().asString();
Note that the servlet prints "Successfully retrieved contract accounts" if contract accounts were obtained. Thus, we can base the test assertion on the occurence of this text string.
Furthermore, note that you have to supply both files credentials.yml and systems.yml in the resources folder of your integration tests to leverage the mocking capabilities of the SDK for local testing.
If the configuration is correct and the provided business partner has contract accounts assigned, the integration test will pass.
Deploy the sample application
Apart from leveraging an integration test to ensure the correct functionality, we can run the application in a TomEE application server on the local machine and call the servlet in an internet browser.
Open a command line and set the following environment variables.
set destinations=[{name: 'ErpQueryEndpoint', url: 'https://myXXXXXX.s4hana.ondemand.com', username: 'USER', password: 'PASSWORD', properties:[{"key":"proxy","value":"http://PROXY:8080"}]}]
set ALLOW_MOCKED_AUTH_HEADER=true
Note that setting the proxy within the destination ErpQueryEndpoint is optional.
Afterwards, we run the following command from the /application directory of your project.
mvn clean install tomee:run
After couple of seconds, the server is up and running.
Open an internet browser and access the servlet manually via the following URL.
Note that you have to specify the correct business partner key.
The servlet now prints the success message and the details of all found contract accounts.
This deep dive focuses on the local deployment, you can of course also deploy this application to the SAP Cloud Platform. For this purpose, check out the tutorials about Cloud Foundry or Neo respectively.
Conclusion
In this tutorial, we took a deeper look at one example SOAP API, introduced the usage of the Apache Axis2 framework and touched upon its integration with the SAP Cloud SDK.
By creating a sample application we demonstrated how to leverage the new SoapQuery class to simplify the consumption of such a SOAP API.
Stay tuned for later blog posts and deep dives as part of this blog series.