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
One takeaway from all the previous blogs should be that our main task is: providing data.

Any doubts?
?
Yes?
-> Then please start again from blog 1
(and let me know if I should have explained better…)

No doubts?
No?
-> Cool, then let’s continue.

 

A meaningful way of storing and providing structured data is a Map
More concrete: java.util.Map
It is perfect for storing pairs of propertyName and propertyValue
Great.
However, in addition, the SDK supports 2 additional formats:

  1. com.sap.cloud.sdk.service.prov.api.EntityData

  2. <myPackage>.MyPOJO


Until now, we’ve stored the data always in Maps, in this blog we’ll have a look into the other 2 formats.
I’ve thought of creating sample code which uses both formats and even a combination.
Ready?
No? -> Then please start from scratch at blog 1

All others: Let’s start with POJO

 

POJO


This term stands for “plain old Java object” and is used like that in our context.
It is just a simple Java class with member variables and corresponding getters/setters
As such, it is a very convenient way of storing values (member variables) and accessing them (getter methods)
The big advantage is that it is handy to use.
Intuitive.
And type safe.

Comparison:

Using Map:
String name = (String) requestMap.get("Name");

Using POJO
String name = person.getName();

As you can see, it is
easier to use,
no error-prone dealing with strings,
no cast

However, apart from such obvious advantages, there’s also a drawback:
The creation of the code: create each Java class, type all the members, all getters and all setters.
Furthermore, the names of the variables have to match EXACTLY the names of the properties defined in the edmx.
You might get old while typing POJOs…

BUT don't give up...

You cannot be surprised that there’s some magic support provided by the SDK…
The SDK, our good friend...
See here what our friend can do for us:
The SDK is able to generate the POJOs based on your OData V4 model

The POJO-generator is plugged into the maven build, I mean: while building your project with maven, the maven build can generate the POJO classes.

The generation can be activated by adding a parameter to your maven command:
-DcodeGen=true

Example:
mvn clean package -DcodeGen=true

Maybe like follows it looks more genuine:







mvn clean package -DcodeGen=true


OK, but now it’s time to get the hands dirty – not too dirty, however, because we have the POJOnator 😉
Note that this is not an official name! It’s just me who likes to tenderly call him like this...

 

Model


As usual, after creating a new Project, we create a model file (if you have doubts, you don't need to start from scratch, just have a look here).

I’ve thought of creating a model with few different artifacts, such that the POJOnator has some entertaining work to do:
<?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="PersonId" />
</Key>
<Property Name="PersonId" Type="Edm.Int32" />
<Property Name="Name" Type="Edm.String" />
<Property Name="JobId" Type="Edm.Int32" />
<Property Name="HomeAddress" Type="demo.Address" />
<NavigationProperty Name="Occupation" Type="demo.Job" Partner="Employees" />
</EntityType>

<EntityType Name="Job">
<Key>
<PropertyRef Name="JobId" />
</Key>
<Property Name="JobId" Type="Edm.Int32" />
<Property Name="JobName" Type="Edm.String" />
<NavigationProperty Name="Employees" Type="Collection(demo.Person)" Partner="Occupation" />
</EntityType>

<ComplexType Name="Address">
<Property Name="City" Type="Edm.String" MaxLength="10"/>
<Property Name="Street" Type="Edm.String" />
<Property Name="Number" Type="Edm.Int32" />
</ComplexType>

<EntityContainer Name="container">
<EntitySet Name="People" EntityType="demo.Person">
<NavigationPropertyBinding Path="Occupation" Target="Jobs" />
</EntitySet>
<EntitySet Name="Jobs" EntityType="demo.Job" >
<NavigationPropertyBinding Path="Employees" Target="People" />
</EntitySet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

 

Once you’ve created your project and added the xml file with above content, you can start your initial maven build.

E.g. like this:







mvn compile -DcodeGen=true


(This command is slightly faster, we only need it for generation of POJOs)

Once the maven build has finished, you can search your console window for the output which was written by the POJOnator



As you’ve observed, one Java class for each OData artifact has been generated.
Furthermore, these classes have been generated into a new package.
All names are derived from the model, the package name also takes the maven pom into account

You may wish to read the nice documentation for additional information.

After successful maven build , you might need to refresh the java folder in your Eclipse project to make the generated classes visible.

Once visible, in our example it looks like this:



 

For your convenience, I’ve created below screenshot, to make visible, how the Java classes are derived from the OData model.

I’ve resisted drawing arrows – you can do it yourself, as homework…



 

 

How does a POJO look like?

Let’s have a look at a POJO
For example, the PersonET.



 

What we can see:

Entity type

The name of the Java class matches the name of the entity type, with appended suffix ET
One Java member variable has been generated for each OData property
The name of each variable is exactly the same like the name of the corresponding OData property
Note that it is case-sensitive
We can see that there’s an annotation: it marks which of the member variables corresponds to the key property
The getters and setters are nicely generated as well. This is important for us, because we will be heavily using them.
The data types are generated as well according to the OData data types. You may refer to the docu for the matching data types

Entity set

The name of the POJO corresponding to the entity set has again the same name, but with appended ES. Reason is that in OData the names of entity type and entity set can be equal. Furthermore, there can be multiple entity sets pointing to the same entity type.
That’s why we need additional POJOs for entity sets. However, to avoid redundancies, the entity set POJOs are subclasses of the entity type POJOs

Complex type

A complex type is as well a data structure, so it is treated like an entity type. It doesn’t have a key annotation
The above entity type uses the complex type, so the POJO has a member variable which is of type AddressCT, which is the POJO generated for the complex type.

 
private AddressCT HomeAddress;

Isn't it nice? Just like we expected it to be.

 

Navigation property

A Java variable for navigation property can be filled in case of $expand.
The Java type for the member variable depends on the relation of the navigation property in the OData model.
In below example, we can see how the OData specific to-many-relation is reflected in Java:
private List<PersonET> Employees;

 

How can a POJO be used?

We treat the POJO just like a normal Java class:
instantiate it and use the setters to fill the POJO with data:
private AddressCT createAddress(){
AddressCT address = new AddressCT();

address.setCity("New York");
address.setStreet("5th Avenue");
address.setNumber(123);

return address;
}

private PersonET createPerson(){
AddressCT address = createAddress();

PersonET person = new PersonET();
person.setPersonId(1);
person.setName("Anna");
person.setJobId(2);
person.setHomeAddress(address);

return person;
}

Compare this code snippet with the alternative using Maps:

For each value, we have to type the (correct!) name of the property - and no POJOnator can help us here...
private Map<String, Object> createAddress(){

Map<String, Object> addressMap = new HashMap<String, Object>();
addressMap.put("City", "New York");
addressMap.put("Street", "5th Avenue");
addressMap.put("Number", 123);

return addressMap;
}

private Map<String, Object> createPerson(){
Map<String, Object> addressMap = createAddress();

Map<String, Object> personMap = new HashMap<String, Object>();
personMap.put("PersonId", 1);
personMap.put("Name", "Anna");
personMap.put("JobId", 2);
personMap.put("HomeAddress", addressMap);

return personMap;
}

 

Now let’s compare the @Query implementation:

First using List of Map and method setDataAsMap(list)
@Query(serviceName="DemoService", entity="People")
public QueryResponse getPeople(QueryRequest request) {
List<Map<String, Object>> peopleList = getAllPeople();
return QueryResponse.setSuccess().setDataAsMap(peopleList).response();
}

Now with POJO, we pass a List of POJOs and use a different setData() method
@Query(serviceName="DemoService", entity="People")
public QueryResponse getPeople(QueryRequest request) {
List<PersonET> peopleList = getAllPeople();
return QueryResponse.setSuccess().setData(peopleList).response();
}

 

I believe it is clear now, you can find the whole source code at the end of this page.
(if not clear, please start from ... and let me know...)

 

EntityData


This object can be seen like a wrapper around Map,  optimized for SDK purposes.*
In addition to the properties and values, the EntityData instance knows about the name of the entity type to which the properties belong.
And furthermore, it knows which property is the key property.

Data can be filled using fluent API:
 EntityData jobEntity = EntityData.getBuilder()
.addKeyElement("JobId", 1)
.addElement("JobName", "Architect")
.buildEntityData("Jobs");

 

Note:
Don't forget to specify the key, for that purpose use a separate method addKeyElement()

Important to remember is the name which is passed to the buildEntityData() method:
As you can see, the entity-name (“Jobs”) matches the name of the entity set, as specified in the OData model (see edmx file)

Accessing data is done by passing the (correct!) name of the property, similar like in case of Map:
jobEntity.getElementValue("JobName");

Please refer to the end of this page for the full source code.

I’m afraid that I don’t know what else should be explained about EntityData….
So even if you say that it is unfair…. I have to close this chapter - already now, so early...

 

Converting


It may happen that you have e.g. an EntityData instance, e.g. provided by whatever data source, and you want to continue with nice POJO.
Or you have a Map, but you need an EntityData, e.g. to be sent to whatever data source (see some future blog)
For such cases, there are convenience conversion methods:

 

Converting to EntityData

If you have a Map and want an EntityData:
entityData.createFromMap(map, keys, entityName)

Apart from the map, the code completion requires info about the key property(ies) and entity set name



 

If you have a POJO and want an EntityData:
entityData.createFrom(POJO, entityName)

In this case, the info about key properties is contained in the POJO, via annotation (see above)
If the POJO for any reason doesn't have any member variable with that annotation, then the EntityData will cause errors.

 

Converting from EntityData

If you have an EntityData instance and need a Map:
Map<String, Object> map = entityData.asMap();

If you have an EntityData instance and prefer a POJO:
PersonET personFromRequest = personEntityFromRequest.as(PersonET.class);

 

Example

I’ve created little example for a meaningless conversion in the @Update implementation
It doesn’t make much sense, only to showcase:

 
// retrieve from request: the data to modify
EntityData personEntityFromRequest = request.getData();
// convert EntityData to POJO
PersonET personFromRequest = personEntityFromRequest.as(PersonET.class);

 

Conclusion


Now that you know how to use all 3 formats, you’ll understand why many objects offer 3 similar methods.

For example, we can see that the QueryResponse has 3 different methods for setting data:
QueryResponse.setSuccess().setData(...)

QueryResponse.setSuccess().setDataAsMap(..)

QueryResponse.setSuccess().setEntityData(...)

The ReadResponse has 1 method name, but it is overloaded and can be used to set data in all 3 formats
Etc.

Finally, now you can go ahead and finish the example project.
Copy the source code from the end of this page into your service implementation class.
You don’t need to create the Java classes for the POJOs, because you’ve already generated them above.

Here it makes sense to add an important note:
Important Note:
Don’t create your ServiceImplementation java class into the same folder where the POJOs have been generated to.
First: that POJO-folder (which ends with “model”) will be deleted in case of re-generation. So you would lose all your code.
Drama!
Second: the framework might not find your annotated methods (@Query etc). Because the FWK searches only in the package which is mentioned in the pom and web.xml
The correct package is generated by the archetype, only there you should create your Java class(es) for service implementation.

 

This time, when using maven to build the project, it is not required to add the POJOnation command, because the POJOs are already there.

As such, mvn clean package is enough.

 

After deploy and test some URLs, you’ll see: in the result, there’s absolutely no difference between maps and POJOs.

This is of course just normal, we cannot be too proud about that

- sigh -

 

Summary


Hope you aren't disappointed, because there was no new functionality to learn in this blog, no surprises in the browser...

However, I bet you'll be heavily using the POJOs in future.

So, if anybody doesn't, please leave a comment, and if I lose my bet, then I owe anything...

 

Links


Overview of blog series and link collection.

POJOnation (officially called: POJO Generation)


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="PersonId" />
</Key>
<Property Name="PersonId" Type="Edm.Int32" />
<Property Name="Name" Type="Edm.String" />
<Property Name="JobId" Type="Edm.Int32" />
<Property Name="HomeAddress" Type="demo.Address" />
<NavigationProperty Name="Occupation" Type="demo.Job" Partner="Employees" />
</EntityType>

<EntityType Name="Job">
<Key>
<PropertyRef Name="JobId" />
</Key>
<Property Name="JobId" Type="Edm.Int32" />
<Property Name="JobName" Type="Edm.String" />
<NavigationProperty Name="Employees" Type="Collection(demo.Person)" Partner="Occupation" />
</EntityType>

<ComplexType Name="Address">
<Property Name="City" Type="Edm.String" MaxLength="10"/>
<Property Name="Street" Type="Edm.String" />
<Property Name="Number" Type="Edm.Int32" />
</ComplexType>

<EntityContainer Name="container">
<EntitySet Name="People" EntityType="demo.Person">
<NavigationPropertyBinding Path="Occupation" Target="Jobs" />
</EntitySet>
<EntitySet Name="Jobs" EntityType="demo.Job" >
<NavigationPropertyBinding Path="Employees" Target="People" />
</EntitySet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

 

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


 
package com.example.DemoProject;

import java.util.ArrayList;
import java.util.List;

import com.example.demoservice.model.AddressCT;
import com.example.demoservice.model.PersonET;
import com.sap.cloud.sdk.service.prov.api.EntityData;
import com.sap.cloud.sdk.service.prov.api.operations.Create;
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.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.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 final List<PersonET> peopleList = new ArrayList<PersonET>();
private static final List<EntityData> jobList = new ArrayList<EntityData>();


// showing POJO
@Query(serviceName="DemoService", entity="People")
public QueryResponse getPeople(QueryRequest request) {
List<PersonET> peopleList = getAllPeople();
return QueryResponse.setSuccess().setData(peopleList).response();
}

// showing EntityData
@Query(serviceName="DemoService", entity="Jobs")
public QueryResponse getJobs(QueryRequest request) {
List<EntityData> jobList = getJobs();
return QueryResponse.setSuccess().setEntityData(jobList).response();
}

// showing POJO
@Read(serviceName="DemoService", entity="People")
public ReadResponse getPerson(ReadRequest request) {
Integer id = (Integer) request.getKeys().get("PersonId");
PersonET person = findPerson(id);
return ReadResponse.setSuccess().setData(person).response();
}

// showing EntityData
@Read(serviceName="DemoService", entity="Jobs")
public ReadResponse getJob(ReadRequest request) {
Integer id = (Integer) request.getKeys().get("JobId");
EntityData jobEntity = findJob(id);
return ReadResponse.setSuccess().setData(jobEntity).response();
}

// showing POJO
@Create(serviceName = "DemoService", entity = "People")
public CreateResponse createPerson(CreateRequest request) {
PersonET personToCreate = request.getDataAs(PersonET.class);
createPerson(personToCreate);
return CreateResponse.setSuccess().response();
}

// showing EntityData
@Create(serviceName = "DemoService", entity = "Jobs")
public CreateResponse createJob(CreateRequest request) {
EntityData jobToCreate = request.getData(); // note that here, to use EntityData, we have to call getData (not getEntityData or similar)
createJob(jobToCreate);
return CreateResponse.setSuccess().response();
}

// showing conversion from EntityData to POJO
@Update(serviceName = "DemoService", entity = "People")
public UpdateResponse updatePerson(UpdateRequest request) {

// retrieve from request: which job to update
Integer id = (Integer)request.getKeys().get("PersonId");

// retrieve from request: the data to modify
EntityData personEntityFromRequest = request.getData();
// convert EntityData to POJO
PersonET personFromRequest = personEntityFromRequest.as(PersonET.class);

// retrieve from database: the person to modify
PersonET existingPerson = findPerson(id);

// do the modification
existingPerson.setName(personFromRequest.getName());
existingPerson.setJobId(personFromRequest.getJobId());
existingPerson.setHomeAddress(personFromRequest.getHomeAddress());

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


/* NAVIGATION */

// showing both EntityData and POJO
@Read(serviceName = "DemoService", entity = "Jobs", sourceEntity = "People")
public ReadResponse getJobForPerson(ReadRequest request) {
Integer personId = (Integer) request.getSourceKeys().get("PersonId");
PersonET person = findPerson(personId);
Integer jobId = person.getJobId();
EntityData jobEntity = findJob(jobId);

return ReadResponse.setSuccess().setData(jobEntity).response();
}

// showing POJO
@Query(serviceName = "DemoService", entity = "People", sourceEntity = "Jobs")
public QueryResponse getPeopleForJob(QueryRequest request) {
List<PersonET> responsePeopleList = new ArrayList<PersonET>();

Integer jobId = (Integer) request.getSourceKeys().get("JobId");

List<PersonET> allPeopleList = getAllPeople();
for(PersonET person : allPeopleList) {
if(((Integer)person.getJobId()).equals(jobId)) {
responsePeopleList.add(person);
}
}

return QueryResponse.setSuccess().setData(responsePeopleList).response();
}


/* DUMMY DATABASE */


/* Person with format POJO */

private List<PersonET> getAllPeople(){
if(peopleList.isEmpty()) {
createPerson(1, "Anna", 1, createAddress("New York", "5th Avenue", 122));
createPerson(2, "Berta", 1, createAddress("Mami", "Beach Avenue", 45));
createPerson(3, "Claudia", 2, createAddress("Las Vegas", "Hopeful Avenue", 1));
createPerson(4, "Debbie", 2, createAddress("New York", "6th Avenue", 133));
}

return peopleList;
}

private PersonET createPerson(Integer personId, String name, Integer jobId, AddressCT address){
PersonET person = new PersonET();

person.setPersonId(personId);
person.setName(name);
person.setJobId(jobId);
person.setHomeAddress(address);

return createPerson(person);
}

private PersonET createPerson(PersonET person) {
peopleList.add(person);
return person;
}

private AddressCT createAddress(String cityName, String streetName, Integer number){
AddressCT address = new AddressCT();

address.setCity(cityName);
address.setStreet(streetName);
address.setNumber(number);

return address;
}

private PersonET findPerson(Integer requiredPersonId){
List<PersonET> peopleList = getAllPeople();
for(PersonET person : peopleList) {
if(person.getPersonId().equals(requiredPersonId)) {
return person;
}
}
return null;
}


/* Job with format EntityData */

private List<EntityData> getJobs(){
if(jobList.isEmpty()) {
createJob(1, "Software Engineer");
createJob(2, "Musician");
createJob(3, "Architect");
}
return jobList;
}

private EntityData createJob(Integer jobId, String name){
EntityData jobEntity = EntityData.getBuilder()
.addKeyElement("JobId", jobId)
.addElement("JobName", name)
.buildEntityData("Jobs"); // give the name of the entity set as defined in edmx

return createJob(jobEntity);
}

private EntityData createJob(EntityData jobEntity) {
jobList.add(jobEntity);
return jobEntity;
}

private EntityData findJob(Integer requiredJobId){
List<EntityData> jobList = getJobs();
for(EntityData jobEntity : jobList) {
if(((Integer)jobEntity.getElementValue("JobId")).equals(requiredJobId)) {
return jobEntity;
}
}
return null;
}
}

 

Appendix 3: Generated POJO for Person


Since the POJOs are anyways generated, we don’t need to paste all the source code here.
This is just an example to show how it looks like.
/*****************************************************************************************************
* This is an auto-generated POJO source code for Entity Type "Person" as
* defined in "DemoService" metadata file.
*
*****************************************************************************************************/
package com.example.demoservice.model;

import java.util.*;
import java.math.*;
import java.sql.Timestamp;
import com.sap.cloud.sdk.service.prov.api.annotations.Key;


public class PersonET {

@Key
private Integer PersonId;
private String Name;
private Integer JobId;
private AddressCT HomeAddress;
private JobET Occupation;

public PersonET() {

}

public Integer getPersonId() {
return PersonId;
}

public void setPersonId(Integer personid) {
PersonId = personid;
}

public String getName() {
return Name;
}

public void setName(String name) {
Name = name;
}

public Integer getJobId() {
return JobId;
}

public void setJobId(Integer jobid) {
JobId = jobid;
}

public AddressCT getHomeAddress() {
return HomeAddress;
}

public void setHomeAddress(AddressCT homeaddress) {
HomeAddress = homeaddress;
}


public JobET getOccupation() {
return Occupation;
}

public void setOccupation(JobET occupation) {
Occupation = occupation;
}

}

3 Comments