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
Hello.

Today is a good day to be creative and add the CREATE capability to our OData service.

New here? Then please check here to understand what is all about.

 
Note:
If you don't like reading, just scroll down to the end of this page to view the full source code.

 

Intro


We're going to create an OData V4 service in Java.
And we're going to use SAP Cloud Platform SDK for service development.

Find an introduction here and an overview of the series of blogs here.

 
Note:
If you don't like reading, just scroll down to the end of this page to view the full source code.

 

 

Learning


Implement CREATE operation.

Implement UPDATE operation.

Implement DELETE operation.

 

Project


You may refer to this blog for detailed information about project creation and to this blog for the prerequisites.

 

Model


This blog doesn’t need to enhance the model. The model remains the same, it has one entity type and one entity set.
What we’re going to enhance is: the functionality offered by our service.
With other words: the possibilities of interaction with the data.
The data is structured according to the definition in the model.
There’s a person, or even people, we want to get the list, or a single person, we want to modify the attributes of a person and we want to create and delete a person.

Sounds too much?

It isn’t…

 

Code: QUERY and READ


I think it makes sense to include these 2 already implemented methods, just to allow for better testing at the end.

There are small differences to the previous blog:
We need to store our dummy database as static member, because we want to create an entry and access it afterwards.
For this blog, I've removed the error handling from the READ implementation, to make the overall code shorter.

Please refer to the end of this page to view the full source code

 

Code: CREATE


In order to create an entry, the user of our service has to fire a POST request to the entity set URL:

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

The POST request needs to include a request body.
And the request body needs to contain the data of the entry to be created.
Furthermore, the POST request needs to specify a header, to inform the server about the format of the data.
We’ll come to that part later

I've mentioned that the user sends the data for the new “Person” in the body of the POST request.
There, it will be in json format.

But how will it reach us, the service implementors, in our code?
Since you’re already experienced with The SDK, you expect that we can ask the instance of request  parameter for that information.
And you also expect that the format of the data is just the same like the format of the data that we provide in a READ implementation.

Yes, you’re correct:
Map<String, Object> requestBody = request.getMapData();

Once we have the data as a map, we can extract the values from the map.
Don't forget, we have to specify the correct names of the properties.
And again, we know the defined data types, so we can safely do a proper cast.
Integer idToCreate = (Integer) requestBody.get("UniqueId");  
String nameToCreate = (String) requestBody.get("Name");

Afterwards, we do the actual creation in the database with the actual values, extracted from the request body.

 
createPerson(idToCreate, nameToCreate);

Note:
Of course, you know, when I say database, I mean any data store or any back end that you have in your individual enterprise scenario.

In our example, we just call a little helper method, “createPerson”.
It will create a new entry which is stored in a static member variable (see complete code at the end of this blog)

Finally, create the response object:
return CreateResponse.setSuccess().response();  

Note:
For CREATE operation, the specification says that the created entity should be returned in the response of the POST request (as per default. We’ll discuss this in a future blog).
Furthermore, the status code is 201 (Created) upon successful completion

As you expect, the framework takes care of building the response, e.g. sending the proper status code.

The complete method implementation:
@Create(serviceName = "DemoService", entity = "People")
public CreateResponse createPerson(CreateRequest request) {

// extract the data for creation
Map<String, Object> requestBody = request.getMapData();
Integer idToCreate = (Integer) requestBody.get("UniqueId"); // we know it is an int, because we know the edmx
String nameToCreate = (String) requestBody.get("Name"); // same here

// do the actual creation in database
createPerson(idToCreate, nameToCreate);

return CreateResponse.setSuccess().response();
}

 
Note:
Of course, code is simplified, e.g. we're skipping the error handling, to make the code as simple as possible.
Ideally, we would e.g. check if the  request body is valid, if the person already exists, etc

 

Let’s proceed.

We’ll test all operations at the end.

 

Code: UPDATE


If the user wants to modify existing data, i.e. UPDATE one existing entry, then he sends a PUT (or PATCH) request to our OData service (that's common for RESTful services).
The URL points to a single resource which needs to be modified:

e.g. https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People(2)

 

The PUT request has a request body which contains the data to be changed.
And also the header is necessary, to tell about the format of the content (is it json or xml)

The implementation steps are similar:

 

1) Get the required information from request:

First we need to know which entry should be modified. This info is part of the request URL:
Integer idForUpdate = (Integer)request.getKeys().get("UniqueId");

Then we need to know which data should be modified. This info is in the request body:
Map<String, Object> requestBody = request.getMapData();

 

2) Modify the actual data in the database

First we need to fetch the person from the database, based on the id which we already have extracted from the URI:
Map<String, Object> personMap = findPerson(idForUpdate);

Then we have to do the modification, based on the request body, which we have as map
Note:
Even if the user passes the key property in the request body: we cannot change it. The key field in the database cannot be modified, to ensure consistency/integrity of the database.

Thus, we only replace the name of the person:
personMap.replace("Name", requestBody.get("Name")); 

 
Note:
It would really really be better to check if the requested person exists at all...
You know how to do handle it, we've learned it in the previous blog, so we skip it here.

 

3) Configure the response

As we know from the specification, the PUT request doesn’t have data in the response (usually) and the status code is 204 (No Content).
As such, we don't need to pass any data to the response object.
return UpdateResponse.setSuccess().response();

The complete code:
@Update(serviceName = "DemoService", entity = "People")
public UpdateResponse updatePerson(UpdateRequest request) {

// retrieve from request: which person to update
Integer idForUpdate = (Integer)request.getKeys().get("UniqueId");

// retrieve from request: the data to modify
Map<String, Object> requestBody = request.getMapData();

// retrieve from database: the person to modify
Map<String, Object> personMap = findPerson(idForUpdate);

// do the modification, but ignore key property (cannot be modified, to keep data consistent)
personMap.replace("Name", requestBody.get("Name"));

return UpdateResponse.setSuccess().response();
}

 

 

Code: DELETE


In this case, the user sends  a DELETE request and the URL points to a single resource which needs to be deleted

e.g. https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People(2)

Nothing else.

And the code without error handling and without further explanation:
@Delete(serviceName = "DemoService", entity = "People")
public DeleteResponse deletePerson(DeleteRequest request) {
Integer id = (Integer)request.getKeys().get("UniqueId");

//find and remove the requested person
List<Map<String,Object>> peopleList = getPeople();
for (Iterator<Map<String, Object>> iterator = peopleList.iterator(); iterator.hasNext();) {
Map<String, Object> personMap = iterator.next();
if(((Integer)personMap.get("UniqueId")).equals(id)) {
iterator.remove();
break;
}
}

return DeleteResponse.setSuccess().response();
}

 
Note:
No notes this time…

 

Run


After build and deploy, open your preferred browser...
Sorry, my mistake.
This time we need a REST client, because for CREATE we need to specify not only the URL, but also the request body and request header.

Example for REST client could be “Postman”, but any other tool can be used.

So let me start again:

After build and deploy, open your preferred REST client.

 

Test the CREATE

1) Invoke the QUERY operation

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

2) Choose one entry to do a READ

E.g. https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People(2)

3) Now, copy the json content of the single entry into your clipboard:



 

4) Change the HTTP verb of the REST client to POST
And change the URL such that it points to the entity set (like for QUERY)
E.g. https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People

5) Enter the request body:
Click on the “Body” tab to paste the content of the clipboard
Remove the @odata.context line
Change the values, such that they’re new

E.g.
{
"UniqueId": 4,
"Name": "Eric"
}

 
Note:
The value for UniqueId must be a number that doesn't exist in the People collection.
Remember, we don’t have a validation in our service implementation, neither we have an automatically generated UniqueId

 

6) Enter the request header:
name: Content-Type 
value: application/json

Why?

Because the content which we’ve pasted into the request body field is of format json
If we would use xml as format, then we would have to change the content-type accordingly
Note:
The OData V4 library only supports json (currently) because the payload overhead is much smaller, so performance is better

7) Run
That’s it for configuring the request.
Now press the “Send” button to execute the request.

As a response, you get the newly created entry (just like specified in the request body) and a status code as 201

Success!!!!

The following screenshot is full of red markers, means contains lot of interesting details:


Note:
If you're wondering, why do we get a response where the content is exactly what we've sent?
What is the benefit of this information?
Answer is, because in many cases, some values are generated by the back end, so the server has to inform the client about those values and also, how the new entry can be addressed (this info is in response header)

8) Check
Now let’s double-check if the creation has been really successful:
We invoke the QUERY again.
E.g. https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People
And in fact, the newly created person is there.

 

Test the UPDATE

Invoke the QUERY operation, then READ operation for any of the entries, e.g. our newly created Eric.
E.g. https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People(4)
Again, copy the response payload into clipboard
Change the HTTP verb from GET to PUT
Paste the clipboard content into the request body field
Modify the name (plus delete the @context line):
e.g. because we’ve learned that Eric has to be spelled as Erik
Make sure that the content-type header is set
Fire the call and be happy with the success result: 204 No Content, no response payload as per default.

And the screenshot - no red boxes this time, you’ll find the interesting parts anyway …



 

Again, in order to double-check you can simply change the HTTP verb from PUT to GET and fire the request. In the response payload you can see the modified name.

 

Test the DELETE

That’s easy:
Don’t change the URL, it is still pointing to the single resource which we’ve just created and modified

E.g. https://<yourApp>.cfapps.sap.hana.ondemand.com/odata/v4/DemoService/People(4)

This is the entity which we want to delete.
Now change the HTTP verb to DELETE and fire the request.
Result:
Empty response body and a success status code, as can be seen here:



In order to double-check we can just fire a GET request to the same URL.
As expected, we get an error, because the person doesn’t exist.
Obviously, because we’ve just deleted it.

Ehmmm, since we’re talking about a person, I would prefer to say:
We’ve just withdrawn that person from our list.
Or maybe even better, we’ve found new opportunities for this person outside of this list…

Whatever.

Anyways, just kidding.

Anyways, we’re now through and it is enough for today.

Good bye 😉

 

Summary


After going through the previous blog and this blog, you've learned how to implement a basic OData service.
Your service supports QUERY, READ, CREATE, UPDATE, DELETE

With other words, your service supports CRUDQ

The upcoming blog will be going more into details.

 

Links


Overview of blog series and link collection.

 

Appendix 1: 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="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>

Appendix 2: Source code of Java class file: ServiceImplementation.java


package com.example.DemoService;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.cloud.sdk.service.prov.api.operations.Create;
import com.sap.cloud.sdk.service.prov.api.operations.Delete;
import com.sap.cloud.sdk.service.prov.api.operations.Query;
import com.sap.cloud.sdk.service.prov.api.operations.Read;
import com.sap.cloud.sdk.service.prov.api.operations.Update;
import com.sap.cloud.sdk.service.prov.api.request.CreateRequest;
import com.sap.cloud.sdk.service.prov.api.request.DeleteRequest;
import com.sap.cloud.sdk.service.prov.api.request.QueryRequest;
import com.sap.cloud.sdk.service.prov.api.request.ReadRequest;
import com.sap.cloud.sdk.service.prov.api.request.UpdateRequest;
import com.sap.cloud.sdk.service.prov.api.response.CreateResponse;
import com.sap.cloud.sdk.service.prov.api.response.DeleteResponse;
import com.sap.cloud.sdk.service.prov.api.response.ErrorResponse;
import com.sap.cloud.sdk.service.prov.api.response.QueryResponse;
import com.sap.cloud.sdk.service.prov.api.response.ReadResponse;
import com.sap.cloud.sdk.service.prov.api.response.UpdateResponse;

public class ServiceImplementation {


private static List<Map<String, Object>> peopleList = null;


@Query(serviceName="DemoService", entity="People")
public QueryResponse getPeople(QueryRequest request) {
List<Map<String, Object>> peopleMap = getPeople();
return QueryResponse.setSuccess().setDataAsMap(peopleMap).response();
}

@Read(serviceName="DemoService", entity="People")
public ReadResponse getPerson(ReadRequest request) {
Integer id = (Integer)request.getKeys().get("UniqueId");
Map<String, Object> personMap = findPerson(id);
return ReadResponse.setSuccess().setData(personMap).response();
}



@Create(serviceName = "DemoService", entity = "People")
public CreateResponse createPerson(CreateRequest request) {

// extract the data for creation
Map<String, Object> requestBody = request.getMapData();
Integer idToCreate = (Integer) requestBody.get("UniqueId"); // we know it is an int, because we know the edmx
String nameToCreate = (String) requestBody.get("Name"); // same here

// do the actual creation in database
createPerson(idToCreate, nameToCreate);

return CreateResponse.setSuccess().response();
}



@Update(serviceName = "DemoService", entity = "People")
public UpdateResponse updatePerson(UpdateRequest request) {

// retrieve from request: which person to update
Integer idForUpdate = (Integer)request.getKeys().get("UniqueId");

// retrieve from request: the data to modify
Map<String, Object> requestBody = request.getMapData();

// retrieve from database: the person to modify
Map<String, Object> personMap = findPerson(idForUpdate);

// do the modification, but ignore key property (cannot be modified, to keep data consistent)
personMap.replace("Name", requestBody.get("Name"));

return UpdateResponse.setSuccess().response();
}


@Delete(serviceName = "DemoService", entity = "People")
public DeleteResponse deletePerson(DeleteRequest request) {
Integer id = (Integer)request.getKeys().get("UniqueId");

//find and remove the requested person
List<Map<String,Object>> peopleList = getPeople();
for (Iterator<Map<String, Object>> iterator = peopleList.iterator(); iterator.hasNext();) {
Map<String, Object> personMap = iterator.next();
if(((Integer)personMap.get("UniqueId")).equals(id)) {
iterator.remove();
break;
}
}

return DeleteResponse.setSuccess().response();
}



/* Dummy Data Store */


private List<Map<String, Object>> getPeople(){

// init the "database"
if(peopleList == null) {
peopleList = new ArrayList<Map<String, Object>>();
createPerson(0, "Anna");
createPerson(1, "Berta");
createPerson(2, "Claudia");
createPerson(3, "Debbie");
}

return peopleList;
}

private void createPerson(Integer id, String name){
Map<String, Object> personMap = new HashMap<String, Object>();
personMap.put("UniqueId", id);
personMap.put("Name", name);

peopleList.add(personMap);
}

private Map<String, Object> findPerson(Integer requiredPersonId){
List<Map<String,Object>> peopleList = getPeople();
for(Map<String, Object> personMap : peopleList) {
if(((Integer)personMap.get("UniqueId")).equals(requiredPersonId)) {
return personMap;
}
}
return null;
}
}
2 Comments