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
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
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
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
9. Test the API automatically
9.1. Create test fragment
9.2. Create JUnit tests
9.3. Run the tests
9.4. Summary
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? :smile:
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.
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
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
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);
}
}
}
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;
}
}
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:
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
24 | |
11 | |
11 | |
8 | |
7 | |
7 | |
7 | |
6 | |
5 | |
5 |