1 2 3 27 Previous Next

SMP Developer Center

395 Posts

Hi,

we put great new features into this release

 

  • Document Service Integration
  • Event log Atom feed
  • Admin UI Overhaul
  • Fiori Mobile Packager improvements

 

Document Service Integration

As you may know there is a service available on HANA Cloud Platform that allows you to store and retrieve unstructured data, like images, movie files or office documents. This service is called Document Service (more information) and implements the Content Management Interoperability Services (CMIS) protocol to upload, retrieve and manage files.

With our integration feature, we make this service available to your mobile application in order to provide your app the ability to leverage extended document management features.

With the Document Service Integration feature you can upload, share, distribute, retrieve static content from and towards your mobile users. Here are some example use cases:

 

Cloud Sync

Let the user put products in his shopping cart on device A.

Upload the shopping card into your repository.

Download the shopping cart document (maybe a JSON file) to device B of that user.

Serve Static Content

You upload a lot of product images into your repository

The mobile app retrieves the product images directly from your repository.

Optional: Download product images to the device and load them into UI from local storage in order to increase the UX (decreased download times).

Private File Storage

Create user specific folders to let them store static content on their own, only available to that specific user. And let CMIS manage the usage rights for you.

 

I think you will come up with even more cool use cases.

Let us know how you use the Document Service integration feature!

 

This is how it works looks like from an overview perspective:

MobileDocuments.png

The CMIS workbench is an open source desktop application that can be used by HCP mobile service administrators to access the app specific repository without prior registration as a mobile app on HCPms (meaning you don’t need an APPCID as an administrator), while mobile apps need to register first to access the repository. This allows the admin to manage the content of the repository. Maybe he wants to upload new product images or wants to delete old content.

 

The blue communication lines describe CMIS protocol related communications.

Before I forget: There is no need to configure anything in the HCP mobile services cockpit to enable this feature. Every configured app gets its own storage folder in the repository.

 

Event log Atom feed

This one is for the administrators among us who are interested in supportability features.

The current log files include stack traces and other internal information not really suitable for a mobile administrator.

The event log framework that we introduced captures runtime events that are really interesting for mobile administrators and enriches them with important information.

In order to access the event log, we provided an OData RSS feed for administrators. There is no way to access the event log from our Admin UI (read more about it in the next section).  Event though you might think this is not really convenient, this has been some advantages – and we can put an UI on it afterwards if necessary.

First let’s take a look on how you access the event log:

In your browser you navigate to:

 

https://[host]/ mobileservices/Admin/EventLog.svc/EventLogs

 

for the trial landscape it would be:


https://hcpms-S0000001trial.hanatrial.ondemand.com/ mobileservices/Admin/EventLog.svc/EventLogs

 

if S0000001 would be your SCN ID.

 

This would list a maximum of 1000 log entries in OData Atom format, which is potentially a little too much information. Before we discuss how to apply a filter on the data, let’s take a closer look to the event important properties:

 

Property

Description

Example Value

ID

The ID of this event

https://[host]/Admin/EventLog.svc/EventLogs(CorrelationId='0be83ade-731a-4a31-9559-cc9aca3b1cdf',Sequence=1453710994677011L)

ApplicationConnectionId

The ApplicationConnection ID of the user/device/app that triggered this event

686a0af9-7e9d-4bf0-b811-0177bf4a4085

ApplicationId

 

ODataSCCBasic_Basic

CorrelationId

GUID that describes one request/response flow

0be83ade-731a-4a31-9559-cc9aca3b1cdf

DeviceType

null, if device type is unknown.

null

LocalizedMessage

The actual message about what happened

Request for unknown Application Connection ID 686a0af9-7e9d-4bf0-b811-0177bf4a4085 failed.

Type

Component where the event was triggered

Registration

UserName

User id of the registered user responsible for this event

P85457478438

LogLevel

 

WARN

 

As you can see this is more helpful than the log UI we had before. The CorrelationId will be returned by the server in every request. Let’s look at an example:

In Postman (a Google Chrome plugin) I created the following registration request and sent it to the server:

request.png

 

The server responded with a http status code of 403 – forbidden. But I was sure that I provided the correct basic authentication credentials.

To identify the root cause of this issue, I copy the CorrelationId from the server response and put it as a filter criterion to the EventLog filter, directly into the browsers address bar:

 

https://mobilepiat-ae3be903b.hana.ondemand.com/Admin/EventLog.svc/EventLogs?$filter=CorrelationId eq ‘e25d1fe9-6655-460a-939c-9edf25b53685’

 

AtomFeed.png

 

Here I can clearly see that the application could not be created because it already exists. Well, this is the typical thing that happens when I use Postman, because it handles cookies automatically and I already sent the ApplicationConnectionID along with my POST request.

 

Since this is a real OData feed, feel free (I couldn’t resist to use this alliteration) to combine various filter criteria.

To get a summary of the last 100 events use:

 

https://mobilepiat-ae3be903b.hana.ondemand.com/Admin/EventLog.svc/EventLogs?$orderby=TimeStamp%20desc&$select=LocalizedMessage&$top=100

 

 

Admin UI Overhaul

Let’s start with an image:

 

AdminUI.png

As you can see, the Mobile Services admin UI now follows the design guides of HCP Cockpit. But despite the good looking UI we also improved the navigation flows. For example, you can now navigate from Applications to Logs with a single click. In the “old” admin UI you always were forced to navigate back to your “Home” screen in order to move to another section.

Even more convenient is the “multi-tab” behavior in the application creation screen, when you are about to create a new application configuration. You can now freely switch back and forth between tabs before you actually save your work.

 

I also like the new list view of applications and the “Filter applications” input field on top, that let’s you quickly find your app configurations – actually not an issue on the trial landscape.

 

Once the admin UI has filled its cache it should be much more responsive as well.

 

I hope you enjoy the new style.

 

Please let me know what you think about this release and how you are going to use these features.

 

Have fun,

Martin Grasshoff

Many months back when I IOS Version was 7 I had written a cross Platform Mobile application in C# for Order to Cash Process -

Order to Cash Mobile Application using Xamarin Forms & C#   After that Apple came up with IOS 8, IOS 9 and now 9.2  - During this time due to changes to 64 bit most of theold libraries could not be used.  I had in the mean time also written a blog on SAP App development using SWIFT for Plant Stock

Iphone App in Swift Xcode 6.3 for SAP Plant Stock  - The old cross platform app was still working on Android and hence I wanted to write this app in swift so that uses with version of IOS can use this app - The back end logic and design is the same as descripted in my earlier blog - The UI was changed and

rewritten in SWIFT.   The way I have invoked SAP from Swift is same as explained in my earlier blog ( mentioned above).  The images below are the screen shots of the app.

otcBlog1.png

 

The flow of Logic is as follows - Login - List of Sales Org - List of Distribution Channels - List of Division - List of Customers on the Customer Screen you can select a customer and choose Add(+) button to create a new Sales order. The left Icon on Customer screen gives list of orders - Select an order - order details -

on the order detail screen - When you enter delivery Qty and Create Delivery - From the Customer Screen you can choose to Create Billing Document and enter the Delivery document to create the Billing -  This app should be on the app stores for free download on the app stores for users to try out like my other apps

otcBlog2.png

In the previous parts (first and second) of this tutorial, we’ve created a Java bundle and an OData service based on it. And we've verified that the OData service works fine and can be consumed.

Now we’d like to take the opportunity and use Eclipse to debug our Java code.

 

 

 

Overview

 

Part I

1. Prepare SMP server

2. Prepare Eclipse

3. Create the OSGi bundle

    3.1. Create Plug-In project

    3.2. Implement DataProvider class

    3.3. Implement Activator class

    3.4. Deploy the bundle to SMP

    3.5. Verify the deployed bundle


Part II

4. Create the OData service

    4.1. Implement the QUERY operation

    4.2. Adding $skip and $top capability

    4.3. Implement the READ operation

5. Test the OData service

 

Part III

6. Debug the Java code

    6.1. Start SMP server in debug mode

    6.2. Connect from Eclipse to SMP

    6.3. Debug the Java code in Eclipse

7. Summary

8. Links

 

Part IV

9. Test the API automatically

    9.1. Create test fragment

    9.2. Create JUnit tests

    9.3. Run the tests

    9.4. Summary

 

 

 

 

 

6. Debug the Java code

 

6.1. Start SMP server in debug mode

 

In order to be able to debug a bundle that is running in SMP server, we have to start the SMP server in debug mode.

 

- In case your SMP is already running, stop it

- Open command prompt and navigate to the folder <SMP_install_dir>/Server

  This folder contains a batch file that is used to start the sever and has name go.bat

- On the command prompt, type the following command

 

go -debug 8000

 

 

 

 

  This command tells the server to start in debug mode and to listen to connection on port 8000

  Press enter and wait until the following message is printed to the command prompt

 

 

 

 

6.2. Connect from Eclipse to SMP

 

After SMP server is up and running, we can connect from Eclipse to the debug-port

 

From the Eclipse main-menu, go to

Run -> Debug Configurations…

Select “Remote Java Application” and click on the “New” button in the dialog’s tool bar

 

 

 

As a result, a new Launch Configuration is created and the details can be entered.

In the “Connect” tab, we have to

- enter a name for the Launch Configuration, this can be any arbitrary name

- choose the Eclipse-project that we want to debug

- in the “Connection Properties” field, enter the SMP server and the debug port as specified in the previous section

 

 

 

In the “Source”-tab, we have to specify, where the sources that we want to debug, are located.

Press “Add”, select Java Project, then select the project that contains the Java files, in our example it is called com.example.dataprovider

(as we created in the first part of this tutorial)

 

 

 

Note:

Actually, in our example, it isn't necessary to manually add the dataprovider-project to the “Source Lookup Path”, because it is already contained in the “Default” folder, because we have specified it as project in the “Connect” tab.

 

That’s it for the settings of the Launch Configuration, which is automatically saved.

 

After pressing “Debug”, Eclipse connects to the specified host and port.

In the “Debug” perspective, you can see the connection.

(The "Debug" perspective can be opened via Window->Open Perspective->Other...)

 

 

 

 

From here, you can use the toolbar button to “disconnect” from the server, i.e. terminate your debug-session.

 

Note that the Launch Configuration doesn’t start or stop the server, it only launches a connection to the SMP server.

 

 

If you get an error message telling that Connection was refused, then you’ve probably forgot to start SMP in debug mode

 

 

 

 

6.3. Debug the Java code in Eclipse

 

Now that the SMP server is running in debug mode and Eclipse is connected to it, we can start debugging.

Open the DataProvider class and set some breakpoints, e.g. in the first line of the getAllProducts() method.

Tip: you can place the cursor in the line and then press Ctr+Shift+B

 

 

 

Then open a browser and invoke the ProductSet URL:

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE/ProductSet

 

Afterwards, change back to Eclipse and you’ll see:

- the execution is paused at the specified position

- the “Variables” View allows to inspect the available objects

- the “Display” View  (open it via Window->Show View) allows to directly type and execute Java statements

- the shortcut Ctrl+Shift+I allows to “inspect” the selected object or statement

etc

 

 

 

 

This is the magic moment and you should take a breath and enjoy that your dreams have come true…

 

 

 

 

Summary

 

Up to now, in the series of blogs forming a tutorial illustrating how I like to work in the area of OData services based on Integration Gateway in SMP 3.0, I’ve shown my main points:

 

- scripting is done in the OData project, but whenever it gets complex, move the code to a separate bundle

- advantages are the enhanced coding support in the editor and the debugging capabilities.

 

 

In part 3 (this page), we’ve learned how to debug the code that we’ve written in the bundle.

 

 

You may have felt the disadvantage of this way of working: quite some overhead with respect to building and deploying the bundle.

In the last part of this tutorial, we’ll learn how to overcome it.

 

 

 

 

Links

 

Installing SMP toolkit in Eclipse:

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

 

The series of tutorials about the REST data source. Many implementation details are also relevant for the "Custom Code" data source.

Integration Gateway: REST data source, overview of Blogs

 

Introduction to “Custom Code” data source:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/10/26/integration-gateway-understanding-custom-code-data-source-1-read-operations-very-simplified

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

 

This last part of our tutorial is an optional step to show how we can test our DataProvider bundle automatically.

See part 1 for creation of the bundle.

 

In our example scenario it makes sense to have automated tests, even more than usually.

Reason: in our DataProvider bundle, we’re exposing an API that we’re using from our OData service project.

During development, we have to build the bundle, deploy to SMP, and we have to generate&deploy the OData service, which is a little tedious.

Therefore, it makes sense to use this API prior to deployment to SMP

Furthermore, we can ensure with our automated tests that the API works as expected and that changes won't break the OData service.

 

Note:

This test bundle is not expected to be deployed to SMP

 

 

 

 

 

Overview

 

Part I

1. Prepare SMP server

2. Prepare Eclipse

3. Create the OSGi bundle

    3.1. Create Plug-In project

    3.2. Implement DataProvider class

    3.3. Implement Activator class

    3.4. Deploy the bundle to SMP

    3.5. Verify the deployed bundle


Part II

4. Create the OData service

    4.1. Implement the QUERY operation

    4.2. Adding $skip and $top capability

    4.3. Implement the READ operation

5. Test the OData service

 

Part III

6. Debug the Java code

    6.1. Start SMP server in debug mode

    6.2. Connect from Eclipse to SMP

    6.3. Debug the Java code in Eclipse

7. Summary

8. Links

 

Part IV

9. Test the API automatically

    9.1. Create test fragment

    9.2. Create JUnit tests

    9.3. Run the tests

    9.4. Summary

 

 

 

9. Test the API automatically

 

What are we going to do?

- We want to create a test class with methods that call the DataProvider just like it will be done by the OData service

- We want these test methods to be implemented as jUnit tests

- We want to run all jUnit tests with one click

- We want to have the test code separated from the DataProvider bundle, such that the test code isn’t deployed to SMP

 

 

 

9.1. Create test fragment

 

Create Fragment project

A Fragment in terms of OSGi is like a special kind of Bundle. It has an own ID and own content. The difference is that it requires a host, which is another bundle.

As such, a fragment cannot live alone.

On the other side, the host doesn’t require the fragment.

Therefore, a typical use case for a fragment is test code.

A fragment shares the classloader of the host bundle, as such it has the great advantage that the test code can access classes that are not meant to be public.

 

In the following section, we will create a fragment in Eclipse that defines our DataProvider bundle as host.

 

 

In Eclipse main menu, go to

File->New->Project->Plug-in Development->Fragment Project

Press "Next" and give a project name, ideally the name of the host bundle with suffix “test”.

In our example:

com.example.dataprovider.test

 

 

 

After pressing "Next", provide the data as desired.

The Host Plug-in is the DataProvider bundle that we’ve created in the first part################## of our tutorial:

com.example.dataprovider

 

 

 

 

Press "Finish" and don’t change to "Plugin Development perspective".

The project skeleton is generated and the editor for the MANIFEST.MF file is opened.

 

 

Add dependencies

In our fragment, we inherit the dependencies of the host bundle, so we don’t need to add the dependency to Olingo and Integration Gateway api libraries.

However, since we’re acting like the OData service implementation project, we have to add some additional dependencies, which we’ll need to get our test code running:

 

 

 

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Test fragment for Example DataProvider
Bundle-SymbolicName: com.example.dataprovider.test
Bundle-Version: 1.0.0
Bundle-Vendor: ExampleVendor
Fragment-Host: com.example.dataprovider;bundle-version="1.0.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Require-Bundle: olingo-odata2-core;bundle-version="2.0.4",
com.sap.it.public.api;bundle-version="1.0.0",
org.apache.camel.camel-core;bundle-version="2.14.1",
com.sap.gw.rt.script.engine.api;bundle-version="1.14.1",
com.sap.it.commons;bundle-version="1.14.0",
com.sap.it.commons.logging.slf4j;bundle-version="1.14.0",
org.slf4j.api;bundle-version="1.7.2"












 

 

Note:

The versions shown above correspond to SMP SP10

 

 

Save the editor

 

 

Adapt build path

We want to write JUnit tests, therefore we need to compile against the JUnit framework library.

Remember that we changed the target platform in Eclipse (tutorial part 1)?

The fragment that we’ve created compiles against SMP, as we’ve configured in part 1, but on SMP server we don’t expect the JUnit-test-framework to be available, so we have to workaround the situation.

There are several options, I think the easiest solution is:

JUnit is contained in our Eclipse IDE, and even if we don’t change the target platform, we can still add the JUni lib manually to the build path of our fragment project.

 

Select your fragment project and from the context menu choose

Build Path -> Configure Build Path…

 

 

 

The project properties dialog is displayed and the “Java Build Path” property is selected.

On the right pane, select the “Libraries” tab.

Press the button “Add Library…”

 

 

On the subsequent popup, select “JUnit”, press “Next” and choose "JUnit 4" as library version.

After pressing “Finish”, this library is added to the build path of your project.

This (Eclipse-) library contains the junit jarfile

 

 

 

 

9.2. Create jUnit tests

 

Now we can start writing our test code.

 

Generate JUnit Test Case

 

Select your fragment project and from the context menu, choose

New->Other->JUnit->JUnit Test Case

 

Select JUnit 4, specify the values as shown below and the "Class under test" is the only class that we’ve created in part 1, the DataProvider

 

 

 

After pressing “Next”, you can select which methods of the class under test should be tested.

We select all the API-methods that are meant to be used by the OData service:

 

 

 

After pressing “Finish”, the Java class and the methods are generated.

 

 

Create helper methods

 

Before we start writing the test code, we create a helper method that creates a Message object which we need for the DataProvider instance.

This is the code:

 

 

private Message createMessage(Integer topNumber, Integer skipNumber, String productID){
    UriInfoImpl uriInfo = new UriInfoImpl();
    // required for testTop
    if(topNumber != null){
        uriInfo.setTop(topNumber);
    }
    // required for testSkip
    if(skipNumber != null){
        uriInfo.setSkip(new Integer(skipNumber));
    }
    // required for testRead
    if(productID != null){
        KeyPredicateImpl keyPredicate = new KeyPredicateImpl(productID, null);
        List<KeyPredicate> keyPredicates = new ArrayList<KeyPredicate>();
        keyPredicates.add(keyPredicate);
        uriInfo.setKeyPredicates(keyPredicates);
    }
    Message message = new Message();
    message.setHeader("UriInfo", uriInfo);
    return message;
}










 

After pasting it into your DataProviderTest class, you’ll see error markers.

Press Ctrl + Shift + O in order to let Eclipse add the required imports.

But the code will still have error markers.

The reason is that the code uses internal classes of the Olingo core plugin, which is not public API, as such not visible from our fragment.

For our test code, we don’t mind using internal classes, so we go ahead and remove the acess rule check:

 

Select the fragment project and from the context menu, choose

Properties->Java Compiler->Errors/Warnings

On the properties page, select “Enable project specific settings”, then assign “Warning” to “Forbidden reference (access rule)” and press OK.

 

 

With this setting, we allow ourselves to use the restricted internal classes.

However, it is enabled only for the test-project, because we've applied the setting on project-level, not for the whole Eclipse.

 

Now that we can use the internal class, we have to add the required import statement to our DataProviderTest class:

 

import org.apache.olingo.odata2.core.uri.*;









 

After that, we can save and the error markers will be gone.

 

If the warnings in the editor bother you too much, you can either change the above setting to “Ignore”, or you can add the following annotation to the method:

 

