Skip to Content

OK, the first blog of this series was meant for those of you who don’t like reading – for this one you’ll need much more patience, as it will contain many many words….

Take a breath (and a coffee) and read on…

 

Who are we and why?

We are nice people
We are lazy people
We are encouraged people
We are Java-loving people
We are service-oriented people
We want to create OData services with Java
We want to benefit from the Cloud
We want to relax

 

Intro

Once upon a time…. The Olingo library was created, allowing to create OData services in Java

Nowadays… creating OData services is fast and easy (and explained…), using the SAP Cloud Platform SDK for service development (I will just call it “The SDK”)

Why?

Because it is shorter

No: Why is it fast and easy?
(and explained?)

Because: Less code (and nice tutorials explaining this few code)
Yes, the SDK removes the need of the famous boilerplate code.
It is a framework on top of Olingo (THE OData library for Java), trying to handle as much as possible, allowing the developer to focus on one main task: providing data.
The framework also provides generic functionality, such that we as developers don’t need to take care (e.g. $top, $select, $expand, etc)

More info can be found in this introduction blog and in SAP Help Portal

 

Learnings

The friendly reader of this blog will learn how to use the SAP Cloud Platform SDK for service development (“The SDK” how I call it),
And how to implement a very very basic READ operation for a first OData service

 

Project

Everything starts with the beginning – the creation of a project.
The SDK provides a maven archetype which makes it easier to create a (standard) web application project  with support for OData provisioning based on the SDK.
Therefore, the following description will be using Eclipse and Maven.

See here for prerequisites.

Follow this blog for a detailed description on how to create a project.

After we’ve created our project, the next step is to define our model.

 

Model

Why “Model” ?

Yes, we follow the model first approach.
We like to talk about cool things like data model. What we mean is just: data.
We mean data which is structured and has relationships.

For example:
We store data about people. People have in common: person with name and age and address and job etc.
Job has its own structure with name, category etc
If we look at a person, then we find its information about name etc, but the info about job we retrieve by navigating to the data for the respective job.
Does this help?

OK, the above example is a small data model and can be nicely defined with OData.
The data model is described with edmx

With other words:
We define an entity data model (EDM) and persist it in XML (edmx)
For that, we create a file with file extension .xml (see here)

Once we’ve created the xml file in the edmx folder, we can start to define the model in the appropriate format.

What is our intention?

For the first very simple OData service, we don’t want to lose time with complexity (as you can see, I’m already spending many words for the simple case…), so we choose a very simple model.

It has only one entity, nothing else.
And even this entity is so small that it contains only 2 properties.

You can think of it as one table which has 2 columns.

How does our mode look like?

I thought of choosing a very simple and human example: People

As such, our model contains one entity called Person
The person needs a unique id, such that it can be identified.
This id is a number, so we choose the data type integer.
In OData it is the primitive type Edm.Int32

The person is a human, so it has a human name
The name is human readable, so the data type is string
In terms of OData, it is Edm.String

Furthermore, in OData there’s a difference between the definition of a type (EntityType) – which is our Person – and accessing the instances, i.e., the data itself, at runtime. For that we need to specify a so-called  EntitySet. We call it People and it refers to the EntityType Person.

Why “People”?

Because data is provided as “set” or “list” of instances, hence the name of the entity set should be plural.

Oh – and one more thing:
The entity set must be wrapped by an entity container

Ups – even one last thing:
All of it is contained in a “Schema”

 

And here is how it looks like, when defined in edmx:

<?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="Person">
				<Key>
					<PropertyRef Name="UniqueId" />
				</Key>
				<Property Name="UniqueId" Type="Edm.Int32" />
				<Property Name="Name" Type="Edm.String" />
			</EntityType>
			<EntityContainer Name="container" >
				<EntitySet Name="People" EntityType="demo.Person" />
			</EntityContainer>
		</Schema>
	</edmx:DataServices>
</edmx:Edmx>

 

Now that you have understood this quite readable xml snippet, you can copy all of it and paste it into the DemoService.xml file which you have created in eclipse.

Don’t forget to save the file before closing.

Ups – Have you closed it?

My suggestion: better reopen it, as you’ll have to refer to it soon…

 

Code code code

Now we’re coming to our favorite part:
Writing code
Writing easy code
Writing easy code using convenient framework
Writing easy code using convenient framework and be superfast and enterprise ready and happy  and and and

Ok, I’m stopping with marketing  bullsh***

What is it about the code…?

1. First of all: No code
This “no code” statement is about the model. The model has been defined in xml
The framework will parse it and convert it into code.
I’m mentioning it, because if you’ve ever created a service based on Olingo, you must have written many many lines of code to define the model.

2. Secondly: the data
One thing is to define the OData model, i.e., the structure of the data that we want to provide with our service.
Obviously, more important is the actual data that we want to provide.
To follow our example: one thing is to have an address book which allows to enter people in a defined structured way. But more important is to have friends who we want to store in the address book and foes and other contacts.

The SDK aims to take over as much work from the developer as possible. But at the same time, let him have as much freedom and flexibility as possible.
This has been achieved with the SAP Cloud Platform SDK for service development.

Oh, sorry… now I promise that this has been the last marketing bu***

 

How does it work?

We don’t need to register anything anywhere, we don’t need to adhere to any convention rules, the only thing that is required: use a Java annotation.

anno-what?

You’ll see.

 

Now it is time to create a Java class. It may have any user-defined name, but it must be located in the generated empty package. See here for details.

In the new and freshly opened Java editor, create a method.

What method?

Just like for the Java class, again, you’re absolutely free to give any arbitrary name for this method.

This method is called by the framework whenever the user of our future OData service invokes a  URL which points to a single entity.
Such request is usually referred to as READ request.
We’ll come to that at the end of this blog.

So, the user fires an HTTP request and then the framework identifies which kind of request was executed by the user.
If it is a READ request, then the FWK will call a method which as a @Read annotation.
That’s why we have to add the @Read annotation to our method:

 

@Read(...)
public ... anyMethodName(...) {
    ...
}

 

But this is not enough, there must be some more information:
The service name and the entity set name
Since there can be many models and many entity sets (within a project), the framework needs to know, for which service and which resource our method is meant to be called.

So, when we write the code, it is meant for one specific entity type. However, there’s still some more information which we will need when writing advanced services, so the framework gives us an instance containing context information, in case we need it: the Request object

 

@Read(serviceName="DemoService", entity="People")
public ReadResponse getPerson(ReadRequest request) {
   ...
}

 

OK, enough introduction.

No, not enough.

 

In our example, our OData service is called by an external user who expects exactly one “Person” to be returned by our service.
So we have to provide that “Person” instance in our code.
It needs to have a structure just like we defined it in our model.
I mean it has an id and a name.
In Java, such structured data can be easily defined as a java.util.Map

The Map instance represents the entity type
The content of the map represents the properties. One Map-entry corresponds to one property.
Each entry of the map has
one key, which represents the property name
one value, which represents the property value

In our example, we need to create one Map with 2 entries, for the 2 properties.
The keys have to match exactly the text as defined in the xml.

That’s why I recommended to have the xml file open… 😉

Map<String, Object> map = new HashMap<String, Object>();
map.put("UniqueId", 1);
map.put("Name", "Kitty");

 

Enough intro ?

Sorry, one last point.

 

The method has a return value.
Of course, that’s how we provide the data to the framework.
The return value carries the instance of the Map.
In addition, our OData service will provide the external user with an HTTP response, which we can
configure.
In our example, we decide that the HTTP call is successful, and as such, the response body will contain one Person’s data.

About the data:
We want to keep our sample code very very simple, so our way to provide the data is to create some dummy data on the fly.
Of course, in a real scenario, the data would be fetched from a database or any other storage.

…?

OK, enough intro.

Here’s the complete method to be copied into your editor:
(see below for the full source code)

 

@Read(serviceName="DemoService", entity="People")
public ReadResponse getPerson(ReadRequest request) {
		
	Map<String, Object> map = new HashMap<String, Object>();
	map.put("UniqueId", 1);
	map.put("Name", "Kitty");
		
	return ReadResponse.setSuccess().setData(map).response();
}

 

Repeated explanation:

The user calls a URL like <service1>/entityset1(id1)
The FWK searches for a method with @Read annotation and attributes service1 and entityset1
The FWK invokes the method and passes an instance of ReadRequest carrying the info about id
The service developer collects the requested data for id1
The service developer, if necessary, converts the data into instance of java.util.Map
The service developer returns the data, if everything is fine

 

Build

After the coding is done, we have to build the Java project.
No surprise that Maven is used to build our project.

You can open a command prompt, navigate to your project folder and execute

mvn package

or for subsequent builds

mvn clean package

 

(of course you can also run mvn clean install)

 

 

Alternatively, you can open a command prompt directly in eclipse itself

 

and execute the build from there

 

 

Alternatively2, you can execute a maven build in Eclipse using the Maven integration

 

 

Then enter the goals: clean package
and press Apply to save the settings

 

 

After pressing Apply, under the hood a so-called “Launch Configuration” has been created.
You may verify it by clicking on “Run” from the main menu bar, then Run Configurations or “Debug Configurations”

 

Then you’ll see that eclipse has generated an entry under “Maven Build”, pointing to your “DemoService” project.

 

 

The advantage of this approach is that you can always re-launch the last launch operation by pressing Ctrl+F12 (or just F12 for debug)
(however, this has to be configured in Window->Preferences->Run/Debug->Launching and enable “Always launch the previously launched application”)

 

Now that we’ve learned 3 ways of building our project, it is enough.

After successful build, the expected DemoService.war file has been generated in the “target” folder of your project.
This file is what will be deployed onto the Java server in the cloud.

 

Deploy

Here we have to be careful…otherwise, this chapter might explode and become a never-ending story…
We cannot expect a full blown introduction in SAP Cloud Platform and Cloud Foundry and CLI here.
Sorry…
So please take care to get started into SAP Cloud Platform and Cloud Foundry environment.
You may refer to my “Prerequisites” blog.

For now, please let me be short and describe the straight-forward way, using the browser (instead of the CLI)

 

Logon to your cloud account

Assuming that you’ve gone through the Prerequisites blog, you have your account and your Cloud Foundry Space.
And you’ve logged in
And you’ve opened the “applications” overview screen.
And you can see a screen like this:

 

And you can see the “Deploy Application” button.
And you’re able to press it.
And you’re able to enter the path to your DemoService.war file located on your local file system:

And you can press “Deploy”
And you have some patience to wait for the deployment to be finished (don’t worry, it doesn’t take too long….)
And you’ve so much patience that you’ve seen the nice green “Started” button

And you’ve been happy and immediately clicked on the hyperlink, which is your application
And… now you may go ahead to the next section

 

Run

After clicking on the hyperlink which represents your Cloud Foundry application, you’re taken to the application details screen.
There you can see the application route:

 

A route is the URL to call the application.
An application can have zero or multiple routes, depending on the use case.

Click on the link, it will open in a separate browser window

 

 

However, this is not the URL of our OData service, it is just the web application.
So please do me the favor to append the following segments:

/odata/v4/

 

The result is not yet the URL of our service, but here the SDK is able to react and display a list of the detected OData services.

In our case it is just one:

 

 

 

So, finally… FINALLY !!! …  you can click on that link

https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/

and enjoy viewing your first OData service, created with THE SDK

This fantastic moment deserves a big screenshot:

 

 

What you’re viewing here is the so-called “Service document”, it displays information about the defined entity sets.
In our case it is just one, according to our small model.

Now I’d like to ask you to append the entity set name to the URL, and also the unique identifier of the entity which we’re providing in our service implementation.

So, the URL to invoke in the browser should look like this:
https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People(1)

and the result:

 

What we see is no surprise, it is the (little bit silly) data which we created in our service implementation.

But don’t allow anybody to spoil the party….

 

The joy is all yours…!

 

You’ve created your first OData service and it has been easy and you’ve enabled your users to access your data, as you intended and now…. NOW… now all the world is yours and you can provide whatever data you wish…!

YOU and THE SDK….

Together you can change the world….!

😉

 

 

Anyways…. The first thing you should do is…. To relax and to continue with the next blogs in this series…;-)

 

 

 

Summary

In this blog we’ve taken our time to slowly go through the process of creating an OData V4 service based on the SAP Cloud Platform SDK for service development.
We’ve defined a very very small OData model (really small) and created a very small implementation for the service.
We’ve focused on a very small aspect of providing data. One piece of data is provided as a HashMap, that’s it.

 

Outlook

However, there are more operations supported by REST and HTTP and there are many more concepts specified by OData.
We’ll cover much of it in the next blogs of this series.

Stay tuned and don’t get tired …. and if you get tired, then please relax and come back…!…;-)

 

Links

Overview of blog series and link collection

 

Appendix: Source code

For writing our sample OData service, we just had to touch 2 files.

In brief:
Generate the project via archetype
Create DemoService.xml files
Create ServiceImplementation.java file

 

Below you find the content of these 2 files

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="Person">
				<Key>
					<PropertyRef Name="UniqueId" />
				</Key>
				<Property Name="UniqueId" Type="Edm.Int32" />
				<Property Name="Name" Type="Edm.String" />
			</EntityType>
			<EntityContainer Name="container" >
				<EntitySet Name="People" EntityType="demo.Person" />
			</EntityContainer>
		</Schema>
	</edmx:DataServices>
</edmx:Edmx>

 

 

Java Class: ServiceImplementation.java

 

 

package com.example.odata.DemoService;

import java.util.HashMap;
import java.util.Map;

import com.sap.cloud.sdk.service.prov.api.operations.Read;
import com.sap.cloud.sdk.service.prov.api.request.ReadRequest;
import com.sap.cloud.sdk.service.prov.api.response.ReadResponse;

public class ServiceImplementation {

	@Read(serviceName="DemoService", entity="People")
	public ReadResponse getPerson(ReadRequest request) {
		
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("UniqueId", 1);
		map.put("Name", "Kitty");
		
		return ReadResponse.setSuccess().setData(map).response();
	}
	
}

 

 

To report this post you need to login first.

14 Comments

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

  1. Former Member

    Hi Carlos,

    thank you very much for this good/organized series of blogs. I am constantly checking for new updates.

    However, I have tried to do the simple tutorial to expose mock data as you have shown here and everything else works fine, except for when I ask for : https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People(1) it shows me an error:

    {

    • error:

      {

      • code“Operation is not supported.”,
      • message“Operation is not supported.”

      }

    }

     

    Can you please help me on this?

    Thanks. 🙂

    (1) 
    1. Carlos Roggan Post author

      Hello Emira,
      super, thank you for your feedback!! – and I’m glad that you’ve followed this 😉

      It is a good question – and I’m planning to create a posting to create such errors.

      You’re getting this error message because: the FWK doesn’t find the implementation for the executed request.

      Example:

      GET request to …/DemoService/People(1)  -> FWK searches for method with @Read annotation
      PUT request to …/DemoService/People(1)  -> FWK searches for method with @Update anno

      So either you have to create that implementation – or tell your users that the operation is just not supported.

      In case you’re sure that in fact you REALLY have implemented it…;-) Then these might be the reasons:

      1) Simple typo:
      If you have a typo in the attributes of the annotation (serviceName or entity)
      then the implementation is not correctly assigned to the invoked operation
      (The request URL DemoService/People(1)  contains the serviceName and the entity name)

      2) The implementation class MUST be located in the package which is scanned by the FWK.
      This package is generated by the wizard.
      This package is mentioned in the pom, it is a property (with comment).
      This package is referred from the web.xml, as a variable pointing to pom

      3) In case you’re deploying to embedded tomcat in Eclipse:
      then the variable in web.xml is not replaced by maven-build
      So, you have to manually replace the variable by the correct packageName, as given in pom

      Hope this helps!
      And never give up 😉
      Cheers,
      Carlos

       

      (1) 
      1. Former Member

        Hello Carlos,

        thank you very much for your fast and detailed comment. 😀 I went straight to the third scenario you described above and that worked for me. ^_^ I just replaced the variable in web.xml with the correct packageName.

        I am grateful for the time you are taking to show all the functionalities of the SDK in practice, in this series of blogs. Also, your style of writing is awesome and it makes reading much more fun!

        Looking forward for the rest of the blog’s series.

        Thanks,

        Emira.

        (1) 
        1. Carlos Roggan Post author

          GREAT ! I’m glad that it works now 😉
          And thanks a ton for encouraging to continue – that is really really helpful, really 😉

          (0) 
  2. Tri Minh Le

    Hello Carlos,

    I’ve enjoyed your blog pretty much.

    I just have some questions regarding to the SDK:

    1. Can the SDK generate oData V2? Or just only V4?
    2. How can we implement QUERY with filter options?
    3. Can we integrate the SDK with S/4HANA Cloud SDK?

    Thanks so much.

    Tri

    (0) 
    1. Carlos Roggan Post author

      Hi Tri,

      thanks for your feedback! 😉

      1: One of the goals of the SDK is to hide complextity from the user, such as e.g. the differences between OData V4 and V2, ideally protocol-agnostic
      As such, you can use the SDK for V2 as well
      You need a different archetype:
      com.sap.cloud.servicesdk.archetypes – odatav2 – 1.14.0

      The edmx must of course be conform to OData V2.
      The code should be portable

      2: this is not yet supported

      3: yes, absolutely true and makes much sense.
      The point is:
      Your service wants to provide any data in a structured way, based on OData.
      Where to get the data from? This is totally free.
      So you can implement in the e.g. QUERY method whatever you like, e.g. call the S4H backend using the S4H SDK, even the VDM is possible

      You need to add the dependency to your pom, then just use it.

      I’ll be posting a blog to showcase such scenario

      Kind Regards,

      Carlos

       

      (1) 
  3. Ivan Mirisola

    Hi Carlos,

    Great blog! Makes life easy when dealing with building services.

    Just one question:

    1. Is there an easy way to test the service locally before sending it to CF instance?

    Regards,
    Ivan

    (1) 
    1. Carlos Roggan Post author

      Hi Ivan, thanks for the feedback, I’m happy that it could help you.

      Wrt to your question:
      as long as you aren’t relying on Cloud-services, you can simply deploy the war file to local tomcat.
      It is a standard web application.

      All these simple samples with mocked data can be easily run in Eclipse using e.g. the tomcat server adapter.
      However, in this case, you have to do 2 manual changes:
      1.You have to manually replace the variable in web.xml by the correct packageName, as given in pom (see comment above)
      2. you might need to add the slf4j-dependencies to pom filw

      <dependencies>
      <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.22</version>
      <scope>compile</scope>
      </dependency>
      <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.22</version>
      <scope>compile</scope>
      </dependency>
      </dependencies>

      (0) 
      1. Ivan Mirisola

        Hi Carlos,

        Great! I did add the dependencies toguether with Neo SDK ver 3.49 and was able to deploy it locally on a Web Tomcat 8 (Neo) server installed by the sdk on my Eclipse.

        Many thanks!

        Regards,
        Ivan

        (1) 
        1. Oliver Merk

          Hi Ivan,

          I have read that you are using Neo SDK. Are you trying to deploy and run the application on SAP Neo? If yes, could you post your pom.xml?

          Kind regards
          Oliver

           

           

           

          (0) 
          1. Ivan Mirisola

            Hi Oliver,

            No, I am not trying to run it on Neo. I am just using the SDK to run tests on the Services SDK locally. Deploying to CF takes a long time just to “debug” simple things. However, if the app runs on local Web Tomcat 8 SDK from Neo, it should run as well on Neo itself. Problem is that you have to make modifications to your web.xml manually to change it to be compatible with Neo.

            Here my pom.xml:

            <?xml version="1.0" encoding="UTF-8"?>
            <project xmlns="http://maven.apache.org/POM/4.0.0"
            	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
            	<modelVersion>4.0.0</modelVersion>
            
            	<parent>
            		<groupId>com.sap.cloud.servicesdk.prov</groupId>
            		<artifactId>projects-parent</artifactId>
            		<version>1.13.1</version>
            	</parent>
            
            	<artifactId>DemoService</artifactId>
            	<groupId>com.example.odata</groupId>
            	<version>0.0.1-SNAPSHOT</version>
            
            	<packaging>war</packaging>
            	<name>Gateway Runtime Reference Content: Java Project for DevX</name>
            
            	<properties>
            		<!-- Name of the package containing the OData service provisioning code -->
            		<packageName>com.example.odata.DemoService</packageName>
            		<sap.gateway.version>1.13.1</sap.gateway.version>
            		<sap.cloud.sdk.version>3.49.11</sap.cloud.sdk.version>
            	</properties>
            	<dependencies>
            		<!-- SAP Cloud Platform SDK dependencies -->
            		<!-- Neo Java Web Tomcat 8 -->
            		<dependency>
            		    <groupId>com.sap.cloud</groupId>
            		    <artifactId>neo-java-web-sdk</artifactId>
            			<version>${sap.cloud.sdk.version}</version>
            			<scope>provided</scope>
            		</dependency>	
            		<dependency>
            			<groupId>org.slf4j</groupId>
            			<artifactId>slf4j-api</artifactId>
            			<scope>provided</scope>
            		</dependency>
            		<dependency>
            			<groupId>org.slf4j</groupId>
            			<artifactId>slf4j-simple</artifactId>
            			<scope>provided</scope>
            		</dependency>
            	</dependencies>
            	<build>
            		<finalName>${project.artifactId}-${project.version}</finalName>
            	</build>
            
            </project>
            

            Regards,
            Ivan

            (0) 
  4. Oliver Merk

    Hi Carlos,

    This SDK is really rocks and speeds up the implementation time for OData Services comparing to standard Olingo implemenation.

    As it is able to run as standard web application, shouldn’t it be possible to deploy it on SAP Neo as well?

    Kind regards

    Oliver

    (0) 
    1. Carlos Roggan Post author

      Hi Oliver,

      thanks for the feedback – it is good to know that it helps you and speeds up your dev.
      Please let us know if you have further questions, which help to improve the SDK

      Wrt NEO:
      Your assumption is correct, technically it should be possible.
      Furthermore, the SDK itself is standard Java API, so it should be possible to add it to an existing standard web application.

      However, it is not officially supported.

      You can contact me privately if you like.

      Kind Regards,
      Carlos

      (0) 

Leave a Reply