Skip to Content

In the previous blog, we’ve discussed the different SDKs and APIs and how they relate to each other.

After so much code-free text, it is now time to get the hands dirty again and play around with the data source for OData V2.

And as usual, we’ll keep the first example as simple as possible.

 

Learning

In the present tutorial, you’ll be creating an OData V4 service exposing data which is fetched from an external service

This blog is part of the series of tutorials for beginners. (prerequisites are listed here)
All blogs were showing how to expose data.
Now, in the present blog, we’re going to learn how to fetch such data
That data is accessible via an OData V2 service
For accessing it, we’re using the data source library for OData V2 consumption
It’s easy !
We’re using destination, and even that is made easy when using the data source library !

 

Overview

This example is meant to just show how to use the Data Source API, so please don’t ask for a meaningful reason about why we’re doing what we’re doing.

What we’re doing:
Like we did in previous tutorials, we’re creating an OData V4 service which exposes data
In the implementation code of our service, we call a second OData service in order to fetch data
In our simple example, we don’t modify the data, we just pass it to the response of our service
In order to differentiate our service from the used service, we reduce the amount of properties.

That’s it.

 

The flow:

The end-user calls our V4 service
Our V4 service calls the V2 service
The V2 service responds with the requested data
Our V4 service receives the data and sends it in the response
The end user receives the data as result of his OData V4 request

 

 

Steps to be performed:

Find and explore an OData V2 service for consumption
Create a destination pointing to that service
Create OData V4 service, based on SAP Cloud Platform SDK for service development
Bind application to the destination service
Deploy application
Run and test the OData v4 service and compare the result with the OData V2 service

 

Preparation

OData V2 service for consumption

For this first simple example, I’ve thought of using the reference service which is advertised on the OData homepage.
It can be found navigating as follows

www.odata.org -> Developers -> Reference Services -> OData v2

Here we can see one service listed with name: OData
The service-URL is http://services.odata.org/V2/OData/OData.svc/
The service has an entity set called Products, which we can nicely consume.
I think this is the easiest way to try a scenario.
In the next tutorial we’ll cover the other operations and functionality

 

Destination

After we’ve decided which OData V2 service we want to call, we have to create a destination in the SAP Cloud Platform.

Please refer to the description in the prerequisites blog:
How to create a destination in SAP Cloud Platform, Cloud Foundry Environment

For the scenario in the present tutorial, the following information needs to be entered in the destination configuration:

Name odatarefservices
Type HTTP
Description Destination for reference services on odata.org
URL http://services.odata.org
Proxy Type Internet
Authentication NoAuthentication

 

 

Project

Nothing about project creation here (but there)
However, after generation of the project, we have to do a few changes to the manifest.yml file

 

manifest.yml

In the “Preparation” section, we’ve created the required service instances.
Now we have to bind our application to the service instances, otherwise they would be useless.

Binding a service instance to a deployed application can be done on Cloud Foundry via the cockpit, or using the command line client.

However, it is more comfortable, to declare the service bindings in the manifest file of the application, even before first deployment.

 

Service usage:

In the manifest.yml file, we declare the usage of existing Cloud Foundry services. In our case, it is the destination service and the xsuaa service which we created beforehand.
During deployment to Cloud Foundry, our app will be bound to the service instances.
As such, these service instances need to be created beforehand, otherwise the deployment will fail.

These are the lines which we need to add to the manifest.yml file which is generated along with our project:

    services:
      - demoxsuaa
      - demodestination

 

Note:
The lines below “services” are meant to list the required service instances.
The names which are entered here, have to match exactly the names of the instances, as created in the preparation section.
Otherwise, the log will print meaningful error messages

 

Note:
If the creation wizard has asked for hdi-container-service name, then the generated manifest will contain that entry under “services”.
In that case, you need to delete that entry, otherwise the deployment will fail if that service instance is not existing.

 

Un-Security:

Remember: in our implementation we’re going to reach out to an existing OData V2 service. For that, we’re using a convenience API.
The OData V2 Data Source API has a security setting which enforces the usage of JWT token.
See here.
However, for simple prototyping use cases it can be switched off.
This is done via an environment variable.
It can be set directly in CloudFoundry in the browser UI, or via command line.

But it can also be set in the manifest file, which makes life easier.
This is the relevant line:

    env:
      ALLOW_MOCKED_AUTH_HEADER: 'true'

 

Note:
Although it is not really required, you may add an entry called “host” to the manifest.
The value of host will be part of the URL of the deployed application.
If the entry is missing, then just the application name will be used.
Example:

host: demoproject

 

 

Note:
You’ve already found that editing the manifest.yml file in a text editor is a – say – delicate operation.
Every little space can damage the file.
You should make sure to use the yml editor in eclipse to avoid problems. The editor at least gives error markers if the indentation is not correct.
Specially copying such lines from a browser window can lead to problems.
When doing copy&paste, I think it is a good practice to make sure that the copied text doesn’t contain any formatting metadata, before pasting it into the manifest file. To do so, copy the text from browser into clipboard, then paste it into a notepad editor, then copy it again from there and paste it into the manifest file.

 

 

Model

As usual, we create an xml file which defines the OData model of our OData V4 service which we want to create and provision.
In this example, our procedure is lightly different. We don’t specify arbitrary property names according to our wish.
Instead, we create our model strictly according to the OData V2 service which we want to consume.

Let’s open the metadata document of the OData V2 service, which acts as our backend:

http://services.odata.org/V2/OData/OData.svc/$metadata

The service defines an EntityType called Product, which we want to reuse:

 

<EntityType Name="Product">
  <Key>
    <PropertyRef Name="ID"/>
  </Key>
  <Property Name="ID" Type="Edm.Int32" Nullable="false"/>
  <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="false"/>
  <Property Name="Description" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationSummary" m:FC_ContentKind="text" m:FC_KeepInContent="false"/>
  <Property Name="ReleaseDate" Type="Edm.DateTime" Nullable="false"/>
  <Property Name="DiscontinuedDate" Type="Edm.DateTime" Nullable="true"/>
  <Property Name="Rating" Type="Edm.Int32" Nullable="false"/>
  <Property Name="Price" Type="Edm.Decimal" Nullable="false"/>
  <NavigationProperty Name="Category" Relationship="ODataDemo.Product_Category_Category_Products" FromRole="Product_Category" ToRole="Category_Products"/>
  <NavigationProperty Name="Supplier" Relationship="ODataDemo.Product_Supplier_Supplier_Products" FromRole="Product_Supplier" ToRole="Supplier_Products"/>
</EntityType>

 

For today, we ignore all the facets, attributes and navigation properties, and we create our own model with identical property names:

<EntityType Name="Product">
  <Key>
    <PropertyRef Name="ID" />
  </Key>
  <Property Name="ID" Type="Edm.Int32"/>
  <Property Name="Name" Type="Edm.String"/>
  <Property Name="Description" Type="Edm.String"/>
</EntityType>

 

Note:
Why we don’t use all properties?
Because we want to show that we can have different model in our V4 service, than the used V2 service

Note:
Why do we have to stick to equal property names?
Because like that, the result of the call to V2 can be automatically mapped to our V4 response data.
It would be possible to invent different property names, but then it would be required to do manual mapping in the Java code.

 

The full edmx file can be found at the end of this page

 

Implementation

Now we’re coming to the interesting part.

First of all, you will ask yourself:
“I’m using a new Data Source API, so which dependency do I need to add to my pom?”

The user-friendly answer is: nothing needs to be added.

The dependency comes out-of-the-box, because the data source libraries are delivered under the umbrella of the SAP Cloud Platform SDK for service development

 

Coming to coding.

We’re implementing the QUERY operation only.
As you know, we need to provide a list of maps to the response object (let’s ignore the other formats, POJO and EntityData, for now)
Instead of manually creating an ArrayList and a HashMap with some dummy mock data, this time we get real (sample) data from the (real) OData V2 service.

This is the URL to get the desired data:

http://services.odata.org/V2/OData/OData.svc/Products 

And this is how how it is called by using the API:

ODataQuery odataQuery = ODataQueryBuilder.withEntity("/V2/OData/OData.svc", "Products").build();
ODataQueryResult result = odataQuery.execute("odatarefservices");		

 

As usual, the API follows the Builder-pattern and the Builder has to be configured with:

The service name, including the path: “/V2/OData/OData.svc”
The name of the EntitySet to call: “Products”

When invoking the execute, the name of the destination which is configured in the Cloud, has to be passed: “odatarefservices”

Remember? Above, we’ve created a destination and the value of the URL is:
http://services.odata.org

Under the hood, the destination is resolved and the full URL is computed, such that it matches the desired full URL http://services.odata.org/V2/OData/OData.svc/Products

 

On successful execution, the result of this call can be found in the ODataQueryResult instance.
If an error occurs, which can easily happen – unfortunately….  – then an exception is thrown, which we need to catch (see below)
The ODataQueryResult object has some convenience, we can ask it to present the result in one of the 3 different formats of data.
In our simple example, we use HashMap, which is most simple.

List<Map<String,Object>> productList = result.asListOfMaps();

BTW, these 3 lines can be chained into one line:

List<Map<String,Object>> productList = ODataQueryBuilder
                                         .withEntity("/V2/OData/OData.svc", "Products")
                                         .build()
                                         .execute("odatarefservices")
                                         .asListOfMaps();		

 

This list is the result of the call to the OData V2 service, parsed and converted to list of map.
Usually in a productive scenario, we would do some modifications on this V2 service result.

Why?

Because otherwise, the end-user could directly use the V2 service and wouldn’t look for our V4 service.

But we don’t do anything in our example, we just pass the list to our response object:

return QueryResponse.setSuccess().setDataAsMap(productList).response();

 

Note:
As you know, the keys of the entries of the Maps in the response have to strictly match the definition of our metadata in the edmx file.
But since we’ve taken care to name our properties exactly like names of the V2 service, we can just pass the maps like they are.
Just one thing we’re modifying: we’re reducing the number of properties, our V4 service has less properties than the response of the V2 service.
With other words: we’re passing a list of maps to the framework and each map contains properties which don’t exist in our V4 service (e.g. ReleaseDate, etc)
However, the FWK doesn’t care if there are more properties than expected, as long as it finds those properties which are required.

 

That’s it.

We’ve created our first OData V4 service with real data from a real backend (somewhat real, as it is also sample content…)

 

 

Deploy

Make sure that your manifest.yml file looks similar to the sample file which I’ve pasted at the end of this page. As usual, you might need to modify the name of the host, as it might be already taken.

After build and push, you might want to check the application bindings, to see if the services have been properly resolved:
You can use the browser UI, or execute this command:

cf service demodestination

This command provides information about the service instance “demodestination” and you should  see that your deployed app is listed there

 

Run

If you’re lucky…..if everything is fine…. If the destination is configured properly….. if the backend is up and running….. if the network is healthy…… if the weather is nice….. well, if you’re just lucky, then you’ll be able to see the products list. You just need to call your OData V4 service URL:
e.g.

https://demoproject.cfapps.eu10.hana.ondemand.com/odata/v4/DemoService/Products

 

Result should look like this:

 

Comparing to the original service:

 

 

As you can expect, the existing framework capabilities are applicable here as well,
e.g. you can apply system query options which are working out of the box:

…/odata/v4/DemoService/Products?$top=1&$select=Name

 

Summary

You should have gained understanding of using a data source API in the implementation of your service.
You can differentiate the SAP Cloud Platform SDK for service development on one side – from the data source library on the other side.
Provisioning on one side, consumption on the other side.

If anything is not clear, please let me know.

In the next tutorial, we’ll make use of a different v2 service in order to show how the other operations are implemented and also to do some more modification.

 

Links

Overview of blog series and link collection

OData homepage: http://www.odata.org
OData reference services: http://www.odata.org/odata-services

 

Appendix 1: Source code of manifest file: manifest.yml

---
applications:
  - name: demoservice
    memory: 512M
    buildpack: sap_java_buildpack
    path: target/DemoService-0.0.1-SNAPSHOT.war  
    env:
      ALLOW_MOCKED_AUTH_HEADER: 'true'
    services:
      - demoxsuaa
      - demodestination

 

Appendix 2: Source code of model file: DemoService.xml

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
	<edmx:DataServices>
		<Schema Namespace="demo" xmlns="http://docs.oasis-open.org/odata/ns/edm">
			<EntityType Name="Product">
				<Key>
					<PropertyRef Name="ID" />
				</Key>
				<Property Name="ID" Type="Edm.Int32"/>
                <Property Name="Name" Type="Edm.String"/>
                <Property Name="Description" Type="Edm.String"/>
			</EntityType>
			<EntityContainer Name="container">
				<EntitySet Name="Products" EntityType="demo.Product"/>
			</EntityContainer>
		</Schema>
	</edmx:DataServices>
</edmx:Edmx>

 

Appendix 3: Source code of Java file: ServiceImplementation.java

 

package com.example.odata.DemoProject;

import java.util.List;
import java.util.Map;

import com.sap.cloud.sdk.odatav2.connectivity.ODataException;
import com.sap.cloud.sdk.odatav2.connectivity.ODataQueryBuilder;
import com.sap.cloud.sdk.odatav2.connectivity.ODataQueryResult;
import com.sap.cloud.sdk.service.prov.api.operations.Query;
import com.sap.cloud.sdk.service.prov.api.request.QueryRequest;
import com.sap.cloud.sdk.service.prov.api.response.ErrorResponse;
import com.sap.cloud.sdk.service.prov.api.response.QueryResponse;


public class ServiceImplementation {

	@Query(serviceName = "DemoService", entity = "Products")
	public QueryResponse getProducts(QueryRequest queryRequest) { 
		try {
			ODataQueryResult result = ODataQueryBuilder
					.withEntity("/V2/OData/OData.svc", "Products")
					.build()
					.execute("odatarefservices");
			List<Map<String,Object>> productList = result.asListOfMaps();
			
			return QueryResponse.setSuccess()
					.setDataAsMap(productList)
					.response();
			
		} catch (ODataException e) {
			return QueryResponse.setError(
					ErrorResponse.getBuilder()
					.setMessage("Error occurred while getting products from backend. See error log for details.")
					.setStatusCode(500)
					.response());
		}
	}
}
To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply