Skip to Content

If you are wondering how to send batch request in Android using the SMP 3.0 SDK SP05+, then this blog is for you.

A batch request allows a mobile client to send multiple operations in a single HTTP request.  The following diagram represents the objects needed to create a batch request with SMP 3.0 native SDK for Android:

  • The ODataRequestParamBatch class represents a batch request and it can contain 1 or more batch items.
  • A batch item is represented by the ODataRequestBatchItem interface and the item can be single read request (ODataRequestParamSingle) or a change set (ODataRequestChangeSet).
  • A change set (ODataRequestChangeSet) can contain only CUD requests and their execution is atomic (either all requests executed successfully, or all failed).

BatchRequest.jpg

In order to use batch request, the mobile client need to:

1. Create a batch request

Code snippet – Create Batch Request (ODataRequestParamBatch)
ODataRequestParamBatch requestParamBatch = new ODataRequestParamBatchDefaultImpl();


2. Create the batch item payload for CREATE and UPDATE operations.


Code Snippet – Create Batch Item Payload (ODataEntity)

ODataEntity newEntity = new ODataEntityDefaultImpl(“RMTSAMPLEFLIGHT.Travelagency”);

newEntity.getProperties().put(“agencynum”, new ODataPropertyDefaultImpl(“agencynum”, “12345678”));

newEntity.getProperties().put(“NAME”, new ODataPropertyDefaultImpl(“NAME”, “Flight Center Inc”));

newEntity.getProperties().put(“STREET”, new ODataPropertyDefaultImpl(“STREET”, “123 Main street”));

 


3. Create a batch item and add item to the batch request


3.1 Single READ request


Code Snippet – Create Batch Item: Single READ Request

// Create batch item

ODataRequestParamSingle batchItem = new ODataRequestParamSingleDefaultImpl();

batchItem.setResourcePath(“TravelAgencies”);

batchItem.setMode(ODataRequestParamSingle.Mode.Read);

batchItem.setCustomTag(“something to identify the request”);


// Add batch item to batch request

requestParamBatch.add(batchItem);


3.2 Change set with UPDATE operation


Code Snippet – Create Batch Item: Change Set

// Create batch item

ODataRequestParamSingle batchItem = new ODataRequestParamSingleDefaultImpl();

// Allocate OData Entity

batchItem.setResourcePath(“TravelAgencies(‘12345678’)”);

batchItem.setMode(ODataRequestParamSingle.Mode.Update);

batchItem.setCustomTag(“something to identify the request”);

batchItem.setPayload(newEntity);

// Add headers

Map<String, String> createHeaders = new HashMap<String, String>();

createHeaders.put(“accept”, “application/atom+xml”);

createHeaders.put(“content-type”, “application/atom+xml”);

batchItem.setOptions(createHeaders);

// Create change set

ODataRequestChangeSet changeSetItem = new ODataRequestChangeSetDefaultImpl();

// Add batch item to change set.

// You can add more batch items to the same change set as long as they are CUD operations

changeSetItem.add(batchItem);

// Add batch item to batch request

requestParamBatch.add(changeSetItem);


4. Send Batch request: Batch requests are submitted as a single HTTP post request to the batch endpoint of a service. The HTTP status code of a batch response is generally “202 Accepted” and it only indicates that the overall request has been accepted for processing, not that every operation successfully completed. There will be a status code returned for each part of the multipart batch request.


Code Snippet – Send Batch Request

// Send request synchronously

ODataResponse oDataResponse = odataStore.executeRequest(requestParamBatch);

// Check http status response for batch request.

// Status code should be “202 Accepted”

Map<ODataResponse.Headers, String> headerMap = oDataResponse.getHeaders();

if (headerMap != null) {

   String code = headerMap.get(ODataResponse.Headers.Code);

}

// Get batch response

if (oDataResponse instanceof ODataResponseBatchDefaultImpl) {

  ODataResponseBatch batchResponse = (ODataResponseBatch) oDataResponse;

  List<ODataResponseBatchItem> responses = batchResponse.getResponses();

  for (ODataResponseBatchItem response : responses) {

      // Check if batch item is a change set

      if (response instanceof ODataResponseChangeSetDefaultImpl) {

       ODataResponseChangeSetDefaultImpl changesetResponse = (ODataResponseChangeSetDefaultImpl) response;

       List<ODataResponseSingle> singles = changesetResponse.getResponses();

            for (ODataResponseSingle singleResponse : singles) {

              // Get Custom tag

              String customTag = singleResponse.getCustomTag();

                        // Get http status code for individual responses

              headerMap = singleResponse.getHeaders();

              String code = headerMap.get(ODataResponse.Headers.Code);

                        // Get individual response

              ODataPayload payload = singleResponse.getPayload();

                         if (payload != null) {

                                     if (payload instanceof ODataError) {

                             ODataError oError = (ODataError) payload;

                             String uiMessage = oError.getMessage();

                      } else {

                                                  // TODO do something with payload

}

              }

       }

    } else {

       // TODO Check if batch item is a single READ request

    }

   }

}


Questions? let me know,


I would also recommend looking at this document to understand how SAP Gateway deal with Batch requests

How To Batch Multiple Operations Into A Single Request



Troubleshooting Section: Testing batch request in a REST client in 3 steps

Testing the batch request from a REST client will help you discard any problems with the backend.

For more information about the REST client, visit  SMP 3.0 : REST API Application Development

Step1: Onboard user and copy Application connection id from the response

Request: POST

URL: http://<server>:<port>/odata/applications/latest/<appid>/Connections

Headers

Content-Type: application/atom+xml

Authorization: Basic <encoded login and password>

Body

<?xml version=”1.0″ encoding=”UTF-8″?>

<entry xml:base=”http://localhost:8080/odata/applications/latest/com.sap.flight.kapsel/Connections

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“>

  <content type=”application/xml”>

<m:properties>

<d:DeviceType>Android</d:DeviceType>

</m:properties>

  </content>

</entry>

batchTest1.jpg

Step2: Fetch CSRF Token

Request: GET

URL: http://<server>:<port>/<appid>/

Headers

Authorization: Basic <encoded login and password>

X-SMP-APPCID: <value received from step 1: onboard user>

X-CSRF-Token: Fetch

batchTest2.jpg

Step3: Send Batch request with two read operations

Request: POST

URL: http://<server>:<port>/<appid>/$batch

Headers

Content-Type: multipart/mixed; boundary=batch

Authorization: Basic <encoded login and password>

X-SMP-APPCID: <value received from step 1: onboard user>

X-CSRF-Token: <value received from step 2: fetch CSRF Token>

Body (mind the spacing between the –batch tags, it’s very important)

–batch

Content-Type: application/http

Content-Transfer-Encoding: binary

GET CarrierCollection HTTP/1.1

–batch

Content-Type: application/http

Content-Transfer-Encoding: binary

GET TravelAgencies/?$filter= COUNTRY eq ‘CA’ HTTP/1.1

–batch–


batchTest3.jpg


Claudia

To report this post you need to login first.

21 Comments

You must be Logged on to comment or reply to a post.

  1. Ali Zahid

    Claudia Pacheco Can you please tell me if my code below is correct?

    ODataRequestParamBatch batch = new ODataRequestParamBatchDefaultImpl();

    for (…) {

      ODataRequestParamSingle request = new ODataRequestParamSingleDefaultImpl();

      request.setResourcePath(url);

      request.setMode(ODataRequestParamSingle.Mode.Read);

      batch.add(request);

    }

    OnlineStore.getInstance().getOnlineStore().scheduleRequest(batch, this);

    This request finishes really fast, and nothing is actually sent to my server.

    Also, how do I read my requests’ responses? They’re always null, before I believe they’re not reaching the server and resolving properly. And I couldn’t use your code (which only talks about change sets, and not read operations).

    (0) 
    1. Claudia Pacheco Post author

      Hi Ali,

      The code snippet I shared is executing a synchronous call and you can get the response directly after calling the executeRequest method as indicating in step 4.

      // Send request synchronously

      ODataResponse oDataResponse = odataStore.executeRequest(requestParamBatch);

      In your sample, you are executing a asynchronous call, which means you need to create a listener that implements ODataRequestListener class. The listener must implement the following methods:

      public void requestStarted(ODataRequestExecution request)

      public void requestCacheResponse(ODataRequestExecution request)

      public void requestFailed(ODataRequestExecution request, ODataException e)

      public void requestServerResponse(ODataRequestExecution request)

      public void requestFinished(ODataRequestExecution request)

      If the request is successful, the requestServerResponse method will be called. You can get the response by getting the response from the request parameter and following the code in step 4.

      public void requestServerResponse(ODataRequestExecution request) {

                  TraceLog.scoped(this).d(“requestServerResponse”);

                  if (request!=null && request.getResponse() !=null) {

                        ODataResponseSingle response = (ODataResponseSingle) request.getResponse();

      If the request fails, the requestFailed method will be called. So you have to implement that method as well.

      Best regards

      Claudia

      (0) 
      1. Ali Zahid

        This is one of my attempts for the requestServerResponse method – Private Paste – Pastie

        If you look at line 12, you’ll notice I check if the `payload` is null. The problem is, it always is null. The time it takes to fire `requestServerResponse` after I call `scheduleRequest` is only a few milliseconds, during which no request can possibly happen.

        Which is the part I’ve messed up on? Am I creating the read requests properly? Is my batch request happening correctly? These are my questions.

        (0) 
        1. Claudia Pacheco Post author

          Your read request looks fine.

          Are you using the latest version and patch? SDK SP07 PL01

          Could you check the http status code you get in the response? are you getting 202 as indicated in step 4?


          // Check http status response for batch request.

          // Status code should be “202 Accepted”

          Map<ODataResponse.Headers, String> headerMap = response.getHeaders();

          if (headerMap != null) {

             String code = headerMap.get(ODataResponse.Headers.Code);

          }


          (0) 
          1. Ali Zahid

            I’ve got a proxy on, and viewing the contents of my request. Nothing seems out of the ordinary. Just a 400 Bad Request.

            (0) 
            1. Claudia Pacheco Post author

              In order to discard any problems with the backend, I think it will be best to test the batch request from a rest client first. If it’s successful, then we can focus on the app. Let me find the payload for batch request and I’ll add it to the blog.

              (0) 
              1. Ali Zahid

                REST client also sent back 400; “Error Message : Could not perform the operation, contact your System Administrator. Check the log file for details”

                So I changed my request URLs to something simpler, and now the request just dies with an HTTP code of 0.

                (0) 
          2. Ana Velasquez

            Hi Claudia,

            The batch request that I’m creating is for the Create operation. I’m trying for hours to achieve this, but the error I’m getting is this, my http status code is 403, and the “description” of the error is “error”. Do you know what this possible could be? I know that http code 403 is in response for a forbidden request, but I’m not in context with what is wrong with my batch request. And which could be a possible solution?

            Best regards,

            Ana

            (0) 
            1. Claudia Pacheco Post author

              Hi Ana,

              I would suggest opening a discussion with your errors to reach the SMP community. You can mention this blog for me to get a notification. I understand you are adding a POST request in your batch, but I would recommend using a REST client to test a simple GET request in your batch to make sure your oData producer supports it. Follow the troubleshooting section in this blog and post the result in your new discussion.

              Best regards,

              Claudia


              (0) 
              1. Ana Velasquez

                Hi Claudia,

                I’ve done what you’ve recommended, first, in Advanced REST Client, I make a GET and POST Request in my batch and it worked fine, both of them. I’ve also created a new discussion with my errors and posted the results of the tests in REST Client, following the troubleshooting section in this blog. In REST Client, everything works fine, my problem is in Android, if I execute a Read operation (not inside a batch request), it works, but then if I tried to execute a Create operation (within a batch request), the 403 error appears.

                Hope you can pass by my discussion, “Batch request failed from Android. 403 Forbidden“.

                Thanks in advance.

                Best regards,

                Ana

                (0) 
  2. Ali Zahid

    Claudia Pacheco It seems that batch requests only work when using Relay Server to connect to SMP, but it doesn’t work when we use Apache Reverse Proxy.

    Is this a known SMP issue? Is there a solution?

    (0) 
    1. Claudia Pacheco Post author

      Hi Ali,

      According to SAP note 1904213 – SAP Mobile Platform Server Release Information

      “SAP Mobile Platform Server is compatible with HTTP(S) reverse proxies that support X509 (where required), cookie and header propagation, Web Sockets, and session affinity” and Apache 2.4 is reverse proxy that has been tested.

      I would suggest checking the SAP Note 1904213 and (if applicable) creating an incident on the SAP support portal, so you can get an official response from SAP support.

      Please share the results once you get the official response, so the community learns from it.

      In the meantime, I’ll investigate further

      Best regards,

      Claudia

      (0) 
  3. Kael Xin

    Dear Claudia,

        I want to send batch with create option,my code as the following:

    ODataRequestParamBatch requestParamBatch = new ODataRequestParamBatchDefaultImpl();

      // Allocate OData Entity

      // Create change set

      ODataRequestChangeSet changeSetItem = new ODataRequestChangeSetDefaultImpl();

      for (Map<String, Object> map : list) {

      ODataEntity newEntity = ODataEntityUtils.createODataEntity(

      entityName, map);

      // Create batch item

      ODataRequestParamSingle batchItem = new ODataRequestParamSingleDefaultImpl();

      batchItem.setResourcePath(“DecomEntitySet”);

      batchItem.setMode(ODataRequestParamSingle.Mode.Create);

      batchItem.setCustomTag(“something to identify the request”);

      batchItem.setPayload(newEntity);

      // Add headers

      Map<String, String> createHeaders = new HashMap<String, String>();

      createHeaders.put(“accept”, “application/atom+xml”);

      createHeaders.put(“content-type”, “application/atom+xml”);

      batchItem.setOptions(createHeaders);

      // Add batch item to change set.

      // You can add more batch items to the same change set as long as

      changeSetItem.add(batchItem);

      }

      // Add batch item to batch request

      requestParamBatch.add(changeSetItem);

    when I got the exception:  java.util.ArrayList.throwIndexOutOfBoundsException

    I work with smp sp05

    Best Regards,

    Kael

    (0) 
    1. Claudia Pacheco Post author

      Hi Kael,

      What does the ODataEntityUtils.createODataEntity do? I believe the java.util.ArrayList.throwIndexOutOfBoundsException you are getting seems to be related to this part of your code. Make sure the Map on your list are not null.

      for (Map<String, Object> map : list) {

        ODataEntity newEntity = ODataEntityUtils.createODataEntity(

        entityName, map);

      Additionally, there were some bugs with batch in the earlier releases. Batch request failed from Android. 403 Forbidden. I would recommend downloading the latest versions


      Best regards,

      Claudia

      (0) 
      1. Kael Xin

        Hi Claudia,

             Thanks,There’re some thing  wrong in my code ,

             and the code execute method:

             ODataResponse oDataResponse =

        odataStore.executeRequest(requestParamBatch);

             successfully,

             but the payload is null which get from response.

             There is nothing wrong in backend system, and I check the log file of SMP

            

             the error is : Missing start boundaryjava.io.IOException: Missing start boundary.

            

             Could you tell me how to resolve it?

        Best Regards,

        Kael

        (0) 

Leave a Reply