@SuppressWarnings("restriction")
private Message createMessage(Integer topNumber, Integer skipNumber, String productID){









 

 

We need another helper method, which creates some dummy data, as such allows us to test independent from the backend data.

 

private List<Map<String, String>> getTestData(int prodAmount){
    List<Map<String, String>> productList = new ArrayList<Map<String, String>>();
    // create some sample product entities as HashMaps
    for (int i = 1; i <= prodAmount; i++) {
        String index = Integer.toString(i);
        Map<String, String> productMap = new HashMap<String, String>();
        productMap.put("ID", index);
        productMap.put("Name", "TestName" + index);
        productMap.put("Description", "TestDescription" + index);
        productList.add(productMap);
    }
    return productList;
}









 

 

 

Implement testGetAllProducts()

 

In the present tutorial, we’re really only doing the simplest approach to showcase the testing.

So for the testGetAllProducts, we only invoke the respective method of the DataProvider class.

In a productive environment, the DataProvider class would allow injection in order to support the test, and we would add more fine-granular checks and asserts

 

    @Test
    public void testGetAllProducts() {
        Message message = createMessage(null, null, null);
        DataProvider dataProvider = new DataProvider(message);
        List<Map<String, String>> allProducts = dataProvider.getAllProducts();
        assertNotNull("Calling getAllProducts shouldn't return null", allProducts);
    }









 

 

Implement testGetProduct()

 

In this test, we simulate that a READ URL for the product with ID 1 has been executed and we check that the product has been found

Then we simulate a READ for productID 6, which doesn’t exist, and we check that the result is null.

 

    @Test
    public void testGetProduct() {
        Message message = createMessage(null, null,"1");
        // start the positive test
        DataProvider dataProvider = new DataProvider(message);
        Map<String, String> product = dataProvider.getProduct();
        assertNotNull("Product expected to exist", product);
        // negative test
        Message invalidMessage = createMessage(null, null,"6"); // this ID doesn't exist
        // start the positive test
        DataProvider dataProvider2 = new DataProvider(invalidMessage);
        Map<String, String> productNull = dataProvider2.getProduct();
        assertNull("Expected to be null", productNull);
    }









 

 

Implement testHandleSkip()

 

In this test method, we simulate a URL with $skip=1

We create test data with 4 Products

Then we check that the result list contains 3 entries

 

    @Test
    public void testHandleSkip() throws Exception{
        Message message = createMessage(null, 1, null);
        List<Map<String, String>> allProducts = getTestData(4);
        // start the test
        DataProvider dataProvider = new DataProvider(message);
        List<Map<String,String>> skipProducts = dataProvider.handleSkip(allProducts);
        assertEquals(3, skipProducts.size());
    }








 

 

Implement testHandleTop()

 

In this test method, we simulate a URL with $top=1

We create test data with 4 Products

Then we check that the result list contains 1 entry

 

 

    @Test
    public void testHandleTop() throws Exception{
        Message message = createMessage(1, null, null);
        List<Map<String, String>> allProducts = getTestData(4);
        // start the test
        DataProvider dataProvider = new DataProvider(message);
        List<Map<String,String>> topProducts = dataProvider.handleTop(allProducts);
        assertEquals(1, topProducts.size());
    }








 

The full source code

 

 

package com.example.dataprovider.test;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.olingo.odata2.api.uri.KeyPredicate;
import org.apache.olingo.odata2.core.uri.KeyPredicateImpl;
import org.apache.olingo.odata2.core.uri.UriInfoImpl;
import org.junit.Test;
import com.example.dataprovider.DataProvider;
import com.sap.gateway.ip.core.customdev.util.Message;
public class DataProviderTest {
    @Test
    public void testGetAllProducts() {
        Message message = createMessage(null, null, null);
        DataProvider dataProvider = new DataProvider(message);
        List<Map<String, String>> allProducts = dataProvider.getAllProducts();
        assertNotNull("Calling getAllProducts shouldn't return null", allProducts);
    }
    @Test
    public void testGetProduct() {
        Message message = createMessage(null, null,"1");
        // start the positive test
        DataProvider dataProvider = new DataProvider(message);
        Map<String, String> product = dataProvider.getProduct();
        assertNotNull("Product expected to exist", product);
        // negative test
        Message invalidMessage = createMessage(null, null,"6"); // this ID doesn't exist
        // start the positive test
        DataProvider dataProvider2 = new DataProvider(invalidMessage);
        Map<String, String> productNull = dataProvider2.getProduct();
        assertNull("Expected to be null", productNull);
    }
    @Test
    public void testHandleSkip() throws Exception{
        Message message = createMessage(null, 1, null);
        List<Map<String, String>> allProducts = getTestData(4);
        // start the test
        DataProvider dataProvider = new DataProvider(message);
        List<Map<String,String>> skipProducts = dataProvider.handleSkip(allProducts);
        assertEquals(3, skipProducts.size());
    }
    @Test
    public void testHandleTop() throws Exception{
        Message message = createMessage(1, null, null);
        List<Map<String, String>> allProducts = getTestData(4);
        // start the test
        DataProvider dataProvider = new DataProvider(message);
        List<Map<String,String>> topProducts = dataProvider.handleTop(allProducts);
        assertEquals(1, topProducts.size());
    }
    /* HELPER */
    // create a dummy message object. Access to internal packages is required
    private Message createMessage(Integer topNumber, Integer skipNumber, String productID){
        UriInfoImpl uriInfo = new UriInfoImpl();
        // required for testTop
        if(topNumber != null){
            uriInfo.setTop(topNumber);
        }
        // required for testSkip
        if(skipNumber != null){
            uriInfo.setSkip(new Integer(skipNumber));
        }
        // required for testRead
        if(productID != null){
            KeyPredicateImpl keyPredicate = new KeyPredicateImpl(productID, null);
            List<KeyPredicate> keyPredicates = new ArrayList<KeyPredicate>();
            keyPredicates.add(keyPredicate);
            uriInfo.setKeyPredicates(keyPredicates);
        }
        Message message = new Message();
        message.setHeader("UriInfo", uriInfo);
        return message;
    }
    // create some sample test data, in order to not depend on backend data
    private List<Map<String, String>> getTestData(int prodAmount){
        List<Map<String, String>> productList = new ArrayList<Map<String, String>>();
        // create some sample product entities as HashMaps
        for (int i = 1; i <= prodAmount; i++) {
            String index = Integer.toString(i);
     
            Map<String, String> productMap = new HashMap<String, String>();
            productMap.put("ID", index);
            productMap.put("Name", "TestName" + index);
            productMap.put("Description", "TestDescription" + index);
     
            productList.add(productMap);
        }
        return productList;
    }
}






 

 

9.3. Run the tests

 

From the Eclipse main menu, choose Run->Run Configurations

In the Run Configurations dialog, select "JUnit" and from the context menu choose “New”

 

 

 

In the details pane, give an arbitrary name for your Run Configuration.

Select “Run all tests…” and make sure that your test fragment project is entered (otherwise press “Search” to enter it)

The “Test runner” has to be specified as "JUnit 4"

 

 

 

Note:

You can also choose to run only one single test.

This is desired, when not all test methods are finalized yet or if running all test method would take too long time, etc

 

 

After pressing “Run”, the selected tests are executed.

The results are presented in the JUnit View. If this is not visible, it has to be opened via Window->Show View -> Other -> Java -> JUnit

 

 

 

 

 

Note:

In case of problems, a JUnit test can also be debugged, which is done by choosing "Debug" instead of "Run"

 

 

Create Test Suite

 

Let’s quickly cover one last topic.

Usually, you’ll have more than one class and more than one scenario to be covered.

In such cases, you’ll have lots of test case classes and it makes sense to aggregate them in a so-called Test Suite.

Like that, you can run all the test methods in all the test cases with one click.

 

In Eclipse, select your test fragment project and choose from the context menu

New->Other->JUnit->JUnit Test Suite

After pressing “Next”, make sure that "JUnit 4" is selected, enter a package and adapt the Name if desired.

The one and only test class that we have, is already selected, so we can press “Finish”

 

 

 

The generated Test Suite class is opened, we don’t need to change anything in our example.

 

 

 

A quick way to run the tests is to directly click into the editor and from the context menu choose Run As->JUnit Test


 

Now all the tests in the suite are executed.

Again, the result can be viewed in the JUNit view, this time the root node is the suite:

 

 

 

 

 

9.4. Summary

 

In the present blog we’ve shown how we can leverage JUnit for reducing develop-deploy-test-turnarounds.

In our JUnit test-code, we act like the OData service that calls the DataProvider-API and as such we can quickly check if it works as expected and we can enhance the DataProvider bundle without the need of deploying it to SMP.

We’ve taken advantage of the Fragment-concept to develop the JUnit tests.

 

 

We’ve reached the end of this little tutorial in which I’ve illustrated how I like to work in the area of OData services based on Integration Gateway in SMP 3.0

My points are:

- scripting is done in the OData project, but whenever it gets complex, move the code to a separate bundle

- advantages are the enhanced coding support in the editor and the debugging capabilities.

 

In part 1 of this tutorial, we’ve created the bundle and we’ve written all the code required to provide the data and to handle the requests.

At the end, we’ve deployed that bundle to the SMP server and verified that it works as expected

In part 2, we’ve created the OData service implementation project and we’ve delegated all the coding to the bundle. We’ve specified a dependency to that bundle, we’ve deployed the OData service project to SMP server and we’ve verified that the OData service works as expected

In part 3, we’ve learned how to debug the code that we’ve written in the bundle

In part 4 (this page), we’ve learned how to test the bundle without deploying it to the server

 

All these 4 blogs together should significantly enhance the development productivity.

I hope you agree and you’ve enjoyed it!

Or:

 

How to expose Microsoft Excel as OData service

on SMP 3.0

based on Integration Gateway

 

 

 

 

 

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

The Integration Gateway component allows to expose backend data as an OData service, where some concrete data sources are supported  more or less automatically

Since SMP SP09, a new data source has been introduced: the “Custom Code” data source, that gives the developer more freedom to access different data sources (see below for more info).

 

 

In the present tutorial, I’d like to take the opportunity to show how this data source can be used to expose data from Microsoft Excel.

 

 

The goal is to showcase that any data which can be stored anywhere can be exposed via this “Custom Code” data source.

The imaginary scenario:

An office desk of a car manufacturer maintains the list of OBD codes in a Microsoft Excel sheet.

This list of OBD codes should be exposed by an OData service, such that it can be accessed by e.g. a mobile application.

 

 

Learnings:

- programmatically parse an excel sheet

- use the SMP toolkit to create an OData service that exposes the Excel data

 

 

The aim is:

- to showcase how easy it is to expose an excel sheet as OData service and

- the flexibility that the Custom Code data source gives to integration developers

 

As usual, the artifacts can be found attached to this blog.

 

 

 

Prerequisies

 

We will be programmatically reading an excel file. For that purpose we’ll be using the apache POI library, the Java API for Microsoft Documents

In order to make use of it, we need to install it in SMP 3.0 server.

Since the server is based on OSGi, we need the POI library packaged as an OSGi- bundle.

Such a bundle is available in the internet: org.apache.servicemix.bundles.poi-3.6_2.jar

It can be downloaded e.g. from here

http://www.java2s.com/Code/Jar/o/Downloadorgapacheservicemixbundlespoi362jar.htm

 

More Prerequisites:

 

  • SAP Mobile Platform 3.0 SP09 (SMP) installation.
  • Eclipse with SAP Mobile Platform Tools installed
    check this blog for help
  • Basic knowledge about SMP and Integration Gateway
    Check this blog for some introduction
  • Introduction to the “Custom Code” data source here.

 

 

 

Overview

 

 

1. Create the Excel file containing the data

2. Add POI lib to SMP 3.0 server

3. Create an OData service to expose the excel data

  3.1. Create SMP project

  3.2. Create the OData model

  3.3. Create the binding

  3.4. Write the script

4. Test the OData service

5. Summary

6. Links

 

 

 

 

 

1. Create the Excel file containing the data

 

Attached to this Blog you’ll find the Excel file that I’m using in this tutorial.

It contains a list of OBD codes.

Each OBD code contains some additional information, which I’ve added in some further columns.

They’re meant to showcase a bit more of OData capabilities, as we’ll see later

We have

  • the OBD code itself
  • the category of it
  • the short description
  • the long description
  • the URL of the docu
  • the URL of a corresponding video

 

 

The excel sheet looks as follows.

 

 

excelfile.JPG

 

 

 

Download the attached Excel file and copy to your local file system.

In our example, we’re using the location:

 

 

C:\temp\obd

 

 

 

 

 

 

2. Add POI lib to SMP 3.0 server

 

Background

In our present tutorial, we’re going to create an "OData service implementation project" in Eclipse, and in this project we're going to use the apache POI library.

The project will be deployed into SMP server and it will be converted into a bundle.

A bundle can only have dependencies on other bundles, therefore we have to make the POI library available as a bundle in SMP.

We could manually convert the lib into a bundle, but such a bundle is already available in the internet: the apache servicemix bundle

In my tutorial, I’m using org.apache.servicemix.bundles.poi-3.6_2.jar

 

Install POI in the server

I’ve downloaded it from the internet, e.g. here to my local file system.

 

Then it has to be installed into SMP.

There are several ways of doing that.

The easiest one is:

Copy the jar file to the pickup folder of your SMP installation.

This is located at

 

<SMP_installdir>\Server\pickup

 

For example:

 

 

 

The SMP server supports the so-called "hot deployment", so the server will notice that you’ve copied a bundle into the pickup folder and it will include it in its OSGi runtime on the fly.

 

 

 

Note:

This way of installing (hot deployment) is used rather for testing purpose. For productive environment, use the command line to really “install” the bundle into OSGi.

For more information about OSGi and installation procedure, you may have a look at my OSGi Blogs: http://scn.sap.com/docs/DOC-52346

and http://scn.sap.com/docs/DOC-52455 and http://scn.sap.com/docs/DOC-52456

 

 

After copying the jar file to the pickup folder, you should open the .state folder and check if the deployment has been successful, as shown by the below screenshot:

 

 

In case of error, you should check the console and also check the SMP error log and the OSGi log.

The reason might be a dependency issue.

 

 

 

3. Create an OData service to expose the excel data

 

Now that the apache POI library is available in SMP, we can use it from within the code that we're going to write in the following chapter.

 

3.1. Create SMP project

 

In Eclipse, go to New->Project->SAP Mobile Platform->SAP Mobile Platform OData Implementation Project

Enter a project name like "OBDService" and a model name like "obdmodel" and press Finish.

 

 

 

3.2. Create the OData model

 

We will keep our OData service simple, so we create only one Entity Type.

Furthermore, we create a Complex Type, just for fun - to show that we can distribute the data

from: 1 row in excel

to: 2 artifacts in OData

 

I mean: alternatively, we could create one Entity Type which has 6 properties. But it is more interesting, to have a Complex Type with 4 properties and use the Complex Type as a complex property in the Entity Type:

 

 

 

 

 

 

 

 

3.3. Create the binding

 

In Eclipse, select the .odatasrv node, choose “Select Data Source” from the context menu and select “Custom Code” as data source.

In our tutorial we’re using Javascript for scripting.

 

 

 

 

 

 

3.4. Write the script

 

In the present tutorial, we’re implementing only the getFeed() method.

That’s enough for the topics that we want to showcase.

 

 

What are we going to do in the script?

Our tasks can be grouped into the following steps:

 

 

1. Get the location of the Excel file

2. Load the Excel file

3. Parse the Excel

4. Store the data in a List of Maps

5. Set the List as response body of our service

 

 

OK, let’s start with the coding.

 

 

1. Read the destination to retrieve the location of the Excel

 

 

Within this section, we’ll learn how to access information from the configured Destination.

Remember:

the Destination is configured in the SMP server and is used to maintain the URL of an ABAP system or the URL of a Database or the persistence unit name of a JPA model.

In our scenario, we assume that the Excel file can be accessed via an HTTP URL.

However, in order to make life easier for testing, we do a little trick:

We store our test-Excel file on our local file system

We create an HTTP destination where we replace the drive symbol C:/ with the http:// protocol

In our script, we obtain the destination and replace the http:// back to C:/

Please note that this is only a workaround that we use in our tutorial. It is of course not an official recommendation.

 

The information about the destination that is configured at runtime, can be accessed from a header.

Then we can ask the destination configuration for the URL:

 

function getQualifiedFileNameFromDestination(message){
    var destinationConfiguration = message.getHeaders().get("DestinationConfiguration");
    if (! destinationConfiguration){
        throw new RuntimeException("The service is not configured with a destination.");
    }
    if (! (destinationConfiguration instanceof HttpDestinationConfiguration)){
        throw new RuntimeException("The service is configured with a destination type. Must be type HTTP.");
    }
    var destinationURL = destinationConfiguration.getDestinationUrl();// URL looks like this: http://temp/obd/OBDCodes.xls
    var fileName = destinationURL.replace("http://", "C:/"); 
    return fileName;
}



































 

This code requires the following import statement:

 

importPackage(com.sap.gateway.core.api.destination.configuration);



































 

 

2. Load the Excel file

 

 

Now that we have the path to the Excel file, we can load it.

We’re using a helper class provided by apache.commons. This is available in SMP 3.0, so we can simply use it.

 

The required imports are:

 

    importPackage(java.io);
    importPackage(org.apache.commons.io);



































 

and the code:

 

function getExcelFileAsStream(fileName){
    var file = new File(fileName);
    var inputStreamExcelFile = FileUtils.openInputStream(file);
    if(inputStreamExcelFile == null){
        log.logErrors(LogMessage.TechnicalError, "Failed to read file in the JS script");
        throw new Exception("Excel file not found at " + fileName);
    }
    return inputStreamExcelFile;
}



































 

 

 


3. Parse the Excel

 

As mentioned above, for parsing the Excel file we’re referencing the POI library, which we have already installed in SMP server.
Now we can use it - and therefore we need the import statement:

 

    importPackage(org.apache.poi.hssf.usermodel);




































First, we load the Excel workbook from the input stream that we’ve created in the previous section.
Since our Excel file has only one sheet, we can obtain it easily.

 

function getExcelSheet(fileName){
    var inputStreamExcelFile = getExcelFileAsStream(fileName);
    var workbook = null;
    try {
        workbook = new HSSFWorkbook(inputStreamExcelFile);
    } catch (e) {
        throw new Exception("Exception occurred while creating HSSFWorkbook from inputStream. Could not read xls file", e);
    }
    var sheet = workbook.getSheetAt(0);
    if (sheet == null) {
        throw new Exception("Excel Sheet not found in ExcelWorkbook");
    }
    return sheet;
}



































The sheet contains the data, so now we can loop over all rows.

 

rowIterator = sheet.rowIterator();
while (rowIterator.hasNext()) {
        var row = rowIterator.next();



































 

We have to care about the first row, which is the header of our table:

 

if (row.getRowNum() == 0){
   continue;
}






Each row can be asked for its cells, according to its order in the Excel worksheet.

 

    var cellObdID = row.getCell(0);



































 

In our Excel, the data type of the rows is String, so we get along with the following code

 

        if(cellObdID.getCellType() == HSSFCell.CELL_TYPE_STRING){
            obdId = cellObdID.getRichStringCellValue().getString().trim();



































 

That’s it for the parsing

 

4. Store the data in a List of Maps

 

After we’ve parsed one row of the Excel sheet, we have the data for one <entry> in the sense of an atom feed of our OData service.
We've stored the value of each of the 6 cells of the row in a variable.
So now we can convert the data (the 6 variables) into the format that is required by the Integration Gateway Framework (IGW).
You should be already familiar with scripting in IGW, so I don’t need to explain.

 

Just a reminder:
At the end, each <entry> of our OData service consists of pairs of propertyName – propertyValue
All properties of the Entity Type that is defined in our OData model are put into one instance of java.util.HashMap
One HashMap is created for every entry of the OData collection
All instances of HashMaps are put into a java.util.ArrayList

As such, a map represents an <entry> and the list represents the <feed>

This ArrayList is at the end returned to the IGW framework by setting it as response body of the message object in the callback method getFeed()

 

One more point to consider.
We’ve defined a Complex Type in our OData model. The Complex Type as well consists of pairs of propertyName – propertyValue.
So we create a second HashMap which represents the Complex Type.   
This instance of HashMap is then set as value to the property of the OData model that has the data type “Complex Type”:

 

obdCodeMap.put("Description", complexTypeObdDescriptionMap);



 

As a reminder, the details about our OData model.

We can see that the type of the "Description" property is specified as Complex Type:

 

 

(Note: the dotted line is not an association, it is the usage)

 

 

 

The code for the nested Maps:

 

// provide data for complex type into Map
var complexTypeObdDescriptionMap = new HashMap();
complexTypeObdDescriptionMap.put("ShortText", shortText);
complexTypeObdDescriptionMap.put("LongText", longText);
complexTypeObdDescriptionMap.put("DocumentationURL", docuUrl);
complexTypeObdDescriptionMap.put("VideoURL", videoUrl);
// provide data for entity type into Map
var obdCodeMap = new HashMap();
obdCodeMap.put("OBDcodeID", obdId);
obdCodeMap.put("Category", category);
obdCodeMap.put("Description", complexTypeObdDescriptionMap);































 

 

And each instance of HashMap that represents an Entity Type is then added to the ArrayList:

 

 

var obdCodeMap = getOBDcodeFromRow(row);
obdCodeList.add(obdCodeMap);































 

 

5. Set the List as response body of our service

 

 

We’ve looped over each row of the Excel file, converted each row into a map and added each map into an ArrayList

The last step is to send the instance of ArrayList back to the framework.

 

 

message.setBody(codes);
return message;































 

 

The complete content of the script

 

 

 

function getFeed(message){
  importPackage(java.util);
  importPackage(java.io);
  importPackage(com.sap.gateway.core.api.destination.configuration);
  importPackage(com.sap.gateway.ip.core.customdev.logging);
  importPackage(org.apache.commons.io); // required for the FileUtils helper method
  importPackage(org.apache.poi.hssf.usermodel); // required for the Excel parser
  // 1st: get the path to the excel file from Destination
  //var fileName = new String("C:/temp/obd/OBDmaintenance_small.xls");
  var fileName = getQualifiedFileNameFromDestination(message);
  // 2nd and 3rd: read the data from the Excel and convert into List of HashMaps
  var codes;
  try {
      codes = readExcel(fileName);
  } catch (e) {
      log.logErrors(LogMessage.TechnicalError, "Failed to get the OBD codes...");
      throw new RuntimeException("Server error. Odata service couldn't get the OBD codes from excel data provider");
  }
  // 4th: set the data as response body and pass it to the framework
  message.setBody(codes);
  return message;
}
/* HELPERS */
function getQualifiedFileNameFromDestination(message){
  var destinationConfiguration = message.getHeaders().get("DestinationConfiguration");
  if (! destinationConfiguration){
      throw new RuntimeException("The service is not configured with a destination.");
  }
  if (! (destinationConfiguration instanceof HttpDestinationConfiguration)){
      throw new RuntimeException("The service is configured with a destination type. Must be type HTTP.");
  }
  var destinationURL = destinationConfiguration.getDestinationUrl();
  // destinationURL will look like this: http://temp/obd/OBDCodes.xls 
  var fileName = destinationURL.replace("http://", "C:/");
  log.logErrors(LogMessage.TechnicalError, "\n===> After replace, the final Excel file path:  " + fileName);
  return fileName;
}
function readExcel(filename){
  var obdCodeList = new ArrayList();
  // get the excel sheet
  var sheet = getExcelSheet(filename);
  if(sheet == null){
      throw new Exception("Failed to obtain Excel sheet.");
  }
  // loop over all rows
  rowIterator = sheet.rowIterator();
  while (rowIterator.hasNext()) {
      var row = rowIterator.next();
      // avoid looping empty undefined rows
      if(row.getRowNum() > sheet.getLastRowNum()){
            break;
      }
      // skip the first row as it contains the header
      if (row.getRowNum() == 0){
            continue;
      }
      var obdCodeMap = getOBDcodeFromRow(row);
      if(obdCodeMap != null){
            obdCodeList.add(obdCodeMap);
      }
  }
  return obdCodeList;
}
function getOBDcodeFromRow(row){
  // note that emtpy cells are returned as null
  var cellObdID = row.getCell(0);
  var cellCategory = row.getCell(1);
  var cellShortText = row.getCell(2);
  var cellLongText = row.getCell(3);
  var cellDocuUrl = row.getCell(4);
  var cellVideoUrl = row.getCell(5);
  var obdId = "";
  if(cellObdID != null){
      if(cellObdID.getCellType() == HSSFCell.CELL_TYPE_STRING){
            obdId = cellObdID.getRichStringCellValue().getString().trim();
      }
  }
  var category = "";
  if(cellCategory != null){
      if(cellCategory.getCellType() == HSSFCell.CELL_TYPE_STRING){
            category = cellCategory.getRichStringCellValue().getString().trim();
      }
  }
  var shortText = "";
  if(cellShortText != null){
  if(cellShortText.getCellType() == HSSFCell.CELL_TYPE_STRING){
  shortText = cellShortText.getRichStringCellValue().getString().trim();
  }
  }
  var longText = "";
  if(cellLongText != null){
      if(cellLongText.getCellType() == HSSFCell.CELL_TYPE_STRING){
            longText = cellLongText.getRichStringCellValue().getString().trim();
      }
  }
  var docuUrl = "";
  if(cellDocuUrl != null){
  if(cellDocuUrl.getCellType() == HSSFCell.CELL_TYPE_STRING){
  docuUrl = cellDocuUrl.getRichStringCellValue().getString().trim();
  }
  }
  var videoUrl = "";
  if(cellVideoUrl != null){
      if(cellVideoUrl.getCellType() == HSSFCell.CELL_TYPE_STRING){
            videoUrl = cellVideoUrl.getRichStringCellValue().getString().trim();
      }
  }
  // provide data for complex type into Map
  var complexTypeObdDescriptionMap = new HashMap();
  complexTypeObdDescriptionMap.put("ShortText", shortText);
  complexTypeObdDescriptionMap.put("LongText", longText);
  complexTypeObdDescriptionMap.put("DocumentationURL", docuUrl);
  complexTypeObdDescriptionMap.put("VideoURL", videoUrl);
  // provide data for entity type into Map
  var obdCodeMap = new HashMap();
  obdCodeMap.put("OBDcodeID", obdId);
  obdCodeMap.put("Category", category);
  obdCodeMap.put("Description", complexTypeObdDescriptionMap);
  return obdCodeMap;
}
function getExcelSheet(fileName){
  var inputStreamExcelFile = getExcelFileAsStream(fileName);
  var workbook = null;
  try {
      workbook = new HSSFWorkbook(inputStreamExcelFile);
  } catch (e) {
      throw new Exception("Exception occurred while creating HSSFWorkbook from inputStream. Could not read xls file", e);
  }
  var sheet = workbook.getSheetAt(0);
  if (sheet == null) {
      throw new Exception("Excel Sheet not found in ExcelWorkbook");
  }
  return sheet;
}
function getExcelFileAsStream(fileName){
  var file = new File(fileName);
  var inputStreamExcelFile = FileUtils.openInputStream(file);
  if(inputStreamExcelFile == null){
      log.logErrors(LogMessage.TechnicalError, "Failed to read file in the JS script");
      throw new Exception("Excel file not found at " + fileName);
  }
  return inputStreamExcelFile;
}
/*
* Other callback methods that have to be implemented
*/
function getEntry(message) {
  return message;
}
function createEntry(message) {
  return message;
}
function updateEntry(message) {
  return message;
}
function deleteEntry(message) {
  return message;
}































 

 

 

4. Test the OData service

 

Deploy the service

 

After we’ve finished the implementation, we can execute “Generate&Deploy Integration Content”  from the context menu of the project.

Once the deployment of the service has finished, we can check that is has been successfully registered in the Admin page of SMP server

 

Create Destination

 

We open the Admin page of our SMP server at

https://localhost:8083/Admin/

Then we navigate to OData services -> Destinations

We press on New to create a new Destination and enter the following details:

 

 

Destination Namean arbitrary name, e.g. EXCELFILE
Destination TypeHTTP
URL

Here we need to give the path and the name of the excel file as we downloaded from SCN and saved on local file system.

Note that the C:\  has to be replaced by http:// and the backslashes have to be replaced by forward slashes.


In our example, we enter the following path as URL:

http://temp/obd/OBDmaintenance_small.xls

 

 

 

 

 

 

 

 

Assign Destination

 

The newly created destination can now be assigned to the service that we have deployed

 

 

 

 

 

Invoke the OData service

 

Now we’re done and we can invoke the service at

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

 

And the list of OBD codes at

 

https://localhost:8083/gateway/odata/SAP/OBDSERVICE;v=1/OBDcodes

 

The service accesses the excel sheet each time you execute the request.

You can now go ahead and modify the excel sheet and you’ll see the changes reflected when you call the service again

 

 

 

 

5. Summary

 

In the present tutorial we’ve learned to use the “Custom Code” data source to connect to a new and different kind of data storage.

The tutorial is based on the Integration Gateway component which comes along with SMP 3.0 SP09.

Additionally, we’ve learned how to programmatically access the Destination information.

And how to programmatically parse an Excel file.

And we've learned how to use ArrayList and nested HashMaps as data structure for providing EntityCollection (getFeed) to Integration Gateway.

 

 

 

6. Links

 

 

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

 

Understanding Custom Code data source part 1: read operations very simplified

 

Understanding Custom Code data source part 2: write operations

 

Understanding Custom Code data source part 3: Function Import

 

Understanding Custom Code data source part 4: example implementation

 

The series of tutorials about the REST data source. Many implementation details are also relevant for the "Custom Code" data source:

Integration Gateway: REST data source, overview of Blogs

 

Homepage Apache POI: http://poi.apache.org/

 

POI bundle download: http://www.java2s.com/Code/Jar/o/Downloadorgapacheservicemixbundlespoi362jar.htm

 

The official documentation of Integration Gateway in SMP can be found via the following navigation:

help.sap.com -> Technology Platform -> SAP Mobile Platform -> SAP Mobile Platform Server SP 10 -> Data Integration -> Integration Gateway

Or use the direct link

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

 

It is the continuation of a tutorial in which we create an OData service that consists of 2 artifacts, a data provider bundle and the OData service itself.

In the first part we’ve created the OSGi bundle that encapsulates all the logic that deals with data, and we’ve deployed it to SMP.

In the present second part, we’re going to create the OData service which uses the API exposed by the bundle.

 

The source code can be found attached to this blog post

 

Note: Again, this blog doesn’t represent the official documentation. It is just my personal way of personal wish of modularization

 

 

 

Overview

 

Part I

1. Prepare SMP server

2. Prepare Eclipse

3. Create the OSGi bundle

    3.1. Create Plug-In project

    3.2. Implement DataProvider class

    3.3. Implement Activator class

    3.4. Deploy the bundle to SMP

    3.5. Verify the deployed bundle


Part II

4. Create the OData service

    4.1. Implement the QUERY operation

    4.2. Adding $skip and $top capability

    4.3. Implement the READ operation

5. Test the OData service

 

Part III

6. Debug the Java code

    6.1. Start SMP server in debug mode

    6.2. Connect from Eclipse to SMP

    6.3. Debug the Java code in Eclipse

7. Summary

8. Links

 

Part IV

9. Test the API automatically

    9.1. Create test fragment

    9.2. Create JUnit tests

    9.3. Run the tests

    9.4. Summary

 

 

 

 

 

 

4. Create the OData service

 

In the previous step, we’ve created an OSGi bundle that allows access to some sample data.

Now we want to expose that data as OData service.

We’ll be using SAP Mobile Platform Toolkit in Eclipse to create an OData service based on Integration Gateway and on the "Custom Code" data source

 

 

Create OData Implementation Project

In Eclipse, choose from the menu

File->New->Project->Other->SAP Mobile Platform -> OData Implementation Project

Provide a project name, e.g. ExampleProject and choose to create a blank model with an arbitrary name, like examplemodel

 

The OData model should look like this:

 

 

 

 

Bind data source

 

In the present tutorial we’re focusing on how to move large part of implementation code into a separate bundle, in order to keep the custom script as slim as possible. So it doesn’t matter which data source we use.

To keep the example quick and simple, we choose the "Custom Code" data source and bind the entity set to it.

 

 

 

Define dependency

Before we implement the script, we want to declare that our OData service bundle depends on the DataProvider bundle.

Background:

As you’ve already understood, the SMP server is based on OSGi. This means that the OData services that we create with the SMP toolkit, at the end of the day are OSGi bundles running in SMP server as well.

During design time, we’re editing a project of type SMP OData Implementation.

When executing the context menu action “Generate&Deploy…”, an archive is generated and deployed to SMP. On SMP, it gets converted to an OSGi bundle and registered in the OSGi runtime.

This can be verified on the OSGi console, just like we’ve done before.

 

In order to add the dependency to our DataProvider bundle, open the MANIFEST.MF file at ExampleProject/META-INF

Press "Add" and select the bundle com.example.dataprovider

 

 

 

Save the editor.

 

 

 

 

4.1. Implement the QUERY operation

 

Now let’s implement the script.

Open the file ProductSet_SCRIPT.js and delete the generated code and start implementing the getFeed() method.

 

Before we can call the DataProvider class, we need to import the corresponding package:

 

    importPackage(com.example.dataprovider);











 

Then we only need to call our DataProvider class to obtain the list with products:

 

    message.setBody(new DataProvider(message).getAllProducts());











 

This sample code shows: We have implemented an OData service in only ONE line of code.

(because all the implementation is located in a separate bundle)

 

In our example, the Javascript code for the complete script file looks as short as shown below:

 

 

function getFeed(message){
    importPackage(com.example.dataprovider); // our DataProvider
    importPackage(com.sap.gateway.ip.core.customdev);
    message.setBody(new DataProvider(message).getAllProducts());
    return message;
}
function getEntry(message) {
    return message;
}
function createEntry(message) {
    return message;
}
function updateEntry(message) {
    return message;
}
function deleteEntry(message) {
    return message;
}











 

After Generate&Deploy, we can invoke our OData service at

 

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE;v=1/ProductSet

 

The result is as expected:

 

 

 

 

 

 

4.2. Adding $skip and $top capability

 

 

The separation of implementation logic (Java) from scripting (Javascript or Groovy) starts getting interesting when adding more complexity to your OData service.

Once your OData service supports many system query options like top, skip, filter, orderby and all the operations and also navigation, etc, you’ll appreciate to be able to organize your code in a Java bundle where you can have different packages and layers, etc.

 

In our example, we want to support 2 system query options: $skip and $top

First step is to delete the line that we’ve written in the previous section, because we have to change it a bit.

 

In the getFeed() method, we first fetch all products.

Then we check if the request URL contains the query option.

If not, nothing is done and the list which contains all products is returned.

If the query option is available in the request URL, then the query option is applied, which means that the list of all products is reduced, depending on the query option.

All this logic is contained in the DataProvider bundle.

From our OData service script, we only call the corresponding methods.

 

Error handling

To showcase some error handling we’re doing the following:

In our DataProvider, we throw an exception, if the value of $skip or $top is higher than the total amount of existing entities.

However, as service implementor, we decide that our OData service should be more tolerant. If the user of our service specifies a value that is higher than the total amount, we catch the exception and only write an entry to the log.

Like that, the service doesn’t break, it just ignores the silly query option.

 

 

function getFeed(message){
    importPackage(java.util);
    importPackage(com.example.dataprovider); // our DataProvider
    importPackage(com.sap.gateway.ip.core.customdev); // required for message
    importPackage(com.sap.gateway.ip.core.customdev.logging);  // required for LogMessage
    // instantiate our DataProvider object
    var dataProvider = new DataProvider(message);
    // call DataProvider to get all the data
    var productList = dataProvider.getAllProducts();
    // delegate the system query options logic to DataProvider
    try{
        productList = dataProvider.handleSkip(productList);
        productList = dataProvider.handleTop(productList);
    }catch(e){
        // here, we would ideally set the status code to 400
        log.logErrors(LogMessage.TechnicalError, "An error occurred while applying system query options." + e.message);
    }
    // done
    message.setBody(productList);
    return message;
}











 

 

 

4.3. Implement the READ operation

 

Let’s spend one more little chapter on coding.

When we refer to the READ operation, we mean a GET request to a single entity,

e.g.

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE/ProductSet('3')

 

In our script, the getEntry() method is invoked.

 

The task that we have to do here is:

As we know, one single product is requested, which is identified by its ID in the request URL.

Remember, the ID property is marked as key.

So, in our code we have to analyze the URL in order to obtain the value of the "ID" property.

Then we search the list of all products until we find the right one.

 

Again, all the logic is handled in the DataProvider bundle, the code in the script is actually done in one line:

 

function getEntry(message) {
    importPackage(java.util);
    importPackage(com.sap.gateway.ip.core.customdev); // required for message
    importPackage(com.example.dataprovider); // our DataProvider
    // this call returns a Map for the product requested by the ID in the URL
    var productMap = new DataProvider(message).getProduct();
    message.setBody(productMap);
    return message;
}









 

 

One word about error handling

In case that we don’t find the requested product, the DataProvider obviously has to return null.

In the OData service we set null as response body of the message object, which is properly interpreted by the IGW framework.

As such, we don’t need to do anything and the correct response is given by our OData service at runtime:

 

 

 

 

5. Test the OData service

 

 

 

After Generate&Deploy, we can invoke our OData service as follows

 

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE;v=1/ProductSet?$skip=2

 

and

 

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE;v=1/ProductSet?$top=1

 

If we specify an invalid number like this:

 

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE;v=1/ProductSet?$top=6

 

The result can be seen in the log, the error that we’re logging in our OData service script

 

If we specify an invalid number like this:


https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE;v=1/ProductSet?$top=-1

 

then the validation of the OData lib (Olingo) handles it and gives the proper error message in the browser

 

 

 

 

The next test is to combine both query options:

 

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE;v=1/ProductSet?$skip=2&$top=1

 

 

The expected result:

We skip the products 1 and 2, and from the remaining, we take only the first.

Therefore, the service is expected to return only the Product3

 

Changing the order gives the same result

https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE;v=1/ProductSet?$top=1&$skip=2

This is intended as it adheres to the OData specification.

 

 

Next step: Debug the Java code running on the SMP server

In the previous blog, I had talked about how you can create a fully functional (skeleton) Windows application with just a few clicks.  The business logic still needs to be included in the application by the developer.  In this blog, I will show you the layout of the application files and how you can modify files to add certain functionality.

 

Nuget Package Manager

Nuget Package Manager has an entry automatically created for the location of the SMP related Nuget packages.  The application wizard is also smart enough to only include the Nuget packages that are needed for the application.  Nuget packages are installed in the following folder - \Program Files (x86)\SAP SE\SMP SDK Installer.  No additional steps are required from the developer unless you want to install / uninstall SMP related Nuget packages.

 

To install / uninstall SMP related Nuget packages, simply open the Nuget Package Manager.  Select the automatically created Package source and you can install / uninstall SMP related Nuget packages.

 

Customization1.png

Understanding project files

Depending on the Windows platforms selected during application wizard, one or more projects might be automatically created.  Since the project is created based on Microsoft recommended principles, you will also find a Shared project that contains shared code between Windows 8.1 and Windows Phone 8.1 projects.

 

Customization2.png

 

Logging and Tracing files

The Utilities class implements the logging and tracing functionality amongst other things.  The project files are located under the Utilities folder.  The Utilities class is not one big monolithic file.  It is however implemented as partial classes.  This allows the developer to remove or edit files easily. For example, if the developer wants to create a new logger, then he can easily find the right place to add the necessary code in Utilities.LoggingTracing.cs file.

 

Note:

  • Windows 8.1 and Windows Phone 8.1 logging and tracing files can be found in Shared project
  • Customize these files based on your business logic

 

Customization3.png

 

Customization4.png

 

Settings file (Windows 8.1)

Settings are implemented as flyouts.  The project files are located under the Settings folder.  Registration settings are implemented in the SettingsFlyoutRegistration.xaml file while the logging and tracing settings are implemented in SettingsFlyoutLoggingTracing.xaml file.

 

Customization5.png

 

 

Customization6.png

 

Online Store

The Global.cs file implements the OpenODataStoreAsync method.  This is a public static method which creates the type of ODataStore instance needed by the application.  In the case of the application wizard, it uses the Online Store implementation.

 

Customization7.png

 

HttpClientSetup.cs sets up the SAP.Net.Http.HttpClient that is used for making HTTP requests. It also takes care of SSL certificate logic.

 

Rendering data is done using the ServiceBrowserControl.  The ServiceBrowser.xaml file is not included in Main.xaml file.  So it is easy to reuse this control in several pages.  It is also easy to remove the control.  All code to access data from the backend is separated in ServiceBrowser.xaml.cs file – This code only deals with accessing data.  So any new developer can get easily familiar with OData library that is provided with SMP SDK.

Adding Push Notifications

The application wizard currently doesn’t have support for push notifications.  But adding support for push notifications is fairly simple. Add the following lines of code after successful logon in the Logon_LogonCompleted event handler.

 

Customization8.png

 

 

Adding Offline Support

The application wizard creates the Windows application using the Online Store.  However, if your business logic requires the use of an Offline Store, then modifications to the generated code is necessary.

 

The Global.cs file implements the method OpenODataStoreAsync method.  The generated code creates an Online Store.  This method needs to be modified to create an Offline Store instead. This Offline Store is then used by the application for all CRUD operations.

 

Customization9.png

 

You also need to add code for Flush and Refresh operations.  Please check the following blog on OData functionality.

 

This concludes the 2 part series of the new features in Windows SMP SDK 3.0 SP 10.  Stay tuned for more exciting updates in SMP SDK 4.0 !!

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

The Integration Gateway component allows to expose backend data as an OData service.

There's support for some concrete data sources like JDBC, JPA, REST service, and for extending the capabilities it is possible to write custom code.

This custom code is placed in a script file which can easily become very long.

For example, when implementing $filter, $orderby, deltatoken, etc

 

Another point is that often such manual coding is repeated in several scripts.

For Example, in the REST data source, the tasks to convert the payload is very similar in the QUERY and in the READ operations, and is repeated in the response of CREATE, etc

It would be nice to reuse the code instead of duplicating it.

 

However, it is not possible to modularize the code to split the script file into several files or to add several Java classes to the OData implementation project that we create in the API toolkit for SAP Mobile Platform in Eclipse.

You don't need to try.

 

But don't you feel the wish - like me - to clean up the code, modularize it, use Java for more complex tasks?

And also the wish to - wow - debug the code with normal Java debugger?

 

Yes?

So please go ahead and read how I solved it:

 

- Move all the code into a separate OSGi bundle.

- Deploy that bundle to SMP server

- Reference this bundle from my OData implementation project.

- Debug the bundle as a "Remote Java Application"

 

In the present tutorial, I’m going to describe it in detail.

As usual, we’ll keep our example as simple as possible.

The source code can be found attached to this blog post.

 

Note: this tutorial doesn’t represent the official documentation. It is just my personal way of working.

 

In the first part, we'll cover the OSGi bundle

In the second part, we'll write the OData service

In the third part, we'll debug the running Java code

Finally, in the fourth part, we'll use junit to test our API before publishing it

 

 

 

Prerequisites

 

 

Overview


Part I

1. Prepare SMP server

2. Prepare Eclipse

3. Create the OSGi bundle

    3.1. Create Plug-In project

    3.2. Implement DataProvider class

    3.3. Implement Activator class

    3.4. Deploy the bundle to SMP

    3.5. Verify the deployed bundle


Part II

4. Create the OData service

    4.1. Implement the QUERY operation

    4.2. Adding $skip and $top capability

    4.3. Implement the READ operation

5. Test the OData service

 

Part III

6. Debug the Java code

    6.1. Start SMP server in debug mode

    6.2. Connect from Eclipse to SMP

    6.3. Debug the Java code in Eclipse

7. Summary

8. Links

 

Part IV

9. Test the API automatically

    9.1. Create test fragment

    9.2. Create JUnit tests

    9.3. Run the tests

    9.4. Summary

 

 

 

 

1. Prepare SMP server

 

 

Usually, the SMP server is installed and started as Windows service.

During development, I prefer to start it from the command shell.

Reason: I can quickly view console logs

 

How to start the SMP server from console?

Open command prompt.

Navigate to the location where you’ve installed the SMP

Step into the \Server directory

Type go.bat (or simply go) and hit enter

 

 

Then wait until you see the ready-message

 

 

Don't you feel happy - like me - whenever you see this message?

 

 

 

2. Prepare Eclipse

 

When we create a bundle in Eclipse, then the dependencies are managed as dependencies to other bundles.

Eclipse resolves the bundle-dependencies to those bundles that are installed inside Eclipse (and projects in workspace).

This means, your bundle can only depend on bundles that are available in Eclipse.

When writing bundles to be running inside Eclipse, this is fine.

But when writing a bundle that is designed to run in a different environment, this is obviously not sufficient.

Like In our case: We want to write a bundle that will afterwards run in SMP (which is also based on OSGi).

It is possible to use the standard Eclipse IDE for creating that bundle and it will run fine in SMP

BUT: our bundle has dependencies to bundles which run in SMP (not in Eclipse)

So what we want is: compile against SMP.

Eclipse supports that with the concept of “Target Platform”, which can be defined in Eclipse.

I’ve explained that in detail in my SCN document

 

 

Configure SMP as target platform

 

 

The steps are:

 

Open the Target Platform preference page at Window->Preferences->Plug-in Development->Target Platform

 

 

 

Here you can see that as per default, our bundle would compile against the currently running Eclipse instance.

What we now are going to do is to add the SMP server to this list and define it as the target against which we compile.

 

Press "Add".

Select “Nothing” and press "Next".

Enter an arbitrary name for this target e.g. “SMP_local”

Press “Add”.

Choose “Installation”.

“Browse” to the location of the locally installed SMP e.g. C:\SMP \server

Note: The chosen path has to point to exactly that folder that is the parent folder of the “configuration” directory.

 

 

 

 

Press “Finish”.

Back in the preference page, now you have to enable this newly created Target Platform to be the active one.

 

 

 

 

Press "OK".

From now on, all bundle-projects in your Eclipse workspace will compile against the bundles that can be found in the SMP.

 

 

 

3. Create the OSGi bundle

 

 

What we want to achieve in this tutorial is to create an OData service that runs in SMP and that exposes data that is provided and managed by a separate OSGi bundle.

We’re going to create this separate bundle in the current chapter.

 

This bundle will represent the data model (note: this is not the OData model) and it will offer a simple API that can be called by other bundles in order to obtain the data.

As such, it will act as “Data Provider”.

It will also host all logic to manipulate the data and it will host helper methods that will be called from the OData service that we’ll create later.

This Data-Provider-Bundle will be manually deployed to SMP

Then we’ll create our OData project and there we’ll call the API of the Data-Provider-Bundle to get the data and expose it as OData service

 

 

3.1. Create Plug-In project

 

Within Eclipse, open Java perspective via Window->Open Perspective->Java

Create a new bundle project as follows:

Choose File->New->Project->Plug-In Development->Plug-In Project

 

Press Next and enter the details of the project:

Enter the project name: com.example.dataprovider

Select as Target Platform “an OSGi framework” and value “Equinox”

Note:

With this setting we specify that our bundle is not intended to run in Eclipse (which is based on OSGi as well), but in a different OSGi-environment.

Our OSGi-environment is our SMP server and it is based on Equinox

Equinox is an implementation of the OSGi specification.

Since we aren't using any special OSGi features, it actually doesn't make much difference for us.

 

 

 

Press Next and enter the details of the bundle

Bundle-ID: com.example.dataprovider

Version: 1.0.0

Bundle Name: Example Dataprovider

Vendor: ExampleVendor

 

Please make sure that you stick to this naming, because it will be used for the java packages as well and the package will be used by the OData service project.

 

 

 

Press next and deselect the template creation

Press Finish

 

In the subsequent popup, choose "No" to not change to Plug-In Development perspective

 

Result: The project is created and the editor of the MANIFEST.MF file is opened.

 

Declare dependencies

Before starting with the code, we need to declare the dependencies, otherwise our code won’t compile.

In our code, we will be using API that is provided by Olingo, the OData library. This library is available as a bundle in the SMP server, so we can use it in our Data-Provider-Bundle.

Furthermore, we’re using the library for custom code provided by the Integration Gateway framework.

 

If not already open, open the manifest file via double-click on <project>/META-INF/MANIFEST.MF

Open the “Dependencies” tab on the bottom of the editor.

On the left pane, we can declare the bundles that we need.

Click on "Add"

Select the bundle olingo-odata2-api

Press OK

 

Repeat the steps for adding com.sap.gw.rt.camel.components.custom-development

As usual, save the editor.

 

 

 

Note:

The description above declares a dependency to a bundle. Dependencies can as well be declared in a more fine-granular way with the concept of Import Package, where you explicitly declare which package you need.

 

Declare Exports

After adding the required dependencies of our bundle to other bundles, we have to declare that our bundle can be used by others.

Remember: we’re creating the bundle because we want to use it from our OData service that we’ll be creating later.

In order to expose our API, the corresponding concept is to "export packages"

We switch to the “Runtime” tab in the bottom of the editor, press "Add" and choose the one and only package that our bundle contains.

Don’t forget to save the editor.

 

 

 

Finally, we can start writing some code

 

What code?

-> This code: We want to have sample data

-> That code: We want to offer it via an API

 

 

3.2. Implement DataProvider class

 

Create new Java class com.example.DataProvider

 

Note:

For the sake of simplicity, we don’t create new packages. If you do so, remember that you have to export it as well.

 

 

 

 

What we want to achieve in this class is to

 

- offer a public method that provides all products

  This method will be called from the OData service when the QUERY operation is executed

  e.g. https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE/ProductSet

 

- offer a public method that provides one single product for a given ID

  This method will be called from the OData service when the READ operation is executed

  e.g. https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE/ProductSet('1')

 

- offer a public method that provides a list of the first n products

  This method will be called from the OData service when the QUERY operation is executed with $top

  e.g. https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE/ProductSet?$top=2

 

- offer a public method that provides a list of the last n products

  This method will be called from the OData service when the QUERY operation is executed with $skip

  e.g. https://localhost:8083/gateway/odata/SAP/EXAMPLESERVICE/ProductSet?$skip=2

 

 

In our example the DataProvider class does not only provide the data, but as well handles the Message object.

The sample code looks as follows:

 

package com.example.dataprovider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.api.uri.KeyPredicate;
import org.apache.olingo.odata2.api.uri.UriInfo;
import com.sap.gateway.ip.core.customdev.util.Message;
public class DataProvider {
    // the Message object contains all context information
    private Message message;
    /* public API */
    // constructor
    public DataProvider(Message message) {
        super();
        this.message = message;
    }
    // QUERY operation
    public List<Map<String, String>> getAllProducts(){
        List<Map<String, String>> productList = new ArrayList<Map<String, String>>();
        // create some sample product entities as HashMaps
        for (int i = 1; i <= 5; i++) {
            String index = Integer.toString(i);
            Map<String, String> productMap = new HashMap<String, String>();
            productMap.put("ID", index);
            productMap.put("Name", "ProductName" + index);
            productMap.put("Description", "Description" + index);
            productList.add(productMap);
        }
        return productList;
    }
    // READ operation
    public Map<String, String> getProduct(){
        // check the URI for the ID of the requested product
        UriInfo uriInfo = (UriInfo) this.message.getHeaders().get("UriInfo");
        List<KeyPredicate> keyPredicates = uriInfo.getKeyPredicates();
        KeyPredicate keyPredicate = keyPredicates.get(0); // we know there's only 1 key prop
        String productID = keyPredicate.getLiteral(); // the value that is given in the URL
        return getProduct(productID);
    }
    public List<Map<String, String>> handleSkip(List<Map<String, String>> productList) throws ODataApplicationException{
        UriInfo uriInfo = (UriInfo) this.message.getHeaders().get("UriInfo");
        Integer skipOption = uriInfo.getSkip();
        // if skipOption is null, then there's no $skip in the URI, so only do something if it is there
        if(skipOption != null){
            int skipNumber = skipOption.intValue();
            productList = this.applySkip(productList, skipNumber);
        }
        return productList;
    }
    public List<Map<String, String>> handleTop(List<Map<String, String>> productList) throws ODataApplicationException{
        UriInfo uriInfo = (UriInfo) this.message.getHeaders().get("UriInfo");
        Integer topOption = uriInfo.getTop();
        if(topOption != null){
            // if skipOption is null, then there's no $skip in the URI, so only do something if it is there
            int topNumber = topOption.intValue();
            productList = this.applyTop(productList, topNumber);
        }
        return productList;
    }
    /*
    * Internal methods
    * */
    // READ operation
    public Map<String, String> getProduct(String productID){
        // find the requested product
        List<Map<String,String>> allProducts = getAllProducts();
        for (Iterator<Map<String, String>> iterator = allProducts.iterator(); iterator.hasNext();) {
            Map<String, String> map = (Map<String, String>) iterator.next();
            String idValue = map.get("ID");
            if (idValue.equals(productID)){
                return map;
            }
        }
        return null;
    }
    // $top
    private List<Map<String, String>> applyTop(List<Map<String,String>> allProducts, int topNumber) throws ODataApplicationException{
        if(topNumber >= 0 && topNumber <= allProducts.size()){
            return allProducts.subList(0, topNumber);
        }else{
            throw new ODataApplicationException("Invalid value for $top", Locale.ROOT, HttpStatusCodes.BAD_REQUEST);
        }
    }
    // $skip
    private List<Map<String, String>> applySkip(List<Map<String,String>> allProducts, int skipNumber) throws ODataApplicationException{
        if(skipNumber >= 0 && skipNumber <= allProducts.size()){
            return allProducts.subList(skipNumber, allProducts.size());
        }else{
            throw new ODataApplicationException("Invalid value for $skip", Locale.ROOT, HttpStatusCodes.BAD_REQUEST);
        }
    }
}




















 

 

 

 

3.3. Implement Activator class

 

I guess that you’ll feel like me, that it would be nice to test the bundle, after it is finalized and deployed to SMP.

Such that we can be sure that our OData service will be able to safely make use of our DataProvider bundle.

In order to test our bundle, we could write a second bundle. But it is easier to proceed as described below.

 

After deploying our bundle to SMP, the OSGi runtime will immediately activate the bundle.

When a bundle gets activated, the “start” method of the Activator class of the bundle gets invoked.

When we created our bundle-project above, we specified that we want an Activator class to be generated (see the screenshot above).

Now we can make use of it.

 

We’ll add some dummy code to the “start” method, in order to call our DataProvider and write the result to the console.

After deployment, we can check the console if the expected output is there.

We can even change to the OSGi console and there we can manually stop the bundle, then start it again and each time we start it, we’ll see the same output (see section below)

 

 

 

package com.example.dataprovider;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
    private static BundleContext context;
    static BundleContext getContext() {
        return context;
    }
    public void start(BundleContext bundleContext) throws Exception {
        Activator.context = bundleContext;
        System.out.println("\n");
        System.out.println("[EXAMPLE] Printing all Products: ");
        List<Map<String,String>> allProducts = new DataProvider(null).getAllProducts();
        for (Iterator<Map<String, String>> iterator = allProducts.iterator(); iterator.hasNext();) {
            Map<String, String> map = (Map<String, String>) iterator.next();
            System.out.println("ID: " + map.get("ID") + " - Name: " + map.get("Name") + " - Description: " + map.get("Description"));
        }
    }
    public void stop(BundleContext bundleContext) throws Exception {
        Activator.context = null;
    }
}


















 

 

3.4. Deploy the bundle to SMP


Now that we’re done with the coding, we’re ready to deploy our bundle to SMP server.

 

In the present tutorial, we’re doing it manually.

Two steps have to be done:

1. Generate a bundle out of the current bundle-project

2. The final bundle jarfile has to be deployed to SMP

 

First step: generate the bundle jar

In order to build the project and generate a bundle, we’re using the export mechanism (advanced users use a build tool like maven)

To export the project, we have to

right-click on the project node in Eclipse,

then choose Export-> Plug-in Development -> Deployable pluig-ins and fragments

After pressing "Next", we have to make sure that our project is selected, then enter the target directory, where our bundle will be generated at.

 

 

 

After pressing "Finish", a “plugins” folder containing a jar file is generated at the specified location.

 

 

 

 

Second step: deploy a bundle to SMP

There are several possible ways of deployment, we’re using the easiest one: copy the bundle to the pickup folder.

 

Some background:

The SMP server supports hot deployment, which means that the OSGi runtime will notice that you’ve copied a bundle into the pickup folder and it will include it in its OSGi runtime on the fly.

The pickup folder is located at

<installDir>\Server\pickup

Note:

This way of installing (hot deployment) is used rather for testing purpose. For productive environment, use the command line to really “install” the bundle into the OSGi container.

For more information about OSGi and installation procedure, you may have a look at my OSGi Blogs:

Here and here and here

 

 

 

3.5. Verify the deployed bundle

 

Now that the bundle has been deployed, let’s quickly verify if everything’s ok.

 

Open the folder \pickup\.state and check if the deployment has been successful.

A file is created which contains some information about successful or erroneous deployment.

 

 

 

In case of error, you should check the console (if you’ve started your SMP server via command prompt) and also check the SMP error log and the OSGi log

 

Next, have a look at the OSGi console to check the state of the deployed bundle.

 

 

For this purpose, we open a command shell.

Then we connect to OSGi via the command

telnet localhost 2401

 

 

After pressing enter, it will connect…

 

 

Then the OSGi-console is ready and waiting for our commands:

 

 

We can type  the command

lb dataprovider

 

The command lb stands for "list bundle" and lists all bundles with the given name that are known to the OSGi runtime.

The below screenshot shows that the bundle is found and that the status is “Active”

 

 

The result of the command shows also the ID under which that the OSGi runtime has registered our bundle.

We can now use this ID to display the details of our bundle.

The command is

bundle <ID>

In our example, the command is the following

bundle 645

 

The below screenshot displays the dependency and the export that we’ve declared in the previous section

 

 

A bundle can be embedded into the OSGi environment without being activated (in such case, its status is “Resolved”)

When the OSGi framework activates a bundle, the “Activator” class of this bundle is called and its “start” method gets invoked.

In our bundle, we have added some log output to the “start” method, such that we can now go ahead and check if our “product” data has properly been created and displayed.

We can see the log output in the console or in the SMP log file

 

 

We can now change to the OSGi console and execute the commands as shown in below screenshot.

Find and "stop" and "start" the bundle and see the same log output

 

 

 

 

That’s it.

 

We have created a bundle.

We have added code to create and manage some sample data.

We have also create a little test code.

We have generated the bundle.

We have deployed it to SMP.

We have verified that it works.

 

 

 

Next step: Create the OData service using SAP Mobile Platform Toolkit in Eclipse.

 

 

 

 

Appendix

How to find the right bundle?

If you have the name of a package and want to know which bundle exposes it, then you can proceed as follows:

Go to the OSGi console

Type the command p followed by the name of the package

After pressing enter, the result printed on the console gives the name and ID of the exposing bundle and also lists the using bundles

 

 

 

 



This blog is about Integration Gateway (IGW) in SAP Mobile Platform 3.0 (SMP), more concrete, about the designtime in Eclipse.

 

 

This information Blog is useful for those of you who are

- working with Eclipse

- working with the API toolkit for SAP Mobile Platform

- using the OData perspective

- using the Service Catalog view

- wondering about services that are marked as “malformed”

 

 

Background

 

Working with the Service Catalog view in Eclipse makes sense if you have an ABAP backend connected, if you have OData services there and if you like to view them in Eclipse.

With the view Service Catalog, you can very easily browse the OData services and view the metadata in the OData Modeler

 

In case you haven't seen it yet:

 

Browse OData services

 

 

 

Apply filter to quickly find your service

 

 

 

 

View details in the property view

 

 

 

 

View the OData model in the graphical OData Modeler (Read Only) and get an overview in the Miniature View

 

 

 

Execute actions, like importing the model into a project

 

 

 

The "Malformed Service" - error

 

 

You may have faced the issue that the service that you’re interested in is marked as erroneous

 

 

 

And even causes an error popup

 

 

 

Solution

 

In such a case you could proceed as follows:

 

-> refresh this service node by opening the context menu and choosing “Refresh”

 

However, this might fail.

In such a case it is useful to know what’s happening behind the scene.

When browsing the services of a connected backend, the API toolkit for SMP reads the OData services and caches the metadata.

This cache is located on your file system at:

 

C:\Users\<yourUserID>\.data-storage\<yourConnection>

 

In this folder, there’s a file for each OData service and the file contains the metadata of the service.

Now, it can occur that such a file is missing or that it is corrupted, both could have been caused by network issues while connecting the backend.

It may be helpful to open the file if it is marked as erroneous, as it contains an error message

 

The solution is simple:

 

Go to your file system and open the ".data-storage" folder as mentioned above.

Find the file that represents your service and delete it (alternatively, you can delete the whole folder.)

Afterwards go back to Eclipse and refresh the OData service or the full connection node, by using either the context-menu or the toolbar button.

 

This brute-force method should solve the problem, you can verify if the metadata file is successfully created in the respective .data-store folder.

If you are not using Afaria to manage application settings, your Kapsel Logon Screen presents a list of choices that might seem a bit formidable to your end users. This article describes an quick and easy change to your SMP SDK installation that will limit the Logon UI to the most common choices needed by users.


Screen Shot 2015-09-15 at 3.50.25 PM.png

The default SMP Logon Screen exposes choices to the user that you might not want to show (SMP 3 SDK SP09; iOS screen)

 

 

The SMP SDK offers support for many different styles of security integration.  In many of those cases, though, we simply need to obtain the user's id and password to authenticate the device for SMP or HCPms access.  It turns out that you can modify a single file in your SMP SDK Kapsel installation directory to change this for all new Kapsel applications you might create. The JavaScript file path is <install_location>/KapselSDK/plugins/logon/www/common/modules/StaticScreens.js ; the passage of code you'll want to modify starts around line 143 of the file (at least that's the location in SMP3 SDK SP10 PL09, you line number may vary).

 

The code you will want to add are the "visible: false" values. I suggest making a backup copy of the file before you save your edits.

 

 

       'SCR_REGISTRATION': {
            id: 'SCR_REGISTRATION',
            nav: {
                submit: {
                },
        cancel: {
        }
            },
            fields: {
                      serverHost : {
                           uiKey:'FLD_HOST',
                           editable:true,
                            visible:false
                      },
                        user : {
                            uiKey:'FLD_USER'
                        },
                        password : {
                            uiKey:'FLD_PASS',
                            type: 'password'
                        },
                        resourcePath : {
                            uiKey:'FLD_RESOURCE_PATH',
                            visible:false
                        },
                        https: {
                            uiKey:'FLD_IS_HTTPS',
                            type: 'switch',
                            'default':false,
                            visible:false
                        },
                        serverPort : {
                            uiKey:'FLD_PORT',
                            type: 'number',
                            editable:true,
                            visible:false
                        },
                        farmId : {
                            uiKey:'FLD_FARMID',
                            visible:false
                        },
                        communicatorId : {
                            uiKey: 'FLD_COMMUNICATORID',
                            'default':'REST',
                            visible:false
                        },
                        securityConfig: {
                            uiKey:'FLD_SECCONF',
                            visible:false
                        },
            }
        },

 

Save these changes under the same filename.

 

You should now be able to see a much cleaner Login UI in any new Kapsel project you create -- either from the kapsel / cordova shell commands or using Web IDE's Hybrid Application Toolkit.  You can also update an existing project by finding and changing the file in the Cordova project's source  tree -- since I use the Hybrid App Toolkit frequently, though, I find it much more convenient to change the default directly in the SDK directory as I have shown here.

 

Enjoy.

SAP Mobile Platform SDK SP10 (herein referred to as “SMP SDK”) has some very interesting features that allows developer to create a fully functional (skeleton) application with just a few clicks.  The reason I use the term ‘skeleton’ is because the business logic (for example, pricing logic, work flow logic etc.) still needs to be included in the application by the developer.  Otherwise, the code for onboarding a device, enabling logging and tracing, opening an Online Store and consuming and even rendering the data in UI controls is all included in the application.  This makes any developer with virtually no knowledge of the SMP product to create an application within a few minutes.  While prior blogs on onboarding, logging and tracing etc. are still valid, it is highly recommended to follow the steps included in this blog when creating a Windows application.

 

Highlights of the Windows application created using the application wizard

  • Simplifies developer learning curve – Onboarding (device registration), logging, tracing, reading from data source is all taken care of
  • Fully functional application with UI to render data using Microsoft recommended approach
  • Project files based on best practices
  • Support for multiple Windows platforms
  • Windows 10 mobile support is preliminary in SP10 (as it was not released by Microsoft when SP10 was released)
  • Windows 10 mobile support will be fully available in SP11

 

 

Creating a new Windows application workflow – from start to finish

The workflow below describes the ease in which you can create a multi-platform Windows application. The new project is fully functional (skeleton) and includes reading and rendering data from an Online Store.

 

AppWizard1.png

 

Note: 

  • This application wizard is only available for native Windows development – and not available for native iOS or Android development
  • Microsoft Visual Studio 2013 does not support Windows 10 applications.  You need to install Microsoft Visual Studio 2015 for Windows 10 support

 

 

Installing SMP SDK SP10

The SMP SDK SP10 installer does some additional work than its predecessors.  The installer automatically checks to see if Microsoft Visual Studio 2013 + Update 3 or higher is installed.  If it detects that Microsoft Visual Studio 2013 + Update 3 or higher is installed, then it installs the Visual Studio template and the application wizard.

 

Nuget Package Manager also has an entry for the location of the SMP related Nuget packages.  This pretty much eliminates any initial setup effort on the part of the developer.  As soon as the SMP SDK SP10 is installed, the developer can immediately create a Windows application.

 

Note:

  • Nuget packages are installed in the following folder.  <System Drive>\Program Files (x86)\SAP SE\SMP SDK Installer
  • The .vsix file can also be found in this location.  In case you uninstall the project template and need to reinstall the project template, you can simply run this file

 

Visual Studio project template

SMP SDK SP10 installs the SAP Business Application project template in Microsoft Visual Studio automatically as part of the installation process.  This allows a developer to create a fully functional Windows application with virtually no experience of the SMP product.

 

To create a Windows application using this new project template, do the following.

  • Open Microsoft Visual Studio and click New Project
  • Under templates, select Visual C# and scroll down on the right pane to select SAP Business Application
  • Enter the name and location for the project and click OK

 

AppWizard2.PNG

 

 

Application wizard – Completely functional Windows app in just a few clicks

I will go over the sequence of wizard pages that are displayed to the user to create a Windows application.  At any point during the application wizard, you can click Finish to create a fully functional Windows application with the default settings.

 

AppWizard3.PNG

 

Select the Windows platforms you want to support.  Note that Windows 10 is supported only on Microsoft Visual Studio 2015.

 

AppWizard4.PNG

 

AppWizard5.PNG

 

Enter all the values required for onboarding.  If no values are entered, then default values are used.  You can also choose to use MobilePlace configuration service and offer demo mode.  Settings page is implemented as a flyover to make changes to Onboarding values.

 

AppWizard6.PNG

 

Select if you want to enable logging and tracing for this application.

 

AppWizard7.PNG

 

The Windows application can consume the backend OData Service and render the data in UI controls automatically.  Microsoft recommended UI principles are practiced.

 

AppWizard8.PNG

You can also customize the appearance of the SAP UI controls.  By default, styles.xaml file includes standard SAP colors and settings.

 

AppWizard9.PNG

 

The application wizard is smart enough to only package Nuget packages that are required by the application.

 

AppWizard10.PNG

 

 

Running the application

The application is fully functional once the application wizard is complete.  To run the application, simply set one of the Windows platforms as your startup project.  For example, set Windows 8.1 as your startup project.  Press F5 (or Ctrl + F5 – without debugger attached) to run the Windows application.

 

You are initially presented with the logon screen.  The values are prepopulated from the application wizard.  Simply click Register to onboard the device.

 

AppWizard11.png

 

Once onboarding is complete, you are also presented with data from the backend OData Service. Simply select the EntitySet that you want to view.  All the values are rendered in UI controls automatically.

 

AppWizard12.png

 

Even complex types are rendered in the UI controls.

 

AppWizard13.png

 

The Settings page allows you to unregister a device, enable logging and tracing etc.

 

AppWizard14.png

 

 

In the next blog, I will talk about how to customize the Windows application.

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

 

In this blog, we’ll have a closer look at 2 new capabilities that have been introduced in SP10

Please note that we’re talking about JDBC data source only.

 

Up to now, it was possible to use $batch for read operations. Write operations were supported as well, but only one operation per change set.

Now, since SP10,  it is possible to have multiple operations within one change set.

This leads to 2 interesting capabilities:

1. Rollback

2. Content ID referencing

 

 

Let’s have a closer look.

I’ll be showing a sample $batch request body for each feature.

Note:

While trying the same, please make sure to use the REST client tool “Postman” for the $batch requests.

Attached you can find a Postman-collection file, which you can download, remove the zip fileextension and import in Postman.

It will help to construct the $batch requests, however, to run the requests, you need a corresponding service and database

 

 

1. Rollback

 

In the following sample request body, I’m showing that an erroneous (inner) request in the $batch leads to a rollback.

The below $batch request contains the following inner requests:

 

POST

---

DELETE

PUT

---

PUT

PUT

---

GET

 

The first POST request is contained in one change set and creates a product

 

Then, in a second change set the new product is deleted and in the same change set the product is modified with a PUT.

This PUT request will fail, because the product has been deleted in the SAME change set.

As such, THIS change set will be rolled back and as such, the deletion is reverted.

 

Afterwards, since the product is not deleted, we can go ahead and in a third change set, we fire two PUT requests, both will succeed.

 

Finally, the last thing is to perform a READ operation.

The GET request is not contained in any change set, because change sets are used for modifying operations only.

The GET request calls the product and shows that the 2 last PUT requests were executed successfully.

 

 

This is my sample request body:

 

 

--batch_mybatch
Content-Type: multipart/mixed; boundary=changeset_mychangeset1
--changeset_mychangeset1
Content-Type: application/http
Content-Transfer-Encoding: binary
POST PRODUCTS HTTP/1.1
Content-Type: application/atom+xml
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="https://localhost:8083/gateway/odata/SAP/JDBC;v=1/">
    <content type="application/xml">
        <m:properties>
            <d:PRODUCTID>108</d:PRODUCTID>
            <d:NAME>Product108</d:NAME>
            <d:DESCRIPTION>Product108 description</d:DESCRIPTION>
            <d:RATING>108</d:RATING>
            <d:PRICE>108</d:PRICE>
            <d:CATEGORYID>555</d:CATEGORYID>
            <d:SUPPLIERID>20</d:SUPPLIERID>
            <d:ISDELETED>0</d:ISDELETED>
        </m:properties>
</content>
</entry>
--changeset_mychangeset1--
--batch_mybatch
Content-Type: multipart/mixed; boundary=changeset_mychangeset2
--changeset_mychangeset2
Content-Type: application/http
Content-Transfer-Encoding: binary
DELETE PRODUCTS(108M) HTTP/1.1
Content-Type: application/atom+xml
--changeset_mychangeset2
Content-Type: application/http
Content-Transfer-Encoding: binary
PUT PRODUCTS(108M) HTTP/1.1
Content-Type: application/atom+xml
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="https://localhost:8083/gateway/odata/SAP/JDBC;v=1/">
    <content type="application/xml">
        <m:properties>
            <d:NAME>Product108BetterName</d:NAME>
        </m:properties>
    </content>
</entry>
--changeset_mychangeset2--
--batch_mybatch
Content-Type: multipart/mixed; boundary=changeset_mychangeset3
--changeset_mychangeset3
Content-Type: application/http
Content-Transfer-Encoding: binary
PUT PRODUCTS(108M) HTTP/1.1
Content-Type: application/atom+xml
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="https://localhost:8083/gateway/odata/SAP/JDBC;v=1/">
    <content type="application/xml">
        <m:properties>
            <d:NAME>Praline</d:NAME>
        </m:properties>
    </content>
</entry>
--changeset_mychangeset3
Content-Type: application/http
Content-Transfer-Encoding: binary
PUT PRODUCTS(108M) HTTP/1.1
Content-Type: application/atom+xml
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="https://localhost:8083/gateway/odata/SAP/JDBC;v=1/">
    <content type="application/xml">
        <m:properties>
            <d:DESCRIPTION>Really tasty</d:DESCRIPTION>
        </m:properties>
    </content>
</entry>
--changeset_mychangeset3--
--batch_mybatch
Content-Type: application/http
Content-Transfer-Encoding: binary
GET PRODUCTS(108M) HTTP/1.1
Accept-Language: en-US
DataServiceVersion: 2.0
MaxDataServiceVersion: 2.0
--batch_mybatch--








 

 

The above request is fired against an OData service, running on SMP 3.0 SP10

https://localhost:8083/gateway/odata/SAP/<MYSERVICE>/$batch

 

The Product entity is bound against a JDBC data source, which connects against a corresponding database.

 

 

 

 

2. Content ID referencing

 

A typical use case that is supported with this feature is the following:

 

Create an entry

Take this newly created entry, follow a navigation link and create an entry for that resource as well

 

Example:

A typical use case is that a user of a webshop chooses an article and puts it into a cart.

Behind the scene, a SalesOrder is created and for this newly created SalesOrder a LineItem is created

 

These 2 steps are normally performed by 2 HTTP POST requests:

One POST request for the first entry, then read the response and construct the URL to fire the second POST, based on that URL.

However, it is better to  perform both requests in one single $batch request.

But the problem is that the second POST request depends on the result of the first, because usually the ID of a SalesOrder is generated by the backend system and cannot be hardcoded in the $batch body.

Now there’s a solution available for this problem: the Content-ID

 

The handling is the following:

Inside the $batch request body, the first POST operation is tagged with a Content-ID header and a value that serves as ID

The second POST  request can use this ID as a variable that represents a resource segment in the URI

Such that, following the above example, the URI for the second request, the creation of a LineItem would be constructed as follows

POST $1/LineItems

where the $1 represents the SalesOrder that has been created before.

 

Please note that both requests, the one that defines a Content-ID and the one that uses it, have to be part of the same Changeset

 

Please find below a full sample request body for a $batch request.

The request body contains

- one changeset with 2 operations, where the first POST creates a Product and the second POST creates the customer belonging to this product.
   The header section of the first operation defines the Content-ID

   The URI of the second POST is constructed using the Content-ID reference, followed by the navigation property name.

- afterwards we perform a GET request to the newly created product. Here a Content-ID cannot be referenced, so we use the hardcoded productID

 

Please note that other kind of usage of the Content-ID reference is not supported in SP10

 

--batch_mybatch
Content-Type: multipart/mixed; boundary=changeset_mychangeset1
--changeset_mychangeset1
Content-Type: application/http
Content-Transfer-Encoding: binary
POST Products HTTP/1.1
Content-Type: application/atom+xml
Content-ID: 1
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="https://localhost:8083/gateway/odata/SAP/JDBC_SP10;v=1/">
    <content type="application/xml">
        <m:properties>
            <d:PRODUCTID>111</d:PRODUCTID>
            <d:NAME>Product111</d:NAME>
            <d:DESCRIPTION>ProductDesc111</d:DESCRIPTION>
            <d:RATING>111</d:RATING>
            <d:PRICE>111.11</d:PRICE>
            <d:CATEGORYID>1</d:CATEGORYID>
            <d:SUPPLIERID>20</d:SUPPLIERID>
            <d:ISDELETED>0</d:ISDELETED>
        </m:properties>
    </content>
</entry>
--changeset_mychangeset1
Content-Type: application/http
Content-Transfer-Encoding: binary
POST $1/Customers HTTP/1.1
Content-Type: application/atom+xml
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="https://localhost:8083/gateway/odata/SAP/JDBC_SP10;v=1/">
    <content type="application/xml">
        <m:properties>
            <d:CUSTOMERID>111</d:CUSTOMERID>
            <d:PRODUCTID>111</d:PRODUCTID>
            <d:NAME>Customer111</d:NAME>
            <d:AGE>111</d:AGE>
        </m:properties>
    </content>
</entry>
--changeset_mychangeset1--
--batch_mybatch
Content-Type: application/http
Content-Transfer-Encoding: binary
GET Products(111M) HTTP/1.1
Accept-Language: en-US
DataServiceVersion: 2.0
MaxDataServiceVersion: 2.0
--batch_mybatch--






 

The above request body is fired against an OData service, running on SMP 3.0 SP10 :

https://localhost:8083/gateway/odata/SAP/<MYSERVICE>/$batch

 

As a result, we can see the newly created Product and the newly created Customer entry

 

The Product entity is bound against a JDBC data source, which connects against a corresponding database.

The model looks as follows:

 

 

Please note that a referential constraint is necessary, where the PRODUCTID key property of the Product is mapped against the PRODUCTID property of the Customer:

 

 

 

By the way, please note that a POST request on a navigation property is not supported outside of a $batch request (in SP10).

I mean, it is not supported in SP 10 to fire a POST request to a URL like:

https://localhost:8083/gateway/odata/SAP/<MYSERVICE>/Products('11')/Customers

It is only possible as an inner request of a $batch request, as shown above

 

 

 

Summary

 

Since SMP 3.0 SP10, the Integration Gateway component supports multiple operations in one change set of a $batch request for the JDBC data source.

This feature includes the 2 following capabilities:

- if an operation in one change set fails, the complete change set is rolled back

- the result of a POST can be referenced via a Content-ID by a subsequent request in the same change set

iOS


1) Create Kapsel project or a Fiori Client application from the latest SMP SDK.

2) Set the following parameter in the LogonInit function.


setAutoSelectCertifcateConfig( autoSelectSingleCert)

parameters:

autoSelectSingleCert: boolean type

Fiori Client can specify this property as a new fiori url parameter "autoSelectSingleCert". It can also be specify in appconfig.js

2) Fiori Client specification:-

For fiori client, the above settings will be persisted in the similar way as other application settings by apppreference plugin, such as DemoMode.

The logon plugin will not need to have any change for this feature.

For regular kapsel project, the index.js should call the authproxy js setAutoSelectCertifcateConfig method in onDeviceReady method.

4. How to test :-

4.1 Set fiori configuration to enable autoSelectSingleCert.

For fiori client, update appconfig.js to set autoSelectSingleCert to true as below,

      "autoSelectSingleCert": true,

or set the fiori url with the parameter of  "autoSelectSingleCert=false"


4.2 disable logon to provide cert automatically

    A new authproxy preference CredentialSearchSource in config.xml can be used to test this feature with logon plugin afaria certificate provide, it your testing gets the client certificate from a different way other than from logon plugin, then you do not need to use the CredentialSearchSource setting for the testing.

First set CredentialSearchSource to 5 in config.xml as

    <preference name="CredentialSearchSource" value="5" />

and then do an afaria NO-SMP certificate registration, setting CredentialSearchSource to 5 will skip logon plugin as credential source when authproxy tries to get the client certificate for mutual auth.

X.509 Authentication in Windows 8.1 or Windows 10 Apps

 

In the last months I was involved in several projects where a hybrid application (based on Cordova and SAP's Kapsel plugins) should use client certificates (X.509 certificate) to authenticate against the server. Most of the projects targeted iOS or Android, but recently more and more customers start asking for solution on Windows 8.1 (or Windows 10).


I want to use this blog post to describe how you can enable certificate-based authentication in a Windows Kapsel application.

 

 

User Certificates in Windows Store Apps

 

When talking about certificate handling we strictly should separate between the different mobile operating systems, because the behavior is slightly different. On iOS user certificates which are in the system keychain cannot be used by your custom applications, thus there you have to insert them into an app keychain. On Android user certificates can be requested directly from the system keystore. The app keystore concept is quite new and not yet fully adopted. On Windows we have a mixture of both concepts (which is quite nice in my opinion. We can make use on an user certificate which is stored in the system keystore, but we also can use a local app keystore which is part of our Windows 8.1 or Win 10 App.

 

What all three operating system have in common is the realization of a "Custom Certificate Provider" extension concept, which is part of SMP's Kapsel SDK. Because the SAP FioriClient is also built on top of this KapselSDK, we can also use this way when we want to use the custom FioriClient. Additionally we can also use this concept when using the KapselSDK (or FioriClient) without SMP, thus when we are directly connecting to SAP Gateway.

 

The idea of the custom certificate provider is the following:

If we start the app the Kapsel Logon plugin will trigger the Logon operation (or when using SMP the onboarding task). When triggering this logon operation we can provide a context where we specify connection and authentication properties. These properties can be defined inside the app, but they could also be retrieved dynamically on runtime, e.g. by using MobilePlace. When we want to use a custom certificate provider, we have to specify here an identifier string. This string is used later to retrieve a class name. this class has to implement the ICertificateProvider interface and will be called by the LogonPlugin to load the certificate. We can implement this class and specify where to get the user certificate. On iOS and Android this Custom Certificate provider is always called if the LogonPlugin or the AuthProxy plugin have to handle a certificate authentication challenge. On Windows this custom provider is called once when the app starts and is responsible for retrieving the certificate and storing it into the app's internal keystore. Later the app will load this cert directly from the keystore.

 

To summarize the possibilities on Windows:

  1. Using System KeyStore
    If the user certificate got imported into the system keystore it can be used directly inside a cordova based kapsel app. There is no additional action needed.

  2. Using App KeyStore (with Custom Certificate Provider)
    If the user certificate is not available inside the system keystore (maybe due to security reasons), the user certificate can be imported into the app keystore at app runtime. For this purpose the Kapsel SDK provides a certificate provider extension that can be implemented.

 

Even if possibility 1) is trivial, I recommend that you are trying this first, because this will ensure that your environment is setup correctly for certificate usage.

 

Preparation

In the following I will describe the two ways mentioned in previous section. For that we need to prepare a cordova app project as well as configuration on SMP.

 

I will extend the sample application which I created for this blog post http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/12/13/developing-kapsel-apps-with-visual-studio

 

If you do not know how to create a cordova project and how to work with Visual Studio I would recommend that you are walking through the linked blog post first. Otherwise you can directly download the sample app (which I will use in this tutorial) from folder “KapselSampleRefSmall” of my github repository: https://github.com/MarvinHoffmann/kapsel and place it into your cordova www folder.

 

Please prepare a user certificate for testing purpose. I will not describe here how to create one, but you can use for example the command line utility openssl or the GUI tool XCA which can simulate a CA (my preferred tool for certificate creation and signing).


SMP Configuration

 

I created a sample application configuration on my SMP server. I named the hybrid app "com.sap.mit.mutualtest", provided any OData endpoint address ( http://services.odata.org/V2/Northwind/Northwind.svc/ ) and a new security profile.

01_mutual_test_app.png

Important is that the X.509 Authentication Provider is added to the security profile. You can tell SMP that e.g. the common name (CN) should be the user's name when registering the connection at SMP.

02_mutual_test_app_security_profile.png


Using User Certificates from System KeyStore

 

As said already we should start performing mutual authentication with a user certificate that we placed in advance into the system keystore. When the test after that is successful we know that our environment is setup correctly for handling certificates.

 

Import of User Certificate

 

At first import your user certificate into the System KeyStore, e.g. by opening your pfx or p12 user certificate file and following the wizard steps

03_import_user_cert.png

You can verify in Certificate Manager if the user certificate got imported into the right store. For this choose "Run" and execute "certmgr.msc".

04_certmgr.msc.png

User Certificates should be stored in folder Personal > Certificates

05_certmgr.msc_personal.png

Mutual Authentication Connection Test

Before testing any real app you can verify the user certificate and also your system setup you can call the SMP server on its mutual auth port directly inside the browser. The standard port (if not changed during installation) for mutual authentication is port 8082, so make sure that you are calling this port. If everything works fine the browser should ask you for your certificate

06_smp_mutual.png

After that the page is loaded. In case of wrong or no user certificates you will not be able to see this page

07_smp_mutual_working.png

It is working...

 

Using User Certificates in Windows Store Apps

 

As mentioned above we want to extend an existing cordova Kapsel app, so that user certificates are used for authentication. Because User certificate handling is happening in the native part of the container, we should open the Windows platform Cordova project. You can find the project in folder platforms/windows.

08_Cordova_Visual_Studio_Project.png

You can now see the different Windows projects (e.g. one for Win Phone, Win 8.1 and Win 10). In File utils/SMPHelper.js we need to adjust the server related settings. Add the correct appId, enable https and add the mutual-auth port (default: 8082). Be aware when changing the www content here you are changing the content directly inside the platform directory!. So if you are developing for different OS or if you are working with Cordova prepare, better edit directly cordova's main www folder.

09_test_kapsel_app.png

The following step is only required if your SMP server is using a self signed certificate, because then you need to specify explicitly that your windows app should trust this server certificate. Get your server's certificate and copy it into your project (e.g. in a folder called assets). Now open the package.windows.appxmanifest file and switch to tab "Declarations". In the dropdown menu choose "Certificates" and add it. Then click on "Add New", specify as certificate store "Root" and choose as content the path to your server certificate. then save everthing.

10_appmanifest_self_signed_certificate.png

Start/Run your app. The Logon screen should appear. After clicking on "Register" (button at right top corner) you should get a message that is informing you that your user certificate is requested by our app.

11_allow_cert.png

After confirmation you should see the app passcode screen and thus the app registration via mutual authentication was successful.

12_reg_success.png

 

Using User Certificate from App KeyStore (Kapsel Custom Certificate Provider)

 

In this scenario we do not have a user certificate in our system keystore. So if you executed the last sample, do not forget to delete your user certificate from the system keystore again. We will create a custom certificate provider that will be used to insert a user certificate into the app's own keychain. In this example we will load the user certificate from a folder. Of course in real scenarios you would load the user certificate e.g. from a smart card, an MDM solution or some other source.

 

1. At first we will adjust the context object which will be an input parameter for the logon method (sap.Logon.startLogonInit). Here we have to tell the logon plugin that it should call our custom certificate provider. The given string will be mapped later against a value which defines our native class implementing the certificate provider interface.

13_add_mapping_customcert_string.png

2. As said we need to map this given string with a real class name. The mapping is based on a resource file called "AppMetadata.resjson". Thus we need to create this file. Right click on the target Cordova project, e.g. for Windows 8.1 and choose "Add > New Item..."

13_add_appmetadata.resjson.png

3. Create a file called "Resources File (.resjson) and name it "AppMetadata.resjson".

15_AppMetadata.resjon.png

4. Add the following content into the resource file. The key value (left part of the string) is the identifier which realizes the mapping to our class "CertProviderWRC.FileCertificateProvider" . In next step we have to create this class.

 

 

{
"com.sap.mit.certprovider":"CertProviderWRC.FileCertificateProvider,FileCertificateProvider,ContentType=WindowsRuntime"
}

16_AppMetadata_cert_string.png

5. We will create a Windows Runtime Component in which we realize our custom certificate provider as a C# class. For that right click on the base project and choose "Add > New Project..."

17_new_project_for_certificate_provider.png

6. Then choose "Visual C# > Windows > Windows 8 > Windows > Windows Runtime Component (Windows 8.1)" and name it "CertProviderWRC".

18_create_windows8_runtime_component.png

7. Before we can start implementing our provider we need to add a reference to SAP's Certificate Provider. SAP's native Windows libraries are packaged in NuGet packages, so we can simply add the SAP.CertificateProvider NuGet package. Right click on "References" of project CertProviderWRC and choose "Manage NuGet Packages..."

 

19_Manage_NuGet_Packages.png

8. the first time you need to add the directory which contains the Windows native libraries (nupkg files) as a new package source. Usually you can find these files inside your MobileSDK installation in \NativeSDK\ODataFramework\Windows\

20_Manage_NuGet_Packages_add_kapsel.png

9. After adding the package source you can use it to see the available NuGet packages. Simply install the SAP.CertificateProvider package.

21_Install_SAP_Certificate_Provider_NuGet.png

 

10. Now we can insert our C# class called "FileCertificateProvider" into the project "CertProviderWRC". I added the source for this certificate provider sample to my github. You can download/clone it here: https://github.com/MarvinHoffmann/kapsel in folder KapselCertificateProviderWindows. Copy this class into the "CertProviderWRC" project. This sample provider is reading a user certificate from within the app itself and storing it into the app's keychain. Thus you need to copy your user certificate into the assets folder and adapt the name and the password of the certificate inside the CertificateProvider class.

22_add_certprovider_c_sharp_class.png

11. Finally we need to reference the CertProviderWRC project. So right click on "CordovApp.Windows > References" and choose "Add Reference"

23_reference_cert_provider_project.png

12. And select the CertProviderWRC project

24_reference_certproviderwrc.png

13. Because by default the debugging is set to "Script only" you cannot see the output produce by our native Certificate Provider class. Thus it makes sense to change the debugging mode. After that setting break points and debugging the certificate provider is possible as well (right click on project and choose "Properties". Then set the "Debugger Type" to "Mixed (Managed and Native)"

 

xx_01_debugging_native.png

14. Start/run your app and check your "Output" window. (If you have the same app previously already installed, it might be better to uninstall the old app). You should see the log entries produced by our certificate provider

25_cert_ready_output.png

15. The app registration is successfully finished and the testapp can be used to send some requests and receive data by using mutual authentication

26_mutual_authentication_successful_app_screen.png

 

Logo.png

Hybrid Application Development with Visual Studio

 

 

Introduction

 

As you might heard Visual Studio is now also able to create and handle Cordova based applications. I watched some live demos and presentations on SAP TechEd 2015 in Barcelona. After that I tested the feature a bit in combination with SAP’s preferred solution for hybrid applications (cordova + Kapsel). I have to say that I was positively surprised about the way Microsoft integrated Cordova handling into the IDE.

 

Currently there is a cooperation between SAP’s mobile development teams and Microsoft going on, you can also read more about that, e.g. here: SAP Fiori Mobile Apps and Tools for Apache Cordova - The Visual Studio Blog - Site Home - MSDN Blogs

 

In the following I want to describe shortly how you can develop a Cordova based app which includes Kapsel plugins inside Visual Studio. Besides Windows as target platform (e.g. Win Phone, Win 8.1 or Win10), Android and iOS are also supported. Android and iOS apps can be executed on a web-based emulator (Ripple). Additionally, Android apps can be also executed on an x86 based emulator. Anyway, in the following I will primarily focus on hybrid apps for Windows 8.1 or Windows 10.

 

Preparation

 

I was using following versions

 

  • Visual Studio 2015 (Version 14.0)
    • There is a Community Edition available which is sufficient for hybrid app development, but please consider the license agreement
    • When installing Visual Studio make sure that you also checked the “HTML/JavaScript (Apache Cordova)” Option under option “Cross Platform Mobile Development”. Make also sure that you have installed all required components that are needed by Codova, e.g. nodejs, Java, GIT… If not you can also do this by using the Visual Studio installer…
      01_Prep_Visual_Studio.png
  • Kapsel SDK (SMP SDK SP10PL06)
  • Sample Kapsel SAPUI5 app from my GitHub repository (detailed steps are described below)
    https://github.com/MarvinHoffmann/kapsel
  • SMP Server SP09
    • Create an application on SMP (or HCPms) server which you can use later for connecting to. In my case the application is called “com.sap.mit.sample” and the endpoint is a simple OData sample service (http://services.odata.org/V2/Northwind/Northwind.svc/ ) SSO Mechanism is not really needed, because the OData service is not authentication protected. 02_smp_app_config.png
      For Authentication I created a new security profile with a “System Login” authentication provider. Where I hardcoded a user named “Marvin” with password “test”. Do not use System Login modules in productive scenarios. This is only for testing purpose, because this tutorial does not cover security related topics.
      03_smp_app_sec.png

Creating Cordova App in Visual Studio

 

  1. Open Visual Studio 2015
  2. Choose File > New > Project… and choose template “Apache Cordova Apps” which is under option “JavaScript”. Highlight “Blank App (Apache Cordova)”, provide name and solution, then click on “OK”.04_visual_studio_cordova_project_creation.png
  3. After the project got created, you can choose on which device type you want to execute this hybrid app (e.g. Android, iOS or Windows).05_visual_studio_project_created.png
  4. Choose “Debug” with “Windows-x64” as Solution Platform, then run on the “Simulator”.
    06_visual_studio_project_run.png
    On first time execution the platform “windows” will be added to the project (You can see this inside the Console Output. After that the simulator will start and will execute a “Hello-World” application.07_cordova_sample_app_run.png
  5. We have a Cordova based application running. In the next step we should add Kapsel plugins, like the Logon and Logger plugin.

 

Adding Kapsel plugins

 

We now want to add some of SAP’s plugins, e.g. Logon and Logger plugin.

 

  1. As a prerequisite we have to tell Visual Studio where to find the Kapsel plugins, so that they can be integrated (during compile time). For that you can add a file named “config.json” in folder “.cordova” in the root of your project and add the following as content:
{
      "plugin_search_path": "C:\\Development\\KapselSDKSP10PL06\\plugins"
}

Of course the path here needs to be adapted to fit your environment, e.g. C:\SAP\MobileSDK3\KapselSDK\plugins

Be careful here, that there are no additional characters are added or a wrong encoding has been used, for best results create the config.json file with notepad and then copy the whole file into the “.cordova” folder. If there is a problem the compiler will directly throw an exception (e.g. Illegal character, or Parsing error, …)

08_prerequesite_plugin_search_path.png

If you skip this step you will run later (on compile time) into the error

 

Registry returned 404 for GET on https://registry.npmjs.org/kapsel-plugin-corelibs

 

2. We can add the kapsel plugins directly in Visual Studio. For that double click on config.xml which will load the cordova specific settings window and open menu entry “Plugins”

09_config_plugins.png

3. Now choose tab “Custom”, select option “local” and then navigate to the plugin you want to add, e.g. C:\SAP\MobileSDK3\KapselSDK\plugins\logon . After that click on “Add”.

10_kapsel_plugin_add.png

4. You can do the same for other plugins, e.g. the Logger plugin. On tab “Installed” you can see the currently installed custom plugins

11_installed_plugins.png

5. Now we need to add some coding to use these Kapsel plugins. For that you can clone my github kapsel repository ( https://github.com/MarvinHoffmann/kapsel ) or download the content as zip file. Copy the content of folder "KapselSampleRefSmall" into your www folder in Visual Studio. You can overwrite the existing index.html.

12_project_structure.png

6. The copied content is showing now how to start the Logon plugin. After successful onboarding a sample sapui5 based app is shown where we can perform some test operations, like sending a request. We will use the SAPUI5 resources and also the jquery version which got bundled together with the logon plugin. In this way we do not need to add these resources separately.

13_index_html.png

7. In file SMPHelper.js the app is getting initialized and also configured. So make sure that you are adapting the values here according to your environment:

13_smphelper_connection_details.png

8. Cordova will throw an event called “deviceready”. We set an event listener here and specify that the method “init” should be called if Cordova throws this event. In the init method we will call

 

sap.Logon.startLogonInit(context, appDelegate);

This will then start the LogonPlugin initialization process. When executing this first time ever (initial app run) a Logon UI is shown to the user. On subsequent app starts the successcallback is called directly without showing a UI to the user.

 

9. When running the app now specify your user and password into the logon screen. In my case I entered user “Marvin” and password “test”

14_app_execution.png

10. After clicking on “Registration” in the right top corner the logon plugin tries to perform a registration against the given SMP (or HCPms). If successful the following screen will be shown where the user can choose an app specific passcode. These passcode settings can be controlled by the app settings on SMP server. If no passcode policy is defined user can click here on “Disable app-passcode” and then again on the button in the right top corner “Continue”.

15_app_passcode.png

11. The app will start. The logon (or registration) process is successfully finished. In this sample app I show the logon plugin’s return values in a text area.

16_app_registration_successful.png

12. You can also see on your SMP (or HCPms) server that the logon plugin created a new connection for our user:

17_smp_registration.png

13. We are finished and have our first windows Kapsel application running…


Logo.png

Actions

Filter Blog

By author:
By date:
By tag: