Quantcast
Channel: SCN : Blog List - SMP Developer Center
Viewing all 370 articles
Browse latest View live

Eclipse Integrated Test Environment for SAP Agentry applications

$
0
0

If you have started using SMP SDK 3.0 SP06, you have a cool new option of testing your SAP Agentry mobile apps right from the new eclipse Test environment view. This is an alternative to the standalone Agentry Test Environment. With the introduction of this new eclipse view, I believe SAP’s intention is very clear. I believe, they would like to move all app testing to Eclipse some time in future.

 

Integrated test environment is included with the Agentry Editor and is installed automatically when you install Agentry Editor. This feature is available in SMP SDK 3,0 SP06.

 

Integrated Test Environment allows you to start and stop the test client, send a transmit; view data; view logs; and testing locales. Integrated test environment does not support test scripts.

 

Here is the information you need to get started with Integrated test environment view. This one talks only about getting up and running with the view and does not explore the different features.

 

In the eclipse environment choose Window=>Show View => Other => Agentry Views => Test Environment

0.1.OpenView.png

0.1.2.OpenView.png


You will notice that there is an issue with the Test Environment view. To overcome this we need to add an argument as per the information provided.

0.2.ViewIssue.png


If you have never modified the eclipse.ini file, here are some important points to keep in mind:

  • The -vm option and its value (the path) must be on separate lines.
  • The value must be the full absolute or relative path to the Java executable, not just to the Java home directory.
  • The -vm option must occur before the -vmargs option, since everything after -vmargs is passed directly to the JVM
  • For the 32-bit Eclipse executable (eclipse.exe on Windows) a 32-bit JVM must be used and for the 64-bit Eclipse executable a 64-bit JVM must be used. 32-bit Eclipse will not work with a 64-bit JVM


The file eclipse.ini can be found in the Eclipse folder(In the same folder as eclipse executable application)

0.3.location Of eclipse ini.png


The default eclipse.ini will look like this.

0.4.default contents Of eclipse ini.png

Please find a suitable jvm.dll and then add the vm argument before vmargs as shown here :

0.5.modified contents Of eclipse ini.png

After restarting the eclipse, you will notice that the error has gone.

0.6.View without isue.png

References:


How to use SAP HCPms with an ABAP system

$
0
0

Introduction

HCPms is the cloud version of SAP Mobile Platform(SMP) and it's now available for free testing at the following URL. You need to register and to apply the steps reported in the blog listed among the prerequisites in order to enable the HCPms environment for your user. Once you have registered to this service you need to configure it in some way so that you can start consuming any OData service it provides.

In another blog, How to on-board user with HANA Cloud Platform mobile services it's already explained how to configure HCPms so that it can connect to the Northwind free service: in this guide I will show you how you can do the same with an ABAP system on premise. We will assume that our backend system is named GM6 and that we want to consume the service SRA010_TIMESHEET_APPROVAL_SRV. Of course this is just an example and you can choose whatever ABAP system and service you want.

 

Prerequisites:

 

Let's get started!

 

1) Open your SAP HANA Cloud Connector and configure a row in the Access Control menu. The Internal Host parameter is the backend hostname

2015-01-29_15-15-15.png

2) Open the SAP HANA Cloud cockpit and create a destination for the server defined in the HANA Cloud Connector

2015-01-29_15-18-10.png

3) Click on the Applications tile and then on the "+" sign to create a new application

01.png

 

4) Create a new application with the following settings:

ParameterValue
Application IDcom.ts.apv
Version1.0
NameTSApproval
TypeHybrid
DescriptionTimesheet Approval
VendorSAP
Security ConfigurationNone

02.png

 

5) Switch to the Backend tab

03.png

 

6) Configure the backend in the following way and click on Save

Parameter
Value
Backend URLhttp://gm6.virtual:8010/sap/opu/odata/sap/SRA010_TIMESHEET_APPROVAL_SRV/
Authentication TypeNo Authentication
Maximum Connections500
Certificate Alias<keep it blank>
Rewrite ModeRewrite URL on HANA Mobile Server
Relative Paths<keep it blank>
Proxy TypeOn Premise

 

04.png

 

7) Going back to the Home you will see that there is now a new application in your cockpit. If you click on the Applications tile you will find the new application and you can ping it in order to check if the connection works fine

05.png

 

8) At this point you need just to create a new application in SAP Web IDE which will consume this data source. Open SAP Web IDE and from Tools --> External Plugins enable the "com.sap.webide.hybrid" plugin

07.png

 

9) Click on OK and then refresh your SAP Web IDE browser page

 

10) Choose File --> New --> Project from Template

 

11) Choose the template SAPUI5 Master Detail Kapsel Application

 

12) Enter the name for your application (i.e. TSApproval)

 

13) From the Service Catalog dropdown list choose the GM6 system we have previously configured. Enter your credentials if required

 

14) Select the service SRA010_TIMESHEET_APPROVAL_SRV from the list of the available services and click on Next


15) Enter the following information and click on Next

06.png

 

16) Click on Finish


17) [Optional] You can select the index.html file in the new application and run it in the desktop to check that all is working fine

 

18) Select the TSApproval application in the SAP Web IDE, right click on it and choose Project Settings --> Device Configuration. Enter the following information

Parameter
Value
App NameTSApproval
App IDcom.ts.apv
DescriptionTimesheet Approval
Version1.0.0
Kapsel Logon ManagerEnabled
Hosthcpms-<your user>trial.hanatrial.ondemand.com
Port443

08.png

 

 

19) Select the TSApproval application in the SAP Web IDE, right click on it and choose Deploy --> Deploy to local Hybrid Toolkit


20) Once the deployment is finished you can right click on the index.html file in the application and choose Run --> Run on --> iOS (or Android) Simulator (or Device)

 

21) Once the application on the emulator (device) is started, configure the Kapsel Logon by providing the credentials for the back end system and setting the Secure switch to ON, then click on Register. On the next screen create a passcode or disable it and click on Submit

09.png

 

22) You will get your application up and running

10.png

 

That's all folks!

 

Regards,

Simmaco

Service Mash-up in Integration Gateway - It's Simpler Than You Think

$
0
0

Integration gateway is a component in SAP Mobile Platform 3. It converts different types of protocols to Odata protocol. Currently it supports SOAP, JDBC, JPA, ODC and REST (read and query). This example is on how to mashup multiple backends and create an Odata service using Integration Gateway.

ScreenHunter_27 Feb. 06 00.07.jpg

Prerequisites

  • SAP Mobile Platform 3.0 SP05 Developer Installation
  • Eclipse Kepler
  • SAP Mobile Platform Eclipse Plugins. Available from the SAP eclipse update site: https://tools.hana.ondemand.com/kepler
  • HSQLDB (jdbc)

 

Steps to Create Project

  • Open Eclipse and select File > New > SAP Mobile Platform Odata Implementation Project.
  • In the pop up window provide the project name “ServiceMashup”.
  • Select Target Runtime Server as “SAP Mobile Platform 3.0 SP5”. Click Next.
  • Enter Model name as “model”. Then click on Finish. It creates the ServiceMashup project in the workspace.

ScreenHunter_27 Feb. 06 00.19.jpg

  • Create entity types CustomerObj and Article, and a complex Type Price.

Note that datatype of ID in CustomerObj is integer (edm.Int16) and BIRTHDATE is Datetime (edm.datetime).

I have followed the existing examples Integration Gateway with JDBC and Integration Gateway with SOAP to show how to mash up service. So in this blog I am not explaining how to set up HSQLDB. Copy and paste JDBC driver package (hsqldb.jar file) from ..\hsqldb-2.3.2\hsqldb\lib to C:\SAP\MobilePlatform3\Server\pickup .

Untitled.png

Define Data Source

  • From the Project Explorer right click on model.odatasrv file > Select Data Source.
  • Choose Entity Set "model.default.CustomerObjSet"
  • Select Query from Operations and select JDBC as Data source. Click Finish.

ScreenHunter_27 Feb. 06 00.38.jpg

  • Right click on model.odatasrv file > Select Data Source.
  • Choose Entity Set "model.default.ArticleSet"
  • Select Query from Operations and select SOAP Service as Data source.

ScreenHunter_27 Feb. 06 00.42.jpg

ScreenHunter_27 Feb. 06 00.48.jpg

Untitled.png

  • Save the project. Right click on the project and deploy it to SMP server.

 

SMP Server Configurations

 

ScreenHunter_27 Feb. 06 01.04.jpg

  • Click on Service > SERVICEMASHUP.

Untitled.png

 

  • From service details screen, choose Assign Destinations for Entity Sets.
  • Click on Add Destination > Choose DestHSQLDB.

Untitled.png

Testing Odata Service

  • Click on Open service document

ScreenHunter_27 Feb. 06 01.43.jpg

  • Add collection CUSTOMER with service document to pull data from JDBC.

ScreenHunter_28 Feb. 06 01.43.jpg

  • Add collection ArticleSet to pull data from SOAP service.

ScreenHunter_28 Feb. 06 01.44.jpg

Integration Gateway: Understanding REST data source [1]: QUERY (very simplified)

$
0
0

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

Since SP05 release, Integration Gateway (IGW) supports REST services as data source.

You can now expose any REST service as OData service through Integration Gateway.

 

With this blog, I’d like to help you understand what has to be done.

And why.

This is kind of introduction, please stay tuned for the upcoming posts which will cover more details.

 

 

 

Prerequisites

 

  • SAP Mobile Platform 3.0 SP05 (SMP) installation.
  • Eclipse Kepler with SAP Mobile Platform Tools installed
    check this blog for help
  • Basic knowledge about SMP and Integration Gateway
    Check this blog for some introduction

 

 

 

Overview

 

  1. REST service as data source
  2. Create OData Implementation Project
  3. Create OData Model
  4. Bind data source
  5. Create Custom Code
  6. Implement the Custom Code
  7. Deploy
  8. Runtime

 

 

 

1. REST service as data source

 

Within the internet, there are lots of free REST based webservices that can be used for testing and demoing.

I’ve found a suitable one, which is free, doesn’t require registration and returns the payload in XML.

We’ll use it in the following example.

 

  Home: http://www.gisgraphy.com/

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

 

Try it out.

 

Please note:

Within the title of this blog, I’ve promised to keep this tutorial VERY simple.

We’ll do so by completely ignoring the response of the REST service.

It doesn’t need to make sense, it is only used for understanding the REST data source in Integration Gateway.

 

 

 

2. Create OData Implementation Project

 

Start Eclipse, change to the OData Perspective: Window -> Open Perspective -> Other… -> OData

 

Create a new OData Implementation Project: File > New > SAP Mobile Platform Odata Implementation Project

Provide an arbitrary name for  the project and the model.

Choose the “Target Runtime Server” as “SAP Mobile Platform 3.0 SP5” (should be preselected)

 

 

 

3. Create OData Model

 

After creation of the OData Implementation Project, the graphical OData modelling tool is opened.

If not, open it with a double-click on the <your_name>.odata file

 

 

 

Create an EntityType and rename it to “Street”

Create one additional property with name “StreetName”

 

 

 

Note:

our OData model has nothing to do with the REST service, as we're going to ignore the REST response.

 

At this point, we have all information that we’ll need later:

  • REST service URL
  • EntityType name
  • EntitySet name
  • Property names

 

 

 

4. Bind data source

 

Within the “Project Explorer”, select the <your_name>.odatasrv file, open the context menu and choose “Select Data Source”.

We have only one EntitySet, so it is already selected.

We’re going to implement the QUERY operation, so this has to be selected (if not already preselected)

Mark the checkbox for “REST Service”

 

 

 

Press “Next”.

Now we have to enter the “relative request URL”.

 

At this point, we have to understand:

 

We have a full URL for a service that returns data: http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41

 

We split the full URL into 2 segments:

 

  1. The host URL
    http://services.gisgrapy.com
    -> it will be specified in SMP as destination for the (future) OData service
    -> it doesn’t have trailing slash.
  2. The relative URL
    /street/streetsearch?lat=52.52&lng=13.41
    -> it is entered in this wizard page.
    -> it is required to have the leading slash.

 

 

Now paste the relative URL into the wizard and press “Finish”

 

 

 

 

 

5. Create Custom Code

 

Within the “Project Explorer”, expand the <your_name>.odatasrv node and open the context menu on the “Query” node

 

 

 

 

 

 

Choose “Define Custom Code” and in the subsequent popup, select “Groovy”

 

 

 

 

Press “Finish”, the script file is created and is opened in an editor.

 

I recommend to follow the few steps described here in order to improve the development experience with Groovy.

 

 

6. Implement the Custom Code

 

This is finally the main section of this blog.

What are we required to do here?

 

We have 2 callback methods:

 

processRequestData()

    ->We ignore it in this blog.

 

processResponseData()

    ->We focus on this one.

 

We have to understand:

When a user invokes the (future) OData service on SMP, the request goes to SMP and to the Integration Gateway component.

Integration Gateway knows to which data source to connect in order to fulfill the request.

The data source returns the desired data.

Now Integration Gateway has to take this structured data and re-structure it in the proper OData structure, before returning it to the user.

 

And this is the pain point in the case of REST data source:

it is structured, but HOW?

 

There’s no spec on which Integration Gateway can rely.

And this is why we, the implementers of the OData project, come into the game:

 

We are supposed to understand the structure of data of the REST service that we want to use.

We are supposed to help IGW to convert the structure of the REST service into the structure of our OData service.

We are supposed to implement this in the custom code.

 

The method “processResponseData() is invoked by the IGW Framework right after it has received the response from the REST service.

In this method, we have to

1) retrieve the response body of the REST service.

2) convert it

3) return it

 

Let’s have a closer look

 

 

6.1 The REST response

 

The method processResponseData() has a parameter called “message” which is of type com.sap.gateway.ip.core.customdev.util.Message

This object provides access to the REST response via the following statement:

 

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

 

 

 

 

6.2 Conversion

 

Let’s be optimistic for now and assume that the call to the REST service has been successful.

In this case, we get a long string which contains the response that is returned if we call the REST service directly:

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

 

rest_orig_payload.JPG

 

In our script, we have to manipulate this string in order to convert it to the desired structure.

But I’ve promised to be very simple, so let’s just ignore the REST response in this tutorial.

Let’s focus only on returning the correct structure.

 

Let’s do a hack that is allowed for educational purpose;-)

Let’s hardcode the return-structure.

 

 

 

6.3 The return structure

 

To be short:

IGW doesn’t require from us that we do the full conversion of the REST-response to an OData-response.

IGW will provide the correct OData format that complies with the spec.

What we have to do is:

-> provide the data in the structure that IGW expects.

I really mean EXACTLY in the structure that IGW expects.

Don’t worry, it is easy:

 

EntitySet

    Entity

          Property

 

Since we are in a QUERY operation, we have to provide the correct entity set name.

Furthermore, a QUERY returns a list, so there can be one or more (or zero) entity types:

 

EntitySet

    Entity1

          Property

          Property

          Property

    Entity2

          Property

          Property

          Property

    ...

 

 

Even more precise, see here the expected structure in the expected format.

The format is in our example xml, because the the chosen REST service returns xml.

BTW, it has to be valid xml, with values and closing tags (otherwise we get an error at runtime)

 

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

 

Up to here it has been the generic description.

 

Now let's pllay it to our concrete example.

Look at the OData model that you’ve created in the graphical editor:

 

 

 

See it?

These are the names which you have to provide in the return structure.

I recommend using copy&paste in order to be sure to avoid tedious errors at runtime.

 

Now let’s build the response string for our example.

We copy the names from our odata model and paste them into our script file.

The expected structure would look like this:

 

<StreetSet>

    <Street>

          <StreetID>1234567</StreetID>

          <StreetName>MainStreet</StreetName>

    </Street>

</StreetSet>

 

 

Note:

I don’t need to mention, that the names of the properties etc have to be correct.

I mean, really correct, including the right case.

 

Now let’s add the following code to our Groovy script:

 

message.setBody(

            "<StreetSet>"+

                    "<Street>"+

                          "<StreetID>1234567</StreetID>"+

                          "<StreetName>MainStreet</StreetName>"+

                    "</Street>"+

            "</StreetSet>");

 

This is our hardcoded payload in the expected structure, according to our OData model.

We still need to specify the format of the payload in the respective header.

 

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

 

and don’t forget to add the import statements:

 

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

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

 

 

We’re done.

 

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

 

 

def Message processResponseData(message) {    String restResponse = message.getBody().toString();    message.setBody(        "<StreetSet>"+            "<Street>"+                "<StreetID>1234567</StreetID>"+                "<StreetName>MainStreet</StreetName>"+            "</Street>"+        "</StreetSet>");    message.setHeader("Content-Type", new String("xml"));    return message;
}

 

 

Don’t forget to save the file.

 

 

 

7. Deploy

 

Within the Project Explorer, select your project and from the context menu choose “Generate and Deploy Integration Content”.

You may specify some service details as desired and press “OK”.

 

 

 

 

After you get the success popup,

 

 

you can proceed and have a look in the Gateway Management Cockpit on the SMP server

 

Of course, here we assume that the SMP is configured in your Eclipse.

If not, you have to do so.

Go to Window -> Preferences -> SAP Mobile Platform Tools -> Server

And specify the details:

 

 

 

 

 

8. Runtime

 

Open the Gateway Management Cockpit at e.g. https://localhost:8083/gateway/cockpit

You might need to press “Refresh” to see your service.

 

 

 

 

Before invoking it, we need to configure the destination.

As we’ve learned above, we’ve split the full URL of our REST service into 2 segments.

The second segment is kept in the service implementation, that's what we've done above.

The first segment has to be configured as destination in our SMP.

Let’s quickly do that.

 

In the Gateway Management Cockpit, press the “Destinations” tab and click on “New Destination”.

Provide an arbitrary name for the destination.

The type is HTTP

The URL is the host URL that we’ve split above: http://services.gisgraphy.com

Authentication is not required for the REST service that we've chosen.

 

Note:

if you’re a keyboard user, you might get crazy because e.g <arrow left> will move the cursor to the previous input field, instead of moving the cursor inside the field to the previous character.

In case you didn’t know: you have to press F2 to edit the content of the current input field.

Yes, sir!

 

 

 

 

Save the destination.

Result should be a success popup.

You can now select the new destination  in the table, then press “Test Connection”.

 

 

Note:

The “Connection Test” is not 100% reliable, in the sense that it might report that the connection test fails, even though the connection is OK.

As we’ve seen above, for the REST data source, we have to split the URL. It can be the case that the first segment, the host, doesn’t respond as desired to the “Connection Test”, but it works fine for the full URL at runtime.

 

 

Note:

Depending on your entwork, it might be necessary to add proxy settings to your SMP server.

This is done at

https://<your_server>:8083/Admin/

In the “Settings”-tab and “System” sub-tab

 

 

Next, you have to assign this destination to your service.

Go back to the “Services” tab, click on your service in order to go to the details screen.

Click the “Add Destination” button and assign your new destination to your service:

 

 

 

 

Press OK.

 

Now you can click the hyperlink in the section “Service Access”:

 

 

 

 

The service document is opened.

Here you can see the one EntitySet that we’ve specified in our OData model:

 

 

This step should really always work fine.

Because until now we haven’t dealt with any backend-data-source-connection.

 

Next step is to invoke the EntitySet at:

https://localhost:8083/gateway/odata/SAP/REST_PROJECT_VERY_SIMPLE;v=1/StreetSet

(as usual, you have to adapt the sample URL to your SMP-installation)

 

The result is our hardcoded payload:

 

 

 

 

That’s it.

 

We’ve understood how the REST data source has to be implemented.

We’re now prepared for real implementation – and real headaches…;-)

 

 

BTW: I've attached the OData model and the custom script file for your convenience.

Enable proxy settings in SMP 3.0 SP4

$
0
0

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

Integration Gateway: Understanding REST data source [2]: QUERY - json (very simplified)

$
0
0

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!

SMP 3.0: Agentry OpenUI in SDK SP06 PL02 will not build for Android - fix guide.

$
0
0

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

 

Integration Gateway: Understanding REST data source [3]: QUERY - xml (standard)

$
0
0

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 */

       int index = restResponse.indexOf("<result>"); // find the first relevant entry

    restResponse = restResponse.substring(index); // cut the undesired information in the beginning of REST response

    restResponse = restResponse.replaceAll("<result>", "<Street>");// replace the REST node with the OData EntityType

    restResponse = restResponse.replaceAll("</result>", "</Street>"); // and the closing tag

    restResponse = restResponse.replaceAll("</results>", "</StreetSet>"); //replace REST rootNode with OData EntitySet

    restResponse = "<StreetSet>" + restResponse; //above, we've cut the root node, so add the opening EntitySet

    

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


Technical Cache for Online Apps - SMP 3.0 OData SDK SP06 (Android)

$
0
0

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

publicvoid 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



      How to use Push Notifications with HANA Cloud Platform mobile services

      $
      0
      0

      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

      Integration Gateway: Understanding REST data source [4]: Filtering

      $
      0
      0

      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:

       

       

           defclass 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)){

                thrownew 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");

                thrownew 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:

                     thrownew 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;

           }

       

           returnnull;

      }

       

       

       

      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:

       

       

      defvoid 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

      Integration Gateway: Understanding REST data source [5]: using XML parser

      $
      0
      0

      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 tutorialsyou 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");

                        returnnull;

           } catch (SAXException e) {

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

                        returnnull;

           }

      }

       

       

       

       

      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;

                } elseif(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.");

                        returnnull;

           }

       

           // create the transformer

           TransformerFactory transformerFactory = TransformerFactory.newInstance();

           Transformer transformer = null;

           try {

                transformer = transformerFactory.newTransformer();

           } catch (TransformerConfigurationException e) {            

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

                        returnnull;

           }

       

           // 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");

                        returnnull;

           }

       

           // 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");

                        returnnull;

           }

       

           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.

       

       

      defvoid 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

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

      */

       

      defvoid 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.");

                        returnnull;

           }

       

           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");

                        returnnull;

           } catch (SAXException e) {

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

                        returnnull;

           }

      }

       

      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;

                } elseif(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.");

                        returnnull;

           }

       

           // create the transformer

           TransformerFactory transformerFactory = TransformerFactory.newInstance();

           Transformer transformer = null;

           try {

                transformer = transformerFactory.newTransformer();

           } catch (TransformerConfigurationException e) {            

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

                        returnnull;

           }

       

           // 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");

                        returnnull;

           }

       

           // 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.");

                        returnnull;

           }

       

           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

      Integration Gateway: Understanding REST data source [6]: using JSON parser

      $
      0
      0

      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.

       

       

      defvoid 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

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

      */

       

       

      defvoid 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 backendpayload

           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

      Debugging Custom Script in Integration Gateway

      $
      0
      0

      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

      Enabling cookie based SSO Authentication for BEP (Exposed as OData services) using integration gateway in SMP 3.0

      $
      0
      0


      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/cockpitand 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/Adminand 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>

       


       

       

       



       
       
       
       
       
       
       
       
       
       
       
       






      How to Configure SSO for SMP 3.0 using Cookie Based Authentication

      $
      0
      0

      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 )

       

      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

       

       

       



      How to create a SAP Netweaver Gateway Project to Expose RFC as OData Services

      $
      0
      0

      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

      SMP 3.0: Agentry Integrated Test Environment

      $
      0
      0

      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

      Integration Gateway: Preparing my Eclipse for Javadoc experience

      $
      0
      0

      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.

      Integration Gateway: Understanding REST data source [7]: READ - xml

      $
      0
      0

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

       

      In the previous tutorials, we’ve learned how to deal with REST services as data source for an OData service in SMP.

      These tutorials were based on the QUERY operation.

      Now, since SMP SP06, the READ operation is supported as well.

      This blog explains how to do the required steps.

       

      This blog is a follow-up of the tutorial for the QUERY operation, so I’ll skip some of the basic explanations.

       

      Why is this blog necessary?

      For the READ operation, you have to know about 2 specialties:

      • How to define the relative URL in Eclipse
      • How to write the script to meet the expected structure of the response.

       

      Please find attached the source files used in this tutorial.

       

       

       

      Prerequisites

       

      I expect that you've gone through my previous tutorial, explaining REST data source – QUERY operation – XML payload

       

      Prerequisites are the same:

      • Eclipse with SAP Mobile Platform Tools installed
      • SMP SP06
      • Basic knowledge about OData provisioning using the Integration Gateway component of SMP

       

       

      Preparation

       

      REST Service

       

      For this tutorial, we’ll be using the following REST service as example  service for the data source:

       

      http://www.thomas-bayer.com/sqlrest/CUSTOMER

       

      Please find some info at http://www.thomas-bayer.com

      The service is free and doesn’t require any registration.

       

      The reason for choosing this service is that it supports READ of single entries.

      The URL for reading a single entry is e.g.

      http://www.thomas-bayer.com/sqlrest/CUSTOMER/1

      where the 1 is the identifier of a customer entry

       

      Destination

       

      In your SMP server, create a  Destination that points to this REST service.

      Please refer to the screenshot for the settings:

       

       

       

      After saving the destination, try the Test Connection button: it should be successful.

      If you get an error message on connection test, you might consider the following:

       

      Note that you might need to enter proxy settings in your SMP:

      https://localhost:8083/Admin/ -> Settings-> System

       

      Note that you might need to restart the SMP server after changing the proxy settings.

       

       

       

      OData Model

       

       

      Create Project

      Create project of type SAP Mobile Platform OData Implementation Project.

       

      Define OData Model

      Within the OData Modeler, create one EntityType that looks as follows:

       

       

       

       

       

      Bind data source

      Select the odatasrv file and from the context menu choose "Set Data Source".

       

      In the wizard, you have to first select the Data Source as REST and then select the "Read" operation for the EntitySet "Customers"

       

       

       

       

      Note that the "Read" option is only available since SMP SP 06

       

      Click Next and specify the following relative URL and press "Finish".

       

      /sqlrest/CUSTOMER/{ID}

       

       

      How are we supposed to build this relative URL?

       

      Here we have to understand:

      As we already know, any REST service is free to use its own patterns for providing resources.

      Since no READ operation is explicitly specified for REST services (like for OData), any REST service out there in the internet can implement it in its own preferred way.

      Which means that our SMP server cannot deduct the URI to find a single entry, only from the service URL (of the REST service)

      So it's us who have to provide the information (about how the REST service does the READ) to the SMP server.

       

      Let’s have a look at our example REST service.

      How is the READ implemented there:

       

      http://www.thomas-bayer.com/sqlrest/CUSTOMER/1

       

      There’s the segment “CUSTOMER” which provides the list of entries

      Afterwards a slash

      Finally a number which is the value of the property "ID" of the thomas-bayer service

       

      Translated into a generic expression:

       

      CUSTOMER/<value-of-ID-field>

       

      (value-of-ID-field means the 1 or 2 or 42 that we can enter at the end of the thomas-bayer URL)

       

      And this is what SMP expects from us:

      A generic expression that provides the key property between curly braces

      So we have to provide the full URI of the READ, but with a variable instead of the concrete value

      But the "full URI" has to be a "relative URL", because it is concatenated with the base URL that is defined in the Destination on the server.

       

       

      Custom Code

       

      After finishing the binding wizard, we’re ready to create the script.

       

      Within the Project Explorer View, select the “Read” node and choose Define Custom Code from the context menu.

      Choose Groovy as language.

       

      Now we have to implement the processResponseData() method.

       

      What do we have to do?

       

      Background

      The REST service that we’re using supports reading of a single entry.

      The URL

      http://www.thomas-bayer.com/sqlrest/CUSTOMER/1

      returns the following response:

       

       

       

      Note that the browser doesn’t display the real full payload (the xml header is hidden), so we have to press “view source” to get the real response payload:

       

       

       

       

       

      In our custom code script, we’re supposed to provide the data in a specific structure, as we’ve learned in the previous tutorials.

      In the case of READ, the expected structure looks as follows:

       

       

      <EntitySet>

          <Entity>

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

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

                <Property3>“value of property3”</Property3>

          </Entity>

      </EntitySet>

       

       

      As you can see, the structure is the same like in the QUERY scenario.

      Note that for reasons of consistency, the structure contains the EntitySet, although the payload of the READ operation doesn't contain it.

       

      In our custom code script, we have to modify the structure of the REST response to match the structure that is expected by the SMP server.

       

       

      Intermediate step

      For those of you who like to do an intermediate step: before we start to generically modify the response of the REST service in order to meet the expected structure, we can provide a hard-coded response (as we did in the first REST tutorial).

      Such implementation looks as follows:

       

       

      def Message processResponseData(message) {

        

         message.setBody("<Customers>"+

                                               "<Customer>"+

                                                     "<ID>111</ID>"+

                                                     "<FIRSTNAME>Jack</FIRSTNAME>"+

                                               "</Customer>"+

                                        "</Customers>");

                

         return message;

      }

       

       

      After generate, deploy, configure and run the service, you should see the result in the browser.

      Check the section below for info about how to run the service.

       

      Note:

      In this hard-coded response, we’re setting less properties than defined in our OData model. But this is OK, at runtime the remaining properties will be empty.

      Remember: Our OData model can be bigger than the REST-service, but it must not be smaller (which would cause exceptions at runtime).

       

       

      Implement the script

      Now let’s do the generic implementation:

      Modify the structure of the REST response to match the structure that is expected by SMP.

      Fortunately, in our example the REST service payload is very similar to the expected structure.

       

      In detail, what we have to do, is the following:

       

      • Remove undesired xml header:
        <?xml version="1.0"?>
      • Remove undesired attributes of the entry tag
        <CUSTOMER xmlns:xlink="http://www.w3.org/1999/xlink">
      • Rename the entry name to match our EntityType name
        </Customer>
      • Surround the entry with opening and closing tags of our EntitySet name
        <Customers>
      • Add the opening Customer tag
        <Customer>


       

       

      This is the simple implementation of the method:

       

       

      def Message processResponseData(message) {  

          String restResponse = message.getBody();

        

             int index = restResponse.indexOf("xlink\">")  + 7;

          restResponse = restResponse.substring(index);

          restResponse = restResponse.replaceAll("</CUSTOMER>", "</Customer>");

          restResponse = "<Customers><Customer>" + restResponse + "</Customers>" ;

       

          message.setBody(restResponse);

        

             return message;

      }

       

       

      Result

       

      After doing generate&deploy in our Eclipse project, change to your browser and open the Management Cockpit.

      Assign the destination that we’ve created in the preparation step.

      Invoke our OData service.

       

      Note that we haven’t implemented the QUERY, so we have to directly invoke a URL for READ of a single entry:

      e.g.

      https://localhost:8083/gateway/odata/SAP/<your_service_name>/Customers('42')

       

      The result is:

       

       

       

      Summary

       

      There should be 2 basic learnings from this tutorial:

       

      • How to provide the relative URL when specifying the REST data source
      • How to write the custom code to meet the expected structure for READ operation in Integration Gateway

       

      Those of you who have followed my blog explaining how to use xml parser for creating the expected structure may ask: Is there a follow-up blog explaining how to do this for the READ operation?

      Well, I’m not intending to create such a blog, since the procedure is exactly the same.

       

       

       

      Links

       

      The prerequisite tutorial that does the same like this blog, but for QUERY operation:

      Introduction in REST data source part 3: Understanding the return structure in xml

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

       

      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 data source 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 OData provisioning in SMP:

      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

      Viewing all 370 articles
      Browse latest View live


      <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>