1 2 3 20 Previous Next

SMP Developer Center

289 Posts

In one of my previous blogs, I’ve described how I enhanced my development experience for Groovy scripting.

I needed it for implementing Custom Scripts for Integration Gateway, which in turn is required for creating OData services based on Integration Gateway in SAP Mobile Platform 3.0.

 

Today, I’d like to extend that description with 2 tips that are useful for scripting, but independent of the chosen language.

The background is that when implementing custom script for Integration Gateway, you’re dealing with objects that are provided by Olingo library, e.g. UriInfo

 

The following example shows the generated sample Implementation in Eclipse (SAP Mobile Platform Tools).

Even after following all steps described in the mentioned blog, I’m still not satisfied.

It’s nice to get the code completion, but the names of the parameters are missing:

 

 

 

 

 

This is just a little example about how tedious it is when the Javadoc is missing.

Now have a look below, how wonderful the working environment is looking in the following screenshot:

 

 

 

 

 

 

Boy, THAT’S a pleasure!

And it’s easy to achieve, I’ll tell you:

 

You only need to attach the Javadoc to your SMP project.

 

In this blog you’ll get the details.

 

 

 

 

Adding Javadoc

 

 

The first of 2 tipps is to make the Javadoc available in your project.

You want to have the Javadocs for the APIs that you're using.

If the Javadoc isn't provided within the libs, then you still have the possibility to attach it to the jar files in Eclipse.

In this blog, I focus on Onlingo, as it is useful to have for Integration Gateway scripting.

 

 

 

Prerequisites

 

  • Install SAP Mobile Platform 3.0 server on your local file system.
  • Install Eclipse Kepler.
  • Install SAP Mobile Platform Tools into Eclipse.
  • Follow my Blog post to enhance the environment.

 

 

 

Preparation

 

  • Download the Javadoc archive from the Olingo website at http://olingo.apache.org/doc/odata2/download.html
    Direct link vor V2.
    Save the zip file on your local file system.
  • Create a SAP Mobile Platform OData Implementation Project in Eclipse.
  • Create Custom Code for Groovy.
  • Apply the tipps that I’ve described in my blog post

 

 

 

Procedure

 

After converting your project to a plug-in project by following above steps, you can see the Plug-in Dependencies node after expanding your project:

 

 

 

 

Right click on the olingo-odata2-api.jar file.

Choose “Properties”, then select “Javadoc Location".

Select the checkbox “Javadoc in archive”, then enter the path to the downloaded Javadoc zip file

Leave the “Path within archive” empty.

You can press “Validate” in order to check if the path is correct.

 

 

 

 

In order to verify the result, you can proceed as follows:

 

Open the Javadoc View in Eclipse via Window -> show View -> other -> Java -> Javadoc

Within the Project Explorer View, expand the tree of your project.

Expand the olingo-odata2-api.jar

Select a .class file, e.g. UriInfo.class

Select a method.

Result: the Javadoc for this method is displayed in the Javadoc view.

 

 

 

 

 

Adding Source Attachment

 

 

 

A second enhancement of development experience that some of you may desire, is the ability of pressing F3 (or use hyperlink) to navigate to the source code of a used class.

Since Olingo is open source, we can download the sources and attach them to our project in Eclipse.

 

 

Preparation

 

The Olingo sources can be downloaded from here

 

Note:

From the homepage http://olingo.apache.org/ you can navigate to other downloads, like V4 in future.

 

 

 

Procedure

 

 

Expand your project to the Plug-In Dependencies node.

 

 

 

Right click on the olingo-odata2-api.jar file

Choose “Properties”, then select “Java Source Attachment” in the left panel.

Select the checkbox “External Location”, then enter the path to the downloaded source-zip file

 

 

 

 

 

 

In order to verify the result, you can proceed as follows:

 

Expand the tree of your project,

Expand the olingo-odata2-api.jar

Double-click a .class file, e.g. HttpHeaders.class

Result: the source file is opened and you can enjoy, e.g. forward navigating with F3

 

 

 

 

 

 

 

Note:

You may wish to repeat the steps for the olingo.odata2-core.jar file.

Intro

 

The new Eclipse based Integrated Test Environment, which takes over the functionalities of the old ATE (Agentry Test Environment), has been awaited for quite some time, and has been released for a while now. It is delivered in SMP 3.0 SDK SP06.

 

I have had a chance to try it out, and even though there is still room for improvements, I must say that it is a MAJOR leap in the right direction.

 

It is more or less a one-to-one migration of the old ATE to an Eclipse based Test Environment, meaning no new functionalities for inspection has been added. However there is one major difference: You can now interact with the UI for other platforms than the Windows based ones. The UI is using the WPF windows client, and when choosing a non-windows based OS, it will still render in the WPF UI.

 

Still it is not a perfect rendering of iOS and Android, as on native devices. Images and screen elements may appear differently in the Test Environment and the native device, both with regards to enablement, size, format and functionality. But it is very nice to be able to inspect data objects, rules and actions, while executing screen logic. As I said a major step in the right direction.

 

The usage of the Test Environment in Eclipse is a little clumsy in my opinion, and not well suited for execution in the same environment as you make Agentry and Java development. Therefore I would suggest to install it in a dedicated Eclipse environment.

 

Furthermore I hope that SAP in the future will extend its capabilities to support multiple sessions (Agentry applications and/or backends) in the same Eclipse environment.

 

Features of the Integrated Test Environment

 

The installation and setup of the Integrated Test Environment has been nicely described in the blog, so I will not elaborate on this.

 

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/02/eclipse-integrated-test-environment-for-sap-agentry-applications

 

Startup of the Test Environment

 

The Test Environment will create some files for configuration and tracing, and these files requires an Agentry Project to exist and be open before you can start the Test Environment. There is no requirement that the project must be identical to Agentry Application you want to test, so it can be any Agentry project.

 

Pic01.jpg

Warning that no open Project exists.

 

Pic02.jpg

Menu item to start the Test Environment is inactive.

 

 

Create dummy Agentry project

To avoid any confusion I have created a dummy Agentry Project in my (dedicated) Eclipse Environment.

Simply choose File->New->Other…->Agentry Project->New Agentry Project, and create an Agentry application with no implementation.

 

Pic03.jpg

Pic04.jpg

After creation of the dummy application it is now possible to start the Test Client (Test Environment).

 

 

When starting the Test Environment you will need to provide credentials (username and password) as well as a URL to hit the Agentry instance on SMP.

 

Pic05.jpg

After startup you will see this screen.

Click "Menu" to go to the application.

From here you can also export the local DB of the application, as well as reset the client.

 

Pic06.jpg

In my case I am accessing a modified version of SAP Work Manager 6.1 which is displayed.

For the first time in Agentry history, it is possible to interact with the UI of an Android screen set

 

 

The Menus

 

Before you can access the menus you need to minimize the Application screen. If you are Alt-Tab'ing between screens, you must minimize every time you return to Eclipse.

 

Pic07.jpg

You cannot minimize or hide this window.

This is the reasons why I call the implementation "clumsy". It is not working smoothly, but still it is minor bugs.

 

After moving the screen out of the way you will gain access to the menus.

 

Pic08.jpgPic09.jpg

Pic10.jpgPic11.jpg

If you are familiar with the ATE, you will recognize all of the menu items, as they also existed here.

 

However a few notes on functionality:

  • I have not been able to get the Transaction display to function. Even when I am sure I have executed some Add Transactions, I don't see the data in the Transactions view.
  • View for Complex Table no longer shows number of records in table, which is a little bit annoying.
  • The Clear Log… functionality does not work. You must delete the debug.log file in the package explorer to clear it.

 

Pic12.jpg

Windows displayed by the Test Environment in Eclipse:

  • Top Left: The dummy Agentry Application.
  • Middle Left: Annoying window that cannot be minimized while inspecting a running application.
  • Top Middle: Debug log of an Action set for logging.
  • Bottom Left: Objects inspection.
  • Bottom Middle: Transaction inspection (which is always empty)
  • Bottom Right: Complex Table inspection.

 

All-in-all a very useful Agentry Integrated Test Environment.

There are some bugs to be fixed, but for a first version SAP has done a very good job.

 

Regards,

Søren Hansen

Dear All,

 

I am writing this blog specifically for SMP 3.0 as from SMP 3.0 onwards the entire architecture is based on OData Framework. Be it any backend datasource, there is a concept of Entity Data Model (EDM) to expose your backend data in OData.

For SAP Backend systems, there are two ways to consume data from RFC(Remote Function Modules).

1. Expose the RFC as a SOAP Service using SOAP Manager and create Data Models on Eclipse IDE with SAP OData Plugins

2. Create a Gateway Service Project on Netweaver Gateway and import the service into your Eclipse IDE with SAP OData Plugins.

 

For method 2, the pre-requisite is to have an SAP Netweaver Gateway installed either in an Central Hub Deployment or Embedded Deployment

Central Hub Deployment of SAP Netweaver Gateway has SAP NW Gateway Server installed in a different instance then the SAP ERP instance whereas Embedded Deployment has all the components of SAP Netweaver Gateway installed in the same instance.

 

For this blog post we are going to follow method 2 and are using an Embedded Deployment model of SAP Netweaver Gateway.

 

Log in to you SAP Server Instance and go the the transaction SEGW

 

(If you are missing any roles, capture the SU53 logs and get in touch with your basis person to provide you with the required authorization. The basic authorization required would be of the Gateway Developer Role)

 

Once you log in to SEGW you would see the below screen

 

1.PNG

 

2.PNG

 

Click on the Create Button to create a new Project.

It will prompt you to enter the Project name, Description and the Package name if you wish to Transport the project to QAS. Else you can just create it

inside the Local Temp ($TMP) Directory by clicking Local Object button

 

3.PNG  4.PNG

 

Now we will create a Data Model Directly from the RFC,

 

I have an RFC made by name Z_SMP_PM_NOTIFICATION

 

which has two input parameters and returns a list as an output

 

35.PNG

 

36.PNG

 

So inside our project Right Click on Data Model and click on Import --> RFC/BOR Interface

 

5.png

Give the name of the Entity and the Destination RFC.

 

Here we will give the name of the Entity as NotificationList and the destination RFC as Z_SMP_PM_NOTIFICATION.

 

37.PNG

 

Click on Next to define the Fields that we require in our entity. For my RFC to work i need the REQ_INTERFACE_NAME as the input as the LT_NOTIF_LIST as output parameter so i would select only those fields.

 

6.PNG

 

Click on Next to define a key. You can also change the name of the fields under the name column. Defining a key for an entity is mandatory

 

38.PNG

 

Click on Finish to create the entity.

 

Next we will create an EntitySet which will be of the datatype the Entity that we created.

 

Double Click on the Entity Sets node and click on the Create Icon

11.PNG to create a new entity. Give the name of the Entity Set and the Entity Type name as the name of the Entity that we just created

 

12.PNG

 

Once this is done you would see an entry under the Service Implementation node with the same name as the Entity Set.

Expand it to see you will see 5 operations along with every entity set

Create - This operation will be used if you want to create any data within SAP from the webservice. for e.g Creating a new Employee in the master table

Delete  - This operation is used to delete existing entry within SAP from the webservice. for eg Deleting a particular Employee within the master table

GetEntity(Read) - This operation is used if you want to retrieve a single record against a key from SAP for e.g Retrieving details of a particular employee

GetEntitySet(Query) - This operation is used to retrieve a list of records from SAP against a query or without a queryfor e.g Retrieving list of employees or retrieving a list of employees with Country Code IN etc

Update - This operation is used to update the existing data of a record within SAP For e.g Updating contact details of a particular employee.

 

Since for this example our RFC returns a list of Notifications so we will proceed for defining a GetEntitySet(Query) Operation on our EntitySet.

Right Click on the GetEntitySet(Query) node and select Map to Datasource.

 

You can either map the fields manually or click on Propose Mapping button on the top to map it automatically

 

39.PNG

 

If your input parameter has any constant value then you can directly define it over here with ' ' quotes.

 

Once this is done we are done with the development of the Entity set and Mapping. Next this is we will Generate the Runtime Objects which all the Gateway Services require for operating.

Gateway Services based on Model Provider and Data Provider Classes.


Click on the Generate Runtime Objects button 19.PNG


If there are not errors you will see a below message that all these classes are created with a green box on their side.


23.PNG


Once all the classes are made, next thing we will do is to register this service against the SAP Netweaver Gateway Server so that we can access it.


The pre requisite for this would be that you create Destination for the Systems. If the destinations are not maintained you can make those using the TCODE SM59 and then create an alias using SPRO->SAP Netweaver-->Gateway-->OData Channel-->Configuration-->ConnectionSettings-->SAP Netweaver Gateway to SAP System--> Manage SAP System Alias and create a new Entry there against the RFC Destination.


Double Click on the Service Maintenance Node and you should see all the Destinations that you have created.

Select the Destination and click on Register Service.


21.PNG


Click on Register Button and it will ask you for a prompt


22.PNG


It will open a new page, Do not change any values, Within the package Assignment give the Package name if you wish to transport or

you can store it as a Local Object.


Click on the green tick to register the service.

Once the service is registered and deployed you will see a green box against the destination.


25.PNG

 

We have now deployed the service on the SAP Netweaver Gateway Server.

 

Next we will proceed to testing the Service that we just deployed. Go to the transaction /IWFND/MAINT_SERVICE

 

Here you will see a list of services

 

26.PNG

 

Select our service and click on the Gateway Client button

 

 

27.PNG

 

It will open up a new window

 

28.PNG

 

click on the 30.PNG and you will see the list of Entity Sets that we created under this Service

 

31.PNG

 

If you have any input parameters then you can give it after the Entity Set name. The RFC that we created had one input parameter which was a constant so i defined the constant value during mapping itself. If you have any input values for query operation you can supply it as follows

 

/sap/opu/odata/sap/ZTESTNOTIFICATION_SRV/NotificationCollection?$filter=parametername eq 'value'

 

Once you add this click on the 32.PNG button to test the service.

It should return with a HTTP Request code 200 and with the data

 

34.PNG

 

This way you can develop multiple entity sets and test it.

 

Hope this helps you all.

 

Thanks,

Rakshit Doshi

Dear All,

 

After all the struggle and trial and error i finally figured out the way on how to configure SSO for SMP 3.0. SSO is quite required as well know for role based authorization for SAP. Earlier during MBO it was quite simple to do this but this isnt the case with SMP 3.0.

I am writting this blog post so that who ever tries this in the future doesnt undergo so much pain.

 

For this blog post i m using the latest version of SMP 3.0 SP6.

So lets get started.

 

Log in to you SAP GUI and run the transaction STRUST.

 

Select the SNC note and download the SAP ECC Systems Certificate

 

Screen Shot 2015-02-27 at 11.17.26 AM.png

 

Export the file by clicking on "Export Certificate", click on "Base 64" to save the certificate file on a directory and copy the same on the SMP Server Console

 

Screen Shot 2015-02-27 at 11.21.05 AM.png

 

Once you have the certificate file exported, log in to the SMP Server Console and copy this file on that machine. Download Portecle from this site. We will use Portecle to import the above exported certificate into our SMP Key Store. Once downloaded , launch Portecle(its just a jar file so no need for installation) and open the SMP Keystore file.

 

File-->Open KeyStore File

 

 

 

Screen Shot 2015-02-27 at 11.26.28 AM.png

 

Go the SMP KeyStore path. Its located in ~Installation Directory/SAP/MobilePlatform3/Server/configuration with the name smp_keystore.jks. It will prompt you for a password. Give the password that you used during installation of SMP Server or use the default password changeit.

 

Once you log in you would see something like this.

 

Screen Shot 2015-02-27 at 11.33.54 AM.png

Go to Tools-->Import Trusted Certificate to import the SAP ECC Certificate we exported using STRUST.

 

Screen Shot 2015-02-27 at 11.34.59 AM.png

 

Screen Shot 2015-02-27 at 11.38.24 AM.png

 

Click on Import. It will show you the details of the certificate and then click on Next. Give an Alias for the Certificate and click on Finish.

So we have added the SAP ECC Certificate to SMP Key Store.

 

Next Step would be to create a Destination on Gateway Cockpit and also to create an Application on Management Cockpit

 

I would assume for this that you have already deployed an application on SMP. If you are not sure how to develop an SMP OData based application with SAP Netweaver Gateway i would request you to follow this blog written by my friend Jitendra Kansal

 

Please follow the steps from the blog post to create an Eclipse based Project, Deploying the project on SMP Server, Creating a Destination in Gateway Cockpit, Importing the IWBEP Service under the destination to our SMP Server and Assigning the Destination to the Deployed Service.

 

(Plz note for the above blog he is using the SAP Netweaver Demo Service from the cloud. If you do not have an account and have an on premise Netweaver Gateway and Do not know how to proceed to creating your own Service I will write a blog post for that soon . You can now check it under this blog post on how to create a Netweaver Gateway Project for on Premise)

 

So now we have a Gateway Destination already built.

Go to the same tab of Destination under Gateway Cockpit and open the destination in change mode

 

Make the changes as below.

Under SSO Mechanism, Add SSO, Edit the Technical User(Basic) and change the password to some wrong password and Save it

 

Screen Shot 2015-02-26 at 3.40.28 PM.png

 

Note :

1. My Destination URL here is different from the one in the blog post as I am using an onpremise url of the netweaver Gateway

2. Changing the password for the Technical User(Basic) is very important else the supplied credentials will not over ride the user credentials and everytime you call the service even with SSO it will always call the backend service with the user you configured in Technical User (Basic).

 

Screen Shot 2015-02-26 at 4.20.41 PM.png

 

So now we are done with the Gateway Configuration for the Destination to support SSO.

Next Thing that we would be doing is to configure an Application on Management Cockpit and then give it an Authentication Mechanism.

Login to the Management Cockpit using http://localhost:8083/Admin

 

Click on the Settings Tab and click Security Profiles

 

Click on New to create a new Security Profile.

We will name it as SAP_SSO2

 

Give the Authentication Provider as HTTP/HTTPS Authentication and provide the same URL that we used while defining the Gateway Destination

 

Screen Shot 2015-02-27 at 11.01.16 AM.png

 

For the Cookie Name give it as MYSAPSSO2

 

Screen Shot 2015-02-27 at 12.13.02 PM.png

 

Click Save to save this security Config. We will assign the same security config to our application connection.

Create a new application connection by clicking on new under the Application Tab

 

Under Backend Connection give the URL of the Service Document of your Deployed Application on Gateway Cockpit.

It would be something like https://localhost:8083/gateway/odata/SAP/<NameofyourdeployedApplication>;v=1

Check the Internal checkbox.

 

Go to the Authentication Tab and Select the authentication mechanism we just created SAP_SSO2

 

Screen Shot 2015-02-27 at 12.19.38 PM.png

 

Click on Save to save all the configuration.

Now we are all done with our configuration.

Time for Testing

 

You can use the Advanced Rest Client Extension of Google Chrome for this. You can use any other rest client for this purpose.

 

Go the the rest client and give the url for registration of user

 

URL : http://<hostaddress>:8080/odata/applications/latest/<applicationid we created>/Connections

 

Give the Headers

Content Type : application/xml

Operation      : POST

 

And post this under the Request Body

 

<?xml version='1.0' encoding='utf-8'?>


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

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

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

     <title type="text"/>

     <updated>2012-06-15T02:23:29Z</updated>

     <author>

     <name/>

     </author>

      <category
          term="applications.Connection"
          scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>

     <content type="application/xml">

     <m:properties>

     <d:DeviceType>IOS</d:DeviceType>

     <d:DeviceModel m:null="true" />

     </m:properties>

     </content>

     </entry>

 

It should look something like this

 

Screen Shot 2015-02-27 at 12.25.27 PM.png

 

Click on Send to post it. It will prompt you for credentials. Give the credentials to authenticate you against the URL that we configured for the security configuation. It will return with a status 201 which will create your user in Admin cockpit. You can check it against the application id that you should now have one registration.

 

Things to note are the cookies that are returned back. We have a cookie by the name X-SMP-APPCID.

 

Next we will test if we can now retrieve the data against that user to check if SSO is configured and SAP Returns on data for this user

 

With the same Rest Client give the below addres

 

URL : http://localhost:8080/<applicationid>/<AnyEntitySet>

In the header specify the cookie

X-SMP-APPCID and the value you got from the previous registration call

 

Operation : GET

 

Note : user GET if you entityset is used for reading operation. If there is a post operation involved you would also need an additional token of x-csrf-token.

 

Click on Send and you should receive Data for that particular user itself.

 

Screen Shot 2015-02-27 at 12.34.16 PM.png

 

Hope this helps all of the people who were facing difficulty in configuring SSO.

 

Reference from Tejesvi DVR, Suma S

 

Thanks,

Rakshit Doshi

 

 

 




Use Case:

To access SAP Gateway exposed services via SMP 3.0 using SSO Cookie based authentication

 

Pre-requisites:

SAP Gateway system is capable of handling cookies eg: MYSAPSSO2

 

 

Steps to Create MYSAPSSO2 Scenario

 

 

I) Enable SMP to access the backend https url:



  1. Open File->Open Keystor fFile in portecle
    Tool and go the Location where the smp_keystore.jks file is Located. i.e. SMP
    server->Configurations  and click ok to open the
    Keystore.jks, Password for the keystore is "changeit".

 

p1.png

 

 

   2)  Go to Tools-> Import Trusted Certificate
and select the Back End System's Certificate from your system and click on
import

 

p2.png

   3)  Click on the ok and yes buttons as shown below.

 

p3.png

 

 

 

p4.png

 

 

4) Enter the Alias Name for the
Certificate which is getting imported

 

5) Save the Keystore in the
portecle Tools once the certificate is imported. Otherwise the imported
certificate will not be Reflected in SMP server's Configurations.

p5.png

 

 

II) SMP Gateway Cockpit Steps:

 

  1. Log on to the SMP Admin Cockpit https://<host>:8083/gateway/cockpit and go to the Destinations-> New
    Destination

           Provide the Destination Name, select destination type as HTTP, provide Destination URL, and Select Authentication
      Type as SSO Mechanism

 

p6.png

 

Click on Add button to select from various SSO Mechanisms.

 

Select Technical User(Basic) authentication as authentication mechanism,
and click on Save button.

 

 

p7.png

 

 

Different SSO Mechanisms at the Gateway cockpit is mainly used to test the destination connection and to initially test
the services in the cockpit.

 

 

The security profile created at the SMP Admin will overwrite the destination configurations created at the gateway
cockpit.

 

 

In a productive scenario we need to consume the services exposed in gateway cockpit through SMP.

 

 

For different SSO Mechanisms refer to the link

 

 

http://help.sap.com/saphelp_smp306svr/helpdata/en/7c/2dd0d470061014a8bfb9194fa26274/content.htm

 

 

Once the destination is created, in the https://<host>:8083/gateway/cockpit Click on register button,
provide the destination created and search for the service required to be
registered.

 

p8.png

 

p9.png

   With this we have registered the service in gateway cockpit. If we need to access this service by cookie based SSO
mechanism through SMP follow the steps mentioned in the next section.

 

 

III) SMP ADMIN Cockpit Steps


1. Log on to the SMP Admin Cockpit https://<host>:8083/Admin and go to the Settings-> Security
Profile tab.

 

2.  Create a security profile with HTTP/HTTPS Authentication and provide the URL of the Back end System from where odata
service is hosted and MYSAPSSO2 Cookie is enabled. Click on Save as Shown Below.

 

 

p10.png

 

p11.png

3. In the SMP Admin Cockpit https://<host>:8083/Admin create the application for the service

document url exposed in gateway cockpit as shown in screen shots below:

 

p12.png

 

p13.png

 

Provide the odata service exposed through gateway cockpit, and mark the service as internal.

 

 

app3.PNG

 

app4.PNG


Once this is done, application is ready to be consumed.


IV) Onboarding through REST CLIENT

 

Onboard to the created application in the REST client/Mobile Application though a
device


     Onboarding URL:

    

          http://<host>:8080/odata/applications/latest/<application id created in SMP>/Connections     


     Onboarding Headers:


          Content-Type: application/xml

          x-smp-appcid: <application1>

 

     Onboarding Post Body:


     <?xml version='1.0' encoding='utf-8'?>


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

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

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

     <title type="text"/>

     <updated>2012-06-15T02:23:29Z</updated>

     <author>

     <name/>

     </author>

      <category
          term="applications.Connection"
          scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>

     <content type="application/xml">

     <m:properties>

     <d:DeviceType>IOS</d:DeviceType>

     <d:DeviceModel m:null="true" />

     </m:properties>

     </content>

     </entry>

    

     Onboarding Operation: POST


          While onboarding it will initially ask for the user details, and for the subsequent requests it will use the Cookie we have configured.

 

After onboarding the entities of the application can be accessed via the url

 

http://<host>:8080/ <application id created in SMP>/<entites>

Headers:

 

  Content-Type: application/xml

  x-smp-appcid: <application1>

 


 

 

 



 
 
 
 
 
 
 
 
 
 
 
 





Some of the features that can be enabled using custom script in Integration Gateway (IGW) are: modify the requests and responses of an Odata request, handle delta token , multipart and composite WSDL support, and SOAP authentication. There are many examples in SCN on how to write custom script in Integration Gateway. In this blog I am explaining how to easily debug the script using IGW's eclipse based tooling.

 

From SMP SP06, debugging of custom script written in JavaScript is possible (Groovy supports in future). I have created a simple project using the WSDL http://www.webservicex.net/medicareSupplier.asmx?WSDL. High level details of the project is given below. Here I am not explaining how to create a project but I am explaining how to debug the script used in the project.

 

Odata Entity:

ScreenHunter_36 Feb. 24 23.40.jpg

 

 

Custom Script written under function processRequestData

  //Import statements

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

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

   importPackage(org.apache.olingo.odata2.api.uri);

   importPackage(java.util);

   importPackage(com.sap.gateway.core.ip.component.commons);

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

   importPackage(org.apache.olingo.odata2.api.processor);

   //Getting value from header

   var context = message.getHeaders().get("odatacontext");

   var City = context.getRequestHeaders().get("City").get(0);

   var child = new LinkedHashMap();

   child.put("key:City", City);

   message.setBody(child);

   return message;

 

How to start debugging ?

  • Open prop.ini file of SMP server (C:\SAP\MobilePlatform3\Server)  and add the property -Drhino.debugger.port=9123

      ScreenHunter_36 Feb. 24 18.40.jpg

  • From eclipse, click on down arrow next to debug menu and choose debug configurations.

     Untitled2.png

If debug menu is not visible: Go to Windows > Customize Perspective. "Navigate to Commands group visibility" tab. Then select "Launch" option in the Availability Command Groups and click OK.

  • On the new window opened, provide below details:

     Untitled.png

  • Navigate to Source tab and provide the path to the script file and click on Debug.

    Untitled1.png

  • Open JavaScript file and set a break point as given below.

     Untitled3.png

 

  • Execute the Odata service from a rest client by passing City as header along with Authorization header.

ScreenHunter_36 Feb. 25 00.00.jpg

    

  • It triggers the break point set in the Javascript. To see the data send from rest client (ie. City) add Expression String(child) as given below. If Expressions tab not visible: Go to Windows > Show view > Expressions.

ScreenHunter_36 Feb. 24 23.59.jpg

 

Regards, Midhun

SAP Technology RIG

This  blog is (even more) about Integration Gateway in SAP Mobile Platform 3.0 (SMP).

It is meant for those of you who have already created OData services based REST data source through Integration Gateway in SMP.

If you’ve implemented the script as described in my previous tutorial and if you’ve wondered if there isn’t a different way to convert the payload than just doing string operations… (only) then you should have a look at the following blog.

 

I’ve demonstrated it for XML – this blog will now do the same for JSON payload.

 

Note that this is not an official guide, just my personal findings and ways to do my stuff.

It might not meet all your requirements – so please do let me know your concerns or suggestions for improvements.

BTW, this tutorial is based on SMP SP05.

 

 

Prerequisites

 

  • My previous tutorial where we’ve created an OData service based on a REST data source
  • SAP Mobile Platform 3.0 SP 05
  • Eclipse Kepler with SAP Mobile Platform Tools installed

 

 

 

 

Preparation

 

 

SAP Mobile Platform

  • As described in one of my previous tutorials  you need to create a destination in your SMP server, that points to the host of the REST service that is used in this tutorial:
    http://sansad.co  with "no Authentication"
    Try the “Test Connection” button: the result should be green success popup.

    Note: you might need to enter proxy settings at https://localhost:8083/Admin/  -> Settings -> System

 

Eclipse

  • Create OData Implementation Project
  • Create OData model: We have to create an OData model that matches the REST service.

    Our model looks as follows:


    You can create the properties manually, or import the edmx file that is attached to this blog.
    Bind the QUERY operation to our example REST service: /api/legislators
  • Create Custom Code script for Groovy
    If desired, do the few preparation steps to enhance your development experience for Groovy, as explained here
  • Add dependency to gson in the MANIFEST.MF file

 

 

That’s it for the preparation.

Let’s start implementing the script.

 

 

 

Implementing the data structure conversion using JSON parser

 

 

 

Overview

 

We will be implementing only the method processResponseData()

 

The steps are:

 

  1. Get the payload string
  2. Parse the JSON payload
  3. Refactor
  4. Transform the JSON-object to String
  5. Set the converted payload string

 

 

 

1. Step: Get the payload

 

This is the same step a usual:

 

 

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

 

 

 

2. Step: Parse the JSON payload

 

For parsing JSON, there are several libraries that could be used.

In this tutorial, we’re using com com.google.gson, the reason is that it is included in SMP, so we don’t have to install any own library/bundle to our SMP .

 

There are also several approaches for parsing JSON, one possibility is shown in the code below.

 

 

def JsonElement parsePayload(String payload){

 

     JsonParser parser = new JsonParser();

 

     return parser.parse(payload);

}

 

 

 

3. Step: Refactor the JSON Elements

 

 

Quick recap: what do we have to do?

This is the response body that we get from the REST service (http://sansad.co/api/legislators):

 

 

 

And this is the structure that is expected/required by Integration Gateway:

 

 

{

"results":

     [

          {

               "PropertyName1":"PropertyValue1",

               "PropertyName2":"PropertyValue2"

          }

     ]

}

 

 

 

So, we have to re-structure the above REST-json-structure, in order to get the structure that is expected by Integration Gateway.

 

In detail:

  1. Delete the undesired (informative) nodes count and page
  2. Remove all the properties that we don’t have in our OData model

 

We use API that is provided by com.google.gson:

 

 

def JsonObject refactorJsonElement(JsonElement jsonElement){

 

     JsonObject feedJsonObject = jsonElement.getAsJsonObject(); // e.g. {"results":[{"age":60,"...

 

     //first refactoring

     doRefactoringForFeed(feedJsonObject);

 

     // second refactoring: need to remove some properties, because the aren't defined in our OData model

     JsonArray resultsArray = feedJsonObject.get("results").getAsJsonArray(); // it is a feed (array)

 

     // we're performing a QUERY operation, so loop over all entries and refactor each

     for(int i = 0; i < resultsArray.size(); i++){

          JsonElement entryElement = resultsArray.get(i);

          if(entryElement instanceof JsonObject){

               //remove undesired properties, corresponding to edmx

               doRefactoringForEntry((JsonObject)entryElement);

          }

     }

 

     return feedJsonObject;

}

 

 

  1. In a first step, we get the top-level node, that is the feed, and remove the 2 undesired child nodes.
    These 2 nodes are for information purpose and don't match to data structure



    And this is how it is removed:


    def JsonObject doRefactoringForFeed(JsonObject feedJsonObject){
           feedJsonObject.remove("count");
           feedJsonObject.remove("page");

                return feedJsonObject;
    }


  2. In a second step, we ask the feed node for the list of entries, loop over them and for each entry, we remove all the undesired properties.
    The “undesired properties” are those that are in the REST-service, but not in our OData model.
    In the following screenshot, only the desired properties are marked red:




    To make the sample code more comprehensive, I’ve hard-coded the  names of the undesired properties.
    (There’s also a way to do it generically, I’ll publish it in a separate blog.)


    def JsonObject doRefactoringForEntry(JsonObject entryObject){
        //remove undesired properties, corresponding to edmx
        entryObject.remove("age");
        entryObject.remove("attendance_percentage");
        entryObject.remove("constituency");
        entryObject.remove("debates");
        entryObject.remove("education_details");
        entryObject.remove("education_qualification");
        entryObject.remove("elected");
        entryObject.remove("gender");
        entryObject.remove("house");
        entryObject.remove("in_office");
        entryObject.remove("last_name");
        entryObject.remove("notes");
        entryObject.remove("private_bills");
        entryObject.remove("questions");
        entryObject.remove("term_end");
        entryObject.remove("term_start");

        return entryObject;
    }

 

 

4. Step: Transform the JSON object to String

 

Now that we have manipulated the JSON nodes as desired, we’re ready to transform it back to string.

 

 

def String transformToString(JsonObject jsonObject){

   

       return jsonObject.toString();

}

 

 

 

 

5. Step: Set the payload

 

The only thing that’s missing is to send the converted payload back to the Integration Gateway.

This is done same way like in the previous tutorials, by setting the header.

Here’s now our convertPayload() method, that invokes all the other methods described above.

We invoke this method in the processResponseData() callback method.

 

 

def void convertPayload(Message message){

   

     String payload = getResponsePayload(message);

 

     //parse the payload and transform into JsonObject

     JsonElement jsonElement = parsePayload(payload);

 

     // now do the refactoring: throw away useless nodes from backend payload

     JsonObject jsonObject = refactorJsonElement(jsonElement);

 

     // transform back to string

     String structuredJsonString = transformToString(jsonObject);

   

     if(structuredJsonString == null){

          log.logErrors(LogMessage.TechnicalError, "Error: failed to convert the backend payload to structured XML");

          //TODO proper error handling

          return;

     }

   

     // finally

     message.setBody(structuredJsonString);

     message.setHeader("Content-Type", new String("json"));

}

 

 

 

Final: Run

 

Now we can generate&deploy in Eclipse, and after configuring the destination, we can run and test the service.

 

Note:

Our sample code is of course not enterprise-ready, but that’s normal of course.

More relevant is that our sample code doesn’t contain error handling, I mean, if the REST service doesn’t return the expected xml-payload, then we have to react appropriately and provide an empty feed, or a meaningful error message in the browser, set the proper HTTP Status code, things like that.

 

I’m pasting the full script below, and I’m also attaching the relevant files to this blog post.

 

 

 

Full source code

 

 

 

import com.google.gson.JsonArray

import com.google.gson.JsonElement

import com.google.gson.JsonObject

import com.google.gson.JsonParser

import com.sap.gateway.ip.core.customdev.logging.LogMessage

import com.sap.gateway.ip.core.customdev.util.Message

 

 

 

/*

* ***************************

*  FRAMEWORK CALLBACK METHODS

* ***************************

*/

 

/**

  Function processRequestData will be called before the request data is

  handed over to the REST service and can be used for providing

  filter capabilities. User can manipulate the request data here.

*/

def Message processRequestData(message) {

 

     return message;

}

 

 

/**

  Function processResponseData will be called after the response data is received

  from the REST service and is used to convert the REST response to OData

  response using String APIs. User can manipulate the response data here.

*/

def Message processResponseData(message) {

 

     convertPayload(message);

 

     return message;

}

 

 

/*

* ***************************

*  RESPONSE HANDLING

* ***************************

*/

 

 

def void convertPayload(Message message){

   

     String payload = getResponsePayload(message);

 

     //parse the payload and transform into JsonObject

     JsonElement jsonElement = parsePayload(payload);

 

     // now do the refactoring: throw away useless nodes from backend payload

     JsonObject jsonObject = refactorJsonElement(jsonElement);

 

     // transform back to string

     String structuredJsonString = transformToString(jsonObject);

   

     if(structuredJsonString == null){

          log.logErrors(LogMessage.TechnicalError, "Error: failed to convert the backend payload to structured XML");

          //TODO proper error handling

          return;

     }

   

     // finally

     message.setBody(structuredJsonString);

     message.setHeader("Content-Type", new String("json"));

}

 

 

 

def String getResponsePayload(Message message){

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

   

     //TODO do some checks on the payload here

   

     return payload;

}

 

 

def JsonElement parsePayload(String payload){

 

     JsonParser parser = new JsonParser();

   

     return parser.parse(payload);

}

 

 

def JsonObject refactorJsonElement(JsonElement jsonElement){

   

     JsonObject feedJsonObject = jsonElement.getAsJsonObject(); // {"results":[{"age":60,"...

   

     //first refactoring

     doRefactoringForFeed(feedJsonObject);

   

     // second refactoring: need to remove some properies, because the aren't defined in our odata model

     JsonArray resultsArray = feedJsonObject.get("results").getAsJsonArray(); // it is a feed / array

   

     // we're performing a QUERY operation, so loop over all entries and refactor each

     for(int i = 0; i < resultsArray.size(); i++){

          JsonElement entryElement = resultsArray.get(i);

          if(entryElement instanceof JsonObject){

               //remove undesired properties, corresponding to edmx

               doRefactoringForEntry((JsonObject)entryElement);

          }

     }

 

     return feedJsonObject;

}

 

 

def JsonObject doRefactoringForFeed(JsonObject feedJsonObject){

     feedJsonObject.remove("count");

     feedJsonObject.remove("page");

   

     return feedJsonObject;

}

   

   

def JsonObject doRefactoringForEntry(JsonObject entryObject){

     //remove undesired properties, corresponding to edmx

     entryObject.remove("age");

     entryObject.remove("attendance_percentage");

     entryObject.remove("constituency");

     entryObject.remove("debates");

     entryObject.remove("education_details");

     entryObject.remove("education_qualification");

     entryObject.remove("elected");

     entryObject.remove("gender");

     entryObject.remove("house");

     entryObject.remove("in_office");

     entryObject.remove("last_name");

     entryObject.remove("notes");

     entryObject.remove("private_bills");

     entryObject.remove("questions");

     entryObject.remove("term_end");

     entryObject.remove("term_start");

   

     return entryObject;

}

 

 

def String transformToString(JsonObject jsonObject){

   

     return jsonObject.toString();

}

 

 

 

 

 

 

Further Reading

 

 

Preparing Eclipse for Groovy scripting:

http://scn.sap.com/docs/DOC-61719

 

Introduction in REST datasource part 1: Understanding the return structure in xml

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/10/understanding-rest-data-source-in-integration-gateway-1-query-very-simplified

 

Introduction in REST datasource part 2: Understanding the return structure in json

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-very-simplified

Introduction in REST datasource part 3: converting xml payload of a REST service for usage in Integration Gateway

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/12/integration-gateway-understanding-rest-data-source-3-query--xml-standard

 

Introduction in REST datasource part 4: implementing filter in custom script for Integration Gateway

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/17/integration-gateway-understanding-rest-data-source-4-filtering

 

Introduction in REST datasource part 5: converting xml payload using xml parser instead of string operations

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/18/integration-gateway-understanding-rest-data-source-5-using-xml-parser

 

Installing SMP toolkit:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/08/22/how-to-install-sap-mobile-platform-tools-for-integration-gateway-in-eclipse-kepler

 

Tutorial for Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/06/10/creating-an-odata-service-based-on-sap-gateway-soap-jdbc-and-jpa-data-sources-ba

 

Tutorial about scripting in Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/10/17/integration-gateway-using-custom-scripting-to-map-entity-sets-to-data-sources

This  blog is (once more) about Integration Gateway in SAP Mobile Platform 3.0.

It is meant for those of you who have already created OData services based on REST data source through Integration Gateway in SAP Mobile Platform 3.0 (SMP).

If you’ve implemented the script as described in my previous tutorial and if you’ve wondered if there isn’t a different way to convert the payload than just doing string operations… (only) then you should have a look at the following blog.

 

In the bottom of my heart… I was wondering if string operations are the only way to convert a payload from one format to another one.

The REST services that I had tried, are providing nicely structured xml payload in their response body.

So why not parse it with an XML parser?

 

In this blog, I’m  describing how I’ve written a prototype for manipulating the payload of a REST service with an XML parser (instead of string operations).

This is not an official guide.

It might not meet all your requirements – so please do let me know your concerns or suggestions for improvements.

This tutorial is based on SMP SP05.

 

 

 

Prerequisites

 

  • My previous tutorial where we’ve created an OData service based on a REST data source
  • SAP Mobile Platform 3.0 SP 05
  • Eclipse Kepler with SAP Mobile Platform Tools installed

 

 

 

Preparation

 

SAP Mobile Platform

As described in one of my previous tutorials you need to create a destination that points to the host of the REST service that is used in this tutorial:

http://services.gisgraphy.com

with "no Authentication".

 

Eclipse

Create OData Implementation Project.

Create OData model or better: import the model which we used in the previous tutorial

Create binding to our example REST service: /street/streetsearch?lat=41.89&lng=-87.64

Create Custom Code script for Groovy.

 

The following tutorial shows how to implement the script.

 

 

 

 

Implementing the data structure conversion using XML parser

 

 

 

 

Overview

 

We will be implementing only the method processResponseData()

 

The steps are:

 

  1. Get the payload string
  2. Parse the xml / convert to DOM
  3. Refactor the DOM
  4. Transform the DOM to String
  5. Set the converted payload string

 

 

1. Step: Get the payload

 

This is the same step a usual:

 

 

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

 

 

 

2. Step: Parse the XML

 

 

In this tutorial, we’re using w3c.dom - API

But before we can parse the response body, we have to transform it to InputSource.

This is the code:

 

 

def Document parsePayload(String payload) {

 

     InputStream inputStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));

     InputSource inputSource = new InputSource(inputStream);

 

     DocumentBuilder parser;

     try {

          parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();

  

          // now parse

                  return parser.parse(inputSource);

     } catch (ParserConfigurationException e) {

          log.logErrors(LogMessage.TechnicalError, "Error: failed to create parser");

                  return null;

     } catch (SAXException e) {

          log.logErrors(LogMessage.TechnicalError, "Exception ocurred while parsing the response body");

                  return null;

     }

}

 

 

 

 

3. Step: Refactor the Document

 

Quick recap: what do we have to do?

This is the response body that we get from the REST service:

 

 

 

 

And this is the structure that is expected by Integration Gateway:

 

<EntitySetName>

    <EntityName1>

          <PropertyName1>“value of property1”</PropertyName1>

          <PropertyName2>“value of property2”</PropertyName2>

          <PropertyName3>“value of property1”</PropertyName3>

    </EntityName1>

    <EntityName2>

          <PropertyName1>“value of property1”</PropertyName1>

          <PropertyName2>“value of property2”</PropertyName2>

          <PropertyName3>“value of property1”</PropertyName3>

    </EntityName2>

</EntitySetName>

 

 

So, we have to re-structure the above REST-xml-structure, in order to get the structure that is expected by Integration Gateway.

 

In detail:

Rename the root node <results> to <StreetSet>

Rename the data nodes <result> to <Street>

Delete the info nodes <numFound> and <QTime>

Delete the attribute xmlns=”…”  in the root node

 

Fortunately, the w3c.dom package provides support for modification of xml nodes.

My proposal for implementation:

 

 

def Document refactorDocument(Document document){

 

     if(document == null){

          log.logErrors(LogMessage.TechnicalError, "Could not load xml-document");

         return;

     }

 

     //find nodes

     Node resultsElement = document.getFirstChild();

     NodeList childrenOfResults = resultsElement.getChildNodes();

 

     // rename the root node: <results xmlns="http://gisgraphy.com">

     document.renameNode(resultsElement, resultsElement.getNamespaceURI(), "StreetSet");

 

     // remove xmlns-attribute from root node: example: <results xmlns="http://gisgraphy.com">

     NamedNodeMap attributesMap = resultsElement.getAttributes();

     if(attributesMap.getNamedItem("xmlns") != null){

         try {

          attributesMap.removeNamedItem("xmlns");

          } catch (DOMException e) {

               log.logErrors(LogMessage.TechnicalError, "Failed removing attribute.");

          }

     }

 

     // nodes to delete:

     Node numNode;

     Node qtNode;

 

     // rename all nodes of the REST service:   <result>...

     for(int i = 0; i < childrenOfResults.getLength(); i++){

          Node childNode = childrenOfResults.item(i);

          String nodeName = childNode.getNodeName();

  

                  if(nodeName.equals("numFound")){ // store this node for later deletion

               numNode = childNode;

          } else if(nodeName.equals("QTime")){ // store this node for later deletion

               qtNode = childNode;

          } else{ // rename this node

               document.renameNode(childNode, childNode.getNamespaceURI(), "Street");

          }

     }

 

     // delete 2 undesired nodes <numFound>50</numFound> and <QTime>..</QTime>

     if(numNode != null){

          resultsElement.removeChild(numNode);

     }

     if(qtNode != null){

          resultsElement.removeChild(qtNode)     

     }

 

     // done with refactoring

     return document;

}

 

 

 

4. Step: Transform the Document to String

 

Now that we have manipulated the Document as desired, we’re ready to transform it back to String.

Here’s kind of default code, nothing to explain here:

 

 

def String transformToString(Document document, String encoding) {

 

     // Explicitly check, otherwise method returns an XML Prolog

     if (document == null) {

          log.logErrors(LogMessage.TechnicalError, "Document is null.");

                  return null;

     }

 

     // create the transformer

     TransformerFactory transformerFactory = TransformerFactory.newInstance();

     Transformer transformer = null;

     try {

          transformer = transformerFactory.newTransformer();

     } catch (TransformerConfigurationException e) {            

          log.logErrors(LogMessage.TechnicalError, "Failed creating Transformer.");

                  return null;

     }

 

     // configure the transformer

     transformer.setOutputProperty(OutputKeys.METHOD, "xml");

     transformer.setOutputProperty(OutputKeys.INDENT, "yes");

     transformer.setOutputProperty(OutputKeys.ENCODING, encoding);

     transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

     transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

 

     // prepare the output

     OutputStream outputStream = new ByteArrayOutputStream();

     OutputStreamWriter outputStreamWriter = null;

     try {

          outputStreamWriter = new OutputStreamWriter(outputStream, encoding);

     } catch (UnsupportedEncodingException e) {

          log.logErrors(LogMessage.TechnicalError, "Failed creating OutputStreamWriter");

                  return null;

     }

 

     // Finally do the transformation

     BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);

     try {

          transformer.transform(new DOMSource(document), new StreamResult(bufferedWriter));

     } catch (TransformerException e) {

          log.logErrors(LogMessage.TechnicalError, "Transformation failed");

                  return null;

     }

 

     return outputStream.toString();

}

 

 

 

5. Step: Set the payload

 

The only thing that’s missing is to send the converted payload back to Integration Gateway.

This is done same way like in the previous tutorials, by setting the header.

Here’s now our convertPayload() method, that invokes all the other methods described above.

We invoke this method in the processResponseData() callback method.

 

 

def void convertPayload(Message message){

 

     String payload = getResponsePayload(message);

 

     //parse the payload and transform into Document

     Document document = parsePayload(payload);

 

     // now do the refactoring: throw away useless nodes from backend payload

     document = refactorDocument(document);

     String structuredXmlString = transformToString(document, "UTF-8");

 

     if(structuredXmlString == null){

          log.logErrors(LogMessage.TechnicalError, "Conversion failed");

          //TODO proper error handling

                  return;

     }

 

     // finally

     message.setBody(structuredXmlString);

     message.setHeader("Content-Type", new String("xml"));

}

 

 

 

Final: Run

 

Note:

Our sample code is of course not enterprise-ready, but that’s normal for a tutorial.

More relevant is that our sample code doesn’t contain error handling, I mean, if the REST service doesn’t return the expected xml-payload, then we have to react appropriately and provide an empty feed, or a meaningful error message in the browser, we have to set the proper HTTP Status code, things like that.

 

I’m pasting the full script below, and I’m also attaching the relevant files to this blog post.

Note that the attached files are based on SP05. So if you use them for a different version, you have to adapt them, e.g. update the dependencies in the manifest file.

 

Now we can generate&deploy in Eclipse, and after configuring the destination, we can run and test the service.

 

 

 

Full source code

 

 

import java.nio.charset.StandardCharsets

 

import javax.xml.parsers.DocumentBuilder

import javax.xml.parsers.DocumentBuilderFactory

import javax.xml.parsers.ParserConfigurationException

import javax.xml.transform.OutputKeys

import javax.xml.transform.Transformer

import javax.xml.transform.TransformerConfigurationException

import javax.xml.transform.TransformerException

import javax.xml.transform.TransformerFactory

import javax.xml.transform.dom.DOMSource

import javax.xml.transform.stream.StreamResult

 

import org.w3c.dom.DOMException;

import org.w3c.dom.Document

import org.w3c.dom.NamedNodeMap;

import org.w3c.dom.Node

import org.w3c.dom.NodeList

import org.xml.sax.InputSource

import org.xml.sax.SAXException

 

import com.sap.gateway.ip.core.customdev.logging.LogMessage

import com.sap.gateway.ip.core.customdev.util.Message

 

 

/*

* ***************************

*  FRAMEWORK CALLBACK METHODS

* ***************************

*/

 

 

/**

  Function processRequestData will be called before the request data is

  handed over to the REST service and can be used for providing

  filter capabilities. User can manipulate the request data here.

*/

def Message processRequestData(message) {

 

       return message;

}

 

/**

  Function processResponseData will be called after the response data is received

  from the REST service and is used to convert the REST response to OData

  response using String APIs. User can manipulate the response data here.

*/

def Message processResponseData(message) {

 

     convertPayload(message);

 

     return message;

}

 

 

/*

* ***************************

*  RESPONSE HANDLING

* ***************************

*/

 

def void convertPayload(Message message){

 

     String payload = getResponsePayload(message);

 

     //parse the payload and transform into Document

     Document document = parsePayload(payload);

 

     // now do the refactoring: throw away useless nodes from backend payload

     document = refactorDocument(document);

     String structuredXmlString = transformToString(document, "UTF-8");

 

     if(structuredXmlString == null){

          log.logErrors(LogMessage.TechnicalError, "Conversion failed");

          //TODO proper error handling

          return;

     }

 

     // finally

     message.setBody(structuredXmlString);

     message.setHeader("Content-Type", new String("xml"));

}

 

 

 

def String getResponsePayload(Message message){

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

 

     //TODO do some checks on the payload here

 

     return payload;

}

 

 

/**

* Parse the response body into a Document

* */

def Document parsePayload(String payload) {

 

     if(payload == null || payload.length() < 1){

          log.logErrors(LogMessage.TechnicalError, "Cannot parse empty payload.");

                  return null;

     }

 

     InputStream inputStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));

     InputSource inputSource = new InputSource(inputStream);

 

     DocumentBuilder parser;

     try {

          parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();

  

          return parser.parse(inputSource);

     } catch (ParserConfigurationException e) {

          log.logErrors(LogMessage.TechnicalError, "Parser creation failed");

                  return null;

     } catch (SAXException e) {

          log.logErrors(LogMessage.TechnicalError, "Response parsing failed");

                  return null;

     }

}

 

def Document refactorDocument(Document document){

 

     if(document == null){

          log.logErrors(LogMessage.TechnicalError, "Failed to load document");

                  return;

     }

 

     //find nodes

     Node resultsElement = document.getFirstChild();

     NodeList childrenOfResults = resultsElement.getChildNodes();

 

     // rename the root node: <results xmlns="http://gisgraphy.com">

     document.renameNode(resultsElement, resultsElement.getNamespaceURI(), "StreetSet");

 

     // remove xmlns-attribute from root node: example: <results xmlns="http://gisgraphy.com">

     NamedNodeMap attributesMap = resultsElement.getAttributes();

     if(attributesMap.getNamedItem("xmlns") != null){

          try {

               attributesMap.removeNamedItem("xmlns");

          } catch (DOMException e) {

               log.logErrors(LogMessage.TechnicalError, "Failed to remove attrib.");

          }

     }

 

     // nodes to delete:

     Node numNode;

     Node qtNode;

 

     // rename all nodes of the REST service:   <result>...

     for(int i = 0; i < childrenOfResults.getLength(); i++){

          Node childNode = childrenOfResults.item(i);

          String nodeName = childNode.getNodeName();

  

          if(nodeName.equals("numFound")){ // store for later deletion

               numNode = childNode;

          } else if(nodeName.equals("QTime")){ // store for later deletion

               qtNode = childNode;

          } else{ // rename this node

               document.renameNode(childNode, childNode.getNamespaceURI(), "Street");

          }

     }

 

     // delete undesired nodes <numFound>50</numFound> and <QTime>..</QTime>

        if(numNode != null){

          resultsElement.removeChild(numNode);

     }

     if(qtNode != null){

          resultsElement.removeChild(qtNode)     

     }

 

     // done with refactoring

     return document;

}

 

 

/**

* Transforms the specified document into a String representation.

* Removes the xml-declaration (<?xml version="1.0" ?>)

* @param encoding should be UTF-8 in most cases

*/

def String transformToString(Document document, String encoding) {

 

     if (document == null) {

          log.logErrors(LogMessage.TechnicalError, "Document is null.");

                  return null;

     }

 

     // create the transformer

     TransformerFactory transformerFactory = TransformerFactory.newInstance();

     Transformer transformer = null;

     try {

          transformer = transformerFactory.newTransformer();

     } catch (TransformerConfigurationException e) {            

          log.logErrors(LogMessage.TechnicalError, "Transformer creation failed.");

                  return null;

     }

 

     // configure the transformer

     transformer.setOutputProperty(OutputKeys.METHOD, "xml");

     transformer.setOutputProperty(OutputKeys.INDENT, "yes");

     transformer.setOutputProperty(OutputKeys.ENCODING, encoding);

     transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

     transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

 

     // prepare the output

     OutputStream outputStream = new ByteArrayOutputStream();

     OutputStreamWriter outputStreamWriter = null;

     try {

          outputStreamWriter = new OutputStreamWriter(outputStream, encoding);

     } catch (UnsupportedEncodingException e) {

          log.logErrors(LogMessage.TechnicalError, "OutputStreamWriter creation failed");

                  return null;

     }

 

     // Finally do the transformation

     BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);

     try {

          transformer.transform(new DOMSource(document), new StreamResult(bufferedWriter));

     } catch (TransformerException e) {

          log.logErrors(LogMessage.TechnicalError, "Transformation failed.");

                  return null;

     }

 

     return outputStream.toString();

}

 

 

 

Further Reading

 

 

 

Preparing Eclipse for Groovy scripting:

http://scn.sap.com/docs/DOC-61719

 

Introduction in REST datasource part 1: Understanding the return structure in xml

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/10/understanding-rest-data-source-in-integration-gateway-1-query-very-simplified

 

Introduction in REST datasource part 2: Understanding the return structure in json

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-very-simplified

 

Introduction in REST datasource part 3: converting xml payload of a REST service for usage in Integration Gateway

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/12/integration-gateway-understanding-rest-data-source-3-query--xml-standard

 

Introduction in REST datasource part 4: implementing filter in custom script for Integration Gateway

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/17/integration-gateway-understanding-rest-data-source-4-filtering

 

Installing SMP toolkit:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/08/22/how-to-install-sap-mobile-platform-tools-for-integration-gateway-in-eclipse-kepler

 

Tutorial for Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/06/10/creating-an-odata-service-based-on-sap-gateway-soap-jdbc-and-jpa-data-sources-ba

 

Tutorial about scripting in Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/10/17/integration-gateway-using-custom-scripting-to-map-entity-sets-to-data-sources

This  blog is (once more) about Integration Gateway in SAP Mobile Platform 3.0.

It is meant for those of you who have already created OData services based on Integration Gateway in SAP Mobile Platform 3.0 (SMP).

If you want your OData service to support filtering, you have to implement it in the custom script.

This tutorial explains how it is done using the ExpressionVisitor interface, which is provided by Olingo.

By the way, this tutorial is based on SMP SP05.

 

We’re not going to start from scratch. So in order to follow this tutorial, you need to have the project in place that is created in my previous tutorial.

 

 

 

Prerequisites

 

 

 

 

Preparation

 

The following steps were done in the previous tutorial:

 

  • Create implementation project
  • Define OData model
  • Generate custom code for Groovy
  • implement the processResponseData() method
  • Deploy it to SMP
  • Create/configure the destination
  • Test the query operation.

 

 

 

Filter Implementation

 

Overview:


  • Retrieve the Objects UriInfo and FilterExpression.
  • Create Visitor class that implements the ExpressionVisitor interface
  • Implement the 4 required methods
  • Invoke the visitor
  • Use the result to manipulate the relative URI

 

 

Step: Callback  method processRequestData()

 

Implementing the callback method processRequestData() that is provided by the Integration Gateway Framework and which is generated into our custom script.

 

We have to understand:

This method is called, before the REST service (= the data source) is invoked.

 

The order is:

The user calls our OData service in SMP. E.g. https://<host>/gateway/odata/SAP/<SERVICE>/StreetSet?$filter=streetType eq ’PEDESTRIAN’

The OData call is received by Integration Gateway.

Integration Gateway requires our help, therefore it passes the OData-URI (which was called by the user) into our custom script, in the processRequestData() method

Here, we have to change the URI, because the filter statement in OData language is not understood by the REST service.

After changing the URI, we give it back to Integration Gateway.

Then Integration Gateway can safely call the REST service and will get the filtered data.

 

At this point we need to check the documentation of our REST service:

http://www.gisgraphy.com/documentation/user-guide.htm#streetwebservice

 

For this tutorial, I’ve chosen the streettype for filtering.

Example filter:

&streettype=PEDESTRIAN

 

The full URL:

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41&streettype=PEDESTRIAN

 

Note:

Please ignore that this URL actually contains 3 filters.

The background is that when we've created the Eclipse project, we’ve configured following URL

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41 as REST service-URL to provide data.

So, since the first 2 filters belong to the base URL (as we've defined it), we have to ignore these first 2 filters in our tutorial

So we have the base URL:

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41

and we add one filter to it:

&streettype=PEDESTRIAN

 

 

Step: Object UriInfo

 

The Olingo API provides the interface UriInfo

 

We need it, because this object contains information about the URI, which includes system query options.

The part of the URI, in which we’re interested, is the filter statement.

 

 

Step: Object FilterExpression

 

The Olingo API provides the interface FilterExpression

We need it in order to parse the OData filter statement.

We obtain the filter statement by asking the UriInfo for the FilterExpression:

 

 

       FilterExpression filterExpression = uriInfo.getFilter();

 

 

The filterExpression object contains the actual filter that is specified in the URL of the invoked OData service on SMP.

This is the $filter parameter, as specified in OData.

 

Example:

 

If the full URL of the OData service invocation is as follows:

 

https://<SMPserver>/gateway/odata/SAP/<YOUR_SERVICE>/StreetSet?$filter=streetType eq ’PEDESTRIAN’

 

then the filterExpression object contains the information about

 

filter=streetType eq ’PEDESTRIAN’

 

If the full URL of the OData service invocation is as follows (just as an example):

 

https://<server>/gateway/odata/SAP/<YOUR_SERVICE>/StreetSet?$format=json

 

then the filterExpression object is null

So we have to explicitly handle the case that no filter is desired:

 

     if (filterExpression == null){

          return;

     }

 

The filterExpression object is required to invoke the ExpressionVisitor.

 

 

 

Step: Interface ExpressionVisitor

 

The Olingo API provides the interface ExpressionVisitor

 

This interface has to be implemented by us.

Here it is where the filter implementation is realized.

The ExpressionVisitor is required, because a filter statement can be complex.

The Olingo framework traverses all the filter statements in the OData service call and passes the segments to us in the different methods of the interface.

 

In our script, we create an inner class that implement the ExpressionVisitor interface:

 

 

     def class RestExpressionVisitor implements ExpressionVisitor{

 

 

We let Eclipse add the methods which have to be implemented.

Most of them can be ignored.

 

In following steps, we'll have a closer look at the relevant methods.

 

visitProperty()


visitLiteral()


visitBinary()


visitFilterExpression()

 

 

They are invoked in this order.

 

 

Example:

 

What happens, if our OData service is invoked with $filter=streetType eq ’PEDESTRIAN’

 

The methods are invoked in the following order:

 

visitProperty()Here we have to deal with the property, e.g.streetType
visitLiteral()

Here we have to deal with the literal, e.g.

‘PEDESTRIAN’
visitBinary()

Here we have to deal with the operator, e.g.

eq
visitFilterExpression()Here we have to deal with our filterExpression, e.g.streetType eq ‘PEDESTRIAN’

 

 

Note:

These sample values on the right column depend on the implementation.

 

 

 

Second example:

What happens, if our OData service is invoked with $filter=streetType eq ’PEDESTRIAN’ and name eq ‘East Lake Street’

 

 

 

visitProperty()Deal with the property, e.g.streetType
visitLiteral()

Deal with the literal, e.g.

‘PEDESTRIAN’
visitBinary()

Deal with the operator, e.g.

eq
visitProperty()Deal with the second property, e.g.name
visitLiteral()Deal with the second literal, e.g.‘East Lake Street’
visitBinary()Deal with the operator, e.g.eq
visitBinary()Deal with the operator, e.g.and
visitFilterExpression()Deal with our filterExpression, e.g.streetType eq ‘PEDESTRIAN’ and name eq ‘East Lake Street’

 

 

Note:

Again, these sample values depend on the implementation

 

We can see that the visitor traverses all segments and finally calls for the final expression

 

 

Note that In this tutorial we cover only simple filter statement.

 

Now let’s implement the methods for our example

 

 

 

Step: Method visitProperty()

 

The method signature:

 

Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmTyped)

 

In this method, we get the possibility to analyze the property mentioned in the filter expression.

In our example:

 

$filter=streetType eq ’PEDESTRIAN’

 

Here, the text streetType is the name of a property (in the OData model).

 

When implementing the visitProperty() method, we can access the property as an object as follows:

 

             EdmProperty edmProperty = (EdmProperty) edmTyped;

 

And the name of the property:

 

             String propertyName = edmProperty.getName();

 

So what?

In our REST example service we really need this method.

Why?

Because the parameter, that is supported by the REST service is: streettype

(see docu)

but the name of the property (in both the OData model and the REST service) is: streetType

 

See it? They differ.

Without converting the OData property name to the REST parameter name, the filtering doesn't work.

 

But we can make use of the visitProperty() method in order to do a mapping from property name to parameter name

 

            String restParameterName = null;

            if(propertyName.equals("streetType")){ // we get the OData property name

             restParameterName = "streettype"; // we convert it to REST parameter name     

       }else{

             restParameterName = propertyName;

       }

 

 

Our method has to return the name of the parameter that is used by the REST service.

See the sample implementation of the method here:

 

@Override

     public Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmTyped) {

 

     if(! (edmTyped instanceof EdmProperty)){

          throw new RuntimeException("Unexpected type");

     }

    

     EdmProperty edmProperty = (EdmProperty) edmTyped;

    

     String propertyName = null;

     try {

          propertyName = edmProperty.getName();

     } catch (EdmException e) {

          ((ILogger)log).logErrors(LogMessage.TechnicalError, "Failed to read property");

          throw new RuntimeException("Unable to access name of property", e);

     }

    

     String restParameterName = null;

     if(propertyName.equals("streetType")){ //in the REST service, the param is different

          restParameterName = "streettype";

     }else{

          restParameterName = propertyName;

     }

    

     return restParameterName; // e.g. will lead to streettype=PEDESTRIAN

}

 

 

 

Step: Method visitLiteral()

 

The method signature:

 

Object visitLiteral(LiteralExpression literalExpression, EdmLiteral edmLiteral)

 

In this method, we get 2 objects, but we focus on the second only:

 

EdmLiteral can be used for obtaining the literal, which is the pure text of the filter.

 

What do we have to do?

As we’ve seen above, the syntax of our REST service for filtering is:

 

&<parametername>=<value>

 

 

Note that the value doesn’t require inverted comma.

-> here we have the difference to OData

 

So in our example the OData segment  ’PEDESTRIAN’

 

Has to be converted to the REST segment PEDESTRIAN

(without inverted commas)

 

This is easy to implement: just remove the inverted commas.

And it is even easier to implement, because the following statement returns the value without inverted comma

 

edmLiteral.getLiteral()

 

So our implementation looks as follows:

 

 

     @Override

     public Object visitLiteral(LiteralExpression literalExpression, EdmLiteral edmLiteral) {

          return edmLiteral.getLiteral();

     }

 

 

 

Step: Method visitBinary()

 

The method signature:

 

Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide)

 

In this method, we have to convert the operator.

 

As we’ve seen above, the syntax of our REST service for filtering is:

 

&<parametername>=<value>

 

 

The operator is: =

-> this is the difference to OData

 

So in our example the OData operator: eq

 

Has to be converted to the REST operator: =

 

This has to be done for all supported operators, so we have to care about each operator in a switch statement.

Note that Olingo provides an enum for the OData operators:

 

org.apache.olingo.odata2.api.uri.expression.BinaryOperator

 

We get it as parameter in the method.

At the end, we have to return the whole expression.

 

In our tutorial, let’s support only 2 operators (it depends of course on the chosen REST service), to allow the above service invocation.

 

public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide) {

     String operatorForREST = "";

                  

     switch(operator){

          case BinaryOperator.EQ:

               operatorForREST = "=";

               break;

          case BinaryOperator.AND:

               operatorForREST = "&";

               break;

          default:

               throw new RuntimeException("Not supported: " + operator.toUriLiteral());

     }

                  

     return leftSide + operatorForREST + rightSide;

}

 

 

Step: Method visitFilterExpression()

 

The method signature:

 

Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression)

 

In this method, we have the chance to check the final filter expression.

The parameter expression contains our REST filter expression, as we've defined it in the previous visitor-methods.

E.g. streettype=PEDESTRIAN

 

What are we going to do here?

In a simple example, we would simply return the instance of expression.

Our REST sample service is a bit special, because our so-called base-URI already contains some parameters. As explained above, we use it simply to get some data. The fact that the REST service needs 2 parameters for geolocation, doesn’t disturb us.

It only forces us to do a little work in the visitFilterExpresission() method.

The reason is:

Our expression looks fine: streettype=PEDESTRIAN

But it has to be appended to the base URL, which already contains parameters:

 

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41

 

So before appending our expression, we have to insert a &

 

That’s all.

 

Our sample implementation:

 

public Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression) {

    

     if(expression instanceof String){

          String expressionAsString = (String)expression;

          if(! expressionAsString.startsWith("&")){

               expressionAsString = "&" + expressionAsString;

               return expressionAsString;

          }

     }


     return expression;

}

 

 

 

Step: Use ExpressionVisitor

 

In the above steps, we've created the RestExpressionVisitor class, that implements the required interface and the necessary methods.

Now we can instantiate and invoke it:

 

       RestExpressionVisitor visitor = new RestExpressionVisitor();

    Object restFilterObject = null;

       try {

          restFilterObject = filterExpression.accept(visitor);

    } catch (ExceptionVisitExpression e) {

          log.logErrors(LogMessage.TechnicalError, "Failed calling accept()");

                 return;

    } catch(ODataApplicationException oe){

          log.logErrors(LogMessage.TechnicalError, "Failed calling accept()");

                 return;

    }

 

 

The returned restFilterObject is the result of our effort: the REST-izised version of our OData-filter.

In our sample implementation, the resulting value is the following string:

 

&streettype=PEDESTRIAN&name=East Lake Street

 

What to do with it?

Remember that we’re implementing the callback method processRequestData(), where we’re supposed to manipulate the backend-URL, before it is called.

So we have to build the URI containing the filter string.

 

Usually, it is formed as follows:

 

<relativeURI> + ? + <filterString>

 

In our special case, the relativeURI already contains the ?

So we don’t need to add it.

We get the relativeURI string from the header, which is passed in the method signature in the message object.

In our example, the relative URI woud be:

 

/street/streetsearch?lat=41.89&lng=-87.64

 

The code looks as follows:

 

 

       String relativeUri = getRelativeUri(message);

       relativeUri = relativeUri + (String)restFilterObject;

 

 

and the code to obtain the relativeURI:

 

 

def String getRelativeUri(Message message){

     Map headers = message.getHeaders();

     Object object = headers.get("RelativeUri");

     if(object instanceof String){

          return (String)object;

     }

 

     return null;

}

 

 

 

The last step to do in the processRequestData() method is: to pass the new relativeUri to the Integration Gateway framework.

This is done by setting the header:

 

 

def void setRelativeUri(Message message, String newUri){

     message.setHeader("RelativeUri", newUri);

}

 

 

 

Final: Run

 

That’s it.

 

At least, that’s it for the filter implementation in the method processRequestData().

Which is the focus of this blog.

We haven’t talked about the processResponseData(), as this was the focus of my previous blog

Note that you also need to add the required import statements and also to add required bundles.

 

 

I’m pasting the full script below, and I'm also attaching the relevant files of my Eclipse project.

Note that the attached files are based on SP05. So if you use them for a different version, you have to adapt them, e.g. update the dependencies in the manifest file.

 

Please refer to my previous blogs for detailed information (see Links section).

 

 

 

Testing the filtering

 

After generate and deploy the designtime artifact and after configuring the service on SMP, you can try calling several URLs with different filters:

 

First test without filter:

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet

 

Try different streetType filters:

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=streetType eq ’PEDESTRIAN’

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=streetType eq ‘FOOTWAY’

 

And also name filters:

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=name eq ‘East Lake Street’

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=name eq ‘North Canal Street’

 

Try combined filter

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=streetType eq ‘PEDESTRIAN’ and name eq ‘East Lake Street’

 

 

There should be different and meaningful response to these calls.

 

 

Full source code

 

 

import org.apache.olingo.odata2.api.edm.EdmException
import org.apache.olingo.odata2.api.edm.EdmLiteral
import org.apache.olingo.odata2.api.edm.EdmProperty
import org.apache.olingo.odata2.api.edm.EdmTyped
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.api.uri.UriInfo
import org.apache.olingo.odata2.api.uri.expression.BinaryExpression
import org.apache.olingo.odata2.api.uri.expression.BinaryOperator
import org.apache.olingo.odata2.api.uri.expression.ExceptionVisitExpression;
import org.apache.olingo.odata2.api.uri.expression.ExpressionVisitor
import org.apache.olingo.odata2.api.uri.expression.FilterExpression
import org.apache.olingo.odata2.api.uri.expression.LiteralExpression
import org.apache.olingo.odata2.api.uri.expression.MemberExpression
import org.apache.olingo.odata2.api.uri.expression.MethodExpression
import org.apache.olingo.odata2.api.uri.expression.MethodOperator
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression
import org.apache.olingo.odata2.api.uri.expression.OrderExpression
import org.apache.olingo.odata2.api.uri.expression.PropertyExpression
import org.apache.olingo.odata2.api.uri.expression.SortOrder
import org.apache.olingo.odata2.api.uri.expression.UnaryExpression
import org.apache.olingo.odata2.api.uri.expression.UnaryOperator
import com.sap.gateway.core.ip.component.commons.ODataExchangeHeaderProperty
import com.sap.gateway.ip.core.customdev.logging.ILogger;
import com.sap.gateway.ip.core.customdev.logging.LogMessage;
import com.sap.gateway.ip.core.customdev.util.Message
/*
* ***************************
*  FRAMEWORK CALLBACK METHODS
* ***************************
*/
/**
  Function processRequestData will be called before the request data is
  handed over to the REST service and can be used for providing
  filter capabilities. User can manipulate the request data here.
*/
def Message processRequestData(message) {
    handleFilter(message);
    return message;
}
/**
  Function processResponseData will be called after the response data is received
  from the REST service and is used to convert the REST response to OData
  response using String APIs. User can manipulate the response data here.
*/
def Message processResponseData(message) {
    convertResponse(message);
    return message;
}
/*
* ***************************
*  RESPONSE HANDLING
* ***************************
*/
def void convertResponse(Message message){
    String restResponse = message.getBody().toString();
    /* CONVERT PAYLOAD */
    int index = restResponse.indexOf("<result>");
    if(index == -1){ // in case of empty payload
        restResponse = "<StreetSet><Street><gid></gid></Street></StreetSet>"; // currently the only possibility for returning nothing
    }else{
        restResponse = restResponse.substring(index);
        restResponse = restResponse.replaceAll("<result>", "<Street>");
        restResponse = restResponse.replaceAll("</result>", "</Street>");
        restResponse = restResponse.replaceAll("</results>", "</StreetSet>");
        restResponse = "<StreetSet>" + restResponse;
    }
    message.setBody(restResponse);
    message.setHeader("Content-Type", new String("xml"));
}
/*
* ***************************
*  REQUEST HANDLING
* ***************************
*/
/**
*     supporting $filter for one property only
* */
def void handleFilter(Message message){
    //retrieve the FilterExpression
    UriInfo uriInfo = getUriInfo(message);
    FilterExpression filterExpression = uriInfo.getFilter();
    if (filterExpression == null){
        // this is the case if the service is invoked without $filter
        return;
    }
    // Invoke the ExpressionVisitor
    RestExpressionVisitor visitor = new RestExpressionVisitor();
    Object restFilterObject = null;
    try {
        restFilterObject = filterExpression.accept(visitor);
    } catch (ExceptionVisitExpression e) {
        log.logErrors(LogMessage.TechnicalError, "Error while calling accept() on custom ExpressionVisitor");
        return;
    } catch(ODataApplicationException oe){
        log.logErrors(LogMessage.TechnicalError, "Error while calling accept() on custom ExpressionVisitor");
        return;
    }
    if(restFilterObject == null){
        log.logErrors(LogMessage.TechnicalError, "Couldn't retrieve the converted filter expression for the backend REST service.");
        return ;
    }
    // add the filter statement (converted to REST-format)
    String relativeUri = getRelativeUri(message); // the REST service URL without hostname. In our example it is: /street/streetsearch?lat=52.52&lng=13.41
    relativeUri = relativeUri + (String)restFilterObject; // we know that it is a String, because we've implemented it in the Visitor below
    // encode
    relativeUri = relativeUri.replaceAll(" ", "%20").replaceAll("'","%27");
    // Finally, set the newly composed URI
    setRelativeUri(message, relativeUri);
}
/**
* Implementation of ExpressionVisitor
*
* Supports combination of 2 filters
* e.g.
 * https://<host>/gateway/odata/SAP/<YOUR_SERVICE>/StreetSet?$filter=streetType eq ‘PEDESTRIAN’ and name eq ‘East Lake Street’
*
* */
def class RestExpressionVisitor implements ExpressionVisitor{
    /*
     * This implementation converts the OData-like operators (eq)
     * to operators in the format, that's understood by the REST service
     * */
    @Override
    public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide) {
        String operatorForREST = "";
           
        switch(operator){
            case BinaryOperator.EQ:
                operatorForREST = "=";
                break;
            case BinaryOperator.AND:
                operatorForREST = "&";
                break;
                default:
                    throw new RuntimeException("This filter operator is not supported: " + operator.toUriLiteral());
            }
           
        return leftSide + operatorForREST + rightSide;
    }
   
    @Override
    public Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression) {
   
        // we need to add & because the filter is not the first URL parameter
        if(expression instanceof String){
            String expressionAsString = (String)expression;
            if(    ! expressionAsString.startsWith("&")){
                expressionAsString = "&" + expressionAsString;
                return expressionAsString;
            }
        }
    }
    @Override
    public Object visitLiteral(LiteralExpression literalExpression, EdmLiteral edmLiteral) {
        return edmLiteral.getLiteral();
    }
    @Override
    public Object visitMember(MemberExpression arg0, Object arg1, Object arg2) {
        return null;
    }
    @Override
    public Object visitMethod(MethodExpression arg0, MethodOperator arg1, List<Object> arg2) {
        return null;
    }
    @Override
    public Object visitOrder(OrderExpression arg0, Object arg1, SortOrder arg2) {
        return null;
    }
    @Override
    public Object visitOrderByExpression(OrderByExpression arg0, String arg1,List<Object> arg2) {
        return null;
    }
    /*
     * We need to do a mapping from OData-property-name to REST-parameter-name
     * */
    @Override
    public Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmTyped) {
        if(! (edmTyped instanceof EdmProperty)){
            throw new RuntimeException("Unexpected type");
        }
   
        EdmProperty edmProperty = (EdmProperty) edmTyped;
        String propertyName = null;
        try {
            propertyName = edmProperty.getName();
        } catch (EdmException e) {
            ((ILogger)log).logErrors(LogMessage.TechnicalError, "EdmException occurred while accessing filter statment property");
            throw new RuntimeException("Unable to access name of property", e);
        }
   
        String restParameterName = null;
        if(propertyName.equals("streetType")){ // in the REST service, the filter parameter is spelled differently than the property name
            restParameterName = "streettype";
        }else{
            restParameterName = propertyName;
        }
   
        return restParameterName; // in our example, this will at the end lead to the expression streettype=PEDESTRIAN
    }
    @Override
    public Object visitUnary(UnaryExpression arg0, UnaryOperator arg1,Object arg2) {
        return null;
    }
}
/*
* ***************************
*  HELPER
* ***************************
*/
def String getRelativeUri(Message message){
    Map headers = message.getHeaders();
    Object object = headers.get("RelativeUri");
    if(object instanceof String){
        return (String)object;
    }
        return null;
}
def void setRelativeUri(Message message, String newUri){
    message.setHeader("RelativeUri", newUri);
}
def UriInfo getUriInfo(Message message){
    Map headers = message.getHeaders();
    Object object = headers.get(ODataExchangeHeaderProperty.UriInfo.toString()); // need dependency to bundle com.sap.gw.rt.ip.commons.camel-commons
    if(object instanceof UriInfo){
        return (UriInfo)object;
    }
    return null;
}




 

 

 

Further Reading

 

Preparing Eclipse for Groovy scripting:

http://scn.sap.com/docs/DOC-61719

 

Introduction in REST datasource part 1: Understanding the return structure in xml

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/10/understanding-rest-data-source-in-integration-gateway-1-query-very-simplified

 

Introduction in REST datasource part 2: Understanding the return structure in json

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-very-simplified

 

Introduction in REST datasource part 3: converting xml payload of a REST service for usage in Integration Gateway

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/12/integration-gateway-understanding-rest-data-source-3-query--xml-standard

 

Installing SMP toolkit:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/08/22/how-to-install-sap-mobile-platform-tools-for-integration-gateway-in-eclipse-kepler

 

Tutorial for Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/06/10/creating-an-odata-service-based-on-sap-gateway-soap-jdbc-and-jpa-data-sources-ba

 

Tutorial about scripting in Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/10/17/integration-gateway-using-custom-scripting-to-map-entity-sets-to-data-sources

Hi there,

In my last blog I promised to write a new one about how to use “mobile services”. I first thought about writing about our Offline OData innovation, but Midhun was much faster than me. So I take the topic of push notifications.

Notifications are adding real time capabilities to mobile applications. They help to speed up things. Typically it is triggered by a business event occurring in a back-end, so one of the most important parts for enabling real-time in mobile scenarios is to carefully think about the implementation of triggering events from the backend. Which business events are important, who are the recipients, what information should be sent (basically none, it should be a poke/pull implantation). If you have a clear understanding of those questions and hopefully some answers you can think about implementing push notifications.

Since all those questions are business related and I can’t answer them here, I'll focus to the technical implementation, which is always the same.

So for this we mock the business event just by using a rest client. That’s the easy part and I will later explain how to do that. The middle layer is obviously SAP HANA Cloud Platform mobile services and for this blog I choose Android to demonstrate the receiver part and I’ll use the Hybrid SDK (Cordova plugins provided by SMP SDK). Basically I followed this great guide (Getting Started with Kapsel - Part 4 -- Push) by Daniel Van Leeuwen, which covers most of the client related steps. I strongly encourage you to do the same if you did not yet. So stop reading here and create a hybrid, Android, mobile app, which is push enabled. Come back to this blog if you are ready.

 

Hi and welcome back.

I think you have recognised that Daniel used the SMP Admin UI to set the server sided configuration of Push. Instead of doing it for SMP, I will now show you how to do the same in the Admin Cockpit of “mobile services”.

 

Open the Admin Cockpit, and select the “Applications” tile.

pic1.png

As you can see I have already created an Application Configuration that points to a public OData service. The AppID (com.sap.hcpms.push.demo) can be named as you like. Here on this screen you can see a PushURL. This URL is the “old” API that supports SAP Gateway subscription based notifications. This kind of notifications is suitable for scenarios where your backend keeps track of the registered devices. Today we will concentrate on the enhanced and more flexible Push API. So right now, just ignore this URL.

At this point you need to either create a new Application Configuration or “Configure” an existing one.

Navigate to the “Push” tab (first click “Configure).

As you can see, you con configure various native push services here. HANA Cloud Platform mobile services supports native Android (GCM), iOS notifications (APNS), Blackberry, Windows Phone and Windows Desktop (Metrrrr…, sorry Windows Store Apps).

Since we want to use Android today, we need to provide an API Key and a Sender ID. In order to get those you need to follow the instructions here:

http://developer.android.com/google/gcm/gs.html

Your Sender ID is your Project Number – a simple numerical value like 123456789123.

The API key is a bit longer, it is also described how to get this under the link above.

We know put the values, generated by Google into the Admin Cockpit and store the values. That’s it. It’s done, really. This is how it should look like:

pic2.png

Save your changes.

 

We can now start our client application and register against HANA Cloud Platform mobile services.

Once we are registered we need to register with Google to get a “registration_id”, this is handled by the push plugin in the client (it uses the native push libraries by Google to do so). This registration id needs to be sent to the mobile services in order to make HCPms aware of the particular device (we knew the device before, but not in the context of GCM). This is also done by the push plugin, so you don’t have to care about it.

In summary, “mobile services” knows how to act like a GCM server and it knows all about the clients. So you could say it behaves like a message broker.

Now we can mock the back-end event using POSTMAN (Google Chrome plugin) or any other REST client (I like the RESTClient from Wiztools as well – more control for cookies, curl for the hackers among you).

This is a valid back-end call:

 

POST /restnotification/application/com.sap.hcpms.push.demo/ HTTP/1.1
Host: hcpms-d055161trial.hanatrial.ondemand.com
Content-Type: application/json
Authorization: Basic bm90bXlwYXNzd29yZA==
Cache-Control: no-cache

{"alert":"New Vacation Approval Request", "data":"It works!!" }

 

 

Notice that we use /restnotifications instead of /Notification. The new REST API provides a lot more options – try it out.


tl;dr

1. Follow:Getting Started with Kapsel - Part 4 -- Push

2. Follow: Getting Started on Android | Android Developers

3. Create Application config in HCPms cockpit and provide Sender ID and API Key

4. Test you application using a rest client.

 

Have Fun,

Martin

The SMP 3.0 OData SDK SP06 for Android introduced the concept of technical cache in the online store (OnlineODataStore). The technical cache stores the response of a GET request in the device. It is read-only, in consequence CREATE, UPDATE and DELETE requests would fail if the device is out of coverage.


The Technical cache is disabled by default. It is up to the application developer to use this feature, for example, to improve user experience.


Four (4) steps are required to use the technical cache:


1. Initialize Online Store with OnlineStoreOptions

The OnlineStoreOptions contains configuration information for the online store. In order to enable the Technical cache you need to set the useCache property to true and assign a cache encryption key (cacheEncryptionKey) to encrypt/decrypt cache persistent store.

 

The memory size of the device limits the technical cache size but it has a mechanism to remove older items in case the data exceeds the cache size. In the OnlineStoreOptions the following optional attributes can be used to configure the maximum cache size in kBytes (maximumCacheSize – default: 16384 kBytes) and percentage of remaining items size in the cache when the older items are removed (cacheCleanupTreshold – default: 60%). A cacheCleanupTreshold of 60% means that 40% of the cache data will be removed.


Code Snippets - How to enable technical cache

//Options to enable Technical Cache

OnlineStoreOptions onlineOptions = new OnlineStoreOptions();

onlineOptions.useCache = true;

onlineOptions.cacheEncryptionKey = "secret";

                  

//Method to open a new online store asynchronously

OnlineODataStore.open(context, endpointURL, httpConversationManager, openListener, onlineOptions);


2.  Send a GET request asynchronously

The client app must send the GET requests through the asynchronous read methods because the content of the technical cache will be return in the ODataRequestListener. For example, application developers can use any of the following read methods:

  • scheduleReadEntity(ODataEntity entity, ODataRequestListener listener, Map<String,String> options)

        Scheduling method for reading an Entity

  • scheduleReadEntity(String resourcePath, ODataRequestListener listener, Map<String,String>  options)

        Scheduling method for reading an Entity

  • scheduleReadEntitySet(String resourcePath, ODataRequestListener listener, Map<String,String>  options)

        Scheduling method for reading an Entity set

 

3. Implement the callback method requestCacheResponse in the ODataRequestListener

In each request the online store notifies the application about the cached response within the request listener through the requestCacheResponse callback


The callback sequence for requests is represented in the following diagram:

callbacksequence2.png

 

Code Snippets - How to get data from technical cache

@Override

public void requestCacheResponse(ODataRequestExecution request) {

ODataProperty property;

ODataPropMap properties;

//Verify request’s response is not null. Request is always not null

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

       //Parse the response

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

       if (response!=null){

              //Get the response payload

              ODataEntitySet feed = (ODataEntitySet) response.getPayload();

              if (feed!=null){

                //Get the list of ODataEntity

                List<ODataEntity> entities = feed.getEntities();

                           //Loop to retrieve the information from the response

                           for (ODataEntity entity: entities){

                            //Obtain the properties you want to display in the screen

                      properties = entity.getProperties();

                      property = properties.get(<property-name>);

                }

                //TODO - Send content to the screen

         }

    }

}

}


and last but not least,


4. include the following libraries and resources

 

The following libraries should be imported under libs folder

    • AfariaSLL.jar
    • ClientHubSLL
    • ClientLog.jar
    • Common.jar
    • Connectivity.jar
    • CoreServices.jar
    • DataVaultLib.jar
    • guava.jar
    • HttpConvAuthFlows.jar
    • HttpConversation.jar
    • maflogger.jar
    • maflogoncore.jar
    • maflogonui.jar
    • mafuicomponents.jar
    • mafsettingscreen.jar
    • MobilePlace.jar
    • ODataAPI.jar
    • odataoffline.jar
    • ODataOnline.jar
    • perflib.jar
    • Request.jar
    • sap-e2etrace.jar
    • simple-xml.jar
    • SupportabilityFacade.jar
    • XscriptParser.jar

 

2. The following resources should be imported under libs/armeabi folder

    • libdatabase_sqlcipher.so
    • libsqlcipher_android.so
    • libstlport_shared.so

 

You can find the .jar and .so files in your OData SDK installation folder:

  • <Client SDK dir>\NativeSDK\ODataFramework\Android\libraries
  • <Client SDK dir>\NativeSDK\MAFReuse\Android\libraries
  • <Client SDK dir>\NativeSDK\ODataFramework\Android\libraries\armeabi

 

 

Hope this helps

Claudia



This  blog is about Integration Gateway in SAP Mobile Platform 3.0.

It is a follow up of the introduction blog Integration Gateway: Understanding REST data source [1]: QUERY (very simplified)

 

In the first blog, the focus was to understand the structure of the string that we have to return in the script.

In order to keep it simple, we used a hardcoded string.

 

Now, in the current blog, we’ll go one step further and modify the response body of the REST service to give it the required format.

 

Note: I’ve attached the relevant files, the OData model and the script file, for your convenience.

 

 

Prerequisites

 

  • SAP Mobile Platform 3.0 SP 05
  • Eclipse Kepler with SAP Mobile Platform Tools installed
  • The REST tutorial part 1

 

 

 

OData model

 

Let’s start with the model.

Other than we did in our intro blog, we now have to create a model that matches the REST service.

 

Here are the rules:

 

 

Entity Type namearbitrary
Entity Set namearbitrary
Property namesProperty names in the OData model have to be exactly the same like in the REST service
Property count

The number of properties in the OData model compared to the number in REST service:

- OData model should have all properties that are in the REST service

- OData model must not have less properties than the REST service

- But OData model can have more properties than the REST service. Additional properties will be empty at runtime.

 

 

 

I’ve created the Entity as follows, according to the documentation of the REST service (http://www.gisgraphy.com/documentation/user-guide.htm#streetwebservice)

 

 

 

 

 

You can either type all properties, or import the model from the attached edmx file.

 

Note:

We don’t care about the types of the properties. Strings are ok for this tutorial.

 

 

 

Custom Code

 

Create binding and custom code (see the intro blog for details)

In the processResponseData () method,  we retrieve the response body of the call to the REST service.

Then we modify it to meet the expected structure.

This modification is easy in our example, because the REST service has a structure that is almost suitable.

 

 

 

 

 

 

The structure of the REST service is almost suitable, because it has 3 hierarchy levels, just like the structure that we have to provide.

What we have to do is:

 

  1. Remove the undesired informative nodes in the beginning
  2. Add the opening tag that was also removed in 1
  3. Rename the nodes according to the OData model

 

 

ConversionXML.png

 

 

This can be done simply with string operations.

There are many possible implementations, my approach is the following:

 

Remove the whole beginning by searching for the first occurrence of <result>, which is the first entry.

Doing that, we have also removed the opening root node. So we have to add it, that’s what we do in the last line.

And the rename is done in the middle for the EntitySet and EntityType nodes.

We don’t have to care about the properties, as they are presented in a suitable format

 

This is how the code looks like (in Groovy):

 

 

def Message processResponseData(message) {

  

     String restResponse = message.getBody().toString();

   

       /* CONVERT PAYLOAD */


    // find the first relevant entry

       int index = restResponse.indexOf("<result>");
    // cut the undesired information in the beginning of REST response

    restResponse = restResponse.substring(index);

    // replace the REST node with the OData EntityType

    restResponse = restResponse.replaceAll("<result>", "<Street>");

    // and the closing tag

    restResponse = restResponse.replaceAll("</result>", "</Street>");

    //replace REST rootNode with OData EntitySet

    restResponse = restResponse.replaceAll("</results>", "</StreetSet>");

    //above, we've cut the root node, so add the opening EntitySet

    restResponse = "<StreetSet>" + restResponse;

   

       // set the converted payload in the expected structure

    message.setBody(restResponse);

    message.setHeader("Content-Type", new String("xml"));

   

       return message;

}

 

 

 

 

As usual, don't forget to add the required import statements:

 

import com.sap.gateway.ip.core.customdev.util.Message

 

 

 

Result

 

After generate & deploy & configure & run we can see the "odataisized" result in the browser:

 

 

 

 

 

Links

 

Preparing Eclipse for Groovy scripting: http://scn.sap.com/docs/DOC-61719

 

Introduction in REST datasource part 1: Understanding the return structure in xml

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/10/understanding-rest-data-source-in-integration-gateway-1-query-very-simplified

 

Introduction in REST datasource part 2: Understanding the return structure in json

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-very-simplified

 

Installing SMP toolkit:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/08/22/how-to-install-sap-mobile-platform-tools-for-integration-gateway-in-eclipse-kepler

 

Tutorial for Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/06/10/creating-an-odata-service-based-on-sap-gateway-soap-jdbc-and-jpa-data-sources-ba

All.

 

I have just build a new version of the Android Agentry Client based on OpenUI in the SMP 3.0 SDK version SP06 PL02.

 

When you import the OpenUI projects into Eclipse you will be presented with errors like below.

 

ImportInEclipse.jpg

 

This is due to a missing project.properties file in the project AgentryAndroidClientSolution.

 

Fix:

 

1. Create the (empty) file project.properties in the root of the project AgentryAndroidClientSolution.

2. Go to the properties window (Alt-Enter) of the project, and enter below values.

 

AgentryAndroidClientSolution.jpg

 

Click OK.

 

Restart Eclipse.

 

Then you should be good to go.

 

 

Søren Hansen

 

This  blog is about Integration Gateway in SAP Mobile Platform 3.0.

 

 

It is a follow up of the introduction blog Integration Gateway: Integration Gateway: Understanding REST data source [1]: QUERY (very simplified)

 

In this blog, we do the same, but with the only difference that we use JSON payload.

 

I’m not going to repeat anything only show the differences.

 

We can use the same REST service as in the previous blog, it does support JSON as well.

We have to add a parameter in order to request the payload format.

 

The relative URL which returns JSON-payload:

 

/street/streetsearch?lat=52.52&lng=13.41&format=json

 

The structure, that Integration Gateway expects if you want to convert a response body in JSON :

 

{

"results":

     [

          {

               "PropertyName1":"PropertyValue1",

               "PropertyName2":"PropertyValue2"

          }

     ]

}

 

In our concrete example project:

 

{

"results":

     [

          {

               "StreetID":"12344",

               "StreetName":"MainStreet"

          }

     ]

}

 

 

 

In our Groovy script, we have to encode the quotation marks:

 

message.setBody(

             "{\"results\":"+

                    "["+

                           "{\"StreetID\":\"1234567\","+

                           "\"StreetName\":\"MainStreet\"}"+

                    "]"+

             "}");

 

 

And set the proper header:

 

message.setHeader("Content-Type", new String("json"));

 

 

That’s it.

 

 

 

 

For your reference, here's the full (dummy) implementation:

 

def Message processResponseData(message) {

       String restResponse = message.getBody().toString();

  

       message.setBody(

             "{\"results\":"+

                    "["+

                           "{\"StreetID\":\"1234567\","+

                           "\"StreetName\":\"MainStreet\"}"+

                    "]"+

             "}");

  

       message.setHeader("Content-Type", new String("json"));

       return message;

}

 

 

 

As usual, don't forget to add the required import statements:

 


import com.sap.gateway.ip.core.customdev.util.Message

 

 

 

Note:

Just as reminder: this is a hardcoded payload, and we're using it only to understand the structure of the string that we have to provide in the custom code.

 

 

After deploy, you can reuse the same destination as created in our previous blog.

When invoking the OData service on SMP, you can request the payload in XML or in JSON.

For JSON, use:

 

https://localhost:8083/gateway/odata/SAP/REST_PROJECT_VERY_SIMPLE_2;v=1/StreetSet?$format=json

 

 

Enjoy!

Configuring Proxy Settings

 

If the SMP 3.0 server is configured in an environment where there is no direct access to the internet and a proxy is required, by default, you need to configure SMP 3.0 SP4 manually in order to configure proxy.

 

Go to SMP Admin page, click on settings, go to the system tab, there we can mention the proxy details as shown below.

 

Odata services which are exposed through gateway cockpit will also use the proxy settings configured in SMP by default.

 

If the services exposed through gateway cockpit do not need to use the proxy settings, make sure to provide the server host (where SMP is installed) and other hosts (which does not need the proxy settings) in the Non Proxy host.

 

If multiple non proxy hosts need to be specified, separate it with “ | ”  (pipeline symbol)

 

eg:

 

*.nonproxyhost1.abc|*.nonproxyhost2.abc

 

Once you are done with the changes. Restart the server to take the effect.

 

system_proxy.PNG

Actions

Filter Blog

By author:
By date:
By tag: