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
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’.
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"
}
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
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
The result will show, that also two other entities were created. One ‘Mother’ entity with ID 2 and a ‘Father’ entity with ID ‘1’.
We can now also call the localhost:8080/odata/Childs(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"
}
Fetching the ‘Father’ entity through our ‘Child’ is also possible by calling the endpoint localhost:8080/odata/Childs(FatherId=1, MotherId=2)?$expand=FatherDetails
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.
To confirm this, we also can call again on the endpoint localhost:8080/odata/Childs(FatherId=1,MotherId=2)
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!
The link to the Github Repository can be found here.
Want to have a look on the other blog post of this series?
- Building an OData Service with a Spring-Boot Java Application using Olingo – Part I
- Building an OData Service with a Spring-Boot Java Application using Olingo – Part II