1 2 3 17 Previous Next

SAP Mobile Platform Developer Center

247 Posts

When the web service response comes dynamically for every request made, certain fields can be populated dynamically because of which response mapping cannot be done completely via mapping tool. Only static fields can be mapped using mapping editor and the dynamic fields can be mapped via custom scripts.

 

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for an operation

      3. Right click on odatasvc and choose Select data source

      4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.

     5. Specify the wsdl file and choose the operation from the list of soap operations and click on finish

     6. Right click on Query and Select Define ResponseMapping

     7. Map all the static fields as below

Here the fields under businessKeys property are populated only after a query request is fired. So, these dynamic fields are mapped via scripts

      8. Right click on Query and select Define Custom Code

     9. Get the entire response from message body, extract the dynamic field values and create a list of hashMaps as below. Set this list to custom header

    10. From the previously set header get the list of hashmaps and from the message body get the list of hashmaps for all static fields which were mapped using mapping editor, combine them into a single list of hashMaps as below

    11. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire an OData Request https://localhost:8083/gateway/odata/SAP/SAMPLE;v=1/QuerySet on the browser.

Wanted to share a quick-fix for an issue that came up this morning, when converting an iOS app from using Maven, to using CocoaPods.

 

With Maven, or just using the SAP Mobile SDK generally, you've needed to set values for HEADER_SEARCH_PATHS, LIBRARY_SEARCH_PATHS, and "Other Linker Flags".  With CocoaPods, you no longer need to, since they are supplied by build-scripts.

 

With the newest version of CocoaPods, I got the following warning on pod install:

 

[!] The `SAPCRMOData [Debug]` target overrides the `OTHER_LDFLAGS` build setting defined in `Pods/Target Support Files/Pods-SAPCRMOData/Pods-SAPCRMOData.debug.xcconfig'. This can lead to problems with the CocoaPods installation

    - Use the `$(inherited)` flag, or

    - Remove the build settings from the target.

 

[!] The `SAPCRMOData [Release]` target overrides the `OTHER_LDFLAGS` build setting defined in `Pods/Target Support Files/Pods-SAPCRMOData/Pods-SAPCRMOData.release.xcconfig'. This can lead to problems with the CocoaPods installation

    - Use the `$(inherited)` flag, or

    - Remove the build settings from the target.

 

(OTHER_LDFLAGS == "Other Linker Flags")

 

The previous developer had set the -all_load and -ObjC flags in the project Build Settings, and they were stepping on the flags which would be set by the CocoaPods script at compile-time.  As a result, I was getting errors in my xCode console that looked like this:

 

ld: symbol(s) not found for architecture i386

 

I knew my libraries were all in the correct places, so I looked back at the CocoaPods warning.  Sure enough, when I replaced the -all_load & -ObjC linker flags with $(inherited), the field immediately updated with the following:

 

-ObjC -lAfariaSLL -lCache -lClientHubSLL -lClientLog -lConnectivity -lCoreServices -lDatavault -lE2ETrace -lE2ETrace2 -lHttpConvAuthFlows -lHttpConversation -lLogger -lMAFFormatters -lMAFLogViewer -lMAFLogger -lMAFLogonManagerNG -lMAFLogonUING -lMAFUIComponents -lMAFUIHelper -lMAFZipHelper -lMobilePlace -lODataAPI -lODataOffline -lODataOnline -lParser -lPerformanceLib -lRequest -lSupportability -lUsage -lXScriptParser -lc++ -lcrypto -lsqlcipher -lssl -lstdc++.6.0.9 -lz -framework CFNetwork -framework CoreData -framework CoreFoundation -framework MessageUI -framework MobileCoreServices -framework Security -framework SystemConfiguration

 

And, the project built correctly!

soap supports Multipart WSDL's,In multipart WSDL files, an implementation WSDL file contains the wsdl:service. This implementation WSDL file imports an interface WSDL file, which contains the other WSDL constructs. This supports multiple Web services using the same WSDL interface definition.

in this page we will look in to handle the multipart wsdl using custom script.

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model Corresponding to the WSDL parameters.

    3. Right click on odatasvc and choose Select data source

    4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.

    5. Specify the wsdl file and choose the appropriate operation from the list of soap operations and click on finish

    6. Right click on Query and select Define Custom Code

    7. Select the script type as either javascript or groovy script

    8. Here in our case the wsdl has Authentication, so we are retrieving the credentials from the request headers and passing it to cxf component as Input in the form of a HashMap.

 

         

    9. Once the web service response is returned, the response has to be parsed to get the corresponding entities and convert the same to an xml format and set it to exchange body.

    10. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Introduction

This blog gives details about various operations supported for a SOAP data source. It gives details about creating an odata model, adding the web service operation and doing request and response mappings from design time tool.


 

Steps from Design Time tool

Query Operation

  1. Create an OData Service Implementation Project
  2. Create an OData Model for getProduct operation, which can have properties ProductID, ProductName, ProductDescription, SupplierId, isDeleted

   

3. Right click on odatasvc and choose Select data source  


4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.


   


5. Specify the wsdl file and choose the getProducts operation from the list of soap operations and click on finish


   


6. Right click on Query and select Define Custom Code.


  

7. Select the script type as either java script or groovy script


   

Details about scripting can be found in the Handling Script in Integration Gateway(Methods,Headers & Properties)


8. Right click on Query and select Define Response Mapping


   


9. Do the mapping as below    

10. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.Note: Query operations do not have request mapping. They only have response mapping.If filters in odata request or navigation properties in odata request or if soap request needs some input parameters they have to be handled by the custom scripts. If mapping cannot be done via mapping tool then they have to be handled in custom scripts.Now fire an OData Request https://localhost:8083/gateway/odata/SAP/Demo;v=11/ProductSet? on the browser and response will give the list of products.




Read Operation

  1. Create an OData Service Implementation Project
  2. Create an OData Model for getCustomers operation, which can have properties CustomerID

   


3. Right click on odatasvc and choose Select data source  


4. Select the entity set and choose the read CRUD operation. Select the data source SOAP Service.


    


5. Specify the wsdl file and choose the getProducts operation from the list of soap operations and click on finish


   

6. Right click on the read operation and select Define request mapping


 


7. Define request mapping as below


  

8. Right click on the read operation and select define Response mapping


  


9. Define the response mapping as below


   

10. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.Note: Read operations have both request mapping and response mapping. If filters in odata request or navigation properties in odata request or if soap request needs some input parameters they have to be handled by the custom scripts. If mapping cannot be done via mapping tool then they have to be handled in custom scripts.Now fire an OData Request https://localhost:8083/gateway/odata/SAP/Demo;v=11/CustomerSet(10) on the browser and response will give the customer with customerId 10.




Create Operation

  1. Create an OData Service Implementation Project
  2. Create an OData Model for createCustomer operation, which can have properties CustomerID, CustomerName and CustomerType

   


3. Right click on odatasvc and choose Select data source   


4. Select the entity set and choose the create CRUD operation. Select the data source SOAP Service.


   


5. Specify the wsdl file and choose the createCustomers operation from the list of soap operations and click on finish


  


6. Right click on the create operation and select Define request mapping


  


7. Define request mapping as below


 

8. Right click on the create operation and select define Response mapping


  


9. Define the response mapping as below


 

10. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.Now fire a POST OData Request


https://localhost:8083/gateway/odata/SAP/Demo;v=11/CustomerSet on the browser with a payload and response should be successful and should return the same payload as responseThe payload will be like<feed 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/BWO_SFSF_03;v=1/">
<id>https://localhost:8083/gateway/odata/SAP/CREATEOPERATIONIMPL;v=1/CustomersSet</id>
<title type="text">CustomersSet</title>
<updated>2014-09-05T11:24:24.457+05:30</updated>
<author>
<name />
</author>
<link href="CustomersSet" rel="self" title="CustomersSet" />
<entry>
<id>https://localhost:8083/gateway/odata/SAP/CREATEOPERATIONIMPL;v=1/CustomersSet(100)</id>
<title type="text">CustomersSet</title>
<updated>2014-09-05T11:24:24.457+05:30</updated>
<category term="Demo.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link href="CustomersSet(100)" rel="edit" title="Customers" />
<content type="application/xml">
<m:properties>
<d:CustomersId>100</d:CustomersId>
<d:CustomerName>Bob</d:CustomerName>
<d:CustomerType>Internal</d:CustomerType>
</m:properties>
</content>
</entry>
</feed>


Update Operation

  1. Create an OData Service Implementation Project
  2. Create an OData Model for createCustomer operation, which can have properties CustomerID, CustomerName and CustomerType

   

3. Right click on odatasvc and choose Select data source   


4. Select the entity set and choose the update CRUD operation. Select the data source SOAP Service.


   


5. Specify the wsdl file and choose the createCustomers operation from the list of soap operations and click on finish


   


6. Right click on the update operation and select Define request mapping


   


7. Define request mapping as below


  

8. Right click on the update operation and select define Response mapping


  


9. Define the response mapping as below


   

10. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.Now fire a PUT OData Request


https://localhost:8083/gateway/odata/SAP/Demo;v=11/CustomerSet on the browser with a payload and response should be successful.The payload will look like<feed 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/BWO_SFSF_03;v=1/">
<id>https://localhost:8083/gateway/odata/SAP/UPDATEOPERATIONIMPL;v=1/CustomersSet</id>
<title type="text">CustomersSet</title>
<updated>2014-09-05T11:24:24.457+05:30</updated>
<author>
<name />
</author>
<link href="CustomersSet" rel="self" title="CustomersSet" />
<entry>
<id>https://localhost:8083/gateway/odata/SAP/CREATEOPERATIONIMPL;v=1/CustomersSet(100)</id>
<title type="text">CustomersSet</title>
<updated>2014-09-05T11:24:24.457+05:30</updated>
<category term="Demo.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link href="CustomersSet(100)" rel="edit" title="Customers" />
<content type="application/xml">
<m:properties>
<d:CustomersId>100</d:CustomersId>
<d:CustomerName>BobABC</d:CustomerName>
<d:CustomerType>Internal</d:CustomerType>
</m:properties>
</content>
</entry>
</feed>



Delete Operation

  1. Create an OData Service Implementation Project
  2. Create an OData Model for deleteCustomer operation, which can have properties CustomersID and CustomerType

   

3. Right click on odatasvc and choose Select data source   


4. Select the entity set and choose the delete CRUD operation. Select the data source SOAP Service.


   


5. Specify the wsdl file and choose the deleteCustomers operation from the list of soap operations and click on finish


   


6. Right click on the delete operation and select Define request mapping


   


7. Define request mapping as below


      

8. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.Now fire a Delete OData Request https://localhost:8083/gateway/odata/SAP/Demo;v=11/CustomerSet(100) on the browser and response should be successful with no payload returned.Note: There is no response mapping for a delete operation




Fault Mapping

  1. Create an OData Service Implementation Project
  2. Create an OData Model for AlphasetRead operation, which can have properties Key and Value

     

    3. Right click on odatasvc and choose Select data source

    4. Select the entity set and choose the ReadCRUD operation. Select the data source SOAP Service.

      

    5. Specify the wsdl file and choose the readAlphaset operation from the list of soap operations and click on finish

      

    6. Right click on the Read operation and select Define request mapping

    7. Define request mapping as below

      

       8. Right click on the Read operation and select Define response mapping

       9. Define response mapping as below

          

      10. Now Right click on the Read operation and select Define Fault mapping as This Service Provides Fault if Incorrect Details are passed to the Service, which can be mapped by the user to Display meaning full error Messages.

      11. Define Fault mapping as below, in this case Name is the Only Entity from the Web Service elements i.e. the Entities on the left hand side of the below image, As there are no corresponding Error Code and Message Text Returned from the Web Service, In Such cases A user can map them to Constant Values i.e. in This case Code is Mapped to 500 and Message Text to "No Such Record Exists" as Constant Values and the Name as Usual can be Mapped with the Name on Right Side.

           

      12. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire a Read OData Request with a Fault/Incorect value, user should see the Error Message with Error code,Name and Message Text as Output.

Other Related Links for SOAP with Custom Script

For detials on implementation of delta token scenario follow the link Handling Soap Delta token in Custom Script

For details on implementation of soap navigation scenario follow the link Soap Navigation

For details on implementation of using filters in query follow the link How to Handle Filters in Soap Operations

For details on implementation of SFSF scenario follow the link Operations needing session key to be be passed in odata request

For details on implementation of top, skip scenario follow the link Handling SOAP $top and $skip with Custom Script

For details on implementation of Inline Count follow the link  Handling SOAP Inline Count in Custom Processor

 

For details on implementation of MultiPart WSDL Handling follow the link Handling SOAP Mulitipart WSDL's in Custom Script

Ramya Vasanth

Soap Navigation

Posted by Ramya Vasanth Oct 29, 2014

Navigation is supported in soap data source when a web service has such an operation which takes an input parameter which is the key element of another entity set.

 

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for 2 entities salesOrders and Customers. Define a navigation from customers to salesOrders as below. This model fetches all the salesOrders for a specific customer. So the web service must have an operation like getSalesOrders and this operation must take customerId as an input parameter and return the sales order list. Here Navigation Property Name is SalesOrdersSet

1.png

    3. Right click on odatasvc and choose Select data source

    4. Select the SalesOrders entitySet and choose the query CRUD operation. Select the data source SOAP Service.

   

    5. Specify the wsdl file and choose the getSalesOrders operation from the list of soap operations and click on finish

    6. Right click on Query and select Define Custom Code

     7. Select the script type as either javascript or groovy script

     8. The navigation segment path has to be fetched from the odata request uri and a proper request payload has to be created. If the navigation segment path is like CustomerSet(10)/SalesOrdersSet, then the key predicate customerId = 10 must be fetched from the navigation segment path and this should be an input parameter to the web service operation getSalesOrders.

If the OData request is https://localhost:8083/gateway/odata/SAP/SOAP;v=1/CustomersSet(10)/SalesOrdersSet

The request payload looks like

   <soapenv:Body>
      <tes:getSalesOrders>
         <tes:CustID>10</tes:CustID>
      </tes:getSalesOrders>
   </soapenv:Body>

 

   9. Right click on Query and select Define Response Mapping

   10. Do the response mapping.

   11. Select a data source for the other entity set as well.

   12. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire an OData Request https://localhost:8083/gateway/odata/SAP/SOAP;v=1/CustomersSet(10)/SalesOrdersSet on the browser and response will give the list of sales orders with customerId 10.

In odata - > soap, inline count operation is supported provided there is a web service operation which can return the In Line count of the No of Records from response. The operation must take an input parameter and return the count of the entries from the Web Service response.

 

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for getSalesOrder operation, which can have properties CustomerID, SalesOrderID, Note etc.

         

    3. Right click on odatasvc and choose Select data source

    4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.

      

    5. Specify the wsdl file and choose the salesOrderInlineCount operation from the list of soap operations and click on finish

       

    6. Right click on Query and select Define Custom Code

    7. Select the script type as either javascript or groovy script

       

     8. The Customer ID on which the Inline count has to be fetched. A hash map must be created in processRequestData function with key being CustID or any entity with which Web service Returns the response.

set the Hash Map to message body.

       

If the odata request ishttps://<localhost:8083/Namspace>/<ProjectName>;v=1/SalesOrder_InlineCountSet

Then the soap request body looks like

<soapenv:Body>

<web:saleOrderInlineCount>

<web:CustID>1</web:CustID>

</web:saleOrderInlineCount>

</soapenv:Body>

    9. Once the web service response is returned, the response includes both the Inline Count Value and the Payload i.e. Each row of entity set. This has to be Parsed to get the inline count value and the Normal Response.

Parsing of the Response which is in the form of an XML can be achieved using Custom Processor below is the sample code for the same.

here we are fetching the Inline Count Value by splitting the tag <inlineCount> from xmlBody which we got from Input Parameter message.

if Inline Count Value is not null we are setting a property value to message object with Key as InlineCount and the value with the value of inline Count we got from response.This property will be converted to a count value in the final odata Response.

Now we will Map the Response to EDMX Entities/Properties i.e. we are achieving this with out Design Time Tool.

create a String buffer and append the xml tag, entity set tag, entity tag, properties to it.

Proprieties are fetched by Iterate through the xmlBody to the corresponding Node level and if the node name matches the EDMX property name then append the value to the property tag.

Iterate until the next element is not found.

set the XML created using String Buffer to the message Body.

       

              

    12. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire an OData Requesthttps://<localhost:8083/Namspace>/<ProjectName>;v=1/SalesOrder_InlineCountSet on the browser and response will give the list of SalesOrder Items and the InlineCount of the Items.

In odata - > soap, delta token operation is supported provided there is a web service operation which can return the delta token and tombstone values. The operation must take an input parameter and return all values added or removed after this value

 

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for getProduct operation, which can have properties ProductID, ProductName, ProductDescription,,SupplierId,isDeleted

    3. Right click on odatasvc and choose Select data source

    4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.

    5. Specify the wsdl file and choose the getProducts operation from the list of soap operations and click on finish

    6. Right click on Query and select Define Custom Code

    7. Select the script type as either javascript or groovy script

     8. The delta token value has to be fetched from the odata request uri and this value must be sent to the web service operation as an input parameter. A hashmap must be created in processRequestData function with key being key of entity set and value being delta token value passed in uri. This must be set to message body.

If the odata request is https://localhost:8083/gateway/odata/SAP/DELTATOKEN;v=1/ProductSet?!deltatoken=10,

Then the soap request body looks like

   <soapenv:Body>
      <tes:getProductsBasedOnDeltaToken>
         <tes:ProductId>10</tes:ProductId>
      </tes:getProductsBasedOnDeltaToken>
   </soapenv:Body>

    9. Once the web service response is returned, the response includes both added entries and deleted entries after the delta token value. This has to be split into 2 result sets one having added entries and one having deleted entries in processResponseData function. Also, the next delta token value must also be set.

Here a column isDeleted is used to distinguish added and deleted entries. There can be different implementations possible to recognize added and deleted entries

Deleted entries must be set to message header and added entries must be set to message body. The next del token value must be set to message header.

     10. Right click on Query and select Define Response Mapping

    11. Do the mapping as below

    12. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire an OData Request https://localhost:8083/gateway/odata/SAP/DELTATOKEN;v=1/ProductSet?!deltatoken=10 on the browser and response will give the list of changed products (either delta token values or tombstone values) greater than productId 10 and also give the next delta token value

In odata - > soap $top and $skip operations are supported provided there is a web service operation which can return the In corresponding response. The operation must take an input parameter and return the Web Service response.

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for getSalesOrder operation, which can have properties CustomerID, SalesOrderID, Note etc.

         

    3. Right click on odatasvc and choose Select data source

    4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.

      

    5. Specify the wsdl file and choose the salesOrderInlineCount operation from the list of soap operations and click on finish

       

    6. Right click on Query and select Define Custom Code

    7. Select the script type as either javascript or groovy script

       

     8. Get the $top/$skip value from the uriInfo and form the corresponding input hash map/Soap Body to the web service as shown below. A hash map must be created in processRequestData function with key being top and value being $top value got from the UriInfo and in case of Skip A hash map must be created in processRequestData function with key being skipand value being $skip value got from the UriInfo

set the Hash Map to message body.

       

If the odata request is https://localhost:8083/<Name SPACE>/<Project Name>;v=1/SalesOrderSet?$top=10

Then the soap request body looks like

<soapenv:Body>
   <web:getSalesOrders>
     <web:CustID></web:CustID>
     <web:CustType></web:CustType>
     <web:top>10</web:top>
     <web:skip></web:skip>
   </web:getSalesOrders>
</soapenv:Body>

if the odata request is https://localhost:8083/<Name SPACE>/<Project Name>;v=1/SalesOrderSet?$skip=2

Then the soap request body looks like

<soapenv:Body>
   <web:getSalesOrders>
     <web:CustID></web:CustID>
     <web:CustType></web:CustType>
     <web:top></web:top>
     <web:skip>2</web:skip>
   </web:getSalesOrders>
</soapenv:Body>

   9. Right click on Query and select Define Response Mapping, Enter the Description for the mapping and click on OK.

        

    10. Do the mapping as below

       

11. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire an OData Request https://localhost:8083/<Name SPACE>/<Project Name>;v=1/SalesOrderSet?$top=10 on the browser and response will give the list of top 10 sales Order entries.

Now fire an OData Request https://localhost:8083/<Name SPACE>/<Project Name>;v=1/SalesOrderSet?$skip=2 on the browser and response will give the list of Sales Order entries with 2 records Skipped.

 

 

When session key has to be passed for every soap operation, the following can be done in order to achieve this.

The first step would be to get the session key from a login operation.

The user must provide the credentials as input to login soap operation. This will return session key if login is successful.

The input parameters (username/password) to login operation can be passed via custom script

This session key is passed as odata request header which can be fetched in custom script and set to exchange header or the cxf payload

 

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for login operation, which can have property SessionKey

    3. Right click on odatasvc and choose Select data source

    4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.

          5. Specify the wsdl file and choose the login operation from the list of soap operations and click on finish

       6. Right click on Query and select Define Custom Code

 

Select the script type

           7. Create a hashMap specifying username and password in the custom script’s processRequestData function

Note: Instead of hard coding username/ password they can be passed as filters in odata request. The filter values can be fetched in the custom script and a hashMap can be created.

If the odata request is https://localhost:8083/gateway/odata/SAP/SAMPLE;v=1/LoginSet,

Then the request payload looks like

   <soapenv:Body>
      <aut:LogIn>
         <UserName><username></UserName>
         <Password><password></Password>
      </aut:LogIn>
   </soapenv:Body>

 

       8. Right click on Query and select Define Response Mapping

      9. Do the mapping as below

     10. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire an OData Request https://localhost:8083/gateway/odata/SAP/SAMPLE;v=1/LoginSet on the browser and response will give a session key.

     11. Modify the same project created above. Add another entity type in odata model

    12. Right click on odatasrv and Select Data source

    13. Select the newly added entity set and choose any CRUD operation

     14. Select the wsdl and operation

   15. Right Click on CRUD operation and select Define custom Code like done before

   16. The custom code must fetch the session key obtained in previous login operation from the request header and set this to exchange header.

The odata request header looks like

Note: Some web services need session Key to be part of cxfPayload header.Then the function processRequestXML in the custom script must have the below code

The request payload looks like

<soapenv:Header>
       <tns:sessionKey xmlns:tns="http://namespace">
              0xxffjjlki3456
       </tns:sessionKey>
</soapenv:Header>
<soapenv:Body>
     <objNamespace:getList xmlns:objNamespace=”http://objectNamespace”>
     </objNamespace:getList>
</soapenv:Body>

   17. Right click on CRUD operation and select Define response mapping and do the mapping like before

   18. Right click on Project and click on Generate and Deploy Integration Content

Now fire an OData Request https://localhost:8083/gateway/odata/SAP/SAMPLE;v=1/ListSet specifying the header

   19. Some web services may need to send username, password as part of exchange headers. In this case the username:password must be encoded and this value must be set to exchange header. The header name will be Authorization and value will be Basic <Encoded Text>

The request payload will be

   <soapenv:Body>
      <xyz:findListOfElements>
         <arg0>        
            <MaxResults/>
            <adjustDates></adjustDates>
         </arg0>
      </xyz:findListOfElements>
   </soapenv:Body>

The custom code script for the same is

Note: in the above script, header value is hardcoded. To avoid this username, password can sent as filter expressions in odata URI and value can be encoded in the script.

Or, encode the username/password and send the encoded value as a request header parameter, which can be fetched in the script file and set to message header.

When an oData URL specifies filter, which has to be passed as a request parameter, then this filter expression must be retrieved from the uri and parsed in order to get the filter key and value.

 

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for login operation, which can have properties CustomerId, CustomerName, Address,CategoryId,SupplierId

    3. Right click on odatasvc and choose Select data source

    4. Select the entity set and choose the query CRUD operation. Select the data source SOAP Service.

    5. Specify the wsdl file and choose the getCustomers operation from the list of soap operations and click on finish

 

6. Right click on Query and select Define Custom Code

 

 

7. Select the script type as either javascript or groovy script

 

    8. The filter options specified in odata request has to be fetched in the custom script’s processRequestdata function. Initially the uriInfo object specified in the org.apache.olingo.odata2.api.uri package has to be fetched using the below function

    9. From the uriInfo fetch the filter expression as below

So, if the odata request is https://localhost:8083/gateway/odata/SAP/SOAP;v=1/CustomersSet?$filter=CategoryId eq 10,

Then the request payload will look like

<soapenv:Body>
      <tes:getCustomers>
         <tes:categoryid>10</tes: categoryid>
      </tes:getCustomers>
</soapenv:Body>

 

   10. Right click on Query and select Define Response Mapping

    11. Do the mapping as below

    12. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

Now fire an OData Request https://localhost:8083/gateway/odata/SAP/SOAP;v=1/CustomersSet?$filter=CategoryId eq 10 on the browser and response will give the list of customers with categoryId 10.

The object passed to the script function includes message body, header and properties.

For JDBC and SOAP data sources each script file will have four functions, 2 functions for handling request and 2 functions for handling response.

For JPA data source each script file will have 2 functions, 1 for handling request and 1 for handling response.

In SOAP:

Function processRequestData can be used to modify the request message body which is a hashmap and return a body which is also a hashMap which is an input to Web service pre processsor.

Function processRequestXML can be used to modify the request message body which is an xml and return a body which is also xml

Function processResponseXML can be used to modify the response message which is an XML obtained from Web service response and return a body which is also XML

Function processResponseData can be used to modify the response message which is a hashMap obtained from Web service PostProcessor and return a body which is also a hashMap.

All the above functions can also modify headers and properties (properties which do not contain string ‘SAP’ or ‘sap’) which are hashMaps

message.getHeader(<HeaderName>,<HeaderType>) and message.getBody(<BodyType>) methods can be used in the above functions to get header and body respectively in a specific format.

 

In JDBC:

Function processRequestData can be used to create a hashMap which is currently used for mapping and this is input to oDataToSQLProcessor

Function processRequestSQL can be used to modify the sql statement which is a string and return the modified sql statement in message body

Function processResponseResult can be used to modify the hashMap result set obtained from a JDBC database call and return the hashMap in message body

Function processResponseData can be used to modify the final result set which is a hashMap obtained from ODataToJDBCProcessor and return a body which is a hashMap.

All the above functions can also modify headers and properties (properties which do not contain string ‘SAP’ or ‘sap’) which are hashMaps

message.getHeader(<HeaderName>,<HeaderType>) and message.getBody(<BodyType>) methods can be used in the above functions to get header and body respectively in a specific format.


In JPA:

Function processRequestData can be used to create a hashMap and this is input to JPAProviderProcessor

Function processResponseData can be used to modify the final result which is a hashMap obtained from JPAProviderProcessor and return a message body which is a hashMap

All the above functions can also modify headers and properties (properties which do not contain string ‘SAP’ or ‘sap’) which are hashMaps

message.getHeader(<HeaderName>,<HeaderType>) and message.getBody(<BodyType>) methods can be used in the above functions to get header and body respectively in a specific format.

You can fetch the body using function message.getBody() and you can set the body as message.setBody(body)

 

Properties is any key value pair (again a hashMap). Property can be set using the function message.setProperty("InlineCount", count);

 

Different headers have to be set based on the scenarios to be tested. Headers can be set using function message.setHeader(key,value)

The message class is included in the package com.sap.gateway.ip.core.customdev.util

 

In order to fetch uriInfo use the function

message.getHeaders().get(ODataExchangeHeaderProperty.UriInfo.toString());

UriInfo class is included in the package org.apache.olingo.odata2.api.uri

UriInfo gives details about

Start entity set – uriInfo. getStartEntitySet()

Target entity set -- uriInfo. getTargetEntitySet()

Filter expression --- uriInfo.getFilter().getExpressionString()

Custom query options --- uriInfo. getCustomQueryOptions().get(“!deltatoken”)

Navigationsegments --- uriInfo. getNavigationSegments()

To check if request has count --- uriInfo.isCount()

 

To get the odataMethod use the function

message.getHeaders().get(ODataExchangeHeaderProperty.ODataMethod.toString());

ODataExchangeHeaderProperty enum is included in the package com.sap.gateway.core.ip.component.commons

The different ODataMethods available are GET_FEED, GET_ENTRY, DELETE_ENTRY, CREATE_ENTRY and UPDATE_ENTRY

 

Further, messages can be logged using the statement

log.logErrors(LogMessage.TechnicalError, <message>);

This is available in package com.sap.gateway.ip.core.customdev.logging

 

1.       Delta token and tombstone scenario in JDBC, SOAP and JPA: For this to be tested few headers have to be set in the Custom Processor

The deleted items must be set

message.setHeader(ODataCamelExchangeHeaders.DELETED_ENTITIES.toString(),deletedItems);

ODataCamelExchangeHeaders enum is included in the package com.sap.gateway.ip.core.customdev.api

The response type must be set

message.setHeader(ODataExchangeHeaderProperty.ODataResponseType.toString(),ODataResponseType.DeltaResponseMap);

The flag for delta token must be set

message.setHeader(ODataCamelExchangeHeaders.IS_DELTA_IMPLEMENTED.toString(), true);

The next delta token value must be set

message.setHeader(ODataExchangeHeaderProperty.DeltaToken.toString(), token);

 

The message body must have a hashmap including deleted entities and actual result entities as below

deltaResponse = new HashMap();

                deltaResponse.put(ODataExchangeBodyDeltaResponseMapProperty.Properties.toString(), resultEntities);                         deltaResponse.put(ODataExchangeBodyDeltaResponseMapProperty.DeletedEntities.toString(), deletedItems);

                message.setBody(deltaResponse);

                HashMap is included in the package java.util

                ODataExchangeBodyDeltaResponseMapProperty enum is included in the package com.sap.gateway.core.ip.component.commons

 

2.       Mapping in JDBC: The headers that have to be set for this are

message.setHeader(ODataCamelExchangeHeaders.JDBC_TABLE_MAPPING.toString(), tableMap);

tableMap is a hashMap with key being entity set name and value being the corresponding database table name.

message.setHeader(ODataCamelExchangeHeaders.JDBC_PROP_MAPPING.toString(), entitiesMap);

entitiesMap is a hashMap with key being entityset name and value being another hashMap having properties i.e. key being edmx property name and value being corresponding database column name.

 

3.       Inline count in SOAP:

If inline count has to be checked in soap data source “InlineCount” property has to be set in the custom processor.

message.setProperty("InlineCount", count);

To fetch the inline count value the CP is placed before the post mapping and the body obtained here is an XML and the same XML format has to be returned. (Exception case)

 

4.       Top, skip functions in JDBC:

Here only message body will be changed. The sql statement received in the message body has to be changed and set back. The header that has to be set to indicate $top is handled by custom processor is

message.setHeader(ODataCamelExchangeHeaders.IS_TOP_HANDLED.toString(), true);

 

5.       Mapping using custom processor in SOAP:

Request mapping has to be done on the XML document returned from a pre processor. The edmx properties have to be mapped with soap request properties and an XML structure has be returned.

function processRequestXML(message) {
  importPackage(java.util);
      importPackage(java.lang);
      var payload = message.getBody().toString();
      var body = message.getBody();
      var buffer = new StringBuffer();

      buffer.append("<tes:GetStockQuote xmlns:tes=\"http://www.restfulwebservices.net/ServiceContracts/2008/01\">");

 

      var tokens = payload.split("(?=<)|(?<=>)");

      for(var i=0;i<tokens.length;i++) {
                  if(tokens[i].contains("CustomerID")) {
                              buffer.append("<tes:request>");
                              buffer.append(tokens[i+1]);
                              buffer.append("</tes:request>");
                              i=i+2;
                  }
      }
      buffer.append("</tes:GetStockQuote>");
      message.setBody(buffer.toString());
      return message;
}

Response mapping has to be done on the payload retrieved from cxf call. Message.getBody().toString() returns a string of cxf payload. To retrieve the actual content of the body whose properties have to be mapped to edmx types use the function Message.getBody().toString(). This returns the String of CxfPayload which has to be parsed as shown below.

The code snippet to parse the payload is

function processResponseXML(message) {

  importPackage(org.apache.camel.component.cxf);

  importPackage(java.util);

  importPackage(java.lang);

 

  var payload = message.getBody().toString();

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

  log.logErrors(LogMessage.TechnicalError, "Response Payload is: " + payload);

  var tokens = payload.split("(?=<)|(?<=>)");

  var buffer = new StringBuffer();

  buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");

  buffer.append("<LoginRequestSet><LoginRequest>");

  var breakLoop = false;

  for(var i=0;i<tokens.length;i++)

  {

  if(tokens[i].contains("id") && !breakLoop)

  {

  buffer.append("<id>");

  buffer.append(tokens[i+1]);

  buffer.append("</id>");

  i=i+2;

  breakLoop = true;

  }

  if(tokens[i].contains("module_name"))

  {

  buffer.append("<module_name>");

  buffer.append(tokens[i+1]);

  buffer.append("</module_name>");

  i=i+2;

  }

  }

  buffer.append("</LoginRequest></LoginRequestSet>");

  message.setBody(buffer.toString());

  return message;

}

It's great to have been able to attend SAP TechEd/d-code last week in Las Vegas and I look forward to the upcoming Berlin event as well.  Now that SMP 3.0 SP04 is out, we have full support for large scale disconnected operation in Hybrid and Native SMP clients via OData Offline services.  A number of attendees visited us in the Platform booth to talk about disconnected operation in SMP.  Many of those folks had questions about the ins-and-outs of OData delta token support and why it might be a useful thing to have.


If you are interested in a good discussion about SMP Offline and OData services, I suggest you review Jeff Gebo's lecture on the subject from Las Vegas.

 

As it turns out, the capabilities of the OData services layer you select will have an impact on the overall scalability of your application. That's what I want to address with this article. I have been thinking on how to describe a simple model to help everyone understand the possible modes for connecting to OData web services. I'd like to propose a simple naming model to convey the various operational differences and performance impact of each, corresponding to "Good", "Better", and "Best".


It goes like this (click to enlarge this drawing):

 

ABC.png

 


Model "C" - "Good" -- An OData service is available, but it does not support delta token processing — this includes, I think, most current Integration Gateway use cases and most non-SAP web service providers.  In the "C" model, an update request from a mobile client requires a "GET" of the full entity collection from the back end; a key/hash service on the SMP server uses SMP's Admin database to maintain a record of all rows present for each registered device.  Each refresh request from the application triggers a fresh "GET" of the provisioned entity collection, the SMP server will then compare the keys of each record and hashes of the new collection with the information stored in the Admin database.  This comparison is used to compute an optimized set of changed data that is then sent from the SMP Server to the client application on the device.


Model "B" - "Better" -- A Delta-enabled OData service is available.  A "Delta-enabled" web service provides OData delta token support by executing the same full collection query from the back-end and then manually computing the delta collection set.  An example of this can be found in the JPA support model example in Apache Olingo 2.0.0:  the service executes a complete "select" on the source database and then uses the delta-token information to filter the selected data set down to a change set.  "B" improves on "C" by eliminating the need for the Key/Hash tracking mechanism at the SMP server.


Model "A" - "Best" -- A Delta-optimized OData service is available. It is capable of leveraging capabilities of the back end system to perform optimized queries at each update request from the client.  Mode "A" improves on "B" by reducing the query load on the back-end system of record.  Extra processing at the web services layer and at the SMP server is avoided, too.

 

Note: "Delta-enabled" is a term we use commonly with SAP.  "Delta-optimized" is my own term used to distinguish a step up from plain-old-delta-enabling.  There might be a standard SAP term for that; if so, let me know.


SUMMARY


Model "C" gives you offline data support at the expense of added SMP Server processing required each time a client application requests a refresh.  The actual impact will depend on the frequency of updates, the number of registered applications, and the size of each collection being updated.  Since full collections will need to be supplied with each refresh from the back end, there may well be a noticeable performance impact on your back end system, as well.


Model "B" significantly reduces the processing burden on the SMP Server and eliminates the need to keep a record of keys and hashes at the server, too.   Model "A" is the best of all. Good for all the server layers involved and that it maximizes performance throughout.


It's worth saying that all of these differences in the services are completely invisible to the Client's Harmonized SDK layer used to program Disconnected operation. It's also true that this processing is the same for both SMP and the newly announced HANA Cloud Platform Mobile Services (HCPms).  And those are both Good Things.

11.png


<updated>Oct 29, 2014</updated>


Hello everyone,

 

Back to the blog :-)


Now that we have gone all the way through the 10 blogs, general understanding of the OData API has been deployed on top of our brains.


From now on, we'll discuss some gotchas to watch out. As all those information are "as it is", please expect constant update in the blog, I'll put the timestamp explicitly whenever some important update come in in the future.


Throughout this blog I would like to cover bits and pieces around SODataOfflineStore.


Removing the offine store

 

In the blog #06, we learned how to create an offline store. We haven't discussed yet how to remove it... Here's the code to name the offline store, which is required for removal step later. The storeName property is optional for SODataOfflineStoreOptions instance.  If it is not set when opening the store, it chooses a default name.

01  SODataOfflineStore *offlineStore = [[SODataOfflineStore alloc] init];
02  SODataOfflineStoreOptions *options = [[SODataOfflineStoreOptions alloc] init];
03  options.storeName = @"MyStore";
04  .. 

And the code below is about how to remove the physical store from the file system. If you don't set the storeName during the creation, the default value will be used without the property value. As written in the line #04, the store must to be closed before the removal.


Note: For security, you should be closing your store instances whenever you do not need them (e.g. via applicationDidEnterBackground:, when you app goes to the background). Once it is closed, it can be open by openStoreWithOptions:error: method (e.g. via applicationWillEnterForeground:).

01  NSError *error = nil;
02  SODataOfflineStoreOptions *options = [[SODataOfflineStoreOptions alloc] init];
03  options.storeName = @"MyStore";
04  [offlineStore closeStoreWithError:&error];
05  [SODataOfflineStore RemoveStoreWithOptions:options error:&error];
06  if (error) {
07    // store removal failed
08  } else {
09    // store removal succeeded
10  }

Defining Request

 

In the blog #06 and #07, we learned what Defining Request is and how to define it in the code. You might encounter runtime error during the offline store instance creation - a few things to keep in mind:

 

  • Entity relationships


In order for entity relationships to be available (e.g. "CarrierCollection('CO')/carrierFlights" - the relationship from one entity to another) on the client we need to have one of two things:


Option 1) Use $expands in your defining requests

 

Option 2) Inside OData services, implement a set of referential constraints between the two entity types


Option 1 - The $expand syntax is to name the navigation property that you want to expand, most OData producers do support $expand.


Note: The defining requests have to be non-overlapping with the data they download. For example, if my defining request code is written in this way in order to navigate from Carrier entity to Flight entity (the data here is well known FLIGHT data example in NetWeaver Gateway):

01  options.definingRequests[@"req1"] = @"/FlightCollection";
02  options.definingRequests[@"req2"] = @"/CarrierCollection?$expand=carrierFlights";

The first defining request downloads it directly, while the second downloads it again with the additional flight data. This is causing the server to report duplicate entry errors. It works as expected after removing the first defining request and only has the second.

01  //options.definingRequests[@"req1"] = @"/FlightCollection";
02  options.definingRequests[@"req2"] = @"/CarrierCollection?$expand=carrierFlights";

Option 2 - A referential constraint is extra information attached to an Association (or relationship) that says how the two entities are related. In OData Metadata, you should be able to find the <ReferentialConstraint> tags if it is implemented in OData services. If these are supplied, the relationships between entities is defined by the properties directly and MobiLink can use that to build up the links behind the scenes. The benefit of this approach is in case $expand can't be used, we can go to this option.


So if the OData service implements both $expand and referential constraint for the entity relationship between Customers and Orders, both do the same job from the client perspective.

01  options.definingRequests[@"req"] = @"/Customers?$expand=Orders";
01  options.definingRequests[@"req1"] = @"/Customers";
02  options.definingRequests[@"req2"] = @"/Orders";

[There is a bit of a description of referential constraints in section 10.4 of Common Schema Definition Language (CSDL). Essentially, referential constraints are OData metadata details that can be supplied in order to make OData properties act more like relational database foreign keys.]

 

  • OData collection times out with HTTP 500 error

 

You might encounter the runtime error during the offline store creation, simply because the OData collection you're trying to fetch is too big - you can copy & paste the OData endpoint URL in the web browser and see if it shows 500 timeout error. The issue in this case is a problem on the server-side.  Our HTTP requests for data were timing out because the backend producer was taking too long to query the data and encode it as OData.

 

In this case we should consider using server side paging on OData producer.


There are two types of paging in OData – server side and client side.

 

Client side paging is driven by having a $top value and then incrementing the $skip values for each request (e.g. $top=100&$skip=0 for first request, then $top=100&$skip=100 for second request etc.) This type of paging will not help with populating the database in the offline store because the defining requests are fixed. (Of course, you can use $top or $skip to make requests for small amounts of data to the local store after the store has been populated.)

 

In server side paging, even if you request the entire big OData collection, the server will only return a portion of the results, but provide a “next link” info, which can be followed to get the next portion and so on.  When the MobiLink is populating the local store it knows how to follow these next links. Server side paging via next links is OData Version 2.0 feature. The $skiptoken query is seen in OData URL if it is implemented.


Offline OData Version Support info


Is here!


OData Query Options supported by SAP NetWeaver Gateway


is here!



Okie that's all for the blog this time. I'll cover some other practical topic in the next blog...



See you in the next blog,

Ken


List of blogs

I can’t urge you strongly enough to give blocks a try, if you have not yet adopted them in your standard programming model.


The most important principle of using blocks, is that you can maintain your memory context in-line, for asynchronous operations. This is especially powerful in the case of network requests.  When programming with response delegates, we need to handle for the characteristics of the initial request (i.e. which collection is being queried, which method is invoked) in the delegate handler.  This results in a significant amount of application-specific code being written into what is effectively the generic communication stack.  By utilizing a callback-style asynchronous method like a block, we can eliminate unique request-specific code from the network stack, and handle responses in their original contexts.  This results in code which is much more readable, debuggable, and reusable between applications.


A CREATE example

 

Creating a block wrapper around the delegate callbacks allows you write methods like this:


-(void)createEntity:(id) entity withCompletion:(void(^)(BOOL success))completion;

 

The beauty of an asychronous method with a completion block, is that you can still program against a local context in-line, instead of maintaining state at the parent level with properties and instance variables.

 

Consider the complexity of calling the above -createEntity: method, and handling from a delegate callback, within a full-fledged application.  Most likely, you want to refresh your UI related to the entity’s collection when it completes.  If you have multiple UI’s reading from the collection, you’ll probably want to refresh the common model, instead of just adding an entity into the local view controller’s list of objects.

 

So, you could add an NSNotification to the -createEntity request delegate callback, which always kicks off a refresh request for a collection when an entity is updated.  But maybe you’re also segueing between screens, so a new request is going to be sent anyway.  Or maybe you’re viewing an entity set that is filtered–which request should be sent in this context?

 

Having the completion block on the end of the -createEntity: method allows you to control the behavior in the specific context from which the operation was started.  See this example, where a CREATE method is invoked during a segue, but the developer is able to add a snippet to refresh the model when the request is completed in the completion block.

 

Example with block createEntity


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
{     
     if ([[segue identifier] isEqualToString:@"ReturnInputFromAdd"]) {
          /* 
          Create a new SODataEntity, populated with values obtained from the UI
           */         
          SODataEntityDefault* entity = [[SODataEntityDefault alloc] initWithType:@"RMTSAMPLEFLIGHT.Travelagency"];         
          for (id prop in self.properties) {             
               [entity.properties setObject:prop forKey:prop.name];         
          }
                 
          self.travelAgency = entity;                 
          if (self.travelAgency) {                         
               [[DataController shared] createEntity:self.travelAgency withCompletion:^(BOOL success) {                
                    /*
                    When the -createEntity: request completes, this code is executed
                    */
                    [[DataController shared] fetchTravelAgencyEntitySet];             
               }];         
          }     
     } 
}

 

Implementing the wrapper method

You can use this method to wrap any delegate interface with a completion block interface, not just -scheduleRequest:delegate:

  1. Create your new method, that will be called directly from your application.  It should have a completion block, that runs at the end of the asynchronous operation, and may have a set of input parameters from the result of the operation

  2. Inside that method's implementation, you create a unique identifier, that should ideally be derivable from the method's parameters (so that it could be reconstructed elsewhere in the application).  In the case of -scheduleRequest: the SODataRequestParam can be used for the unique id

  3. Add a listener to [NSNotificationCenter defaultCenter] for this unique id.  Use the block version of the -addObserver: interface, so that you can add context-specific code to be executed on the response

  4. Invoke the original delegate version of the method

  5. In the delegate callback (e.g.:  SODataRequestDelegate) methods, reconstruct the unique id for the notification from step (3) from the response object, and post a notification with the response as the payload

  6. Handle the response in the NSNotification completion block from step (3).  Typically, this means parsing the response

  7. Call the completion block for your wrapper method.  Note that the 3 parameters in the completion() match the parameters in completionHandler() in step (1)

     

    See example:  ScheduleRequest with completionHandler.

/*
The wrapper method, created with a completion block
*/
- (void) scheduleRequest:(id)request completionHandler:(void(^)(NSArray *entities, id<SODataRequestExecution>requestExecution>, NSError *error))completion  
       
     NSString *finishedSubscription = [NSString stringWithFormat:@"%@.%@", kRequestDelegateFinished, request]; 
     [[NSNotificationCenter defaultCenter] addObserverForName:finishedSubscription object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {       
     id<SODataRequestExecution>requestExecution = note.object;     
     id<SODataResponse>response = requestExecution.response;     
     id<SODataResponseSingle>respSingle = (id) response;     
     id<SODataPayload>p = respSingle.payload;     
     SODataEntitySetDefault *entities = (id)p;             
     // return (NSArray *entities, id<SODataRequestExecution>requestExecution, NSError *error)     
     completion(entities.entities, requestExecution, nil); 
     }]; 
     // call the original delegate-style interface
     [self.store scheduleRequest:request delegate:self]; 
} 
/*
The original delegate callback
*/
- (void) requestFinished:(id)requestExecution {     
     // build notification tag for this request     
     NSString *finishedSubscription = [NSString stringWithFormat:@"%@.%@", kRequestDelegateFinished, requestExecution.request];         
     // send notification for the finished request     
     [[NSNotificationCenter defaultCenter] postNotificationName:finishedSubscription object:requestExecution]; 
}

Profit

Ultimately, you can use this wrapper method inside any number of Collection-specific requests, so that instead of tagging requests with identifiers and metadata, and filtering them within the delegate, and/or adding NSNotification listeners all over the application to respond to Collection-specific network responses, the  response to each specific request can be handled directly in the context from which it originated!  I'll leave you with a final example, that uses the wrapper method just created to build a clean -fetch:method:

 

Note how this method is called in the first example above, once the CREATE operation is completed.

 

fetchTravelAgenciesSampleWithCompletion

/*
Returns an array of Travelagency entities
*/
-(void)fetchTravelAgenciesSampleWithCompletion:(void(^)(NSArray *entities))completion {  
       
     NSString *resourcePath = @"TravelagencyCollection"; 
    
     SODataRequestParamSingleDefault *myRequest = [[SODataRequestParamSingleDefault alloc] initWithMode:mode resourcePath:resourcePath]; 
       
     [self scheduleRequest:myRequest completionHandler:^(NSArray *entities, id requestExecution, NSError *error) {         
          if (entities) {             
               completion(entities);         
          } else {             
               NSLog(@"did not get any entities, with error: %@", error);         
          }     
     }]; 
}

Unfortunately, the InstallShield version used by the SAP Mobile SDK installer for Mac has a dependency on Java 7, and doesn't play nice with Java 8.  So, if you find that clicking on setupMacOSX.jar seems to have no effect, you might be on a v8 version.

 

Switching back to v7 is quick; here's a short how-to:

 

  1. Get Java Version 7:  Java Downloads for All Operating Systems Version 7 Update 71.  At the time of writing, the recommended version is Version 7 Update 71. 

  2. Uninstall newer version of Java.  How do I uninstall Java on my Mac?

    In Terminal, paste the following command: 

    sudo rm -fr /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin

  3. Run the Version 7 installer

 

Once you've installed to the SDK, it is fine to switch back to a newer Java version.  There are no runtime dependencies on the InstallShield when programming with the SDK components.

Actions

Filter Blog

By author:
By date:
By tag: