Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
CarlosRoggan
Product and Topic Expert
Product and Topic Expert
 

This blog is part of the series of tutorials for beginners.

In the previous tutorial we’ve made our first experience with the data source API for OData V2 consumption.
We’ve learned in a simple example how we can write simple code that connects to an existing simple OData v2 service and exposes the retrieved simple data as simple OData v4 service.
In the present tutorial we’re going to take the next step and implement all supported operations: READ, CREATE, UPDATE, DELETE, QUERY (CRUDQ)

 

Prerequisites


In order to implement the CUD (CREATE, UPDATE, DELETE) operations, we need access to an OData v2 service which supports write access.

A good idea is to use the SAP Gateway Demo System.
We need to quickly register, then we can use the sample service. Please refer to the description in prerequisites blog
Make sure to take a note of your user and password.

 

Preparation


The preparation steps are similar like in previous blog:
Access the OData v2 service which we want to consume, then make it available via destination in the cloud.

Check OData V2 service for consumption

This is the URL of the OData v2 service which we want to consume in our Java code:

https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC

 

Create Destination in Cloud Foundry

In the present tutorial we’re using a different OData V2 service than in the previous, so we need a new destination configuration.
See here for more information about creating a destination in SAP Cloud Platform, Cloud Foundry Environment.

See below the details to be entered for the present tutorial:

 




































Name ES5
Type HTTP
Description Destination for OData services on Demo System
URL https://sapes5.sapdevcenter.com
Proxy Type Internet
Authentication BasicAuthentication
User <your Demo-System-user>
Password <your Demo-System-password>


 

 

Optional: make familiar with backend service

In our new service implementation, we’re using an existing OData service to get the required Data.
As such, we need to know how that service is built and how it works.
I think it makes sense to manually try the operations on the service which we’re going to execute programmatically afterwards.
However, it is not mandatory for following the tutorial.

Please refer to the Appendix 5 section at the end of this page to see the URLs and instructions for the requests.

Metadata:
There’s a little info which we need to know about the service, because it affects our work:The entity of interest is called BusinessPartner and the entity set is called BusinessPartnerSet

- The name of the key field: BusinessPartnerID
- When creating a new BusinessPartner, the value for the key field is generated in the ERP backend
- When creating a new BusinessPartner, all non-nullable properties must be filled (e.g. BusinessPartnerRole, etc)
- When creating a new BusinessPartner, we have to know that the email property must be unique, otherwise the ERP backend will complain

Note:
Regarding the properties which are non-nullable in the ERP backend:
I think the best practice would be to declare them as Nullable=false in our service metadata as well.
However, I think we should omit those facets, so we can test the behavior of our service in case we break those constraints.

 

Scenario


In this tutorial, we’re still keeping things simple, as we want to focus on implementing CUD operations.
Along with the OData v4 service which we’re going to create, we’re offering access to all customers in a simplified way.
That’s our mission.
So what we have to do is:
We provide a service with a simple model, less and flat properties with intuitive names

 

Project


Project creation is described here
Same like in previous tutorial, after project creation, we have to do a few changes to the manifest.yml file:
    services:
- demoxsuaa
- demodestination
env:
ALLOW_MOCKED_AUTH_HEADER: 'true'

Please refer to the Appendix for the full content of the manifest.yml file.

 

Model


This is our simple model:
<EntityType Name="Customer">
<Key>
<PropertyRef Name="CustomerId" />
</Key>
<Property Name="CustomerId" Type="Edm.String" />
<Property Name="CompanyCountry" Type="Edm.String" />
<Property Name="CompanyName" Type="Edm.String" />
<Property Name="CompanyEmail" Type="Edm.String" />
</EntityType>
<EntityContainer Name="EntityContainer">
<EntitySet Name="Customers" EntityType="demo.Customer" />
</EntityContainer>


Please refer to the Appendix 1 section at the end of this page to get the full content of the file.

 

Implementation


In this tutorial, all we have to do in our Java code is to delegate the incoming service requests to the consumed backend service.
Nothing else.
We aren’t implementing any OData specific requirements, all the work is done by the backend service which we consume.
We’re only delegating.

However, in order to delegate the call, we have to transform the structure of our v4 model to the structure of the v2 backend service.

Differences are small:
EntityType BusinessPartner is renamed to Customer
EntitySet BusinessPartnerSet is renamed to Customers
The amount of properties is reduced
The properties are renamed
The BusinessPartner has a property Address which is a ComplexType. We take only one simple property into our model
In our v4 service, we ignore 3 properties (currency, role, address type) which are mandatory in the backend. So when creating a (v4) customer with our service, we hard-code these values before delegating to backend.

Summarizing, in our implementation, our job is to map property names.



 

Note:
In our sample code, we’re using HashMaps. As you know, all that could be done with POJOs as well, but I think that sticking to HashMaps makes it easier to follow the tutorial, because we can keep all the code in one class.

Please refer to the Appendix section at the end of this page for the full source code.

 

Query


Implementing the QUERY operation for an OData v2 data source was already explained in the previous tutorial.

However, in the current example, we have little more work to do.
Remember that in the previous blog we intentionally defined our model with property names which were identical to the v2 property names.
To avoid effort.
Now the time has come to be less lazy and more flexible.
We call the v2 service in the same way as before and in the result we get a list of maps which correspond to the v2 entity.
Since our v4 entity has different structure and different property names, we have to manually fill each of our v4 property with the value which we get from the v2 map.

The following example shows mapping of a property with different name.
In the response, we get a map which is a BusinessPartner. We access the desired property via the key of the map and assign that value as value of a second map which represents our v4 customer:
Map<String, Object> v4CustomerEntity = new HashMap<String, Object>();
v4CustomerEntity.put("CustomerId", v2BusinessPartner.get("BusinessPartnerID"));

 

The following example shows how we’re flattening a complex type (Address) into a normal property (CompanyCountry).
From the map which represents a v2 BusinessPartner we access the Address property. The value of this entry in the BusinessPartner map, is again a map. So we can cast and access the Country property of the Address map.
Map<String, Object> v2BuPaAddress = (Map<String, Object>)v2BusinessPartner.get("Address"); 
v4CustomerEntity.put("CompanyCountry", v2BuPaAddress.get("Country"));


 

Read


Implementing the READ is very similar to QUERY, only one aspect to take care of:

The key property


When sending an READ request to our v4 DemoService, the user of our v4 service points to a single resource which is identified by its key.

Example:

<host> /odata/v4/DemoService/Customers('0100000001')

As you already know, this is the short way of pointing to a resource (applicable if there’s only one key property). The full notation contains the key property name(s)

Example

<host>/odata/v4/DemoService/Customers(CustomerId='0100000001')

 

If you’re now assuming that the property name can become a problem, then you’re right.
The key property name of our v4 service is different from the key of the v4 service.
So even though the key value is the same,  we cannot just forward the key to the backend, because the key contains property name and value

The solution to this problem is simple: we have to convert the key property name of our v4 service to the name of the consumed v2 service.

Example:

<v4-host>/odata/v4/DemoService/Customers(CustomerId='0100000001')

will become

<v2-host>.../IWBEP/GWSAMPLE_BASIC/BusinessPartnerSet(BusinessPartnerID='0100000001')

To achieve that, we create a little helper method:
private Map<String, Object> convertV4CustomerKeytoV2BusinessPartnerKey(Map<String, Object> v4Key){
Map<String, Object> v2BusinessPartnerKey = new HashMap<String, Object>();
v2BusinessPartnerKey.put("BusinessPartnerID", v4Key.get("CustomerId"));
return v2BusinessPartnerKey;
}

 

Create


When implementing the CREATE operation, we have to be aware that 2 transformations are waiting for us to be implemented.

First:
The user of our v4 service sends a POST request with a request body containing data that matches our v4 service definition.
We have to convert the payload before we forward it to the v2 service.
That’s done in a helper method, please refer to the end of this page for full source code
Map<String, Object> v4CustomerToCreate = createRequest.getMapData();
Map<String, Object> v2BusinessPartnerToCreate = convertV4CustomerToV2BusinessPartner(v4CustomerToCreate);

 

Second:
After successful creation, the v2 service responds with the payload of the newly created BusinessPartner entity.
That’s according to the specification: an OData v2 service always has to send the created entity in the response of the CREATE operation.
In v4, as we already learned in a previous tutorial, the CREATE request can be configured, if no response payload is desired. The framework will take care of sending it or not, depending on the request. However, while implementing our CREATE operation, we always have to provide the response payload.
To do so,we take the result of the v2 creation and convert it back to the v4 structure:
Map<String, Object> createdV4Customer = convertV2BusinessPartnerToV4Customer(createdBusinessPartner);
return CreateResponse.setSuccess().setData(createdV4Customer).response();

 

Update


The implementation of this operation has a few noteworthy aspects

Key Property

Same like discussed above (READ operation).

 

Update method

This refers to the HTTP verb which can be chosen for executing an UPDATE operation: PUT or PATCH
In our implementation, when executing the UPDATE request to the v2 service, we have to pass the desired HTTP verb.
Depending on your use case or on the implementation of the consumed v2 service, this has an impact.
In our case, we just use the same method like the user of our v4 service uses.
So we delegate the HTTP verb.

To do so, we first retrieve the HTP verb which was used to call our v4 service:
updateRequest.getHttpMethod()
then use it for the v2 request:
UpdateMethod updateMethod = UpdateMethod.valueOf(updateRequest.getHttpMethod()); 
UpdateRequest.execute(updateMethod, "ES5");

 

Conditional Request

This is a new topic for us, we shouldn’t go too  much into detail, just few words.

The consumed V2 service [has defined an eTag such that it] requires an If-Match header [for UPDATE], otherwise it fails

We could design our v4 service accordingly [and define an eTag as well and also require the header from our users], but we want to keep it simple.
So we do a kind of workaround: we just hardcode the required header and pass it to the v2 service. As value we just declare that we wish to match “all”:
.withHeader("If-Match", "*") 

 

Convert payload

Since an UPDATE request intends to modify the stored data, it sends the new data in the request payload.
Like in the CREATE operation, we need to convert the v4-payload-format to the structure of the consumed v2 service.
We can simply reuse the convert method which we coded for the CREATE operation.

One thing to mention:
In case of PATCH, user are encouraged to send only the property which they want to modify.
So during our conversion of property names, we have to make sure that we don’t fill the value of the missing properties with null.
Object v4Email = v4Customer.get("CompanyEmail");
if(v4Email != null) {
v2BusinessPartner.put("EmailAddress", v4Email);
}

 

Delete


In case of DELETE the request to v2 service doesn’t return any response payload, same like UPDATE.
In the UPDATE implementation we’ve ignored the return type of the execute method.
In below snippet we’re accessing it, just to show that it can be used,e.g. in case that detailed trouble shooting is necessary, or as part of detailed error handling:
ODataDeleteResult result = ODataDeleteRequestBuilder
.withEntity("/sap/opu/odata/IWBEP/GWSAMPLE_BASIC", "BusinessPartnerSet", v2BuPaKey)
.build()
.execute("ES5");
LoggerFactory.getLogger(getClass())
.info("DELETE request sent to V2 service returned status code: "
+ result.getHttpStatusCode());

 

Run


Up to this point, we've discussed the 3 relevant files:

Service model (edmx file, DemoService.xml)
Service implementation (java class file, ServiceImplementation.java)
Service deployment descriptor (manifest file, manifest.yml)

As usual, we build with maven and deploy the war file to Cloud Foundry.
After deployment, we may wish to verify the correct service binding (as declared in the manifest), to make sure that the destination configuration is ready to be used by our application (as deployed via war file).

To be on the safe side, let's briefly show how the CRUDQ requests are executed against our v4 customer service.

As mentioned above, in the Appendix 5 section you can find the corresponding requests to the v2 service, you can verify the difference between both services.

Note:
In case that the query takes too long, because users are creating too many entries, you might wish to reset the sample data. See here for instructions.

 

Query

https://<yourProjectName>.cfapps.eu10.hana.ondemand.com/odata/v4/DemoService/Customers



 

 

Read

<host>/odata/v4/DemoService/Customers('0100000001')



 

Create

URL:
<host>/odata/v4/DemoService/Customers

HTTP verb:
POST

Request headers:
















Header name Header value
Authorization <your user/pw>
Content-Type application/json


Request body:
{
"CompanyCountry": "DE",
"CompanyName": "MyCompanyExtension1",
"CompanyEmail": "CompExt1@mycompany1.de"
}

Result:



 

Update

URL:
We should use the URL of the entry which we've just created in the previous step, e.g.

<host>/odata/v4/DemoService/Customers('0100005884')

HTTP verb:
PATCH

Request headers:
















Header name Header value
Authorization <your user/pw>
Content-Type application/json


Request body:
{
"CompanyEmail": "MyContact1@mycompany1.de"
}

Result:



 

Delete

URL:
We should use the URL of the entry which we  created in the previous step, e.g.

<host>/odata/v4/DemoService/Customers('0100005884')

HTTP verb:
DELETE



 

Summary


After going through this tutorial, you're more familiar with consuming an OData v2 data source in your service implementation, deal with it, modify it, play with it.

In the next tutorial, we'll have a look at some more features offered by the data source API.

 

Links


Overview of blog series and link collection

 

 

Appendix 1


Source code of manifest file: manifest.yml


---
applications:
- name: DemoProject
memory: 512M
buildpack: sap_java_buildpack
path: target/DemoProject-0.0.1-SNAPSHOT.war
services:
- demoxsuaa
- demodestination
env:
ALLOW_MOCKED_AUTH_HEADER: 'true'

Appendix 2


Source code of model file: DemoService.xml



<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="demo" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="Customer">
<Key>
<PropertyRef Name="CustomerId"/>
</Key>
<Property Name="CustomerId" Type="Edm.String" />
<Property Name="CompanyCountry" Type="Edm.String"/>
<Property Name="CompanyName" Type="Edm.String"/>
<Property Name="CompanyEmail" Type="Edm.String" />
</EntityType>
<EntityContainer Name="EntityContainer">
<EntitySet Name="Customers" EntityType="demo.Customer"/>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

 

Appendix 3


Source code of Java file: ServiceImplementation.java


package com.example.DemoProject15sourcev2;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpStatus;

import com.sap.cloud.sdk.odatav2.connectivity.ODataCreateRequestBuilder;
import com.sap.cloud.sdk.odatav2.connectivity.ODataDeleteRequestBuilder;
import com.sap.cloud.sdk.odatav2.connectivity.ODataException;
import com.sap.cloud.sdk.odatav2.connectivity.ODataQueryBuilder;
import com.sap.cloud.sdk.odatav2.connectivity.ODataUpdateRequestBuilder;
import com.sap.cloud.sdk.odatav2.connectivity.UpdateMethod;
import com.sap.cloud.sdk.service.prov.api.operations.Create;
import com.sap.cloud.sdk.service.prov.api.operations.Delete;
import com.sap.cloud.sdk.service.prov.api.operations.Query;
import com.sap.cloud.sdk.service.prov.api.operations.Read;
import com.sap.cloud.sdk.service.prov.api.operations.Update;
import com.sap.cloud.sdk.service.prov.api.request.CreateRequest;
import com.sap.cloud.sdk.service.prov.api.request.DeleteRequest;
import com.sap.cloud.sdk.service.prov.api.request.QueryRequest;
import com.sap.cloud.sdk.service.prov.api.request.ReadRequest;
import com.sap.cloud.sdk.service.prov.api.request.UpdateRequest;
import com.sap.cloud.sdk.service.prov.api.response.CreateResponse;
import com.sap.cloud.sdk.service.prov.api.response.DeleteResponse;
import com.sap.cloud.sdk.service.prov.api.response.ErrorResponse;
import com.sap.cloud.sdk.service.prov.api.response.QueryResponse;
import com.sap.cloud.sdk.service.prov.api.response.ReadResponse;
import com.sap.cloud.sdk.service.prov.api.response.UpdateResponse;



public class ServiceImplementation {

@Query(serviceName = "DemoService", entity = "Customers")
public QueryResponse getCustomers(QueryRequest queryRequest) {

try {
List<Map<String, Object>> v2BusinessPartnerList = ODataQueryBuilder
.withEntity("/sap/opu/odata/IWBEP/GWSAMPLE_BASIC", "BusinessPartnerSet")
.build()
.execute("ES5")
.asListOfMaps();

// convert the list of v2 maps to list of v4 maps
List<Map<String, Object>> v4CustomerList = convertV2BusinessPartnersToV4Customers(v2BusinessPartnerList);

return QueryResponse.setSuccess()
.setDataAsMap(v4CustomerList)
.response();

} catch (ODataException e) {
return QueryResponse.setError(ErrorResponse.getBuilder()
.setMessage("Error occurred while connecting to OData V2 service: " + e.getODataExceptionType() + " - " + e.getMessage())
.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR)
.setCause(e)
.response());
}
}

@Read(serviceName = "DemoService", entity = "Customers")
public ReadResponse getCustomer(ReadRequest readRequest) {
// get the id from the URL
Map<String, Object> v4CustomerKey = readRequest.getKeys();
Map<String, Object> v2BusinessPartnerKey = convertV4CustomerKeytoV2BusinessPartnerKey(v4CustomerKey);

try {
Map<String, Object> v2BusinessPartner = ODataQueryBuilder
.withEntity("/sap/opu/odata/IWBEP/GWSAMPLE_BASIC", "BusinessPartnerSet")
.keys(v2BusinessPartnerKey)
.build()
.execute("ES5")
.asMap();

//convert the v2 map to v4 map
Map<String, Object> v4Customer = convertV2BusinessPartnerToV4Customer(v2BusinessPartner);

return ReadResponse.setSuccess().setData(v4Customer).response();

} catch (ODataException e) {
return ReadResponse.setError(ErrorResponse.getBuilder()
.setMessage("Error occurred while connecting to OData V2 service: " + e.getODataExceptionType() + " - " + e.getMessage())
.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR)
.setCause(e)
.response());
}
}

@Create(serviceName = "DemoService", entity = "Customers")
public CreateResponse createCustomer(CreateRequest createRequest) {
// get the request payload with data for customer to create, and convert it to v4 BusinessPartner
Map<String, Object> v4CustomerToCreate = createRequest.getMapData();
Map<String, Object> v2BusinessPartnerToCreate = convertV4CustomerToV2BusinessPartner(v4CustomerToCreate);

try {
// call the backend to trigger the creation
Map<String, Object> createdBusinessPartner = ODataCreateRequestBuilder
.withEntity("/sap/opu/odata/IWBEP/GWSAMPLE_BASIC", "BusinessPartnerSet")
.withBodyAsMap(v2BusinessPartnerToCreate)
.build()
.execute("ES5")
.asMap();

// result of CREATE operation in v2 service is the created BusinessPartner.
// our impl has to return the created v4 customer. As such, we need to convert the result as well
Map<String, Object> createdV4Customer = convertV2BusinessPartnerToV4Customer(createdBusinessPartner);

return CreateResponse.setSuccess().setData(createdV4Customer).response();

} catch (ODataException e) {
return CreateResponse.setError(ErrorResponse.getBuilder()
.setMessage("Error occurred while connecting to OData V2 service: " + e.getODataExceptionType() + " - " + e.getMessage())
.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR)
.setCause(e)
.response());
}
}

@Update(serviceName = "DemoService", entity = "Customers")
public UpdateResponse updateCustomer(UpdateRequest updateRequest) {
// access info if PUT or PATCH was used
UpdateMethod updateMethod = UpdateMethod.valueOf(updateRequest.getHttpMethod());
//convert key
Map<String, Object> v4CustomerKey = updateRequest.getKeys();
Map<String, Object> v2BuPaKey = convertV4CustomerKeytoV2BusinessPartnerKey(v4CustomerKey);
//convert payload
Map<String, Object> customerToUpdate = updateRequest.getMapData();
Map<String, Object> v2BuPaToUpdate = convertV4CustomerToV2BusinessPartner(customerToUpdate);

try {
ODataUpdateRequestBuilder
.withEntity("/sap/opu/odata/IWBEP/GWSAMPLE_BASIC", "BusinessPartnerSet", v2BuPaKey)
.withBodyAsMap(v2BuPaToUpdate)
.withHeader("If-Match", "*") // our service has no etag, so just take all
.build()
.execute(updateMethod, "ES5");

return UpdateResponse.setSuccess().response();

}catch(ODataException e){
return UpdateResponse.setError(
ErrorResponse.getBuilder()
.setMessage("Error occurred while connecting to OData V2 service: " + e.getODataExceptionType() + " - " + e.getMessage())
.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR)
.setCause(e)
.response());
}
}


@Delete(entity = "Customers", serviceName = "DemoService")
public DeleteResponse deleteCustomer(DeleteRequest deleteRequest) {
Map<String, Object> v4CustomerKey = deleteRequest.getKeys();
Map<String, Object> v2BuPaKey = convertV4CustomerKeytoV2BusinessPartnerKey(v4CustomerKey);

try {
ODataDeleteRequestBuilder
.withEntity("/sap/opu/odata/IWBEP/GWSAMPLE_BASIC", "BusinessPartnerSet", v2BuPaKey)
.withHeader("If-Match", "*")
.build()
.execute("ES5");

return DeleteResponse.setSuccess().response();

}catch(ODataException e){
return DeleteResponse.setError(ErrorResponse.getBuilder()
.setMessage("Error occurred while connecting to OData V2 service: " + e.getODataExceptionType() + " - " + e.getMessage())
.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR)
.setCause(e)
.response());
}
}


/* HELPERS */

private List<Map<String, Object>> convertV2BusinessPartnersToV4Customers(List<Map<String, Object>> v2BusinessPartnerList){
List<Map<String, Object>> v4CustomerList = new ArrayList<Map<String, Object>>();

if (v2BusinessPartnerList != null && v2BusinessPartnerList.size() > 0) {
for (Map<String, Object> businessPartnerEntity : v2BusinessPartnerList) {
Map<String, Object> customerEntity = convertV2BusinessPartnerToV4Customer(businessPartnerEntity);
v4CustomerList.add(customerEntity);
}
}

return v4CustomerList;
}

@SuppressWarnings("unchecked")
private Map<String, Object> convertV2BusinessPartnerToV4Customer(Map<String, Object> v2BusinessPartner) {
Map<String, Object> customerEntity = new HashMap<String, Object>();
customerEntity.put("CustomerId", v2BusinessPartner.get("BusinessPartnerID"));
customerEntity.put("CompanyName", v2BusinessPartner.get("CompanyName"));
customerEntity.put("CompanyEmail", v2BusinessPartner.get("EmailAddress"));
// Address property is a ComplexType in backend. Here we take only one property for our v4 model
Map<String, Object> v2BuPaAddress = (Map<String, Object>)v2BusinessPartner.get("Address");
customerEntity.put("CompanyCountry", v2BuPaAddress.get("Country"));

return customerEntity;
}

private Map<String, Object> convertV4CustomerToV2BusinessPartner(Map<String, Object> v4Customer) {

// v2 service: Address property is a ComplexType, so we need a separate map
Map<String, Object> v2BuPaAddress = new HashMap<String, Object>();
Object country = v4Customer.get("CompanyCountry");
if(country != null) {
v2BuPaAddress.put("Country", country);
}

v2BuPaAddress.put("City", "-");
v2BuPaAddress.put("Street", "-");
v2BuPaAddress.put("AddressType", "02"); // address type 02 means business address

// the v2 BusinessPartner map
Map<String, Object> v2BusinessPartner = new HashMap<String, Object>();

v2BusinessPartner.put("Address", v2BuPaAddress);// add the map as value of the Address property (v2 service)

Object companyName = v4Customer.get("CompanyName");
if(companyName != null) {
v2BusinessPartner.put("CompanyName", companyName);
}

Object email = v4Customer.get("CompanyEmail");
if(email != null) {
v2BusinessPartner.put("EmailAddress", email);
}

// we can ignore the ID, because it is anyways generated in backend
//v2BusinessPartner.put("BusinessPartnerID", customerId);

// hard-coded values
v2BusinessPartner.put("BusinessPartnerRole", "01"); //customer role
v2BusinessPartner.put("CurrencyCode", "EUR");

return v2BusinessPartner;
}

private Map<String, Object> convertV4CustomerKeytoV2BusinessPartnerKey(Map<String, Object> v4Key){
Map<String, Object> v2BusinessPartnerKey = new HashMap<String, Object>();
v2BusinessPartnerKey.put("BusinessPartnerID", v4Key.get("CustomerId"));
return v2BusinessPartnerKey;
}

}

 

Appendix 4


Screenshot of Destination Configuration




 

Appendix 5


Sample requests for v2 backend service


Metadata

We need to check this, in order to know about the possible queries and the property names and also we might need info about property facets

https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/$metadata

 

Query

We’re interested in the list of BusinessPartners

https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/BusinessPartnerSet

 

Read

One single BusinessPartner

https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/BusinessPartnerSet('0100000010')

 

Create

https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/BusinessPartnerSet

Notes:
Email is mandatory and must be unique
BusinessPartnerID is generated in the backend
We don’t need to fill app properties, only the mandatory and useful ones
The backend service is protected against CSRF, so for modifying operations, we need to pass a valid csrf-token. See here

Headers:




















Header name Header value
Authorization <your user/pw>
x-csrf-token <your token>
Content-Type application/json


Request Body:
{
"Address": {
"City": "Berlin",
"PostalCode": "12345",
"Street": "Hauptstraße",
"Country": "DE",
"AddressType": "02"
},
"CompanyName": "My Startup Company",
"WebAddress": "http://www.mystartup.de",
"EmailAddress": "mystartup1 @mystartup.de",
"LegalForm": "GmbH",
"CurrencyCode": "EUR",
"BusinessPartnerRole": "02"
}

Response:
201

Response Body:
Contains Created entity and the URL of it

 

Update

PATCH https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/BusinessPartnerSet('0100012733')

Headers:
























Header name Header value
Authorization <your user/pw>
x-csrf-token <your token>
Content-Type application/json
If-Match *


Request Body
{
"Address": {
"Street": "Hauptstraße 11"
}
}

Response:
204

 

Delete

DELETE https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/BusinessPartnerSet('0100012733')

Headers:




















Header name Header value
Authorization <your user/pw>
x-csrf-token <your token>
If-Match *


Request Body:
empty

Response:
204

 

 

 
6 Comments