Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
CarlosRoggan
Product and Topic Expert
Product and Topic Expert

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

The OData specification describes the concept of navigation, in order to easily navigate from one resource to another one.


REST services can also support a kind of “navigation”, where the result set of one resource depends on another resource.

If we use such a REST service as data source in our Integration Gateway project, we can add the navigation capability to our OData service.

In this tutorial, I’d like to show an example for an easy-to-implement bidirectional navigation.

This tutorial is based on SMP SP07.

Update:

The sample REST service is now made available for public usage, so I've updated the tutorial to use it.

Also, the code is attached to this blog.

Prerequisites

I expect that you've gone through my previous tutorials , explaining REST data source, QUERY  and READ  operation.

Please check the Links section for more info.

Furthermore, you need:

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

Preparation

REST Service

For this tutorial, we need a REST service with at least 2 resources that supports that kind of “navigation”.

I’m using a service that is public available, you only need to sign up, afterwards you can access it with your SCN  user and password.

Please see the following document for details:

Getting started with the SAP Netweaver Gateway Service Consumption System

Finally, you should be able to access it via the following URL:

https://sapes1.sapdevcenter.com/sap/opu/rest/address/companies

Let’s have a look at how the backend-REST-service is used.

First, we invoke the URL for the “companies”:

https://scn.sap.com/https://sapes1.sapdevcenter.com/sap/opu/rest/address/companies

The result is a list of “companies”:

We can now choose one single “company”:


https://sapes1.sapdevcenter.com/sap/opu/rest/address/companies/2


The “company” is uniquely identified by the ID field, which is in the URL.

Now, this REST-service allows to add a segment to the URL, in order to retrieve the “contacts” that are responsible for this “company”:

https://sapes1.sapdevcenter.com/sap/opu/rest/address/companies/2/contacts

The result is a list of “contacts”, since there can be more than one “contact” for a “company”.

This is a one-to-many relationship.

In the details of a “contact”, we can see the ID of the “company” for which he is responsible.

After navigating from a “company” to its “contacts”, let’s now check the other way ‘round:

Open one single “contact”

https://sapes1.sapdevcenter.com/sap/opu/rest/address/contacts/46

Go to the “company” for which this “contact” is responsible:

https://sapes1.sapdevcenter.com/sap/opu/rest/addresshttps://sapes1.sapdevcenter.com/sap/opu/rest/address/contacts/46/company

Here we have a many-to-one relation, so the result is a single entry.

This company is the same that we’ve seen above.

Eclipse Project

We need a SAP Mobile Platform OData Implementation Project that already has the QUERY and READ operations implemented for both resources of the backend-REST-service.

And it should be up and running on our SMP server.

OData Model

The 2 EntityTypes are modeled according to the 2 resources of the backend REST service.

Additionally, we have an association between them.

Note that is has to be a bidirectional association, because we want to navigate from the "Company" to the "Contacts" and as well from a "Contact" to its "Company".


As we’ve seen above, a company can have many contacts, but a contact can only belong to one company.

So we specify a one-to-many relation.


And we create a Referential Constraint, which maps the property ID (of the Company) to the property COMPANY_ID (of the Contact)

That’s it for the modelling.

Next, we create the bindings and the Custom Code for QUERY and READ operations for both EntitySets.

Since this is explained in my previous blogs, we can skip all explanations about it.

Here are the relative URLs to be used in the Eclipse wizard:

QUERY companies/sap/opu/rest/address/companies
READ company/sap/opu/rest/address/companies/{ID}
QUERY contacts/sap/opu/rest/address/contacts
READ contact/sap/opu/rest/address/contacts/{ID}


Custom Code

After testing the QUERY and READ operations of our OData service at runtime, we can start taking care about the navigation.

Background

We invoke the URL for a company, e.g. https://localhost:8083/gateway/odata/<ns>/<service>/Companies('2')

In the response we can see that the Integration Gateway framework has generated a link element:

What happens, if we invoke that link?

https://localhost:8083/gateway/odata/<ns>/<service>/Companies('2')/ContactSet

Let’s try it.

Implementing the navigation for one-to-many relation

First we have to understand, that our script for the contact-QUERY is invoked.

The last segment of the URL is the ‘ContactSet’, which means that a collection of contacts is requested.

Therefore, when navigating from company to contacts, the contact-QUERY script is invoked (the default name Contacts_REST_Query.groovy)

Note that this is the case because we have a 1-to-many association between Company and Contact

First, the processRequestData() method is invoked.

And here we have to do the actual work.

The Integration Gateway framework doesn’t know how the backend REST service is designed.

Integration Gateway only knows that the user requested “give me the contacts which are relevant for the-company-with-ID-2”

But it doesn’t know how to get exactly this amount of data.

Therefore, we have to tell, how this is done, because we do know it: the URI has to look as follows:

companies/<valueOfTheID>/contacts

And we tell it in the processRequestData() method.

Summarizing:

Our task is to modify the backend-REST-service-URI that is called by Integration Gateway.



Implementing the processRequestData() method

We’re already familiar with such tasks.

We hock into the HTTP request, before it is fired agains the backend-REST-service.

In the processRequestData() method, we have to obtain the URI and modify it.

Note:

Our contact-QUERY script is invoked in both cases, with navigation and without navigation.

E.g.

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Contacts

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Companies('2')/ContactSet

Both URLs lead to our script being invoked.

So we have to consider it in our implementation.


Steps that have to be performed:

1) Obtain the UriInfo object

We need it in order to get the information about the navigation, which is in the URL.

     a) We need to know from where the navigation comes.

         In our example, we have only 2 EntityTypes, so the navigation starts always from a “Company”, but for other services it would be different.

         This is realized with the method UriInfo.getStartEntitySet()

     b) We need to know the ID of the “Company”, because depending on it, the amount of “Contacts” is different

        This information is contained in the UriInfo.getKeyPredicates()

2) Obtain the relative URI

We need it, in order to modify it.

This is how the URL that is invoked by the user of our OData service:

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Companies('2')/ContactSet

and this is how it has to look like when the backend-REST-service is called:

https://<host>:<port>/rest/address/companies/2/contacts

3) Set the relative URI

After correcting the relative URI, it has to be given back to Integration Gateway.

This is done by setting the header in the message object

And here’s the code snippet for our example.

def Message processRequestData(message) {

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

       // check if navigation is in place

    List<NavigationSegment> navSegments = uriInfo.getNavigationSegments();

       if(navSegments.size() < 1){

             // no navigation, just normal getEntitySet operation

             // this is the case for the call to

        //<...>localhost:8083/gateway/<...>/Contacts

             return message; // do nothing

    }else if (navSegments.size() > 1){

               /* this would be the case for e.g.

         * <...>/<srv>/Companies('2')/ContactSet('46')/Company/ContactSet

         * here the number of navSegments would be: 3

         * */

        log.logErrors(LogMessage.TechnicalError, "Not supported");

              return message; // ignore it for today

     }

       /* in our example, for the URL 

    * <...>localhost:8083/gateway/<...>/Companies('2')/ContactSet

    * the number of navSegments is: 1

    * */

       // handle the navigation

    String startEntitySetName = uriInfo.getStartEntitySet().getName();

       // we know that our EntityType has only one key field

    KeyPredicate keyPredicate = uriInfo.getKeyPredicates().get(0);

    String keyLiteral = keyPredicate.getLiteral();

       /* in our example, for the URL 

    * <...>localhost:8083/gateway/<...>/Companies('2')/ContactSet

    * the value of keyLiteral will be: 2

    * */

       // this is not really required for our service thathas only 2 EntityTypes:

       if(startEntitySetName.equals("Companies")){ 

    

       String relativeUri = (String)message.getHeaders().get("RelativeUri");

             /* in our example, the value is: 

        * /rest/addressbook/contacts

       */

             // here we're doing the actual work: modify the URI

       String targetRESTuri = relativeUri.replace(

                                 "contacts", "companies/" + keyLiteral + "/contacts");

    

             // finally, set the manipulated REST-service-URI for the navigation

       ((Message)message).setHeader("RelativeUri", targetRESTuri);

            /* in our example, the final URI has to look like this:

       *  /rest/addressbook/companies/2/contacts

       */

    }

       return message;

}

Implementing the processResponseData() method

In terms of navigation, nothing has to be done here.

This method is already implemented for the QUERY operation, we don’t need to change or add anything.

Result

After deploy and configure the service, the navigation can be tried via a URL like this:

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Companies('2')/ContactSet

The result should be the same like in the backend:

https://<host>:<port>/rest/address/companies/2/contacts

Implementing the navigation for many-to-one relation

The first navigation example was for the 1-to-many relationship.

My sample backend-REST-service also supports to ask a single “contact” for the “company” that he is responsible for.

This is a many-to-1 relationship.

In our OData model, we’ve described this with a bidirectional association.

The OData modeler tool has generated a NavigationProperty called “Company” in the EntityType “Contact”.

Which means that we can invoke a URL like this:

https://localhost:8083/gateway/odata/<ns>/<service>/Contacts('46')/Company

Which corresponds to a backend-REST-service-URL like this:

https://<host>:<port>/rest/address/contacts/46/company

The implementation is very similar, the difference is:

Since here we have a many-to-1 relation, the script that is called is the Company-READ.

Again the response doesn’t need to be changed, it is just the READ implementation that is used to represent the target entry.

As for the request, the implementation is very similar like above

def Message processRequestData(message) {

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

   List<NavigationSegment> navSegments = uriInfo.getNavigationSegments();

   if(navSegments.size() == 1){

    String startEntitySetName = uriInfo.getStartEntitySet().getName();

    String keyLiteral  = uriInfo.getKeyPredicates().get(0).getLiteral()

    

       if(startEntitySetName.equals("Contacts")){

         // in our example: /rest/addressbook/companies/46

      String relativeUri = (String)message.getHeaders().get("RelativeUri");

           

         // modify the URI

         // in our example, we need: /rest/addressbook/contacts/46/company

     String targetRESTuri = relativeUri.replace("companies", "contacts") + "/company";

         // finally, set the manipulated REST-service-URI for the navigation

      ((Message)message).setHeader("RelativeUri", targetRESTuri);

    }

  }

  return message;

}






Summary

In this tutorial, we’ve learned how to realize navigation between 2 entities, based on REST data source.

It has been easy to realize, because the used backend-REST-service supported a kind of navigation that was easy to adapt to the OData way.

For other REST services it might be more tricky.

Links

Installing SMP Toolkit:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/08/22/how-to-install-sap-m...

Tutorial for OData provisioning in SMP:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/06/10/creating-an-odata-se...

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

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

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/10/understanding-rest-d...

Introduction in REST data source part 2: Understanding the return structure in json
http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-...

Introduction in REST data source part 3: Implementing the QUERY operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/12/integration-gateway-...

Introduction in REST data source part 7: Implementing the READ operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-...

Overview of all REST blogs

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/04/08/integration-gateway-...

The official documentation: http://help.sap.com/mobile-platform/