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
0 Kudos

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.

Moreover, this data source supports function imports.

In the first 2 blogs, we’ve already seen how to implement the read and write operations.

In the current blog, we’ll see how to  implement the script required to support function imports in an OData service, based on IGW.

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 each section 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 which introduces the "Custom Code" data source

Overview

1. Background

2. Design the OData model

3. Implement the custom code

  3.1. Implement Function Import with primitive return type

  3.2. Implement Function Import with parameters

  3.3. Implement Function Import with return type collection of complex type

4. Summary

5. Links

6. Appendix

  6.1. Quick Start for experts

1. Background

The OData specification supports function imports in OData services.

With the help of a function import, an operation can be offered by an OData service.

Such an operation can accept input parameters and can return a value.

The type of an input parameters has to be a primitive type (i.e. Edm.Boolean, Edm.String etc).

The type of the return type can be either primitive or complex or entity type or a collection of them.

The function import is addressed like other resources: the function import name follows the service name in the URL

See the OData spec for more information: http://www.odata.org/documentation/odata-version-2-0/uri-conventions/

An example for a function import could be:

ConfirmLeaveRequest which takes the ID of the LeaveRequest as input parameter

2. Design the OData model

For our tutorial, we’ll create 3 very simple examples:

Our OData service will provide no data, only 3 function imports.

Don’t ask for which purpose we do that 😉

It’s just to showcase 3 cases:

1. One function import that returns a primitive value

2. One function import that has input parameters

3. One function import that returns a collection of complex type

Our OData model has 3 function imports and one complex type:

Select the function import “concat” and specify the "HTTP Method Type" as POST:

Within the function import “getComplexList”, leave the type of the “count” parameter as Edm.String and specify the facet "MaxLength" as 1

Select the return type and specify the “Collection” attribute as “true”

Bind data source for Function Import

In order to implement the function import, we have to bind it to a data source. This is done by selecting the new radio button “Function Import” in the “Select Data Source” wizard.

The only supported data source is “Custom Code”:

The result is a script file which contains one method to be implemented.

3. Implement the custom code

3.1. Implement Function Import with primitive return type

The first function import that we’re implementing, called isValid, is intended to show how to stick to the defined return type.

Until now, the format for the response body was fix: either java.util.Map, or a java.util.List with maps

When implementing a function import, it has to be treated differently:

The type to set in the response body has to match the return type defined in the OData model.

In our example, we’ve defined a primitive value as return type: Edm.Boolean. So in our script, we have to set a boolean as response body.

To keep it (very) simple, we just hard-code the response: message.setBody(true)

The full source code:


function processFunctionImport(message){
    message.setBody(true);
    return message;
}







After deployment, we can invoke the function import (no need to defined a destination) in a browser window .

Since we haven’t changed the HTTP method, the default is that the function import can be invoked with a GET request.

The URL:

https://localhost:8083/gateway/odata/SAP/<SERVICENAME>/isValid

The result is the value “true” surrounded with an xml-tag that specifies the function import name

Note:

Other primitive types as return types for a function import are treated similarly.

3.2. Implement Function Import with parameters

The next example, the function import "concat" shows how to access the function import parameters.

In our OData model, we’ve specified 2 parameters, with names “first” and “second”.

The type of both is Edm.String

As a simple example implementation, we access the values that are given by the user. Then we return the values as concatenated string.

Don’t ask why we do that;-)

The Integration Gateway framework provides us with a helper class called ScriptUtil, which offers a helper method that parses the URI and returns all parameters in a java.util.HashMap:

ScriptUtil.getFunctionImportParameters(message)

In this map, the key is the parameter name and the value is the parameter value as given by the user of our OData service.

In the next example, we’ll learn more about function import parameters.

Note that we need to specify an import statement in order to access this helper class:


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

The full source code:


function processFunctionImport(message){
    importPackage(com.sap.gateway.core.ip.script); // required for ScriptUtil
    var paramMap = ScriptUtil.getFunctionImportParameters(message);
    var value1 = paramMap.get("first");
    var value2 = paramMap.get("second");
    message.setBody(value1 + value2);
    return message;
}







Note:

Since we’ve specified the HTTP method as POST, this function import cannot be invoked in the browser, we need to use a REST client to execute a POST request

As usual, the x-csrf-token needs to be fetched first. (See the Appendix in this blog for a description)

The result looks as follows:

3.3. Implement Function Import with complex return type collection

The last example, "getComplexList" shows how to build a complex type and how to return a collection of complex types.

Additionally, we learn how to access the facets.

Step 1: build data for complex type

As you've already guessed, the data for a complex type is stored in a java.util.Map

Since in our OData model, we’ve specified the return type as collections of complex types, we have to return a java.util.List of maps.

So, our first approach for the implementation looks as follows:


function processFunctionImport(message){
    importPackage(java.util);
    var responseMap = new HashMap();
    responseMap.put("prop1", "value1");
    responseMap.put("prop2", "value2");
    var responseList = new ArrayList();
    responseList.add(responseMap);
    message.setBody(responseList);
    return message;
}






After deployment, you can invoke the function import as follows:

https://localhost:8083/gateway/odata/SAP/<SERVICENAME>/getComplexList?&count=%275%27

The result is a list with one element.

Step 2: access the function import parameter

The following shows how to manually access the value that the user puts into the parameter of the function import.

We’re not using the ScriptUtil, because we need the manual code for the third step.

The following code snippet retrieves the value of the function import parameter from the URI, converts it to an integer and uses it as number of iterations in a loop.

As a result, we create as many instances of a map (representing a complex type) as the number in the parameter indicates.


function processFunctionImport(message){
    importPackage(java.util);
    // retrieve the value of the FunctionImportParameter
    var uriInfo = message.getHeaders().get("UriInfo");
    var edmLiteral = uriInfo.getFunctionImportParameters().get("count");
    var paramValue = edmLiteral.getLiteral();
    var givenValue = parseInt(paramValue);
    // build the response according to the given parameter value
    var responseList = new ArrayList();
    var i = 1;
    while(i <= givenValue){
        var responseMap = new HashMap();
        responseMap.put("prop1", "value1no" + i);
        responseMap.put("prop2", "value2no" + i);
 
        responseList.add(responseMap);
 
        i++
    }
    message.setBody(responseList);
    return message;
}





After deployment, you can invoke the function import as follows:

https://localhost:8083/gateway/odata/SAP/<SERVICENAME>/getComplexList?&count=%275%27

The result is a list with five elements.

Step 3: access the facets of a parameter

In our OData model, we’ve specified a facet for the parameter of the function import.

And this is the reason why we’ve specified the type of the parameter to be Edm.String: an integer would have made more sense, but there are no facets applicable for integer.

If we choose Edm.String, the applicable facet is “MaxLength”.

We’ve specified a MaxLength of 1, this means, the String that we pass to the parameter can have only one character. Since we only allow numbers (numbers passed as String, sorry for that confusion), the allowed numbers can reach from 1 to 9.

This doesn’t make much sense, but it is meant only as an example.

The facet is accessed from the UriInfo.

       var uriInfo = message.getHeaders().get("UriInfo");

       var facets = uriInfo.getFunctionImport().getParameter("count").getFacets();

       var allowedLength = facets.getMaxLength().intValue();

What we’re doing here is to access the metadata of our OData service. In the variable “allowedLength”, the number 1 will be stored. It is the number that we’ve specified it in the model. We might as well hard-code it, but this generic way is more stable.

Once we've retrieved the length that is allowed, we have to access the actual value that the user has given:

       var edmLiteral = uriInfo.getFunctionImportParameters().get("count");

       var paramValue = edmLiteral.getLiteral();

The variable edmLiteral is an instance of the class EdmLiteral that is provided by the underlying OData library (java), which is called Olingo

See here for more info.

It is open source, the source code can be found in Github.

The EdmLiteral can be asked for the type and for the literal.

In our sample code, we're interested in the literal, it is returned as String.

Now that we have both, the allowed value and the actual value, we can perform some checks:

* is the given value a number?

* is the given value a negative number?

* is the given number higher than the allowed MaxLength?

In order to perform these checks we need the length of the String and we need to convert the string to int:

       var givenValue = parseInt(paramValue);

       var givenLength = new String (paramValue).length;

The full source code:


function processFunctionImport(message){
    importPackage(java.util);
    importPackage(org.apache.olingo.odata2.api.edm);
    importPackage(org.apache.olingo.odata2.api.uri)
    importPackage(java.lang);
    var uriInfo = message.getHeaders().get("UriInfo");
    // retrieve the value of the FunctionImportParameter
    var edmLiteral = uriInfo.getFunctionImportParameters().get("count");
    var paramValue = edmLiteral.getLiteral();
    var givenValue = parseInt(paramValue);
    var givenLength = new String (paramValue).length;
    // retrieve the allowed length from the metadata
    var facets = uriInfo.getFunctionImport().getParameter("count").getFacets();
    var allowedLength = facets.getMaxLength().intValue();
    // check if the value of the parameter is allowed
    if(isNaN(givenValue)){
        throw Exception("BAD REQUEST: the given parameter value must be a number!");
    }
    if(givenValue < 0){
        throw Exception("BAD REQUEST: the given parameter value must not be negative!");
    }
    if(givenLength > allowedLength){
        throw Exception("BAD REQUEST: the given parameter value has a length that is longer that allowed!");
    }
    // build the response according to the given parameter value
    var responseList = new ArrayList();
    var i = 1;
    while(i <= givenValue){
        var responseMap = new HashMap();
        responseMap.put("prop1", "value1no" + i);
        responseMap.put("prop2", "value2no" + i);
        responseList.add(responseMap);
        i++
    }
    message.setBody(responseList);
    return message;
}








After deployment, you can try the following URL in a browser:

https://localhost:8083/gateway/odata/SAP/<SERVICENAME>/getComplexList?&count=%278%27

The result is already known to us.

But the following 3 invalid URLs should give the expected error message in the SMP log:

https://localhost:8083/gateway/odata/SAP/<SERVICENAME>/getComplexList?&count=%27xx%27

https://localhost:8083/gateway/odata/SAP/<SERVICENAME>/getComplexList?&count=%27-1%27

https://localhost:8083/gateway/odata/SAP/<SERVICENAME>/getComplexList?&count=%2711%27

4. Summary

In this tutorial we’ve learned how to implement function imports in a very simplified way.

We’ve taken the opportunity to also look into different coding principles, using IGW- and Olingo-APIs

5. Links

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

Tutorial for CUSTOM CODE Part 2: Implementing write 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.



6. Appendix


Quick Start for experts


If you're already familiar with IGW scripting:

* the format of the response body has to match the OData model return type

    example for Edm.Boolean:


message.setBody(true);









* if the return type is complex type or entity type, then the format is java.util.map

    if the return type is collection of complex type or collection or entity type, then the format is list of map

* parameter values can be accessed via a map that can be retrieved via ScriptUtil


    var paramMap = ScriptUtil.getFunctionImportParameters(message);   
    var value1 = paramMap.get("<paramName>");










* facets are retrieved from the UriInfo

   example for MaxLength


    var facets = uriInfo.getFunctionImport().getParameter("<paramName>").getFacets();
    var allowedLength = facets.getMaxLength().intValue();









* logging:


    importPackage(com.sap.gateway.ip.core.customdev.logging);
    log.logErrors(LogMessage.TechnicalError, "Error...");









1 Comment