Skip to Content
Technical Articles
Author's profile photo Edrilan Berisha

Building an OData Service with a Spring-Boot Java Application using Olingo – Part III (Payloads and Custom Logic)

It’s time to come to the third and final part of my blog. In the last blog post, we implemented our OData service with help of the olingo library. We were able to fetch the metadata from our service or call our REST health check endpoint to see if our service is still up and running.

In this blog post I will show how you are able to perform a classic OData deep insert. But also show how filters and different OData requests are executed correctly.

Let’s call our Mothers entity set by calling the endpoint: localhost:8080/odata/Mothers

 

Call%20Mothers%20Entity%20Set

Call Mothers Entity Set

 

You will receive an empty response with an 200 HTTP code. So we have not created yet any mother. So let’s call a single entity and try to see if there is a mother with the ID ‘1’.

Call%20Mothers%281%29%20Entity

Call Mothers(1) Entity

 

We get this time an error message, saying the requested entity couldn’t be found, with an 404 HTTP code. So Olingo already also handles the error case with standard error handling, all of this out of the box without further coding from us.

So let’s create our first entity of mother by changing the action to POST and sending the following payload:

{
    "Name": "Liz"
}

Post%20Mother%281%29%20Entity

Create Mother(1) Entity

 

As you can see the new entity got an ID assigned without us defining it in the payload. Which is done because we set the annotations in our entity class.

If we want to delete this entity, we can do so by changing the HTTP method to DELETE

Delete%20Mother%281%29%20Entity

Delete Mother(1) Entity

 

The response 204 No Content, means we have successfully deleted the mother with ID ‘1’.

Now we will do a deep insert on our ‘Childs’ entity set. The payload for that looks like this:

{
"Name": "Sarah",
 "FatherDetails":[
        {  
          "Name": "Alex"
        }
      ],
    "MotherDetails":[
        {   
          "Name": "Anna"
        }
      ]
}

 

We call the endpoint localhost:8080/odata/Childs 

Deep%20Insert%20on%20Child%20Entity%20Set

Deep Insert on Child Entity Set

 

The result will show, that also two other entities were created. One ‘Mother’ entity with ID 2 and a  ‘Father’ entity with ID ‘1’.

Result%20of%20Deep%20Insert%20on%20Child%20Entity%20Set

Result of Deep Insert on Child Entity Set

 

We can now also call the localhost:8080/odata/Childs(FatherId=1, MotherId=2) entity

Get%20Child%28FatherId%3D1%2C%20MotherId%3D2%29%20Entity

Get Child(FatherId=1, MotherId=2) Entity

 

Updating does also work with the PUT action, calling the same endpoint localhost:8080/odata/Childs(FatherId=1, MotherId=2)

{
    "Name": "Lena"
}

Put%20Child%28FatherId%3D1%2C%20MotherId%3D1%29

Put Child(FatherId=1, MotherId=2)

 

Fetching the ‘Father’ entity through our ‘Child’ is also possible by calling the endpoint localhost:8080/odata/Childs(FatherId=1, MotherId=2)?$expand=FatherDetails

FathersDetails%20on%20Child%28FatherId%3D1%2C%20MotherId%3D2%29%20Entity

FathersDetails on Child(FatherId=1, MotherId=2) Entity

 

 

Let’s add now some custom logic for the ‘Child’ entity. For that we will extend all three of our entity classes, Mother, Father and Child. By adding another field to those entities ‘Surname’. We will also need getter and setter methods for this field, so we add this as well to all three classes.

 

Mother.java will look like this:

package com.github.olingo.example.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Objects;

@Entity
@Table(name = "MOTHER")
public class Mother {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String surname;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Mother mother = (Mother) o;
        return Objects.equals(id, mother.id) &&
                Objects.equals(name, mother.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }
}

 

Father.java:

package com.github.olingo.example.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Objects;

@Entity
@Table(name = "FATHER")
public class Father {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String surname;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Father father = (Father) o;
        return Objects.equals(id, father.id) &&
                Objects.equals(name, father.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }
}

 

Child.java:

package com.github.olingo.example.entity;

import javax.persistence.*;
import java.util.Objects;

@Entity
@Table(name = "CHILD")
public class Child {
    @EmbeddedId
    private ChildPK childPK = new ChildPK();

    @ManyToOne(cascade = CascadeType.PERSIST)
    @MapsId("fatherId")
    private Father father;

    @ManyToOne(cascade = CascadeType.PERSIST)
    @MapsId("motherId")
    private Mother mother;

    private String name;

    private String surname;

    public Child() {
    }

    public Child(Father father, Mother mother) {
        this.childPK = new ChildPK(father, mother);
    }

    public ChildPK getChildPK() {
        return childPK;
    }

    public void setChildPK(ChildPK childPK) {
        this.childPK = childPK;
    }

    public Father getFather() {
        return father;
    }

    public void setFather(Father father) {
        this.father = father;
    }

    public Mother getMother() {
        return mother;
    }

    public void setMother(Mother mother) {
        this.mother = mother;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Child child = (Child) o;
        return Objects.equals(childPK, child.childPK) &&
                Objects.equals(father, child.father) &&
                Objects.equals(mother, child.mother) &&
                Objects.equals(name, child.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(childPK, father, mother, name);
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }
}

 

Now we will add the custom logic in our CustomODataJpaProcessor. Our custom logic should do the following, if someone created a ‘Child’ without a surname, we check if the Mother was created with a surname, if that is true, we take the mother’s surname for the ‘Child’. If that is not provided by the payload, we check on the surname of the ‘Father’ entity and as our last fallback if that is not provided either, we use a default surname ‘Gashi’ in our example. The coding we have to add will be added in the createEntity method of our CustomODataJpaProcessor. We will create a new method which we call ‘postProcessCreateChild’.

private ODataResponse postProcessCreateChild(Object createdEntity, PostUriInfo uriParserResultView, String contentType) throws ODataJPARuntimeException, ODataNotFoundException {
        Child child = (Child) createdEntity;
        if (child.getSurname() == null || child.getSurname().equalsIgnoreCase("")) {
            if (child.getMother().getSurname() != null && !child.getMother().getSurname().equalsIgnoreCase("")) {
                child.setSurname(child.getMother().getSurname());
            } else if (child.getMother().getSurname() != null && !child.getFather().getSurname().equalsIgnoreCase("")) {
                child.setSurname(child.getFather().getSurname());
            } else {
                child.setSurname("Gashi");
            }
        }
        return responseBuilder.build(uriParserResultView, createdEntity, contentType);
    }

 

Though we want to call this method only for those incoming requests where the user creates a ‘Child’.

@Override
    public ODataResponse createEntity(final PostUriInfo uriParserResultView, final InputStream content, final String requestContentType, final String contentType) throws ODataJPAModelException, ODataJPARuntimeException, ODataNotFoundException, EdmException, EntityProviderException {
        logger.info("POST: Entity {} called", uriParserResultView.getTargetEntitySet().getName());
        ODataResponse response = null;
        try {
            Object createdEntity = jpaProcessor.process(uriParserResultView, content, requestContentType);
            if (createdEntity.getClass().equals(Child.class)) {
                response = postProcessCreateChild(createdEntity, uriParserResultView, contentType);
            } else {
                response = responseBuilder.build(uriParserResultView, createdEntity, contentType);
            }
        } finally {
            this.close();
        }
        return response;
    }

 

Now we can build and run our application again with ‘mvn spring-boot:run’. By calling the endpoint localhost:8080/odata/Childs with a POST request, using the following payload:

{
"Name": "Sarah",
 "FatherDetails":[
        {  
          "Name": "Alex",
          "Surname": "Miller"
        }
      ],
    "MotherDetails":[
        {   
          "Name": "Anna",
          "Surname": "Berisha"
        }
      ]
}

 

We see in the response of our request, that a ‘Child’ with the name ‘Sarah’ and surname ‘Berisha’ was created.

Create%20Child%20Entity%20with%20Custom%20Logic

Create Child Entity with Custom Logic

 

To confirm this, we also can call again on the endpoint localhost:8080/odata/Childs(FatherId=1,MotherId=2)

Read%20newly%20created%20Child%20Entity%20with%20Custom%20Logic

Read newly created Child Entity with Custom Logic

 

In case you are facing issues with your project and you cannot figure out why, feel free to clone the solution from this branch here. So with this, I will close this blog series here. I hope the blog posts did help you and will be a good starting point for your spring-boot application to build an OData service as well. If so feel free to share it with other developers. Thanks for reading!

 

Want to have a look on the other blog post of this series?

 

Best,

Edrilan Berisha

SAP S/4HANA Financials Development

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.