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 about Integration Gateway (IGW) in SAP Mobile Platform 3.0 (SMP).

Since SMP SP09, a new data source has been introduced:

the Custom Code data source allows to provide any data as OData service.

In order to achieve that, a script file needs to be implemented.

In the first part of this tutorial, we’ve learned how to implement the 2 methods that are required for read operations.

In the current tutorial, we’ll have a look at the write operations.

Again, it is shown in a very simplified way, based on hard-coded data, just to show the essentials.

The full source code can be found in the Appendix and also attached to this blog.

A quick start section for IGW-experts can also be found in the Appendix.

Prerequisites

  • SAP Mobile Platform 3.0 SP09 (SMP) installation.
  • Eclipse with SAP Mobile Platform Tools installed
    check this blog for help
  • Basic knowledge about SMP and Integration Gateway
    Check this blog for some introduction
  • The first part of this tutorial

Overview

1. Design the OData model

2. Implement the custom code

  2.1. createEntry(message)

  2.2. updateEntry(message)

  2.3. deleteEntry(message)

3. Summary

4. Links

5. Appendix

  5.1. Execute POST/PUT/DELETE requests

  5.2. Full source code

  5.3. Quick Start for experts

1. Design the OData model

In the current tutorial, we’re going to reuse the project that we’ve created in the first tutorial.

Alternatively, you can create a new project and import the edmx file that is attached to this blog.

As a reminder, our OData model looks as follows:

The data type for the properties is Edm.String

2. Implement the custom code

In the present tutorial, we’ll implement the methods that are in charge of creating or changing data in the backend.

2.1. createEntry(message)

This method is invoked, if the user of our OData service executes a CREATE operation.

In order to create a new customer-entity, he fires a POST request to the entityset URL:

https://localhost:8083/gateway/odata/SAP/<MYSERVICE>/CustomerSet

This POST request sends a payload in the request body.

This payload contains the data that should be created in the backend.

The IGW framework allows us to access that payload in order to use it to create the data in the backend.

The payload is stored in the message object that is passed in the signature of the createEntry() method and can be accessed via message.getBody()

In our simple example, we ignore the payload that is passed, because we’re dealing with dummy data.

We have to consider:

As of OData specification (BTW, we’re talking about OData V2), once an OData service receives a POST request, it not only has to create the given data in the backend, but additionally it has to respond to the request with the created entity in the response body.

And this is what we have to implement in our example service.

To keep it simple, we once more return some hard-coded dummy data.

Note:

The reason behind this requirement is that in many cases, the backend generates some fields of the data. For example, this can be the ID fields or GUID fields, also timestamps are typically generated in the backend, etc.

Therefore, returning the created entry ensures that the user knows what data has really been created and gives him the opportunity to know e.g. the ID of the newly generated entry.

One last topic to consider:

If the creation fails in the backend, e.g. because the given payload is not valid, we have to set a proper HTTP status code. We cannot expect the framework to do this automatically.

We ignore this step in the current tutorial.

Summarizing the tasks:

1. Access the given payload and create the data in the backend

    -> We ignore this step in the current tutorial

2. Return the created data

3. In case of error, react properly

    -> We ignore this step in the current tutorial

The implementation is the same like the getEntry() method, because here again, we simply return one dummy entity:

      var customerMap = new HashMap();

    customerMap.put("ID", "2");

    customerMap.put("Name", "MyNewCompany");

    message.setBody(customerMap);   

      return message;

After implementing the createEntry() method, we can go ahead and test it.

For executing a POST request, we need a REST client tool.

If you’re not familiar with it, check the Appendix section below.

After generate and deploy, send a POST request with the following payload:


<entry xmlns="http://www.w3.org/2005/Atom"


  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"


  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"


  xml:base="https://localhost:8083/gateway/odata/SAP/CUSTOMDEMO1/">


      <content type="application/xml">


        <m:properties>


            <d:ID>2</d:ID>


            <d:Name>MyNewCompany</d:Name>


        </m:properties>


    </content>


</entry>









In the response body of the POST request, we should see the hard-code values that we have written in our script.

The request payload is ignored.

Optional

We can enhance our implementation:

instead of ignoring the request payload, we can access it and use those values to return them in the response:

      importPackage(java.util);

      // access the request payload from the message object

      var customerMap = message.getBody()

      var theGivenID = customerMap.get("ID");

      var theGivenName = customerMap.get("Name");

      // create the map carrying the dummy data

      var customerMap = new HashMap();

      customerMap.put("ID", theGivenID);

      customerMap.put("Name", theGivenName);

        //set the list as the body.

        message.setBody(customerMap);

      return message;

After deployment, you can send any data in the request body, our OData service will return the same in the response.


<entry xmlns="http://www.w3.org/2005/Atom"


  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"


  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"


  xml:base="https://localhost:8083/gateway/odata/SAP/CUSTOMDEMO1/">


      <content type="application/xml">


        <m:properties>


            <d:ID>44</d:ID>


            <d:Name>MyOtherCompany</d:Name>


        </m:properties>


      </content>


</entry>










Note:

In order to keep it simple, we ignore any error handling.

2.2. updateEntry()

In case of UPDATE, the user of our OData service wants to change one or more property values. So he sends a PUT request and passes the data in the payload.

We can access the data from the message object.

Unlike the POST request, for the PUT request we don’t have to send any payload along with the response body.

And again, in case of invalid requests or any issues in the backend, we have to take care of error handling.

In our simple tutorial, we aren’t changing any backend-data, so there’s nothing to implement here.

However, let’s take the opportunity to look into the request payload.

The request payload is passed to the OData service via the REST client and the IGW framework passes it to us in the message object of the updateEntry() method.

We retrieve the data from the message object and we’ll see that it is passed as a java.util.Map

We can then retrieve the new values for the properties.

Then, instead of sending them to a backend, we write them to the log.

The sample code shows how to access the request payload and how to write an error message to the log.

Note that we have to add the respective import statement in order to use the logging classes.

    importPackage(com.sap.gateway.ip.core.customdev.logging);

      var customerMap = message.getBody()

      var theGivenID = customerMap.get("ID");

      var theGivenName = customerMap.get("Name");

    log.logErrors(LogMessage.TechnicalError, "The ID to update: " + theGivenID);

    log.logErrors(LogMessage.TechnicalError, "The Name to update: " + theGivenName);

      return message;

Now you can Generate&Deploy the project and execute a PUT request using the REST client.

The request URL:

https://localhost:8083/gateway/odata/SAP/<YOURSERVICE>/CustomerSet('1')

The request payload is obtained in the same way like for a POST request (see Appendix, and don't forget the csrf-token)

Example payload:


<entry xmlns="http://www.w3.org/2005/Atom"


  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"


  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"


  xml:base="https://localhost:8083/gateway/odata/SAP/CUSTOMDEMO1/">


      <content type="application/xml">


      <m:properties>


        <d:ID>3</d:ID>


        <d:Name>MyCoolestCompany</d:Name>


      </m:properties>


  </content>


</entry>









After sending the PUT request, check the SMP error log, you’ll find the following 2 (error-)messages there:

ERROR#com.sap.gateway.ip.core.customdev.processor.IGWScriptProcessor##smpAdmin#http-bio-8083-exec-1####f34f9e4f-7476-4403-ab4b-14682ca3b6ac#RequestResponse###[Gateway][TECHNICAL][TechnicalError]:The ID to update: 3 |

ERROR#com.sap.gateway.ip.core.customdev.processor.IGWScriptProcessor##smpAdmin#http-bio-8083-exec-1####f34f9e4f-7476-4403-ab4b-14682ca3b6ac#RequestResponse###[Gateway][TECHNICAL][TechnicalError]:The Name to update: MyCoolestCompany

2.3. deleteEntry()

In case of deletion, no request body and no response body is required.

In a real productive OData service, we would have to identify, which entry has to be deleted.

This info is given in the URL, the so-called Key Predicates.

In our example, the URL that points to the entry to be deleted looks as follows:

https://localhost:8083/gateway/odata/SAP/<YOURSERVICE>/CustomerSet('1')

The ‘1’ corresponds to the key field. The name of the key field is: ID

So the alternative way for the URL:

https://localhost:8083/gateway/odata/SAP/<YOURSERVICE>/CustomerSet(ID='1')

Some entities may require more than one key field in order to uniquely identified, that’s why we talk about key predicates in plural.

An example could be: CustomerSet(ID='1', Country='US')

In our example, let’s access the key predicates and – instead of deleting the data in a backend – we write it to the log.

One information for your convenience:

The helper class ScriptUtil parses the key predicate part of the URI (it can be long, depending on the amount of key fields and of the data types) and returns it as HashMap.

The HashMap contains entries with key-value pairs and the key is the name of the key property.

Therefore, we can simply call keyPredicates.get("ID")

    importPackage(com.sap.gateway.core.ip.script);

    importPackage(com.sap.gateway.ip.core.customdev.logging);

      var keyPredicates = ScriptUtil.getKeyPredicates(message);

      var theGivenID = keyPredicates.get("ID");

    log.logErrors(LogMessage.TechnicalError, "The ID to delete: " + theGivenID);

      return message;

In order to test the deletion, you have to again fetch the csrf-token and then send it.

The URL:

https://localhost:8083/gateway/odata/SAP/<YOURSERVICE>/CustomerSet('1234')

And the result in the log will be

ERROR#com.sap.gateway.ip.core.customdev.processor.IGWScriptProcessor##smpAdmin#http-bio-8083-exec-7####d86363bf-c7ae-4049-b58b-9c3cc4790af7#RequestResponse###[Gateway][TECHNICAL][TechnicalError]:The ID to delete: 1234

3. Summary

In this tutorial we’ve learned how to implement the creation, update and deletion of entries of our OData service, based on Integration Gateway.

We’ve learned how to access the payload that is sent by the user of our OData service.

And we’ve learned how to access the key-field information of the URL

4. Links

Tutorial for Understanding CUSTOM CODE data source Part 1: implementing read operations

Installing SMP toolkit

Tutorial for Integration Gateway

The series of tutorials about the REST data source. Many implementation details are also relevant for the "Custom Code" data source.

5. Appendix

5.1. Execute POST/PUT/DELETE requests

In order to execute these requests, a REST client has to be used.

This can be installed as browser-plugin in Firefox or Chrome.

In my example, I’m using the “Advanced REST Client” in Chrome.

First, I open the browser app.

Then, in a second browser tab of Chrome, I invoke the service-Url, such that the browser does the login. The advantage is that I don't need to provide credentials in the REST client tool.

Then, in the REST client tab, I’m able to invoke the READ-URL with GET:

https://localhost:8083/gateway/odata/SAP/<YOURSERVICE>/CustomerSet('1')

x-csrf-token

The SMP server requires x-csrf-token for modifying requests.

Therefore, I proceed as follows in order to obtain that token

Add a Header with name x-csrf-token and value fetch

Then I execute a GET request to the service-URL of a single READ:

https://localhost:8083/gateway/odata/SAP/<YOURSERVICe>/CustomerSet('1')

Note:

for obtaining the csrf-token, the root-Service-URL can be used as well

https://localhost:8083/gateway/odata/SAP/<YOUR_SERVICE>

In the response, I can find a Header with the value of the x-csrf-token

I copy the value to clipboard and paste it in the request Header value field, which replaces the previous value (which was fetch)

Then I change the HTTP method to POST

Then I copy the response body and paste it to the request body field (payload in the request section of the REST client)

Then I change the URL such that it points to the entity collection:

https://localhost:8083/gateway/odata/SAP/<YOURSERVICE>/CustomerSet

Then I add a Header with name Content-Type and value application/atom+xml

Then I execute the request.

The request is successful, the status code is 201 and the response body contains the payload that we’ve hard-coded in Eclipse

5.2. The full source code





/**
  This function is executed during a GET_FEED call. It returns a list of HashMaps corresponding to the Entity being queried.
*/
function getFeed(message){
    importPackage(java.util);
    var customerMap = new HashMap();
    customerMap.put("ID", "1");
    customerMap.put("Name", "MyCoolCompany");
    var list = new ArrayList();
    list.add(customerMap);
    message.setBody(list);
    return message;
}
/**
  This method will be called for READ operation.
*/
function getEntry(message) {
    importPackage(java.util);
    // create the map carrying the dummy data
    var customerMap = new HashMap();
    customerMap.put("ID", "1");
    customerMap.put("Name", "MyCoolCompany");
    //set the list as the body.
    message.setBody(customerMap);
    return message;
}
/**
  This method will be called for CREATE operations.
*/
function createEntry(message) {
    importPackage(java.util);
    // access the request payload from the message object
    var customerMap = message.getBody()
    var theGivenID = customerMap.get("ID");
    var theGivenName = customerMap.get("Name");
    // create the map carrying the dummy data
    var customerMap = new HashMap();
    customerMap.put("ID", theGivenID);
    customerMap.put("Name", theGivenName);
    //set the list as the body.
    message.setBody(customerMap);
    return message;
//    // hard-coding the response
//    var customerMap = new HashMap();
//    customerMap.put("ID", "2");
//    customerMap.put("Name", "MyNewCompany");
//
//    message.setBody(customerMap);
//    return message;
}
/**
This method will be called for UPDATE(PUT) operations.
*/
function updateEntry(message) {
    importPackage(com.sap.gateway.ip.core.customdev.logging);
    var customerMap = message.getBody()
    var theGivenID = customerMap.get("ID");
    var theGivenName = customerMap.get("Name");
    log.logErrors(LogMessage.TechnicalError, "The ID to update: " + theGivenID);
    log.logErrors(LogMessage.TechnicalError, "The Name to update: " + theGivenName);
    return message;
}
/**
This method will be called for DELETE operations.
*/
function deleteEntry(message){
    importPackage(com.sap.gateway.core.ip.script);
    importPackage(com.sap.gateway.ip.core.customdev.logging);
    var keyPredicates = ScriptUtil.getKeyPredicates(message);
    var theGivenID = keyPredicates.get("ID");
    log.logErrors(LogMessage.TechnicalError, "The ID to delete: " + theGivenID);
    return message; 
}









5.3. Quick Start for experts


If you're already familiar with IGW scripting:

createEntry()


access the request payload from the message object.

The format is java.util.Map

Set the created entry as response body to the message object

The format is java.util.Map

updateEntry()

access the request payload from the message object.

The format is java.util.Map

Access the key predicates from the message object, with the helper method

ScriptUtil.getKeyPredicates(message);

deleteEntry()

Access the key predicates from the message object, with the helper method

ScriptUtil.getKeyPredicates(message);



1 Comment